Browse Source

Add `CPT_Remainder` parameter type for `Command`s

pull/8/head
Anton Tarasenko 4 years ago
parent
commit
4662f54dea
  1. 1
      sources/Commands/BuiltInCommands/ACommandHelp.uc
  2. 8
      sources/Commands/Command.uc
  3. 27
      sources/Commands/CommandDataBuilder.uc
  4. 39
      sources/Commands/CommandParser.uc
  5. 2
      sources/Commands/Tests/MockCommandB.uc
  6. 26
      sources/Commands/Tests/TEST_Command.uc

1
sources/Commands/BuiltInCommands/ACommandHelp.uc

@ -268,6 +268,7 @@ private final function PrintParameter(ConsoleWriter cout, Parameter parameter)
cout.UseColor(_.color.typeNumber);
break;
case CPT_Text:
case CPT_Remainder:
cout.UseColor(_.color.typeString);
break;
case CPT_Object:

8
sources/Commands/Command.uc

@ -61,11 +61,19 @@ enum ErrorType
*/
enum ParameterType
{
// Parses into `BoolBox`
CPT_Boolean,
// Parses into `IntBox`
CPT_Integer,
// Parses into `FloatBox`
CPT_Number,
// Parses into `Text`
CPT_Text,
// Special parameter that consumes the rest of the input into `Text`
CPT_Remainder,
// Parses into `AssociativeArray`
CPT_Object,
// Parses into `DynamicArray`
CPT_Array
};

27
sources/Commands/CommandDataBuilder.uc

