Skip to content
Merged
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
30 changes: 16 additions & 14 deletions src/CommandLine/Core/InstanceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,23 @@ public static ParserResult<T> Build<T>(
var specPropsWithValue =
optionSpecPropsResult.SucceededWith().Concat(valueSpecPropsResult.SucceededWith());

Func<T> buildMutable = () =>
var setPropertyErrors = new List<Error>();

Func <T> buildMutable = () =>
{
var mutable = factory.MapValueOrDefault(f => f(), Activator.CreateInstance<T>());
mutable =
mutable.SetProperties(specPropsWithValue, sp => sp.Value.IsJust(), sp => sp.Value.FromJustOrFail())
.SetProperties(
specPropsWithValue,
sp => sp.Value.IsNothing() && sp.Specification.DefaultValue.IsJust(),
sp => sp.Specification.DefaultValue.FromJustOrFail())
.SetProperties(
specPropsWithValue,
sp =>
sp.Value.IsNothing() && sp.Specification.TargetType == TargetType.Sequence
&& sp.Specification.DefaultValue.MatchNothing(),
sp => sp.Property.PropertyType.GetTypeInfo().GetGenericArguments().Single().CreateEmptyArray());
return mutable;
setPropertyErrors.AddRange(mutable.SetProperties(specPropsWithValue, sp => sp.Value.IsJust(), sp => sp.Value.FromJustOrFail()));
setPropertyErrors.AddRange(mutable.SetProperties(
specPropsWithValue,
sp => sp.Value.IsNothing() && sp.Specification.DefaultValue.IsJust(),
sp => sp.Specification.DefaultValue.FromJustOrFail()));
setPropertyErrors.AddRange(mutable.SetProperties(
specPropsWithValue,
sp =>
sp.Value.IsNothing() && sp.Specification.TargetType == TargetType.Sequence
&& sp.Specification.DefaultValue.MatchNothing(),
sp => sp.Property.PropertyType.GetTypeInfo().GetGenericArguments().Single().CreateEmptyArray()));
return mutable;
};

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

var warnings = from e in allErrors where nonFatalErrors.Contains(e.Tag) select e;
Expand Down
39 changes: 9 additions & 30 deletions src/CommandLine/Core/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,51 +80,30 @@ public static TargetType ToTargetType(this Type type)
: TargetType.Scalar;
}

public static T SetProperties<T>(
public static IEnumerable<Error> SetProperties<T>(
this T instance,
IEnumerable<SpecificationProperty> specProps,
Func<SpecificationProperty, bool> predicate,
Func<SpecificationProperty, object> selector)
{
return specProps.Where(predicate).Aggregate(
instance,
(current, specProp) =>
{
specProp.Property.SetValue(current, selector(specProp));
return instance;
});
return specProps.Where(predicate).SelectMany(specProp => specProp.SetValue(instance, selector(specProp)));
}

private static T SetValue<T>(this PropertyInfo property, T instance, object value)
private static IEnumerable<Error> SetValue<T>(this SpecificationProperty specProp, T instance, object value)
{
Action<Exception> fail = inner => {
throw new InvalidOperationException("Cannot set value to target instance.", inner);
};

try
{
property.SetValue(instance, value, null);
}
#if !PLATFORM_DOTNET
catch (TargetException e)
{
fail(e);
specProp.Property.SetValue(instance, value, null);
return Enumerable.Empty<Error>();
}
#endif
catch (TargetParameterCountException e)
{
fail(e);
}
catch (MethodAccessException e)
catch (TargetInvocationException e)
{
fail(e);
return new[] { new SetValueExceptionError(specProp.Specification.FromSpecification(), e.InnerException, value) };
}
catch (TargetInvocationException e)
catch (Exception e)
{
fail(e);
return new[] { new SetValueExceptionError(specProp.Specification.FromSpecification(), e, value) };
}

return instance;
}

public static object CreateEmptyArray(this Type type)
Expand Down
39 changes: 38 additions & 1 deletion src/CommandLine/Error.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ public enum ErrorType
/// <summary>
/// Value of <see cref="CommandLine.VersionRequestedError"/> type.
/// </summary>
VersionRequestedError
VersionRequestedError,
/// <summary>
/// Value of <see cref="CommandLine.SetValueExceptionError"/> type.
/// </summary>
SetValueExceptionError
}

/// <summary>
Expand Down Expand Up @@ -471,4 +475,37 @@ internal VersionRequestedError()
{
}
}

/// <summary>
/// Models as error generated when exception is thrown at Property.SetValue
/// </summary>
public sealed class SetValueExceptionError : NamedError
{
private readonly Exception exception;
private readonly object value;

internal SetValueExceptionError(NameInfo nameInfo, Exception exception, object value)
: base(ErrorType.SetValueExceptionError, nameInfo)
{
this.exception = exception;
this.value = value;
}

/// <summary>
/// The expection thrown from Property.SetValue
/// </summary>
public Exception Exception
{
get { return exception; }
}

/// <summary>
/// The value that had to be set to the property
/// </summary>
public object Value
{
get { return value; }
}

}
}
3 changes: 3 additions & 0 deletions src/CommandLine/Text/SentenceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ public override Func<Error, string> FormatError
case ErrorType.RepeatedOptionError:
return "Option '".JoinTo(((RepeatedOptionError)error).NameInfo.NameText,
"' is defined multiple times.");
case ErrorType.SetValueExceptionError:
var setValueError = (SetValueExceptionError)error;
return "Error setting value to option '".JoinTo(setValueError.NameInfo.NameText, "': ", setValueError.Exception.Message);
}
throw new InvalidOperationException();
};
Expand Down
1 change: 1 addition & 0 deletions tests/CommandLine.Tests/CommandLine.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
<Compile Include="Fakes\Options_With_Default_Set_To_Sequence.cs" />
<Compile Include="Fakes\Options_With_Guid.cs" />
<Compile Include="Fakes\Options_With_Option_And_Value_Of_String_Type.cs" />
<Compile Include="Fakes\Options_With_Property_Throwing_Exception.cs" />
<Compile Include="Fakes\Options_With_SetName_That_Ends_With_Previous_SetName.cs" />
<Compile Include="Fakes\Options_With_Shuffled_Index_Values.cs" />
<Compile Include="Fakes\Options_With_Uri_And_SimpleType.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CommandLine.Tests.Fakes
{
class Options_With_Property_Throwing_Exception
{
private string optValue;

[Option('e')]
public string OptValue
{
get
{
return optValue;
}
set
{
if (value != "good")
throw new ArgumentException("Invalid value, only accept 'good' value");

optValue = value;
}
}
}
}
17 changes: 17 additions & 0 deletions tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,23 @@ public void Parse_to_type_with_single_string_ctor_builds_up_correct_instance()
// Teardown
}

[Fact]
public void Parse_option_with_exception_thrown_from_setter_generates_SetValueExceptionError()
{
// Fixture setup
var expectedResult = new[] { new SetValueExceptionError(new NameInfo("e", ""), new ArgumentException(), "bad") };

// Exercize system
var result = InvokeBuild<Options_With_Property_Throwing_Exception>(
new[] { "-e", "bad" });

// Verify outcome
((NotParsed<Options_With_Property_Throwing_Exception>)result).Errors.ShouldBeEquivalentTo(expectedResult);

// Teardown
}


[Theory]
[InlineData(new[] { "--stringvalue", "x-" }, "x-")]
[InlineData(new[] { "--stringvalue", "x--" }, "x--")]
Expand Down