Skip to content

Multiple long args #886

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 17 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
31 changes: 31 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Either fork from this fiddle and paste link here: https://dotnetfiddle.net/mh9CjX

or

Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Additional context**
Add any other context about the problem here.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file.

CommandLineParser project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.9.0-preview2]

### Added
- Properly assign arguments after a double dash to values, fix #605 by [@robnasby, PR# 610](https://github.com/commandlineparser/commandline/pull/610).

### Changed
- Drop "Add multi-instance option support".


## [2.9.0-preview1] - 2020-7-24

### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ __This library provides _hassle free_ command line parsing with a constantly upd
- Support Mutable and Immutable types.
- Support HelpText localization.
- Support ordering of options in HelpText.
- Support [Mutually Exclusive Options](https://github.com/commandlineparser/commandline/wiki/Mutually-Exclusive-Options) and Options groups.
- Support [Mutually Exclusive Options](https://github.com/commandlineparser/commandline/wiki/Mutually-Exclusive-Options) and [Option groups](https://github.com/commandlineparser/commandline/wiki/Option-Groups).
- Support named and value options.
- Support Asynchronous programming with async and await.
- Unparsing support: `CommandLine.Parser.Default.FormatCommandLine<T>(T options)`.
Expand Down
13 changes: 2 additions & 11 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.9.0-ci-{build}
version: 2.9.1-ci-{build}

image: Visual Studio 2019

Expand Down Expand Up @@ -44,18 +44,9 @@ on_failure:
appveyor PushArtifact .\files.lst -DeploymentName "Failed Build File Listing"

deploy:
- provider: GitHub
auth_token:
secure: hVyVwHl0JiVq0VxXB4VMRWbUtrGclIzadfnWFcWCQBLvbgMLahLBnWlwGglT63pZ
artifact: /.*(\.|\.s)nupkg/
prerelease: false
force_update: true #fsharp package runs as separate build job, so have to force_update to add fsharp.nuget added
on:
APPVEYOR_REPO_TAG: true

- provider: NuGet
api_key:
secure: e2gJJ3r6Uls5trJwryaudAZd49QniNfIjax/A+tfywlchSnIQVOzOQCO9tTSNccI
secure: llMIgYMuLHh9thyKMEAmkWraTaA9Zvcm1F8/yRwm0HCiPIt/ehR/GI4kJKyMTPyf
artifact: /.*(\.|\.s)nupkg/
on:
APPVEYOR_REPO_TAG: true

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion demo/ReadText.LocalizedDemo/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@
<value>A sequence option '{0}' is defined with fewer or more items than required.</value>
</data>
<data name="SentenceSequenceOutOfRangeErrorValue" xml:space="preserve">
<value>A sequence value not bound to option name is defined with few items than required.</value>
<value>A sequence value not bound to option name is defined with fewer items than required.</value>
</data>
<data name="SentenceSetValueExceptionError" xml:space="preserve">
<value>Error setting value to option '{0}': {1}</value>
Expand Down
100 changes: 100 additions & 0 deletions src/CommandLine/CastExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System;
using System.Linq;
using System.Reflection;

namespace CommandLine
{
internal static class CastExtensions
{
private const string ImplicitCastMethodName = "op_Implicit";
private const string ExplicitCastMethodName = "op_Explicit";

public static bool CanCast<T>(this Type baseType)
{
return baseType.CanImplicitCast<T>() || baseType.CanExplicitCast<T>();
}

public static bool CanCast<T>(this object obj)
{
var objType = obj.GetType();
return objType.CanCast<T>();
}

public static T Cast<T>(this object obj)
{
try
{
return (T) obj;
}
catch (InvalidCastException)
{
if (obj.CanImplicitCast<T>())
return obj.ImplicitCast<T>();
if (obj.CanExplicitCast<T>())
return obj.ExplicitCast<T>();
else
throw;
}
}

private static bool CanImplicitCast<T>(this Type baseType)
{
return baseType.CanCast<T>(ImplicitCastMethodName);
}

private static bool CanImplicitCast<T>(this object obj)
{
var baseType = obj.GetType();
return baseType.CanImplicitCast<T>();
}

private static bool CanExplicitCast<T>(this Type baseType)
{
return baseType.CanCast<T>(ExplicitCastMethodName);
}

private static bool CanExplicitCast<T>(this object obj)
{
var baseType = obj.GetType();
return baseType.CanExplicitCast<T>();
}

private static bool CanCast<T>(this Type baseType, string castMethodName)
{
var targetType = typeof(T);
return baseType.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(mi => mi.Name == castMethodName && mi.ReturnType == targetType)
.Any(mi =>
{
ParameterInfo pi = mi.GetParameters().FirstOrDefault();
return pi != null && pi.ParameterType == baseType;
});
}

private static T ImplicitCast<T>(this object obj)
{
return obj.Cast<T>(ImplicitCastMethodName);
}

private static T ExplicitCast<T>(this object obj)
{
return obj.Cast<T>(ExplicitCastMethodName);
}

private static T Cast<T>(this object obj, string castMethodName)
{
var objType = obj.GetType();
MethodInfo conversionMethod = objType.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(mi => mi.Name == castMethodName && mi.ReturnType == typeof(T))
.SingleOrDefault(mi =>
{
ParameterInfo pi = mi.GetParameters().FirstOrDefault();
return pi != null && pi.ParameterType == objType;
});
if (conversionMethod != null)
return (T) conversionMethod.Invoke(null, new[] {obj});
else
throw new InvalidCastException($"No method to cast {objType.FullName} to {typeof(T).FullName}");
}
}
}
2 changes: 1 addition & 1 deletion src/CommandLine/Core/InstanceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public static ParserResult<T> Build<T>(
var missingValueErrors = from token in errorsPartition
select
new MissingValueOptionError(
optionSpecs.Single(o => token.Text.MatchName(o.ShortName, o.LongName, nameComparer))
optionSpecs.Single(o => token.Text.MatchName(o.ShortName, o.LongNames, nameComparer))
.FromOptionSpecification());

var specPropsWithValue =
Expand Down
7 changes: 4 additions & 3 deletions src/CommandLine/Core/NameExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information.

using System;
using System.Linq;

namespace CommandLine.Core
{
static class NameExtensions
{
public static bool MatchName(this string value, string shortName, string longName, StringComparer comparer)
public static bool MatchName(this string value, string shortName, string[] longNames, StringComparer comparer)
{
return value.Length == 1
? comparer.Equals(value, shortName)
: comparer.Equals(value, longName);
: longNames.Any(longName => comparer.Equals(value, longName));
}

public static NameInfo FromOptionSpecification(this OptionSpecification specification)
{
return new NameInfo(
specification.ShortName,
specification.LongName);
specification.LongNames);
}

public static NameInfo FromSpecification(this Specification specification)
Expand Down
4 changes: 2 additions & 2 deletions src/CommandLine/Core/NameLookup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ static class NameLookup
{
public static NameLookupResult Contains(string name, IEnumerable<OptionSpecification> specifications, StringComparer comparer)
{
var option = specifications.FirstOrDefault(a => name.MatchName(a.ShortName, a.LongName, comparer));
var option = specifications.FirstOrDefault(a => name.MatchName(a.ShortName, a.LongNames, comparer));
if (option == null) return NameLookupResult.NoOptionFound;
return option.ConversionType == typeof(bool) || (option.ConversionType == typeof(int) && option.FlagCounter)
? NameLookupResult.BooleanOptionFound
Expand All @@ -29,7 +29,7 @@ public static Maybe<char> HavingSeparator(string name, IEnumerable<OptionSpecifi
StringComparer comparer)
{
return specifications.SingleOrDefault(
a => name.MatchName(a.ShortName, a.LongName, comparer) && a.Separator != '\0')
a => name.MatchName(a.ShortName, a.LongNames, comparer) && a.Separator != '\0')
.ToMaybe()
.MapValueOrDefault(spec => Maybe.Just(spec.Separator), Maybe.Nothing<char>());
}
Expand Down
2 changes: 1 addition & 1 deletion src/CommandLine/Core/OptionMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public static Result<
pt =>
{
var matched = options.Where(s =>
s.Key.MatchName(((OptionSpecification)pt.Specification).ShortName, ((OptionSpecification)pt.Specification).LongName, comparer)).ToMaybe();
s.Key.MatchName(((OptionSpecification)pt.Specification).ShortName, ((OptionSpecification)pt.Specification).LongNames, comparer)).ToMaybe();
if (matched.IsJust())
{
var matches = matched.GetValueOrDefault(Enumerable.Empty<KeyValuePair<string, IEnumerable<string>>>());
Expand Down
30 changes: 25 additions & 5 deletions src/CommandLine/Core/OptionSpecification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace CommandLine.Core
sealed class OptionSpecification : Specification
{
private readonly string shortName;
private readonly string longName;
private readonly string[] longNames;
private readonly char separator;
private readonly string setName;
private readonly string group;
Expand All @@ -23,7 +23,21 @@ public OptionSpecification(string shortName, string longName, bool required, str
required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, conversionType == typeof(int) && flagCounter ? TargetType.Switch : targetType, hidden)
{
this.shortName = shortName;
this.longName = longName;
this.longNames = new [] { longName };
this.separator = separator;
this.setName = setName;
this.group = group;
this.flagCounter = flagCounter;
}

public OptionSpecification(string shortName, string[] longNames, bool required, string setName, Maybe<int> min, Maybe<int> max,
char separator, Maybe<object> defaultValue, string helpText, string metaValue, IEnumerable<string> enumValues,
Type conversionType, TargetType targetType, string group, bool flagCounter = false, bool hidden = false)
: base(SpecificationType.Option,
required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, conversionType == typeof(int) && flagCounter ? TargetType.Switch : targetType, hidden)
{
this.shortName = shortName;
this.longNames = longNames;
this.separator = separator;
this.setName = setName;
this.group = group;
Expand All @@ -34,7 +48,7 @@ public static OptionSpecification FromAttribute(OptionAttribute attribute, Type
{
return new OptionSpecification(
attribute.ShortName,
attribute.LongName,
attribute.LongNames,
attribute.Required,
attribute.SetName,
attribute.Min == -1 ? Maybe.Nothing<int>() : Maybe.Just(attribute.Min),
Expand All @@ -57,14 +71,20 @@ public static OptionSpecification NewSwitch(string shortName, string longName, b
'\0', Maybe.Nothing<object>(), helpText, metaValue, Enumerable.Empty<string>(), typeof(bool), TargetType.Switch, string.Empty, false, hidden);
}

public static OptionSpecification NewSwitch(string shortName, string[] longNames, bool required, string helpText, string metaValue, bool hidden = false)
{
return new OptionSpecification(shortName, longNames, required, string.Empty, Maybe.Nothing<int>(), Maybe.Nothing<int>(),
'\0', Maybe.Nothing<object>(), helpText, metaValue, Enumerable.Empty<string>(), typeof(bool), TargetType.Switch, string.Empty, false, hidden);
}

public string ShortName
{
get { return shortName; }
}

public string LongName
public string[] LongNames
{
get { return longName; }
get { return longNames; }
}

public char Separator
Expand Down
2 changes: 1 addition & 1 deletion src/CommandLine/Core/Specification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public static Specification FromProperty(PropertyInfo property)
var spec = OptionSpecification.FromAttribute(oa.Single(), property.PropertyType,
ReflectionHelper.GetNamesOfEnum(property.PropertyType));

if (spec.ShortName.Length == 0 && spec.LongName.Length == 0)
if (spec.ShortName.Length == 0 && spec.LongNames.Length == 0)
{
return spec.WithLongName(property.Name.ToLowerInvariant());
}
Expand Down
4 changes: 2 additions & 2 deletions src/CommandLine/Core/SpecificationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static OptionSpecification WithLongName(this OptionSpecification specific
{
return new OptionSpecification(
specification.ShortName,
newLongName,
new [] { newLongName },
specification.Required,
specification.SetName,
specification.Min,
Expand All @@ -41,7 +41,7 @@ public static OptionSpecification WithLongName(this OptionSpecification specific

public static string UniqueName(this OptionSpecification specification)
{
return specification.ShortName.Length > 0 ? specification.ShortName : specification.LongName;
return specification.ShortName.Length > 0 ? specification.ShortName : specification.LongNames[0];
}

public static IEnumerable<Specification> ThrowingValidate(this IEnumerable<Specification> specifications, IEnumerable<Tuple<Func<Specification, bool>, string>> guardsLookup)
Expand Down
7 changes: 6 additions & 1 deletion src/CommandLine/Core/SpecificationGuards.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;
using CSharpx;

namespace CommandLine.Core
Expand Down Expand Up @@ -30,7 +31,11 @@ private static Func<Specification, bool> GuardAgainstSequenceWithWrongRange()

private static Func<Specification, bool> GuardAgainstOneCharLongName()
{
return spec => spec.IsOption() && ((OptionSpecification)spec).LongName.Length == 1;
return spec =>
{
var optionSpecification = spec as OptionSpecification;
return spec.IsOption() && (optionSpecification?.LongNames.Any(x => x.Length == 1) ?? false);
};
}

private static Func<Specification, bool> GuardAgainstSequenceWithZeroRange()
Expand Down
Loading