From 4d8bb3d09cc6a47d767171bcec66108b075dcee3 Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Wed, 19 Oct 2022 03:50:35 +0700 Subject: [PATCH] Add alias support into "help" command --- .../Commands/BuiltInCommands/ACommandHelp.uc | 330 ++++++++++++++++-- 1 file changed, 305 insertions(+), 25 deletions(-) diff --git a/sources/Commands/BuiltInCommands/ACommandHelp.uc b/sources/Commands/BuiltInCommands/ACommandHelp.uc index b18187f..e5859d5 100644 --- a/sources/Commands/BuiltInCommands/ACommandHelp.uc +++ b/sources/Commands/BuiltInCommands/ACommandHelp.uc @@ -1,6 +1,6 @@ /** * Command for displaying help information about registered Acedia's commands. - * Copyright 2021 - 2022 Anton Tarasenko + * Copyright 2021-2022 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -20,15 +20,35 @@ class ACommandHelp extends Command dependson(LoggerAPI); +// 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; + var LoggerAPI.Definition testMsg; var public const int TSPACE, TCOMMAND_NAME_FALLBACK, TPLUS; -var public const int TOPEN_BRACKET, TCLOSE_BRACKET, TCOLUMN_SPACE; +var public const int TOPEN_BRACKET, TCLOSE_BRACKET, TCOLON_SPACE; var public const int TKEY, TDOUBLE_KEY, TCOMMA_SPACE, TBOOLEAN, TINDENT; 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; + +protected function Constructor() +{ + super.Constructor(); + if (class'Aliases_Feature'.static.GetEnabledInstance() != none) { + ReloadCommandAliases(); + } + _.environment.OnFeatureEnabled(self).connect = HandleFeatureEnabled; +} + +protected function Finalizer() +{ + _.memory.Free(commandToAliasesMap); + commandToAliasesMap = none; +} protected function BuildData(CommandDataBuilder builder) { @@ -37,7 +57,11 @@ protected function BuildData(CommandDataBuilder builder) builder.OptionalParams() .ParamTextList(P("commands")) .Describe(P("Displays information about all specified commands.")); - builder.Option(P("list")) + builder.Option(P("aliases")) + .Describe(P("When displaying available commands, specifying this flag" + @ "will additionally make command to display all of their available" + @ "aliases.")) + .Option(P("list")) .Describe(P("Display available commands. Optionally command groups can" @ "be specified and then only commands from such groups will be" @ "listed. Otherwise all commands will be displayed.")) @@ -55,7 +79,9 @@ protected function Executed(Command.CallData callData, EPlayer callerPlayer) if (options.HasKey(P("list"))) { commandGroupsToDisplay = options.GetArrayListBy(P("/list/groups")); - DisplayCommandLists(commandGroupsToDisplay); + DisplayCommandLists( + commandGroupsToDisplay, + options.HasKey(P("aliases"))); _.memory.Free(commandGroupsToDisplay); } // Help pages. @@ -71,7 +97,98 @@ protected function Executed(Command.CallData callData, EPlayer callerPlayer) } } -private final function DisplayCommandLists(ArrayList commandGroupsToDisplay) +private final function HandleFeatureEnabled(Feature enabledFeature) +{ + if (enabledFeature == none) return; + if (enabledFeature.class != class'Aliases_Feature') return; + + ReloadCommandAliases(); +} + +private final function ReloadCommandAliases() +{ + local int i; + local Text aliasValue; + local array availableAliases; + local BaseAliasSource commandAliasSource; + local MutableText commandName, subcommandName; + + _.memory.Free(commandToAliasesMap); + commandToAliasesMap = _.collections.EmptyHashTable(); + commandAliasSource = _.alias.GetCommandSource(); + if (commandAliasSource == none) { + return; + } + 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]); + commandName.FreeSelf(); + subcommandName.FreeSelf(); + aliasValue.FreeSelf(); + } + _.memory.FreeMany(availableAliases); + commandAliasSource.FreeSelf(); +} + +private final function ParseCommandNames( + BaseText source, + out MutableText commandName, + out MutableText subcommandName) +{ + local Parser valueParser; + + if (source == none) { + return; + } + if (subcommandName != none) { + subcommandName.FreeSelf(); + } + valueParser = source.Parse(); + subcommandName = valueParser + .MUntil(commandName, _.text.GetCharacter(".")) + .MatchS(".") + .GetRemainderM(); + _.memory.Free(valueParser); +} + +// Assumes that `commandToAliasesMap` and its arguments are not `none` +private final function InsertIntoMap( + BaseText commandName, + BaseText subcommandName, + BaseText alias) +{ + local Text commandKey, subcommandKey; + local HashTable subcommandToAliasesMap; + local ArrayList aliasesArray; + + commandKey = commandName.LowerCopy(); + subcommandKey = subcommandName.LowerCopy(); + subcommandToAliasesMap = commandToAliasesMap.GetHashTable(commandKey); + if (subcommandToAliasesMap == none) { + subcommandToAliasesMap = _.collections.EmptyHashTable(); + } + commandToAliasesMap.SetItem(commandKey, subcommandToAliasesMap); + aliasesArray = subcommandToAliasesMap.GetArrayList(subcommandKey); + if (aliasesArray == none) { + aliasesArray = _.collections.EmptyArrayList(); + } + subcommandToAliasesMap.SetItem(subcommandKey, aliasesArray); + if (aliasesArray.Find(alias) < 0) { + aliasesArray.AddItem(alias); + } + // Cleaning up local references to collections and keys + subcommandToAliasesMap.FreeSelf(); + aliasesArray.FreeSelf(); + commandKey.FreeSelf(); + subcommandKey.FreeSelf(); +} + +private final function DisplayCommandLists( + ArrayList commandGroupsToDisplay, + bool displayAliases) { local int i; local array commandNames, groupsNames; @@ -82,10 +199,8 @@ private final function DisplayCommandLists(ArrayList commandGroupsToDisplay) if (commandsFeature == none) { return; } - if (commandGroupsToDisplay == none) - { + if (commandGroupsToDisplay == none) { groupsNames = commandsFeature.GetGroupsNames(); - DisplayCommandsNamesArray(commandsFeature, commandNames); } else { @@ -109,7 +224,10 @@ private final function DisplayCommandLists(ArrayList commandGroupsToDisplay) else { callerConsole.WriteLine(groupsNames[i]); } - DisplayCommandsNamesArray(commandsFeature, commandNames); + DisplayCommandsNamesArray( + commandsFeature, + commandNames, + displayAliases); _.memory.FreeMany(commandNames); } } @@ -118,7 +236,8 @@ private final function DisplayCommandLists(ArrayList commandGroupsToDisplay) private final function DisplayCommandsNamesArray( Commands_Feature commandsFeature, - array commandsNamesArray) + array commandsNamesArray, + bool displayAliases) { local int i; local Command nextCommand; @@ -131,21 +250,104 @@ private final function DisplayCommandsNamesArray( continue; } nextData = nextCommand.BorrowData(); - callerConsole.UseColor(_.color.textEmphasis) + callerConsole + .UseColorOnce(_.color.textEmphasis) .Write(nextData.name) - .ResetColor() - .Write(T(TCOLUMN_SPACE)) + .Write(T(TCOLON_SPACE)) .WriteLine(nextData.summary); + if (displayAliases) { + DisplayCommandAliases(nextData.name); + } _.memory.Free(nextCommand); } } +private final function DisplayCommandAliases(BaseText commandName) +{ + local CollectionIterator iter; + local Text commandKey; + local Text nextSubCommand; + local ArrayList nextAliasesArray; + local HashTable subCommandToAliasesMap; + + if (commandName == none) return; + if (commandToAliasesMap == none) return; + + commandKey = commandName.LowerCopy(); + subCommandToAliasesMap = commandToAliasesMap.GetHashTable(commandName); + commandKey.FreeSelf(); + if (subCommandToAliasesMap == none) { + return; + } + //callerConsole.WriteBlock(T(TALIASES)); + // Display aliases to command itself first + nextAliasesArray = subCommandToAliasesMap.GetArrayList(T(TEMPTY)); + DisplayAliasesArray(commandName, none, nextAliasesArray); + _.memory.Free(nextAliasesArray); + // Then aliases to all of its subcommands, in no particular order + iter = subCommandToAliasesMap.Iterate(); + while (!iter.HasFinished()) + { + nextSubCommand = Text(iter.Getkey()); // cannot be `none` + if (nextSubCommand.IsEmpty()) + { + nextSubCommand.FreeSelf(); + iter.Next(); + continue; + } + nextAliasesArray = ArrayList(iter.Get()); + DisplayAliasesArray(commandName, nextSubCommand, nextAliasesArray); + _.memory.Free(nextAliasesArray); + _.memory.Free(nextSubCommand); + iter.Next(); + } + iter.FreeSelf(); +} + +private final function DisplayAliasesArray( + BaseText commandName, + BaseText subcommandName, + ArrayList aliasesArray) +{ + local int i; + local Text nextAlias; + + if (commandName == none) return; + if (aliasesArray == none) return; + + callerConsole + .Write(T(TALIASES_FOR)) + .Write(T(TSPACE)) + .UseColorOnce(_.color.TextEmphasis) + .Write(commandName); + if (subcommandName != none) + { + callerConsole + .Write(T(TSPACE)) + .UseColorOnce(_.color.TextEmphasis) + .Write(subCommandName); + } + callerConsole.Write(T(TCOLON_SPACE)); + for (i = 0; i < aliasesArray.GetLength(); i += 1) + { + if (i > 0) { + callerConsole.Write(T(TCOMMA_SPACE)); + } + nextAlias = aliasesArray.GetText(i); + callerConsole.UseColorOnce(_.color.TextNeutral).Write(nextAlias); + nextAlias.FreeSelf(); + } + callerConsole.WriteBlock(); +} + private final function DisplayCommandHelpPages(ArrayList commandList) { local int i; - local Text nextCommandName; + local Text nextCommandName, nextCommandValue; local Command nextCommand; local Commands_Feature commandsFeature; + local MutableText parsedCommandName, parsedSubcommandName; + commandsFeature = Commands_Feature(class'Commands_Feature'.static.GetEnabledInstance()); if (commandsFeature == none) { @@ -160,24 +362,50 @@ private final function DisplayCommandHelpPages(ArrayList commandList) // 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); - _.memory.Free(nextCommandName); - if (nextCommand == none) { + // 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)); } - PrintHelpPage(nextCommand.BorrowData()); + if (parsedSubcommandName == none) { + PrintHelpPage(nextCommand.BorrowData()); + } + else + { + PrintHelpPageFor( + nextCommand.BorrowData(), + nextCommandName, + parsedSubcommandName); + } _.memory.Free(nextCommand); + _.memory.Free(parsedSubcommandName); + _.memory.Free(nextCommandName); + parsedSubcommandName = none; } } // Following methods are mostly self-explanatory private final function PrintHelpPage(Command.Data data) { - local Text commandNameUpperCase; + local Text commandNameLowerCase, commandNameUpperCase; // Get capitalized command name commandNameUpperCase = data.name.UpperCopy(); // Print header: name + basic info @@ -192,32 +420,80 @@ private final function PrintHelpPage(Command.Data data) callerConsole.WriteLine(T(TCMD_WITHOUT_TARGET)); } // Print commands and options - PrintCommands(data); + commandNameLowerCase = data.name.LowerCopy(); + PrintCommands(data, commandNameLowerCase, none); + commandNameLowerCase.FreeSelf(); PrintOptions(data); // Clean up callerConsole.ResetColor().Flush(); } -private final function PrintCommands(Command.Data data) +private final function PrintHelpPageFor( + Command.Data data, + BaseText commandAlias, + BaseText usedSubcommand) +{ + local Text commandNameLowerCase, commandNameUpperCase; + // Get capitalized command name + commandNameUpperCase = commandAlias.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)); + } + else { + callerConsole.WriteLine(T(TCMD_WITHOUT_TARGET)); + } + // Print commands and options + commandNameLowerCase = commandAlias.LowerCopy(); + PrintCommands(data, commandNameLowerCase, usedSubcommand); + commandNameLowerCase.FreeSelf(); + PrintOptions(data); + // Clean up + callerConsole.ResetColor().Flush(); +} + +private final function PrintCommands( + Command.Data data, + BaseText commandName, + BaseText filterSubcommandName) { local int i; local array subCommands; + if (filterSubcommandName != none && filterSubcommandName.IsEmpty()) { + filterSubcommandName = none; + } subCommands = data.subCommands; - for (i = 0; i < subCommands.length; i += 1) { - PrintSubCommand(subCommands[i], data.name); + for (i = 0; i < subCommands.length; i += 1) + { + if ( filterSubcommandName == none + || filterSubcommandName.Compare(subCommands[i].name)) + { + PrintSubCommand( + subCommands[i], + commandName, + filterSubcommandName != none); + } } } private final function PrintSubCommand( SubCommand subCommand, - BaseText commandName) + BaseText commandName, + bool skipSubcommandName) { // Command + parameters // Command name + sub command name callerConsole.UseColor(_.color.textEmphasis) .Write(commandName) .Write(T(TSPACE)); - if (subCommand.name != none && !subCommand.name.IsEmpty()) { + if ( !skipSubcommandName + && subCommand.name != none + && !subCommand.name.IsEmpty()) + { callerConsole.Write(subCommand.name).Write(T(TSPACE)); } callerConsole.UseColor(_.color.textDefault); @@ -351,7 +627,7 @@ defaultproperties stringConstants(5) = "--" TCOMMA_SPACE = 6 stringConstants(6) = ", " - TCOLUMN_SPACE = 7 + TCOLON_SPACE = 7 stringConstants(7) = ": " TINDENT = 8 stringConstants(8) = " " @@ -377,4 +653,8 @@ defaultproperties stringConstants(18) = "{$TextHeader List of registered commands}" TEMPTY_GROUP = 19 stringConstants(19) = "Empty group" + TALIASES_FOR = 20 + stringConstants(20) = "Aliases for" + TEMPTY = 21 + stringConstants(21) = "" } \ No newline at end of file