Skip to content

Add default verb support #556

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#version should be only changed with RELEASE eminent, see RELEASE.md

version: 2.7.83-beta-{build}
version: 2.8.0-beta-{build}

image: Visual Studio 2019

Expand All @@ -15,22 +15,37 @@ init:
if ($env:APPVEYOR_REPO_TAG -eq "true") {
$ver = $env:APPVEYOR_REPO_TAG_NAME
if($ver.StartsWith("v") -eq $true) { $ver = $ver.Substring(1) }
Update-AppveyorBuild -Version $ver
$env:PACKAGE_VERSION = $ver
} else {
$env:PACKAGE_VERSION = $env:APPVEYOR_BUILD_VERSION
}
- ps: |
Write-Host "PACKAGE_VERSION:$env:PACKAGE_VERSION | APPVEYOR_BUILD_VERSION='$env:APPVEYOR_BUILD_VERSION'" -ForegroundColor Yellow
Write-Host "APPVEYOR_REPO_TAG_NAME:$env:APPVEYOR_REPO_TAG_NAME'" -ForegroundColor Yellow

skip_commits:
files:
- docs/*
- art/*
- '**/*.md'
#- .travis.yml
- .gitignore
- .editorconfig
message: /updated readme.*|update readme.*s|update docs.*|update version.*|update changelog.*/

environment:
matrix:
- BUILD_TARGET: base
- BUILD_TARGET: fsharp

build_script:
- cmd: dotnet build src/CommandLine/ -c Release --version-suffix %APPVEYOR_BUILD_VERSION% /p:BuildTarget=%BUILD_TARGET%
- cmd: dotnet build src/CommandLine/ -c Release --version-suffix %PACKAGE_VERSION% /p:BuildTarget=%BUILD_TARGET%

test_script:
- cmd: dotnet test tests/CommandLine.Tests/ /p:BuildTarget=%BUILD_TARGET%

after_test:
- cmd: dotnet pack src/CommandLine/ -c Release --version-suffix %APPVEYOR_BUILD_VERSION% /p:BuildTarget=%BUILD_TARGET%
- cmd: dotnet pack src/CommandLine/ -c Release --version-suffix %PACKAGE_VERSION% /p:BuildTarget=%BUILD_TARGET%

artifacts:
- path: 'src/CommandLine/bin/Release/*.nupkg'
Expand All @@ -57,3 +72,4 @@ deploy:
artifact: 'NuGetPackages'
on:
APPVEYOR_REPO_TAG: true

4 changes: 2 additions & 2 deletions demo/ReadText.Demo/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface IOptions
string FileName { get; set; }
}

