Skip to content
This repository was archived by the owner on Jul 11, 2018. It is now read-only.
Open
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
5 changes: 5 additions & 0 deletions src/CommandLine.Tests/CommandLine.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
<Compile Include="..\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="Fakes\FakeOptionsWithNullable.cs" />
<Compile Include="Fakes\FakeOptionsWithNullDefault.cs" />
<Compile Include="Fakes\FakeOptionsWithSequence.cs" />
<Compile Include="Fakes\FakeOptionsWithSequenceWithMinZero.cs" />
<Compile Include="Fakes\FakeOptionsWithSetAndNonSet.cs" />
<Compile Include="Fakes\FakeOptionsWithTwoIntegers.cs" />
<Compile Include="Fakes\FakeInterfaceOptions.cs" />
<Compile Include="Fakes\FakeOptionsWithHelpTextEnum.cs" />
Expand Down
10 changes: 10 additions & 0 deletions src/CommandLine.Tests/Fakes/FakeOptionsWithNullDefault.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Collections.Generic;

namespace CommandLine.Tests.Fakes
{
class FakeOptionsWithNullDefault
{
[Option('i', DefaultValue = null)]
public IEnumerable<int> IntSequence { get; set; }
}
}
11 changes: 11 additions & 0 deletions src/CommandLine.Tests/Fakes/FakeOptionsWithNullable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace CommandLine.Tests.Fakes
{
class FakeOptionsWithNullable
{
[Option('n')]
public int? NullableIntValue { get; set; }

[Option('c')]
public Colors? NullableColorsValue { get; set; }
}
}
10 changes: 10 additions & 0 deletions src/CommandLine.Tests/Fakes/FakeOptionsWithSequence.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Collections.Generic;

namespace CommandLine.Tests.Fakes
{
class FakeOptionsWithSequence
{
[Option('i')]
public IEnumerable<int> IntSequence { get; set; }
}
}
10 changes: 10 additions & 0 deletions src/CommandLine.Tests/Fakes/FakeOptionsWithSequenceWithMinZero.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Collections.Generic;

namespace CommandLine.Tests.Fakes
{
class FakeOptionsWithSequenceWithMinZero
{
[Option('i', Min = 0)]
public IEnumerable<int> IntSequence { get; set; }
}
}
11 changes: 11 additions & 0 deletions src/CommandLine.Tests/Fakes/FakeOptionsWithSetAndNonSet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace CommandLine.Tests.Fakes
{
class FakeOptionsWithSetAndNonSet
{
[Option('s', "set", SetName = "Set")]
public bool Set { get; set; }

[Option('n', "nonset")]
public bool NonSet { get; set; }
}
}
2 changes: 1 addition & 1 deletion src/CommandLine.Tests/Unit/Core/OptionMapperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public void Map_boolean_switch_creates_boolean_value()
var specProps = new[]
{
SpecificationProperty.Create(
new OptionSpecification("x", string.Empty, false, string.Empty, -1, -1, Maybe.Nothing<object>(), typeof(bool), string.Empty, string.Empty, new List<string>()),
new OptionSpecification("x", string.Empty, false, string.Empty, -1, -1, Maybe.Nothing<object>(), false, typeof(bool), string.Empty, string.Empty, new List<string>()),
typeof(FakeOptions).GetProperties().Single(p => p.Name.Equals("BoolValue", StringComparison.Ordinal)),
Maybe.Nothing<object>())
};
Expand Down
8 changes: 4 additions & 4 deletions src/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ public void Partition_sequence_returns_sequence()
};
var specs =new[]
{
new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, -1, -1, null, typeof(string), string.Empty, string.Empty, new List<string>()),
new OptionSpecification("i", string.Empty, false, string.Empty, 3, 4, null, typeof(IEnumerable<int>), string.Empty, string.Empty, new List<string>())
new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, -1, -1, null, false, typeof(string), string.Empty, string.Empty, new List<string>()),
new OptionSpecification("i", string.Empty, false, string.Empty, 3, 4, null, false, typeof(IEnumerable<int>), string.Empty, string.Empty, new List<string>())
};

