Skip to content

SpecialCommands

DoctorKrolic edited this page Oct 23, 2024 · 5 revisions

As a result of parse operation so called "special command" instead of options object can be parsed. In such case ParseResult<T> does not contain Options, but contains non-null SpecialCommandHandler. Calling Handle method on that property executes logic of parsed special command and returns an exit code, which is typically used to exit an app with (e.g. ExecutesDefaults implementation does that).

Special commands' semantics

Each special command declares its aliases in a form of a collection of strings. These aliases are used to identify command in arguments. Special command can only be called as the first argument and if one is detected, parsing stops and all other arguments are skipped. So, for instance, if there is a common --help special command declared, passing arguments yourapp --help and yourapp --help other args are identical and both lead to the command being parsed.

There are 2 built-in special commands generated by default for every parser:

  • --version command
  • --help command

--version command

There is only one handler of this command generated per assembly. Calling this command writes assembly name and version, restricted to 3 digits, to Console.Out and always returns exit code 0. The generated handler is ArgumentParsing.Generated.VersionCommandHandler. Example output of --version command:

TestConsoleApp 1.0.0

--help command

There is one handler of this command per options type. Name of the handler is ArgumentParsing.Generated.HelpCommandHandler_<encoded options type name>. <encoded options type name> is full options type name with dots (.) replaced with underscores (_). So if your options type is YourAppName.Options, name of its help command handler is ArgumentParsing.Generated.HelpCommandHandler_YourAppName_Options.

Calling help command handler writes default help screen to Console.Out and always returns exit code 0.

The help screen text is generated via a separate static GenerateHelpText method. The method also accepts an optional errors parameter. If this parameter is supplied help screen will contain errors section, where all errors are listed. This is used in ExecuteDefaults implementation for error scenarios. So given an options type:

using ArgumentParsing;

[OptionsType]
class MyOptions
{
    [Option('o')]
    public int Option { get; set; }

    [Parameter(0)]
    public int Value { get; set; }
}

Here is a help screen for it:

TestConsoleApp 1.0.0
Copyright (C) 2024

OPTIONS:

  -o, --option

PARAMETERS:

  value (at index 0)

COMMANDS:

  --help        Show help screen

  --version     Show version information

And the same screen with errors section:

TestConsoleApp 1.0.0
Copyright (C) 2024

ERROR(S):
  Value 'a' is in incorrect format for option 'o'
  Unknown option 'a' in argument '-a'

OPTIONS:

  -o, --option

PARAMETERS:

  value (at index 0)

COMMANDS:

  --help        Show help screen

  --version     Show version information

It is possible to add additional help-related information via [HelpInfo(description)] attribute from ArgumentParsing.SpecialCommands.Help namespace where description is a description of that member. Here is an example of a previous options type with additional help info:

using ArgumentParsing;

[OptionsType]
class MyOptions
{
    [Option('o'), HelpInfo("This is an option")]
    public int Option { get; set; }

    [Parameter(0), HelpInfo("This is a parameter")]
    public int Value { get; set; }
}

And here is updated help screen:

TestConsoleApp 1.0.0
Copyright (C) 2024

ERROR(S):
  Unknown option 'a' in argument '-a'

OPTIONS:

  -o, --option  This is an option

PARAMETERS:

  value (at index 0)    This is a parameter

COMMANDS:

  --help        Show help screen

  --version     Show version information

It is possible to completely change default help screen without overriding the default --help command implementation by using [HelpTextGenerator] attribute from ArgumentParsing.SpecialCommands.Help namespace. This way you can restructure help screen however you want with minimal effort required. The attribute is applied to an options type and specifies a method, which is gonna be used for generating help screen text for that type. The method is also used by ExecuteDefaults implementation to generate error help screen.

[HelpTextGenerator] accepts a type and a method name. The method must be accessible within assembly and have the following signature: static string MethodName(ParseErrorCollection? errors = null). The method either receives null as an argument for errors or the collection with at least one element, in which case the generated text is expected to contain some sort of an error section. Here is an example of a valid help text generator declaration:

using ArgumentParsing;
using ArgumentParsing.Results.Errors;
using ArgumentParsing.SpecialCommands.Help;

[OptionsType, HelpTextGenerator(typeof(HelpUtilities), nameof(HelpUtilities.GenerateHelpText))]
class MyOptions
{
}

static class HelpUtilities
{
    public static string GenerateHelpText(ParseErrorCollection? errors = null)
    {
        // implementation
    }
}

Tip

Use nameof() for method name parameter of [HelpTextGenerator] attribute for better IDE experience and clarity.

Note

Nothing prevents you from declaring help text generator method inside options type itself:

using ArgumentParsing;
using ArgumentParsing.Results.Errors;
using ArgumentParsing.SpecialCommands.Help;

