{"id":13414881,"url":"https://github.com/nemec/clipr","last_synced_at":"2025-03-14T22:32:26.324Z","repository":{"id":6361505,"uuid":"7598636","full_name":"nemec/clipr","owner":"nemec","description":"Command Line Interface ParseR for .Net","archived":false,"fork":false,"pushed_at":"2019-04-24T07:14:39.000Z","size":1672,"stargazers_count":89,"open_issues_count":11,"forks_count":9,"subscribers_count":12,"default_branch":"master","last_synced_at":"2024-10-14T13:37:05.180Z","etag":null,"topics":["c-sharp","clipr","command-line-parser","parsed-arguments","parser-library"],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nemec.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-01-14T04:54:02.000Z","updated_at":"2024-03-01T07:09:07.000Z","dependencies_parsed_at":"2022-08-30T03:40:12.756Z","dependency_job_id":null,"html_url":"https://github.com/nemec/clipr","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nemec%2Fclipr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nemec%2Fclipr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nemec%2Fclipr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nemec%2Fclipr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nemec","download_url":"https://codeload.github.com/nemec/clipr/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243658057,"owners_count":20326459,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c-sharp","clipr","command-line-parser","parsed-arguments","parser-library"],"created_at":"2024-07-30T21:00:39.161Z","updated_at":"2025-03-14T22:32:25.884Z","avatar_url":"https://github.com/nemec.png","language":"C#","readme":"clipr: A Command Line Interface ParseR for .Net 3.5+ and .Net Core\n===============================================\n\nCreated by [Dan Nemec](https://github.com/nemec)\n\n[![NuGet](https://img.shields.io/nuget/dt/clipr.svg)](https://www.nuget.org/packages/clipr/)\n[![NuGet](https://img.shields.io/nuget/v/clipr.svg)](https://www.nuget.org/packages/clipr/)\n[![NuGet](https://img.shields.io/nuget/vpre/clipr.svg)](https://www.nuget.org/packages/clipr/)\n\nThis command line parser library is very much inspired by the\n[argparse](http://docs.python.org/2/library/argparse.html) library\nfor Python. It's one of the easiest parser libraries I've used while\nstill offering a comprehensive set of powerful features.\n\nI've borrowed and ported many of its features to C# taking advantage of\nC#'s static typing features where it makes sense. Thanks to the powerful\nnature of the Reflection and TypeDescriptor assemblies, it's quite easy\nto take the arguments already sent to any Console project and fill a\nclass with those values, even if they contain custom Types. Here's a quick\nbut powerful example of the objects this library enables you to build:\n\n```csharp\n[ApplicationInfo(Description = \"This is a set of options.\")]\npublic class Options\n{\n    [NamedArgument('v', \"verbose\", Action = ParseAction.Count,\n        Description = \"Increase the verbosity of the output.\")]\n    public int Verbosity { get; set; }\n\n    [PositionalArgument(0, MetaVar = \"OUT\",\n        Description = \"Output file.\")]\n    public string OutputFile { get; set; }\n\n    [PositionalArgument(1, MetaVar = \"N\",\n        NumArgs = 1,\n        Constraint = NumArgsConstraint.AtLeast,\n        Description = \"Numbers to sum.\")]\n    public List\u003cint\u003e Numbers { get; set; } \n}\n\nstatic void Main()\n{\n    var opt = CliParser.Parse\u003cOptions\u003e(\n        \"-vvv output.txt 1 2 -1 7\".Split());\n    Console.WriteLine(opt.Verbosity);\n    // \u003e\u003e\u003e 3\n\n    Console.WriteLine(opt.OutputFile);\n    // \u003e\u003e\u003e output.txt\n\n    var sum = 0;\n    foreach (var number in opt.Numbers)\n    {\n        sum += number;\n    }\n    Console.WriteLine(sum);\n    // \u003e\u003e\u003e 9\n}\n```\n\n## Changelog\n\n### Master\n\n### 2018-03-25\n\n* Fix minor help page formatting errors.\n* Add Verbs/Commands to help page.\n* Add the ability to configure the TextWriter for help and version output\n  so it is no longer writing only to `Console.Error`.\n* Allow each verb to inherit the `--help` trigger so that you can get detailed\n  help information for verbs.\n\n### 2017-07-13 1.6.0.1\n\n* Fix a bug in parsing consecutive Optional values.\n* Fix minor resx issues.\n\n### 2017-01-02 1.6.0\n\n* Add ability to mask password.\n* Add \"optional\" value constraint for reference or nullable types.\n* Add section in help generation for required named arguments.\n* Fixed bug in .Net Core localization resource name.\n\n[Full Changelog](CHANGELOG.md)\n\n## CliParser vs. CliParser\u003c\u003e\n\nIf you don't need any special options or custom help generators,\nthe static CliParser class is the easiest way to initialize the\nparser. Behind the scenes it will create the parser and begin parsing\nthe arguments passed in. If the destination type has a parameterless\nconstrutor, you don't even need to set up the object first! It can `new()`\nup an object for you and spit it out after parsing is complete, ready to\nbe used.\n\n## Parse vs. Tryparse vs. StrictParse\n\nThere are three ways to parse a list of arguments. The former, `Parse()`, will\nattempt to parse the input arguments and throw a ParseException if something\nwent wrong while parsing or a ParserExit exception if the help or version\ninformation were triggered and printed to the console.\n\nThe `TryParse()` method is similar to the typical TryParse methods found\non integers and datetimes. It returns a boolean value of true if parsing\nsucceeded, false otherwise. There is one overload that lets you input an\ninstance of the type you want to parse (in cases where the constructor takes\nparameters), but the other overload uses the `out` keyword to construct\na new instance of the type before parsing. If parsing fails, that instance\nwill be null.\n\nThe `StrictParse()` method was made for a very specific use case -- most\napplications that parse arguments, when they encounter an invalid argument\nor some other error, will print help / usage information and immediately\nquit, letting the user correct her mistakes and rerun the program. In that\nspirit, the `StrictParse()` method will not throw any exceptions\n(if you see one, report it on the Github page). Instead, it will print the\nerror message and the one-line usage documentation, then terminate using\n`Environment.Exit`. Note that your program **will not have the opportunity\nto clean up** when that happens. If you've allocated any unmanaged resouces \nor left some important files in a half-written state, unpredictible things\nmay happen. Of course, parsing arguments is usually the first thing you do\nin `Main` so it's not usually going to be an issue, but `Environment.Exit`\nis not the cleanest form of flow control so I feel it deserves a mention.\n\n# Integrity Checking\n\nOne of the most important non-functional features of a\nlibrary like this is making sure that you, the developer, don't have to\ntest the input with various garbage to make sure you've implemented the\nlibrary correctly. In that spirit, I've done my best to sanity check the\nAttributes you place on your options class when the parser is initialized\n(before any arguments are passed to it). You shouldn't have to wait until\na user passes the wrong arguments in to discover that you've defined a\nduplicate argument short name or that the constant values you're storing\naren't actually convertible to the property type.\n\n# Features\n\n## Named and Positional Arguments\n\nThere are two types of arguments that may be defined: Named and Positional\narguments.\n\n* Named arguments are those set off by short names or long names\n  (such as `-v` or `--verbose`). They are optional (in that the parser \n  will not show an error if one is not specified) unless explicitly\n  marked as required and may\n  be given in any order (eg. the parser does not care if you use\n  `-v --input file.txt` or `--input file.txt -v`).\n* Positional arguments, on the other hand, are always required and must be\n  given in the correct order (indicated by the Index property when defining\n  the argument). Since they have a defined order, there is no need to tag\n  them with short names or long names: just put them in the argument list\n  and they will be parsed automatically.\n* While positional arguments are required to be given in *order*, they may\n  be freely intermixed with named arguments. For example, the following are\n  all valid ways to call the example above and will give the same output:\n\n      clipr.Sample.exe -v -v out.txt 2 3\n      clipr.Sample.exe out.txt -v -v 2 3\n      clipr.Sample.exe --verbose out.txt 2 -v 3\n\n* Since short arguments must be a single character, any short argument\n  with an action that does not consume values (StoreConst, StoreTrue,\n  StoreFalse, AppendConst, Count) may be combined together. `-vvs` is\n  functionally the same as `-v -v -s`.\n* Similarly, short arguments *with* values may be input without a space\n  between the flag and the first value (`-fmyfilename.txt`).\n\n## Multiple Action Types\n\nStoring argument values isn't enough. There are a number of actions that\ncan be performed when the user specifies a named argument.\n\n* Store: Exactly what you'd expect, this action stores the value(s) given\n  after the argument in a property and is the default action.\n* StoreConst: Instead of letting the user choose which value is stored, a value\n  stored in the Attribute's Const property will be used. Intended to avoid\n  `if(opts.FlagWasSet){ opts.SomeOtherProperty = MyConstantValue; }` --\n  when parsing is finished, you should be ready to roll! Additionally, as long\n  as the value stored in Const is convertible to the property type (through\n  TypeDescriptor magic, more on that later), the property will be filled when\n  the flag is set. Since Attribute properties may only be filled with\n  compile-time constants, this allows you to use the same string serialization\n  tricks for const arguments that you can use to transform argument strings\n  into custom type objects.\n* StoreTrue: Shortcut for constructing a boolean StoreConst. Lets you\n  specify command line flags that consume no values (eg. `myprog.exe -v`).\n* StoreFalse: The opposite of StoreTrue. Since booleans are value types with\n  a default value of false already, it's worth noting here that nullable\n  boolean properties work just fine, too (`bool? MyFlag { get; set; }`), which\n  can be used to detect the presence of the \"false\" flag.\n* Append: Like Store, but if a user specifies the flag more than once the\n  parsed values are appended to the existing ones rather than replacing them\n  (eg. `myprog.exe -f file1.txt -f file2.txt`). The backing property must\n  be some sort of IList.\n* AppendConst: Combine StoreConst and Append and you've got it.\n* Count: Counts the number of times the flag was specified as an argument.\n  Good for specifying a \"level\" (like verbosity). There is no way to limit\n  the number of times a user specifies the argument.\n\n## Variable/Optional Argument Count\n\nIn addition to choosing an action for each argument, you can specify\n*how many* values each argument can consume. The first part is the NumArgs\nproperty, which gives the value limit for the argument. The second part,\nconstraints, comes in four flavors: Exact, AtLeast, AtMost, and Optional. Any\nnamed argument may take exactly the number of values specified or use that as\nthe minimum or maximum number consumed. The final constraint, Optional, can be\nset on a reference type or Nullable property and it indicates that a value\nfor the argument can be provided or left off. If the argument is given without\na value, the value will be copied from the `Const` property on the argument.\nFor more complex types, `Const` may be set to a string and the value will be\nconverted to the destination type in the same way other argument values are \nconverted.\n\n```csharp\n[NamedArgument('s', \"server\", Constraint = NumArgsConstraint.Optional, Const = 1234)]\npublic int? Server { get; set; }\n```\n\nYou may also use Optional arguments to define boolean flags that can easily be scripted (explicitly set to true or false).\n\n```csharp\nprivate class Options\n{\n\t[NamedArgument('f', \"flag\",\n\t   Constraint = NumArgsConstraint.Optional,\n\t   Action = ParseAction.Store,\n\t   Const = true)]\n\tpublic bool Flag { get; set; }\n\n}\n```\n\nAnd call it with any of the following:\n\n    -f\n    -f true\n    -f false\n\nSince positional arguments are not\ndelimited in any discernable way only the *last* positional argument,\nby Index, may use the constraints AtLeast or AtMost. All previous positional\narguments must consume an exact number of values.\n\n## Default Argument Values\n\nSet default values for a property in the config object's constructor. If a\nvalue is provided on the command line, it will overwrite the default value.\n\n```csharp\npublic class Options\n{\n    public Options()\n    {\n        OutputFile = \"out.txt\";\n    }\n\n    [PositionalArgument(0, MetaVar = \"OUT\",\n        Description = \"Output file.\")]\n    public string OutputFile { get; set; }\n}\n```\n    \n\n## Force Positional Argument Parsing\n\nIf, for any reason, you want the parser to stop parsing named arguments\nand count the rest as positional arguments, use a `--`. This is useful\nin cases where you want a positional argument that begins with a `-`\n(`./prog.exe -- --sometext--`) or when a named argument would otherwise\nconsume your positional arguments as one of its own\n(`./prog.exe --consumes-optional-value -- positional`).\n\n## Password Masking\n\nIn some cases, it's useful to offer the ability for a user to \"mask\" their\npassword, or provide it separately from the command line in order to\nprevent the password from appearing in console history or screenshots.\nThis feature can be enabled on `NamedArgument`s by annotating the property\nwith a `PromptValueIfMissingAttribute`. You may also, optionally, choose to\nmask the password (so nothing shows on the screen when you type) or not.\nIf the password is provided in the command line arguments, the value will\nused without prompting. If missing, the console will prompt for the missing\ninput. Additionally, a `SignalString` can be provided to explicitly indicate\nthat a value should be prompted. This resolves ambiguity in cases where\na positional argument/verb could be mistaken for a value.\nDefaults to a dash `-`.\n\n```csharp\npublic class Options\n{\n    [PromptIfValueMissing(MaskInput = true)]\n    [NamedArgument('s', \"secret\")]\n    public string SecretValue { get; set; }\n}\n```\n\n```csharp\nvar opt = CliParser.Parse\u003cOptions\u003e(new[] {\"-s\"});\n```\n\n```csharp\npublic class OptionsWithPositional\n{\n    [PromptIfValueMissing(MaskInput = true)]\n    [NamedArgument('s', \"secret\")]\n    public string SecretValue { get; set; }\n\n\t[PositionalArgument(0)]\n\tpublic string Name { get; set; }\n}\n```\n\n```csharp\nvar opt = CliParser.Parse\u003cOptionsWithPositional\u003e(new[] {\"-s - Nemec\"});\n```\n\nPrints:\n\n    Input a value for -s:\n\nThis feature only applies in a specific set of circumstances:\n* Can only annotate a `NamedArgument` (not a `PositionalArgument`).\n* The `ParseAction` must be `ParseAction.Store` (the default).\n\n## Mutually Exclusive Arguments\n\nNamed arguments can be given a MutuallyExclusiveGroupAttribute. If multiple\nnamed arguments belong to the same group and the user tries to specify more\nthan one, a parser error is generated. Groups can also be marked as Required.\nIf at least one MutuallyExclusiveGroupAttribute for a group is required and\nthe user does *not* provide one of the member arguments, an error is also\ngenerated.\n\n## Verbs\n\nVerbs are the name given to a set of initial arguments that conditionally\nparse a set of configuration options based on the given verb. Think\n`git add` and `git commit`. Each has a different set of options that's\nused in a different way. Each verb class is a valid configuration class,\nso verbs are basically a way to combine multiple sets of configurations,\nconditionally, into one. Imagine that in addition to `./git`, there was\nalso `./git-add` and `./git-commit` each with its own configuration classes.\nWith clipr, you can reuse those individual classes in `./git` as verbs.\n\nBy default, the parser can automatically create an instance of each verb\ntype as long as it has a parameterless default constructor. For more\ncomplex verb types, the `CliParser` constructor accepts an implementation of\nthe `clipr.IOC.IVerbFactory` interface and will delegate to that interface\nwhen it needs to create a verb. Your choice of IOC Container can also be\nhooked into this Interface with an adapter. Also provided is a\n`clipr.IOC.SimpleVerbFactory` implementation that allows you to define\na factory for each verb type in a collection initializer. The type in\nthe initializer is optional and, if missing, will be inferred from the\nfactory's return type.\n\n```csharp\nvar factory = new SimpleVerbFactory\n{\n\t{ () =\u003e new GitAdd(\".\") }\n\t{ typeof(GitCommit), () =\u003e new GitCommit() }\n}\n```\n\n```csharp\npublic class GitOptions\n{\n\t[Verb(\"add\")]\n\tpublic GitAdd Add { get; set; }\n\n\t[Verb]  // The lowercased property name is used when no name is provided\n\tpublic GitCommit Commit { get; set; }\n}\n```\n\nSome notes on verbs:\n\n  * A configuration class cannot contain both positional parameters and\n    verbs (although the verb itself may define its own positional parameters).\n  * Verbs may be nested arbitrarily deep, so long as you adhere to the\n    above requirement (although it's not recommended that you nest too\n    deeply).\n  * Multiple verb attributes may be registered for the same verb. These\n    will act like aliases (`svn co` vs. `svn checkout`).\n  * PostParse methods \n\n## Post-Parse Triggers\n\nUsing the PostParseAttribute you can mark parameterless methods to be\nautomatically run once parsing is completed. When PostParse methods are\nnested within Verbs, they will be executed in order from innermost class\nto outermost, which means that whenever a PostParse method is executed, the\nconfiguration class *and* all its verbs will be fully initialized by that\npoint.\n\n## Generated Help and Version Information\n\nBy default, Reflection is used to generate automatic help and version\ninformation based on the ArgumentAttributes you apply to the option class.\nTake the example above, this is the generated help documentation:\n\n    $ .\\Clipr.Sample.exe --help\n    usage: clipr.Sample [ -v|--verbose ] OUT N ...\n\n     This is a set of options.\n\n    positional arguments:\n     N              Numbers to sum.\n     OUT            Output file.\n\n    optional arguments:\n     -h, --help     Display this help document.\n     --version      Display version information.\n     -v, --verbose  Increase the verbosity of the output.\n\nYou may also programmatically generate the help and usage for a class:\n\n```csharp\nvar parser = new CliParser\u003cOptions\u003e(new Options());\nvar help = new AutomaticHelpGenerator\u003cOptions\u003e();\n// Gets the usage message, a short summary of available arguments.\nConsole.WriteLine(help.GetUsage(parser.Config));\n// Gets the full help message with argument descriptions.\nConsole.WriteLine(help.GetHelp(parser.Config));\n```\n\nVersion information is similarly auto-generated from the version string\ncompiled into the application using this library. In the future there\nwill be an easy way to specify the version manually, but until then you'll\nhave to implement the `IVersion` interface yourself and replace the `Version`\nproperty within the `IHelpGenerator`.\n\n## Localization\n\nclipr supports localization, both of the help UI generated by\n`AutomaticHelpGenerator` and the options themselves. Only the description\nof an argument is localizable - the long name of an argument cannot\nbe localized to prevent issues if a shell script that called your application\nwere run on a PC under a different locale.\n\nCurrently, the UI is only localized in three languages: English, Spanish, \nGerman, and Portuguese. If you're fluent in another language or see a problem\nwith the existing translations, please consider adding a translation! More\ndetails can be found in [issue #28](https://github.com/nemec/clipr/issues/28).\n\nTo localize an argument description, apply the\n`clipr.Attributes.LocalizeAttribute` attribute to the property. Localization\nrequires a strongly-typed Resources class, provided as the `ResourceType`\nproperty to the attribute. If this attribute is applied to the enclosing class,\nthe `ResourceType` will be inherited by any properties within unless otherwise\nspecified. If not provided, the `ResourceName` defaults to 'ClassName' if\napplied to a class or 'ClassName_PropertyName' if applied to a property.\n\n```csharp\n[Localize(ResourceType = typeof(Properties.Resources))]\npublic class LocalizationOptions\n{\n    [Localize]  // Resource Name defaults to LocalizationOptions_TurnOnThePower\n    [NamedArgument(\"turnonthepower\", Action = ParseAction.StoreTrue)]\n    public bool TurnOnThePower { get; set; }\n\n    [Localize(\"FileToAdd\", typeof(Properties.Resources))]\n    [PositionalArgument(0)]\n    public string FileToAdd { get; set; }\n}\n```\n\n## TypeDescriptor / TypeConverter\n\nCustom types may be used as config values, but only if a \n[TypeConverter](http://msdn.microsoft.com/en-us/library/ayybcxe5.aspx) is\ndefined for that type. Since TypeConverters are big, messy beasts, and\nfor purposes of this library there is only one conversion case\n(string -\u003e MyType), the `clipr.Utils` namespace contains a\n`StringTypeConverter` that simplifies the conversion interface. After\nimplementing the converter, tag your custom class with a\n[TypeConverterAttribute](http://msdn.microsoft.com/en-us/library/system.componentmodel.typeconverterattribute.aspx)\nto register the converter with .Net.\n\nYou may also attach a TypeConverter attribute to a Property on the class,\nwhich allows targeted conversion. It also allows you to apply custom\nconverters to types that you do not own, like built-in classes.\n\n## Static Enumerations\n\nSometimes there is a need to add logic to the Enum type. Since .Net enums are\npurely integers, the only alternative is to implement the so-called \"Typesafe\nEnum\" pattern by creating a class with fields for each enum value, possibly\nwith subclasses defining value-specific behavior.\n\nSince clipr cannot rely on a specific typesafe enum implementation, it defines\na common set of criteria for identifying and parsing them.\n\n1. Enum values must be `public`, `static`, and `readonly` fields of a class.\n2. Fields must be the same type as the underlying enum or a subclass.\n3. Static enums may only be parsed by name: the name of the field. Parsing\n  is case insensitive (using the Invariant culture).\n4. The class must be tagged with the `clipr.StaticEnumerationAttribute`\n  attribute. If the attribute cannot be added to the class directly (such as\n  third party types), but the class follows (1) and (2), the attribute may also\n  be applied to the option property.\n\nSample:\n\n```csharp\n[StaticEnumeration]\ninternal abstract class SomeEnum\n{\n    public static readonly SomeEnum First = new EnumSubclass();\n\n    public abstract void DoSomeWork();\n\n    public class EnumSubclass : SomeEnum\n    {\n        public override void DoSomeWork() { }\n    }\n}\n\ninternal class StaticEnumerationOptions\n{\n    [NamedArgument('e')]\n    public SomeEnum Value { get; set; } \n}\n\ninternal class StaticEnumerationExplicitOptions\n{\n    [NamedArgument('e')]\n    [StaticEnumeration]  // Allowed in case attr is not defined on SomeEnum\n    public SomeEnum Value { get; set; } \n}\n\npublic static void Main()\n{\n  var obj = CliParser.Parse\u003cStaticEnumerationOptions\u003e(\n      \"-e first\".Split());\n  Assert.AreSame(SomeEnum.First, obj.Value);\n}\n```\n\n## Fluent Interface\n\n\\*\\* Alpha Feature \\*\\*\n\nInstead of attributes, the parser may be configured using a fluent interface.\nThere are five branches off of the parser to configure new arguments:\n`HasNamedArgument`, `HasNamedArgumentList`, `HasPositionalArgument`,\n`HasPositionalArgumentList`, and `HasVerb`. The argument instances returned\nfrom each of these may be used to configure the individual argument and\nyou may return to the parser by chaining the `And` property when finished\nconfiguring.\n\n```csharp\nvar opt = new Options();\n\nnew CliParserBuilder\u003cOptions\u003e(opt)\n    .HasNamedArgument(o =\u003e o.Verbosity)\n        .WithShortName('v')\n        .CountsInvocations()\n.And\n    .HasNamedArgument(o =\u003e o.OutputFile)\n        .WithShortName()\n.And\n    .HasPositionalArgumentList(o =\u003e o.Numbers)\n        .HasDescription(\"These are numbers.\")\n        .Consumes.AtLeast(1)\n.And.Parser\n    .Parse(\"-vvv -o out.txt 3 4 5 6\".Split());\n\nConsole.WriteLine(opt.Verbosity);\n// \u003e\u003e\u003e 3\n\nConsole.WriteLine(opt.OutputFile);\n// \u003e\u003e\u003e output.txt\n    \nvar sum = 0;\nforeach (var number in opt.Numbers)\n{\n    sum += number;\n}\nConsole.WriteLine(sum);\n// \u003e\u003e\u003e 9\n```\n\nYou may also add a Verb to the parser config with this syntax:\n\n```csharp\nvar opt = new Options();\nnew CliParserBuilder\u003cOptions\u003e(opt)\n    .HasVerb(\"add\", c =\u003e c.AddVerb,\n              // Note that in the Fluent Interface, you're nesting parsers\n              // Theoretically this means you can nest an \n              // Attribute-configured parser inside a Fluent parser\n              // (although you cannot do the opposite, due to limitations\n              // with Attributes).\n              new CliParserBuilder\u003cAddFileOptions\u003e(new AddFileOptions())\n                  .HasPositionalArgument(c =\u003e c.Filename)\n                  .And)  // A necessary evil if defining inline.\n.And.Parser\n    .Parse(\"add myfile.txt\");\n\nConsole.WriteLine(opt.AddVerb);\n// myfile.txt\n```\n\n## Dictionary Backend\n\nIn addition to binding to a class (via properties), it is also possible\nto bind to keys in a dictionary by specifying the indexer (plus key name)\nin lieu of a property. The downside is that this requires all keys and \nall values to share the same type, respectively (the TypeConverter will\nleave the values in a Dictionary\u003cstring, object\u003e as strings). Once you get\npast that, what this does is let you *dynamically* define the available\nconfiguration properties at runtime (which is as good as you're going to\nget, given that .Net currently doesn't allow ExpandoObject or dynamic\nin Expressions).\n\n```csharp\nvar key = 1;\nvar opt = new Dictionary\u003cint, string\u003e();\nvar parser = new CliParser\u003cDictionary\u003cint, string\u003e\u003e(opt);\nparser.HasNamedArgument(c =\u003e c[key])\n        .WithShortName('n');\n\nparser.Parse(\"-n frank\".Split());\n\nConsole.WriteLine(\"Parsed Keys:\");\nforeach (var kv in opt)\n{\n    Console.WriteLine(\"\\t{0}: {1}\", kv.Key, kv.Value);\n}\n// Parsed Keys:\n//    1: frank\n```\n\nNotes:\n\n* Any type with an indexer will work, it doesn't have to be a Dictionary.\n* Non-constant key expressions will be evaluated at configuration time\n  (such as dict[MyProperty] or dict[MethodCall()]), so variables may\n  also be used as keys without issue.\n* Since they're evaluated in configuration (rather than parsing), *any*\n  type may be used as the indexer key, even if it's not convertible\n  from a string.\n\n\n## TODO\n\nRender help information for verbs\n","funding_links":[],"categories":["CLI"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnemec%2Fclipr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnemec%2Fclipr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnemec%2Fclipr/lists"}