Initial import
This commit is contained in:
Родитель
6bd6610476
Коммит
a470f5a380
|
@ -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
|
|
@ -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")]
|
|
@ -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 '{0}' isn'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 '{0}' is already defined..
|
||||
/// </summary>
|
||||
internal static string CommandAlreadyDefinedFmt {
|
||||
get {
|
||||
return ResourceManager.GetString("CommandAlreadyDefinedFmt", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Name '{0}' 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 '{0}'.
|
||||
/// </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 '{0}' is already defined..
|
||||
/// </summary>
|
||||
internal static string OptionAlreadyDefinedFmt {
|
||||
get {
|
||||
return ResourceManager.GetString("OptionAlreadyDefinedFmt", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Name '{0}' 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 '{0}' is already defined..
|
||||
/// </summary>
|
||||
internal static string ParameterAlreadyDefinedFmt {
|
||||
get {
|
||||
return ResourceManager.GetString("ParameterAlreadyDefinedFmt", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Name '{0}' 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 '{0}' doesn't exist..
|
||||
/// </summary>
|
||||
internal static string ResponseFileDoesNotExistFmt {
|
||||
get {
|
||||
return ResourceManager.GetString("ResponseFileDoesNotExistFmt", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to unknown command '{0}'.
|
||||
/// </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>
|
|
@ -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!");
|
||||
}
|
||||
}
|
Двоичный файл не отображается.
Загрузка…
Ссылка в новой задаче