diff --git a/sources/Commands/BuiltInCommands/ACommandHelp.uc b/sources/Commands/BuiltInCommands/ACommandHelp.uc index e5859d5..14f48ef 100644 --- a/sources/Commands/BuiltInCommands/ACommandHelp.uc +++ b/sources/Commands/BuiltInCommands/ACommandHelp.uc @@ -20,7 +20,7 @@ class ACommandHelp extends Command dependson(LoggerAPI); -// For each key (given by lower case command name) stores another `HashMap` +// For each key (given by lower case command name) stores another `HashMap` // that uses sub-command names as keys and returns `ArrayList` of aliases. var private HashTable commandToAliasesMap; @@ -33,15 +33,19 @@ var public const int TBOOLEAN_TRUE_FALSE, TBOOLEAN_ENABLE_DISABLE; var public const int TBOOLEAN_ON_OFF, TBOOLEAN_YES_NO; var public const int TOPTIONS, TCMD_WITH_TARGET, TCMD_WITHOUT_TARGET; var public const int TSEPARATOR, TLIST_REGIRESTED_CMDS, TEMPTY_GROUP; -var public const int TALIASES_FOR, TEMPTY; +var public const int TALIASES_FOR, TEMPTY, TDOT; protected function Constructor() { + local Feature preenabledInstance; + super.Constructor(); - if (class'Aliases_Feature'.static.GetEnabledInstance() != none) { - ReloadCommandAliases(); - } - _.environment.OnFeatureEnabled(self).connect = HandleFeatureEnabled; + // We need to update aliases map every time aliases feature is reenabled + _.environment.OnFeatureEnabled(self).connect = FillCommandToAliasesMap; + // If `Aliases_Feature` is already enabled - read its command aliases now + preenabledInstance = class'Aliases_Feature'.static.GetEnabledInstance(); + FillCommandToAliasesMap(Aliases_Feature(preenabledInstance)); + _.memory.Free(preenabledInstance); } protected function Finalizer() @@ -71,8 +75,9 @@ protected function BuildData(CommandDataBuilder builder) protected function Executed(Command.CallData callData, EPlayer callerPlayer) { - local HashTable parameters, options;; + local HashTable parameters, options; local ArrayList commandsToDisplay, commandGroupsToDisplay; + parameters = callData.parameters; options = callData.options; // Print command list if "--list" option was specified @@ -97,42 +102,45 @@ protected function Executed(Command.CallData callData, EPlayer callerPlayer) } } -private final function HandleFeatureEnabled(Feature enabledFeature) +// If instance of the `Aliases_Feature` is passed as an argument (allowing this +// method to be used as a slot for `OnFeatureEnabled` signal) and +// `commandAliasSource` is available - empties current `commandToAliasesMap` +// and refills it with available command aliases. +private final function FillCommandToAliasesMap(Feature enabledFeature) { + local int i; + local Text aliasValue; + local array availableAliases; + local BaseAliasSource commandAliasSource; + local MutableText commandName, subcommandName; + if (enabledFeature == none) return; if (enabledFeature.class != class'Aliases_Feature') return; + commandAliasSource = _.alias.GetCommandSource(); + if (commandAliasSource == none) return; - ReloadCommandAliases(); -} - -private final function ReloadCommandAliases() -{ - local int i; - local Text aliasValue; - local array availableAliases; - local BaseAliasSource commandAliasSource; - local MutableText commandName, subcommandName; - + // Drop map built by previous aliases (if it was even build up until now) _.memory.Free(commandToAliasesMap); commandToAliasesMap = _.collections.EmptyHashTable(); - commandAliasSource = _.alias.GetCommandSource(); - if (commandAliasSource == none) { - return; - } + // Construct a command -> alias map by iterating over aliases availableAliases = commandAliasSource.GetAllAliases(); for (i = 0; i < availableAliases.length; i += 1) { aliasValue = _.alias.ResolveCommand(availableAliases[i]); ParseCommandNames(aliasValue, commandName, subcommandName); - InsertIntoMap(commandName, subcommandName, availableAliases[i]); + aliasValue.FreeSelf(); + InsertIntoAliasesMap(commandName, subcommandName, availableAliases[i]); commandName.FreeSelf(); subcommandName.FreeSelf(); - aliasValue.FreeSelf(); } + // Clean up _.memory.FreeMany(availableAliases); commandAliasSource.FreeSelf(); } +// Breaks command name as it is intended to be specified in command aliases +// ("" or ".") into command and subcommand, +// returning both as `out` parameters. private final function ParseCommandNames( BaseText source, out MutableText commandName, @@ -148,14 +156,14 @@ private final function ParseCommandNames( } valueParser = source.Parse(); subcommandName = valueParser - .MUntil(commandName, _.text.GetCharacter(".")) - .MatchS(".") + .MUntil(commandName, T(TDOT).GetCharacter(0)) + .Match(T(TDOT)) .GetRemainderM(); _.memory.Free(valueParser); } -// Assumes that `commandToAliasesMap` and its arguments are not `none` -private final function InsertIntoMap( +// Assumes that `commandToAliasesMap` and its arguments are not `none`. +private final function InsertIntoAliasesMap( BaseText commandName, BaseText subcommandName, BaseText alias) @@ -224,7 +232,7 @@ private final function DisplayCommandLists( else { callerConsole.WriteLine(groupsNames[i]); } - DisplayCommandsNamesArray( + PrintCommandsNamesArray( commandsFeature, commandNames, displayAliases); @@ -232,9 +240,10 @@ private final function DisplayCommandLists( } } _.memory.FreeMany(groupsNames); + commandsFeature.FreeSelf(); } -private final function DisplayCommandsNamesArray( +private final function PrintCommandsNamesArray( Commands_Feature commandsFeature, array commandsNamesArray, bool displayAliases) @@ -256,13 +265,13 @@ private final function DisplayCommandsNamesArray( .Write(T(TCOLON_SPACE)) .WriteLine(nextData.summary); if (displayAliases) { - DisplayCommandAliases(nextData.name); + PrintCommandAliases(nextData.name); } _.memory.Free(nextCommand); } } -private final function DisplayCommandAliases(BaseText commandName) +private final function PrintCommandAliases(BaseText commandName) { local CollectionIterator iter; local Text commandKey; @@ -279,10 +288,9 @@ private final function DisplayCommandAliases(BaseText commandName) if (subCommandToAliasesMap == none) { return; } - //callerConsole.WriteBlock(T(TALIASES)); // Display aliases to command itself first nextAliasesArray = subCommandToAliasesMap.GetArrayList(T(TEMPTY)); - DisplayAliasesArray(commandName, none, nextAliasesArray); + PrintAliasesArray(commandName, none, nextAliasesArray); _.memory.Free(nextAliasesArray); // Then aliases to all of its subcommands, in no particular order iter = subCommandToAliasesMap.Iterate(); @@ -296,7 +304,7 @@ private final function DisplayCommandAliases(BaseText commandName) continue; } nextAliasesArray = ArrayList(iter.Get()); - DisplayAliasesArray(commandName, nextSubCommand, nextAliasesArray); + PrintAliasesArray(commandName, nextSubCommand, nextAliasesArray); _.memory.Free(nextAliasesArray); _.memory.Free(nextSubCommand); iter.Next(); @@ -304,7 +312,7 @@ private final function DisplayCommandAliases(BaseText commandName) iter.FreeSelf(); } -private final function DisplayAliasesArray( +private final function PrintAliasesArray( BaseText commandName, BaseText subcommandName, ArrayList aliasesArray) @@ -343,95 +351,100 @@ private final function DisplayAliasesArray( private final function DisplayCommandHelpPages(ArrayList commandList) { local int i; - local Text nextCommandName, nextCommandValue; + local bool printedSomething; + local Text nextUserProvidedName; + local MutableText referredSubcommand; local Command nextCommand; - local Commands_Feature commandsFeature; - local MutableText parsedCommandName, parsedSubcommandName; - commandsFeature = - Commands_Feature(class'Commands_Feature'.static.GetEnabledInstance()); - if (commandsFeature == none) { - return; - } // If arguments were empty - at least display our own help page if (commandList == none) { - PrintHelpPage(BorrowData()); + PrintHelpPageFor(BorrowData().name, none, BorrowData()); return; } // Otherwise - print help for specified commands for (i = 0; i < commandList.GetLength(); i += 1) { - // Try normal command name - nextCommandName = commandList.GetText(i); - nextCommand = commandsFeature.GetCommand(nextCommandName); - // Try command alias - if (nextCommand == none) - { - nextCommandValue = _.alias.ResolveCommand(nextCommandName); - ParseCommandNames( - nextCommandValue, - parsedCommandName, - parsedSubcommandName); - nextCommand = commandsFeature.GetCommand(parsedCommandName); - _.memory.Free(nextCommandValue); - _.memory.Free(parsedCommandName); - } - if (nextCommand == none) - { - _.memory.Free(nextCommandName); - continue; - } - if (i > 0) { - callerConsole.WriteLine(T(TSEPARATOR)); - } - if (parsedSubcommandName == none) { - PrintHelpPage(nextCommand.BorrowData()); - } - else + nextUserProvidedName = commandList.GetText(i); + nextCommand = GetCommandFromUserProvidedName( + nextUserProvidedName, + referredSubcommand); + if (nextCommand != none) { + if (printedSomething) { + callerConsole.WriteLine(T(TSEPARATOR)); + } PrintHelpPageFor( - nextCommand.BorrowData(), - nextCommandName, - parsedSubcommandName); + nextUserProvidedName, + referredSubcommand, + nextCommand.BorrowData()); + printedSomething = true; } _.memory.Free(nextCommand); - _.memory.Free(parsedSubcommandName); - _.memory.Free(nextCommandName); - parsedSubcommandName = none; + _.memory.Free(nextUserProvidedName); + _.memory.Free(referredSubcommand); + // `referredSubcommand` is passed as an `out` parameter on + // every iteration, so we need to prevent the possibility of its value + // being used. + // NOTE: `nextCommand` and `nextUserProvidedName` are just + // rewritten. + referredSubcommand = none; } } -// Following methods are mostly self-explanatory -private final function PrintHelpPage(Command.Data data) +// Returns `Command` based on the name, given by user. +// `referredSubcommand` is always overwritten (freed if non-`none` value +// is passed) and is used to return name of the subcommand for returned +// `Command` that is specified by `nextUserProvidedName` (only relevant for +// aliases that refer to a particular subcommand). +private final function Command GetCommandFromUserProvidedName( + BaseText nextUserProvidedName, + out MutableText referredSubcommand) { - local Text commandNameLowerCase, commandNameUpperCase; - // Get capitalized command name - commandNameUpperCase = data.name.UpperCopy(); - // Print header: name + basic info - callerConsole.UseColor(_.color.textHeader) - .Write(commandNameUpperCase) - .UseColor(_.color.textDefault); - commandNameUpperCase.FreeSelf(); - if (data.requiresTarget) { - callerConsole.WriteLine(T(TCMD_WITH_TARGET)); + local Command result; + local Text commandAliasValue; + local Commands_Feature commandsFeature; + local MutableText parsedCommandName; + + // Clear `out` parameter no matter what + if (referredSubcommand != none) + { + referredSubcommand.FreeSelf(); + referredSubcommand = none; } - else { - callerConsole.WriteLine(T(TCMD_WITHOUT_TARGET)); + // Try accessing (check availability of) `Commands_Feature` + commandsFeature = + Commands_Feature(class'Commands_Feature'.static.GetEnabledInstance()); + if (commandsFeature == none) { + return none; } - // Print commands and options - commandNameLowerCase = data.name.LowerCopy(); - PrintCommands(data, commandNameLowerCase, none); - commandNameLowerCase.FreeSelf(); - PrintOptions(data); - // Clean up - callerConsole.ResetColor().Flush(); + // Try getting command using `nextUserProvidedName` as a literal name + result = commandsFeature.GetCommand(nextUserProvidedName); + if (result != none) + { + commandsFeature.FreeSelf(); + return result; + } + // On failure - try resolving it as an alias + commandAliasValue = _.alias.ResolveCommand(nextUserProvidedName); + ParseCommandNames(commandAliasValue, parsedCommandName, referredSubcommand); + result = commandsFeature.GetCommand(parsedCommandName); + // Empty subcommand name from the alias is essentially no subcommand name + if (referredSubcommand != none && referredSubcommand.IsEmpty()) + { + referredSubcommand.FreeSelf(); + referredSubcommand = none; + } + _.memory.Free(commandAliasValue); + _.memory.Free(parsedCommandName); + commandsFeature.FreeSelf(); + return result; } private final function PrintHelpPageFor( - Command.Data data, BaseText commandAlias, - BaseText usedSubcommand) + BaseText referredSubcommand, + Command.Data commandData) { local Text commandNameLowerCase, commandNameUpperCase; // Get capitalized command name @@ -441,7 +454,7 @@ private final function PrintHelpPageFor( .Write(commandNameUpperCase) .UseColor(_.color.textDefault); commandNameUpperCase.FreeSelf(); - if (data.requiresTarget) { + if (commandData.requiresTarget) { callerConsole.WriteLine(T(TCMD_WITH_TARGET)); } else { @@ -449,9 +462,9 @@ private final function PrintHelpPageFor( } // Print commands and options commandNameLowerCase = commandAlias.LowerCopy(); - PrintCommands(data, commandNameLowerCase, usedSubcommand); + PrintCommands(commandData, commandNameLowerCase, referredSubcommand); commandNameLowerCase.FreeSelf(); - PrintOptions(data); + PrintOptions(commandData); // Clean up callerConsole.ResetColor().Flush(); } @@ -459,23 +472,21 @@ private final function PrintHelpPageFor( private final function PrintCommands( Command.Data data, BaseText commandName, - BaseText filterSubcommandName) + BaseText referredSubcommand) { local int i; local array subCommands; - if (filterSubcommandName != none && filterSubcommandName.IsEmpty()) { - filterSubcommandName = none; - } + subCommands = data.subCommands; for (i = 0; i < subCommands.length; i += 1) { - if ( filterSubcommandName == none - || filterSubcommandName.Compare(subCommands[i].name)) + if ( referredSubcommand == none + || referredSubcommand.Compare(subCommands[i].name)) { PrintSubCommand( subCommands[i], commandName, - filterSubcommandName != none); + referredSubcommand != none); } } } @@ -657,4 +668,6 @@ defaultproperties stringConstants(20) = "Aliases for" TEMPTY = 21 stringConstants(21) = "" + TDOT = 22 + stringConstants(22) = "." } \ No newline at end of file diff --git a/sources/Commands/Command.uc b/sources/Commands/Command.uc index 85d0427..747459e 100644 --- a/sources/Commands/Command.uc +++ b/sources/Commands/Command.uc @@ -6,7 +6,7 @@ * `Executed()` is called first, whenever command is executed and * `ExecuteFor()` is called only for targeted commands, once for each * targeted player. - * Copyright 2021 - 2022 Anton Tarasenko + * Copyright 2021-2022 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -605,7 +605,6 @@ public final function Text GetGroupName() return commandData.group.LowerCopy(); } -// TODO: use `SharedRef` instead /** * Returns `Command.Data` struct that describes caller `Command`. * diff --git a/sources/Commands/CommandParser.uc b/sources/Commands/CommandParser.uc index b83ff59..2ac4f96 100644 --- a/sources/Commands/CommandParser.uc +++ b/sources/Commands/CommandParser.uc @@ -3,7 +3,7 @@ * a given `Command.Data`. While it's meant to be allocated for * a `self.ParseWith()` call and deallocated right after, it can be reused * without deallocation. - * Copyright 2021 - 2022 Anton Tarasenko + * Copyright 2021-2022 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * diff --git a/sources/Commands/Commands.uc b/sources/Commands/Commands.uc index be4a6c2..35138ca 100644 --- a/sources/Commands/Commands.uc +++ b/sources/Commands/Commands.uc @@ -1,6 +1,6 @@ /** * Config object for `Commands_Feature`. - * Copyright 2021 Anton Tarasenko + * Copyright 2021-2022 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * diff --git a/sources/Commands/Commands_Feature.uc b/sources/Commands/Commands_Feature.uc index 0ddbe21..d2387af 100644 --- a/sources/Commands/Commands_Feature.uc +++ b/sources/Commands/Commands_Feature.uc @@ -3,7 +3,7 @@ * parse their arguments into standard Acedia collection. It also allows to * manage them (and specify limitation on how they can be called) in a * centralized manner. - * Copyright 2021 - 2022 Anton Tarasenko + * Copyright 2021-2022 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * diff --git a/sources/Commands/PlayersParser.uc b/sources/Commands/PlayersParser.uc index 9e31535..04abc22 100644 --- a/sources/Commands/PlayersParser.uc +++ b/sources/Commands/PlayersParser.uc @@ -1,7 +1,7 @@ /** * Object for parsing what converting textual description of a group of * players into array of `EPlayer`s. Depends on the game context. - * Copyright 2021 Anton Tarasenko + * Copyright 2021-2022 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. *