Argument Separator#
The argument separater --
has been adopted by various tools to serve two different strategies: end-of-options indicator and pass-thru arguments.
Use AppSettings.Parser.DefaultArgumentSeparatorStrategy
to specify which strategy to use.
The default is EndOfOptions
so that by default users have it as a fallback to enter any value as an operand.
The strategy can be changed for a command using the Command
attribute: [Command(ArgumentSeparatorStrategy=ArgumentSeparatorStrategy.PassThru)]
.
We recommend leaving the default as EndOfOptions
and overriding on the Command
when you have a command that expects to receive arguments to pass thru to another process.
End of Options Indicator#
The first -- argument that is not an option-argument should be accepted as a delimiter indicating the end of options. Any following arguments should be treated as operands, even if they begin with the '-' character.
This convention was created to handle cases where operand values begin with -
or --
causing the parser to interpret them as options.
All operands after the first --
will be stored in CommandContext.ParseResult.SeparatedArguments
regardless of whether they were expected or not.
Let's look at an example.
public void EndOfOptions(IConsole console, CommandContext ctx, string? arg1)
{
var parserSettings = ctx.AppConfig.AppSettings.Parser;
console.WriteLine("IgnoreUnexpectedOperands: " +
parserSettings.IgnoreUnexpectedOperands);
console.WriteLine("DefaultArgumentSeparatorStrategy: " +
parserSettings.DefaultArgumentSeparatorStrategy);
console.WriteLine();
console.WriteLine($"arg1: {arg1}");
console.WriteLine($"separated: {string.Join(',', ctx.ParseResult!.SeparatedArguments)}");
console.WriteLine($"remaining: {string.Join(',', ctx.ParseResult!.RemainingOperands)}");
}
This command expects a single operand, but if the operand value looks like an option, the parser will throw an exception
$ example.exe EndOfOptions --option-mask
Unrecognized option '--option-mask'
Usage: example.exe EndOfOptions [<arg1>]
Arguments:
arg1 <TEXT>
But if we put the separator before the operand value, the parser will interpret the value as an operand
$ example.exe EndOfOptions -- --option-mask
IgnoreUnexpectedOperands: False
DefaultArgumentSeparatorStrategy: EndOfOptions
arg1: --option-mask
separated: --option-mask
remaining:
Separated operands are available in CommandContext.ParseResult.SeparatedArguments
Unexpected Operands#
Unexpected operands occur when there are no longer operands to assign values to. This will result in a parsing exception. Using the EndOfOptions command from above...
$ example.exe EndOfOptions expected unexpected
Unrecognized command or argument 'unexpected'
Usage: example.exe EndOfOptions [<arg1>]
Arguments:
arg1 <TEXT>
set AppSettings.Parser.IgnoreUnexpectedOperands = true
or [Command(IgnoreUnexpectedOperands = true)]
to ignore unexpected operands.
$ example.exe EndOfOptions expected unexpected
IgnoreUnexpectedOperands: True
DefaultArgumentSeparatorStrategy: EndOfOptions
arg1: expected
separated:
remaining: unexpected
Unexpected operands are available in CommandContext.ParseResult.RemainingOperands
Pass-thru arguments#
While the Posix guideline specifies the --
should be used as an end-of-options indicator, there's a common pattern
to use --
to denote arguments to be passed to a sub-process.
For example, dotnet.exe has this discription for --
:
Delimits arguments to dotnet run from arguments for the application being run. All arguments after this delimiter are passed to the application run.
Let's modify the EndOfOptions example using the [Command]
attribute to set the ArgumentSeparatorStrategy
and IgnoreUnexpectedOperands
[Command(ArgumentSeparatorStrategy = ArgumentSeparatorStrategy.PassThru)]
public void PassThru(IConsole console, CommandContext ctx, string? arg1)
{
var parserSettings = ctx.AppConfig.AppSettings.Parser;
console.WriteLine("IgnoreUnexpectedOperands: " +
parserSettings.IgnoreUnexpectedOperands);
console.WriteLine("DefaultArgumentSeparatorStrategy: " +
parserSettings.DefaultArgumentSeparatorStrategy);
console.WriteLine();
console.WriteLine($"arg1: {arg1}");
console.WriteLine($"separated: {string.Join(',', ctx.ParseResult!.SeparatedArguments)}");
console.WriteLine($"remaining: {string.Join(',', ctx.ParseResult!.RemainingOperands)}");
}
Help will append [[--] <arg>...]
to the usage example when ArgumentSeparatorStrategy.PassThru
is used.
$ example.exe PassThru -h
Usage: example.exe PassThru [<arg1>] [[--] <arg>...]
Arguments:
arg1 <TEXT>
Here is how an unexpected arg is processed with the separator
$ example.exe PassThru expected -- pass-thru
IgnoreUnexpectedOperands: False
DefaultArgumentSeparatorStrategy: EndOfOptions
arg1: expected
separated: pass-thru
remaining:
How to support both?#
Explicit support for both concepts is complicated to provide generically because the framework cannot know
- if a operands for a given command can be formatted like options or directives
- if a command can expect pass-thru arguments
- if a user entered
--
to indicate end-of-options or pass-thru arguments
Due to this complexity, we'll give you the data and let you determine the best approach based on the requirements of the command.
Argument Parsing Diagram#
This diagram shows how the parser handles options and operands based on settings.