rott/kf_sources/AcediaCore/Classes/CommandDataBuilder.uc
2026-07-14 20:27:09 +07:00

939 lines
35 KiB
Ucode

/**
* Author: dkanus
* Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
* License: GPL
* Copyright 2021-2023 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class CommandDataBuilder extends AcediaObject
dependson(Command);
//! This is an auxiliary class for convenient creation of [`Command::Data`]
//! using a builder pattern.
//!
//! ## Implementation
//!
//! We will store all defined data in two ways:
//!
//! 1. Selected data: data about parameters for subcommand/option that is
//! currently being filled;
//! 2. Prepared data: data that was already filled as "selected data" then
//! stored in these records. Whenever we want to switch to filling another
//! subcommand/option or return already prepared data we must dump
//! "selected data" into "prepared data" first and then return the latter.
//!
//! Builder object is automatically created when new `Command` instance is
//! allocated and doesn't normally need to be allocated by hand.
// "Prepared data"
var private Text commandName, commandGroup;
var private Text commandSummary;
var private array<Command.SubCommand> subcommands;
var private array<Command.Option> options;
var private bool requiresTarget;
// Auxiliary arrays signifying that we've started adding optional parameters
// into appropriate `subcommands` and `options`.
//
// All optional parameters must follow strictly after required parameters and
// so, after user have started adding optional parameters to subcommand/option,
// we prevent them from adding required ones (to that particular
// command/option).
var private array<byte> subcommandsIsOptional;
var private array<byte> optionsIsOptional;
// "Selected data"
// `false` means we have selected sub-command, `true` - option
var private bool selectedItemIsOption;
// `name` for sub-commands, `longName` for options
var private Text selectedItemName;
// Description of selected sub-command/option
var private Text selectedDescription;
// Are we filling optional parameters (`true`)? Or required ones (`false`)?
var private bool selectionIsOptional;
// Array of parameters we are currently filling (either required or optional)
var private array<Command.Parameter> selectedParameterArray;
var private LoggerAPI.Definition errLongNameTooShort, errShortNameTooLong;
var private LoggerAPI.Definition warnSameLongName, warnSameShortName;
protected function Constructor() {
// Fill empty subcommand (no special key word) by default
SubCommand(P(""));
}
protected function Finalizer() {
subcommands.length = 0;
subcommandsIsOptional.length = 0;
options.length = 0;
optionsIsOptional.length = 0;
selectedParameterArray.length = 0;
commandName = none;
commandGroup = none;
commandSummary = none;
selectedItemName = none;
selectedDescription = none;
requiresTarget = false;
selectedItemIsOption = false;
selectionIsOptional = false;
}
/// Method that starts defining a new sub-command.
///
/// Creates new sub-command with a given name (if it's missing) and then selects
/// sub-command with a given name to add parameters to.
///
/// [`name`] defines name of the sub-command user wants, case-sensitive.
/// If `none` is passed, this method will do nothing.
public final function SubCommand(BaseText name) {
local int subcommandIndex;
if (name == none) {
return;
}
if (!selectedItemIsOption && selectedItemName != none && selectedItemName.Compare(name)) {
return;
}
RecordSelection();
subcommandIndex = FindSubCommandIndex(name);
if (subcommandIndex < 0) {
MakeEmptySelection(name, false);
return;
}
// Load appropriate prepared data, if it exists for
// sub-command with name `name`
selectedItemIsOption = false;
selectedItemName = subcommands[subcommandIndex].name;
selectedDescription = subcommands[subcommandIndex].description;
selectionIsOptional = subcommandsIsOptional[subcommandIndex] > 0;
if (selectionIsOptional) {
selectedParameterArray = subcommands[subcommandIndex].optional;
} else {
selectedParameterArray = subcommands[subcommandIndex].required;
}
}
/// Method that starts defining a new option.
///
/// This method checks if some of the recorded options are in conflict with
/// given `longName` and `shortName` (already using one and only one of them).
/// In case there is no conflict, it creates new option with specified long and
/// short names (if such option is missing) and selects option with a long name
/// `longName` to add parameters to.
///
/// [`longName`] defines long name of the option, case-sensitive (for using
/// an option in form "--..."). Must be at least two characters long.
/// [`shortName`] defines short name of the option, case-sensitive (for using
/// an option in form "-..."). Must be exactly one character.
///
/// # Errors
///
/// Errors will be logged in case either of arguments are `none`, have
/// inappropriate length or are in conflict with each other.
public final function Option(BaseText longName, optional BaseText shortName) {
local int optionIndex;
local BaseText.Character shortNameAsCharacter;
// Unlike for `SubCommand()`, we need to ensure that option naming is
// correct and does not conflict with existing options
// (user might attempt to add two options with same long names and
// different short ones).
shortNameAsCharacter = GetValidShortName(longName, shortName);
if ( !_.text.IsValidCharacter(shortNameAsCharacter)
|| VerifyNoOptionNamingConflict(longName, shortNameAsCharacter)) {
// ^ `GetValidShortName()` and `VerifyNoOptionNamingConflict()`
// are responsible for logging warnings/errors
return;
}
SelectOption(longName);
// Set short name for new options
optionIndex = FindOptionIndex(longName);
if (optionIndex < 0) {
// We can only be here if option was created for the first time
RecordSelection();
// So now it cannot fail
optionIndex = FindOptionIndex(longName);
options[optionIndex].shortName = shortNameAsCharacter;
}
}
/// Adds description to the selected sub-command / option.
///
/// Highlights parts of the description in-between "`" characters.
///
/// Does nothing if nothing is yet selected.
public final function Describe(BaseText description) {
local int fromIndex, toIndex;
local BaseText.Formatting keyWordFormatting;
local bool lookingForEnd;
local MutableText coloredDescription;
if (description == none) {
return;
}
keyWordFormatting = _.text.FormattingFromColor(_.color.TextEmphasis);
coloredDescription = description.MutableCopy();
while (true) {
if (lookingForEnd) {
toIndex = coloredDescription.IndexOf(P("`"), fromIndex + 1);
} else {
fromIndex = coloredDescription.IndexOf(P("`"), toIndex + 1);
}
if (toIndex < 0 || fromIndex < 0) {
break;
}
if (lookingForEnd) {
coloredDescription.ChangeFormatting(
keyWordFormatting,
fromIndex,
toIndex - fromIndex + 1);
lookingForEnd = false;
} else {
lookingForEnd = true;
}
}
coloredDescription.Replace(P("`"), P(""));
if (lookingForEnd) {
coloredDescription.ChangeFormatting(keyWordFormatting, fromIndex);
}
_.memory.Free(selectedDescription);
selectedDescription = coloredDescription.IntoText();
}
/// Sets new group of `Command.Data` under construction.
///
/// Group name is meant to be shared among several commands, allowing user to
/// filter or fetch commands of a certain group.
/// Group name is case-insensitive.
public final function Group(BaseText newName) {
if (newName != none && newName == commandGroup) {
return;
}
_.memory.Free(commandGroup);
if (newName != none) {
commandGroup = newName.Copy();
} else {
commandGroup = none;
}
}
/// Sets new summary of `Command.Data` under construction.
///
/// Summary gives a short description of the command on the whole that will
/// be displayed when "help" command is listing available command
public final function Summary(BaseText newSummary) {
if (newSummary != none && newSummary == commandSummary) {
return;
}
_.memory.Free(commandSummary);
if (newSummary != none) {
commandSummary = newSummary.Copy();
} else {
commandSummary = none;
}
}
/// Makes caller builder to mark `Command.Data` under construction to require
/// a player target.
public final function RequireTarget() {
requiresTarget = true;
}
/// Any parameters added to currently selected sub-command / option after
/// calling this method will be marked as optional.
///
/// Further calls when the same sub-command / option is selected will do
/// nothing.
public final function OptionalParams()
{
if (selectionIsOptional) {
return;
}
// Record all required parameters first, otherwise there would be no way
// to distinguish between them and optional parameters
RecordSelection();
selectionIsOptional = true;
selectedParameterArray.length = 0;
}
/// Returns data that has been constructed so far by the caller
/// [`CommandDataBuilder`].
///
/// Does not reset progress.
public final function Command.Data BorrowData() {
local Command.Data newData;
// TODO: is this copying needed?
RecordSelection();
newData.group = commandGroup;
newData.summary = commandSummary;
newData.subcommands = subcommands;
newData.options = options;
newData.requiresTarget = requiresTarget;
return newData;
}
// Adds new parameter to selected sub-command / option
private final function PushParameter(Command.Parameter newParameter) {
selectedParameterArray[selectedParameterArray.length] = newParameter;
}
// Fills `Command.ParameterType` struct with given values
// (except boolean format).
// Assumes `displayName != none`.
private final function Command.Parameter NewParameter(
BaseText displayName,
Command.ParameterType parameterType,
bool isListParameter,
optional BaseText variableName
) {
local Command.Parameter newParameter;
newParameter.displayName = displayName.Copy();
newParameter.type = parameterType;
newParameter.allowsList = isListParameter;
if (variableName != none) {
newParameter.variableName = variableName.Copy();
} else {
newParameter.variableName = displayName.Copy();
}
return newParameter;
}
/// Adds new boolean parameter (required or optional depends on whether
/// `OptionalParams()` call happened) to the currently selected
/// sub-command / option.
///
/// Only fails if provided `name` is `none`.
///
/// [`name`] will become the name of the parameter
/// (it would appear in the generated "help" command info).
///
/// [`format`] defines preferred format of boolean values.
/// Command parser will still accept boolean values in any form, this setting
/// only affects how parameter will be displayed in generated help.
///
/// [`variableName`] will become key for this parameter's value in `HashTable`
/// after user's command
/// input is parsed.
/// If left `none`, - will coincide with `name` parameter.
public final function ParamBoolean(
BaseText name,
optional Command.PreferredBooleanFormat format,
optional BaseText variableName
) {
local Command.Parameter newParam;
if (name != none) {
newParam = NewParameter(name, CPT_Boolean, false, variableName);
newParam.booleanFormat = format;
PushParameter(newParam);
}
}
/// Adds new integer list parameter (required or optional depends on whether
/// `OptionalParams()` call happened) to the currently selected
/// sub-command / option.
///
/// Only fails if provided `name` is `none`.
///
/// List parameters expect user to enter one or more value of the same type as
/// command's arguments.
///
/// [`name`] will become the name of the parameter
/// (it would appear in the generated "help" command info).
///
/// [`format`] defines preferred format of boolean values.
/// Command parser will still accept boolean values in any form, this setting
/// only affects how
/// parameter will be displayed in generated help.
///
/// [`variableName`] will become key for this parameter's value in `HashTable`
/// after user's command input is parsed.
/// If left `none`, - will coincide with `name` parameter.
public final function ParamBooleanList(
BaseText name,
optional Command.PreferredBooleanFormat format,
optional BaseText variableName
) {
local Command.Parameter newParam;
if (name != none) {
newParam = NewParameter(name, CPT_Boolean, true, variableName);
newParam.booleanFormat = format;
PushParameter(newParam);
}
}
/// Adds new integer parameter (required or optional depends on whether
/// `OptionalParams()` call happened) to the currently selected
/// sub-command / option.
///
/// Only fails if provided `name` is `none`.
///
/// [`name`] will become the name of the parameter
/// (it would appear in the generated "help" command info).
///
/// [`variableName`] will become key for this parameter's value in `HashTable`
/// after user's command input is parsed.
/// If left `none`, - will coincide with `name` parameter.
public final function ParamInteger(BaseText name, optional BaseText variableName) {
if (name != none) {
PushParameter(NewParameter(name, CPT_Integer, false, variableName));
}
}
/// Adds new integer list parameter (required or optional depends on whether
/// `OptionalParams()` call happened) to the currently selected
/// sub-command / option.
///
/// Only fails if provided `name` is `none`.
///
/// List parameters expect user to enter one or more value of the same type as
/// command's arguments.
///
/// [`name`] will become the name of the parameter (it would appear in
/// the generated "help" command info).
///
/// [`variableName`] will become key for this parameter's value in `HashTable`
/// after user's command input is parsed.
/// If left `none`, - will coincide with `name` parameter.
public final function ParamIntegerList(BaseText name, optional BaseText variableName) {
if (name != none) {
PushParameter(NewParameter(name, CPT_Integer, true, variableName));
}
}
/// Adds new numeric parameter (required or optional depends on whether
/// `OptionalParams()` call happened) to the currently selected
/// sub-command / option.
///
/// Only fails if provided `name` is `none`.
///
/// [`name`] will become the name of the parameter (it would appear in the
/// generated "help" command info).
///
/// [`variableName`] will become key for this parameter's value in `HashTable`
/// after user's command input is parsed.
/// If left `none`, - will coincide with `name` parameter.
public final function ParamNumber(BaseText name, optional BaseText variableName) {
if (name != none) {
PushParameter(NewParameter(name, CPT_Number, false, variableName));
}
}
/// Adds new numeric list parameter (required or optional depends on whether
/// `OptionalParams()` call happened) to the currently selected
/// sub-command / option.
///
/// Only fails if provided `name` is `none`.
///
/// List parameters expect user to enter one or more value of the same type as
/// command's arguments.
///
/// [`name`] will become the name of the parameter (it would appear in the
/// generated "help" command info).
///
/// [`variableName`] will become key for this parameter's value in `HashTable`
/// after user's command input is parsed.
/// If left `none`, - will coincide with `name` parameter.
public final function ParamNumberList(BaseText name, optional BaseText variableName) {
if (name != none) {
PushParameter(NewParameter(name, CPT_Number, true, variableName));
}
}
/// Adds new text parameter (required or optional depends on whether
/// `OptionalParams()` call happened) to the currently selected
/// sub-command / option.
///
/// Only fails if provided `name` is `none`.
///
/// [`name`] will become the name of the parameter (it would appear in the
/// generated "help" command info).
///
/// [`variableName`] will become key for this parameter's value in `HashTable`
/// after user's command input is parsed.
/// If left `none`, - will coincide with `name` parameter.
///
/// [`aliasSourceName`] defines name of the alias source that must be used to
/// auto-resolve this parameter's value. `none` means that parameter will be
/// recorded as-is, any other value (either "weapon", "color", "feature",
/// "entity" or some kind of custom alias source name) will make values prefixed
/// with "$" to be resolved as custom aliases.
/// In case auto-resolving is used, value will be recorded as a `HasTable` with
/// two fields: "alias" - value provided by user and (in case "$" prefix was
/// used) "value" - actual resolved value of an alias.
/// If alias has failed to be resolved, `none` will be stored as a value.
public final function ParamText(
BaseText name,
optional BaseText variableName,
optional BaseText aliasSourceName
) {
local Command.Parameter newParameterValue;
if (name == none) {
return;
}
newParameterValue = NewParameter(name, CPT_Text, false, variableName);
if (aliasSourceName != none) {
newParameterValue.aliasSourceName = aliasSourceName.Copy();
}
PushParameter(newParameterValue);
}
/// Adds new text list parameter (required or optional depends on whether
/// `OptionalParams()` call happened) to the currently selected
/// sub-command / option.
///
/// Only fails if provided `name` is `none`.
///
/// List parameters expect user to enter one or more value of the same type as
/// command's arguments.
///
/// [`name`] will become the name of the parameter (it would appear in the
/// generated "help" command info).
///
/// [`variableName`] will become key for this parameter's value in `HashTable`
/// after user's command input is parsed. If left `none`, - will coincide with
/// `name` parameter.
///
/// [`aliasSourceName`] defines name of the alias source that must be used to
/// auto-resolve this parameter's value. `none` means that parameter will be
/// recorded as-is, any other value (either "weapon", "color", "feature",
/// "entity" or some kind of custom alias source name) will make values prefixed
/// with "$" to be resolved as custom aliases.
/// In case auto-resolving is used, value will be recorded as a `HasTable` with
/// two fields: "alias" - value provided by user and (in case "$" prefix was
/// used) "value" - actual resolved value of an alias.
/// If alias has failed to be resolved, `none` will be stored as a value.
public final function ParamTextList(
BaseText name,
optional BaseText variableName,
optional BaseText aliasSourceName
) {
local Command.Parameter newParameterValue;
if (name == none) {
return;
}
newParameterValue = NewParameter(name, CPT_Text, true, variableName);
if (aliasSourceName != none) {
newParameterValue.aliasSourceName = aliasSourceName.Copy();
}
PushParameter(newParameterValue);
}
/// Adds new remainder parameter (required or optional depends on whether
/// `OptionalParams()` call happened) to the currently selected
/// sub-command / option.
///
/// Only fails if provided `name` is `none`.
///
/// Remainder parameter is a special parameter that will simply consume all
/// remaining command's input as-is.
///
/// [`name`] will become the name of the parameter
/// (it would appear in the generated "help" command info).
///
/// [`variableName`] will become key for this parameter's value in `HashTable`
/// after user's command input is parsed.
/// If left `none`, - will coincide with `name` parameter.
public final function ParamRemainder(BaseText name, optional BaseText variableName) {
if (name != none) {
PushParameter(NewParameter(name, CPT_Remainder, false, variableName));
}
}
/// Adds new JSON object parameter (required or optional depends on whether
/// `OptionalParams()` call happened) to the currently selected
/// sub-command / option.
///
/// Only fails if provided `name` is `none`.
///
/// [`name`] will become the name of the parameter
/// (it would appear in the generated "help" command info).
///
/// [`variableName`] will become key for this parameter's value in `HashTable`
/// after user's command input is parsed.
/// If left `none`, - will coincide with `name` parameter.
public final function ParamObject(BaseText name, optional BaseText variableName) {
if (name != none) {
PushParameter(NewParameter(name, CPT_Object, false, variableName));
}
}
/// Adds new JSON object list parameter (required or optional depends on whether
/// `OptionalParams()` call happened) to the currently selected
/// sub-command / option.
///
/// Only fails if provided `name` is `none`.
///
/// List parameters expect user to enter one or more value of the same type as
/// command's arguments.
///
/// [`name`] will become the name of the parameter
/// (it would appear in the generated "help" command info).
///
/// [`variableName`] will become key for this parameter's value in `HashTable`
/// after user's command input is parsed.
/// If left `none`, - will coincide with `name` parameter.
public final function ParamObjectList(BaseText name, optional BaseText variableName) {
if (name != none) {
PushParameter(NewParameter(name, CPT_Object, true, variableName));
}
}
/// Adds new JSON array parameter (required or optional depends on whether
/// `OptionalParams()` call happened) to the currently selected
/// sub-command / option.
///
/// Only fails if provided `name` is `none`.
///
/// List parameters expect user to enter one or more value of the same type as
/// command's arguments.
///
/// [`name`] will become the name of the parameter
/// (it would appear in the generated "help" command info).
///
/// [`variableName`] will become key for this parameter's value in `HashTable`
/// after user's command input is parsed.
/// If left `none`, - will coincide with `name` parameter.
public final function ParamArray(BaseText name, optional BaseText variableName) {
if (name != none) {
PushParameter(NewParameter(name, CPT_Array, false, variableName));
}
}
/// Adds new JSON array list parameter (required or optional depends on whether
/// `OptionalParams()` call happened) to the currently selected
/// sub-command / option.
///
/// Only fails if provided `name` is `none`.
///
/// List parameters expect user to enter one or more value of the same type as
/// command's arguments.
///
/// [`name`] will become the name of the parameter
/// (it would appear in the generated "help" command info).
///
/// [`variableName`] will become key for this parameter's value in `HashTable`
/// after user's command input is parsed.
/// If left `none`, - will coincide with `name` parameter.
public final function ParamArrayList(BaseText name, optional BaseText variableName) {
if (name != none) {
PushParameter(NewParameter(name, CPT_Array, true, variableName));
}
}
/// Adds new JSON value parameter (required or optional depends on whether
/// `OptionalParams()` call happened) to the currently selected
/// sub-command / option.
///
/// Only fails if provided `name` is `none`.
///
/// List parameters expect user to enter one or more value of the same type as
/// command's arguments.
///
/// [`name`] will become the name of the parameter
/// (it would appear in the generated "help" command info).
///
/// [`variableName`] will become key for this parameter's value in `HashTable`
/// after user's command input is parsed.
/// If left `none`, - will coincide with `name` parameter.
public final function ParamJSON(BaseText name, optional BaseText variableName) {
if (name != none) {
PushParameter(NewParameter(name, CPT_JSON, false, variableName));
}
}
/// Adds new JSON value list parameter (required or optional depends on whether
/// `OptionalParams()` call happened) to the currently selected
/// sub-command / option.
///
/// Only fails if provided `name` is `none`.
///
/// List parameters expect user to enter one or more value of the same type as
/// command's arguments.
///
/// [`name`] will become the name of the parameter
/// (it would appear in the generated "help" command info).
///
/// [`variableName`] will become key for this parameter's value in `HashTable`
/// after user's command input is parsed.
/// If left `none`, - will coincide with `name` parameter.
public final function ParamJSONList(BaseText name, optional BaseText variableName) {
if (name != none) {
PushParameter(NewParameter(name, CPT_JSON, true, variableName));
}
}
/// Adds new parameter that defines a set of players (required or optional
/// depends on whether `OptionalParams()` call happened) to the currently
/// selected sub-command / option.
///
/// Only fails if provided `name` is `none`.
///
/// List parameters expect user to enter one or more value of the same type as
/// command's arguments.
///
/// [`name`] will become the name of the parameter
/// (it would appear in the generated "help" command info).
///
/// [`variableName`] will become key for this parameter's value in `HashTable`
/// after user's command input is parsed.
/// If left `none`, - will coincide with `name` parameter.
public final function ParamPlayers(BaseText name, optional BaseText variableName) {
if (name != none) {
PushParameter(NewParameter(name, CPT_PLAYERS, false, variableName));
}
}
/// Adds new parameter that defines a list of sets of players (required or
/// optional depends on whether `OptionalParams()` call happened) to
/// the currently selected sub-command / option.
///
/// Only fails if provided `name` is `none`.
///
/// List parameters expect user to enter one or more value of the same type as
/// command's arguments.
///
/// [`name`] will become the name of the parameter
/// (it would appear in the generated "help" command info).
///
/// [`variableName`] will become key for this parameter's value in `HashTable`
/// after user's command input is parsed.
/// If left `none`, - will coincide with `name` parameter.
public final function ParamPlayersList(BaseText name,optional BaseText variableName) {
if (name != none) {
PushParameter(NewParameter(name, CPT_PLAYERS, true, variableName));
}
}
// Find index of sub-command with a given name `name` in `subcommands`.
// `-1` if there's not sub-command with such name.
// Case-sensitive.
private final function int FindSubCommandIndex(BaseText name) {
local int i;
if (name == none) {
return -1;
}
for (i = 0; i < subcommands.length; i += 1) {
if (name.Compare(subcommands[i].name)) {
return i;
}
}
return -1;
}
// Find index of option with a given name `name` in `options`.
// `-1` if there's not sub-command with such name.
// Case-sensitive.
private final function int FindOptionIndex(BaseText longName) {
local int i;
if (longName == none) {
return -1;
}
for (i = 0; i < options.length; i += 1) {
if (longName.Compare(options[i].longName)) {
return i;
}
}
return -1;
}
// Creates an empty selection record for subcommand or option with name (long name) `name`.
// Doe not check whether subcommand/option with that name already exists.
// Copies passed `name`, assumes that it is not `none`.
private final function MakeEmptySelection(BaseText name, bool selectedOption) {
selectedItemIsOption = selectedOption;
selectedItemName = name.Copy();
selectedDescription = none;
selectedParameterArray.length = 0;
selectionIsOptional = false;
}
// Select option with a given long name `longName` from `options`.
// If there is no option with specified `longName` in prepared data - creates new record in
// selection, otherwise copies previously saved data.
// Automatically saves previously selected data into prepared data.
// Copies `name` if it has to create new record.
private final function SelectOption(BaseText longName) {
local int optionIndex;
if (longName == none) {
return;
}
if (selectedItemIsOption && selectedItemName != none && selectedItemName.Compare(longName)) {
return;
}
RecordSelection();
optionIndex = FindOptionIndex(longName);
if (optionIndex < 0) {
MakeEmptySelection(longName, true);
return;
}
// Load appropriate prepared data, if it exists for
// option with long name `longName`
selectedItemIsOption = true;
selectedItemName = options[optionIndex].longName;
selectedDescription = options[optionIndex].description;
selectionIsOptional = optionsIsOptional[optionIndex] > 0;
if (selectionIsOptional) {
selectedParameterArray = options[optionIndex].optional;
} else {
selectedParameterArray = options[optionIndex].required;
}
}
// Saves currently selected data into prepared data.
private final function RecordSelection() {
if (selectedItemName == none) {
return;
}
if (selectedItemIsOption) {
RecordSelectedOption();
} else {
RecordSelectedSubCommand();
}
}
// Saves selected sub-command into prepared records.
// Assumes that command and not an option is selected.
private final function RecordSelectedSubCommand() {
local int selectedSubCommandIndex;
local Command.SubCommand newSubcommand;
if (selectedItemName == none) {
return;
}
selectedSubCommandIndex = FindSubCommandIndex(selectedItemName);
if (selectedSubCommandIndex < 0) {
selectedSubCommandIndex = subcommands.length;
subcommands[selectedSubCommandIndex] = newSubcommand;
}
subcommands[selectedSubCommandIndex].name = selectedItemName;
subcommands[selectedSubCommandIndex].description = selectedDescription;
if (selectionIsOptional) {
subcommands[selectedSubCommandIndex].optional = selectedParameterArray;
subcommandsIsOptional[selectedSubCommandIndex] = 1;
} else {
subcommands[selectedSubCommandIndex].required = selectedParameterArray;
subcommandsIsOptional[selectedSubCommandIndex] = 0;
}
}
// Saves currently selected option into prepared records.
// Assumes that option and not an command is selected.
private final function RecordSelectedOption() {
local int selectedOptionIndex;
local Command.Option newOption;
if (selectedItemName == none) {
return;
}
selectedOptionIndex = FindOptionIndex(selectedItemName);
if (selectedOptionIndex < 0) {
selectedOptionIndex = options.length;
options[selectedOptionIndex] = newOption;
}
options[selectedOptionIndex].longName = selectedItemName;
options[selectedOptionIndex].description = selectedDescription;
if (selectionIsOptional) {
options[selectedOptionIndex].optional = selectedParameterArray;
optionsIsOptional[selectedOptionIndex] = 1;
} else {
options[selectedOptionIndex].required = selectedParameterArray;
optionsIsOptional[selectedOptionIndex] = 0;
}
}
// Validates names (printing errors in case of failure) for the option.
// Long name must be at least 2 characters long.
// Short name must be either:
// 1. exactly one character long;
// 2. `none`, which leads to deriving `shortName` from `longName`
// as a first character.
// Anything else will result in logging a failure and rejection of
// the option altogether.
// Returns `none` if validation failed and chosen short name otherwise
// (if `shortName` was used for it - it's value will be copied).
private final function BaseText.Character GetValidShortName(
BaseText longName,
BaseText shortName
) {
// Validate `longName`
if (longName == none) {
return _.text.GetInvalidCharacter();
}
if (longName.GetLength() < 2) {
_.logger.Auto(errLongNameTooShort).ArgClass(class).Arg(longName.Copy());
return _.text.GetInvalidCharacter();
}
// Validate `shortName`,
// deriving if from `longName` if necessary & possible
if (shortName == none) {
return longName.GetCharacter(0);
}
if (shortName.IsEmpty() || shortName.GetLength() > 1) {
_.logger.Auto(errShortNameTooLong).ArgClass(class).Arg(longName.Copy());
return _.text.GetInvalidCharacter();
}
return shortName.GetCharacter(0);
}
// Checks that if any option record has a long/short name from a given pair of
// names (`longName`, `shortName`), then it also has another one.
//
// i.e. we cannot have several options with identical names:
// (--silent, -s) and (--sick, -s).
private final function bool VerifyNoOptionNamingConflict(
BaseText longName,
BaseText.Character shortName
) {
local int i;
local bool sameShortNames, sameLongNames;
// To make sure we will search through the up-to-date `options`,
// record selection into prepared records.
RecordSelection();
for (i = 0; i < options.length; i += 1) {
sameShortNames = _.text.AreEqual(shortName, options[i].shortName);
sameLongNames = longName.Compare(options[i].longName);
if (sameLongNames && !sameShortNames) {
_.logger.Auto(warnSameLongName).ArgClass(class).Arg(longName.Copy());
return true;
}
if (!sameLongNames && sameShortNames) {
_.logger.Auto(warnSameLongName).ArgClass(class).Arg(_.text.FromCharacter(shortName));
return true;
}
}
return false;
}
defaultproperties
{
errLongNameTooShort = (l=LOG_Error,m="Command `%1` is trying to register an option with a name that is way too short (<2 characters). Option will be discarded: %2")
errShortNameTooLong = (l=LOG_Error,m="Command `%1` is trying to register an option with a short name that doesn't consist of just one character. Option will be discarded: %2")
warnSameLongName = (l=LOG_Error,m="Command `%1` is trying to register several options with the same long name \"%2\", but different short names. This should not happen, do not expect correct behavior.")
warnSameShortName = (l=LOG_Error,m="Command `%1` is trying to register several options with the same short name \"%2\", but different long names. This should not have happened, do not expect correct behavior.")
}