Toolshed part 2 (#18997)

* fixe

* Save work.

* Rune-aware parser.

* oogh

* pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests pass tests

* Publicizes a lot of common generic commands, so custom toolshed envs can include them.

* i think i might implode

* Tests.

* a

* b

* awa

---------

Co-authored-by: moonheart08 <moonheart08@users.noreply.github.com>
This commit is contained in:
Moony
2023-08-23 16:03:41 -05:00
committed by GitHub
parent 852b66dee6
commit 3d3c9e3348
20 changed files with 349 additions and 162 deletions

View File

@@ -13,8 +13,7 @@ public sealed class AdminTest : ToolshedTest
await Server.WaitAssertion(() =>
{
Assert.That(InvokeCommand("cmd:list where { acmd:perms isnull }", out var res));
var list = ((IEnumerable<CommandSpec>) res).ToList();
Assert.That(list, Is.Empty, "All commands must have admin permissions set up.");
Assert.That((IEnumerable<CommandSpec>) res, Is.Empty, "All commands must have admin permissions set up.");
});
}
}

View File

@@ -1,84 +0,0 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.Syntax;
using Robust.Shared.Toolshed.TypeParsers;
namespace Content.IntegrationTests.Tests.Toolshed;
[TestFixture]
public sealed class CommandRunTest : ToolshedTest
{
[Test]
public async Task SimpleCommandRun()
{
await Server.WaitAssertion(() =>
{
ParseCommand("entities");
ParseCommand("entities select 1");
ParseCommand("entities with Item select 1");
ExpectError<OutOfInputError>();
ParseCommand("entities with");
ExpectError<NoImplementationError>();
ParseCommand("player:list with MetaData");
ExpectError<ExpressionOfWrongType>();
ParseCommand("player:list", expectedType: typeof(IEnumerable<EntityUid>));
ParseCommand("entities not with MetaData");
ParseCommand("with MetaData select 2 any", inputType: typeof(List<EntityUid>));
ParseCommand("entities not with MetaData => $myEntities");
ParseCommand("=> $fooBar with MetaData", inputType: typeof(List<EntityUid>));
});
}
[Test]
public async Task EntityUidTypeParser()
{
await Server.WaitAssertion(() =>
{
ParseCommand("ent 1");
ParseCommand("ent c1");
ExpectError<InvalidEntityUid>();
ParseCommand("ent foodigity");
});
}
[Test]
public async Task QuantityTypeParser()
{
await Server.WaitAssertion(() =>
{
ParseCommand("entities select 100");
ParseCommand("entities select 50%");
ExpectError<InvalidQuantity>();
ParseCommand("entities select -1");
ExpectError<InvalidQuantity>();
ParseCommand("entities select 200%");
ExpectError<InvalidQuantity>();
ParseCommand("entities select hotdog");
});
}
[Test]
public async Task ComponentTypeParser()
{
await Server.WaitAssertion(() =>
{
ParseCommand("entities with MetaData");
ExpectError<UnknownComponentError>();
ParseCommand("entities with Foodiddy");
ExpectError<UnknownComponentError>();
ParseCommand("entities with MetaDataComponent");
});
}
}

View File

@@ -1,9 +1,13 @@
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Toolshed;
namespace Content.IntegrationTests.Tests.Toolshed;
// this is an EXACT DUPLICATE of LocTest from robust. If you modify this, modify that too.
// Anyone who fails to heed these instructions consents to being scrungled to death.
[TestFixture]
public sealed class LocTest : ToolshedTest
{
@@ -12,8 +16,10 @@ public sealed class LocTest : ToolshedTest
{
await Server.WaitAssertion(() =>
{
IoCManager.Resolve<ILocalizationManager>().LoadCulture(new CultureInfo("en-US"));
Assert.That(InvokeCommand("cmd:list where { cmd:descloc loc:tryloc isnull }", out var res));
Assert.That((IEnumerable<CommandSpec>)res, Is.Empty, "All commands must have localized descriptions.");
Assert.That((IEnumerable<CommandSpec>)res!, Is.Empty, "All commands must have localized descriptions.");
});
}
}

View File

