Skip to content

Fix #601 - better getopt compatibility for -- and --help handling #607

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 8 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
35 changes: 30 additions & 5 deletions src/CommandLine/Core/InstanceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,31 @@ public static ParserResult<T> Build<T>(
bool autoHelp,
bool autoVersion,
IEnumerable<ErrorType> nonFatalErrors)
{
return Build(
factory,
tokenizer,
arguments,
nameComparer,
ignoreValueCase,
parsingCulture,
autoHelp,
autoVersion,
false,
nonFatalErrors);
}

public static ParserResult<T> Build<T>(
Maybe<Func<T>> factory,
Func<IEnumerable<string>, IEnumerable<OptionSpecification>, Result<IEnumerable<Token>, Error>> tokenizer,
IEnumerable<string> arguments,
StringComparer nameComparer,
bool ignoreValueCase,
CultureInfo parsingCulture,
bool autoHelp,
bool autoVersion,
bool allowMultiInstance,
IEnumerable<ErrorType> nonFatalErrors)
{
var typeInfo = factory.MapValueOrDefault(f => f().GetType(), typeof(T));

Expand Down Expand Up @@ -64,14 +89,14 @@ public static ParserResult<T> Build<T>(
OptionMapper.MapValues(
(from pt in specProps where pt.Specification.IsOption() select pt),
optionsPartition,
(vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, parsingCulture, ignoreValueCase),
(vals, type, isScalar, isFlag) => TypeConverter.ChangeType(vals, type, isScalar, isFlag, parsingCulture, ignoreValueCase),
nameComparer);

var valueSpecPropsResult =
ValueMapper.MapValues(
(from pt in specProps where pt.Specification.IsValue() orderby ((ValueSpecification)pt.Specification).Index select pt),
valuesPartition,
(vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, parsingCulture, ignoreValueCase));
valuesPartition,
(vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, false, parsingCulture, ignoreValueCase));

var missingValueErrors = from token in errorsPartition
select
Expand All @@ -86,7 +111,7 @@ public static ParserResult<T> Build<T>(

//build the instance, determining if the type is mutable or not.
T instance;
if(typeInfo.IsMutable() == true)
if (typeInfo.IsMutable() == true)
{
instance = BuildMutable(factory, specPropsWithValue, setPropertyErrors);
}
Expand All @@ -95,7 +120,7 @@ public static ParserResult<T> Build<T>(
instance = BuildImmutable(typeInfo, factory, specProps, specPropsWithValue, setPropertyErrors);
}

var validationErrors = specPropsWithValue.Validate(SpecificationPropertyRules.Lookup(tokens));
var validationErrors = specPropsWithValue.Validate(SpecificationPropertyRules.Lookup(tokens, allowMultiInstance));

var allErrors =
tokenizerResult.SuccessMessages()
Expand Down
29 changes: 28 additions & 1 deletion src/CommandLine/Core/InstanceChooser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,31 @@ public static ParserResult<object> Choose(
bool autoHelp,
bool autoVersion,
IEnumerable<ErrorType> nonFatalErrors)
{
return Choose(
tokenizer,
types,
arguments,
nameComparer,
ignoreValueCase,
parsingCulture,
autoHelp,
autoVersion,
false,
nonFatalErrors);
}

public static ParserResult<object> Choose(
Func<IEnumerable<string>, IEnumerable<OptionSpecification>, Result<IEnumerable<Token>, Error>> tokenizer,
IEnumerable<Type> types,
IEnumerable<string> arguments,
StringComparer nameComparer,
bool ignoreValueCase,
CultureInfo parsingCulture,
bool autoHelp,
bool autoVersion,
bool allowMultiInstance,
IEnumerable<ErrorType> nonFatalErrors)
{
var verbs = Verb.SelectFromTypes(types);
var defaultVerbs = verbs.Where(t => t.Item1.IsDefault);
Expand All @@ -46,7 +71,7 @@ public static ParserResult<object> Choose(
arguments.Skip(1).FirstOrDefault() ?? string.Empty, nameComparer))
: (autoVersion && preprocCompare("version"))
? MakeNotParsed(types, new VersionRequestedError())
: MatchVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors);
: MatchVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, allowMultiInstance, nonFatalErrors);
};

return arguments.Any()
Expand Down Expand Up @@ -92,6 +117,7 @@ private static ParserResult<object> MatchVerb(
CultureInfo parsingCulture,
bool autoHelp,
bool autoVersion,
bool allowMultiInstance,
IEnumerable<ErrorType> nonFatalErrors)
{
return verbs.Any(a => nameComparer.Equals(a.Item1.Name, arguments.First()))
Expand All @@ -106,6 +132,7 @@ private static ParserResult<object> MatchVerb(
parsingCulture,
autoHelp,
autoVersion,
allowMultiInstance,
nonFatalErrors)
: MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors);
}
Expand Down
6 changes: 3 additions & 3 deletions src/CommandLine/Core/NameLookup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace CommandLine.Core
enum NameLookupResult
{
NoOptionFound,
BooleanOptionFound,
FlagOptionFound,
OtherOptionFound
}