[Verb("head", HelpText = "Displays first lines of a file.")]
[Verb("head", true, HelpText = "Displays first lines of a file.")]
class HeadOptions : IOptions
{
public uint? Lines { get; set; }
Expand Down Expand Up @@ -62,4 +62,4 @@ class TailOptions : IOptions

public string FileName { get; set; }
}
}
}
6 changes: 3 additions & 3 deletions src/CommandLine/CommandLine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@
<AssemblyName>CommandLine</AssemblyName>
<OutputType>Library</OutputType>
<TargetFrameworks>netstandard2.0;net40;net45;net461</TargetFrameworks>
<DefineConstants>$(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;ERRH_DISABLE_INLINE_METHODS;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC;CSX_REM_CRYPTORAND</DefineConstants>
<DefineConstants>$(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC;CSX_REM_CRYPTORAND;ERRH_ADD_MAYBE_METHODS</DefineConstants>
<DefineConstants Condition="'$(BuildTarget)' != 'fsharp'">$(DefineConstants);SKIP_FSHARP</DefineConstants>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyOriginatorKeyFile>..\..\CommandLine.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<PackageId Condition="'$(BuildTarget)' != 'fsharp'">CommandLineParser</PackageId>
<PackageId Condition="'$(BuildTarget)' == 'fsharp'">CommandLineParser.FSharp</PackageId>
<Authors>gsscoder;nemec;ericnewton76</Authors>
<Authors>gsscoder;nemec;ericnewton76;moh-hassan</Authors>
<Title>Command Line Parser Library</Title>
<Version Condition="'$(VersionSuffix)' != ''">$(VersionSuffix)</Version>
<Version Condition="'$(VersionSuffix)' == ''">0.0.0</Version>
<Description Condition="'$(BuildTarget)' != 'fsharp'">Terse syntax C# command line parser for .NET. For FSharp support see CommandLineParser.FSharp. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks.</Description>
<Description Condition="'$(BuildTarget)' == 'fsharp'">Terse syntax C# command line parser for .NET with F# support. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks.</Description>
<Copyright>Copyright (c) 2005 - 2018 Giacomo Stelluti Scala &amp; Contributors</Copyright>
<Copyright>Copyright (c) 2005 - 2020 Giacomo Stelluti Scala &amp; Contributors</Copyright>
<PackageLicenseFile>License.md</PackageLicenseFile>
<PackageIcon>CommandLine20.png</PackageIcon>
<PackageProjectUrl>https://github.com/commandlineparser/commandline</PackageProjectUrl>
Expand Down
6 changes: 3 additions & 3 deletions src/CommandLine/Core/InstanceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ public static ParserResult<T> Build<T>(
var validationErrors = specPropsWithValue.Validate(SpecificationPropertyRules.Lookup(tokens));

var allErrors =
tokenizerResult.SuccessfulMessages()
tokenizerResult.SuccessMessages()
.Concat(missingValueErrors)
.Concat(optionSpecPropsResult.SuccessfulMessages())
.Concat(valueSpecPropsResult.SuccessfulMessages())
.Concat(optionSpecPropsResult.SuccessMessages())
.Concat(valueSpecPropsResult.SuccessMessages())
.Concat(validationErrors)
.Concat(setPropertyErrors)
.Memoize();
Expand Down
46 changes: 41 additions & 5 deletions src/CommandLine/Core/InstanceChooser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ public static ParserResult<object> Choose(
bool autoVersion,
IEnumerable<ErrorType> nonFatalErrors)
{
var verbs = Verb.SelectFromTypes(types);
var defaultVerbs = verbs.Where(t => t.Item1.IsDefault);

int defaultVerbCount = defaultVerbs.Count();
if (defaultVerbCount > 1)
return MakeNotParsed(types, new MultipleDefaultVerbsError());

var defaultVerb = defaultVerbCount == 1 ? defaultVerbs.First() : null;

Func<ParserResult<object>> choose = () =>
{
var firstArg = arguments.First();
Expand All @@ -31,25 +40,52 @@ public static ParserResult<object> Choose(
nameComparer.Equals(command, firstArg) ||
nameComparer.Equals(string.Concat("--", command), firstArg);

var verbs = Verb.SelectFromTypes(types);

return (autoHelp && preprocCompare("help"))
? MakeNotParsed(types,
MakeHelpVerbRequestedError(verbs,
arguments.Skip(1).FirstOrDefault() ?? string.Empty, nameComparer))
: (autoVersion && preprocCompare("version"))
? MakeNotParsed(types, new VersionRequestedError())
: MatchVerb(tokenizer, verbs, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors);
: MatchVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors);
};

return arguments.Any()
? choose()
: MakeNotParsed(types, new NoVerbSelectedError());
: (defaultVerbCount == 1
? MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors)
: MakeNotParsed(types, new NoVerbSelectedError()));
}

private static ParserResult<object> MatchDefaultVerb(
Func<IEnumerable<string>, IEnumerable<OptionSpecification>, Result<IEnumerable<Token>, Error>> tokenizer,
IEnumerable<Tuple<Verb, Type>> verbs,
Tuple<Verb, Type> defaultVerb,
IEnumerable<string> arguments,
StringComparer nameComparer,
bool ignoreValueCase,
CultureInfo parsingCulture,
bool autoHelp,
bool autoVersion,
IEnumerable<ErrorType> nonFatalErrors)
{
return !(defaultVerb is null)
? InstanceBuilder.Build(
Maybe.Just<Func<object>>(() => defaultVerb.Item2.AutoDefault()),
tokenizer,
arguments,
nameComparer,
ignoreValueCase,
parsingCulture,
autoHelp,
autoVersion,
nonFatalErrors)
: MakeNotParsed(verbs.Select(v => v.Item2), new BadVerbSelectedError(arguments.First()));
}

private static ParserResult<object> MatchVerb(
Func<IEnumerable<string>, IEnumerable<OptionSpecification>, Result<IEnumerable<Token>, Error>> tokenizer,
IEnumerable<Tuple<Verb, Type>> verbs,
Tuple<Verb, Type> defaultVerb,
IEnumerable<string> arguments,
StringComparer nameComparer,
bool ignoreValueCase,
Expand All @@ -71,7 +107,7 @@ private static ParserResult<object> MatchVerb(
autoHelp,
autoVersion,
nonFatalErrors)
: MakeNotParsed(verbs.Select(v => v.Item2), new BadVerbSelectedError(arguments.First()));
: MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors);
}

private static HelpVerbRequestedError MakeHelpVerbRequestedError(
Expand Down
28 changes: 26 additions & 2 deletions src/CommandLine/Core/SpecificationPropertyRules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,35 @@ public static IEnumerable<Func<IEnumerable<SpecificationProperty>, IEnumerable<E
{
EnforceMutuallyExclusiveSet(),
EnforceGroup(),
EnforceMutuallyExclusiveSetAndGroupAreNotUsedTogether(),
EnforceRequired(),
EnforceRange(),
EnforceSingle(tokens)
};
}

private static Func<IEnumerable<SpecificationProperty>, IEnumerable<Error>> EnforceMutuallyExclusiveSetAndGroupAreNotUsedTogether()
{
return specProps =>
{
var options =
from sp in specProps
where sp.Specification.IsOption()
let o = (OptionSpecification)sp.Specification
where o.SetName.Length > 0
where o.Group.Length > 0
select o;

if (options.Any())
{
return from o in options
select new GroupOptionAmbiguityError(new NameInfo(o.ShortName, o.LongName));
}

return Enumerable.Empty<Error>();
};
}

private static Func<IEnumerable<SpecificationProperty>, IEnumerable<Error>> EnforceGroup()
{
return specProps =>
Expand All @@ -36,14 +59,15 @@ where o.Group.Length > 0
select new
{
Option = o,
Value = sp.Value
Value = sp.Value,
DefaultValue = sp.Specification.DefaultValue
};

var groups = from o in optionsValues
group o by o.Option.Group into g
select g;

var errorGroups = groups.Where(gr => gr.All(g => g.Value.IsNothing()));
var errorGroups = groups.Where(gr => gr.All(g => g.Value.IsNothing() && g.DefaultValue.IsNothing()));

if (errorGroups.Any())
{
Expand Down
2 changes: 1 addition & 1 deletion src/CommandLine/Core/Tokenizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public static Result<IEnumerable<Token>, Error> ExplodeOptionList(

var flattened = exploded.SelectMany(x => x);

return Result.Succeed(flattened, tokenizerResult.SuccessfulMessages());
return Result.Succeed(flattened, tokenizerResult.SuccessMessages());
}

public static IEnumerable<Token> Normalize(
Expand Down
19 changes: 15 additions & 4 deletions src/CommandLine/Core/Verb.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ sealed class Verb
private readonly string name;
private readonly string helpText;
private readonly bool hidden;
private readonly bool isDefault;

public Verb(string name, string helpText, bool hidden = false)
public Verb(string name, string helpText, bool hidden = false, bool isDefault = false)
{
this.name = name ?? throw new ArgumentNullException(nameof(name));
if ( string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));
this.name = name;

this.helpText = helpText ?? throw new ArgumentNullException(nameof(helpText));
this.hidden = hidden;
this.isDefault = isDefault;
}

public string Name
Expand All @@ -35,12 +40,18 @@ public bool Hidden
get { return hidden; }
}

public bool IsDefault
{
get => isDefault;
}

public static Verb FromAttribute(VerbAttribute attribute)
{
return new Verb(
attribute.Name,
attribute.HelpText,
attribute.Hidden
attribute.Hidden,
attribute.IsDefault
);
}

Expand All @@ -54,4 +65,4 @@ select Tuple.Create(
type);
}
}
}
}
58 changes: 56 additions & 2 deletions src/CommandLine/Error.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using System;
using System.Collections.Generic;
using System.Linq;

