-
Notifications
You must be signed in to change notification settings - Fork 0
Options
In library's terminology an "option" is a named value, which syntax follows GNU syntax conventions:
- Arguments are options if they begin with a hyphen delimiter (
-) - Multiple options may follow a hyphen delimiter in a single token if the options do not take arguments. Thus,
-abcis equivalent to-a -b -c - Short option names are single alphanumeric characters. Here the library is a bit strickter and allows only letters as a valid short option names
- An option and its argument may or may not appear as separate tokens. (In other words, the whitespace separating them is optional.) Thus,
-o fooand-ofooare equivalent - Options typically precede other non-option arguments (but that is not a requirement)
- The argument
--terminates all options; any following arguments are treated as non-option arguments, even if they begin with a hyphen - A token consisting of a single hyphen character is interpreted as an ordinary non-option argument
- Options may be supplied in any order, or appear multiple times. The interpretation is left up to the particular application program. In case of this library having option defined multiple times creates
DuplicateOptionError(s) unless option if of a sequence type - Long options consist of
--followed by a name made of alphanumeric characters and dashes. Option names are typically one to three words long, with hyphens to separate words. Users can abbreviate the option names as long as the abbreviations are unique -
--name=valuesyntax can be used to specify option's value
Each option corresponds to a C# property in options type. In order to declare an option property is annotated with [Option] attribute from ArgumentParsing namespace, e.g.:
using ArgumentParsing;
[OptionsType]
class Options
{
[Option]
public string MyOption { get; set; }
}Each option has short single character name, used in short argument syntax (e.g. option with short name a can be referenced by argument -a), and a long name, used in long argument syntax (e.g. option with long name option-a can be referenced by argument --option-a). By default options, annotated with pure [Option] attribute, are automatically assigned a long name, which is corresponding C# property name in lower kebab case, e.g. in the example above MyOption will be automatically assigned my-option long name. Short name is never assigned automatically. There are several constructors of [Option] attribute, which allow to set or override default option's short and long names:
using ArgumentParsing;
[OptionsType]
class Options
{
[Option] // No short name, long name `option-a`
public string OptionA { get; set; }
[Option('b')] // Short name `b`, long name `option-b`
public string OptionB { get; set; }
[Option("my-option-c")] // No short name, long name `my-option-c`
public string OptionC { get; set; }
[Option('d', "my-option-d")] // Short name `d`, long name `my-option-d`
public string OptionD { get; set; }
[Option('e', null)] // Short name `e`, no long name
public string OptionE { get; set; }
}Note
The following scenario is not valid:
using ArgumentParsing;
[OptionsType]
class Options
{
[Option(null)] // This technically declares an option with no short name and no long name
public string Option { get; set; }
}There are certain rules applied to option names:
- Short option name can only be a letter
- Long option name must start with a letter
- Long option name can only contain alphanumeric characters and hyphen characters (
-)
If short or long name doesn't follow these rules an error is reported on option declaration. If there are two options with same short or long name, an error is reported on both option declarations to easily identify problematic cases.
Options support a wide variety of types.
-
stringandchar - Integer numeric types:
byte,sbyte,short,ushort,int,uint,long,ulongandBigInteger - Real numeric types:
float,doubleanddecimal bool- Any
enum -
DateTime,DateOnly,TimeOnly TimeSpan
Parse strategies for each category are the following:
-
stringoption values are directly captured from supplied arguments - If value is single-character long it can be assigned to a
charoption. Ifcharoption encounters a value of length greater than one, aBadOptionValueFormatErroris reported - All numeric types,
enums and date/time-related options are parsed with theirTryParsemethods. If parsing fails, aBadOptionValueFormatErroris reported.CultureInfo.InvariantCultureis used as a format provider for parsing when there is aTryParseoverload, which accepts anIFormatProvider -
booloptions are somewhat special. Ifbooloption is not specified in arguments, it isfalseby default. If it is specified, however, it automatically becomestrue.booloptions do not accept values. If abooloption is supplied with a value, aFlagOptionValueErroris reported
In addition to base types, any Nullable<T> option type is valid as long as underlying T is a valid base value type. If a nullable option is not supplied in arguments, it is null by default, otherwise it contains corresponding value of type T. This allows to distinguish between cases when option value is not supplied and when it is assigned a default value, e.g. an option of type int? will be null if it is not supplied in arguments, but it can be assigned value 0 in arguments (which is a default value for an underlying int type) and these are two distinct cases. If that option had int type, not supplying it in arguments and supplying a value of 0 would be undistinguishable.
Options of bool? type have special behavior since they combine semantics of bool and Nullable<T> option types. If bool? option is not supplied in arguments, it is null by default. If it is supplied without a value, it it true. However, unlike bool options, bool? options can accept an optional value, which is gonna be parsed and assigned to the option. So in the end for a bool? option with short name b and long name option-b the following behavior is applied:
| Arguments | Option value |
|---|---|
| <no args> | null |
| -b | true |
| -b true | true |
| -b false | false |
| --option-b | true |
| --option-b true | true |
| --option-b=true | true |
| --option-b false | false |
| --option-b=false | false |
Passing incorrect value will cause BadOptionValueFormatError, e.g. in case -b badValue is encountered.
There is also a separate subclass of options called "sequence" options. Valid types of sequence options are:
IEnumerable<T>IReadOnlyCollection<T>IReadOnlyList<T>ImmutableArray<T>
...where T is a valid base type.
Note
The list of valid sequence type collections is intentionally limited to immutable collection types/interfaces. Future additions to this list will follow the same principal, meaning that sequence types like List<T> are probably never gonna be added
Important
Due to the nature of source generators you will have direct access to the underlying types of sequence interfaces (unless they are provided as gnerated collections with file accessibility, which is not currently the case), meaning, that you, for instance, can declare a sequence of IEnumerable<T> type and after getting options object downcast this option to its actual type. Such scenarios are not supported! The "contract" you have with a library is that you get IEnumerable<T>, but nothing more concrete. The underlying type of such interfaces can chage between releases or even be different for different options in one program depending on certain logic internal to the generator
Sequence options can take multiple values of their type T in a row, e.g. for the given options type:
using ArgumentParsing;
using System.Collections.Generic;
[OptionsType]
class Options
{
[Option('i')]
public IEnumerable<int> Ints { get; set; }
}... the following arguments are valid:
| Arguments | Option value |
|---|---|
| <no args> | [] |
| -i | [] |
| --ints | [] |
| -i 1 | [1] |
| -i 1 2 3 | [1, 2, 3] |
| --ints 1 | [1] |
| --ints 1 2 3 | [1, 2, 3] |
If option value is "directly assigned", i.e. using short syntax without space (-i1) or --name=value syntax, subsequent arguments are not allowed:
-i5 6 7 // illegal
--ints=1 2 3 // illegal
Duplicate arguments of sequence options are not allowed as well:
-i 1 2 3 -i 4 5 6 // illegal
--ints 1 2 3 --ints 4 5 6 //illegal
These restrictions are in place to avoid confusing usage scenarios (e.g. --ints=1 --ints=2) and leave some space for extending sequence options in the future.
Options can be made required. In such case MissingRequiredOptionError is reported if option is not provided in arguments.
There are two ways of making an option required:
- Declare option property as
required. This is a recommended way if your environment (C# version + runtime metadata attribute) supportsrequiredproperties - Annotate option with
[System.ComponentModel.DataAnnotations.Required]attribute
There are 2 special cases when it comes to required options:
-
booloptions cannot be made required. Due to their semantics making abooloption required will force it to be present in arguments, which implies that the value of such option in all valid cases will always betrue. Thus requiredbooloptions don't make sense and are forbidden (error diagnostic is reported if you try to declare one) - It doesn't make sense to declare nullable required options as well, since this makes
nullvalue of such options impossible to get in all valid cases. But since they can still have different underlying values, this is just a warning and not an error