Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:commandlineparser/commandline in…
Browse files Browse the repository at this point in the history
…to develop
  • Loading branch information
ericnewton76 committed Feb 1, 2019
2 parents bd67c24 + 9f91088 commit 5343c5d
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 113 deletions.
2 changes: 1 addition & 1 deletion src/CommandLine/CommandLine.csproj
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<AssemblyName>CommandLine</AssemblyName>
<OutputType>Library</OutputType>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFrameworks>netstandard2.0; net461; netcoreapp2.0</TargetFrameworks>
<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>
<DefineConstants Condition="'$(BuildTarget)' != 'fsharp'">$(DefineConstants);SKIP_FSHARP</DefineConstants>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
Expand Down
109 changes: 73 additions & 36 deletions src/CommandLine/Core/InstanceBuilder.cs
Expand Up @@ -82,44 +82,19 @@ static class InstanceBuilder
var specPropsWithValue =
optionSpecPropsResult.SucceededWith().Concat(valueSpecPropsResult.SucceededWith()).Memorize();
var setPropertyErrors = new List<Error>();
var setPropertyErrors = new List<Error>();
Func <T> buildMutable = () =>
//build the instance, determining if the type is mutable or not.
T instance;
if(typeInfo.IsMutable() == true)
{
var mutable = factory.MapValueOrDefault(f => f(), Activator.CreateInstance<T>());
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 = () =>
instance = BuildMutable(factory, specPropsWithValue, setPropertyErrors);
}
else
{
var ctor = typeInfo.GetTypeInfo().GetConstructor((from sp in specProps select sp.Property.PropertyType).ToArray());
var values = (from prms in ctor.GetParameters()
join sp in specPropsWithValue on prms.Name.ToLower() equals sp.Property.Name.ToLower() into spv
from sp in spv.DefaultIfEmpty()
select
sp == null
? specProps.First(s => String.Equals(s.Property.Name, prms.Name, StringComparison.CurrentCultureIgnoreCase))
.Property.PropertyType.GetDefaultValue()
: sp.Value.GetValueOrDefault(
sp.Specification.DefaultValue.GetValueOrDefault(
sp.Specification.ConversionType.CreateDefaultForImmutable()))).ToArray();
var immutable = (T)ctor.Invoke(values);
return immutable;
};
var instance = typeInfo.IsMutable() ? buildMutable() : buildImmutable();
instance = BuildImmutable(typeInfo, factory, specProps, specPropsWithValue, setPropertyErrors);
}
var validationErrors = specPropsWithValue.Validate(SpecificationPropertyRules.Lookup(tokens));
var allErrors =
Expand Down Expand Up @@ -150,5 +125,67 @@ static class InstanceBuilder

return result;
}

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

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;
}

private static T BuildImmutable<T>(Type typeInfo, Maybe<Func<T>> factory, IEnumerable<SpecificationProperty> specProps, IEnumerable<SpecificationProperty> specPropsWithValue, List<Error> setPropertyErrors)
{
var ctor = typeInfo.GetTypeInfo().GetConstructor(
specProps.Select(sp => sp.Property.PropertyType).ToArray()
);

if(ctor == null)
{
throw new InvalidOperationException($"Type appears to be immutable, but no constructor found for type {typeInfo.FullName} to accept values.");
}

var values =
(from prms in ctor.GetParameters()
join sp in specPropsWithValue on prms.Name.ToLower() equals sp.Property.Name.ToLower() into spv
from sp in spv.DefaultIfEmpty()
select
sp == null
? specProps.First(s => String.Equals(s.Property.Name, prms.Name, StringComparison.CurrentCultureIgnoreCase))
.Property.PropertyType.GetDefaultValue()
: sp.Value.GetValueOrDefault(
sp.Specification.DefaultValue.GetValueOrDefault(
sp.Specification.ConversionType.CreateDefaultForImmutable()))).ToArray();

var immutable = (T)ctor.Invoke(values);

return immutable;
}

}
}
}
13 changes: 7 additions & 6 deletions src/CommandLine/Core/ReflectionExtensions.cs
Expand Up @@ -135,12 +135,13 @@ public static object GetDefaultValue(this Type type)

public static bool IsMutable(this Type type)
{
Func<bool> isMutable = () => {
var props = type.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(p => p.CanWrite);
var fields = type.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Any();
return props || fields;
};
return type != typeof(object) ? isMutable() : true;
if(type == typeof(object))
return true;

var props = type.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(p => p.CanWrite);
var fields = type.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Any();

return props || fields;
}

public static object CreateDefaultForImmutable(this Type type)
Expand Down
15 changes: 15 additions & 0 deletions tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs
Expand Up @@ -1148,6 +1148,21 @@ public void Build_DefaultBoolTypeString_ThrowsInvalidOperationException()
}


[Fact]
public void OptionClass_IsImmutable_HasNoCtor()
{
Action act = () => InvokeBuild<ValueWithNoSetterOptions>(new string[] { "Test" }, false, false);

act.Should().Throw<InvalidOperationException>();
}

private class ValueWithNoSetterOptions
{
[Value(0, MetaName = "Test", Default = 0)]
public int TestValue { get; }
}


