This commit is contained in:
Zlatko Knezevic 2016-11-03 05:33:28 -07:00
Родитель 6bd6610476
Коммит a470f5a380
39 изменённых файлов: 3303 добавлений и 0 удалений

4
.gitignore поставляемый
Просмотреть файл

@ -250,3 +250,7 @@ paket-files/
# JetBrains Rider
.idea/
*.sln.iml
*.*~
*.swp
backup/

Просмотреть файл

@ -0,0 +1,34 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.25902.2
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-packer", "dotnet-packer\dotnet-packer.csproj", "{0A4D9107-C593-4AFB-AB17-559324D2ECB4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine", "System.CommandLine\System.CommandLine.csproj", "{BAD7C7D4-FB52-4FF3-9037-5BA935861E51}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleTargets.PackerTarget", "SampleTargets.PackerTarget\SampleTargets.PackerTarget.csproj", "{463C66F0-921D-4D34-8BDE-7C9D0BFFAF7B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0A4D9107-C593-4AFB-AB17-559324D2ECB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0A4D9107-C593-4AFB-AB17-559324D2ECB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A4D9107-C593-4AFB-AB17-559324D2ECB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A4D9107-C593-4AFB-AB17-559324D2ECB4}.Release|Any CPU.Build.0 = Release|Any CPU
{BAD7C7D4-FB52-4FF3-9037-5BA935861E51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BAD7C7D4-FB52-4FF3-9037-5BA935861E51}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BAD7C7D4-FB52-4FF3-9037-5BA935861E51}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BAD7C7D4-FB52-4FF3-9037-5BA935861E51}.Release|Any CPU.Build.0 = Release|Any CPU
{463C66F0-921D-4D34-8BDE-7C9D0BFFAF7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{463C66F0-921D-4D34-8BDE-7C9D0BFFAF7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{463C66F0-921D-4D34-8BDE-7C9D0BFFAF7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{463C66F0-921D-4D34-8BDE-7C9D0BFFAF7B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

13
NuGet.config Normal file
Просмотреть файл

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="nugetbuild" value="https://www.myget.org/F/nugetbuild/api/v3/index.json" />
<add key="dotnet-buildtools" value="https://dotnet.myget.org/F/dotnet-buildtools/api/v3/index.json" />
<add key="dotnet-core" value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json" />
<add key="roslyn-tools" value="https://dotnet.myget.org/F/roslyn-tools/api/v3/index.json" />
<add key="dotnet-cli" value="https://dotnet.myget.org/F/dotnet-cli/api/v3/index.json" />
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

Просмотреть файл

@ -0,0 +1,9 @@
namespace CliCommands.Packer.Task
{
public enum DeploymentType
{
FrameworkDependent,
SelfContained
}
}

Просмотреть файл

@ -0,0 +1,14 @@
namespace CliCommands.Packer.Task
{
public enum PackerFormat
{
// macOS app bundle
None,
AppBundle,
DebianDeb,
UbuntuDeb,
Rpm,
Pkg,
Zip
}
}

Просмотреть файл

@ -0,0 +1,11 @@
using CliCommands.Packer.Task.Models;
namespace CliCommands.Packer.Task
{
public interface IPacker
{
void Pack(PackagingMetadata metadata);
bool Validate();
}
}

Просмотреть файл

@ -0,0 +1,17 @@
using System.Collections.Generic;
namespace CliCommands.Packer.Task.Models
{
public class PackagingMetadata
{
public string PackageFileName { get; set; }
public string ApplicationName { get; set; }
public string ApplicationVersion {get; set; }
public string PathToFiles {get; set; }
public DeploymentType DeploymentType { get; set; }
public Dictionary<string, string> CustomMetadata {get; set;}
}
}

Просмотреть файл

@ -0,0 +1,93 @@
using Microsoft.Build.Framework;
using System;
using CliCommands.Packer.Task.Models;
using System.Reflection;
using System.Linq;
using System.IO;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace CliCommands.Packer.Task
{
public class Packer : Microsoft.Build.Utilities.Task
{
[Required]
public string Format {get; set; }
[Required]
public string PublishedOutput {get; set; }
public string PackageName {get; set; }
public string ApplicationName {get; set; }
public string ApplicationVersion {get; set; }
public string Rid { get; set; }
public string MetadataFile {get; set;}
public ITaskItem AdditionalMetadata { get; set; }
public bool IsPortable => String.IsNullOrEmpty(Rid);
public override bool Execute()
{
var packer = GetPackerFromFormat();
var metadata = GetPackagingMetadata();
if (!packer.Validate())
{
Log.LogError($"The chosen packer is not valid for this operating system.");
return false;
}
Log.LogMessage(MessageImportance.Normal, $"Chosen packer is: {Format}; published output is {PublishedOutput}");
Log.LogMessage(MessageImportance.Normal, $"The package will be published as {metadata.PackageFileName}");
try {
packer.Pack(metadata);
Log.LogMessage(MessageImportance.High, "Packaging complete!");
return true;
} catch (Exception ex)
{
Log.LogError($"An error happened: {ex.Message}");
return false;
}
}
private PackagingMetadata GetPackagingMetadata()
{
var result = new PackagingMetadata();
result.DeploymentType = IsPortable ? DeploymentType.FrameworkDependent : DeploymentType.SelfContained;
result.ApplicationName = ApplicationName;
result.ApplicationVersion = ApplicationVersion;
if (String.IsNullOrEmpty(PackageName))
{
result.PackageFileName = Path.Combine(PublishedOutput, $"{result.ApplicationName}-{result.ApplicationVersion}");
} else
{
result.PackageFileName = PackageName;
}
result.PathToFiles = PublishedOutput;
result.CustomMetadata = (Dictionary<string, string>)AdditionalMetadata.CloneCustomMetadata();
// We inverse here and now go through the file. If the key exists, we simply skip since the properties
// win by order.
if (!String.IsNullOrEmpty(MetadataFile) && File.Exists(MetadataFile))
{
var fileMetadata = JObject.Parse(File.ReadAllText(MetadataFile));
foreach (var line in fileMetadata)
{
var key = line.Key.Substring(0,1).ToUpper() + line.Key.Substring(1, line.Key.Length);
if (!result.CustomMetadata.ContainsKey(key))
result.CustomMetadata.Add(key, line.Value.ToString());
}
}
return result;
}
private IPacker GetPackerFromFormat()
{
var type = this.GetType().
GetTypeInfo().Assembly.GetExportedTypes()
.Where(t => t.GetInterfaces().Contains(typeof(IPacker)) && t.Name != "BasePacker")
.Single(t => t.Name.ToLowerInvariant().Contains(Format.ToLowerInvariant()));
return (IPacker)type.GetConstructor(Type.EmptyTypes).Invoke(Type.EmptyTypes);
}
}
}

Просмотреть файл

@ -0,0 +1,17 @@
using System;
using CliCommands.Packer.Task.Models;
namespace CliCommands.Packer.Task.Packers
{
public abstract class BasePacker : IPacker
{
public abstract void Pack(PackagingMetadata metadata);
public virtual bool Validate()
{
// By default, we always assume the packer is valid;
// it is the job of any packer to override and implement custom logic
return true;
}
}
}

Просмотреть файл

@ -0,0 +1,29 @@
using CliCommands.Packer.Task.Models;
using System.IO.Compression;
using System.IO;
using System;
namespace CliCommands.Packer.Task.Packers
{
public class ZipPacker : BasePacker
{
public override void Pack(PackagingMetadata metadata)
{
var finalName = metadata.PackageFileName + (metadata.PackageFileName.Contains(".zip") ? String.Empty : ".zip");
if (File.Exists(finalName))
File.Delete(finalName);
var tempFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
try {
ZipFile.CreateFromDirectory(metadata.PathToFiles, tempFile);
File.Copy(tempFile, finalName);
} catch
{
throw;
} finally
{
File.Delete(tempFile);
}
}
}
}

Просмотреть файл

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<installer-gui-script minSpecVersion="1">
<title>{ApplicationName}</title>
<!--<license file="eula.rtf" mime-type="application/rtf" />
<background file="dotnetbackground.png" mime-type="image/png"/>-->
<options customize="never" require-scripts="false" />
<volume-check>
<allowed-os-version>
<os-version min="10.10" />
</allowed-os-version>
</volume-check>
<choices-outline>
<line choice="{ComponentId}.pkg" />
</choices-outline>
<pkg-ref id="{ComponentId}.pkg">{ComponentId}.pkg</pkg-ref>
<!--<choices-outline>
<line choice="{SharedFxComponentId}.pkg" />
<line choice="{SharedHostComponentId}.pkg" />
<line choice="{HostFxrComponentId}.pkg" />
</choices-outline>-->
<!--<choice id="{SharedFxComponentId}.pkg" visible="true" title="{SharedFxBrandName} (x64)" description="The .NET Core Shared Framework">
<pkg-ref id="{SharedFxComponentId}.pkg" />
</choice>
<choice id="{HostFxrComponentId}.pkg" visible="true" title="{HostFxrBrandName} (x64)" description="The .NET Core HostFxr">
<pkg-ref id="{HostFxrComponentId}.pkg" />
</choice>
<choice id="{SharedHostComponentId}.pkg" visible="true" title="{SharedHostBrandName} (x64)" description="The .NET Core Shared Host." >
<pkg-ref id="{SharedHostComponentId}.pkg" />
</choice>
<pkg-ref id="{SharedFxComponentId}.pkg">{SharedFxComponentId}.pkg</pkg-ref>
<pkg-ref id="{HostFxrComponentId}.pkg">{HostFxrComponentId}.pkg</pkg-ref>
<pkg-ref id="{SharedHostComponentId}.pkg">{SharedHostComponentId}.pkg</pkg-ref>-->
</installer-gui-script>

Просмотреть файл

@ -0,0 +1,52 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<PropertyGroup>
<Description>Sample Packer</Description>
<VersionPrefix>0.1.0-preview</VersionPrefix>
<TargetFramework>netstandard1.3</TargetFramework>
<DebugType>portable</DebugType>
<AssemblyName>SampleTargets.PackerTarget</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Compile Include="**\*.cs" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
<EmbeddedResource Include="**\*.resx" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
<EmbeddedResource Include="Resources\Pkg\dist-template.xml;compiler\resources\**\*" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
</ItemGroup>
<ItemGroup>
<None Include="build\SampleTargets.PackerTarget.targets" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Sdk">
<Version>1.0.0-alpha-20161029-1</Version>
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyModel">
<Version>1.0.1-beta-000933</Version>
</PackageReference>
<PackageReference Include="Microsoft.Build.Framework">
<Version>0.1.0-preview-00028-160627</Version>
</PackageReference>
<PackageReference Include="Microsoft.Build.Utilities.Core">
<Version>0.1.0-preview-00028-160627</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>9.0.1</Version>
</PackageReference>
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<PackageReference Include="NETStandard.Library">
<Version>1.6.0</Version>
</PackageReference>
</ItemGroup>
<ItemGroup />
<PropertyGroup Label="Globals">
<ProjectGuid>463c66f0-921d-4d34-8bde-7c9d0bffaf7b</ProjectGuid>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<DefineConstants>$(DefineConstants);NETSTANDARD1_3</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants>
</PropertyGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

Просмотреть файл

@ -0,0 +1,14 @@
<?xml version="1.0"?>
<package >
<metadata>
<id>SampleTargets.PackerTarget</id>
<version>0.1.0-preview</version>
<authors>Zlatko Knezevic</authors>
<description>A sample extension to the MSBuild-based CLI toolset</description>
</metadata>
<files>
<file src="build\**" target="build" />
<file src="bin\Debug\netstandard1.3\*.dll" target="build" />
<file src="bin\Debug\netstandard1.3\*.json" target="build" />
</files>
</package>

Просмотреть файл

@ -0,0 +1,52 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="Packer" AssemblyFile="$(MSBuildThisFileDirectory)SampleTargets.PackerTarget.dll" />
<PropertyGroup>
<Format>Zip</Format>
<MetadataFile>metadata.json</MetadataFile>
</PropertyGroup>
<ItemGroup>
<PackerMetadata Include="PackerMetadata">
<!-- PKG packing custom metadata -->
<PkgLicenseFile Condition="'$(PkgLicenseFile)' == ''">license.rtf</PkgLicenseFile>
<PkgBackgroundImage Condition="'$(PkgBackgroundImage)' == ''">someimage.png</PkgBackgroundImage>
<PkgDistributionFile Condition="'$(PkgDistributionFile)' == ''">some-dist.xml</PkgDistributionFile>
<PkgBuildProductArchive Condition="'$(PkgBuildProductArchive)' == ''">true</PkgBuildProductArchive>
<PkgComponentId Condition="'$(PkgComponentId)' == ''">com.blackdwarf.testingapp</PkgComponentId>
<PkgInstallLocation Condition="'$(PkgComponentId)' == ''">/usr/local/blah</PkgInstallLocation>
<!-- Include the shared runtime if the app is portable and the packer permits -->
<TryIncludeSharedRuntime Condition="'$(TryIncludeSharedRuntime)' == ''">true</TryIncludeSharedRuntime>
<!-- Include all of the application bundle needed metadata proeprties here -->
<AppBundleIconFile Condition="'$(AppBundleIconFile)' == ''">someicon.png</AppBundleIconFile>
<AppBundlePlistFile Condition="'$(AppBundlePlistFile)' == ''">somefile.plist</AppBundlePlistFile>
</PackerMetadata>
</ItemGroup>
<Choose>
<When Condition="'$(AssemblyName)' == ''">
<PropertyGroup>
<ApplicationName>$(MsBuildProjectName)</ApplicationName>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<ApplicationName>$(AssemblyName)</ApplicationName>
</PropertyGroup>
</Otherwise>
</Choose>
<Target Name="Packer" DependsOnTargets="Publish">
<Packer
Format="$(Format)"
PublishedOutput="$(PublishDir)"
PackageName="$(PackageName)"
ApplicationName="$(ApplicationName)"
ApplicationVersion="$(Version)"
Rid="$(RuntimeIdentifier)"
AdditionalMetadata="@(PackerMetadata)"
MetadataFile="$(MetadataFile)"
/>
</Target>
</Project>

Просмотреть файл

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("System.CommandLine.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100039ac461fa5c82c7dd2557400c4fd4e9dcdf7ac47e3d572548c04cd4673e004916610f4ea5cbf86f2b1ca1cb824f2a7b3976afecfcf4eb72d9a899aa6786effa10c30399e6580ed848231fec48374e41b3acf8811931343fc2f73acf72dae745adbcb7063cc4b50550618383202875223fc75401351cd89c44bf9b50e7fa3796")]

Просмотреть файл

@ -0,0 +1,488 @@
# 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 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 <arg>]
-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<string, T>` 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
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<string> defines = Array.Empty<string>();
IReadOnlyList<string> sources = Array.Empty<string>();
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;
// ...
});
```

Просмотреть файл

@ -0,0 +1,50 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<PropertyGroup>
<Description>CLI commandline command parsing</Description>
<Copyright>Microsoft Corporation, All rights reserved</Copyright>
<VersionPrefix>0.1.0</VersionPrefix>
<TargetFramework>netstandard1.6</TargetFramework>
<AssemblyName>System.CommandLine</AssemblyName>
<AssemblyOriginatorKeyFile>../tools/Key.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageTags>Command line parsing support corefxlab</PackageTags>
<PackageReleaseNotes>Pre-release package, for testing only</PackageReleaseNotes>
<PackageIconUrl>http://go.microsoft.com/fwlink/?LinkID=288859</PackageIconUrl>
<PackageProjectUrl>https://github.com/dotnet/corefxlab</PackageProjectUrl>
<PackageLicenseUrl>http://go.microsoft.com/fwlink/?LinkId=329770</PackageLicenseUrl>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
</PropertyGroup>
<ItemGroup>
<Compile Include="**\*.cs" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
<EmbeddedResource Include="**\*.resx" Exclude="bin\**;obj\**;**\*.xproj;packages\**">
<Link>System.Strings</Link>
</EmbeddedResource>
<EmbeddedResource Include="compiler\resources\**\*" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Sdk">
<Version>1.0.0-alpha-20161102-2</Version>
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="NETStandard.Library">
<Version>1.6</Version>
</PackageReference>
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.6' ">
<PackageReference Include="System.Runtime.Extensions">
<Version>4.1.0</Version>
</PackageReference>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>bad7c7d4-fb52-4ff3-9037-5ba935861e51</ProjectGuid>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.6' ">
<DefineConstants>$(DefineConstants);NETSTANDARD1_6</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants>
</PropertyGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

Просмотреть файл

@ -0,0 +1,107 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace System.CommandLine
{
public abstract class Argument
{
internal Argument(ArgumentCommand command, IEnumerable<string> names, bool isOption)
{
var nameArray = names.ToArray();
Command = command;
Name = nameArray.First();
Names = new ReadOnlyCollection<string>(nameArray);
IsOption = isOption;
}
public ArgumentCommand Command { get; private set; }
public string Name { get; private set; }
public ReadOnlyCollection<string> 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 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<string> 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();
}
}
}

Просмотреть файл

@ -0,0 +1,40 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
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;
}
}
}

