Skip to content

Commit 5343c5d

Browse files
committed
Merge branch 'develop' of github.com:commandlineparser/commandline into develop
2 parents bd67c24 + 9f91088 commit 5343c5d

File tree

5 files changed

+147
-113
lines changed

5 files changed

+147
-113
lines changed

src/CommandLine/CommandLine.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<AssemblyName>CommandLine</AssemblyName>
55
<OutputType>Library</OutputType>
6-
<TargetFramework>netstandard2.0</TargetFramework>
6+
<TargetFrameworks>netstandard2.0; net461; netcoreapp2.0</TargetFrameworks>
77
<DefineConstants>$(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;ERRH_DISABLE_INLINE_METHODS;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC</DefineConstants>
88
<DefineConstants Condition="'$(BuildTarget)' != 'fsharp'">$(DefineConstants);SKIP_FSHARP</DefineConstants>
99
<GenerateDocumentationFile>true</GenerateDocumentationFile>

src/CommandLine/Core/InstanceBuilder.cs

Lines changed: 73 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -82,44 +82,19 @@ public static ParserResult<T> Build<T>(
8282
var specPropsWithValue =
8383
optionSpecPropsResult.SucceededWith().Concat(valueSpecPropsResult.SucceededWith()).Memorize();
8484

85-
var setPropertyErrors = new List<Error>();
85+
var setPropertyErrors = new List<Error>();
8686

87-
Func <T> buildMutable = () =>
87+
//build the instance, determining if the type is mutable or not.
88+
T instance;
89+
if(typeInfo.IsMutable() == true)
8890
{
89-
var mutable = factory.MapValueOrDefault(f => f(), Activator.CreateInstance<T>());
90-
setPropertyErrors.AddRange(mutable.SetProperties(specPropsWithValue, sp => sp.Value.IsJust(), sp => sp.Value.FromJustOrFail()));
91-
setPropertyErrors.AddRange(mutable.SetProperties(
92-
specPropsWithValue,
93-
sp => sp.Value.IsNothing() && sp.Specification.DefaultValue.IsJust(),
94-
sp => sp.Specification.DefaultValue.FromJustOrFail()));
95-
setPropertyErrors.AddRange(mutable.SetProperties(
96-
specPropsWithValue,
97-
sp =>
98-
sp.Value.IsNothing() && sp.Specification.TargetType == TargetType.Sequence
99-
&& sp.Specification.DefaultValue.MatchNothing(),
100-
sp => sp.Property.PropertyType.GetTypeInfo().GetGenericArguments().Single().CreateEmptyArray()));
101-
return mutable;
102-
};
103-
104-
Func<T> buildImmutable = () =>
91+
instance = BuildMutable(factory, specPropsWithValue, setPropertyErrors);
92+
}
93+
else
10594
{
106-
var ctor = typeInfo.GetTypeInfo().GetConstructor((from sp in specProps select sp.Property.PropertyType).ToArray());
107-
var values = (from prms in ctor.GetParameters()
108-
join sp in specPropsWithValue on prms.Name.ToLower() equals sp.Property.Name.ToLower() into spv
109-
from sp in spv.DefaultIfEmpty()
110-
select
111-
sp == null
112-
? specProps.First(s => String.Equals(s.Property.Name, prms.Name, StringComparison.CurrentCultureIgnoreCase))
113-
.Property.PropertyType.GetDefaultValue()
114-
: sp.Value.GetValueOrDefault(
115-
sp.Specification.DefaultValue.GetValueOrDefault(
116-
sp.Specification.ConversionType.CreateDefaultForImmutable()))).ToArray();
117-
var immutable = (T)ctor.Invoke(values);
118-
return immutable;
119-
};
120-
121-
var instance = typeInfo.IsMutable() ? buildMutable() : buildImmutable();
122-
95+
instance = BuildImmutable(typeInfo, factory, specProps, specPropsWithValue, setPropertyErrors);
96+
}
97+
12398
var validationErrors = specPropsWithValue.Validate(SpecificationPropertyRules.Lookup(tokens));
12499

125100
var allErrors =
@@ -150,5 +125,67 @@ from sp in spv.DefaultIfEmpty()
150125

