Skip to content

Commit ee245ea

Browse files
authored
Merge pull request #67 from jpdillingham/develop
Add ArgumentParseOptions and overloads for Parse() accepting them
2 parents d6819d2 + cb6dc14 commit ee245ea

File tree

3 files changed

+141
-20
lines changed

3 files changed

+141
-20
lines changed

src/Utility.CommandLine.Arguments/Arguments.cs

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,36 @@ public ArgumentInfo(char shortName, string longName, string helpText, PropertyIn
167167
public char ShortName { get; }
168168
}
169169

170+
/// <summary>
171+
/// Parsing options.
172+
/// </summary>
173+
public class ArgumentParseOptions
174+
{
175+
/// <summary>
176+
/// Gets or sets the <see cref="Type"/> for which the command line string is to be parsed.
177+
/// </summary>
178+
/// <remarks>
179+
/// Supersedes combination options; arguments backed by properties of a collection type are combined, while those that aren't are not.
180+
/// </remarks>
181+
public Type TargetType { get; set; }
182+
183+
/// <summary>
184+
/// Gets or sets a value indicating whether duplicate argument values should be combined into a list.
185+
/// </summary>
186+
/// <remarks>
187+
/// Only applicable if <see cref="TargetType"/> is not specified.
188+
/// </remarks>
189+
public bool CombineAllMultiples { get; set; }
190+
191+
/// <summary>
192+
/// Gets or sets a value indicating whether duplicate argument values for arguments in the array should be combined into a list.
193+
/// </summary>
194+
/// <remarks>
195+
/// Only applicable if <see cref="TargetType"/> is not specified.
196+
/// </remarks>
197+
public string[] CombinableArguments { get; set; } = Array.Empty<string>();
198+
}
199+
170200
/// <summary>
171201
/// Provides static methods used to retrieve the command line arguments and operands with which the application was
172202
/// started, as well as a Type to contain them.
@@ -310,19 +340,53 @@ public object this[string key]
310340
return retVal;
311341
}
312342

343+
/// <summary>
344+
/// Returns a dictionary containing the values specified in the command line arguments with which the application was
345+
/// started, keyed by argument name.
346+
/// </summary>
347+
/// <param name="configure">An action to configure the provided <see cref="ArgumentParseOptions"/> instance.</param>
348+
/// <returns>
349+
/// The dictionary containing the arguments and values specified in the command line arguments with which the
350+
/// application was started.
351+
/// </returns>
352+
public static Arguments Parse(Action<ArgumentParseOptions> configure = null)
353+
{
354+
return Parse(null, configure);
355+
}
356+
313357
/// <summary>
314358
/// Returns a dictionary containing the values specified in the command line arguments with which the application was
315359
/// started, keyed by argument name.
316360
/// </summary>
317361
/// <param name="commandLineString">The command line arguments with which the application was started.</param>
318-
/// <param name="type">The <see cref="Type"/> for which the command line string is to be parsed.</param>
319-
/// <param name="caller">Internal parameter used to identify the calling method.</param>
362+
/// <param name="configure">An action to configure the provided <see cref="ArgumentParseOptions"/> instance.</param>
363+
/// <returns>
364+
/// The dictionary containing the arguments and values specified in the command line arguments with which the
365+
/// application was started.
366+
/// </returns>
367+
public static Arguments Parse(string commandLineString, Action<ArgumentParseOptions> configure = null)
368+
{
369+
configure = configure ?? new Action<ArgumentParseOptions>((_) => { });
370+
var options = new ArgumentParseOptions();
371+
configure(options);
372+
373+
return Parse(commandLineString, options);
374+
}
375+
376+
/// <summary>
377+
/// Returns a dictionary containing the values specified in the command line arguments with which the application was
378+
/// started, keyed by argument name.
379+
/// </summary>
380+
/// <param name="commandLineString">The command line arguments with which the application was started.</param>
381+
/// <param name="options">Parser options.</param>
320382
/// <returns>
321383
/// The dictionary containing the arguments and values specified in the command line arguments with which the
322384
/// application was started.
323385
/// </returns>
324-
public static Arguments Parse(string commandLineString = default(string), Type type = null, [CallerMemberName] string caller = default(string))
386+
public static Arguments Parse(string commandLineString, ArgumentParseOptions options)
325387
{
388+
options = options ?? new ArgumentParseOptions();
389+
326390
commandLineString = commandLineString == default(string) || string.IsNullOrEmpty(commandLineString) ? Environment.CommandLine : commandLineString;
327391

328392
List<KeyValuePair<string, string>> argumentList;
@@ -353,8 +417,8 @@ public object this[string key]
353417
operandList = GetOperandList(commandLineString);
354418
}
355419

