Skip to content
DoctorKrolic edited this page Oct 22, 2024 · 8 revisions

Prerequisites

Before diving into how library works there is one statement to be made: the central point of the library is generation of argument parser method, which is done in a standard manner for SG-related tasks: user specifies partial declaration part and annotates it with an attribute, so that generator can kick in and emit partial implementation part. However, since argument parser method must return non-void type, this is considered to be an extended partial methods feature, which is available only for C# 9 and above. This does not mean, that you cannot use the library e.g. on .NET Framework, which is C# 7.3 at max. It just means that you have to explicitly specify C# language version for target frameworks, that by default has version lower than C# 9. E.g. here is an example of project file (.csproj), which uses .NET Framework as a runtime, but configures language version to be C# 9:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net472</TargetFramework>
    <LangVersion>9</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="ArgumentParsing" Version="<Current library version>" />
  </ItemGroup>
</Project>

The general recommendation, however, is since you already have to specify language version explicitly, you can just set it to latest to potentially unlock more feature for you and generator to use. For the rest of this wiki it will be assumed, that you have configured language version of your project correctly and won't run into "Feature X is not available on your C# version Y" errors.

Getting started

Argument parser method

As already mentioned before, the central point of the library is generation of argument parser method. Here is an example of valid declaration of such method:

using ArgumentParsing;
using ArgumentParsing.Results;

partial class Program
{
    [GeneratedArgumentParser]
    private static partial ParseResult<Options> ParseArguments(string[] args);
}

[OptionsType]
class Options
{
}

In the given example implementation of ParseArguments method and several helper classes will be supplied by the source generator, included in the library. In general, as soon as you annotate a partial declaration method with [GeneratedArgumentParser] attribute from ArgumentParsing namespace generator kicks in and tries to analyze method declaration and emit additional sources if possible. There are several restrictions applied to the signature of such parser method:

  • It must accept only one parameter by value, so no modifiers like this, ref, scoped and so on are allowed
  • Type of that parameter bust be a collection of strings. "Collection of strings" is defined as type, which can be enumerated in foreach loop with type of iterable variable being string. Examples of such types are string[], List<string>, IEnumerable<string>, ReadOnlySpan<string> and so on. Things like HashSet<string> are also possible because they are legit collections of strings, but are not recommended since order of values when enumerating sets isn't strictly defined and can differ from the order, in which elements are added into such collection
  • The return type of parser method must be a ParseResult<T> from ArgumentParsing.Results namespace where T is a valid options type
  • Valid options type is non-special class or struct, which has a parameterless constructor
  • Valid options type must be annotated with [OptionsType] attribute from ArgumentParsing namespace

If at least one condition from the list above is not satisfied, an approproate error diagnostic is gonna be reported in the editor or during command line build and generator won't produce any additional sources for this parser method.

There are no additional restrictions for parser method name and name of its parameter. However, library defines a convention, that argument parser's parameter should be named args.

Options type

As soon as any class/struct is annotated with [OptionsType] attribute it becomes an options type. Additional diagnostics, including compiler errors, can be reported on the type itself and/or its members no matter whether the type is actually used in generated argument parser or not. Options type contains properties, which are gonna be parsed from the supplied arguments by the argument parser method. There are two types of properties from the perspective of a parser: actual options and parameters, including ordinal parameters and remaining parameters. For detailed information about them check out options and parameters pages respectively.

Result of argument parsing

As a result of argument parsing operation you get instance of ParseResult<T> structure. This type represent result of a parse operation in several different states. State of the given result can be accessed via its State property, which is of ParseResultState enum type from ArgumentParsing.Results namespace. Depending on its value ParseResult<T> can be in one of following states:

  • ParseResultState.ParsedOptions. In such case options type is correctly parsed from arguments without any errors. ParseResult<T> contains property of options type T called Options. In this case it holds the value of parsed options object
  • ParseResultState.ParsedWithErrors. In such case at least one parse error occured during argument parsing. Errors property of ParseResult<T> contains collection of errors occured then. For more details on parse errors check out parse errors page
  • ParseResultState.ParsedSpecialCommand. In such case a special command (e.g. --help or --version) was parsed from the supplied arguments. SpecialCommandHandler property of ParseResult<T> contains corresponding special command handler object then. For more information on special commands check out special commands page
  • ParseResultState.None. This is a default value, which is normally not possible to get from argument parsing method. It means that an instance of ParseResult<T> was initialized with default keyword or created with a default structs parameterless constructor

ParseResult<T> only holds data, which corresponds to its state. This means that, e.g. if ParseResult<T> is in state ParsedOptions, only Options property has some meaningful value, while Errors and SpecialCommandHandler are both equal to null (their default values).

Default result handling

While ParseResult<T> is very flexible in terms of how error recovery can be done, in most cases some decent default implementation is gonna be sufficient. The library provides such default implementation by generating an extension method for every ParseResult<T> an argument parser method can produce. Method has the following signature:

public static void ExecuteDefaults(this ParseResult<Options> result, Action<Options> action)

Here Options is your options type. The method handles given ParseResult<T> the following way:

  • If result is in ParsedOptions state, action argument is called with parsed options object
  • If result is in ParsedWithErrors state, a help screen with all encountered errors is generated and is written to Console.Error. The app then exits with exit code 1. Here is an example of such screen from the quick start example app:
QuickStartExample 1.0.0
Copyright (C) 2024

ERROR(S):
  Missing required parameter 'first-required-parameter' (parameter index '0')
  Missing required parameter 'second-required-parameter' (parameter index '1')

OPTIONS:

  -v, --verbose Enables verbose logging mode

PARAMETERS:

  first-required-parameter (at index 0) Required

  second-required-parameter (at index 1)        Required

  Remaining parameters

COMMANDS:

  --help        Show help screen

  --version     Show version information

  • If result is in ParsedSpecialCommand state, HandleCommand method of SpecialCommandHandler is invoked and app exits with exit code, returned from the handler

So in the end the typical template of your application can look like this:

using ArgumentParsing;
using ArgumentParsing.Generated;
using ArgumentParsing.Results;

namespace YourAppNamespace;

partial class Program
{
    private static void Main(string[] args)
    {
        ParseArguments(args).ExecuteDefaults(ExecuteMainApp);
    }

    [GeneratedArgumentParser]
    private static partial ParseResult<Options> ParseArguments(string[] args);

    private static void ExecuteMainApp(Options options)
    {
        // Your app logic here
    }
}

[OptionsType]
class Options
{
    // Your options members here
}

Important

In order to be able to have an extension method on the given ParseResult<T> type, options type T must have internal accessibility or above. If your options type is not accessible enough (e.g. it is a private nested class) no ExecuteDefaults implementation will be generated and you will get a warning about that. You may make your options type accessible or suppress the warning and handle different states of the given ParseResult<T> on your own

Note

Due to a roslyn bug editor completions won't suggest you unimported ExecuteDefaults method by default. Ensure you have using ArgumentParsing.Generated; in the scope to get this method in IntelliSense suggestions

Clone this wiki locally