@@ -22,10 +22,12 @@ public abstract class ToolshedTest : IInvocationContext
protected RobustIntegrationTest.ServerIntegrationInstance Server = default!;
protected RobustIntegrationTest.ClientIntegrationInstance? Client = null;
protected ToolshedManager Toolshed = default!;
public ToolshedManager Toolshed { get; private set; } = default!;
public ToolshedEnvironment Environment => Toolshed.DefaultEnvironment;
protected IAdminManager AdminManager = default!;
protected IInvocationContext? Context = null;
protected IInvocationContext? InvocationContext = null;
[TearDown]
public async Task TearDownInternal()
@@ -44,11 +46,11 @@ public abstract class ToolshedTest : IInvocationContext
public virtual async Task Setup()
{
PairTracker = await PoolManager.GetServerClient(new PoolSettings {Connected = Connected});
Server = PairTracker.Pair.Server;
Server = PairTracker.Server;
if (Connected)
{
Client = PairTracker.Pair.Client;
Client = PairTracker.Client;
await Client.WaitIdleAsync();
}
@@ -63,10 +65,17 @@ public abstract class ToolshedTest : IInvocationContext
return Toolshed.InvokeCommand(this, command, null, out result);
}
protected T InvokeCommand<T>(string command)
{
InvokeCommand(command, out var res);
Assert.That(res, Is.AssignableTo<T>());
return (T) res!;
}
protected void ParseCommand(string command, Type? inputType = null, Type? expectedType = null, bool once = false)
{
var parser = new ForwardParser(command, Toolshed);
var success = CommandRun.TryParse(false, false, parser, inputType, expectedType, once, out _, out _, out var error);
var parser = new ParserContext(command, Toolshed);
var success = CommandRun.TryParse(false, parser, inputType, expectedType, once, out _, out _, out var error);
if (error is not null)
ReportError(error);
@@ -77,9 +86,9 @@ public abstract class ToolshedTest : IInvocationContext
public bool CheckInvokable(CommandSpec command, out IConError? error)
{
if (Context is not null)
if (InvocationContext is not null)
{
return Context.CheckInvokable(command, out error);
return InvocationContext.CheckInvokable(command, out error);
}
error = null;
@@ -92,9 +101,9 @@ public abstract class ToolshedTest : IInvocationContext
{
get
{
if (Context is not null)
if (InvocationContext is not null)
{
return Context.Session;
return InvocationContext.Session;
}
return InvocationSession;

View File

@@ -1,47 +0,0 @@
using Content.Server.Polymorph.Components;
using Content.Server.Polymorph.Systems;
using Content.Shared.Administration;
using Content.Shared.Polymorph;
using Robust.Shared.Console;
using Robust.Shared.Prototypes;
namespace Content.Server.Administration.Commands;
[AdminCommand(AdminFlags.Fun)]
public sealed class PolymorphCommand : IConsoleCommand
{
public string Command => "polymorph";
public string Description => Loc.GetString("polymorph-command-description");
public string Help => Loc.GetString("polymorph-command-help-text");
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 2)
{
shell.WriteError(Loc.GetString("shell-wrong-arguments-number"));
return;
}
if (!EntityUid.TryParse(args[0], out var entityUid))
{
shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number"));
return;
}
var protoManager = IoCManager.Resolve<IPrototypeManager>();
if (!protoManager.TryIndex<PolymorphPrototype>(args[1], out var polyproto))
{
shell.WriteError(Loc.GetString("polymorph-not-valid-prototype-error"));
return;
}
var entityManager = IoCManager.Resolve<IEntityManager>();
var polySystem = entityManager.EntitySysManager.GetEntitySystem<PolymorphSystem>();
entityManager.EnsureComponent<PolymorphableComponent>(entityUid);
polySystem.PolymorphEntity(entityUid, polyproto);
}
}

View File

@@ -203,7 +203,7 @@ namespace Content.Server.Administration.Managers
}
}
foreach (var spec in _toolshed.AllCommands())
foreach (var spec in _toolshed.DefaultEnvironment.AllCommands())
{
var (isAvail, flagsReq) = GetRequiredFlag(spec.Cmd);

View File

@@ -221,7 +221,7 @@ namespace Content.Server.Administration.Systems
var player = actor.PlayerSession;
// Delete verb
if (_toolshed.ActivePermissionController?.CheckInvokable(new CommandSpec(_toolshed.GetCommand("delete"), null), player, out _) ?? false)
if (_toolshed.ActivePermissionController?.CheckInvokable(new CommandSpec(_toolshed.DefaultEnvironment.GetCommand("delete"), null), player, out _) ?? false)
{
Verb verb = new()
{
@@ -236,7 +236,7 @@ namespace Content.Server.Administration.Systems
}
// Rejuvenate verb
if (_toolshed.ActivePermissionController?.CheckInvokable(new CommandSpec(_toolshed.GetCommand("rejuvenate"), null), player, out _) ?? false)
if (_toolshed.ActivePermissionController?.CheckInvokable(new CommandSpec(_toolshed.DefaultEnvironment.GetCommand("rejuvenate"), null), player, out _) ?? false)
{
Verb verb = new()
{
@@ -250,7 +250,7 @@ namespace Content.Server.Administration.Systems
}
// Control mob verb
if (_groupController.CanCommand(player, "controlmob") &&
if (_toolshed.ActivePermissionController?.CheckInvokable(new CommandSpec(_toolshed.DefaultEnvironment.GetCommand("mind"), "control"), player, out _) ?? false &&
args.User != args.Target)
{
Verb verb = new()

View File

@@ -1,13 +1,15 @@
using Content.Server.Administration.Systems;
using Content.Shared.Administration;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.Errors;
namespace Content.Server.Administration.Toolshed;
[ToolshedCommand, AdminCommand(AdminFlags.Admin)]
[ToolshedCommand, AdminCommand(AdminFlags.Debug)]
public sealed class RejuvenateCommand : ToolshedCommand
{
private RejuvenateSystem? _rejuvenate;
[CommandImplementation]
public IEnumerable<EntityUid> Rejuvenate([PipedArgument] IEnumerable<EntityUid> input)
{
@@ -19,4 +21,19 @@ public sealed class RejuvenateCommand : ToolshedCommand
yield return i;
}
}
[CommandImplementation]
public void Rejuvenate([CommandInvocationContext] IInvocationContext ctx)
{
_rejuvenate ??= GetSys<RejuvenateSystem>();
if (ExecutingEntity(ctx) is not { } ent)
{
if (ctx.Session is {} session)
ctx.ReportError(new SessionHasNoEntityError(session));
else
ctx.ReportError(new NotForServerConsoleError());
}
else
_rejuvenate.PerformRejuvenate(ent);
}
}

View File

@@ -2,8 +2,11 @@
using Content.Server.Chemistry.EntitySystems;
using Content.Shared.Administration;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.Syntax;
using Robust.Shared.Toolshed.TypeParsers;
namespace Content.Server.Administration.Toolshed;
@@ -38,6 +41,38 @@ public sealed class SolutionCommand : ToolshedCommand
{
return input.Select(x => Get(ctx, x, name)).Where(x => x is not null).Cast<SolutionRef>();
}
[CommandImplementation("adjreagent")]
public SolutionRef AdjReagent(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] SolutionRef input,
[CommandArgument] Prototype<ReagentPrototype> name,
[CommandArgument] ValueRef<FixedPoint2> amountRef
)
{
_solutionContainer ??= GetSys<SolutionContainerSystem>();
var amount = amountRef.Evaluate(ctx);
if (amount > 0)
{
_solutionContainer.TryAddReagent(input.Owner, input.Solution, name.Value.ID, amount, out _);
}
else if (amount < 0)
{
_solutionContainer.TryRemoveReagent(input.Owner, input.Solution, name.Value.ID, -amount);
}
return input;
}
[CommandImplementation("adjreagent")]
public IEnumerable<SolutionRef> AdjReagent(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] IEnumerable<SolutionRef> input,
[CommandArgument] Prototype<ReagentPrototype> name,
[CommandArgument] ValueRef<FixedPoint2> amountRef
)
=> input.Select(x => AdjReagent(ctx, x, name, amountRef));
}
public readonly record struct SolutionRef(EntityUid Owner, Solution Solution)

View File

@@ -11,7 +11,6 @@ using Content.Server.GhostKick;
using Content.Server.Info;
using Content.Server.Maps;
using Content.Server.MoMMI;
using Content.Server.NewCon;
using Content.Server.NodeContainer.NodeGroups;
using Content.Server.Objectives;
using Content.Server.Objectives.Interfaces;
@@ -24,7 +23,6 @@ using Content.Server.Worldgen.Tools;
using Content.Shared.Administration.Logs;
using Content.Shared.Administration.Managers;
using Content.Shared.Kitchen;
using Robust.Shared.Toolshed;
namespace Content.Server.IoC
{

View File

@@ -0,0 +1,62 @@
using Content.Server.Mind.Components;
using Content.Server.Players;
using Robust.Server.Player;
using Robust.Shared.Players;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.Errors;
using Robust.Shared.Toolshed.Syntax;
namespace Content.Server.Mind.Toolshed;
/// <summary>
/// Contains various mind-manipulation commands like getting minds, controlling mobs, etc.
/// </summary>
[ToolshedCommand]
public sealed class MindCommand : ToolshedCommand
{
private MindSystem? _mind;
[CommandImplementation("get")]
public Mind? Get([PipedArgument] IPlayerSession session)
{
return session.ContentData()?.Mind;
}
[CommandImplementation("get")]
public Mind? Get([PipedArgument] EntityUid ent)
{
if (!TryComp<MindContainerComponent>(ent, out var container))
{
return null;
}
return container.Mind;
}
[CommandImplementation("control")]
public EntityUid Control(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] EntityUid target,
[CommandArgument] ValueRef<IPlayerSession> playerRef)
{
_mind ??= GetSys<MindSystem>();
var player = playerRef.Evaluate(ctx);
if (player is null)
{
ctx.ReportError(new NotForServerConsoleError());
return target;
}
var mind = player.ContentData()?.Mind;
if (mind is null)
{
ctx.ReportError(new SessionHasNoEntityError(player));
return target;
}
_mind.TransferTo(mind, target);
return target;
}
}

View File

@@ -248,22 +248,22 @@ namespace Content.Server.Polymorph.Systems
/// </summary>
/// <param name="uid">The entityuid of the entity being reverted</param>
/// <param name="component"></param>
public void Revert(EntityUid uid, PolymorphedEntityComponent? component = null)
public EntityUid? Revert(EntityUid uid, PolymorphedEntityComponent? component = null)
{
if (Deleted(uid))
return;
return null;
if (!Resolve(uid, ref component))
return;
return null;
var parent = component.Parent;
if (Deleted(parent))
return;
return null;
if (!_proto.TryIndex(component.Prototype, out PolymorphPrototype? proto))
{
_sawmill.Error($"{nameof(PolymorphSystem)} encountered an improperly initialized polymorph component while reverting. Entity {ToPrettyString(uid)}. Prototype: {component.Prototype}");
return;
return null;
}
var uidXform = Transform(uid);
@@ -317,6 +317,8 @@ namespace Content.Server.Polymorph.Systems
("child", Identity.Entity(parent, EntityManager))),
parent);
QueueDel(uid);
return parent;
}
/// <summary>