356-
var argumentDictionary = GetArgumentDictionary(argumentList, type);
357-
return new Arguments(commandLineString, argumentList, argumentDictionary, operandList, type);
420+
var argumentDictionary = GetArgumentDictionary(argumentList, options);
421+
return new Arguments(commandLineString, argumentList, argumentDictionary, operandList, options.TargetType);
358422
}
359423

360424
/// <summary>
@@ -368,7 +432,7 @@ public object this[string key]
368432
public static void Populate(string commandLineString = default(string), bool clearExistingValues = true, [CallerMemberName] string caller = default(string))
369433
{
370434
var type = ArgumentsExtensions.GetCallingType(caller);
371-
Populate(type, Parse(commandLineString, type), clearExistingValues);
435+
Populate(type, Parse(commandLineString, options => options.TargetType = type), clearExistingValues);
372436
}
373437

374438
/// <summary>
@@ -383,7 +447,7 @@ public object this[string key]
383447
/// <param name="clearExistingValues">Whether to clear the properties before populating them. Defaults to true.</param>
384448
public static void Populate(Type type, string commandLineString = default(string), bool clearExistingValues = true)
385449
{
386-
Populate(type, Parse(commandLineString, type), clearExistingValues);
450+
Populate(type, Parse(commandLineString, options => options.TargetType = type), clearExistingValues);
387451
}
388452

389453
/// <summary>
@@ -537,10 +601,10 @@ private static void ClearProperties(Dictionary<string, PropertyInfo> properties)
537601
}
538602
}
539603

540-
private static Dictionary<string, object> GetArgumentDictionary(List<KeyValuePair<string, string>> argumentList, Type targetType = null)
604+
private static Dictionary<string, object> GetArgumentDictionary(List<KeyValuePair<string, string>> argumentList, ArgumentParseOptions options)
541605
{
542606
var dict = new ConcurrentDictionary<string, object>();
543-
var argumentInfo = targetType == null ? new List<ArgumentInfo>() : GetArgumentInfo(targetType);
607+
var argumentInfo = options.TargetType == null ? new List<ArgumentInfo>() : GetArgumentInfo(options.TargetType);
544608

545609
foreach (var arg in argumentList)
546610
{
@@ -567,7 +631,22 @@ private static Dictionary<string, object> GetArgumentDictionary(List<KeyValuePai
567631
}
568632
else
569633
{
570-
dict.AddOrUpdate(arg.Key, arg.Value, (key, existingValue) => arg.Value);
634+
if (dict.ContainsKey(arg.Key) && (options.CombineAllMultiples || options.CombinableArguments.Contains(arg.Key)))
635+
{
636+
dict.AddOrUpdate(arg.Key, arg.Value, (key, existingValue) =>
637+
{
638+
if (existingValue.GetType() == typeof(List<object>))
639+
{
640+
return ((List<object>)existingValue).Concat(new[] { arg.Value }).ToList();
641+
}
642+
643+
return new List<object>() { existingValue, arg.Value };
644+
});
645+
}
646+
else
647+
{
648+
dict.AddOrUpdate(arg.Key, arg.Value, (key, existingValue) => arg.Value);
649+
}
571650
}
572651
}
573652

src/Utility.CommandLine.Arguments/Utility.CommandLine.Arguments.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
<PropertyGroup>
1313
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
14-
<Version>5.0.0</Version>
14+
<Version>6.0.0</Version>
1515
<Authors>JP Dillingham</Authors>
1616
<Product>Utility.CommandLine.Arguments</Product>
1717
<PackageProjectUrl>https://github.com/jpdillingham/Utility.CommandLine.Arguments</PackageProjectUrl>

tests/Utility.CommandLine.Arguments.Tests/ArgumentsTests.cs

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,48 @@ public void ParseStringOfLongs()
366366
Assert.Equal("5 5", test["five"]);
367367
}
368368

369+
[Fact]
370+
public void ParseMultiples_No_CombineAllMultiples()
371+
{
372+
Dictionary<string, object> test = Arguments.Parse("--test 1 --test 2", options => options.CombineAllMultiples = false).ArgumentDictionary;
373+
374+
Assert.NotEmpty(test);
375+
Assert.Single(test);
376+
Assert.Equal("2", test["test"]);
377+
}
378+
379+
[Fact]
380+
public void ParseMultiples_With_CombineAllMultiples()
381+
{
382+
Dictionary<string, object> test = Arguments.Parse("--test 1 --test 2", options => options.CombineAllMultiples = true).ArgumentDictionary;
383+
384+
Assert.NotEmpty(test);
385+
Assert.Single(test);
386+
387+
var values = (List<object>)test["test"];
388+
389+
Assert.Equal(2, values.Count);
390+
Assert.Equal("1", values[0]);
391+
Assert.Equal("2", values[1]);
392+
}
393+
394+
[Fact]
395+
public void ParseMultiples_With_Specific_CombinableArguments()
396+
{
397+
Dictionary<string, object> test = Arguments.Parse("--test 1 --test 2 --foo foo --foo bar", options => options.CombinableArguments = new[] { "test" }).ArgumentDictionary;
398+
399+
Assert.NotEmpty(test);
400+
Assert.Equal(2, test.Count);
401+
402+
var values = (List<object>)test["test"];
403+
404+
Assert.Equal(2, values.Count);
405+
Assert.Equal("1", values[0]);
406+
Assert.Equal("2", values[1]);
407+
408+
Assert.Equal("bar", test["foo"]);
409+
}
410+
369411
[Fact]
370412
public void ParseValueBeginningWithSlash()
371413
{
@@ -790,7 +832,7 @@ public void List_Is_Appended_Given_Two_Short_Args()
790832
new KeyValuePair<string, string>("l", "bar")
791833
};
792834