// Exercize system
Expand All @@ -46,8 +46,8 @@ public void Partition_sequence_returns_sequence_with_duplicates()
};
var specs =new[]
{
new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, -1, -1, null, typeof(string), string.Empty, string.Empty, null),
new OptionSpecification("i", string.Empty, false, string.Empty, 3, 4, null, typeof(IEnumerable<int>), string.Empty, string.Empty, null)
new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, -1, -1, null, false, typeof(string), string.Empty, string.Empty, null),
new OptionSpecification("i", string.Empty, false, string.Empty, 3, 4, null, false, typeof(IEnumerable<int>), string.Empty, string.Empty, null)
};

// Exercize system
Expand Down
132 changes: 131 additions & 1 deletion src/CommandLine.Tests/Unit/ParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public void Parameters_names_are_inferred()
var sut = new Parser();

// Exercize system
var result = sut.ParseArguments<FakeOptions>(new[] { "--stringvalue", "strvalue", "--intsequence", "1", "2", "3" });
var result = sut.ParseArguments<FakeOptions>(new[] { "--stringvalue", "strvalue", "-i", "1", "2", "3" });

// Verify outcome
result.Value.ShouldHave().AllProperties().EqualTo(expectedOptions);
Expand Down Expand Up @@ -227,5 +227,135 @@ public void Parse_verbs_using_generic_overload()
Assert.False(result.Errors.Any());
// Teardown
}

[Fact]
public void Parse_nullable_options()
{
// Fixture setup
var expectedOptions = new FakeOptionsWithNullable
{
NullableIntValue = 60,
NullableColorsValue = Colors.Red
};
var sut = new Parser();

// Exercize system
var result = sut.ParseArguments<FakeOptionsWithNullable>(new[] { "-n", "60", "-c", "Red" });

// Verify outcome
Assert.Empty(result.Errors);
result.Value.ShouldHave().AllProperties().EqualTo(expectedOptions);
// Teardown
}

[Fact]
public void Parse_check_empty_sequence()
{
// Fixture setup
var sut = new Parser();

// Exercize system
var result = sut.ParseArguments<FakeOptionsWithSequence>(new string[0]);

// Verify outcome
Assert.NotNull(result.Value.IntSequence);
Assert.Empty(result.Value.IntSequence);
// Teardown
}

[Fact]
public void Parse_check_nonempty_sequence()
{
// Fixture setup
var expectedOptions = new FakeOptionsWithSequence
{
IntSequence = new[] { 60, 120 }
};
var sut = new Parser();

// Exercize system
var result = sut.ParseArguments<FakeOptionsWithSequence>(new[] { "-i", "60", "120" });

// Verify outcome
Assert.Empty(result.Errors);
result.Value.ShouldHave().AllProperties().EqualTo(expectedOptions);
// Teardown
}

[Fact]
public void Parse_allow_null_default_value()
{
// Fixture setup
var sut = new Parser();

// Exercize system
var result = sut.ParseArguments<FakeOptionsWithNullDefault>(new string[0]);

// Verify outcome
Assert.Null(result.Value.IntSequence);
// Teardown
}

[Fact]
public void Parse_reject_sequence_without_values()
{
// Fixture setup
var sut = new Parser();

// Exercize system
var result = sut.ParseArguments<FakeOptionsWithSequence>(new[] { "-i" });

// Verify outcome
Assert.NotEmpty(result.Errors);
// Teardown
}

[Fact]
public void Parse_allow_min_equal_zero_empty()
{
// Fixture setup
var sut = new Parser();

// Exercize system
var result = sut.ParseArguments<FakeOptionsWithSequenceWithMinZero>(new[] { "-i" });

// Verify outcome
Assert.NotNull(result.Value.IntSequence);
Assert.Empty(result.Value.IntSequence);
// Teardown
}

[Fact]
public void Parse_allow_min_equal_zero_nonempty()
{
// Fixture setup
var expectedOptions = new FakeOptionsWithSequence
{
IntSequence = new[] { 60, 120 }
};
var sut = new Parser();

// Exercize system
var result = sut.ParseArguments<FakeOptionsWithSequenceWithMinZero>(new[] { "-i", "60", "120" });

// Verify outcome
Assert.Empty(result.Errors);
result.Value.ShouldHave().AllProperties().EqualTo(expectedOptions);
// Teardown
}