View File

@@ -0,0 +1,36 @@
using System.Linq;
using Content.Server.Administration;
using Content.Server.Polymorph.Systems;
using Content.Shared.Administration;
using Content.Shared.Polymorph;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.TypeParsers;
namespace Content.Server.Polymorph.Toolshed;
/// <summary>
/// Polymorphs the given entity(s) into the target morph.
/// </summary>
[ToolshedCommand, AdminCommand(AdminFlags.Fun)]
public sealed class PolymorphCommand : ToolshedCommand
{
private PolymorphSystem? _system;
[CommandImplementation]
public EntityUid? Polymorph(
[PipedArgument] EntityUid input,
[CommandArgument] Prototype<PolymorphPrototype> prototype
)
{
_system ??= GetSys<PolymorphSystem>();
return _system.PolymorphEntity(input, prototype.Value);
}
[CommandImplementation]
public IEnumerable<EntityUid> Polymorph(
[PipedArgument] IEnumerable<EntityUid> input,
[CommandArgument] Prototype<PolymorphPrototype> prototype
)
=> input.Select(x => Polymorph(x, prototype)).Where(x => x is not null).Select(x => (EntityUid)x!);
}

View File

@@ -0,0 +1,30 @@
using System.Linq;
using Content.Server.Administration;
using Content.Server.Polymorph.Systems;
using Content.Shared.Administration;
using Content.Shared.Polymorph;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.TypeParsers;
namespace Content.Server.Polymorph.Toolshed;
/// <summary>
/// Undoes a polymorph, reverting the target to it's original form.
/// </summary>
[ToolshedCommand, AdminCommand(AdminFlags.Fun)]
public sealed class UnpolymorphCommand : ToolshedCommand
{
private PolymorphSystem? _system;
[CommandImplementation]
public EntityUid? Unpolymorph([PipedArgument] EntityUid input)
{
_system ??= GetSys<PolymorphSystem>();
return _system.Revert(input);
}
[CommandImplementation]
public IEnumerable<EntityUid> Unpolymorph([PipedArgument] IEnumerable<EntityUid> input)
=> input.Select(Unpolymorph).Where(x => x is not null).Select(x => (EntityUid)x!);
}