793-
var a = Arguments.Parse("-l foo -l bar", GetType());
835+
var a = Arguments.Parse("-l foo -l bar", options => options.TargetType = GetType());
794836
var dict = a.ArgumentDictionary;
795837

796838
List<object> argList = null;
@@ -813,7 +855,7 @@ public void List_Is_Appended_Given_Two_Long_Args()
813855
new KeyValuePair<string, string>("list", "bar")
814856
};
815857

816-
var a = Arguments.Parse("--list foo --list bar", GetType());
858+
var a = Arguments.Parse("--list foo --list bar", options => options.TargetType = GetType());
817859
var dict = a.ArgumentDictionary;
818860

819861
List<object> argList = null;
@@ -836,7 +878,7 @@ public void List_Is_Appended_Given_Mixed_Args_Short_First()
836878
new KeyValuePair<string, string>("list", "bar")
837879
};
838880

839-
var a = Arguments.Parse("-l foo --list bar", GetType());
881+
var a = Arguments.Parse("-l foo --list bar", options => options.TargetType = GetType());
840882
var dict = a.ArgumentDictionary;
841883

842884
List<object> argList = null;
@@ -859,7 +901,7 @@ public void List_Is_Appended_Given_Mixed_Args_Long_First()
859901
new KeyValuePair<string, string>("l", "bar")
860902
};
861903

862-
var a = Arguments.Parse("--list foo -l bar", GetType());
904+
var a = Arguments.Parse("--list foo -l bar", options => options.TargetType = GetType());
863905
var dict = a.ArgumentDictionary;
864906

865907
List<object> argList = null;
@@ -901,7 +943,7 @@ public void Value_Is_Replaced_Given_Multiple_Short()
901943
new KeyValuePair<string, string>("b", "2")
902944
};
903945

904-
var a = Arguments.Parse("-b 1 -b 2", GetType());
946+
var a = Arguments.Parse("-b 1 -b 2", options => options.TargetType = GetType());
905947
var dict = a.ArgumentDictionary;
906948

907949
Assert.Single(dict);
@@ -918,7 +960,7 @@ public void Value_Is_Replaced_Given_Multiple_Long()
918960
new KeyValuePair<string, string>("bb", "2")
919961
};
920962

921-
var a = Arguments.Parse("--bb 1 --bb 2", GetType());
963+
var a = Arguments.Parse("--bb 1 --bb 2", options => options.TargetType = GetType());
922964
var dict = a.ArgumentDictionary;
923965

924966
Assert.Single(dict);
@@ -986,7 +1028,7 @@ public void Arguments_Sets_TargetType_Given_Type()
9861028
new KeyValuePair<string, string>("bb", "2")
9871029
};
9881030

989-
var a = Arguments.Parse("--bb 1 --bb 2", GetType());
1031+
var a = Arguments.Parse("--bb 1 --bb 2", options => options.TargetType = GetType());
9901032

9911033
Assert.NotNull(a.TargetType);
9921034
Assert.Equal(GetType(), a.TargetType);
@@ -1015,7 +1057,7 @@ public void Value_Is_Replaced_Given_Mixed_Args_Long_First()
10151057
new KeyValuePair<string, string>("b", "2")
10161058
};
10171059

1018-
var a = Arguments.Parse("--bb 1 -b 2", GetType());
1060+
var a = Arguments.Parse("--bb 1 -b 2", options => options.TargetType = GetType());
10191061
var dict = a.ArgumentDictionary;
10201062

10211063
Assert.Single(dict);
@@ -1032,7 +1074,7 @@ public void Value_Is_Replaced_Given_Mixed_Args_Short_First()
10321074
new KeyValuePair<string, string>("bb", "2")
10331075
};
10341076

1035-
var a = Arguments.Parse("-b 1 --bb 2", GetType());
1077+
var a = Arguments.Parse("-b 1 --bb 2", options => options.TargetType = GetType());
10361078
var dict = a.ArgumentDictionary;
10371079

10381080
Assert.Single(dict);

0 commit comments

Comments
 (0)