Skip to content

Commit 487aaeb

Browse files
committed
Merge branch 'develop' into ViktorHofer-netstandard20
Conflicts: .paket/Paket.Restore.targets paket.lock src/CommandLine/CommandLine.csproj src/CommandLine/Core/ReflectionExtensions.cs tests/CommandLine.Tests.Properties/CommandLine.Tests.Properties.csproj tests/CommandLine.Tests/CommandLine.Tests.csproj tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs
2 parents 36e7ba3 + 83dad85 commit 487aaeb

18 files changed

+300
-75
lines changed

README.md

+62-17
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,43 @@ You can utilize the parser library in several ways:
4646
1. Create a class to define valid options, and to receive the parsed options.
4747
2. Call ParseArguments with the args string array.
4848

49+
C# Quick Start:
50+
51+
```csharp
52+
using System;
53+
using CommandLine;
54+
55+
namespace QuickStart
56+
{
57+
class Program
58+
{
59+
public class Options
60+
{
61+
[Option('v', "verbose", Required = false, HelpText = "Set output to verbose messages.")]
62+
public bool Verbose { get; set; }
63+
}
64+
65+
static void Main(string[] args)
66+
{
67+
Parser.Default.ParseArguments<Options>(args)
68+
.WithParsed<Options>(o =>
69+
{
70+
if (o.Verbose)
71+
{
72+
Console.WriteLine($"Verbose output enabled. Current Arguments: -v {o.Verbose}");
73+
Console.WriteLine("Quick Start Example! App is in Verbose mode!");
74+
}
75+
else
76+
{
77+
Console.WriteLine($"Current Arguments: -v {o.Verbose}");
78+
Console.WriteLine("Quick Start Example!");
79+
}
80+
});
81+
}
82+
}
83+
}
84+
```
85+
4986
C# Examples:
5087

5188
```csharp
@@ -55,10 +92,14 @@ class Options
5592
public IEnumerable<string> InputFiles { get; set; }
5693

5794
// Omitting long name, defaults to name of property, ie "--verbose"
58-
[Option(Default = false, HelpText = "Prints all messages to standard output.")]
95+
[Option(
96+
Default = false,
97+
HelpText = "Prints all messages to standard output.")]
5998
public bool Verbose { get; set; }
60-
61-
[Option("stdin", Default = false, HelpText = "Read from stdin")]
99+
100+
[Option("stdin",
101+
Default = false
102+
HelpText = "Read from stdin")]
62103
public bool stdin { get; set; }
63104

64105
[Value(0, MetaName = "offset", HelpText = "File offset.")]
@@ -79,7 +120,7 @@ F# Examples:
79120
type options = {
80121
[<Option('r', "read", Required = true, HelpText = "Input files.")>] files : seq<string>;
81122
[<Option(HelpText = "Prints all messages to standard output.")>] verbose : bool;
82-
[<Option(DefaultValue = "русский", HelpText = "Content language.")>] language : string;
123+
[<Option(Default = "русский", HelpText = "Content language.")>] language : string;
83124
[<Value(0, MetaName="offset", HelpText = "File offset.")>] offset : int64 option;
84125
}
85126
@@ -94,18 +135,22 @@ VB.Net:
94135

95136
```VB.NET
96137
Class Options
97-
<CommandLine.Option("r", "read", Required:=True, HelpText:="Input files to be processed.")>
98-
Public Property InputFiles As IEnumerable(Of String)
99-
100-
' Omitting long name, defaults to name of property, ie "--verbose"
101-
<CommandLine.Option(HelpText:="Prints all messages to standard output.")>
102-
Public Property Verbose As Boolean
103-
104-
<CommandLine.Option([Default]:="中文", HelpText:="Content language.")>
105-
Public Property Language As String
106-
107-
<CommandLine.Value(0, MetaName:="offset", HelpText:="File offset.")>
108-
Public Property Offset As Long?
138+
<CommandLine.Option('r', "read", Required := true,
139+
HelpText:="Input files to be processed.")>
140+
Public Property InputFiles As IEnumerable(Of String)
141+
142+
' Omitting long name, defaults to name of property, ie "--verbose"
143+
<CommandLine.Option(
144+
HelpText:="Prints all messages to standard output.")>
145+
Public Property Verbose As Boolean
146+
147+
<CommandLine.Option(Default:="中文",
148+
HelpText:="Content language.")>
149+
Public Property Language As String
150+
151+
<CommandLine.Value(0, MetaName:="offset",
152+
HelpText:="File offset.")>
153+
Public Property Offset As Long?
109154
End Class
110155

