Skip to content

Commit

Permalink
Merge branch 'master' into subcommand
Browse files Browse the repository at this point in the history
  • Loading branch information
andrey-zherikov committed Jan 11, 2024
2 parents 5c92339 + 3d1e8b2 commit e5bb105
Show file tree
Hide file tree
Showing 6 changed files with 541 additions and 345 deletions.
82 changes: 57 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1396,91 +1396,123 @@ assert(CLI!T.parseArgs!((T t) { assert(t == T(Value("foo"))); return 12345; })([

## Argument parsing customization

Some time the functionality provided out of the box is not enough and it needs to be tuned.
Sometime the functionality that is provided out of the box is not enough and needs to be tuned.

Parsing of command-line string values into some typed `receiver` member consists of multiple steps:
Parsing of command-line string values into some typed `receiver` member consists of following steps:

- **Pre-validation** – argument values are validated as raw strings.
- **Parsing** – raw argument values are converted to a different type (usually the type of the receiver).
- **Validation** – converted value is validated.
- **Action** – depending on a type of the `receiver`, it might be either assignment of converted value to a `receiver`,
appending value if `receiver` is an array or other operation.

In case if argument does not expect any value, then the only one step is involved:
In case if argument does not have any value to parse, then the only one step is involved:

- **Action if no value** – similar to **Action** step above but without converted value.

If any of the steps fails, then the command-line parsing fails as well.

Each of the steps above can be customized with UDA modifiers below. These modifiers take a function that might accept
either argument value(s) or `Param` struct that has these fields (there is also an alias, `RawParam`, where the type of
the `value` field is `string[]`):
either argument value(s) or `Param` struct that has the fields below:

- `config`- Config object that is passed to parsing function.
- `name` – Argument name that is specified in command line.
- `value` – Array of argument values that are provided in command line.

There is also an alias, `RawParam`, where the type of the `value` field is `string[]`. This alias represents "raw"
values from command line.

### Pre-validation

`PreValidation` modifier can be used to customize the validation of raw string values. It accepts a function with one of
the following signatures:

- `bool validate(string value)`
- `bool validate(string[] value)`
- `bool validate(RawParam param)`
- `bool validate(string value)`
- `Result validate(string value)`
- `bool validate(string[] value)`
- `Result validate(string[] value)`
- `bool validate(RawParam param)`
- `Result validate(RawParam param)`

Note that the first function will be called once for every value specified in command line for an argument.

Parameters:

- `value`/`param` values to be parsed.

Return value:

The function should return `true` if validation passed and `false` otherwise.
- `true`/`Result.Success` if validation passed or
- `false`/`Result.Error` otherwise.

### Parsing

`Parse` modifier allows providing custom conversion from raw string to typed value. It accepts a function with one of
`Parse` modifier allows providing custom conversion from raw string to a typed value. It accepts a function with one of
the following signatures:

- `ParseType parse(string value)`
- `ParseType parse(string[] value)`
- `ParseType parse(RawParam param)`
- `bool parse(ref ParseType receiver, RawParam param)`
- `void parse(ref ParseType receiver, RawParam param)`
- `bool parse(ref ParseType receiver, RawParam param)`
- `void parse(ref ParseType receiver, RawParam param)`
- `Result parse(ref ParseType receiver, RawParam param)`

Note that `ParseType` is a type that a string value is supposed to be parsed to and it is not required be the same as
a target type.

Parameters:

- `ParseType` is a type that the string value will be parsed to.
- `value`/`param` values to be parsed.
- `receiver` is an output variable for parsed value.

Parse function is supposed to parse values from `value`/`param` parameter into `ParseType` type and optionally return
boolean type indicating whether parsing was done successfully (`true`) or not (`false`).
Return value:

- `true`/`Result.Success` if parsing was successful or
- `false`/`Result.Error` otherwise.

### Validation

`Validation` modifier can be used to validate the parsed value. It accepts a function with one of the following
`Validation` modifier can be used to validate parsed value. It accepts a function with one of the following
signatures:

- `bool validate(ParseType value)`
- `bool validate(ParseType[] value)`
- `bool validate(Param!ParseType param)`
- `bool validate(ParseType value)`
- `Result validate(ParseType value)`
- `bool validate(ParseType[] value)`
- `Result validate(ParseType[] value)`
- `bool validate(Param!ParseType param)`
- `Result validate(Param!ParseType param)`

Parameters:

- `value`/`param` has a value returned from `Parse` step.
- `value`/`param` contains a value returned from `Parse` step.

The function should return `true` if validation passed and `false` otherwise.
Return value:

- `true`/`Result.Success` if validation passed or
- `false`/`Result.Error` otherwise.

### Action

`Action` modifier allows providing a custom logic of how `receiver` should be changed when argument has a value in
command line. It accepts a function with one of the following signatures:

- `bool action(ref T receiver, ParseType value)`
- `void action(ref T receiver, ParseType value)`
- `bool action(ref T receiver, Param!ParseType param)`
- `void action(ref T receiver, Param!ParseType param)`
- `bool action(ref T receiver, ParseType value)`
- `void action(ref T receiver, ParseType value)`
- `Result action(ref T receiver, ParseType value)`
- `bool action(ref T receiver, Param!ParseType param)`
- `void action(ref T receiver, Param!ParseType param)`
- `Result action(ref T receiver, Param!ParseType param)`

Parameters:

- `receiver` is a receiver (destination field) which is supposed to be changed based on a `value`/`param`.
- `value`/`param` has a value returned from `Parse` step.

Return value:

- `true`/`Result.Success` if operation was successful or
- `false`/`Result.Error` otherwise.

### Arguments with no values

Sometimes arguments are allowed to have no values in command line. Here are two cases that arise in this situation:
Expand Down
2 changes: 1 addition & 1 deletion source/argparse/internal/help.d
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ package struct HelpArgumentUDA
auto output = stdout.lockingTextWriter();
printHelp(_ => output.put(_), config, cmdStack[$-1], args, progName);

return Result(0);
return Result.Error(0, ""); // hack to force-exit (to be removed)
}

auto parse(Config config, COMMAND_STACK, RECEIVER)(const COMMAND_STACK cmdStack, ref RECEIVER, string argName, string[] rawValues)
Expand Down
84 changes: 65 additions & 19 deletions source/argparse/internal/parsehelpers.d
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,52 @@ import argparse.result;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package auto Convert(T)(string value)
package Result Convert(T)(ref T receiver, RawParam param)
{
import std.conv: to;
return value.length > 0 ? value.to!T : T.init;
try
{
import std.conv: to;

foreach (value; param.value)
receiver = value.length > 0 ? value.to!T : T.init;

return Result.Success;
}
catch(Exception e)
{
return Result.Error(e.msg);
}
}

unittest
{
assert(Convert!int("7") == 7);
assert(Convert!string("7") == "7");
assert(Convert!char("7") == '7');
alias test(T) = (string value)
{
T receiver;
assert(Convert!T(receiver, RawParam(null, "", [value])));
return receiver;
};

assert(test!int("7") == 7);
assert(test!string("7") == "7");
assert(test!char("7") == '7');
}

unittest
{
alias testErr(T) = (string value)
{
T receiver;
return Convert!T(receiver, RawParam(null, "", [value]));
};

assert(testErr!int("unknown").isError());
assert(testErr!bool("unknown").isError());
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package(argparse) auto PassThrough(string[] values)
package(argparse) string[] PassThrough(string[] values)
{
return values;
}
Expand All @@ -32,45 +62,61 @@ unittest

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package auto Assign(DEST, SRC=DEST)(ref DEST param, SRC value)
package struct Assign
{
param = value;
static void opCall(T)(ref T param, T[] value)
{
foreach(ref v; value)
param = v;
}
static void opCall(T)(ref T receiver, T value)
{
receiver = value;
}
}

unittest
{
int i;
Assign!(int)(i,7);
Assign(i,7);
assert(i == 7);

Assign(i,[1,2,3]);
assert(i == 3);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package auto Append(T)(ref T param, T value)
package struct Append
{
param ~= value;
static void opCall(T)(ref T param, T value)
{
param ~= value;
}
}


unittest
{
int[] i;
Append!(int[])(i, [1, 2, 3]);
Append!(int[])(i, [7, 8, 9]);
Append(i, [1, 2, 3]);
Append(i, [7, 8, 9]);
assert(i == [1, 2, 3, 7, 8, 9]);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package auto Extend(T)(ref T[] param, T value)
package struct Extend
{
param ~= value;
static void opCall(T)(ref T[] param, T value)
{
param ~= value;
}
}

unittest
{
int[][] i;
Extend!(int[])(i,[1,2,3]);
Extend!(int[])(i,[7,8,9]);
Extend(i,[1,2,3]);
Extend(i,[7,8,9]);
assert(i == [[1,2,3],[7,8,9]]);
}

Expand Down
5 changes: 3 additions & 2 deletions source/argparse/internal/parser.d
Original file line number Diff line number Diff line change
Expand Up @@ -525,8 +525,9 @@ private struct Parser
else
{
// Provide suggestions for the last argument only

return Result(0, Result.Status.success, "", cmdStack.map!((ref _) => _.suggestions(args[0])).join);
auto res = Result.Success;
res.suggestions = cmdStack.map!((ref _) => _.suggestions(args[0])).join;
return res;
}
}
else
Expand Down
Loading

0 comments on commit e5bb105

Please sign in to comment.