public static IEnumerable<object> RequiredValueStringData
{
get
Expand Down
121 changes: 51 additions & 70 deletions tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs
Expand Up @@ -2,22 +2,24 @@

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using CommandLine.Core;
using CommandLine.Infrastructure;
using CommandLine.Tests.Fakes;
using CommandLine.Tests.Unit.Infrastructure;
using CommandLine.Text;
using FluentAssertions;
using Xunit;
using System.Text;

namespace CommandLine.Tests.Unit.Text
{
public class HelpTextTests
public class HelpTextTests : IDisposable
{
public void Dispose()
{
ReflectionHelper.SetAttributeOverride(null);
}

[Fact]
public void Create_empty_instance()
{
Expand Down Expand Up @@ -573,91 +575,70 @@ public void Default_set_to_sequence_should_be_properly_printed()
[Fact]
public void AutoBuild_when_no_assembly_attributes()
{
try
{
string expectedCopyright = "Copyright (C) 1 author";
string expectedCopyright = "Copyright (C) 1 author";

ReflectionHelper.SetAttributeOverride(new Attribute[0]);
ReflectionHelper.SetAttributeOverride(new Attribute[0]);

ParserResult<Simple_Options> fakeResult = new NotParsed<Simple_Options>(
TypeInfo.Create(typeof (Simple_Options)), new Error[0]);
bool onErrorCalled = false;
HelpText actualResult = HelpText.AutoBuild(fakeResult, ht =>
{
onErrorCalled = true;
return ht;
}, ex => ex);

onErrorCalled.Should().BeTrue();
actualResult.Copyright.Should().Be(expectedCopyright);
}
finally
ParserResult<Simple_Options> fakeResult = new NotParsed<Simple_Options>(
TypeInfo.Create(typeof (Simple_Options)), new Error[0]);
bool onErrorCalled = false;
HelpText actualResult = HelpText.AutoBuild(fakeResult, ht =>
{
ReflectionHelper.SetAttributeOverride(null);
}
onErrorCalled = true;
return ht;
}, ex => ex);

onErrorCalled.Should().BeTrue();
actualResult.Copyright.Should().Be(expectedCopyright);
}

[Fact]
public void AutoBuild_with_assembly_title_and_version_attributes_only()
{
try
{
string expectedTitle = "Title";
string expectedVersion = "1.2.3.4";
string expectedTitle = "Title";
string expectedVersion = "1.2.3.4";

ReflectionHelper.SetAttributeOverride(new Attribute[]
{
new AssemblyTitleAttribute(expectedTitle),
new AssemblyInformationalVersionAttribute(expectedVersion)
});

ParserResult<Simple_Options> fakeResult = new NotParsed<Simple_Options>(
TypeInfo.Create(typeof (Simple_Options)), new Error[0]);
bool onErrorCalled = false;
HelpText actualResult = HelpText.AutoBuild(fakeResult, ht =>
{
onErrorCalled = true;
return ht;
}, ex => ex);

onErrorCalled.Should().BeTrue();
actualResult.Heading.Should().Be(string.Format("{0} {1}", expectedTitle, expectedVersion));
}
finally
ReflectionHelper.SetAttributeOverride(new Attribute[]
{
new AssemblyTitleAttribute(expectedTitle),
new AssemblyInformationalVersionAttribute(expectedVersion)
});

ParserResult<Simple_Options> fakeResult = new NotParsed<Simple_Options>(
TypeInfo.Create(typeof (Simple_Options)), new Error[0]);
bool onErrorCalled = false;
HelpText actualResult = HelpText.AutoBuild(fakeResult, ht =>
{
ReflectionHelper.SetAttributeOverride(null);
}
onErrorCalled = true;
return ht;
}, ex => ex);

onErrorCalled.Should().BeTrue();
actualResult.Heading.Should().Be(string.Format("{0} {1}", expectedTitle, expectedVersion));
}


[Fact]
public void AutoBuild_with_assembly_company_attribute_only()
{
try
{
string expectedCompany = "Company";
string expectedCompany = "Company";

ReflectionHelper.SetAttributeOverride(new Attribute[]
{
new AssemblyCompanyAttribute(expectedCompany)
});
ReflectionHelper.SetAttributeOverride(new Attribute[]
{
new AssemblyCompanyAttribute(expectedCompany)
});

ParserResult<Simple_Options> fakeResult = new NotParsed<Simple_Options>(
TypeInfo.Create(typeof (Simple_Options)), new Error[0]);
bool onErrorCalled = false;
HelpText actualResult = HelpText.AutoBuild(fakeResult, ht =>
{
onErrorCalled = true;
return ht;
}, ex => ex);

onErrorCalled.Should().BeFalse(); // Other attributes have fallback logic
actualResult.Copyright.Should().Be(string.Format("Copyright (C) {0} {1}", DateTime.Now.Year, expectedCompany));
}
finally
ParserResult<Simple_Options> fakeResult = new NotParsed<Simple_Options>(
TypeInfo.Create(typeof (Simple_Options)), new Error[0]);
bool onErrorCalled = false;
HelpText actualResult = HelpText.AutoBuild(fakeResult, ht =>
{
ReflectionHelper.SetAttributeOverride(null);
}
onErrorCalled = true;
return ht;
}, ex => ex);

onErrorCalled.Should().BeFalse(); // Other attributes have fallback logic
actualResult.Copyright.Should().Be(string.Format("Copyright (C) {0} {1}", DateTime.Now.Year, expectedCompany));
}

[Fact]
Expand Down

0 comments on commit 5343c5d

Please sign in to comment.