Просмотреть файл

@ -0,0 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
namespace System.CommandLine
{
public sealed class ArgumentCommand<T> : ArgumentCommand
{
internal ArgumentCommand(string name, T value)
: base(name)
{
Value = value;
}
public new T Value { get; private set; }
internal override object GetValue()
{
return Value;
}
}
}

Просмотреть файл

@ -0,0 +1,201 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
namespace System.CommandLine
{
internal static class ArgumentLexer
{
public static IReadOnlyList<ArgumentToken> Lex(IEnumerable<string> args, Func<string, IEnumerable<string>> responseFileReader = null)
{
var result = new List<ArgumentToken>();
// 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 ExpandReponseFiles(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
string nameAndValue;
if (!TryExtractOption(arg, out modifier, out 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<string> ExpandReponseFiles(IEnumerable<string> args, Func<string, IEnumerable<string>> 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<ArgumentToken> 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)
{
return TrySplitNameValue(text, ':', out name, out value) ||
TrySplitNameValue(text, '=', out name, out value);
}
private static bool TrySplitNameValue(string text, char separator, out string name, out string value)
{
var i = text.IndexOf(separator);
if (i >= 0)
{
name = text.Substring(0, i);
value = text.Substring(i + 1);
return true;
}
name = null;
value = null;
return false;
}
}
}

Просмотреть файл

@ -0,0 +1,60 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
namespace System.CommandLine
{
public sealed class ArgumentList<T> : Argument
{
internal ArgumentList(ArgumentCommand command, IEnumerable<string> names, IReadOnlyList<T> defaultValue)
: base(command, names, true)
{
Value = defaultValue;
DefaultValue = defaultValue;
}
internal ArgumentList(ArgumentCommand command, string name, IReadOnlyList<T> defaultValue)
: base(command, new[] { name }, false)
{
Value = defaultValue;
DefaultValue = defaultValue;
}
public override bool IsList
{
get { return true; }
}
public new IReadOnlyList<T> Value { get; private set; }
public new IReadOnlyList<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(IReadOnlyList<T> value)
{
Value = value;
MarkSpecified();
}
public override string GetDisplayValue()
{
return string.Join(@", ", Value);
}
}
}

Просмотреть файл

@ -0,0 +1,267 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
namespace System.CommandLine
{
internal sealed class ArgumentParser
{
private readonly IReadOnlyList<ArgumentToken> _tokens;
public ArgumentParser(IEnumerable<string> arguments)
: this(arguments, null)
{
}
public ArgumentParser(IEnumerable<string> arguments, Func<string, IEnumerable<string>> 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<T>(string diagnosticName, IReadOnlyCollection<string> names, Func<string, T> valueConverter, out T value)
{
IReadOnlyList<T> values;
if (!TryParseOptionList(diagnosticName, names, valueConverter, out values))
{
value = default(T);
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<T>(string diagnosticName, IReadOnlyCollection<string> names, Func<string, T> valueConverter, out IReadOnlyList<T> values)
{
var result = new List<T>();
var tokenIndex = 0;
var isFlag = typeof(T) == typeof(bool);
while (tokenIndex < _tokens.Count)
{
if (TryParseOption(ref tokenIndex, names))
{
string valueText;
if (TryParseOptionArgument(ref tokenIndex, isFlag, out valueText))
{
var value = ParseValue(diagnosticName, valueConverter, valueText);
result.Add(value);
}
else if (isFlag)
{
var value = (T)(object)true;
result.Add(value);
}
else
{
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<T>(string diagnosticName, Func<string, T> 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(T);
return false;
}
public bool TryParseParameterList<T>(string diagnosticName, Func<string, T> valueConverter, out IReadOnlyList<T> values)
{
var result = new List<T>();
T value;
while (TryParseParameter(diagnosticName, valueConverter, out value))
{
result.Add(value);
}
if (!result.Any())
{
values = null;
return false;
}
values = result.ToArray();
return true;
}
private bool TryParseOption(ref int tokenIndex, IReadOnlyCollection<string> 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.
ArgumentToken lookahead;
// So, do we have a token?
if (!TryGetNextToken(tokenIndex, out 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<T>(string diagnosticName, Func<string, T> 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<string> GetUnreadOptionNames()
{
return _tokens.Where(t => !t.IsMatched && t.IsOption).Select(t => t.Modifier + t.Name).ToArray();
}
public IReadOnlyList<string> GetUnreadParameters()
{
return _tokens.Where(t => !t.IsMatched && !t.IsOption).Select(t => t.ToString()).ToArray();
}
public IReadOnlyList<string> GetUnreadArguments()
{
return _tokens.Where(t => !t.IsMatched).Select(t => t.ToString()).ToArray();
}
}
}

Просмотреть файл

@ -0,0 +1,424 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.IO;
namespace System.CommandLine
{
public sealed partial class ArgumentSyntax
{
private readonly IEnumerable<string> _arguments;
private readonly List<ArgumentCommand> _commands = new List<ArgumentCommand>();
private readonly List<Argument> _options = new List<Argument>();
private readonly List<Argument> _parameters = new List<Argument>();
private ArgumentParser _parser;
private ArgumentCommand _definedCommand;
private ArgumentCommand _activeCommand;
internal ArgumentSyntax(IEnumerable<string> arguments)
{
_arguments = arguments;
ApplicationName = GetApplicationName();
HandleErrors = true;
HandleHelp = true;
HandleResponseFiles = true;
}
public static ArgumentSyntax Parse(IEnumerable<string> arguments, Action<ArgumentSyntax> 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);
}
// 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<T> DefineCommand<T>(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<T>(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<T> DefineOption<T>(string name, T defaultValue, Func<string, T> valueConverter)
{
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<T>(_definedCommand, names, defaultValue);
_options.Add(option);
if (_activeCommand != _definedCommand)
return option;
try
{
T value;
if (Parser.TryParseOption(option.GetDisplayName(), option.Names, valueConverter, out value))
option.SetValue(value);
}
catch (ArgumentSyntaxException ex)
{
ReportError(ex.Message);
}
return option;
}
public ArgumentList<T> DefineOptionList<T>(string name, IReadOnlyList<T> defaultValue, Func<string, T> valueConverter)
{
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<T>(_definedCommand, names, defaultValue);
_options.Add(optionList);
if (_activeCommand != _definedCommand)
return optionList;
try
{
IReadOnlyList<T> value;
if (Parser.TryParseOptionList(optionList.GetDisplayName(), optionList.Names, valueConverter, out value))
optionList.SetValue(value);
}
catch (ArgumentSyntaxException ex)
{
ReportError(ex.Message);
}
return optionList;
}
public Argument<T> DefineParameter<T>(string name, T defaultValue, Func<string, T> 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<T>(_definedCommand, name, defaultValue);
_parameters.Add(parameter);
if (_activeCommand != _definedCommand)
return parameter;
try
{
T value;
if (Parser.TryParseParameter(parameter.GetDisplayName(), valueConverter, out value))
parameter.SetValue(value);
}
catch (ArgumentSyntaxException ex)
{
ReportError(ex.Message);
}
return parameter;
}
public ArgumentList<T> DefineParameterList<T>(string name, IReadOnlyList<T> defaultValue, Func<string, T> 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<T>(_definedCommand, name, defaultValue);
_parameters.Add(parameterList);
if (_activeCommand != _definedCommand)
return parameterList;
try
{
IReadOnlyList<T> values;
if (Parser.TryParseParameterList(parameterList.GetDisplayName(), valueConverter, out 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<string> 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<string> 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; }
private ArgumentParser Parser
{
get
{
if (_parser == null)
_parser = new ArgumentParser(_arguments, ParseResponseFile);
return _parser;
}
}
private IEnumerable<Argument> DefinedOptions
{
get { return _options.Where(o => o.Command == null || o.Command == _definedCommand); }
}
private IEnumerable<Argument> DefinedParameters
{
get { return _parameters.Where(p => p.Command == null || p.Command == _definedCommand); }
}
public ArgumentCommand ActiveCommand
{
get { return _activeCommand; }
}
public IReadOnlyList<ArgumentCommand> Commands
{
get { return _commands; }
}
public IEnumerable<Argument> GetArguments()
{
return _options.Concat(_parameters);
}
public IEnumerable<Argument> GetArguments(ArgumentCommand command)
{
return GetArguments().Where(c => c.Command == null || c.Command == command);
}
public IEnumerable<Argument> GetOptions()
{
return _options;
}
public IEnumerable<Argument> GetOptions(ArgumentCommand command)
{
return _options.Where(c => c.Command == null || c.Command == command);
}
public IEnumerable<Argument> GetParameters()
{
return _parameters;
}
public IEnumerable<Argument> GetParameters(ArgumentCommand command)
{
return _parameters.Where(c => c.Command == null || c.Command == command);
}
public IEnumerable<Argument> GetActiveArguments()
{
return GetArguments(ActiveCommand);
}
public IEnumerable<Argument> GetActiveOptions()
{
return GetOptions(ActiveCommand);
}
public IEnumerable<Argument> 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);
}
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
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)
{
}
}
}

Просмотреть файл

@ -0,0 +1,183 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Globalization;
namespace System.CommandLine
{
public partial class ArgumentSyntax
{
private static readonly Func<string, string> s_stringParser = v => v;
private static readonly Func<string, bool> s_booleanParser = v => bool.Parse(v);
private static readonly Func<string, int> s_int32Parser = v => int.Parse(v, CultureInfo.InvariantCulture);
// Commands
public ArgumentCommand<string> DefineCommand(string name)
{
return DefineCommand(name, name);
}
public ArgumentCommand<T> DefineCommand<T>(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<string> DefineCommand(string name, ref string value, string help)
{
return DefineCommand(name, ref value, name, help);
}
// Options
public Argument<string> DefineOption(string name, string defaultValue)
{
return DefineOption(name, defaultValue, s_stringParser);
}
public Argument<bool> DefineOption(string name, bool defaultValue)
{
return DefineOption(name, defaultValue, s_booleanParser);
}
public Argument<int> DefineOption(string name, int defaultValue)
{
return DefineOption(name, defaultValue, s_int32Parser);
}
public Argument<T> DefineOption<T>(string name, ref T value, Func<string, T> valueConverter, string help)
{
var option = DefineOption(name, value, valueConverter);
option.Help = help;
value = option.Value;
return option;
}
public Argument<string> DefineOption(string name, ref string value, string help)
{
return DefineOption(name, ref value, s_stringParser, help);
}
public Argument<bool> DefineOption(string name, ref bool value, string help)
{
return DefineOption(name, ref value, s_booleanParser, help);
}
public Argument<int> DefineOption(string name, ref int value, string help)
{
return DefineOption(name, ref value, s_int32Parser, help);
}
// Option lists
public ArgumentList<string> DefineOptionList(string name, IReadOnlyList<string> defaultValue)
{
return DefineOptionList(name, defaultValue, s_stringParser);
}
public ArgumentList<int> DefineOptionList(string name, IReadOnlyList<int> defaultValue)
{
return DefineOptionList(name, defaultValue, s_int32Parser);
}
public ArgumentList<T> DefineOptionList<T>(string name, ref IReadOnlyList<T> value, Func<string, T> valueConverter, string help)
{
var optionList = DefineOptionList(name, value, valueConverter);
optionList.Help = help;
value = optionList.Value;
return optionList;
}
public ArgumentList<string> DefineOptionList(string name, ref IReadOnlyList<string> value, string help)
{
return DefineOptionList(name, ref value, s_stringParser, help);
}
public ArgumentList<int> DefineOptionList(string name, ref IReadOnlyList<int> value, string help)
{
return DefineOptionList(name, ref value, s_int32Parser, help);
}
// Parameters
public Argument<string> DefineParameter(string name, string defaultValue)
{
return DefineParameter(name, defaultValue, s_stringParser);
}
public Argument<bool> DefineParameter(string name, bool defaultValue)
{
return DefineParameter(name, defaultValue, s_booleanParser);
}
public Argument<int> DefineParameter(string name, int defaultValue)
{
return DefineParameter(name, defaultValue, s_int32Parser);
}
public Argument<T> DefineParameter<T>(string name, ref T value, Func<string, T> valueConverter, string help)
{
var parameter = DefineParameter(name, value, valueConverter);
parameter.Help = help;
value = parameter.Value;
return parameter;
}
public Argument<string> DefineParameter(string name, ref string value, string help)
{
return DefineParameter(name, ref value, s_stringParser, help);
}
public Argument<bool> DefineParameter(string name, ref bool value, string help)
{
return DefineParameter(name, ref value, s_booleanParser, help);
}
public Argument<int> DefineParameter(string name, ref int value, string help)
{
return DefineParameter(name, ref value, s_int32Parser, help);
}
// Parameter list
public ArgumentList<string> DefineParameterList(string name, IReadOnlyList<string> defaultValue)
{
return DefineParameterList(name, defaultValue, s_stringParser);
}
public ArgumentList<int> DefineParameterList(string name, IReadOnlyList<int> defaultValue)
{
return DefineParameterList(name, defaultValue, s_int32Parser);
}
public ArgumentList<T> DefineParameterList<T>(string name, ref IReadOnlyList<T> value, Func<string, T> valueConverter, string help)
{
var parameterList = DefineParameterList(name, value, valueConverter);
parameterList.Help = help;
value = parameterList.Value;
return parameterList;
}
public ArgumentList<string> DefineParameterList(string name, ref IReadOnlyList<string> value, string help)
{
return DefineParameterList(name, ref value, s_stringParser, help);
}
public ArgumentList<int> DefineParameterList(string name, ref IReadOnlyList<int> value, string help)
{
return DefineParameterList(name, ref value, s_int32Parser, help);
}
}
}

Просмотреть файл

@ -0,0 +1,82 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
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);
}
}
}

Просмотреть файл

@ -0,0 +1,50 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
namespace System.CommandLine
{
public sealed class Argument<T> : Argument
{
internal Argument(ArgumentCommand command, IEnumerable<string> names, T defaultValue)
: base(command, names, true)
{
Value = defaultValue;
Value = DefaultValue = defaultValue;
}
internal Argument(ArgumentCommand command, string name, T defaultValue)
: base(command, new[] { name }, false)
{
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();
}
}
}

Просмотреть файл

@ -0,0 +1,86 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license 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<T> Empty<T>()
{
return Linq.Enumerable.Empty<T>();
}
public static IEnumerable<U> Select<T, U>(this IEnumerable<T> values, Func<T, U> func)
{
Debug.Assert(values != null);
foreach (T value in values)
{
yield return func(value);
}
}
public static IEnumerable<T> Where<T>(this IEnumerable<T> values, Func<T, bool> func)
{
Debug.Assert(values != null);
foreach (T value in values)
{
if (func(value))
yield return value;
}
}
public static IEnumerable<T> Concat<T>(this IEnumerable<T> first, IEnumerable<T> second)
{
return Linq.Enumerable.Concat(first, second);
}
public static bool All<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
return Linq.Enumerable.All(source, predicate);
}
public static bool Any<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
return Linq.Enumerable.Any(source, predicate);
}
public static bool Any<T>(this IEnumerable<T> source)
{
return Linq.Enumerable.Any(source);
}
public static T[] ToArray<T>(this IEnumerable<T> source)
{
return Linq.Enumerable.ToArray(source);
}
public static T Last<T>(this IEnumerable<T> source)
{
return Linq.Enumerable.Last(source);
}
public static T FirstOrDefault<T>(this IEnumerable<T> source)
{
return Linq.Enumerable.FirstOrDefault(source);
}
public static T First<T>(this IEnumerable<T> source)
{
return Linq.Enumerable.First(source);
}
public static int Max(this IEnumerable<int> source)
{
return Linq.Enumerable.Max(source);
}
}
}

Просмотреть файл

@ -0,0 +1,260 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
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<string> SyntaxElements;
public IReadOnlyList<HelpRow> 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<string> 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<HelpRow> 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<string> 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<string> GetGlobalSyntax()
{
yield return @"<command>";
yield return @"[<args>]";
}
private static IEnumerable<string> 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(@" <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<HelpRow> GetCommandRows(ArgumentSyntax argumentSyntax)
{
return argumentSyntax.Commands
.Where(c => !c.IsHidden)
.Select(c => new HelpRow { Header = c.Name, Text = c.Help });
}
private static IEnumerable<HelpRow> 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(@" <arg>");
if (argument.IsList)
sb.Append(@"...");
return sb.ToString();
}
private static IEnumerable<string> WordWrapLines(IEnumerable<string> 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<string> SplitWords(string text)
{
return string.IsNullOrEmpty(text)
? Enumerable.Empty<string>()
: text.Split(' ');
}
}
}

Просмотреть файл

@ -0,0 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("System.CommandLine.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]

253
System.CommandLine/System/Strings.Designer.cs сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,253 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace System {
using System;
using System.Reflection;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 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() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[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;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Cannot define commands if global options or parameters exist..
/// </summary>
internal static string CannotDefineCommandsIfArgumentsExist {
get {
return ResourceManager.GetString("CannotDefineCommandsIfArgumentsExist", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cannot define multiple parameter lists..
/// </summary>
internal static string CannotDefineMultipleParameterLists {
get {
return ResourceManager.GetString("CannotDefineMultipleParameterLists", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to value &apos;{0}&apos; isn&apos;t valid for {1}: {2}.
/// </summary>
internal static string CannotParseValueFmt {
get {
return ResourceManager.GetString("CannotParseValueFmt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Command &apos;{0}&apos; is already defined..
/// </summary>
internal static string CommandAlreadyDefinedFmt {
get {
return ResourceManager.GetString("CommandAlreadyDefinedFmt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Name &apos;{0}&apos; cannot be used for a command..
/// </summary>
internal static string CommandNameIsNotValidFmt {
get {
return ResourceManager.GetString("CommandNameIsNotValidFmt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to error: {0}.
/// </summary>
internal static string ErrorWithMessageFmt {
get {
return ResourceManager.GetString("ErrorWithMessageFmt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to extra parameter &apos;{0}&apos;.
/// </summary>
internal static string ExtraParameterFmt {
get {
return ResourceManager.GetString("ExtraParameterFmt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to usage: {0}.
/// </summary>
internal static string HelpUsageOfApplicationFmt {
get {
return ResourceManager.GetString("HelpUsageOfApplicationFmt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to invalid option {0}.
/// </summary>
internal static string InvalidOptionFmt {
get {
return ResourceManager.GetString("InvalidOptionFmt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to missing command.
/// </summary>
internal static string MissingCommand {
get {
return ResourceManager.GetString("MissingCommand", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You must specify a name..
/// </summary>
internal static string NameMissing {
get {
return ResourceManager.GetString("NameMissing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Option &apos;{0}&apos; is already defined..
/// </summary>
internal static string OptionAlreadyDefinedFmt {
get {
return ResourceManager.GetString("OptionAlreadyDefinedFmt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Name &apos;{0}&apos; cannot be used for an option..
/// </summary>
internal static string OptionNameIsNotValidFmt {
get {
return ResourceManager.GetString("OptionNameIsNotValidFmt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to option {0} requires a value.
/// </summary>
internal static string OptionRequiresValueFmt {
get {
return ResourceManager.GetString("OptionRequiresValueFmt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Options must be defined before any parameters..
/// </summary>
internal static string OptionsMustBeDefinedBeforeParameters {
get {
return ResourceManager.GetString("OptionsMustBeDefinedBeforeParameters", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Parameter &apos;{0}&apos; is already defined..
/// </summary>
internal static string ParameterAlreadyDefinedFmt {
get {
return ResourceManager.GetString("ParameterAlreadyDefinedFmt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Name &apos;{0}&apos; cannot be used for a parameter..
/// </summary>
internal static string ParameterNameIsNotValidFmt {
get {
return ResourceManager.GetString("ParameterNameIsNotValidFmt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Parameters cannot be defined after parameter lists..
/// </summary>
internal static string ParametersCannotBeDefinedAfterLists {
get {
return ResourceManager.GetString("ParametersCannotBeDefinedAfterLists", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Response file &apos;{0}&apos; doesn&apos;t exist..
/// </summary>
internal static string ResponseFileDoesNotExistFmt {
get {
return ResourceManager.GetString("ResponseFileDoesNotExistFmt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to unknown command &apos;{0}&apos;.
/// </summary>
internal static string UnknownCommandFmt {
get {
return ResourceManager.GetString("UnknownCommandFmt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unmatched quote at position {0}..
/// </summary>
internal static string UnmatchedQuoteFmt {
get {
return ResourceManager.GetString("UnmatchedQuoteFmt", resourceCulture);
}
}
}
}

Просмотреть файл

@ -0,0 +1,183 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="CannotDefineCommandsIfArgumentsExist" xml:space="preserve">
<value>Cannot define commands if global options or parameters exist.</value>
</data>
<data name="CannotParseValueFmt" xml:space="preserve">
<value>value '{0}' isn't valid for {1}: {2}</value>
</data>
<data name="CommandAlreadyDefinedFmt" xml:space="preserve">
<value>Command '{0}' is already defined.</value>
</data>
<data name="ErrorWithMessageFmt" xml:space="preserve">
<value>error: {0}</value>
</data>
<data name="ExtraParameterFmt" xml:space="preserve">
<value>extra parameter '{0}'</value>
</data>
<data name="HelpUsageOfApplicationFmt" xml:space="preserve">
<value>usage: {0}</value>
</data>
<data name="InvalidOptionFmt" xml:space="preserve">
<value>invalid option {0}</value>
</data>
<data name="MissingCommand" xml:space="preserve">
<value>missing command</value>
</data>
<data name="NameMissing" xml:space="preserve">
<value>You must specify a name.</value>
</data>
<data name="OptionAlreadyDefinedFmt" xml:space="preserve">
<value>Option '{0}' is already defined.</value>
</data>
<data name="OptionsMustBeDefinedBeforeParameters" xml:space="preserve">
<value>Options must be defined before any parameters.</value>
</data>
<data name="ResponseFileDoesNotExistFmt" xml:space="preserve">
<value>Response file '{0}' doesn't exist.</value>
</data>
<data name="UnknownCommandFmt" xml:space="preserve">
<value>unknown command '{0}'</value>
</data>
<data name="UnmatchedQuoteFmt" xml:space="preserve">
<value>Unmatched quote at position {0}.</value>
</data>
<data name="OptionRequiresValueFmt" xml:space="preserve">
<value>option {0} requires a value</value>
</data>
<data name="ParameterAlreadyDefinedFmt" xml:space="preserve">
<value>Parameter '{0}' is already defined.</value>
</data>
<data name="CannotDefineMultipleParameterLists" xml:space="preserve">
<value>Cannot define multiple parameter lists.</value>
</data>
<data name="ParametersCannotBeDefinedAfterLists" xml:space="preserve">
<value>Parameters cannot be defined after parameter lists.</value>
</data>
<data name="CommandNameIsNotValidFmt" xml:space="preserve">
<value>Name '{0}' cannot be used for a command.</value>
</data>
<data name="OptionNameIsNotValidFmt" xml:space="preserve">
<value>Name '{0}' cannot be used for an option.</value>
</data>
<data name="ParameterNameIsNotValidFmt" xml:space="preserve">
<value>Name '{0}' cannot be used for a parameter.</value>
</data>
</root>

38
dotnet-packer/Program.cs Executable file
Просмотреть файл

@ -0,0 +1,38 @@
using System;
//using System.CommandLine;
using System.Diagnostics;
class Program
{
static void Main(string[] args)
{
var format = "Zip";
//var pkgName = "";
//ArgumentSyntax.Parse(args, syntax =>
//{
//syntax.DefineOption("f|format", ref format, "The format that you want to pack in. Currently valid: zip");
//syntax.DefineOption("o|output", ref pkgName, "The resulting package name");
//});
var shellOutCommand = "dotnet";
//var arguments = $"msbuild /t:Packer /p:PackageName={pkgName} /p:Format={format} /v:Quiet";
var arguments = $"msbuild /t:Packer /p:Format={format}";
var psi = new ProcessStartInfo
{
FileName = shellOutCommand,
Arguments = arguments
};
var process = new Process
{
StartInfo = psi,
};
var rcode = process.Start();
}
}

Просмотреть файл

@ -0,0 +1,31 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<PropertyGroup Label="Globals">
<ProjectGuid>0a4d9107-c593-4afb-ab17-559324d2ecb4</ProjectGuid>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.0</TargetFramework>
<VersionPrefix>0.1.0-preview</VersionPrefix>
</PropertyGroup>
<ItemGroup>
<Compile Include="**\*.cs" />
<EmbeddedResource Include="**\*.resx" />
</ItemGroup>
<ItemGroup>
<Content Include="$(OutputPath)\dotnet-packer.runtimeconfig.json">
<Pack>true</Pack>
<PackagePath>lib\$(TargetFramework)</PackagePath>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.App">
<Version>1.0.1</Version>
</PackageReference>
<PackageReference Include="Microsoft.NET.Sdk">
<Version>1.0.0-alpha-20161102-2</Version>
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

Просмотреть файл

@ -0,0 +1,35 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="**\*.cs" />
<EmbeddedResource Include="**\*.resx" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.App">
<Version>1.0.1</Version>
</PackageReference>
<PackageReference Include="Microsoft.NET.Sdk">
<Version>1.0.0-alpha-20161029-1</Version>
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="SampleTargets.PackerTarget">
<Version>0.1.0-preview</Version>
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="dotnet-packer">
<Version>0.1.0-*</Version>
</DotNetCliToolReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

Просмотреть файл

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<add key="localFeed" value="../../SampleTargets.PackerTarget/" />
<add key="localFeed2" value="../../dotnet-packer" />
</packageSources>
</configuration>

Просмотреть файл

@ -0,0 +1,9 @@
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}

Двоичные данные
tools/Key.snk Executable file

Двоичный файл не отображается.