namespace CommandLine
{
Expand Down Expand Up @@ -74,7 +75,16 @@ public enum ErrorType
/// <summary>
/// Value of <see cref="CommandLine.MissingGroupOptionError"/> type.
/// </summary>
MissingGroupOptionError
MissingGroupOptionError,
/// <summary>

/// Value of <see cref="CommandLine.MultipleDefaultVerbsError"/> type.
/// </summary>
MultipleDefaultVerbsError

/// Value of <see cref="CommandLine.GroupOptionAmbiguityError"/> type.
/// </summary>
GroupOptionAmbiguityError

}

Expand Down Expand Up @@ -532,7 +542,7 @@ internal InvalidAttributeConfigurationError()
}
}

public sealed class MissingGroupOptionError : Error
public sealed class MissingGroupOptionError : Error, IEquatable<Error>, IEquatable<MissingGroupOptionError>
{
public const string ErrorMessage = "At least one option in a group must have value.";

Expand All @@ -555,5 +565,49 @@ public IEnumerable<NameInfo> Names
{
get { return names; }
}

public new bool Equals(Error obj)
{
var other = obj as MissingGroupOptionError;
if (other != null)
{
return Equals(other);
}

return base.Equals(obj);
}

public bool Equals(MissingGroupOptionError other)
{
if (other == null)
{
return false;
}

return Group.Equals(other.Group) && Names.SequenceEqual(other.Names);
}
}

public sealed class GroupOptionAmbiguityError : NamedError
{
public NameInfo Option;

internal GroupOptionAmbiguityError(NameInfo option)
: base(ErrorType.GroupOptionAmbiguityError, option)
{
Option = option;
}
}

/// <summary>
/// Models an error generated when multiple default verbs are defined.
/// </summary>
public sealed class MultipleDefaultVerbsError : Error
{
public const string ErrorMessage = "More than one default verb is not allowed.";

internal MultipleDefaultVerbsError()
: base(ErrorType.MultipleDefaultVerbsError)
{ }
}
}
Loading