Skip to content

Commit 83dad85

Browse files
committed
Merge branch 'develop' of github.com:commandlineparser/commandline into develop
2 parents 205775e + 77f4840 commit 83dad85

File tree

10 files changed

+133
-65
lines changed

10 files changed

+133
-65
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ let main args =
249249
| :? CommandLine.NotParsed<obj> -> 1
250250
```
251251

252-
# Contibutors
252+
# Contributors
253253
First off, _Thank you!_ All contributions are welcome.
254254

255255
Please consider sticking with the GNU getopt standard for command line parsing.

src/CommandLine/Core/InstanceBuilder.cs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,22 +80,23 @@ public static ParserResult<T> Build<T>(
8080
var specPropsWithValue =
8181
optionSpecPropsResult.SucceededWith().Concat(valueSpecPropsResult.SucceededWith());
8282

83-
Func<T> buildMutable = () =>
83+
var setPropertyErrors = new List<Error>();
84+
85+
Func <T> buildMutable = () =>
8486
{
8587
var mutable = factory.MapValueOrDefault(f => f(), Activator.CreateInstance<T>());
86-
mutable =
87-
mutable.SetProperties(specPropsWithValue, sp => sp.Value.IsJust(), sp => sp.Value.FromJustOrFail())
88-
.SetProperties(
89-
specPropsWithValue,
90-
sp => sp.Value.IsNothing() && sp.Specification.DefaultValue.IsJust(),
91-
sp => sp.Specification.DefaultValue.FromJustOrFail())
92-
.SetProperties(
93-
specPropsWithValue,
94-
sp =>
95-
sp.Value.IsNothing() && sp.Specification.TargetType == TargetType.Sequence
96-
&& sp.Specification.DefaultValue.MatchNothing(),
97-
sp => sp.Property.PropertyType.GetTypeInfo().GetGenericArguments().Single().CreateEmptyArray());
98-
return mutable;
88+
setPropertyErrors.AddRange(mutable.SetProperties(specPropsWithValue, sp => sp.Value.IsJust(), sp => sp.Value.FromJustOrFail()));
89+
setPropertyErrors.AddRange(mutable.SetProperties(
90+
specPropsWithValue,
91+
sp => sp.Value.IsNothing() && sp.Specification.DefaultValue.IsJust(),
92+
sp => sp.Specification.DefaultValue.FromJustOrFail()));
93+
setPropertyErrors.AddRange(mutable.SetProperties(
94+
specPropsWithValue,
95+
sp =>
96+
sp.Value.IsNothing() && sp.Specification.TargetType == TargetType.Sequence
97+
&& sp.Specification.DefaultValue.MatchNothing(),
98+
sp => sp.Property.PropertyType.GetTypeInfo().GetGenericArguments().Single().CreateEmptyArray()));
99+
return mutable;
99100
};
100101

101102
Func<T> buildImmutable = () =>
@@ -121,6 +122,7 @@ join sp in specPropsWithValue on prms.Name.ToLower() equals sp.Property.Name.ToL
121122
.Concat(optionSpecPropsResult.SuccessfulMessages())
122123
.Concat(valueSpecPropsResult.SuccessfulMessages())
123124
.Concat(validationErrors)
125+
.Concat(setPropertyErrors)
124126
.Memorize();
125127

126128
var warnings = from e in allErrors where nonFatalErrors.Contains(e.Tag) select e;

src/CommandLine/Core/ReflectionExtensions.cs

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -80,51 +80,30 @@ public static TargetType ToTargetType(this Type type)
8080
: TargetType.Scalar;
8181
}
8282

83-
public static T SetProperties<T>(
83+
public static IEnumerable<Error> SetProperties<T>(
8484
this T instance,
8585
IEnumerable<SpecificationProperty> specProps,
8686
Func<SpecificationProperty, bool> predicate,
8787
Func<SpecificationProperty, object> selector)
8888
{
89-
return specProps.Where(predicate).Aggregate(
90-
instance,
91-
(current, specProp) =>
92-
{
93-
specProp.Property.SetValue(current, selector(specProp));
94-
return instance;
95-
});
89+
return specProps.Where(predicate).SelectMany(specProp => specProp.SetValue(instance, selector(specProp)));
9690
}
9791

98-
private static T SetValue<T>(this PropertyInfo property, T instance, object value)
92+
private static IEnumerable<Error> SetValue<T>(this SpecificationProperty specProp, T instance, object value)
9993
{
100-
Action<Exception> fail = inner => {
101-
throw new InvalidOperationException("Cannot set value to target instance.", inner);
102-
};
103-
10494
try
10595
{
106-
property.SetValue(instance, value, null);
107-
}
108-
#if !PLATFORM_DOTNET
109-
catch (TargetException e)
110-
{
111-
fail(e);
96+
specProp.Property.SetValue(instance, value, null);
97+
return Enumerable.Empty<Error>();
11298
}
113-
#endif
114-
catch (TargetParameterCountException e)
115-
{
116-
fail(e);
117-
}
118-
catch (MethodAccessException e)
99+
catch (TargetInvocationException e)
119100
{
120-
fail(e);
101+
return new[] { new SetValueExceptionError(specProp.Specification.FromSpecification(), e.InnerException, value) };
121102
}
122-
catch (TargetInvocationException e)
103+
catch (Exception e)
123104
{
124-
fail(e);
105+
return new[] { new SetValueExceptionError(specProp.Specification.FromSpecification(), e, value) };
125106
}
126-
127-
return instance;
128107
}
129108

130109
public static object CreateEmptyArray(this Type type)

src/CommandLine/Error.cs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ public enum ErrorType
6060
/// <summary>
6161
/// Value of <see cref="CommandLine.VersionRequestedError"/> type.
6262
/// </summary>
63-
VersionRequestedError
63+
VersionRequestedError,
64+
/// <summary>
65+
/// Value of <see cref="CommandLine.SetValueExceptionError"/> type.
66+
/// </summary>
67+
SetValueExceptionError
6468
}
6569

6670
/// <summary>
@@ -471,4 +475,37 @@ internal VersionRequestedError()
471475
{
472476
}
473477
}
478+
479+
/// <summary>
480+
/// Models as error generated when exception is thrown at Property.SetValue
481+
/// </summary>
482+
public sealed class SetValueExceptionError : NamedError
483+
{
484+
private readonly Exception exception;
485+
private readonly object value;
486+
487+
internal SetValueExceptionError(NameInfo nameInfo, Exception exception, object value)
488+
: base(ErrorType.SetValueExceptionError, nameInfo)
489+
{
490+
this.exception = exception;
491+
this.value = value;
492+
}
493+
494+
/// <summary>
495+
/// The expection thrown from Property.SetValue
496+
/// </summary>
497+
public Exception Exception
498+
{
499+
get { return exception; }
500+
}
501+
502+
/// <summary>
503+
/// The value that had to be set to the property
504+
/// </summary>
505+
public object Value
506+
{
507+
get { return value; }
508+
}
509+
510+
}
474511
}

src/CommandLine/Text/HelpText.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,7 @@ internal static void AddLine(StringBuilder builder, string value, int maximumLen
640640
throw new ArgumentOutOfRangeException(nameof(value));
641641
}
642642

643-
value = value.Trim();
643+
value = value.TrimEnd();
644644

645645
builder.AppendWhen(builder.Length > 0, Environment.NewLine);
646646
do

src/CommandLine/Text/SentenceBuilder.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ public override Func<Error, string> FormatError
137137
case ErrorType.RepeatedOptionError:
138138
return "Option '".JoinTo(((RepeatedOptionError)error).NameInfo.NameText,
139139
"' is defined multiple times.");
140+
case ErrorType.SetValueExceptionError:
141+
var setValueError = (SetValueExceptionError)error;
142+
return "Error setting value to option '".JoinTo(setValueError.NameInfo.NameText, "': ", setValueError.Exception.Message);
140143
}
141144
throw new InvalidOperationException();
142145
};

tests/CommandLine.Tests/CommandLine.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
<Compile Include="Fakes\Options_With_Default_Set_To_Sequence.cs" />
6363
<Compile Include="Fakes\Options_With_Guid.cs" />
6464
<Compile Include="Fakes\Options_With_Option_And_Value_Of_String_Type.cs" />
65+
<Compile Include="Fakes\Options_With_Property_Throwing_Exception.cs" />
6566
<Compile Include="Fakes\Options_With_SetName_That_Ends_With_Previous_SetName.cs" />
6667
<Compile Include="Fakes\Options_With_Shuffled_Index_Values.cs" />
6768
<Compile Include="Fakes\Options_With_Uri_And_SimpleType.cs" />
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace CommandLine.Tests.Fakes
8+
{
9+
class Options_With_Property_Throwing_Exception
10+
{
11+
private string optValue;
12+
13+
[Option('e')]
14+
public string OptValue
15+
{
16+
get
17+
{
18+
return optValue;
19+
}
20+
set
21+
{
22+
if (value != "good")
23+
throw new ArgumentException("Invalid value, only accept 'good' value");
24+
25+
optValue = value;
26+
}
27+
}
28+
}
29+
}

tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,23 @@ public void Parse_to_type_with_single_string_ctor_builds_up_correct_instance()
984984
// Teardown
985985
}
986986

987+
[Fact]
988+
public void Parse_option_with_exception_thrown_from_setter_generates_SetValueExceptionError()
989+
{
990+
// Fixture setup
991+
var expectedResult = new[] { new SetValueExceptionError(new NameInfo("e", ""), new ArgumentException(), "bad") };
992+
993+
// Exercize system
994+
var result = InvokeBuild<Options_With_Property_Throwing_Exception>(
995+
new[] { "-e", "bad" });
996+
997+
// Verify outcome
998+
((NotParsed<Options_With_Property_Throwing_Exception>)result).Errors.ShouldBeEquivalentTo(expectedResult);
999+
1000+
// Teardown
1001+
}
1002+
1003+
9871004
[Theory]
9881005
[InlineData(new[] { "--stringvalue", "x-" }, "x-")]
9891006
[InlineData(new[] { "--stringvalue", "x--" }, "x--")]

tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -505,8 +505,8 @@ public void Invoke_AutoBuild_for_Options_with_Usage_returns_appropriate_formatte
505505
var helpText = HelpText.AutoBuild(fakeResult);
506506

507507
// Verify outcome
508-
var text = helpText.ToString();
509-
var lines = text.ToNotEmptyLines().TrimStringArray();
508+
var text = helpText.ToString();
509+
var lines = text.ToNotEmptyLines();
510510
#if !PLATFORM_DOTNET
511511
lines[0].Should().StartWithEquivalent("CommandLine");
512512
lines[1].Should().StartWithEquivalent("Copyright (c)");
@@ -516,28 +516,28 @@ public void Invoke_AutoBuild_for_Options_with_Usage_returns_appropriate_formatte
516516
lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation");
517517
#endif
518518
lines[2].ShouldBeEquivalentTo("ERROR(S):");
519-
lines[3].ShouldBeEquivalentTo("Token 'badtoken' is not recognized.");
519+
lines[3].ShouldBeEquivalentTo(" Token 'badtoken' is not recognized.");
520520
lines[4].ShouldBeEquivalentTo("USAGE:");
521521
lines[5].ShouldBeEquivalentTo("Normal scenario:");
522-
lines[6].ShouldBeEquivalentTo("mono testapp.exe --input file.bin --output out.bin");
522+
lines[6].ShouldBeEquivalentTo(" mono testapp.exe --input file.bin --output out.bin");
523523
lines[7].ShouldBeEquivalentTo("Logging warnings:");
524-
lines[8].ShouldBeEquivalentTo("mono testapp.exe -w --input file.bin");
524+
lines[8].ShouldBeEquivalentTo(" mono testapp.exe -w --input file.bin");
525525
lines[9].ShouldBeEquivalentTo("Logging errors:");
526-
lines[10].ShouldBeEquivalentTo("mono testapp.exe -e --input file.bin");
527-
lines[11].ShouldBeEquivalentTo("mono testapp.exe --errs --input=file.bin");
526+
lines[10].ShouldBeEquivalentTo(" mono testapp.exe -e --input file.bin");
527+
lines[11].ShouldBeEquivalentTo(" mono testapp.exe --errs --input=file.bin");
528528
lines[12].ShouldBeEquivalentTo("List:");
529-
lines[13].ShouldBeEquivalentTo("mono testapp.exe -l 1,2");
529+
lines[13].ShouldBeEquivalentTo(" mono testapp.exe -l 1,2");
530530
lines[14].ShouldBeEquivalentTo("Value:");
531-
lines[15].ShouldBeEquivalentTo("mono testapp.exe value");
532-
lines[16].ShouldBeEquivalentTo("-i, --input Set input file.");
533-
lines[17].ShouldBeEquivalentTo("-i, --output Set output file.");
534-
lines[18].ShouldBeEquivalentTo("--verbose Set verbosity level.");
535-
lines[19].ShouldBeEquivalentTo("-w, --warns Log warnings.");
536-
lines[20].ShouldBeEquivalentTo("-e, --errs Log errors.");
537-
lines[21].ShouldBeEquivalentTo("-l List.");
538-
lines[22].ShouldBeEquivalentTo("--help Display this help screen.");
539-
lines[23].ShouldBeEquivalentTo("--version Display version information.");
540-
lines[24].ShouldBeEquivalentTo("value pos. 0 Value.");
531+
lines[15].ShouldBeEquivalentTo(" mono testapp.exe value");
532+
lines[16].ShouldBeEquivalentTo(" -i, --input Set input file.");
533+
lines[17].ShouldBeEquivalentTo(" -i, --output Set output file.");
534+
lines[18].ShouldBeEquivalentTo(" --verbose Set verbosity level.");
535+
lines[19].ShouldBeEquivalentTo(" -w, --warns Log warnings.");
536+
lines[20].ShouldBeEquivalentTo(" -e, --errs Log errors.");
537+
lines[21].ShouldBeEquivalentTo(" -l List.");
538+
lines[22].ShouldBeEquivalentTo(" --help Display this help screen.");
539+
lines[23].ShouldBeEquivalentTo(" --version Display version information.");
540+
lines[24].ShouldBeEquivalentTo(" value pos. 0 Value.");
541541

542542
// Teardown
543543
}

0 commit comments

Comments
 (0)