-
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,
-abc
is 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 foo
and-ofoo
are 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=value
syntax 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.
-
string
andchar
- Integer numeric types:
byte
,sbyte
,short
,ushort
,int
,uint
,long
,ulong
andBigInteger
- Real numeric types:
float
,double
anddecimal
bool
- Any
enum
-
DateTime
,DateOnly
,TimeOnly
TimeSpan
Parse strategies for each category are the following:
-
string
option values are directly captured from supplied arguments - If value is single-character long it can be assigned to a
char
option. Ifchar
option encounters a value of length greater than one, aBadOptionValueFormatError
is reported - All numeric types,
enum
s and date/time-related options are parsed with theirTryParse
methods. If parsing fails, aBadOptionValueFormatError
is reported.CultureInfo.InvariantCulture
is used as a format provider for parsing when there is aTryParse
overload, which accepts anIFormatProvider
-
bool
options are somewhat special. Ifbool
option is not specified in arguments, it isfalse
by default. If it is specified, however, it automatically becomestrue
.bool
options do not accept values. If abool
option is supplied with a value, aFlagOptionValueError
is 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) supportsrequired
properties - Annotate option with
[System.ComponentModel.DataAnnotations.Required]
attribute
There are 2 special cases when it comes to required options:
-
bool
options cannot be made required. Due to their semantics making abool
option 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 requiredbool
options 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
null
value 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