@ -812,6 +812,33 @@ public final function CommandDataBuilder ParamTextList(
return self;
}
/**
* Adds new remainder parameter (required or optional depends on whether
* `RequireTarget()` call happened) to the currently selected
* sub-command / option.
*
* Only fails if provided `name` is `none`.
*
* @param name Name of the parameter, will be copied
* (as it would appear in the generated help info).
* @param variableName Name of the variable that will store this
* parameter's value in `AssociativeArray` after user's command input
* is parsed. Provided value will be copied.
* If left `none`, - will coincide with `name` parameter.
* @return Returns the caller `CommandDataBuilder` to allow for
* method chaining.
*/
public final function CommandDataBuilder ParamRemainder(
Text name,
optional Text variableName)
{
if (name == none) {
return self;
}
PushParameter(NewParameter(name, CPT_Remainder, false, variableName));
return self;
}
/**
* Adds new object parameter (required or optional depends on whether
* `RequireTarget()` call happened) to the currently selected

39
sources/Commands/CommandParser.uc

@ -54,7 +54,8 @@ class CommandParser extends AcediaObject
*
* Finally, to allow users to specify options at any point in command,
* we call `TryParsingOptions()` at the beginning of every
* `ParseSingleValue()`, since option definition can appear at any place
* `ParseSingleValue()` (the only parameter that has higher priority than
* options is `CPT_Remainder`), since option definition can appear at any place
* between parameters. We also call `TryParsingOptions()` *after* we've parsed
* all command's parameters, since that case won't be detected by parsing
* them *before* every parameter.
@ -397,8 +398,13 @@ private final function bool ParseSingleValue(
AssociativeArray parsedParameters,
Command.Parameter expectedParameter)
{
// Before parsing a value we need to check if user has specified any
// options instead.
// First we try `CPT_Remainder` parameter, since it is a special case that
// consumes all further input
if (expectedParameter.type == CPT_Remainder) {
return ParseRemainderValue(parsedParameters, expectedParameter);
}
// Before parsing any other value we need to check if user has
// specified any options instead.
// However this might lead to errors if we are already parsing
// necessary parameters of another option:
// we must handle such situation and report an error.
@ -426,6 +432,9 @@ private final function bool ParseSingleValue(
else if (expectedParameter.type == CPT_Text) {
return ParseTextValue(parsedParameters, expectedParameter);
}
else if (expectedParameter.type == CPT_Remainder) {
return ParseRemainderValue(parsedParameters, expectedParameter);
}
else if (expectedParameter.type == CPT_Object) {
return ParseObjectValue(parsedParameters, expectedParameter);
}
@ -549,6 +558,25 @@ private final function bool ParseTextValue(
return true;
}
// Assumes `commandParser` and `parsedParameters` are not `none`.
// Parses a single `Text` value into given `parsedParameters`
// associative array, consuming all remaining contents.
private final function bool ParseRemainderValue(
AssociativeArray parsedParameters,
Command.Parameter expectedParameter)
{
local string textValue;
// TODO: use parsing methods into `Text`
// (needs some work for reading formatting `string`s from `Text` objects)
commandParser.Skip().MUntilS(textValue);
if (!commandParser.Ok()) {
return false;
}
RecordParameter(parsedParameters, expectedParameter,
_.text.FromFormattedString(textValue));
return true;
}
// Assumes `commandParser` and `parsedParameters` are not `none`.
// Parses a single JSON object into given `parsedParameters`
// associative array.
@ -782,7 +810,10 @@ private final function bool AddOptionByCharacter(
private final function bool ParseOptionParameters(Command.Option pickedOption)
{
local AssociativeArray optionParameters;
if (currentTargetIsOption && currentTarget != CPT_ExtraParameter) {
// If we are already parsing other option's parameters and did not finish
// parsing all required ones - we cannot start another option
if (currentTargetIsOption && currentTarget != CPT_ExtraParameter)
{
DeclareError(CET_NoRequiredParamForOption, targetOption.longName);
return false;
}

2
sources/Commands/Tests/MockCommandB.uc

@ -41,6 +41,8 @@ protected function BuildData(CommandDataBuilder builder)
.OptionalParams()
.ParamNumberList(P("numeric list"), P("list"))
.ParamBoolean(P("maybe"));
builder.Option(P("remainder"))
.ParamRemainder(P("everything"));
}
defaultproperties

26
sources/Commands/Tests/TEST_Command.uc

@ -1,6 +1,6 @@
/**
* Set of tests for `Command` class.
* Copyright 2020 Anton Tarasenko
* Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
@ -23,7 +23,7 @@ class TEST_Command extends TestCase
var string queryASuccess1, queryASuccess2, queryASuccess3, queryASuccess4;
var string queryAFailure1, queryAFailure2;
var string queryBSuccess1, queryBSuccess2;
var string queryBSuccess1, queryBSuccess2, queryBSuccess3;
var string queryBFailure1, queryBFailure2, queryBFailure3;
var string queryBFailureUnknownOptionLong, queryBFailureUnknownOptionShort;
var string queryBFailureUnused;
@ -59,6 +59,7 @@ protected static function Test_MockB()
SubTest_MockBFailed();
SubTest_MockBQ1();
SubTest_MockBQ2();
SubTest_MockBQ3Remainder();
}
protected static function Test_CommandCallErrors()
@ -381,6 +382,26 @@ protected static function SubTest_MockBQ2()
TEST_ExpectTrue(IntBox(subArray.GetItem(0)).Get() == 8);
}
protected static function SubTest_MockBQ3Remainder()
{
local CommandCall result;
local DynamicArray subArray;
local AssociativeArray options, subObject;
Issue("Cannot parse command queries with `CPT_Remainder` type parameters.");
result = class'MockCommandB'.static.GetInstance()
.ProcessInput(PRS(default.queryBSuccess3), none);
TEST_ExpectTrue(result.GetParameters().GetLength() == 1);
subArray = DynamicArray(result.GetParameters().GetItem(P("list")));
TEST_ExpectTrue(FloatBox(subArray.GetItem(0)).Get() == 3);
TEST_ExpectTrue(FloatBox(subArray.GetItem(1)).Get() == -76);
options = result.GetOptions();
TEST_ExpectTrue(options.GetLength() == 1);
TEST_ExpectTrue(options.HasKey(P("remainder")));
subObject = AssociativeArray(options.GetItem(P("remainder")));
TEST_ExpectTrue( Text(subObject.GetItem(P("everything"))).ToPlainString()
== "--type \"value\" -va 8 -sV --forced -T \"\" 32");
}
// [1, 2, 3, 6]
defaultproperties
{
caseName = "Command"
@ -395,6 +416,7 @@ defaultproperties
queryBSuccess1 = "[7, null] --values 1 3 5 2 4 text"
queryBSuccess2 = "do --type \"value\" -va 8 -sV --forced -T \"\" "
queryBSuccess3 = "do 3 -76 -r --type \"value\" -va 8 -sV --forced -T \"\" 32"
// long then same as short
queryBFailure1 = "[] 8 -tv 13"
queryBFailure2 = "do 7 5 -sfV --forced yes"

Loading…
Cancel
Save