Expand All @@ -20,8 +20,8 @@ public static NameLookupResult Contains(string name, IEnumerable<OptionSpecifica
{
var option = specifications.FirstOrDefault(a => name.MatchName(a.ShortName, a.LongName, comparer));
if (option == null) return NameLookupResult.NoOptionFound;
return option.ConversionType == typeof(bool)
? NameLookupResult.BooleanOptionFound
return option.ConversionType == typeof(bool) || option.FlagCounter
? NameLookupResult.FlagOptionFound
: NameLookupResult.OtherOptionFound;
}

Expand Down
38 changes: 23 additions & 15 deletions src/CommandLine/Core/OptionMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,41 @@ public static Result<
MapValues(
IEnumerable<SpecificationProperty> propertyTuples,
IEnumerable<KeyValuePair<string, IEnumerable<string>>> options,
Func<IEnumerable<string>, Type, bool, Maybe<object>> converter,
Func<IEnumerable<string>, Type, bool, bool, Maybe<object>> converter,
StringComparer comparer)
{
var sequencesAndErrors = propertyTuples
.Select(
pt =>
{
var matched = options.FirstOrDefault(s =>
var matched = options.Where(s =>
s.Key.MatchName(((OptionSpecification)pt.Specification).ShortName, ((OptionSpecification)pt.Specification).LongName, comparer)).ToMaybe();
return matched.IsJust()
? (
from sequence in matched
from converted in
converter(
sequence.Value,
pt.Property.PropertyType,
pt.Specification.TargetType != TargetType.Sequence)
select Tuple.Create(
pt.WithValue(Maybe.Just(converted)), Maybe.Nothing<Error>())
)

if (matched.IsJust())
{
var matches = matched.GetValueOrDefault(Enumerable.Empty<KeyValuePair<string, IEnumerable<string>>>());
var values = new List<string>();
foreach (var kvp in matches)
{
foreach (var value in kvp.Value)
{
values.Add(value);
}
}

bool isFlag = pt.Specification.Tag == SpecificationType.Option && ((OptionSpecification)pt.Specification).FlagCounter;

return converter(values, isFlag ? typeof(bool) : pt.Property.PropertyType, pt.Specification.TargetType != TargetType.Sequence, isFlag)
.Select(value => Tuple.Create(pt.WithValue(Maybe.Just(value)), Maybe.Nothing<Error>()))
.GetValueOrDefault(
Tuple.Create<SpecificationProperty, Maybe<Error>>(
pt,
Maybe.Just<Error>(
new BadFormatConversionError(
((OptionSpecification)pt.Specification).FromOptionSpecification()))))
: Tuple.Create(pt, Maybe.Nothing<Error>());
((OptionSpecification)pt.Specification).FromOptionSpecification()))));
}

return Tuple.Create(pt, Maybe.Nothing<Error>());
}
).Memoize();
return Result.Succeed(
Expand Down
16 changes: 12 additions & 4 deletions src/CommandLine/Core/OptionSpecification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@ sealed class OptionSpecification : Specification
private readonly char separator;
private readonly string setName;
private readonly string group;
private readonly bool flagCounter;

public OptionSpecification(string shortName, string longName, 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 hidden = false)
Type conversionType, TargetType targetType, string group, bool flagCounter, bool hidden)
: base(SpecificationType.Option,
required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, targetType, hidden)
required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, conversionType == typeof(int) && flagCounter ? TargetType.Switch : targetType, hidden)
{
this.shortName = shortName;
this.longName = longName;
this.separator = separator;
this.setName = setName;
this.group = group;
this.flagCounter = flagCounter;
}

public static OptionSpecification FromAttribute(OptionAttribute attribute, Type conversionType, IEnumerable<string> enumValues)
Expand All @@ -45,13 +47,14 @@ public static OptionSpecification FromAttribute(OptionAttribute attribute, Type
conversionType,
conversionType.ToTargetType(),
attribute.Group,
attribute.FlagCounter,
attribute.Hidden);
}

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

public string ShortName
Expand All @@ -78,5 +81,10 @@ public string Group
{
get { return group; }
}

public bool FlagCounter
{
get { return flagCounter; }
}
}
}
2 changes: 1 addition & 1 deletion src/CommandLine/Core/Scalar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public static IEnumerable<Token> Partition(
{
return from tseq in tokens.Pairwise(
(f, s) =>
f.IsName() && s.IsValue()
f.IsName() && s.IsValueUnforced()
? typeLookup(f.Text).MapValueOrDefault(info =>
info.TargetType == TargetType.Scalar ? new[] { f, s } : new Token[] { }, new Token[] { })
: new Token[] { })
Expand Down
Loading