-
Notifications
You must be signed in to change notification settings - Fork 0
Home
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.
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
string
s. "Collection ofstring
s" is defined as type, which can be enumerated inforeach
loop with type of iterable variable beingstring
. Examples of such types arestring[]
,List<string>
,IEnumerable<string>
,ReadOnlySpan<string>
and so on. Things likeHashSet<string>
are also possible because they are legit collections ofstring
s, 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>
fromArgumentParsing.Results
namespace whereT
is a valid options type - Valid options type is non-special
class
orstruct
, which has a parameterless constructor - Valid options type must be annotated with
[OptionsType]
attribute fromArgumentParsing
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
.
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.
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 typeT
calledOptions
. 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 ofParseResult<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 ofParseResult<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 ofParseResult<T>
was initialized withdefault
keyword or created with a defaultstruct
s 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).
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 toConsole.Error
. The app then exits with exit code1
. 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 ofSpecialCommandHandler
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