diff --git a/Directory.Build.props b/Directory.Build.props
index 52199a0..80add0a 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -17,7 +17,7 @@
- 2.3.38
+ 3.7.115
diff --git a/NuGet.Config b/NuGet.Config
deleted file mode 100644
index fa6832a..0000000
--- a/NuGet.Config
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/src/ShaderGen.App/ShaderGen.App.csproj b/src/ShaderGen.App/ShaderGen.App.csproj
index 047021b..fbcda94 100644
--- a/src/ShaderGen.App/ShaderGen.App.csproj
+++ b/src/ShaderGen.App/ShaderGen.App.csproj
@@ -2,7 +2,7 @@
Exe
- netcoreapp2.0
+ net6.0
@@ -16,8 +16,8 @@
+
-
diff --git a/src/ShaderGen.Build/ShaderGen.Build.csproj b/src/ShaderGen.Build/ShaderGen.Build.csproj
index ceee5f0..c30dfd1 100644
--- a/src/ShaderGen.Build/ShaderGen.Build.csproj
+++ b/src/ShaderGen.Build/ShaderGen.Build.csproj
@@ -1,9 +1,9 @@
- netcoreapp2.0
+ net6.0
false
- $([System.IO.Path]::GetFullPath('$(RepositoryRootDirectory)bin\$(Configuration)'))\ShaderGen.App\netcoreapp2.0\publish
+ $([System.IO.Path]::GetFullPath('$(RepositoryRootDirectory)bin\$(Configuration)'))\ShaderGen.App\net6.0\publish
ShaderGen.Build
diff --git a/src/ShaderGen.Primitives/ShaderGen.Primitives.csproj b/src/ShaderGen.Primitives/ShaderGen.Primitives.csproj
index 9742679..6979da3 100644
--- a/src/ShaderGen.Primitives/ShaderGen.Primitives.csproj
+++ b/src/ShaderGen.Primitives/ShaderGen.Primitives.csproj
@@ -1,7 +1,7 @@
- netstandard2.0
+ net6.0
ShaderGen
ShaderGen.Primitives
diff --git a/src/ShaderGen.Tests/ShaderGen.Tests.csproj b/src/ShaderGen.Tests/ShaderGen.Tests.csproj
index f99d930..a81e952 100644
--- a/src/ShaderGen.Tests/ShaderGen.Tests.csproj
+++ b/src/ShaderGen.Tests/ShaderGen.Tests.csproj
@@ -1,7 +1,7 @@
- netcoreapp2.0
+ net6.0
false
Program
diff --git a/src/ShaderGen.sln b/src/ShaderGen.sln
index 05fb89a..e87bc03 100644
--- a/src/ShaderGen.sln
+++ b/src/ShaderGen.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26730.12
+# Visual Studio Version 17
+VisualStudioVersion = 17.13.35919.96 d17.13
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShaderGen", "ShaderGen\ShaderGen.csproj", "{89E900C2-3DCF-434B-B807-AEB9F55BF6F1}"
EndProject
@@ -12,6 +12,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShaderGen.Primitives", "Sha
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShaderGen.Build", "ShaderGen.Build\ShaderGen.Build.csproj", "{F960509E-C4BF-4518-937E-98B96AF0757F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.CommandLine", "System.CommandLine\System.CommandLine.csproj", "{80F7C944-4B94-6A98-F407-939315CA2DC4}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -82,6 +84,18 @@ Global
{F960509E-C4BF-4518-937E-98B96AF0757F}.Release|x64.Build.0 = Release|Any CPU
{F960509E-C4BF-4518-937E-98B96AF0757F}.Release|x86.ActiveCfg = Release|Any CPU
{F960509E-C4BF-4518-937E-98B96AF0757F}.Release|x86.Build.0 = Release|Any CPU
+ {80F7C944-4B94-6A98-F407-939315CA2DC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {80F7C944-4B94-6A98-F407-939315CA2DC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {80F7C944-4B94-6A98-F407-939315CA2DC4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {80F7C944-4B94-6A98-F407-939315CA2DC4}.Debug|x64.Build.0 = Debug|Any CPU
+ {80F7C944-4B94-6A98-F407-939315CA2DC4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {80F7C944-4B94-6A98-F407-939315CA2DC4}.Debug|x86.Build.0 = Debug|Any CPU
+ {80F7C944-4B94-6A98-F407-939315CA2DC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {80F7C944-4B94-6A98-F407-939315CA2DC4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {80F7C944-4B94-6A98-F407-939315CA2DC4}.Release|x64.ActiveCfg = Release|Any CPU
+ {80F7C944-4B94-6A98-F407-939315CA2DC4}.Release|x64.Build.0 = Release|Any CPU
+ {80F7C944-4B94-6A98-F407-939315CA2DC4}.Release|x86.ActiveCfg = Release|Any CPU
+ {80F7C944-4B94-6A98-F407-939315CA2DC4}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/ShaderGen/ShaderGen.csproj b/src/ShaderGen/ShaderGen.csproj
index ac369da..d76f282 100644
--- a/src/ShaderGen/ShaderGen.csproj
+++ b/src/ShaderGen/ShaderGen.csproj
@@ -1,6 +1,6 @@
- netstandard2.0
+ net6.0
ShaderGen
Translates C# code to HLSL and GLSL shader code.
diff --git a/src/System.CommandLine/Properties/Assembly.cs b/src/System.CommandLine/Properties/Assembly.cs
new file mode 100644
index 0000000..176df9b
--- /dev/null
+++ b/src/System.CommandLine/Properties/Assembly.cs
@@ -0,0 +1,7 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("System.CommandLine.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100039ac461fa5c82c7dd2557400c4fd4e9dcdf7ac47e3d572548c04cd4673e004916610f4ea5cbf86f2b1ca1cb824f2a7b3976afecfcf4eb72d9a899aa6786effa10c30399e6580ed848231fec48374e41b3acf8811931343fc2f73acf72dae745adbcb7063cc4b50550618383202875223fc75401351cd89c44bf9b50e7fa3796")]
\ No newline at end of file
diff --git a/src/System.CommandLine/README.md b/src/System.CommandLine/README.md
new file mode 100644
index 0000000..dc4a0e5
--- /dev/null
+++ b/src/System.CommandLine/README.md
@@ -0,0 +1,507 @@
+# System.CommandLine
+
+The purpose of this library is to make command line tools first class by
+providing a command line parser. We've already made an attempt in 2009, but
+that wasn't a design we (or the community) was
+[happy with](http://tirania.org/blog/archive/2009/Feb-21.html).
+
+Here are the goals:
+
+* Designed for cross-platform usage
+* Lightweight with minimal configuration
+* Optional but built-in support for help, validation, and response files
+* Support for multiple commands, like version control tools
+
+[Syntax](#syntax) and [API samples](#api-samples) are below.
+
+## Why a new library?
+
+There is already a set of libraries available for command line parsing, such as:
+
+* [Mono.Options][Mono.Options] (also known as `NDesk.Options`)
+* [CommandLine][CommandLine]
+* An [internal one][vance] that Vance Morrison wrote many years ago.
+
+[Mono.Options]: http://tirania.org/blog/archive/2008/Oct-14.html
+[CommandLine]: https://github.com/gsscoder/commandline
+[vance]: https://github.com/dotnet/buildtools/blob/master/src/common/CommandLine.cs
+
+So the question is: why a new one? There are a couple of reasons:
+
+1. We want to support a syntax that feels natural when used across platforms. In
+ particular, we want to be very close to the Unix- and GNU style.
+
+2. We need something that is quite low level. In particular, we don't want to
+ have a library that requires reflection for attribute discovery or for
+ setting properties.
+
+3. We want an experience that achieves an extremely minimal setup in terms of
+ lines of code required for parsing.
+
+While some of the libraries solve some of these aspects, none of them solve the
+combination.
+
+Of course, providing a command line parser isn't just providing a parsing
+mechanism: in order to be usable, the library has to be opinionated in both the
+supported syntax, as well as in the shape of the APIs. In the BCL, we've always
+taken the stance that we want to provide a layered experience that allows
+getting the 80% scenario done, while allowing to be extensible for a potential
+long tail of additional scenarios. If that means we've had to make policy decisions,
+so be it because not making those forces all of our consumers to come up with
+their own policy.
+
+That being said, the goal isn't to provide the final command line parser
+library. In fact, I'm not aware of any platform that gets away with having a
+single one. If you're happy with the one you're already using or if you even
+wrote your own: that's perfectly fine. But after one and a half decades it's
+time for the BCL to provide a built-in experience as well.
+
+## Work in progress
+
+* Should we support a case insensitive mode?
+* Should we have a way to name option arguments, e.g. `DefineOption("n=name")`?
+* Should provide a string based approach to define usage?
+* Should we provide an error handler?
+* Should we provide a help request handler?
+* Should we expose the response file hander?
+* Should we allow "empty" commands, so that the tool can support options without
+ a command, like `git --version`?
+
+## Syntax
+
+The syntax conventions are heavily inspired by the following existing
+conventions:
+
+* [Unix History][Unix-History]
+* [POSIX Conventions][POSIX-Conventions]
+* [GNU Standards for Command Line Interfaces][GNU]
+* [GNU List of standard option names][GNU-Options]
+
+[Unix-History]: http://catb.org/~esr/writings/taoup/html/ch10s05.html
+[POSIX-Conventions]: http://www.cs.unicam.it/piergallini/home/materiale/gc/java/essential/attributes/_posix.html
+[GNU]: http://www.gnu.org/prep/standards/html_node/Command_002dLine-Interfaces.html
+[GNU-Options]: http://www.gnu.org/prep/standards/html_node/Option-Table.html#Option-Table
+
+In general, all strings are treated in a case sensitive way. This allows
+supporting options that only differ by case, which is pretty common on
+Unix systems, e.g.
+
+ # This reverses the output
+ $ ls -r *.txt
+ # This does a recursive search
+ $ ls -R *.txt
+
+### Single character options
+
+Single character options are delimited by a single dash, e.g.
+
+ $ tool -x -d -f
+
+They can be *bundled* together, such as
+
+ $ tool -xdf
+
+Please note that slashes aren't supported.
+
+### Keyword options
+
+Keyword options are delimited by two dashes, such as:
+
+ $ tool --verbose
+
+### Option arguments
+
+Both, the single letter form, as well as the long forms, support arguments.
+Arguments must be separated by either a space, an equal sign or a colon:
+
+ # All three forms are identical:
+ $ tool --out result.exe
+ $ tool --out=result.exe
+ $ tool --out:result.exe
+
+Multiple spaces are allowed as well:
+
+ $ tool --out result.exe
+ $ tool --out = result.exe
+ $ tool --out : result.exe
+
+This even works when combined with bundling, but in that case only the last
+option can have an argument. So this:
+
+ $ tool -am "hello"
+
+is equivalent to:
+
+ $ tool -a -m "hello"
+
+### Multiple occurrences
+
+Unix has a strong tradition for scripting. In order to make it easier to forward
+arguments to scripts, it's common practice to allow options to appear more than
+once. The semantics are that the last one wins. So this:
+
+ $ tool -a this -b -a that
+
+has the same effect as that:
+
+ $ tool -b -a that
+
+### Parameters
+
+Parameters, sometimes also called non-option arguments, can be anywhere in the
+input:
+
+ # Both forms equivalent:
+ $ tool input1.ext input2.ext -o result.ext
+ $ tool input1.ext -o result.ext input2.ext
+
+### Commands
+
+Very often, tools have multiple commands with independent options. Good example
+are version control tools, e.g.
+
+ $ tool fetch origin --prune
+ $ tool commit -m 'Message'
+
+### Response files
+
+It's common practice to allow passing command line arguments via response files.
+This can look as follows:
+
+ $ tool -f -r @D:\src\defaults.rsp --additional
+
+The API supports multiple response files being passed in. It will simply expand
+those in-place, i.e. it's valid to have additional options and parameters
+before, as well as after the response file reference.
+
+## API Samples
+
+### Hello world
+
+```C#
+using System;
+using System.CommandLine;
+
+static class Program
+{
+ static void Main(string[] args)
+ {
+ var addressee = "world";
+
+ ArgumentSyntax.Parse(args, syntax =>
+ {
+ syntax.DefineOption("n|name", ref addressee, "The addressee to greet");
+ });
+
+ Console.WriteLine("Hello {0}!", addressee);
+ }
+}
+```
+
+Usage looks as follows:
+
+```
+$ ./hello -h
+usage: hello [-n ]
+
+ -n, --name The addressee to greet
+
+$ ./hello
+Hello world!
+$ ./hello -n Jennifer
+Hello Jennifer!
+$ ./hello --name Tom
+Hello Tom!
+$ ./hello -x
+error: invalid option -x
+```
+
+### Defining options
+
+The `ArgumentSyntax` class allows defining options and parameters for any data
+type. In order to parse the value, you need to supply a `Func` that
+performs the parsing. So if you want to use a guid, you could define an option
+like this:
+
+```C#
+Guid guid = Guid.Empty;
+syntax.DefineOption("g|guid", ref guid, Guid.Parse, "The GUID to use");
+```
+
+The library provides overloads that handle the most common types, such as
+`string`, `int`, and `bool`, so that you don't have to pass in parsers for
+those.
+
+Boolean options are specially handled in that they are considered flags, i.e.
+they don't require an argument -- they are simply considered `true` if they are
+specified. However you can still explicitly pass in `true` or `false`. So this
+
+ $ tool -x
+
+Is equivalent to
+
+ $ tool -x:true
+
+Normally, options must supply an argument value (or it's considered an error). However, there may be times when you want to allow a particular option whether it supplies a value or not. To do this, use the overloaded `DefineOption()` methods:
+
+```C#
+int port = 1234;
+var option = syntax.DefineOption("s|server", ref port, false,
+ "Start the server, optionally specifying a port");
+```
+
+Then the value in code will be set regardless of whether the command line option specifies one and an error will not be thrown if the option value is omitted. You can check for whether the option was specified at all by checking the `Argument.IsSpecified` flag. For example, given the above option definition:
+
+ $ tool
+ # port = 1234, option.IsSpecified = false
+
+ $ tool --server
+ # port = 1234, option.IsSpecified = true
+
+ $ tool --server 9876
+ # port = 9876, option.IsSpecified = true
+
+The syntax used to define the option supports multiple names by separating them
+using a pipe. All names are aliases for the same option. For diagnostic
+purposes, the first name will be used. By convention that should be the short
+name, but it's really up to you.
+
+### Defining parameters
+
+Parameters work in a very similar way:
+
+```C#
+Guid guid = Guid.Empty;
+syntax.DefineParameter("guid", ref guid, Guid.Parse, "The GUID to use");
+```
+
+However, since parameters are matched by position and not by using a named
+option, the name is only used for diagnostic purposes and to render a readable
+syntax. Hence, they don't support the pipe syntax because having multiple names
+wouldn't make any sense there.
+
+Please note that parameters must be specified after options. The reason being
+that the parser needs to know all options before it can match parameters.
+Otherwise parsing this command would be ambiguous:
+
+ $ tool -x one two
+
+Without knowing whether `-x` takes an argument, it's not clear whether `one`
+will be an argument or the first parameter.
+
+### Defining option and parameter lists
+
+Both, options and parameters, support the notion of lists. For example, consider
+a compiler `comp`:
+
+ $ comp -D DEBUG -D ARCH=x86 source1 source2 -out:hello
+
+You would define the options and parameters as follows:
+
+```C#
+string output = string.Empty;
+IReadOnlyList defines = Array.Empty();
+IReadOnlyList sources = Array.Empty();
+
+syntax.DefineOption("out", ref output, "Output name");
+syntax.DefineOptionList("D|define", ref defines, "Preprocessor definitions");
+syntax.DefineParameterList("source", ref sources, "The source files to compile");
+```
+
+In general, you can define multiple option lists but only one parameter list.
+The reason being that a parameter list will consume all remaining parameters.
+You can define individual parameters and a parameter list but the parameter list
+must be after the individual parameters otherwise the individual ones will never
+be matched.
+
+### Defining commands
+
+Commands are defined in a similar way to options and parameters. The way they
+are associated with options and commands is by order:
+
+```C#
+var command = string.Empty;
+var prune = false;
+var message = string.Empty;
+var amend = false;
+
+syntax.DefineCommand("pull", ref command, "Pull from another repo");
+syntax.DefineOption("p|prune", ref prune, "Prune branches");
+
+syntax.DefineCommand("commit", ref command, "Committing changes");
+syntax.DefineOption("m|message", ref message, "The message to use");
+syntax.DefineOption("amend", ref amend, "Amend existing commit");
+```
+
+In this case the `pull` command has a `-p` option and the `commit` command has
+`-m` and `--amend` options. It's worth noting that you can use the same option
+name between different commands as they are logically in different scopes.
+
+In order to check which command was used you've two options. You can either
+use the version we used above in which case the `ref` variable passed to
+`DefineCommand` will contain the name of the command that was specified. But
+you're not limited to just plain strings. For example, this will work as well:
+
+```C#
+enum Command { Pull, Commit }
+
+// ...
+
+Command command = Command.Pull;
+syntax.DefineCommand("pull", ref command, Command.Pull, "Pull from another repo");
+syntax.DefineCommand("commit", ref command, Command.Commit, "Committing changes");
+```
+
+### Custom help
+
+By default, `ArgumentSyntax` will display the help and exit when `-?`, `-h` or
+`--help` is specified. Some tools perform different actions, for instance `git`,
+which displays the help on the command line when `-h` is used but opens the web
+browser when `--help` is used.
+
+You can support this by handling help yourself:
+
+```C#
+ArgumentSyntax.Parse(args, syntax =>
+{
+ // Turn off built-in help processing
+
+ syntax.HandleHelp = false;
+
+ // Define your own options
+
+ syntax.DefineOption("n|name", ref addressee, "The addressee to greet");
+
+ // Define custom help options. Optionally, you can hide those options
+ // from the help text.
+
+ var longHelp = syntax.DefineOption("help", false);
+ longHelp.IsHidden = true;
+
+ var quickHelp = syntax.DefineOption("h", false);
+ quickHelp.IsHidden = true;
+
+ if (longHelp.Value)
+ {
+ // Open a browser
+ var url = "https://en.wikipedia.org/wiki/%22Hello,_World!%22_program";
+ Process.Start(url);
+ Environment.Exit(0);
+ }
+ else if (quickHelp.Value)
+ {
+ // Show help text. Even if you disable built-in help processing
+ // you can still use the built-in help page generator. Optionally,
+ // you can ask it to word wrap it according to some maximum, such
+ // as the width of the console window.
+ var maxWidth = Console.WindowWidth - 2;
+ var helpText = syntax.GetHelpText(maxWidth);
+ Console.WriteLine(helpText);
+ Environment.Exit(0);
+ }
+});
+```
+
+### Additional validation
+
+Let's say you want to perform additional validation, such as that supplied
+arguments point to valid files or that certain options aren't used in
+combination. You can do this by simply adding a bit of validation code at
+the end of the `Parse` method that calls `ReportError`.
+
+```C#
+ArgumentSyntax.Parse(args, syntax =>
+{
+ syntax.DefineOption("n|name", ref addressee, "The addressee to greet");
+
+ if (addressee.Any(char.IsWhiteSpace))
+ syntax.ReportError("addressee cannot contain whitespace");
+});
+```
+
+Usage will look like this:
+
+```
+$ ./hello -n "Immo Landwerth"
+error: addressee cannot contain whitespace
+```
+
+### Custom error handling
+
+There are cases where you want to use `ArgumentSyntax` in such a way that user
+errors shouldn't result in the process being terminated. You can do this by
+disabling the built-in error handling. In that case, the `ReportError` method --
+and thus the `Parse` method -- will throw `ArgumentSyntaxException`.
+
+```C#
+try
+{
+ ArgumentSyntax.Parse(args, syntax =>
+ {
+ syntax.HandleErrors = false;
+ syntax.DefineOption("n|name", ref addressee);
+ });
+}
+catch (ArgumentSyntaxException ex)
+{
+ Console.WriteLine("Ooops... something didn't go well.");
+ Console.WriteLine(ex.Message);
+ return 1;
+}
+```
+
+### Accessing options and parameters
+
+The `ArgumentSyntax` object also provides access to the defined options and
+parameters. The `Parse` method returns the used instance, so you can use that
+to access them. You can either access all of the defined ones or you can access
+only the ones that are relevant to the currently active command.
+
+```C#
+static void Main(string[] args)
+{
+ var addressee = "world";
+
+ var result = ArgumentSyntax.Parse(args, syntax =>
+ {
+ syntax.DefineOption("n|name", ref addressee, "The addressee to greet");
+
+ if (addressee.Any(char.IsWhiteSpace))
+ syntax.ReportError("adressee cannot contain whitespace");
+ });
+
+ foreach (var argument in result.GetActiveArguments())
+ {
+ if (argument.IsOption)
+ {
+ var names = string.Join(", ", argument.GetDisplayNames());
+ Console.WriteLine("Option {0}", names);
+ }
+ else
+ {
+ Console.WriteLine("Parameter {0}", argument.GetDisplayName());
+ }
+
+ Console.WriteLine("Help : {0}", argument.Help);
+ Console.WriteLine("IsHidden : {0}", argument.IsHidden);
+ Console.WriteLine("Value : {0}", argument.Value);
+ Console.WriteLine("DefaultValue : {0}", argument.DefaultValue);
+ Console.WriteLine("IsSpecified : {0}", argument.IsSpecified);
+ }
+}
+```
+
+### Turning off response files
+
+In case you don't want consumers to use response files or you need to process
+parameters that could be prefixed with an `@`-sign, you can disable response
+file expansion:
+
+```C#
+ArgumentSyntax.Parse(args, syntax =>
+{
+ syntax.HandleResponseFiles = false;
+
+ // ...
+});
+```
diff --git a/src/System.CommandLine/System.CommandLine.csproj b/src/System.CommandLine/System.CommandLine.csproj
new file mode 100644
index 0000000..7e19bc0
--- /dev/null
+++ b/src/System.CommandLine/System.CommandLine.csproj
@@ -0,0 +1,8 @@
+
+
+ net6.0
+ false
+ CLI commandline command parsing
+ Command line parsing support corefxlab
+
+
diff --git a/src/System.CommandLine/System/CommandLine/Argument.cs b/src/System.CommandLine/System/CommandLine/Argument.cs
new file mode 100644
index 0000000..121dc98
--- /dev/null
+++ b/src/System.CommandLine/System/CommandLine/Argument.cs
@@ -0,0 +1,110 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace System.CommandLine
+{
+ public abstract class Argument
+ {
+ internal Argument(ArgumentCommand command, IEnumerable names, bool isOption, bool isRequired)
+ {
+ var nameArray = names.ToArray();
+ Command = command;
+ Name = nameArray.First();
+ Names = new ReadOnlyCollection(nameArray);
+ IsOption = isOption;
+ IsRequired = isRequired;
+ }
+
+ public ArgumentCommand Command { get; private set; }
+
+ public string Name { get; private set; }
+
+ public ReadOnlyCollection Names { get; private set; }
+
+ public string Help { get; set; }
+
+ public bool IsOption { get; private set; }
+
+ public bool IsParameter
+ {
+ get { return !IsOption; }
+ }
+
+ public bool IsSpecified { get; private set; }
+
+ public bool IsHidden { get; set; }
+
+ public bool IsRequired { get; private set; }
+
+ public virtual bool IsList
+ {
+ get { return false; }
+ }
+
+ public object Value
+ {
+ get { return GetValue(); }
+ }
+
+ public object DefaultValue
+ {
+ get { return GetDefaultValue(); }
+ }
+
+ public bool IsActive
+ {
+ get { return Command == null || Command.IsActive; }
+ }
+
+ public abstract bool IsFlag { get; }
+
+ internal abstract object GetValue();
+
+ internal abstract object GetDefaultValue();
+
+ internal void MarkSpecified()
+ {
+ IsSpecified = true;
+ }
+
+ public string GetDisplayName()
+ {
+ return GetDisplayName(Name);
+ }
+
+ public IEnumerable GetDisplayNames()
+ {
+ return Names.Select(GetDisplayName);
+ }
+
+ private string GetDisplayName(string name)
+ {
+ return IsOption ? GetOptionDisplayName(name) : GetParameterDisplayName(name);
+ }
+
+ private static string GetOptionDisplayName(string name)
+ {
+ var modifier = name.Length == 1 ? @"-" : @"--";
+ return modifier + name;
+ }
+
+ private static string GetParameterDisplayName(string name)
+ {
+ return @"<" + name + @">";
+ }
+
+ public virtual string GetDisplayValue()
+ {
+ return Value == null ? string.Empty : Value.ToString();
+ }
+
+ public override string ToString()
+ {
+ return GetDisplayName();
+ }
+ }
+}
diff --git a/src/System.CommandLine/System/CommandLine/ArgumentCommand.cs b/src/System.CommandLine/System/CommandLine/ArgumentCommand.cs
new file mode 100644
index 0000000..c784794
--- /dev/null
+++ b/src/System.CommandLine/System/CommandLine/ArgumentCommand.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.CommandLine
+{
+ public abstract class ArgumentCommand
+ {
+ internal ArgumentCommand(string name)
+ {
+ Name = name;
+ }
+
+ public string Name { get; private set; }
+
+ public string Help { get; set; }
+
+ public object Value
+ {
+ get { return GetValue(); }
+ }
+
+ public bool IsHidden { get; set; }
+
+ public bool IsActive { get; private set; }
+
+ internal abstract object GetValue();
+
+ internal void MarkActive()
+ {
+ IsActive = true;
+ }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.CommandLine/System/CommandLine/ArgumentCommand_1.cs b/src/System.CommandLine/System/CommandLine/ArgumentCommand_1.cs
new file mode 100644
index 0000000..eb5ebdc
--- /dev/null
+++ b/src/System.CommandLine/System/CommandLine/ArgumentCommand_1.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.CommandLine
+{
+ public sealed class ArgumentCommand : ArgumentCommand
+ {
+ internal ArgumentCommand(string name, T value)
+ : base(name)
+ {
+ Value = value;
+ }
+
+ public new T Value { get; private set; }
+
+ internal override object GetValue()
+ {
+ return Value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.CommandLine/System/CommandLine/ArgumentLexer.cs b/src/System.CommandLine/System/CommandLine/ArgumentLexer.cs
new file mode 100644
index 0000000..b39bace
--- /dev/null
+++ b/src/System.CommandLine/System/CommandLine/ArgumentLexer.cs
@@ -0,0 +1,196 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace System.CommandLine
+{
+ internal static class ArgumentLexer
+ {
+ public static IReadOnlyList Lex(IEnumerable args, Func> responseFileReader = null)
+ {
+ var result = new List();
+
+ // We'll split the arguments into tokens.
+ //
+ // A token combines the modifier (/, -, --), the option name, and the option
+ // value.
+ //
+ // Please note that this code doesn't combine arguments. It only provides
+ // some pre-processing over the arguments to split out the modifier,
+ // option, and value:
+ //
+ // { "--out", "out.exe" } ==> { new ArgumentToken("--", "out", null),
+ // new ArgumentToken(null, null, "out.exe") }
+ //
+ // {"--out:out.exe" } ==> { new ArgumentToken("--", "out", "out.exe") }
+ //
+ // The reason it doesn't combine arguments is because it depends on the actual
+ // definition. For example, if --out is a flag (meaning it's of type bool) then
+ // out.exe in the first example wouldn't be considered its value.
+ //
+ // The code also handles the special -- token which indicates that the following
+ // arguments shouldn't be considered options.
+ //
+ // Finally, this code will also expand any reponse file entries, assuming the caller
+ // gave us a non-null reader.
+
+ var hasSeenDashDash = false;
+
+ foreach (var arg in ExpandResponseFiles(args, responseFileReader))
+ {
+ // If we've seen a -- already, then we'll treat one as a plain name, that is
+ // without a modifier or value.
+
+ if (!hasSeenDashDash && arg == @"--")
+ {
+ hasSeenDashDash = true;
+ continue;
+ }
+
+ string modifier;
+ string name;
+ string value;
+
+ if (hasSeenDashDash)
+ {
+ modifier = null;
+ name = arg;
+ value = null;
+ }
+ else
+ {
+ // If we haven't seen the -- separator, we're looking for options.
+ // Options have leading modifiers, i.e. /, -, or --.
+ //
+ // Options can also have values, such as:
+ //
+ // -f:false
+ // --name=hello
+
+
+ if (!TryExtractOption(arg, out modifier, out string nameAndValue))
+ {
+ name = arg;
+ value = null;
+ }
+ else if (!TrySplitNameValue(nameAndValue, out name, out value))
+ {
+ name = nameAndValue;
+ value = null;
+ }
+ }
+
+ var token = new ArgumentToken(modifier, name, value);
+ result.Add(token);
+ }
+
+ // Single letter options can be combined, for example the following two
+ // forms are considered equivalent:
+ //
+ // (1) -xdf
+ // (2) -x -d -f
+ //
+ // In order to free later phases from handling this case, we simply expand
+ // single letter options to the second form.
+
+ for (var i = result.Count - 1; i >= 0; i--)
+ {
+ if (IsOptionBundle(result[i]))
+ ExpandOptionBundle(result, i);
+ }
+
+ return result.ToArray();
+ }
+
+ private static IEnumerable ExpandResponseFiles(IEnumerable args, Func> responseFileReader)
+ {
+ foreach (var arg in args)
+ {
+ if (responseFileReader == null || !arg.StartsWith(@"@"))
+ {
+ yield return arg;
+ }
+ else
+ {
+ var fileName = arg.Substring(1);
+
+ var responseFileArguments = responseFileReader(fileName);
+
+ // The reader can suppress expanding this response file by
+ // returning null. In that case, we'll treat the response
+ // file token as a regular argument.
+
+ if (responseFileArguments == null)
+ {
+ yield return arg;
+ }
+ else
+ {
+ foreach (var responseFileArgument in responseFileArguments)
+ yield return responseFileArgument.Trim();
+ }
+ }
+ }
+ }
+
+ private static bool IsOptionBundle(ArgumentToken token)
+ {
+ return token.IsOption &&
+ token.Modifier == @"-" &&
+ token.Name.Length > 1;
+ }
+
+ private static void ExpandOptionBundle(IList receiver, int index)
+ {
+ var options = receiver[index].Name;
+ receiver.RemoveAt(index);
+
+ foreach (var c in options)
+ {
+ var name = char.ToString(c);
+ var expandedToken = new ArgumentToken(@"-", name, null);
+ receiver.Insert(index, expandedToken);
+ index++;
+ }
+ }
+
+ private static bool TryExtractOption(string text, out string modifier, out string remainder)
+ {
+ return TryExtractOption(text, @"--", out modifier, out remainder) ||
+ TryExtractOption(text, @"-", out modifier, out remainder);
+ }
+
+ private static bool TryExtractOption(string text, string prefix, out string modifier, out string remainder)
+ {
+ if (text.StartsWith(prefix))
+ {
+ remainder = text.Substring(prefix.Length);
+ modifier = prefix;
+ return true;
+ }
+
+ remainder = null;
+ modifier = null;
+ return false;
+ }
+
+ private static bool TrySplitNameValue(string text, out string name, out string value)
+ {
+ for (int idx = 0; idx < text.Length; idx++)
+ {
+ char ch = text[idx];
+ if (ch == ':' || ch == '=')
+ {
+ name = text.Substring(0, idx);
+ value = text.Substring(idx + 1);
+ return true;
+ }
+ }
+ name = null;
+ value = null;
+ return false;
+ }
+ }
+}
diff --git a/src/System.CommandLine/System/CommandLine/ArgumentList_1.cs b/src/System.CommandLine/System/CommandLine/ArgumentList_1.cs
new file mode 100644
index 0000000..c4cd667
--- /dev/null
+++ b/src/System.CommandLine/System/CommandLine/ArgumentList_1.cs
@@ -0,0 +1,60 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace System.CommandLine
+{
+ public sealed class ArgumentList : Argument
+ {
+ internal ArgumentList(ArgumentCommand command, IEnumerable names, IReadOnlyList defaultValue, bool isRequired)
+ : base(command, names, true, isRequired)
+ {
+ Value = defaultValue;
+ DefaultValue = defaultValue;
+ }
+
+ internal ArgumentList(ArgumentCommand command, string name, IReadOnlyList defaultValue)
+ : base(command, new[] { name }, false, true)
+ {
+ Value = defaultValue;
+ DefaultValue = defaultValue;
+ }
+
+ public override bool IsList
+ {
+ get { return true; }
+ }
+
+ public new IReadOnlyList Value { get; private set; }
+
+ public new IReadOnlyList DefaultValue { get; private set; }
+
+ public override bool IsFlag
+ {
+ get { return typeof(T) == typeof(bool); }
+ }
+
+ internal override object GetValue()
+ {
+ return Value;
+ }
+
+ internal override object GetDefaultValue()
+ {
+ return DefaultValue;
+ }
+
+ internal void SetValue(IReadOnlyList value)
+ {
+ Value = value;
+ MarkSpecified();
+ }
+
+ public override string GetDisplayValue()
+ {
+ return string.Join(@", ", Value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.CommandLine/System/CommandLine/ArgumentParser.cs b/src/System.CommandLine/System/CommandLine/ArgumentParser.cs
new file mode 100644
index 0000000..c44111c
--- /dev/null
+++ b/src/System.CommandLine/System/CommandLine/ArgumentParser.cs
@@ -0,0 +1,265 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace System.CommandLine
+{
+ internal sealed class ArgumentParser
+ {
+ private readonly IReadOnlyList _tokens;
+
+ public ArgumentParser(IEnumerable arguments)
+ : this(arguments, null)
+ {
+ }
+
+ public ArgumentParser(IEnumerable arguments, Func> responseFileReader)
+ {
+ if (arguments == null)
+ throw new ArgumentNullException("arguments");
+
+ _tokens = ArgumentLexer.Lex(arguments, responseFileReader);
+ }
+
+ public bool TryParseCommand(string name)
+ {
+ var token = _tokens.FirstOrDefault();
+
+ if (token == null || token.IsOption || token.IsSeparator)
+ return false;
+
+ if (!string.Equals(token.Name, name, StringComparison.Ordinal))
+ return false;
+
+ token.MarkMatched();
+ return true;
+ }
+
+ public bool TryParseOption(string diagnosticName, IReadOnlyCollection names, Func valueConverter, bool isRequired, out T value, out bool specified)
+ {
+ if (!TryParseOptionList(diagnosticName, names, valueConverter, isRequired, out IReadOnlyList values, out specified))
+ {
+ value = default;
+ return false;
+ }
+
+ // Please note that we don't verify that the option is only specified once.
+ // It's tradition on Unix to allow single options to occur more than once,
+ // with 'last one wins' semantics. This simplifies scripting because you
+ // can easily combine arguments.
+
+ value = values.Last();
+ return true;
+ }
+
+ public bool TryParseOptionList(string diagnosticName, IReadOnlyCollection names, Func valueConverter, bool isRequired, out IReadOnlyList values, out bool specified)
+ {
+ var result = new List();
+ var tokenIndex = 0;
+ var isFlag = typeof(T) == typeof(bool);
+ specified = false;
+
+ while (tokenIndex < _tokens.Count)
+ {
+ if (TryParseOption(ref tokenIndex, names))
+ {
+ specified = true;
+ if (TryParseOptionArgument(ref tokenIndex, isFlag, out string valueText))
+ {
+ var value = ParseValue(diagnosticName, valueConverter, valueText);
+ result.Add(value);
+ }
+ else if (isFlag)
+ {
+ var value = (T)(object)true;
+ result.Add(value);
+ }
+ else if (isRequired)
+ {
+ var message = string.Format(Strings.OptionRequiresValueFmt, diagnosticName);
+ throw new ArgumentSyntaxException(message);
+ }
+ }
+
+ tokenIndex++;
+ }
+
+ if (!result.Any())
+ {
+ values = null;
+ return false;
+ }
+
+ values = result.ToArray();
+ return true;
+ }
+
+ public bool TryParseParameter(string diagnosticName, Func valueConverter, out T value)
+ {
+ foreach (var token in _tokens)
+ {
+ if (token.IsMatched || token.IsOption || token.IsSeparator)
+ continue;
+
+ token.MarkMatched();
+
+ var valueText = token.Name;
+ value = ParseValue(diagnosticName, valueConverter, valueText);
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
+ public bool TryParseParameterList(string diagnosticName, Func valueConverter, out IReadOnlyList values)
+ {
+ var result = new List();
+
+ while (TryParseParameter(diagnosticName, valueConverter, out T value))
+ {
+ result.Add(value);
+ }
+
+ if (!result.Any())
+ {
+ values = null;
+ return false;
+ }
+
+ values = result.ToArray();
+ return true;
+ }
+
+ private bool TryParseOption(ref int tokenIndex, IReadOnlyCollection names)
+ {
+ while (tokenIndex < _tokens.Count)
+ {
+ var a = _tokens[tokenIndex];
+
+ if (a.IsOption)
+ {
+ if (names.Any(n => string.Equals(a.Name, n, StringComparison.Ordinal)))
+ {
+ a.MarkMatched();
+ return true;
+ }
+ }
+
+ tokenIndex++;
+ }
+
+ return false;
+ }
+
+ private bool TryParseOptionArgument(ref int tokenIndex, bool isFlag, out string argument)
+ {
+ argument = null;
+
+ // Let's see whether the current token already has value, like "-o:value"
+
+ var a = _tokens[tokenIndex];
+ if (a.HasValue)
+ {
+ a.MarkMatched();
+ argument = a.Value;
+ return true;
+ }
+
+ // OK, we may need to have to advance one or two tokens. Since we don't know
+ // up front, we'll take a look ahead.
+
+
+ // So, do we have a token?
+
+ if (!TryGetNextToken(tokenIndex, out ArgumentToken lookahead))
+ return false;
+
+ // If it's an option, then it's not an argument and we're done.
+
+ if (lookahead.IsOption)
+ return false;
+
+ // OK, the lookahead is either a separator or it's an argument.
+
+ if (!lookahead.IsSeparator)
+ {
+ // If this is a flag, we need an explicit separator.
+ // Since there is none, we don't consume this token.
+
+ if (isFlag)
+ return false;
+
+ lookahead.MarkMatched();
+ argument = lookahead.Name;
+ tokenIndex++;
+ return true;
+ }
+
+ // Skip separator
+
+ lookahead.MarkMatched();
+ tokenIndex++;
+
+ // See whether the next token is an argument.
+
+ if (!TryGetNextToken(tokenIndex, out lookahead))
+ return false;
+
+ if (lookahead.IsOption || lookahead.IsSeparator)
+ return false;
+
+ lookahead.MarkMatched();
+ argument = lookahead.Name;
+ tokenIndex++;
+ return true;
+ }
+
+ private bool TryGetNextToken(int tokenIndex, out ArgumentToken token)
+ {
+ if (++tokenIndex >= _tokens.Count)
+ {
+ token = null;
+ return false;
+ }
+
+ token = _tokens[tokenIndex];
+ return true;
+ }
+
+ private static T ParseValue(string diagnosticName, Func valueConverter, string valueText)
+ {
+ try
+ {
+ return valueConverter(valueText);
+ }
+ catch (FormatException ex)
+ {
+ var message = string.Format(Strings.CannotParseValueFmt, valueText, diagnosticName, ex.Message);
+ throw new ArgumentSyntaxException(message);
+ }
+ }
+
+ public string GetUnreadCommand()
+ {
+ return _tokens.Where(t => !t.IsOption && !t.IsSeparator).Select(t => t.Name).FirstOrDefault();
+ }
+
+ public IReadOnlyList GetUnreadOptionNames()
+ {
+ return _tokens.Where(t => !t.IsMatched && t.IsOption).Select(t => t.Modifier + t.Name).ToArray();
+ }
+
+ public IReadOnlyList GetUnreadParameters()
+ {
+ return _tokens.Where(t => !t.IsMatched && !t.IsOption).Select(t => t.ToString()).ToArray();
+ }
+
+ public IReadOnlyList GetUnreadArguments()
+ {
+ return _tokens.Where(t => !t.IsMatched).Select(t => t.ToString()).ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.CommandLine/System/CommandLine/ArgumentSyntax.cs b/src/System.CommandLine/System/CommandLine/ArgumentSyntax.cs
new file mode 100644
index 0000000..aa2184a
--- /dev/null
+++ b/src/System.CommandLine/System/CommandLine/ArgumentSyntax.cs
@@ -0,0 +1,443 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.IO;
+
+namespace System.CommandLine
+{
+ public sealed partial class ArgumentSyntax
+ {
+ private readonly IEnumerable _arguments;
+ private readonly List _commands = new List();
+ private readonly List _options = new List();
+ private readonly List _parameters = new List();
+
+ private ArgumentParser _parser;
+ private ArgumentCommand _definedCommand;
+ private ArgumentCommand _activeCommand;
+
+ internal ArgumentSyntax(IEnumerable arguments)
+ {
+ _arguments = arguments;
+
+ ApplicationName = GetApplicationName();
+ HandleErrors = true;
+ HandleHelp = true;
+ HandleResponseFiles = true;
+ ErrorOnUnexpectedArguments = true;
+ }
+
+ public static ArgumentSyntax Parse(IEnumerable arguments, Action defineAction)
+ {
+ if (arguments == null)
+ throw new ArgumentNullException("arguments");
+
+ if (defineAction == null)
+ throw new ArgumentNullException("defineAction");
+
+ var syntax = new ArgumentSyntax(arguments);
+ defineAction(syntax);
+ syntax.Validate();
+ return syntax;
+ }
+
+ private void Validate()
+ {
+ // Check whether help is requested
+
+ if (HandleHelp && IsHelpRequested())
+ {
+ var helpText = GetHelpText();
+ Console.Error.Write(helpText);
+ Environment.Exit(0);
+ }
+
+ // Check for invalid or missing command
+
+ if (_activeCommand == null && _commands.Any())
+ {
+ var unreadCommand = Parser.GetUnreadCommand();
+ var message = unreadCommand == null
+ ? Strings.MissingCommand
+ : string.Format(Strings.UnknownCommandFmt, unreadCommand);
+ ReportError(message);
+ }
+
+ if (ErrorOnUnexpectedArguments)
+ {
+ // Check for invalid options and extra parameters
+
+ foreach (var option in Parser.GetUnreadOptionNames())
+ {
+ var message = string.Format(Strings.InvalidOptionFmt, option);
+ ReportError(message);
+ }
+
+ foreach (var parameter in Parser.GetUnreadParameters())
+ {
+ var message = string.Format(Strings.ExtraParameterFmt, parameter);
+ ReportError(message);
+ }
+ }
+ }
+
+ private bool IsHelpRequested()
+ {
+ return Parser.GetUnreadOptionNames()
+ .Any(a => string.Equals(a, @"-?", StringComparison.Ordinal) ||
+ string.Equals(a, @"-h", StringComparison.Ordinal) ||
+ string.Equals(a, @"--help", StringComparison.Ordinal));
+ }
+
+ public void ReportError(string message)
+ {
+ if (HandleErrors)
+ {
+ Console.Error.WriteLine(Strings.ErrorWithMessageFmt, message);
+ Environment.Exit(1);
+ }
+
+ throw new ArgumentSyntaxException(message);
+ }
+
+ public ArgumentCommand DefineCommand(string name, T value)
+ {
+ if (string.IsNullOrEmpty(name))
+ throw new ArgumentException(Strings.NameMissing, "name");
+
+ if (!IsValidName(name))
+ {
+ var message = string.Format(Strings.CommandNameIsNotValidFmt, name);
+ throw new ArgumentException(message, "name");
+ }
+
+ if (_commands.Any(c => string.Equals(c.Name, name, StringComparison.Ordinal)))
+ {
+ var message = string.Format(Strings.CommandAlreadyDefinedFmt, name);
+ throw new InvalidOperationException(message);
+ }
+
+ if (_options.Concat(_parameters).Any(c => c.Command == null))
+ throw new InvalidOperationException(Strings.CannotDefineCommandsIfArgumentsExist);
+
+ var definedCommand = new ArgumentCommand(name, value);
+ _commands.Add(definedCommand);
+ _definedCommand = definedCommand;
+
+ if (_activeCommand != null)
+ return definedCommand;
+
+ if (!Parser.TryParseCommand(name))
+ return definedCommand;
+
+ _activeCommand = _definedCommand;
+ _activeCommand.MarkActive();
+
+ return definedCommand;
+ }
+
+ public Argument DefineOption(string name, T defaultValue, Func valueConverter, bool isRequired)
+ {
+ if (string.IsNullOrEmpty(name))
+ throw new ArgumentException(Strings.NameMissing, "name");
+
+ if (DefinedParameters.Any())
+ throw new InvalidOperationException(Strings.OptionsMustBeDefinedBeforeParameters);
+
+ var names = ParseOptionNameList(name);
+ var option = new Argument(_definedCommand, names, defaultValue, isRequired);
+ _options.Add(option);
+
+ if (_activeCommand != _definedCommand)
+ return option;
+
+ try
+ {
+ if (Parser.TryParseOption(option.GetDisplayName(), option.Names, valueConverter, isRequired, out T value, out bool specified))
+ {
+ option.SetValue(value);
+ }
+ else if (specified)
+ {
+ // No value was provided, but the option was specified and a value wasn't required
+ option.MarkSpecified();
+ }
+ }
+ catch (ArgumentSyntaxException ex)
+ {
+ ReportError(ex.Message);
+ }
+
+ return option;
+ }
+
+ public ArgumentList DefineOptionList(string name, IReadOnlyList defaultValue, Func valueConverter, bool isRequired)
+ {
+ if (string.IsNullOrEmpty(name))
+ throw new ArgumentException(Strings.NameMissing, "name");
+
+ if (DefinedParameters.Any())
+ throw new InvalidOperationException(Strings.OptionsMustBeDefinedBeforeParameters);
+
+ var names = ParseOptionNameList(name);
+ var optionList = new ArgumentList(_definedCommand, names, defaultValue, isRequired);
+ _options.Add(optionList);
+
+ if (_activeCommand != _definedCommand)
+ return optionList;
+
+ try
+ {
+ if (Parser.TryParseOptionList(optionList.GetDisplayName(), optionList.Names, valueConverter, isRequired, out IReadOnlyList value, out bool specified))
+ {
+ optionList.SetValue(value);
+ }
+ else if (specified)
+ {
+ // No value was provided, but the option was specified and a value wasn't required
+ optionList.MarkSpecified();
+ }
+ }
+ catch (ArgumentSyntaxException ex)
+ {
+ ReportError(ex.Message);
+ }
+
+ return optionList;
+ }
+
+ public Argument DefineParameter(string name, T defaultValue, Func valueConverter)
+ {
+ if (string.IsNullOrEmpty(name))
+ throw new ArgumentException(Strings.NameMissing, "name");
+
+ if (!IsValidName(name))
+ {
+ var message = string.Format(Strings.ParameterNameIsNotValidFmt, name);
+ throw new ArgumentException(message, "name");
+ }
+
+ if (DefinedParameters.Any(p => p.IsList))
+ throw new InvalidOperationException(Strings.ParametersCannotBeDefinedAfterLists);
+
+ if (DefinedParameters.Any(p => string.Equals(name, p.Name, StringComparison.OrdinalIgnoreCase)))
+ {
+ var message = string.Format(Strings.ParameterAlreadyDefinedFmt, name);
+ throw new InvalidOperationException(message);
+ }
+
+ var parameter = new Argument(_definedCommand, name, defaultValue);
+ _parameters.Add(parameter);
+
+ if (_activeCommand != _definedCommand)
+ return parameter;
+
+ try
+ {
+ if (Parser.TryParseParameter(parameter.GetDisplayName(), valueConverter, out T value))
+ parameter.SetValue(value);
+ }
+ catch (ArgumentSyntaxException ex)
+ {
+ ReportError(ex.Message);
+ }
+
+ return parameter;
+ }
+
+ public ArgumentList DefineParameterList(string name, IReadOnlyList defaultValue, Func valueConverter)
+ {
+ if (string.IsNullOrEmpty(name))
+ throw new ArgumentException(Strings.NameMissing, "name");
+
+ if (!IsValidName(name))
+ {
+ var message = string.Format(Strings.ParameterNameIsNotValidFmt, name);
+ throw new ArgumentException(message, "name");
+ }
+
+ if (DefinedParameters.Any(p => p.IsList))
+ throw new InvalidOperationException(Strings.CannotDefineMultipleParameterLists);
+
+ var parameterList = new ArgumentList(_definedCommand, name, defaultValue);
+ _parameters.Add(parameterList);
+
+ if (_activeCommand != _definedCommand)
+ return parameterList;
+
+ try
+ {
+ if (Parser.TryParseParameterList(parameterList.GetDisplayName(), valueConverter, out IReadOnlyList values))
+ parameterList.SetValue(values);
+ }
+ catch (ArgumentSyntaxException ex)
+ {
+ ReportError(ex.Message);
+ }
+
+ return parameterList;
+ }
+
+ private static bool IsValidName(string name)
+ {
+ if (string.IsNullOrEmpty(name))
+ return false;
+
+ if (name[0] == '-')
+ return false;
+
+ return name.All(c => char.IsLetterOrDigit(c) ||
+ c == '-' ||
+ c == '_');
+ }
+
+ private IEnumerable ParseOptionNameList(string name)
+ {
+ var names = name.Split('|').Select(n => n.Trim()).ToArray();
+
+ foreach (var alias in names)
+ {
+ if (!IsValidName(alias))
+ {
+ var message = string.Format(Strings.OptionNameIsNotValidFmt, alias);
+ throw new ArgumentException(message, "name");
+ }
+
+ foreach (var option in DefinedOptions)
+ {
+ if (option.Names.Any(n => string.Equals(n, alias, StringComparison.Ordinal)))
+ {
+ var message = string.Format(Strings.OptionAlreadyDefinedFmt, alias);
+ throw new InvalidOperationException(message);
+ }
+ }
+ }
+
+ return names;
+ }
+
+ private IEnumerable ParseResponseFile(string fileName)
+ {
+ if (!HandleResponseFiles)
+ return null;
+
+ if (!File.Exists(fileName))
+ {
+ var message = string.Format(Strings.ResponseFileDoesNotExistFmt, fileName);
+ ReportError(message);
+ }
+
+ return File.ReadLines(fileName);
+ }
+
+ private static string GetApplicationName()
+ {
+ var processPath = Environment.GetCommandLineArgs()[0];
+ var processName = Path.GetFileNameWithoutExtension(processPath);
+ return processName;
+ }
+
+ public string ApplicationName { get; set; }
+
+ public bool HandleErrors { get; set; }
+
+ public bool HandleHelp { get; set; }
+
+ public bool HandleResponseFiles { get; set; }
+
+ public bool ErrorOnUnexpectedArguments { get; set; }
+
+ public IEnumerable RemainingArguments
+ => Parser.GetUnreadArguments();
+
+ private ArgumentParser Parser
+ {
+ get
+ {
+ if (_parser == null)
+ _parser = new ArgumentParser(_arguments, ParseResponseFile);
+
+ return _parser;
+ }
+ }
+
+ private IEnumerable DefinedOptions
+ {
+ get { return _options.Where(o => o.Command == null || o.Command == _definedCommand); }
+ }
+
+ private IEnumerable DefinedParameters
+ {
+ get { return _parameters.Where(p => p.Command == null || p.Command == _definedCommand); }
+ }
+
+ public ArgumentCommand ActiveCommand
+ {
+ get { return _activeCommand; }
+ }
+
+ public IReadOnlyList Commands
+ {
+ get { return _commands; }
+ }
+
+ public IEnumerable GetArguments()
+ {
+ return _options.Concat(_parameters);
+ }
+
+ public IEnumerable GetArguments(ArgumentCommand command)
+ {
+ return GetArguments().Where(c => c.Command == null || c.Command == command);
+ }
+
+ public IEnumerable GetOptions()
+ {
+ return _options;
+ }
+
+ public IEnumerable GetOptions(ArgumentCommand command)
+ {
+ return _options.Where(c => c.Command == null || c.Command == command);
+ }
+
+ public IEnumerable GetParameters()
+ {
+ return _parameters;
+ }
+
+ public IEnumerable GetParameters(ArgumentCommand command)
+ {
+ return _parameters.Where(c => c.Command == null || c.Command == command);
+ }
+
+ public IEnumerable GetActiveArguments()
+ {
+ return GetArguments(ActiveCommand);
+ }
+
+ public IEnumerable GetActiveOptions()
+ {
+ return GetOptions(ActiveCommand);
+ }
+
+ public IEnumerable GetActiveParameters()
+ {
+ return GetParameters(ActiveCommand);
+ }
+
+ public string GetHelpText()
+ {
+ // TODO: This should use Console.WindowWidth but this API isn't available yet.
+ // return GetHelpText(Console.WindowWidth - 2);
+ return GetHelpText(72);
+ }
+
+ public string GetHelpText(int maxWidth)
+ {
+ return HelpTextGenerator.Generate(this, maxWidth);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.CommandLine/System/CommandLine/ArgumentSyntaxException.cs b/src/System.CommandLine/System/CommandLine/ArgumentSyntaxException.cs
new file mode 100644
index 0000000..96c1236
--- /dev/null
+++ b/src/System.CommandLine/System/CommandLine/ArgumentSyntaxException.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.CommandLine
+{
+ public sealed class ArgumentSyntaxException : Exception
+ {
+ public ArgumentSyntaxException()
+ {
+ }
+
+ public ArgumentSyntaxException(string message)
+ : base(message)
+ {
+ }
+
+ public ArgumentSyntaxException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.CommandLine/System/CommandLine/ArgumentSyntax_Definers.cs b/src/System.CommandLine/System/CommandLine/ArgumentSyntax_Definers.cs
new file mode 100644
index 0000000..16b19d8
--- /dev/null
+++ b/src/System.CommandLine/System/CommandLine/ArgumentSyntax_Definers.cs
@@ -0,0 +1,253 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace System.CommandLine
+{
+ public partial class ArgumentSyntax
+ {
+ private static readonly Func s_stringParser = v => v;
+ private static readonly Func s_booleanParser = v => bool.Parse(v);
+ private static readonly Func s_int32Parser = v => int.Parse(v, CultureInfo.InvariantCulture);
+
+ // Commands
+
+ public ArgumentCommand DefineCommand(string name)
+ {
+ return DefineCommand(name, name);
+ }
+
+ public ArgumentCommand DefineCommand(string name, ref T command, T value, string help)
+ {
+ var result = DefineCommand(name, value);
+ result.Help = help;
+
+ if (_activeCommand == result)
+ command = value;
+
+ return result;
+ }
+
+ public ArgumentCommand DefineCommand(string name, ref string value, string help)
+ {
+ return DefineCommand(name, ref value, name, help);
+ }
+
+ // Options
+
+ public Argument DefineOption(string name, string defaultValue)
+ {
+ return DefineOption(name, defaultValue, s_stringParser);
+ }
+
+ public Argument DefineOption(string name, bool defaultValue)
+ {
+ return DefineOption(name, defaultValue, s_booleanParser);
+ }
+
+ public Argument DefineOption(string name, int defaultValue)
+ {
+ return DefineOption(name, defaultValue, s_int32Parser);
+ }
+
+ public Argument DefineOption(string name, string defaultValue, bool requireValue)
+ {
+ return DefineOption(name, defaultValue, s_stringParser, requireValue);
+ }
+
+ public Argument DefineOption(string name, bool defaultValue, bool requireValue)
+ {
+ return DefineOption(name, defaultValue, s_booleanParser, requireValue);
+ }
+
+ public Argument DefineOption(string name, int defaultValue, bool requireValue)
+ {
+ return DefineOption(name, defaultValue, s_int32Parser, requireValue);
+ }
+
+ public Argument DefineOption(string name, T defaultValue, Func valueConverter)
+ {
+ return DefineOption(name, defaultValue, valueConverter, true);
+ }
+
+ public Argument DefineOption(string name, ref T value, Func valueConverter, string help)
+ {
+ return DefineOption(name, ref value, valueConverter, true, help);
+ }
+
+ public Argument DefineOption(string name, ref T value, Func valueConverter, bool requireValue, string help)
+ {
+ var option = DefineOption(name, value, valueConverter, requireValue);
+ option.Help = help;
+
+ value = option.Value;
+ return option;
+ }
+
+ public Argument DefineOption(string name, ref string value, string help)
+ {
+ return DefineOption(name, ref value, s_stringParser, help);
+ }
+
+ public Argument DefineOption(string name, ref bool value, string help)
+ {
+ return DefineOption(name, ref value, s_booleanParser, help);
+ }
+
+ public Argument DefineOption(string name, ref int value, string help)
+ {
+ return DefineOption(name, ref value, s_int32Parser, help);
+ }
+
+ public Argument DefineOption(string name, ref string value, bool requireValue, string help)
+ {
+ return DefineOption(name, ref value, s_stringParser, requireValue, help);
+ }
+
+ public Argument DefineOption(string name, ref bool value, bool requireValue, string help)
+ {
+ return DefineOption(name, ref value, s_booleanParser, requireValue, help);
+ }
+
+ public Argument DefineOption(string name, ref int value, bool requireValue, string help)
+ {
+ return DefineOption(name, ref value, s_int32Parser, requireValue, help);
+ }
+
+ // Option lists
+
+ public ArgumentList DefineOptionList(string name, IReadOnlyList defaultValue)
+ {
+ return DefineOptionList(name, defaultValue, s_stringParser);
+ }
+
+ public ArgumentList DefineOptionList(string name, IReadOnlyList defaultValue)
+ {
+ return DefineOptionList(name, defaultValue, s_int32Parser);
+ }
+
+ public ArgumentList DefineOptionList(string name, IReadOnlyList defaultValue, bool requireValue)
+ {
+ return DefineOptionList(name, defaultValue, s_stringParser, requireValue);
+ }
+
+ public ArgumentList DefineOptionList(string name, IReadOnlyList defaultValue, bool requireValue)
+ {
+ return DefineOptionList(name, defaultValue, s_int32Parser, requireValue);
+ }
+
+ public ArgumentList DefineOptionList(string name, IReadOnlyList defaultValue, Func valueConverter)
+ {
+ return DefineOptionList(name, defaultValue, valueConverter, true);
+ }
+
+ public ArgumentList DefineOptionList(string name, ref IReadOnlyList value, Func valueConverter, string help)
+ {
+ return DefineOptionList(name, ref value, valueConverter, true, help);
+ }
+
+ public ArgumentList DefineOptionList(string name, ref IReadOnlyList value, Func valueConverter, bool requireValue, string help)
+ {
+ var optionList = DefineOptionList(name, value, valueConverter, requireValue);
+ optionList.Help = help;
+
+ value = optionList.Value;
+ return optionList;
+ }
+
+ public ArgumentList DefineOptionList(string name, ref IReadOnlyList value, string help)
+ {
+ return DefineOptionList(name, ref value, s_stringParser, help);
+ }
+
+ public ArgumentList DefineOptionList(string name, ref IReadOnlyList value, string help)
+ {
+ return DefineOptionList(name, ref value, s_int32Parser, help);
+ }
+
+ public ArgumentList DefineOptionList(string name, ref IReadOnlyList value, bool requireValue, string help)
+ {
+ return DefineOptionList(name, ref value, s_stringParser, requireValue, help);
+ }
+
+ public ArgumentList DefineOptionList(string name, ref IReadOnlyList value, bool requireValue, string help)
+ {
+ return DefineOptionList(name, ref value, s_int32Parser, requireValue, help);
+ }
+
+ // Parameters
+
+ public Argument DefineParameter(string name, string defaultValue)
+ {
+ return DefineParameter(name, defaultValue, s_stringParser);
+ }
+
+ public Argument DefineParameter(string name, bool defaultValue)
+ {
+ return DefineParameter(name, defaultValue, s_booleanParser);
+ }
+
+ public Argument DefineParameter(string name, int defaultValue)
+ {
+ return DefineParameter(name, defaultValue, s_int32Parser);
+ }
+
+ public Argument DefineParameter(string name, ref T value, Func valueConverter, string help)
+ {
+ var parameter = DefineParameter(name, value, valueConverter);
+ parameter.Help = help;
+
+ value = parameter.Value;
+ return parameter;
+ }
+
+ public Argument DefineParameter(string name, ref string value, string help)
+ {
+ return DefineParameter(name, ref value, s_stringParser, help);
+ }
+
+ public Argument DefineParameter(string name, ref bool value, string help)
+ {
+ return DefineParameter(name, ref value, s_booleanParser, help);
+ }
+
+ public Argument DefineParameter(string name, ref int value, string help)
+ {
+ return DefineParameter(name, ref value, s_int32Parser, help);
+ }
+
+ // Parameter list
+
+ public ArgumentList DefineParameterList(string name, IReadOnlyList defaultValue)
+ {
+ return DefineParameterList(name, defaultValue, s_stringParser);
+ }
+
+ public ArgumentList DefineParameterList(string name, IReadOnlyList defaultValue)
+ {
+ return DefineParameterList(name, defaultValue, s_int32Parser);
+ }
+
+ public ArgumentList DefineParameterList(string name, ref IReadOnlyList value, Func valueConverter, string help)
+ {
+ var parameterList = DefineParameterList(name, value, valueConverter);
+ parameterList.Help = help;
+
+ value = parameterList.Value;
+ return parameterList;
+ }
+
+ public ArgumentList DefineParameterList(string name, ref IReadOnlyList value, string help)
+ {
+ return DefineParameterList(name, ref value, s_stringParser, help);
+ }
+
+ public ArgumentList DefineParameterList(string name, ref IReadOnlyList value, string help)
+ {
+ return DefineParameterList(name, ref value, s_int32Parser, help);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.CommandLine/System/CommandLine/ArgumentToken.cs b/src/System.CommandLine/System/CommandLine/ArgumentToken.cs
new file mode 100644
index 0000000..b155d25
--- /dev/null
+++ b/src/System.CommandLine/System/CommandLine/ArgumentToken.cs
@@ -0,0 +1,81 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.CommandLine
+{
+ internal sealed class ArgumentToken
+ {
+ internal ArgumentToken(string modifier, string name, string value)
+ {
+ Modifier = modifier;
+ Name = name;
+ Value = value;
+ }
+
+ public string Modifier { get; private set; }
+
+ public string Name { get; private set; }
+
+ public string Value { get; private set; }
+
+ public bool IsOption
+ {
+ get { return !string.IsNullOrEmpty(Modifier); }
+ }
+
+ public bool IsSeparator
+ {
+ get { return Name == @":" || Name == @"="; }
+ }
+
+ public bool HasValue
+ {
+ get { return !string.IsNullOrEmpty(Value); }
+ }
+
+ public bool IsMatched { get; private set; }
+
+ public void MarkMatched()
+ {
+ IsMatched = true;
+ }
+
+ private bool Equals(ArgumentToken other)
+ {
+ return string.Equals(Modifier, other.Modifier) &&
+ string.Equals(Name, other.Name) &&
+ string.Equals(Value, other.Value);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(obj, null))
+ return false;
+
+ if (ReferenceEquals(obj, this))
+ return true;
+
+ var other = obj as ArgumentToken;
+ return !ReferenceEquals(other, null) && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = (Modifier != null ? Modifier.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ (Name != null ? Name.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ (Value != null ? Value.GetHashCode() : 0);
+ return hashCode;
+ }
+ }
+
+ public override string ToString()
+ {
+ return HasValue
+ ? string.Format(@"{0}{1}:{2}", Modifier, Name, Value)
+ : string.Format(@"{0}{1}", Modifier, Name);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.CommandLine/System/CommandLine/Argument_1.cs b/src/System.CommandLine/System/CommandLine/Argument_1.cs
new file mode 100644
index 0000000..7f10399
--- /dev/null
+++ b/src/System.CommandLine/System/CommandLine/Argument_1.cs
@@ -0,0 +1,50 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace System.CommandLine
+{
+ public sealed class Argument : Argument
+ {
+ internal Argument(ArgumentCommand command, IEnumerable names, T defaultValue, bool isRequired)
+ : base(command, names, true, isRequired)
+ {
+ Value = defaultValue;
+ Value = DefaultValue = defaultValue;
+ }
+
+ internal Argument(ArgumentCommand command, string name, T defaultValue)
+ : base(command, new[] { name }, false, true)
+ {
+ Value = defaultValue;
+ DefaultValue = defaultValue;
+ }
+
+ public new T Value { get; private set; }
+
+ public new T DefaultValue { get; private set; }
+
+ public override bool IsFlag
+ {
+ get { return typeof(T) == typeof(bool); }
+ }
+
+ internal override object GetValue()
+ {
+ return Value;
+ }
+
+ internal override object GetDefaultValue()
+ {
+ return DefaultValue;
+ }
+
+ internal void SetValue(T value)
+ {
+ Value = value;
+ MarkSpecified();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.CommandLine/System/CommandLine/Enumerable.cs b/src/System.CommandLine/System/CommandLine/Enumerable.cs
new file mode 100644
index 0000000..2dca1dc
--- /dev/null
+++ b/src/System.CommandLine/System/CommandLine/Enumerable.cs
@@ -0,0 +1,87 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.CommandLine
+{
+ // Low level replacement of LINQ Enumerable class. In particular, this implementation
+ // doesn't use generic virtual methods.
+ // System.CommandLine needs to be usable for small .NET Native apps that don't carry the
+ // overhead of expensive runtime features such as the generic virtual methods.
+ internal static class Enumerable
+ {
+ public static IEnumerable Empty()
+ {
+ return Linq.Enumerable.Empty();
+ }
+
+ public static IEnumerable Select(this IEnumerable values, Func func)
+ {
+ Debug.Assert(values != null);
+
+ foreach (T value in values)
+ {
+ yield return func(value);
+ }
+ }
+
+ public static IEnumerable Where(this IEnumerable values, Func func)
+ {
+ Debug.Assert(values != null);
+
+ foreach (T value in values)
+ {
+ if (func(value))
+ yield return value;
+ }
+ }
+
+ public static IEnumerable Concat(this IEnumerable first, IEnumerable second)
+ {
+ return Linq.Enumerable.Concat(first, second);
+ }
+
+ public static bool All(this IEnumerable source, Func predicate)
+ {
+ return Linq.Enumerable.All(source, predicate);
+ }
+
+ public static bool Any(this IEnumerable source, Func predicate)
+ {
+ return Linq.Enumerable.Any(source, predicate);
+ }
+
+ public static bool Any(this IEnumerable source)
+ {
+ return Linq.Enumerable.Any(source);
+ }
+
+ public static T[] ToArray(this IEnumerable source)
+ {
+ return Linq.Enumerable.ToArray(source);
+ }
+
+ public static T Last(this IEnumerable source)
+ {
+ return Linq.Enumerable.Last(source);
+ }
+
+ public static T FirstOrDefault(this IEnumerable source)
+ {
+ return Linq.Enumerable.FirstOrDefault(source);
+ }
+
+ public static T First(this IEnumerable source)
+ {
+ return Linq.Enumerable.First(source);
+ }
+
+ public static int Max(this IEnumerable source)
+ {
+ return Linq.Enumerable.Max(source);
+ }
+ }
+}
diff --git a/src/System.CommandLine/System/CommandLine/HelpTextGenerator.cs b/src/System.CommandLine/System/CommandLine/HelpTextGenerator.cs
new file mode 100644
index 0000000..3d8b522
--- /dev/null
+++ b/src/System.CommandLine/System/CommandLine/HelpTextGenerator.cs
@@ -0,0 +1,260 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Text;
+
+namespace System.CommandLine
+{
+ internal static class HelpTextGenerator
+ {
+ public static string Generate(ArgumentSyntax argumentSyntax, int maxWidth)
+ {
+ var forCommandList = argumentSyntax.ActiveCommand == null &&
+ argumentSyntax.Commands.Any();
+
+ var page = forCommandList
+ ? GetCommandListHelp(argumentSyntax)
+ : GetCommandHelp(argumentSyntax, argumentSyntax.ActiveCommand);
+
+ var sb = new StringBuilder();
+ sb.WriteHelpPage(page, maxWidth);
+ return sb.ToString();
+ }
+
+ private struct HelpPage
+ {
+ public string ApplicationName;
+ public IEnumerable SyntaxElements;
+ public IReadOnlyList Rows;
+ }
+
+ private struct HelpRow
+ {
+ public string Header;
+ public string Text;
+ }
+
+ private static void WriteHelpPage(this StringBuilder sb, HelpPage page, int maxWidth)
+ {
+ sb.WriteUsage(page.ApplicationName, page.SyntaxElements, maxWidth);
+
+ if (!page.Rows.Any())
+ return;
+
+ sb.AppendLine();
+
+ sb.WriteRows(page.Rows, maxWidth);
+
+ sb.AppendLine();
+ }
+
+ private static void WriteUsage(this StringBuilder sb, string applicationName, IEnumerable syntaxElements, int maxWidth)
+ {
+ var usageHeader = string.Format(Strings.HelpUsageOfApplicationFmt, applicationName);
+ sb.Append(usageHeader);
+
+ if (syntaxElements.Any())
+ sb.Append(@" ");
+
+ var syntaxIndent = usageHeader.Length + 1;
+ var syntaxMaxWidth = maxWidth - syntaxIndent;
+
+ sb.WriteWordWrapped(syntaxElements, syntaxIndent, syntaxMaxWidth);
+ }
+
+ private static void WriteRows(this StringBuilder sb, IReadOnlyList rows, int maxWidth)
+ {
+ const int indent = 4;
+ var maxColumnWidth = rows.Select(r => r.Header.Length).Max();
+ var helpStartColumn = maxColumnWidth + 2 * indent;
+
+ var maxHelpWidth = maxWidth - helpStartColumn;
+ if (maxHelpWidth < 0)
+ maxHelpWidth = maxWidth;
+
+ foreach (var row in rows)
+ {
+ var headerStart = sb.Length;
+
+ sb.Append(' ', indent);
+ sb.Append(row.Header);
+
+ var headerLength = sb.Length - headerStart;
+ var requiredSpaces = helpStartColumn - headerLength;
+
+ sb.Append(' ', requiredSpaces);
+
+ var words = SplitWords(row.Text);
+ sb.WriteWordWrapped(words, helpStartColumn, maxHelpWidth);
+ }
+ }
+
+ private static void WriteWordWrapped(this StringBuilder sb, IEnumerable words, int indent, int maxidth)
+ {
+ var helpLines = WordWrapLines(words, maxidth);
+ var isFirstHelpLine = true;
+
+ foreach (var helpLine in helpLines)
+ {
+ if (isFirstHelpLine)
+ isFirstHelpLine = false;
+ else
+ sb.Append(' ', indent);
+
+ sb.AppendLine(helpLine);
+ }
+
+ if (isFirstHelpLine)
+ sb.AppendLine();
+ }
+
+ private static HelpPage GetCommandListHelp(ArgumentSyntax argumentSyntax)
+ {
+ return new HelpPage
+ {
+ ApplicationName = argumentSyntax.ApplicationName,
+ SyntaxElements = GetGlobalSyntax(),
+ Rows = GetCommandRows(argumentSyntax).ToArray()
+ };
+ }
+
+ private static HelpPage GetCommandHelp(ArgumentSyntax argumentSyntax, ArgumentCommand command)
+ {
+ return new HelpPage
+ {
+ ApplicationName = argumentSyntax.ApplicationName,
+ SyntaxElements = GetCommandSyntax(argumentSyntax, command),
+ Rows = GetArgumentRows(argumentSyntax, command).ToArray()
+ };
+ }
+
+ private static IEnumerable GetGlobalSyntax()
+ {
+ yield return @"";
+ yield return @"[]";
+ }
+
+ private static IEnumerable GetCommandSyntax(ArgumentSyntax argumentSyntax, ArgumentCommand command)
+ {
+ if (command != null)
+ yield return command.Name;
+
+ foreach (var option in argumentSyntax.GetOptions(command).Where(o => !o.IsHidden))
+ yield return GetOptionSyntax(option);
+
+ if (argumentSyntax.GetParameters(command).All(p => p.IsHidden))
+ yield break;
+
+ if (argumentSyntax.GetOptions(command).Any(o => !o.IsHidden))
+ yield return @"[--]";
+
+ foreach (var parameter in argumentSyntax.GetParameters(command).Where(o => !o.IsHidden))
+ yield return GetParameterSyntax(parameter);
+ }
+
+ private static string GetOptionSyntax(Argument option)
+ {
+ var sb = new StringBuilder();
+
+ sb.Append(@"[");
+ sb.Append(option.GetDisplayName());
+
+ if (!option.IsFlag)
+ sb.Append(option.IsRequired ? @" " : @" [arg]");
+
+ if (option.IsList)
+ sb.Append(@"...");
+
+ sb.Append(@"]");
+
+ return sb.ToString();
+ }
+
+ private static string GetParameterSyntax(Argument parameter)
+ {
+ var sb = new StringBuilder();
+
+ sb.Append(parameter.GetDisplayName());
+ if (parameter.IsList)
+ sb.Append(@"...");
+
+ return sb.ToString();
+ }
+
+ private static IEnumerable GetCommandRows(ArgumentSyntax argumentSyntax)
+ {
+ return argumentSyntax.Commands
+ .Where(c => !c.IsHidden)
+ .Select(c => new HelpRow { Header = c.Name, Text = c.Help });
+ }
+
+ private static IEnumerable GetArgumentRows(ArgumentSyntax argumentSyntax, ArgumentCommand command)
+ {
+ return argumentSyntax.GetArguments(command)
+ .Where(a => !a.IsHidden)
+ .Select(a => new HelpRow { Header = GetArgumentRowHeader(a), Text = a.Help });
+ }
+
+ private static string GetArgumentRowHeader(Argument argument)
+ {
+ var sb = new StringBuilder();
+
+ foreach (var displayName in argument.GetDisplayNames())
+ {
+ if (sb.Length > 0)
+ sb.Append(@", ");
+
+ sb.Append(displayName);
+ }
+
+ if (argument.IsOption && !argument.IsFlag)
+ sb.Append(argument.IsRequired ? @" " : @" [arg]");
+
+ if (argument.IsList)
+ sb.Append(@"...");
+
+ return sb.ToString();
+ }
+
+ private static IEnumerable WordWrapLines(IEnumerable tokens, int maxWidth)
+ {
+ var sb = new StringBuilder();
+
+ foreach (var token in tokens)
+ {
+ var newLength = sb.Length == 0
+ ? token.Length
+ : sb.Length + 1 + token.Length;
+
+ if (newLength > maxWidth)
+ {
+ if (sb.Length == 0)
+ {
+ yield return token;
+ continue;
+ }
+
+ yield return sb.ToString();
+ sb.Clear();
+ }
+
+ if (sb.Length > 0)
+ sb.Append(@" ");
+
+ sb.Append(token);
+ }
+
+ if (sb.Length > 0)
+ yield return sb.ToString();
+ }
+
+ private static IEnumerable SplitWords(string text)
+ {
+ return string.IsNullOrEmpty(text)
+ ? Enumerable.Empty()
+ : text.Split(' ');
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.CommandLine/System/Strings.Designer.cs b/src/System.CommandLine/System/Strings.Designer.cs
new file mode 100644
index 0000000..35afbbd
--- /dev/null
+++ b/src/System.CommandLine/System/Strings.Designer.cs
@@ -0,0 +1,253 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace System {
+ using System;
+ using System.Reflection;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Strings {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Strings() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.Strings", typeof(Strings).GetTypeInfo().Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Cannot define commands if global options or parameters exist..
+ ///
+ internal static string CannotDefineCommandsIfArgumentsExist {
+ get {
+ return ResourceManager.GetString("CannotDefineCommandsIfArgumentsExist", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Cannot define multiple parameter lists..
+ ///
+ internal static string CannotDefineMultipleParameterLists {
+ get {
+ return ResourceManager.GetString("CannotDefineMultipleParameterLists", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to value '{0}' isn't valid for {1}: {2}.
+ ///
+ internal static string CannotParseValueFmt {
+ get {
+ return ResourceManager.GetString("CannotParseValueFmt", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Command '{0}' is already defined..
+ ///
+ internal static string CommandAlreadyDefinedFmt {
+ get {
+ return ResourceManager.GetString("CommandAlreadyDefinedFmt", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Name '{0}' cannot be used for a command..
+ ///
+ internal static string CommandNameIsNotValidFmt {
+ get {
+ return ResourceManager.GetString("CommandNameIsNotValidFmt", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to error: {0}.
+ ///
+ internal static string ErrorWithMessageFmt {
+ get {
+ return ResourceManager.GetString("ErrorWithMessageFmt", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to extra parameter '{0}'.
+ ///
+ internal static string ExtraParameterFmt {
+ get {
+ return ResourceManager.GetString("ExtraParameterFmt", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to usage: {0}.
+ ///
+ internal static string HelpUsageOfApplicationFmt {
+ get {
+ return ResourceManager.GetString("HelpUsageOfApplicationFmt", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to invalid option {0}.
+ ///
+ internal static string InvalidOptionFmt {
+ get {
+ return ResourceManager.GetString("InvalidOptionFmt", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to missing command.
+ ///
+ internal static string MissingCommand {
+ get {
+ return ResourceManager.GetString("MissingCommand", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to You must specify a name..
+ ///
+ internal static string NameMissing {
+ get {
+ return ResourceManager.GetString("NameMissing", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Option '{0}' is already defined..
+ ///
+ internal static string OptionAlreadyDefinedFmt {
+ get {
+ return ResourceManager.GetString("OptionAlreadyDefinedFmt", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Name '{0}' cannot be used for an option..
+ ///
+ internal static string OptionNameIsNotValidFmt {
+ get {
+ return ResourceManager.GetString("OptionNameIsNotValidFmt", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to option {0} requires a value.
+ ///
+ internal static string OptionRequiresValueFmt {
+ get {
+ return ResourceManager.GetString("OptionRequiresValueFmt", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Options must be defined before any parameters..
+ ///
+ internal static string OptionsMustBeDefinedBeforeParameters {
+ get {
+ return ResourceManager.GetString("OptionsMustBeDefinedBeforeParameters", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Parameter '{0}' is already defined..
+ ///
+ internal static string ParameterAlreadyDefinedFmt {
+ get {
+ return ResourceManager.GetString("ParameterAlreadyDefinedFmt", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Name '{0}' cannot be used for a parameter..
+ ///
+ internal static string ParameterNameIsNotValidFmt {
+ get {
+ return ResourceManager.GetString("ParameterNameIsNotValidFmt", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Parameters cannot be defined after parameter lists..
+ ///
+ internal static string ParametersCannotBeDefinedAfterLists {
+ get {
+ return ResourceManager.GetString("ParametersCannotBeDefinedAfterLists", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Response file '{0}' doesn't exist..
+ ///
+ internal static string ResponseFileDoesNotExistFmt {
+ get {
+ return ResourceManager.GetString("ResponseFileDoesNotExistFmt", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to unknown command '{0}'.
+ ///
+ internal static string UnknownCommandFmt {
+ get {
+ return ResourceManager.GetString("UnknownCommandFmt", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Unmatched quote at position {0}..
+ ///
+ internal static string UnmatchedQuoteFmt {
+ get {
+ return ResourceManager.GetString("UnmatchedQuoteFmt", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/System.CommandLine/System/Strings.resx b/src/System.CommandLine/System/Strings.resx
new file mode 100644
index 0000000..0949d6f
--- /dev/null
+++ b/src/System.CommandLine/System/Strings.resx
@@ -0,0 +1,183 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Cannot define commands if global options or parameters exist.
+
+
+ value '{0}' isn't valid for {1}: {2}
+
+
+ Command '{0}' is already defined.
+
+
+ error: {0}
+
+
+ extra parameter '{0}'
+
+
+ usage: {0}
+
+
+ invalid option {0}
+
+
+ missing command
+
+
+ You must specify a name.
+
+
+ Option '{0}' is already defined.
+
+
+ Options must be defined before any parameters.
+
+
+ Response file '{0}' doesn't exist.
+
+
+ unknown command '{0}'
+
+
+ Unmatched quote at position {0}.
+
+
+ option {0} requires a value
+
+
+ Parameter '{0}' is already defined.
+
+
+ Cannot define multiple parameter lists.
+
+
+ Parameters cannot be defined after parameter lists.
+
+
+ Name '{0}' cannot be used for a command.
+
+
+ Name '{0}' cannot be used for an option.
+
+
+ Name '{0}' cannot be used for a parameter.
+
+
\ No newline at end of file