[Fact]
public void Parse_allow_set_option_with_non_set()
{
// Fixture setup
var sut = new Parser();

// Exercize system
var result = sut.ParseArguments<FakeOptionsWithSetAndNonSet>(new[] { "-s", "-n" });

// Verify outcome
Assert.Empty(result.Errors);
// Teardown
}
}
}
15 changes: 14 additions & 1 deletion src/CommandLine/Core/InstanceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,25 +65,38 @@ public static ParserResult<T> Build<T>(

var valueSpecProps = ValueMapper.MapValues(
(from pt in specProps where pt.Specification.IsValue() select pt),
partitions.Item2,
partitions.Item2,
(vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, parsingCulture));

var missingValueErrors = from token in partitions.Item3
let optionSpec = optionSpecs.Single(o => token.Text.MatchName(o.ShortName, o.LongName, nameComparer))
where !(optionSpec.ConversionType.ToDescriptor() == DescriptorType.Sequence && optionSpec.Min == 0)
select new MissingValueOptionError(
NameInfo.FromOptionSpecification(optionSpecs.Single(o => token.Text.MatchName(o.ShortName, o.LongName, nameComparer))));

var emptyOptionSpecProps = from sp in optionSpecProps.Value
let o = (OptionSpecification) sp.Specification
where tokens.Count(token => token.Text.MatchName(o.ShortName, o.LongName, nameComparer)) == 1
&& o.ConversionType.ToDescriptor() == DescriptorType.Sequence
&& o.Min == 0
select sp;

var specPropsWithValue = optionSpecProps.Value.Concat(valueSpecProps.Value);

instance = instance
.SetProperties(specPropsWithValue,
sp => sp.Value.IsJust(),
sp => sp.Value.FromJust())
.SetProperties(emptyOptionSpecProps,
sp => sp.Value.IsNothing(),
sp => sp.Property.PropertyType.GetGenericArguments().Single().CreateEmptyArray())
.SetProperties(specPropsWithValue,
sp => sp.Value.IsNothing() && sp.Specification.DefaultValue.IsJust(),
sp => sp.Specification.DefaultValue.FromJust())
.SetProperties(specPropsWithValue,
sp => sp.Value.IsNothing()
&& sp.Specification.ConversionType.ToDescriptor() == DescriptorType.Sequence
&& !sp.Specification.DefaulSpecified
&& sp.Specification.DefaultValue.MatchNothing(),
sp => sp.Property.PropertyType.GetGenericArguments().Single().CreateEmptyArray());

Expand Down
5 changes: 3 additions & 2 deletions src/CommandLine/Core/OptionSpecification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ internal sealed class OptionSpecification : Specification
private readonly string metaValue;
private readonly System.Collections.Generic.IEnumerable<string> enumValues;

public OptionSpecification(string shortName, string longName, bool required, string setName, int min, int max, Maybe<object> defaultValue, System.Type conversionType, string helpText, string metaValue, System.Collections.Generic.IEnumerable<string> enumValues)
: base(SpecificationType.Option, required, min, max, defaultValue, conversionType)
public OptionSpecification(string shortName, string longName, bool required, string setName, int min, int max, Maybe<object> defaultValue, bool defaultSpecified, System.Type conversionType, string helpText, string metaValue, System.Collections.Generic.IEnumerable<string> enumValues)
: base(SpecificationType.Option, required, min, max, defaultValue, defaultSpecified, conversionType)
{
this.shortName = shortName;
this.longName = longName;
Expand All @@ -35,6 +35,7 @@ public static OptionSpecification FromAttribute(OptionAttribute attribute, Syste
attribute.Min,
attribute.Max,
attribute.DefaultValue.ToMaybe(),
attribute.DefaultSpecified,
conversionType,
attribute.HelpText,
attribute.MetaValue,
Expand Down
9 changes: 8 additions & 1 deletion src/CommandLine/Core/Specification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,20 @@ internal abstract class Specification
private readonly int min;
private readonly int max;
private readonly Maybe<object> defaultValue;
private readonly bool defaultSpecified;
/// <summary>
/// This information is denormalized to decouple Specification from PropertyInfo.
/// </summary>
private readonly System.Type conversionType;

protected Specification(SpecificationType tag, bool required, int min, int max, Maybe<object> defaultValue, System.Type conversionType)
protected Specification(SpecificationType tag, bool required, int min, int max, Maybe<object> defaultValue, bool defaultSpecified, System.Type conversionType)
{
this.tag = tag;
this.required = required;
this.min = min;
this.max = max;
this.defaultValue = defaultValue;
this.defaultSpecified = defaultSpecified;
this.conversionType = conversionType;
}

Expand Down Expand Up @@ -60,6 +62,11 @@ public Maybe<object> DefaultValue
get { return this.defaultValue; }
}

public bool DefaulSpecified
{
get { return defaultSpecified; }
}

public System.Type ConversionType
{
get { return this.conversionType; }
Expand Down
1 change: 1 addition & 0 deletions src/CommandLine/Core/SpecificationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public static OptionSpecification WithLongName(this OptionSpecification specific
specification.Min,
specification.Max,
specification.DefaultValue,
specification.DefaulSpecified,
specification.ConversionType,
specification.HelpText,
specification.MetaValue,
Expand Down
2 changes: 1 addition & 1 deletion src/CommandLine/Core/SpecificationGuards.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ private static Func<Specification, bool> GuardAgainstScalarWithRange()

private static Func<Specification, bool> GuardAgainstSequenceWithWrongRange()
{
return spec => spec.ConversionType.ToDescriptor() == DescriptorType.Sequence && spec.Min > spec.Max;
return spec => spec.ConversionType.ToDescriptor() == DescriptorType.Sequence && (spec.Min > spec.Max && spec.Min > 0);
}

private static Func<Specification, bool> GuardAgainstOneCharLongName()
Expand Down
2 changes: 1 addition & 1 deletion src/CommandLine/Core/SpecificationPropertyRules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ private static Func<IEnumerable<SpecificationProperty>, IEnumerable<Maybe<Error>
{
return specProps =>
{
var options = specProps.Where(sp => sp.Specification.IsOption() && sp.Value.IsJust());
var options = specProps.Where(sp => sp.Specification.IsOption() && sp.Value.IsJust() && !string.IsNullOrEmpty(((OptionSpecification)sp.Specification).SetName));
var groups = options.GroupBy(g => ((OptionSpecification)g.Specification).SetName);
if (groups.Count() > 1)
{
Expand Down
4 changes: 2 additions & 2 deletions src/CommandLine/Core/TokenPartitioner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ private static IEnumerable<Token> PartitionSequences(
Func<string, Maybe<Tuple<DescriptorType, Maybe<int>>>> typeLookup)
{
return from tseq in tokens.Pairwise(
(f, s) =>
(f, s) =>
f.IsName() && s.IsValue()
? typeLookup(f.Text).Return(info =>
info.Item1 == DescriptorType.Sequence
? new[] { f }.Concat(tokens.SkipWhile(t => t.Equals(f)).TakeWhile(v => v.IsValue()).Take(MaybeExtensions.Return(info.Item2, items => items, 0)))
? new[] { f }.Concat(tokens.SkipWhile(t => t.Equals(f)).TakeWhile(v => v.IsValue()).Take(MaybeExtensions.Return(info.Item2, items => items, int.MaxValue)))
: new Token[] { } , new Token[] { })
: new Token[] {})
from t in tseq
Expand Down
2 changes: 2 additions & 0 deletions src/CommandLine/Core/TypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ private static Maybe<object> ChangeType(string value, Type conversionType, Cultu
{
try
{
conversionType = Nullable.GetUnderlyingType(conversionType) ?? conversionType;

return Maybe.Just(
MatchBoolString(value)
? ConvertBoolString(value)
Expand Down
2 changes: 1 addition & 1 deletion src/CommandLine/Core/TypeLookup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static Maybe<Tuple<DescriptorType, Maybe<int>>> GetDescriptorInfo(
.ToMaybe()
.Map(
s => Tuple.Create(
s.ConversionType.ToDescriptor(), (s.Min < 0 && s.Max < 0) ? Maybe.Nothing<int>() : Maybe.Just(s.Max)));
s.ConversionType.ToDescriptor(), (s.Max < 0) ? Maybe.Nothing<int>() : Maybe.Just(s.Max)));
}
}
}
Loading