151126
return result;
152127
}
128+
129+
private static T BuildMutable<T>(Maybe<Func<T>> factory, IEnumerable<SpecificationProperty> specPropsWithValue, List<Error> setPropertyErrors )
130+
{
131+
var mutable = factory.MapValueOrDefault(f => f(), Activator.CreateInstance<T>());
132+
133+
setPropertyErrors.AddRange(
134+
mutable.SetProperties(
135+
specPropsWithValue,
136+
sp => sp.Value.IsJust(),
137+
sp => sp.Value.FromJustOrFail()
138+
)
139+
);
140+
141+
setPropertyErrors.AddRange(
142+
mutable.SetProperties(
143+
specPropsWithValue,
144+
sp => sp.Value.IsNothing() && sp.Specification.DefaultValue.IsJust(),
145+
sp => sp.Specification.DefaultValue.FromJustOrFail()
146+
)
147+
);
148+
149+
setPropertyErrors.AddRange(
150+
mutable.SetProperties(
151+
specPropsWithValue,
152+
sp => sp.Value.IsNothing()
153+
&& sp.Specification.TargetType == TargetType.Sequence
154+
&& sp.Specification.DefaultValue.MatchNothing(),
155+
sp => sp.Property.PropertyType.GetTypeInfo().GetGenericArguments().Single().CreateEmptyArray()
156+
)
157+
);
158+
159+
return mutable;
160+
}
161+
162+
private static T BuildImmutable<T>(Type typeInfo, Maybe<Func<T>> factory, IEnumerable<SpecificationProperty> specProps, IEnumerable<SpecificationProperty> specPropsWithValue, List<Error> setPropertyErrors)
163+
{
164+
var ctor = typeInfo.GetTypeInfo().GetConstructor(
165+
specProps.Select(sp => sp.Property.PropertyType).ToArray()
166+
);
167+
168+
if(ctor == null)
169+
{
170+
throw new InvalidOperationException($"Type appears to be immutable, but no constructor found for type {typeInfo.FullName} to accept values.");
171+
}
172+
173+
var values =
174+
(from prms in ctor.GetParameters()
175+
join sp in specPropsWithValue on prms.Name.ToLower() equals sp.Property.Name.ToLower() into spv
176+
from sp in spv.DefaultIfEmpty()
177+
select
178+
sp == null
179+
? specProps.First(s => String.Equals(s.Property.Name, prms.Name, StringComparison.CurrentCultureIgnoreCase))
180+
.Property.PropertyType.GetDefaultValue()
181+
: sp.Value.GetValueOrDefault(
182+
sp.Specification.DefaultValue.GetValueOrDefault(
183+
sp.Specification.ConversionType.CreateDefaultForImmutable()))).ToArray();
184+
185+
var immutable = (T)ctor.Invoke(values);
186+
187+
return immutable;
188+
}
189+
153190
}
154-
}
191+
}

src/CommandLine/Core/ReflectionExtensions.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,13 @@ public static object GetDefaultValue(this Type type)
135135

136136
public static bool IsMutable(this Type type)
137137
{
138-
Func<bool> isMutable = () => {
139-
var props = type.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(p => p.CanWrite);
140-
var fields = type.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Any();
141-
return props || fields;
142-
};
143-
return type != typeof(object) ? isMutable() : true;
138+
if(type == typeof(object))
139+
return true;
140+
141+
var props = type.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(p => p.CanWrite);
142+
var fields = type.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Any();
143+
144+
return props || fields;
144145
}
145146

146147
public static object CreateDefaultForImmutable(this Type type)

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,6 +1148,21 @@ public void Build_DefaultBoolTypeString_ThrowsInvalidOperationException()
11481148
}
11491149

11501150

