Skip to content

Commit 3b825c3

Browse files
authored
Merge pull request #286 from e673/issue283
Implement new functionality suggested in Issue 283
2 parents f99cb9e + 2a1e13c commit 3b825c3

File tree

7 files changed

+113
-45
lines changed

7 files changed

+113
-45
lines changed

src/CommandLine/Core/InstanceBuilder.cs

+16-14
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

+9-30
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

+38-1
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/SentenceBuilder.cs

+3
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

+1
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" />
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

+17
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--")]

0 commit comments

Comments
 (0)