[OptionsType, HelpTextGenerator(typeof(MyOptions), nameof(GenerateHelpText))]
class MyOptions
{
    public static string GenerateHelpText(ParseErrorCollection? errors = null)
    {
        // implementation
    }
}

If options type has custom [HelpTextGenerator] attribute then the default --help command will not generate its own GenerateHelpText method.

Changing default special command list

There are two kinds of special commands: built-in and additional ones. Both are controlled at the level of an argument parser method.

Built-in commands

Built-in, as pointed above, are --help and --version commands. They can be disabled or replaced with additional commands. To control which built-ins are available, [GeneratedArgumentParser] attribute has an optionsl BuiltInCommandHandlers property of type BuiltInCommandHandlers from ArgumentParsing.SpecialCommands namespace, which is a [Flags] enum. If property is not specified, both --help and --version are generated, otherwise its value is used. Here are a few examples:

[GeneratedArgumentParser(BuiltInCommandHandlers = BuiltInCommandHandlers.None)] // no built-in commands
[GeneratedArgumentParser(BuiltInCommandHandlers = BuiltInCommandHandlers.Help)] // only `--help` built-in command
[GeneratedArgumentParser(BuiltInCommandHandlers = BuiltInCommandHandlers.Version)] // only `--version` built-in command
[GeneratedArgumentParser(BuiltInCommandHandlers = BuiltInCommandHandlers.Version | BuiltInCommandHandlers.Version)] // both `--help` and `--version` built-in commands. This is equivalent to not specifying `BuiltInCommandHandlers` property at all

Additional commands

Additional commands are added using AdditionalCommandHandlers property as a type array:

[GeneratedArgumentParser(AdditionalCommandHandlers = [typeof(AdditionalCommandHandler), typeof(AnotherAdditionalCommandHandler)])]

Each type in that array is a special command handler, which must have the following traits:

  • It must implement ISpecialCommandHandler interface from ArgumentParsing.SpecialCommands namespace
  • It must be annotated with [SpecialCommandAliases] attribute with at least one declared alias

So a valid command handler looks like this:

using ArgumentParsing.SpecialCommands;

[SpecialCommandAliases("--info")]
class InfoCommandHandler : ISpecialCommandHandler
{
    public int HandleCommand()
    {
        // implementation
    }
}

Full example of using this command handler is the following:

using ArgumentParsing;
using ArgumentParsing.Results;
using ArgumentParsing.SpecialCommands;

partial class Program
{
    [GeneratedArgumentParser(AdditionalCommandHandlers = [typeof(InfoCommandHandler)])]
    public static partial ParseResult<MyOptions> ParseArguments(string[] args);
}

[OptionsType]
class MyOptions
{
}

[SpecialCommandAliases("--info")]
class InfoCommandHandler : ISpecialCommandHandler
{
    public int HandleCommand()
    {
        // implementation
    }
}

Tip

In order to replace a built-in handler, you need to disable default implementation and provide your own as an additional handler. For instance, here is how you can replace a built-in --help command:

using ArgumentParsing;
using ArgumentParsing.Results;
using ArgumentParsing.SpecialCommands;

partial class Program
{
    [GeneratedArgumentParser(BuiltInCommandHandlers = BuiltInCommandHandlers.Version, AdditionalCommandHandlers = [typeof(MyHelpCommandHandler)])]
    public static partial ParseResult<MyOptions> ParseArguments(string[] args);
}

[OptionsType]
class MyOptions
{
}

[SpecialCommandAliases("--help")]
class MyHelpCommandHandler : ISpecialCommandHandler
{
    public int HandleCommand()
    {
        // implementation
    }
}

Of course, by doing this you loose all built-in functionality like custom help text generator via [HelpTextGenerator] attribute.

Command help info

If your parser has built-in --help command, it will list all commands to the corresponding section of the help screen. You can add help descriptions to additional command handlers or change default help description for built-in ones.

For additional command handlers, you just annotate command handler type with [HelpInfo] attribute:

using ArgumentParsing.SpecialCommands;
using ArgumentParsing.SpecialCommands.Help;

[SpecialCommandAliases("--info"), HelpInfo("Show information about the app")]
class InfoCommandHandler : ISpecialCommandHandler
{
    public int HandleCommand()
    {
        // implementation
    }
}

For built-in commands, you can annotate argument parser method with [BuiltInCommandHelpInfo] attribute with desired help description:

using ArgumentParsing;
using ArgumentParsing.Results;
using ArgumentParsing.SpecialCommands;
using ArgumentParsing.SpecialCommands.Help;

partial class Program
{
    [GeneratedArgumentParser]
    [BuiltInCommandHelpInfo(BuiltInCommandHandlers.Help, "Custom help command description")]
    [BuiltInCommandHelpInfo(BuiltInCommandHandlers.Version, "Custom version command description")]
    public static partial ParseResult<MyOptions> ParseArguments(string[] args);
}
Clone this wiki locally