1151+
[Fact]
1152+
public void OptionClass_IsImmutable_HasNoCtor()
1153+
{
1154+
Action act = () => InvokeBuild<ValueWithNoSetterOptions>(new string[] { "Test" }, false, false);
1155+
1156+
act.Should().Throw<InvalidOperationException>();
1157+
}
1158+
1159+
private class ValueWithNoSetterOptions
1160+
{
1161+
[Value(0, MetaName = "Test", Default = 0)]
1162+
public int TestValue { get; }
1163+
}
1164+
1165+
11511166
public static IEnumerable<object> RequiredValueStringData
11521167
{
11531168
get

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

Lines changed: 51 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,24 @@
22

33
using System;
44
using System.Collections.Generic;
5-
using System.Globalization;
65
using System.Linq;
76
using System.Reflection;
8-
using CommandLine.Core;
97
using CommandLine.Infrastructure;
108
using CommandLine.Tests.Fakes;
11-
using CommandLine.Tests.Unit.Infrastructure;
129
using CommandLine.Text;
1310
using FluentAssertions;
1411
using Xunit;
1512
using System.Text;
1613

1714
namespace CommandLine.Tests.Unit.Text
1815
{
19-
public class HelpTextTests
16+
public class HelpTextTests : IDisposable
2017
{
18+
public void Dispose()
19+
{
20+
ReflectionHelper.SetAttributeOverride(null);
21+
}
22+
2123
[Fact]
2224
public void Create_empty_instance()
2325
{
@@ -573,91 +575,70 @@ public void Default_set_to_sequence_should_be_properly_printed()
573575
[Fact]
574576
public void AutoBuild_when_no_assembly_attributes()
575577
{
576-
try
577-
{
578-
string expectedCopyright = "Copyright (C) 1 author";
578+
string expectedCopyright = "Copyright (C) 1 author";
579579

580-
ReflectionHelper.SetAttributeOverride(new Attribute[0]);
580+
ReflectionHelper.SetAttributeOverride(new Attribute[0]);
581581

582-
ParserResult<Simple_Options> fakeResult = new NotParsed<Simple_Options>(
583-
TypeInfo.Create(typeof (Simple_Options)), new Error[0]);
584-
bool onErrorCalled = false;
585-
HelpText actualResult = HelpText.AutoBuild(fakeResult, ht =>
586-
{
587-
onErrorCalled = true;
588-
return ht;
589-
}, ex => ex);
590-
591-
onErrorCalled.Should().BeTrue();
592-
actualResult.Copyright.Should().Be(expectedCopyright);
593-
}
594-
finally
582+
ParserResult<Simple_Options> fakeResult = new NotParsed<Simple_Options>(
583+
TypeInfo.Create(typeof (Simple_Options)), new Error[0]);
584+
bool onErrorCalled = false;
585+
HelpText actualResult = HelpText.AutoBuild(fakeResult, ht =>
595586
{
596-
ReflectionHelper.SetAttributeOverride(null);
597-
}
587+
onErrorCalled = true;
588+
return ht;
589+
}, ex => ex);
590+
591+
onErrorCalled.Should().BeTrue();
592+
actualResult.Copyright.Should().Be(expectedCopyright);
598593
}
599594

600595
[Fact]
601596
public void AutoBuild_with_assembly_title_and_version_attributes_only()
602597
{
603-
try
604-
{
605-
string expectedTitle = "Title";
606-
string expectedVersion = "1.2.3.4";
598+
string expectedTitle = "Title";
599+
string expectedVersion = "1.2.3.4";
607600

608-
ReflectionHelper.SetAttributeOverride(new Attribute[]
609-
{
610-
new AssemblyTitleAttribute(expectedTitle),
611-
new AssemblyInformationalVersionAttribute(expectedVersion)
612-
});
613-
614-
ParserResult<Simple_Options> fakeResult = new NotParsed<Simple_Options>(
615-
TypeInfo.Create(typeof (Simple_Options)), new Error[0]);
616-
bool onErrorCalled = false;
617-
HelpText actualResult = HelpText.AutoBuild(fakeResult, ht =>
618-
{
619-
onErrorCalled = true;
620-
return ht;
621-
}, ex => ex);
622-
623-
onErrorCalled.Should().BeTrue();
624-
actualResult.Heading.Should().Be(string.Format("{0} {1}", expectedTitle, expectedVersion));
625-
}
626-
finally
601+
ReflectionHelper.SetAttributeOverride(new Attribute[]
602+
{
603+
new AssemblyTitleAttribute(expectedTitle),
604+
new AssemblyInformationalVersionAttribute(expectedVersion)
605+
});
606+
607+
ParserResult<Simple_Options> fakeResult = new NotParsed<Simple_Options>(
608+
TypeInfo.Create(typeof (Simple_Options)), new Error[0]);
609+
bool onErrorCalled = false;
610+
HelpText actualResult = HelpText.AutoBuild(fakeResult, ht =>
627611
{
628-
ReflectionHelper.SetAttributeOverride(null);
629-
}
612+
onErrorCalled = true;
613+
return ht;
614+
}, ex => ex);
615+
616+
onErrorCalled.Should().BeTrue();
617+
actualResult.Heading.Should().Be(string.Format("{0} {1}", expectedTitle, expectedVersion));
630618
}
631619

632620

633621
[Fact]
634622
public void AutoBuild_with_assembly_company_attribute_only()
635623
{
636-
try
637-
{
638-
string expectedCompany = "Company";
624+
string expectedCompany = "Company";
639625

640-
ReflectionHelper.SetAttributeOverride(new Attribute[]
641-
{
642-
new AssemblyCompanyAttribute(expectedCompany)
643-
});
626+
ReflectionHelper.SetAttributeOverride(new Attribute[]
627+
{
628+
new AssemblyCompanyAttribute(expectedCompany)
629+
});
644630

645-
ParserResult<Simple_Options> fakeResult = new NotParsed<Simple_Options>(
646-
TypeInfo.Create(typeof (Simple_Options)), new Error[0]);
647-
bool onErrorCalled = false;
648-
HelpText actualResult = HelpText.AutoBuild(fakeResult, ht =>
649-
{
650-
onErrorCalled = true;
651-
return ht;
652-
}, ex => ex);
653-
654-
onErrorCalled.Should().BeFalse(); // Other attributes have fallback logic
655-
actualResult.Copyright.Should().Be(string.Format("Copyright (C) {0} {1}", DateTime.Now.Year, expectedCompany));
656-
}
657-
finally
631+
ParserResult<Simple_Options> fakeResult = new NotParsed<Simple_Options>(
632+
TypeInfo.Create(typeof (Simple_Options)), new Error[0]);
633+
bool onErrorCalled = false;
634+
HelpText actualResult = HelpText.AutoBuild(fakeResult, ht =>
658635
{
659-
ReflectionHelper.SetAttributeOverride(null);
660-
}
636+
onErrorCalled = true;
637+
return ht;
638+
}, ex => ex);
639+
640+
onErrorCalled.Should().BeFalse(); // Other attributes have fallback logic
641+
actualResult.Copyright.Should().Be(string.Format("Copyright (C) {0} {1}", DateTime.Now.Year, expectedCompany));
661642
}
662643

663644
[Fact]

0 commit comments

Comments
 (0)