View File

@@ -5,7 +5,7 @@ using Robust.Server.Player;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.Syntax;
namespace Content.Server.NewCon.Commands.AdminDebug;
namespace Content.Server.Toolshed.Commands.AdminDebug;
[ToolshedCommand, AdminCommand(AdminFlags.Debug)]
public sealed class ACmdCommand : ToolshedCommand

View File

@@ -6,7 +6,7 @@ using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.Syntax;
using Robust.Shared.Toolshed.TypeParsers;
namespace Content.Server.NewCon.Commands.Verbs;
namespace Content.Server.Toolshed.Commands.Verbs;
[ToolshedCommand, AdminCommand(AdminFlags.Admin)]
public sealed class RunVerbAsCommand : ToolshedCommand

View File

@@ -8,7 +8,7 @@ using Robust.Server.Player;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.Errors;
namespace Content.Server.NewCon.Commands;
namespace Content.Server.Toolshed.Commands;
[ToolshedCommand, AdminCommand(AdminFlags.Admin)]
public sealed class VisualizeCommand : ToolshedCommand

View File

@@ -58,5 +58,15 @@ command-description-tag-addmany =
Adds a list of tags to the given entities.
command-description-tag-rmmany =
Removes a list of tags from the given entities.
command-description-polymorph =
Polymorphs the input entity with the given prototype.
command-description-unpolymorph =
Reverts a polymorph.
command-description-solution-get =
Returns a solution stored in an entity's solution container.
Grabs the given solution off the given entity.
command-description-solution-adjreagent =
Adjusts the given reagent on the given solution.
command-description-mind-get =
Grabs the mind from the entity, if any.
command-description-mind-control =
Assumes control of an entity with the given player.