111156
Sub Main(ByVal args As String())
@@ -204,7 +249,7 @@ let main args =
204249
| :? CommandLine.NotParsed<obj> -> 1
205250
```
206251

207-
# Contibutors
252+
# Contributors
208253
First off, _Thank you!_ All contributions are welcome.
209254

210255
Please consider sticking with the GNU getopt standard for command line parsing.

src/CommandLine/Core/InstanceBuilder.cs

+16-14
Original file line numberDiff line numberDiff line change
@@ -77,22 +77,23 @@ public static ParserResult<T> Build<T>(
7777
var specPropsWithValue =
7878
optionSpecPropsResult.SucceededWith().Concat(valueSpecPropsResult.SucceededWith());
7979

80-
Func<T> buildMutable = () =>
80+
var setPropertyErrors = new List<Error>();
81+
82+
Func <T> buildMutable = () =>
8183
{
8284
var mutable = factory.MapValueOrDefault(f => f(), Activator.CreateInstance<T>());
83-
mutable =
84-
mutable.SetProperties(specPropsWithValue, sp => sp.Value.IsJust(), sp => sp.Value.FromJustOrFail())
85-
.SetProperties(
86-
specPropsWithValue,
87-
sp => sp.Value.IsNothing() && sp.Specification.DefaultValue.IsJust(),
88-
sp => sp.Specification.DefaultValue.FromJustOrFail())
89-
.SetProperties(
90-
specPropsWithValue,
91-
sp =>
92-
sp.Value.IsNothing() && sp.Specification.TargetType == TargetType.Sequence
93-
&& sp.Specification.DefaultValue.MatchNothing(),
94-
sp => sp.Property.PropertyType.GetTypeInfo().GetGenericArguments().Single().CreateEmptyArray());
95-
return mutable;
85+
setPropertyErrors.AddRange(mutable.SetProperties(specPropsWithValue, sp => sp.Value.IsJust(), sp => sp.Value.FromJustOrFail()));
86+
setPropertyErrors.AddRange(mutable.SetProperties(
87+
specPropsWithValue,
88+
sp => sp.Value.IsNothing() && sp.Specification.DefaultValue.IsJust(),
89+
sp => sp.Specification.DefaultValue.FromJustOrFail()));
90+
setPropertyErrors.AddRange(mutable.SetProperties(
91+
specPropsWithValue,
92+
sp =>
93+
sp.Value.IsNothing() && sp.Specification.TargetType == TargetType.Sequence
94+
&& sp.Specification.DefaultValue.MatchNothing(),
95+
sp => sp.Property.PropertyType.GetTypeInfo().GetGenericArguments().Single().CreateEmptyArray()));
96+
return mutable;
9697
};
9798

9899
Func<T> buildImmutable = () =>
@@ -118,6 +119,7 @@ join sp in specPropsWithValue on prms.Name.ToLower() equals sp.Property.Name.ToL
118119
.Concat(optionSpecPropsResult.SuccessfulMessages())
119120
.Concat(valueSpecPropsResult.SuccessfulMessages())
120121
.Concat(validationErrors)
122+
.Concat(setPropertyErrors)
121123
.Memorize();
122124

123125
var warnings = from e in allErrors where nonFatalErrors.Contains(e.Tag) select e;

src/CommandLine/Core/ReflectionExtensions.cs

+9-28
Original file line numberDiff line numberDiff line change
@@ -80,49 +80,30 @@ public static TargetType ToTargetType(this Type type)
8080
: TargetType.Scalar;
8181
}
8282

83-
public static T SetProperties<T>(
83+
public static IEnumerable<Error> SetProperties<T>(
8484
this T instance,
8585
IEnumerable<SpecificationProperty> specProps,
8686
Func<SpecificationProperty, bool> predicate,
8787
Func<SpecificationProperty, object> selector)
8888
{
89-
return specProps.Where(predicate).Aggregate(
90-
instance,
91-
(current, specProp) =>
92-
{
93-
specProp.Property.SetValue(current, selector(specProp));
94-
return instance;
95-
});
89+
return specProps.Where(predicate).SelectMany(specProp => specProp.SetValue(instance, selector(specProp)));
9690
}
9791

98-
private static T SetValue<T>(this PropertyInfo property, T instance, object value)
92+
private static IEnumerable<Error> SetValue<T>(this SpecificationProperty specProp, T instance, object value)
9993
{
100-
Action<Exception> fail = inner => {
101-
throw new InvalidOperationException("Cannot set value to target instance.", inner);
102-
};
103-
10494
try
10595
{
106-
property.SetValue(instance, value, null);
107-
}
108-
catch (TargetException e)
109-
{
110-
fail(e);
111-
}
112-
catch (TargetParameterCountException e)
113-
{
114-
fail(e);
96+
specProp.Property.SetValue(instance, value, null);
97+
return Enumerable.Empty<Error>();
11598
}
116-
catch (MethodAccessException e)
99+
catch (TargetInvocationException e)
117100
{
118-
fail(e);
101+
return new[] { new SetValueExceptionError(specProp.Specification.FromSpecification(), e.InnerException, value) };
119102
}
120-
catch (TargetInvocationException e)
103+
catch (Exception e)
121104
{
122-
fail(e);
105+
return new[] { new SetValueExceptionError(specProp.Specification.FromSpecification(), e, value) };
123106
}
124-
125-
return instance;
126107
}
127108

128109
public static object CreateEmptyArray(this Type type)

src/CommandLine/Core/SpecificationGuards.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ static class SpecificationGuards
1313
Tuple.Create(GuardAgainstScalarWithRange(), "Scalar option specifications do not support range specification."),
1414
Tuple.Create(GuardAgainstSequenceWithWrongRange(), "Bad range in sequence option specifications."),
1515
Tuple.Create(GuardAgainstSequenceWithZeroRange(), "Zero is not allowed in range of sequence option specifications."),
16-
Tuple.Create(GuardAgainstOneCharLongName(), "Long name should be longest than one character.")
16+
Tuple.Create(GuardAgainstOneCharLongName(), "Long name should be longer than one character.")
1717
};
1818

1919
private static Func<Specification, bool> GuardAgainstScalarWithRange()

src/CommandLine/Core/TypeConverter.cs

+15-3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ private static Maybe<object> ChangeTypeScalar(string value, Type conversionType,
4646
return result.ToMaybe();
4747
}
4848

49+
private static object ConvertString(string value, Type type, CultureInfo conversionCulture)
50+
{
51+
try
52+
{
53+
return Convert.ChangeType(value, type, conversionCulture);
54+
}
55+
catch (InvalidCastException)
56+
{
57+
// Required for converting from string to TimeSpan because Convert.ChangeType can't
58+
return System.ComponentModel.TypeDescriptor.GetConverter(type).ConvertFrom(null, conversionCulture, value);
59+
}
60+
}
61+
4962
private static Result<object, Exception> ChangeTypeScalarImpl(string value, Type conversionType, CultureInfo conversionCulture, bool ignoreValueCase)
5063
{
5164
Func<object> changeType = () =>
@@ -68,10 +81,9 @@ private static Result<object, Exception> ChangeTypeScalarImpl(string value, Type
6881
() =>
6982
#if !SKIP_FSHARP
7083
isFsOption
71-
? FSharpOptionHelper.Some(type, Convert.ChangeType(value, type, conversionCulture)) :
84+
? FSharpOptionHelper.Some(type, ConvertString(value, type, conversionCulture)) :
7285
#endif
73-
Convert.ChangeType(value, type, conversionCulture);
74-
86+
ConvertString(value, type, conversionCulture);
7587
#if !SKIP_FSHARP
7688
Func<object> empty = () => isFsOption ? FSharpOptionHelper.None(type) : null;
7789
#else

src/CommandLine/Error.cs

+38-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ public enum ErrorType
6060
/// <summary>
6161
/// Value of <see cref="CommandLine.VersionRequestedError"/> type.
6262
/// </summary>
63-
VersionRequestedError
63+
VersionRequestedError,
64+
/// <summary>
65+
/// Value of <see cref="CommandLine.SetValueExceptionError"/> type.
66+
/// </summary>
67+
SetValueExceptionError
6468
}
6569

6670
/// <summary>
@@ -471,4 +475,37 @@ internal VersionRequestedError()
471475
{
472476
}
473477
}
478+
479+
/// <summary>
480+
/// Models as error generated when exception is thrown at Property.SetValue
481+
/// </summary>
482+
public sealed class SetValueExceptionError : NamedError
483+
{
484+
private readonly Exception exception;
485+
private readonly object value;
486+
487+
internal SetValueExceptionError(NameInfo nameInfo, Exception exception, object value)
488+
: base(ErrorType.SetValueExceptionError, nameInfo)
489+
{
490+
this.exception = exception;
491+
this.value = value;
492+
}
493+
494+
/// <summary>
495+
/// The expection thrown from Property.SetValue
496+
/// </summary>
497+
public Exception Exception
498+
{
499+
get { return exception; }
500+
}
501+
502+
/// <summary>
503+
/// The value that had to be set to the property
504+
/// </summary>
505+
public object Value
506+
{
507+
get { return value; }
508+
}
509+
510+
}
474511
}

src/CommandLine/ParserSettings.cs

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ public ParserSettings()
3535
try
3636
{
3737
maximumDisplayWidth = Console.WindowWidth;
38+
if (maximumDisplayWidth < 1)
39+
{
40+
maximumDisplayWidth = DefaultMaximumLength;
41+
}
3842
}
3943
catch (IOException)
4044
{

src/CommandLine/Text/HeadingInfo.cs

+9-1
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,16 @@ public static HeadingInfo Default
5757
{
5858
var title = ReflectionHelper.GetAttribute<AssemblyTitleAttribute>()
5959
.MapValueOrDefault(
60-
titleAttribute => Path.GetFileNameWithoutExtension(titleAttribute.Title),
60+
titleAttribute =>
61+
{
62+
if (titleAttribute.Title.ToLowerInvariant().EndsWith(".dll"))
63+
{
64+
return titleAttribute.Title.Substring(0, titleAttribute.Title.Length - ".dll".Length);
65+
}
66+
return titleAttribute.Title;
67+
},
6168
ReflectionHelper.GetAssemblyName());
69+
6270
var version = ReflectionHelper.GetAttribute<AssemblyInformationalVersionAttribute>()
6371
.MapValueOrDefault(
6472
versionAttribute => versionAttribute.InformationalVersion,

src/CommandLine/Text/HelpText.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ public HelpText(SentenceBuilder sentenceBuilder, string heading, string copyrigh
105105
try
106106
{
107107
maximumDisplayWidth = Console.WindowWidth;
108+
if (maximumDisplayWidth < 1)
109+
{
110+
maximumDisplayWidth = DefaultMaximumLength;
111+
}
108112
}
109113
catch (IOException)
110114
{
@@ -636,7 +640,7 @@ internal static void AddLine(StringBuilder builder, string value, int maximumLen
636640
throw new ArgumentOutOfRangeException(nameof(value));
637641
}
638642

639-
value = value.Trim();
643+
value = value.TrimEnd();
640644

641645
builder.AppendWhen(builder.Length > 0, Environment.NewLine);
642646
do

src/CommandLine/Text/SentenceBuilder.cs

+3
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ public override Func<Error, string> FormatError
137137
case ErrorType.RepeatedOptionError:
138138
return "Option '".JoinTo(((RepeatedOptionError)error).NameInfo.NameText,
139139
"' is defined multiple times.");
140+
case ErrorType.SetValueExceptionError:
141+
var setValueError = (SetValueExceptionError)error;
142+
return "Error setting value to option '".JoinTo(setValueError.NameInfo.NameText, "': ", setValueError.Exception.Message);
140143
}
141144
throw new InvalidOperationException();
142145
};

0 commit comments

Comments
 (0)