Skip to content

Commit

Permalink
Merge branch 'master' into net40-net45
Browse files Browse the repository at this point in the history
  • Loading branch information
moh-hassan committed Apr 26, 2019
2 parents 4295f74 + 030eb8d commit 8d89898
Show file tree
Hide file tree
Showing 12 changed files with 245 additions and 128 deletions.
26 changes: 26 additions & 0 deletions .editorconfig
@@ -0,0 +1,26 @@
#top-most EditorConfig for project
root = true

[*]
end_of_line = crlf
insert_final_newline = true

[*.cs]
indent_style = space
indent_size = 4

[*.{xml,csproj,config}]
indent_style = tab
indent_size = 4

[*.json]
indent_style = space
indent_size = 2

[*.js]
indent_style = space
indent_size = 2

[*.yml]
indent_style = space
indent_size = 2
9 changes: 7 additions & 2 deletions CommandLine.sln
Expand Up @@ -3,9 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27703.2042
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandLine", "src\CommandLine\CommandLine.csproj", "{E1BD3C65-49C3-49E7-BABA-C60980CB3F20}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommandLine", "src\CommandLine\CommandLine.csproj", "{E1BD3C65-49C3-49E7-BABA-C60980CB3F20}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandLine.Tests", "tests\CommandLine.Tests\CommandLine.Tests.csproj", "{0A15C4D2-B3E9-43AB-8155-1B39F7AC8A5E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommandLine.Tests", "tests\CommandLine.Tests\CommandLine.Tests.csproj", "{0A15C4D2-B3E9-43AB-8155-1B39F7AC8A5E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{1361E8B1-D0E1-493E-B8C1-7380A7B7C472}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -25,6 +27,9 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0A15C4D2-B3E9-43AB-8155-1B39F7AC8A5E} = {1361E8B1-D0E1-493E-B8C1-7380A7B7C472}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5B5A476C-82FB-49FB-B592-5224D9005186}
EndGlobalSection
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -27,7 +27,7 @@ __This library provides _hassle free_ command line parsing with a constantly upd
- Supports `--help`, `--version`, `version` and `help [verb]` by default.
- Map to sequences (via `IEnumerable<T>` and similar) and scalar types, including Enums and `Nullable<T>`.
- You can also map to every type with a constructor that accepts a string (like `System.Uri`).
- Define [verb commands](https://github.com/commandlineparser/commandline/wiki#verbs) similar to `git commit -a`.
- Define [verb commands](https://github.com/commandlineparser/commandline/wiki/Verbs) similar to `git commit -a`.
- Unparsing support: `CommandLine.Parser.Default.FormatCommandLine<T>(T options)`.
- CommandLineParser.FSharp package is F#-friendly with support for `option<'a>`, see [demo](https://github.com/commandlineparser/commandline/blob/master/demo/fsharp-demo.fsx). _NOTE: This is a separate NuGet package._
- Most of features applies with a [CoC](http://en.wikipedia.org/wiki/Convention_over_configuration) philosophy.
Expand Down
18 changes: 14 additions & 4 deletions appveyor.yml
@@ -1,5 +1,7 @@
#version should be only changed with RELEASE eminent, see RELEASE.md
version: 2.3.{build}
version: 2.4.{build}

image: Visual Studio 2017

clone_depth: 1
pull_requests:
Expand Down Expand Up @@ -31,18 +33,26 @@ after_test:

artifacts:
- path: 'src/CommandLine/bin/Release/*.nupkg'
name: NugetPackages
name: NuGetPackages

on_failure:
- cmd: |
tree /f /a >files.lst
appveyor PushArtifact .\files.lst -DeploymentName "Failed Build File Listing"
deploy:
- provider: GitHub
auth_token:
secure: hVyVwHl0JiVq0VxXB4VMRWbUtrGclIzadfnWFcWCQBLvbgMLahLBnWlwGglT63pZ
artifact: 'NuGetPackages'
prerelease: false
force_update: true #fsharp package runs as separate build job, so have to force_update to add fsharp.nuget added
on:
APPVEYOR_REPO_TAG: true

- provider: NuGet
api_key:
secure: +Zxb8M5W+UJV1yd9n8seu3PvH/hGNPEmgriGBnsSmtxjKPQAJ4+iL7tKAmfPHAuG
secure: Ab4T/48EyIJhVrqkfKdUxmHUtseEVuXuyrGACxZ0KN35rb/BzABlBM2YjZojicvT
artifact: 'NuGetPackages'
on:
APPVEYOR_REPO_TAG: true

4 changes: 2 additions & 2 deletions src/CommandLine/CommandLine.csproj
Expand Up @@ -14,12 +14,12 @@
<Authors>gsscoder;nemec;ericnewton76</Authors>
<Title>Command Line Parser Library</Title>
<Version Condition="'$(VersionSuffix)' != ''">$(VersionSuffix)</Version>
<Version Condition="'$(VersionSuffix)' == ''">2.5.0-Dev</Version>
<Version Condition="'$(VersionSuffix)' == ''">0.0.0</Version>
<Description Condition="'$(BuildTarget)' != 'fsharp'">Terse syntax C# command line parser for .NET. For FSharp support see CommandLineParser.FSharp. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks.</Description>
<Description Condition="'$(BuildTarget)' == 'fsharp'">Terse syntax C# command line parser for .NET with F# support. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks.</Description>
<Copyright>Copyright (c) 2005 - 2018 Giacomo Stelluti Scala &amp; Contributors</Copyright>
<PackageLicenseUrl>https://raw.githubusercontent.com/gsscoder/commandline/master/doc/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/gsscoder/commandline</PackageProjectUrl>
<PackageProjectUrl>https://github.com/commandlineparser/commandline</PackageProjectUrl>
<PackageIconUrl>https://raw.githubusercontent.com/commandlineparser/commandline/master/art/CommandLine20.png</PackageIconUrl>
<PackageTags>command line;commandline;argument;option;parser;parsing;library;syntax;shell</PackageTags>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
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;
}

}
}
}
26 changes: 20 additions & 6 deletions src/CommandLine/Core/ReflectionExtensions.cs
Expand Up @@ -14,6 +14,8 @@ namespace CommandLine.Core
{
static class ReflectionExtensions
{
public const string CannotSetValueToTargetInstance = "Cannot set value to target instance.";

public static IEnumerable<T> GetSpecifications<T>(this Type type, Func<PropertyInfo, T> selector)
{
return from pi in type.FlattenHierarchy().SelectMany(x => x.GetTypeInfo().GetProperties())
Expand Down Expand Up @@ -91,6 +93,10 @@ public static TargetType ToTargetType(this Type type)

private static IEnumerable<Error> SetValue<T>(this SpecificationProperty specProp, T instance, object value)
{
Action<Exception> fail = inner => {
throw new InvalidOperationException(CannotSetValueToTargetInstance, inner);
};

try
{
specProp.Property.SetValue(instance, value, null);
Expand All @@ -104,6 +110,13 @@ private static IEnumerable<Error> SetValue<T>(this SpecificationProperty specPro
{
return new[] { new SetValueExceptionError(specProp.Specification.FromSpecification(), e, value) };
}
catch(ArgumentException e)
{
var argEx = new ArgumentException(InvalidAttributeConfigurationError.ErrorMessage, e);
fail(argEx);
}

return instance;
}

public static object CreateEmptyArray(this Type type)
Expand All @@ -122,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
8 changes: 7 additions & 1 deletion src/CommandLine/Error.cs
Expand Up @@ -64,7 +64,13 @@ public enum ErrorType
/// <summary>
/// Value of <see cref="CommandLine.SetValueExceptionError"/> type.
/// </summary>
SetValueExceptionError
SetValueExceptionError,

VersionRequestedError,
/// <summary>
/// Value of <see cref="CommandLine.InvalidAttributeConfigurationError"/> type.
/// </summary>
InvalidAttributeConfigurationError
}

/// <summary>
Expand Down
7 changes: 2 additions & 5 deletions src/CommandLine/Infrastructure/ReflectionHelper.cs
Expand Up @@ -84,11 +84,8 @@ public static bool IsFSharpOptionType(Type type)

public static T CreateDefaultImmutableInstance<T>(Type[] constructorTypes)
{
var t = typeof(T);
var ctor = t.GetTypeInfo().GetConstructor(constructorTypes);
var values = (from prms in ctor.GetParameters()
select prms.ParameterType.CreateDefaultForImmutable()).ToArray();
return (T)ctor.Invoke(values);
var t = typeof(T);
return (T)CreateDefaultImmutableInstance(t, constructorTypes);
}

public static object CreateDefaultImmutableInstance(Type type, Type[] constructorTypes)
Expand Down
11 changes: 11 additions & 0 deletions tests/CommandLine.Tests/Fakes/Options_With_InvalidDefaults.cs
@@ -0,0 +1,11 @@

namespace CommandLine.Tests.Fakes
{
class Options_With_InvalidDefaults
{
// Default of string and integer type property will also throw.

[Option(Default = false)]
public string FileName { get; set; }
}
}
32 changes: 31 additions & 1 deletion tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs
Expand Up @@ -1133,7 +1133,37 @@ public void Parse_TimeSpan()
// Teardown
}

public static IEnumerable<object[]> RequiredValueStringData
[Fact]
public void Build_DefaultBoolTypeString_ThrowsInvalidOperationException()
{
// Exercize system
Action test = () => InvokeBuild<Options_With_InvalidDefaults>(
new string[] { });

// Verify outcome
test.ShouldThrow<InvalidOperationException>()
.WithMessage(ReflectionExtensions.CannotSetValueToTargetInstance)
.WithInnerException<ArgumentException>()
.WithInnerMessage(InvalidAttributeConfigurationError.ErrorMessage);
}


[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

0 comments on commit 8d89898

Please sign in to comment.