Skip to content

Commit 3d3d967

Browse files
Remove constraint on T for ParseArguments with factory (#590)
* Remove constraint on T for ParseArguments with factory * Make it possible to use factory for classes without public empty constructor * Allow types with explicitly defined interface implementations * Add test for #70 * Make sure explicit interface implementations can be parsed * Add test to make sure parser can detect explicit interface implementations
1 parent 30539a5 commit 3d3d967

File tree

9 files changed

+124
-6
lines changed

9 files changed

+124
-6
lines changed

Diff for: src/CommandLine/Core/InstanceBuilder.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public static ParserResult<T> Build<T>(
3939

4040
Func<T> makeDefault = () =>
4141
typeof(T).IsMutable()
42-
? factory.MapValueOrDefault(f => f(), Activator.CreateInstance<T>())
42+
? factory.MapValueOrDefault(f => f(), () => Activator.CreateInstance<T>())
4343
: ReflectionHelper.CreateDefaultImmutableInstance<T>(
4444
(from p in specProps select p.Specification.ConversionType).ToArray());
4545

@@ -128,7 +128,7 @@ public static ParserResult<T> Build<T>(
128128

129129
private static T BuildMutable<T>(Maybe<Func<T>> factory, IEnumerable<SpecificationProperty> specPropsWithValue, List<Error> setPropertyErrors )
130130
{
131-
var mutable = factory.MapValueOrDefault(f => f(), Activator.CreateInstance<T>());
131+
var mutable = factory.MapValueOrDefault(f => f(), () => Activator.CreateInstance<T>());
132132

133133
setPropertyErrors.AddRange(
134134
mutable.SetProperties(

Diff for: src/CommandLine/Core/ReflectionExtensions.cs

+14-3
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,21 @@ public static bool IsMutable(this Type type)
133133
if(type == typeof(object))
134134
return true;
135135

136-
var props = type.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(p => p.CanWrite);
137-
var fields = type.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Any();
136+
// Find all inherited defined properties and fields on the type
137+
var inheritedTypes = type.GetTypeInfo().FlattenHierarchy().Select(i => i.GetTypeInfo());
138138

139-
return props || fields;
139+
foreach (var inheritedType in inheritedTypes)
140+
{
141+
if (
142+
inheritedType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(p => p.CanWrite) ||
143+
inheritedType.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Any()
144+
)
145+
{
146+
return true;
147+
}
148+
}
149+
150+
return false;
140151
}
141152

142153
public static object CreateDefaultForImmutable(this Type type)

Diff for: src/CommandLine/Infrastructure/CSharpx/Maybe.cs

+8
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,14 @@ public static T2 MapValueOrDefault<T1, T2>(this Maybe<T1> maybe, Func<T1, T2> fu
371371
return maybe.MatchJust(out value1) ? func(value1) : noneValue;
372372
}
373373

374+
/// <summary>
375+
/// If contains a values executes a mapping function over it, otherwise returns the value from <paramref name="noneValueFactory"/>.
376+
/// </summary>
377+
public static T2 MapValueOrDefault<T1, T2>(this Maybe<T1> maybe, Func<T1, T2> func, Func<T2> noneValueFactory) {
378+
T1 value1;
379+
return maybe.MatchJust(out value1) ? func(value1) : noneValueFactory();
380+
}
381+
374382
/// <summary>
375383
/// Returns an empty list when given <see cref="CSharpx.Nothing{T}"/> or a singleton list when given a <see cref="CSharpx.Just{T}"/>.
376384
/// </summary>

Diff for: src/CommandLine/Parser.cs

-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ public ParserResult<T> ParseArguments<T>(IEnumerable<string> args)
116116
/// and a sequence of <see cref="CommandLine.Error"/>.</returns>
117117
/// <exception cref="System.ArgumentNullException">Thrown if one or more arguments are null.</exception>
118118
public ParserResult<T> ParseArguments<T>(Func<T> factory, IEnumerable<string> args)
119-
where T : new()
120119
{
121120
if (factory == null) throw new ArgumentNullException("factory");
122121
if (!typeof(T).IsMutable()) throw new ArgumentException("factory");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information.
2+
3+
namespace CommandLine.Tests.Fakes
4+
{
5+
class Mutable_Without_Empty_Constructor
6+
{
7+
[Option("amend", HelpText = "Used to amend the tip of the current branch.")]
8+
public bool Amend { get; set; }
9+
10+
private Mutable_Without_Empty_Constructor()
11+
{
12+
}
13+
14+
public static Mutable_Without_Empty_Constructor Create()
15+
{
16+
return new Mutable_Without_Empty_Constructor();
17+
}
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace CommandLine.Tests.Fakes
2+
{
3+
class Options_With_Only_Explicit_Interface : IInterface_With_Two_Scalar_Options
4+
{
5+
bool IInterface_With_Two_Scalar_Options.Verbose { get; set; }
6+
7+
string IInterface_With_Two_Scalar_Options.InputFile { get; set; }
8+
}
9+
}

Diff for: tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs

+14
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,20 @@ public void Specifying_options_two_or_more_times_with_mixed_short_long_options_g
787787
((NotParsed<Simple_Options>)result).Errors.Should().HaveCount(x => x == expected);
788788
}
789789

790+
[Theory]
791+
[InlineData(new[] { "--inputfile=file1.bin" }, "file1.bin")]
792+
[InlineData(new[] { "--inputfile", "file2.txt" }, "file2.txt")]
793+
public void Can_define_options_on_explicit_interface_properties(string[] arguments, string expected)
794+
{
795+
// Exercize system
796+
var result = InvokeBuild<Options_With_Only_Explicit_Interface>(
797+
arguments);
798+
799+
// Verify outcome
800+
expected.Should().BeEquivalentTo(((IInterface_With_Two_Scalar_Options)((Parsed<Options_With_Only_Explicit_Interface>)result).Value).InputFile);
801+
}
802+
803+
790804
[Theory]
791805
[InlineData(new[] { "--inputfile=file1.bin" }, "file1.bin")]
792806
[InlineData(new[] { "--inputfile", "file2.txt" }, "file2.txt")]

Diff for: tests/CommandLine.Tests/Unit/Issue591ests.cs

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System.Linq;
2+
using CommandLine.Tests.Fakes;
3+
using CommandLine.Text;
4+
using FluentAssertions;
5+
using Xunit;
6+
using Xunit.Abstractions;
7+
8+
//Issue #591
9+
//When options class is only having explicit interface declarations, it should be detected as mutable.
10+
11+
namespace CommandLine.Tests.Unit
12+
{
13+
public class Issue591ests
14+
{
15+
[Fact]
16+
public void Parse_option_with_only_explicit_interface_implementation()
17+
{
18+
string actual = string.Empty;
19+
20+
var arguments = new[] { "--inputfile", "file2.txt" };
21+
var result = Parser.Default.ParseArguments<Options_With_Only_Explicit_Interface>(arguments);
22+
result.WithParsed(options => {
23+
actual = ((IInterface_With_Two_Scalar_Options)options).InputFile;
24+
});
25+
26+
actual.Should().Be("file2.txt");
27+
}
28+
}
29+
}

Diff for: tests/CommandLine.Tests/Unit/Issue70Tests.cs

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System.Linq;
2+
using CommandLine.Tests.Fakes;
3+
using CommandLine.Text;
4+
using FluentAssertions;
5+
using Xunit;
6+
using Xunit.Abstractions;
7+
8+
//Issue #70
9+
//When the factory overload is used for ParseArguments, there should be no constraint not having an empty constructor.
10+
11+
namespace CommandLine.Tests.Unit
12+
{
13+
public class Issue70Tests
14+
{
15+
[Fact]
16+
public void Create_instance_with_factory_method_should_not_fail()
17+
{
18+
bool actual = false;
19+
20+
var arguments = new[] { "--amend" };
21+
var result = Parser.Default.ParseArguments(() => Mutable_Without_Empty_Constructor.Create(), arguments);
22+
result.WithParsed(options => {
23+
actual = options.Amend;
24+
});
25+
26+
actual.Should().BeTrue();
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)