View File

@@ -1,4 +1,4 @@
- Flags: QUERY
- Flags: QUERY
Commands:
- entities
- nearby
@@ -7,6 +7,19 @@
- player
- splat
- emplace
- bin
- extremes
- reduce
- sortby
- sort
- sortdownby
- sortdown
- sortmapby
- sortmapdownby
- iota
- rep
- to
- iterate
- Flags: DEBUG
Commands:
@@ -23,7 +36,14 @@
- types
- ecscomp
- actor
- spawn
- mappos
- pos
- tp
- allcomps
- replace
- entitysystemupdateorder
- mind
- Flags: HOST
Commands:
@@ -69,3 +89,95 @@
- '>='
- '=='
- '!='
- f
- i
- s
- b
- '+/'
- '-/'
- '*/'
- '//'
- join
- append
- '?'
- 'or?'
- '??'
- rng
- 'sum'
- take
- curtick
- curtime
- realtime
- servertime
- more
- '%'
- '%/'
- '&~'
- '|~'
- '^~'
- '~'
- 'abs'
- 'average'
- 'bibytecount'
- 'shortestbitlength'
- 'countleadzeros'
- 'counttrailingzeros'
- 'fpi'
- 'fe'
- 'ftau'
- 'fepsilon'
- dpi
- de
- dtau
- depsilon
- hpi
- he
- htau
- hepsilon
- floor
- ceil
- round
- trunc
- round2frac
- exponentbytecount
- significandbytecount
- significandbitcount
- exponentshortestbitcount
- stepnext
- stepprev
- checkedto
- saturactedto
- truncto
- iscanonical
- iscomplex
- iseven
- isodd
- isfinite
- isimaginary
- isinfinite
- isinteger
- isnan
- isnegative
- ispositive
- isreal
- issubnormal
- iszero
- pow
- sqrt
- cbrt
- root
- hypot
- sin
- sinpi
- asin
- asinpi
- cos
- cospi
- acos
- acospi
- tan
- tanpi
- atan
- atanpi
- pick
- tee

View File

@@ -645,6 +645,7 @@ public sealed class $CLASS$ : Shared$CLASS$ {
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ruinable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=saltern/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=sandboxing/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=satisfier/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Serilog/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Shrimple/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=singulo/@EntryIndexedValue">True</s:Boolean>
@@ -694,6 +695,7 @@ public sealed class $CLASS$ : Shared$CLASS$ {
<s:Boolean x:Key="/Default/UserDictionary/Words/=Xeno/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Xenoarchaeology/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=xform/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Xnor/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=yamls/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Zumos/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=652E0DBD3559BD4EA35305A83762B0C8/@KeyIndexDefined">True</s:Boolean>