Skip to content

Add ArgumentParseOptions and overloads for Parse() accepting them #67

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 89 additions & 10 deletions src/Utility.CommandLine.Arguments/Arguments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,36 @@ public ArgumentInfo(char shortName, string longName, string helpText, PropertyIn
public char ShortName { get; }
}

/// <summary>
/// Parsing options.
/// </summary>
public class ArgumentParseOptions
{
/// <summary>
/// Gets or sets the <see cref="Type"/> for which the command line string is to be parsed.
/// </summary>
/// <remarks>
/// Supersedes combination options; arguments backed by properties of a collection type are combined, while those that aren't are not.
/// </remarks>
public Type TargetType { get; set; }

/// <summary>
/// Gets or sets a value indicating whether duplicate argument values should be combined into a list.
/// </summary>
/// <remarks>
/// Only applicable if <see cref="TargetType"/> is not specified.
/// </remarks>
public bool CombineAllMultiples { get; set; }

/// <summary>
/// Gets or sets a value indicating whether duplicate argument values for arguments in the array should be combined into a list.
/// </summary>
/// <remarks>
/// Only applicable if <see cref="TargetType"/> is not specified.
/// </remarks>
public string[] CombinableArguments { get; set; } = Array.Empty<string>();
}

/// <summary>
/// Provides static methods used to retrieve the command line arguments and operands with which the application was
/// started, as well as a Type to contain them.
Expand Down Expand Up @@ -310,19 +340,53 @@ public object this[string key]
return retVal;
}

/// <summary>
/// Returns a dictionary containing the values specified in the command line arguments with which the application was
/// started, keyed by argument name.
/// </summary>
/// <param name="configure">An action to configure the provided <see cref="ArgumentParseOptions"/> instance.</param>
/// <returns>
/// The dictionary containing the arguments and values specified in the command line arguments with which the
/// application was started.
/// </returns>
public static Arguments Parse(Action<ArgumentParseOptions> configure = null)
{
return Parse(null, configure);
}

/// <summary>
/// Returns a dictionary containing the values specified in the command line arguments with which the application was
/// started, keyed by argument name.
/// </summary>
/// <param name="commandLineString">The command line arguments with which the application was started.</param>
/// <param name="type">The <see cref="Type"/> for which the command line string is to be parsed.</param>
/// <param name="caller">Internal parameter used to identify the calling method.</param>
/// <param name="configure">An action to configure the provided <see cref="ArgumentParseOptions"/> instance.</param>
/// <returns>
/// The dictionary containing the arguments and values specified in the command line arguments with which the
/// application was started.
/// </returns>
public static Arguments Parse(string commandLineString, Action<ArgumentParseOptions> configure = null)
{
configure = configure ?? new Action<ArgumentParseOptions>((_) => { });
var options = new ArgumentParseOptions();
configure(options);

return Parse(commandLineString, options);
}

/// <summary>
/// Returns a dictionary containing the values specified in the command line arguments with which the application was
/// started, keyed by argument name.
/// </summary>
/// <param name="commandLineString">The command line arguments with which the application was started.</param>
/// <param name="options">Parser options.</param>
/// <returns>
/// The dictionary containing the arguments and values specified in the command line arguments with which the
/// application was started.
/// </returns>
public static Arguments Parse(string commandLineString = default(string), Type type = null, [CallerMemberName] string caller = default(string))
public static Arguments Parse(string commandLineString, ArgumentParseOptions options)
{
options = options ?? new ArgumentParseOptions();

commandLineString = commandLineString == default(string) || string.IsNullOrEmpty(commandLineString) ? Environment.CommandLine : commandLineString;

List<KeyValuePair<string, string>> argumentList;
Expand Down Expand Up @@ -353,8 +417,8 @@ public object this[string key]
operandList = GetOperandList(commandLineString);
}

var argumentDictionary = GetArgumentDictionary(argumentList, type);
return new Arguments(commandLineString, argumentList, argumentDictionary, operandList, type);
var argumentDictionary = GetArgumentDictionary(argumentList, options);
return new Arguments(commandLineString, argumentList, argumentDictionary, operandList, options.TargetType);
}

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

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

/// <summary>
Expand Down Expand Up @@ -537,10 +601,10 @@ private static void ClearProperties(Dictionary<string, PropertyInfo> properties)
}
}

private static Dictionary<string, object> GetArgumentDictionary(List<KeyValuePair<string, string>> argumentList, Type targetType = null)
private static Dictionary<string, object> GetArgumentDictionary(List<KeyValuePair<string, string>> argumentList, ArgumentParseOptions options)
{
var dict = new ConcurrentDictionary<string, object>();
var argumentInfo = targetType == null ? new List<ArgumentInfo>() : GetArgumentInfo(targetType);
var argumentInfo = options.TargetType == null ? new List<ArgumentInfo>() : GetArgumentInfo(options.TargetType);

foreach (var arg in argumentList)
{
Expand All @@ -567,7 +631,22 @@ private static Dictionary<string, object> GetArgumentDictionary(List<KeyValuePai
}
else
{
dict.AddOrUpdate(arg.Key, arg.Value, (key, existingValue) => arg.Value);
if (dict.ContainsKey(arg.Key) && (options.CombineAllMultiples || options.CombinableArguments.Contains(arg.Key)))
{
dict.AddOrUpdate(arg.Key, arg.Value, (key, existingValue) =>
{
if (existingValue.GetType() == typeof(List<object>))
{
return ((List<object>)existingValue).Concat(new[] { arg.Value }).ToList();
}

return new List<object>() { existingValue, arg.Value };
});
}
else
{
dict.AddOrUpdate(arg.Key, arg.Value, (key, existingValue) => arg.Value);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<PropertyGroup>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>5.0.0</Version>
<Version>6.0.0</Version>
<Authors>JP Dillingham</Authors>
<Product>Utility.CommandLine.Arguments</Product>
<PackageProjectUrl>https://github.com/jpdillingham/Utility.CommandLine.Arguments</PackageProjectUrl>
Expand Down
60 changes: 51 additions & 9 deletions tests/Utility.CommandLine.Arguments.Tests/ArgumentsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,48 @@ public void ParseStringOfLongs()
Assert.Equal("5 5", test["five"]);
}

[Fact]
public void ParseMultiples_No_CombineAllMultiples()
{
Dictionary<string, object> test = Arguments.Parse("--test 1 --test 2", options => options.CombineAllMultiples = false).ArgumentDictionary;

Assert.NotEmpty(test);
Assert.Single(test);
Assert.Equal("2", test["test"]);
}

[Fact]
public void ParseMultiples_With_CombineAllMultiples()
{
Dictionary<string, object> test = Arguments.Parse("--test 1 --test 2", options => options.CombineAllMultiples = true).ArgumentDictionary;

Assert.NotEmpty(test);
Assert.Single(test);

var values = (List<object>)test["test"];

Assert.Equal(2, values.Count);
Assert.Equal("1", values[0]);
Assert.Equal("2", values[1]);
}

[Fact]
public void ParseMultiples_With_Specific_CombinableArguments()
{
Dictionary<string, object> test = Arguments.Parse("--test 1 --test 2 --foo foo --foo bar", options => options.CombinableArguments = new[] { "test" }).ArgumentDictionary;

Assert.NotEmpty(test);
Assert.Equal(2, test.Count);

var values = (List<object>)test["test"];

Assert.Equal(2, values.Count);
Assert.Equal("1", values[0]);
Assert.Equal("2", values[1]);

Assert.Equal("bar", test["foo"]);
}

[Fact]
public void ParseValueBeginningWithSlash()
{
Expand Down Expand Up @@ -790,7 +832,7 @@ public void List_Is_Appended_Given_Two_Short_Args()
new KeyValuePair<string, string>("l", "bar")
};

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

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

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

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

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

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

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

List<object> argList = null;
Expand Down Expand Up @@ -901,7 +943,7 @@ public void Value_Is_Replaced_Given_Multiple_Short()
new KeyValuePair<string, string>("b", "2")
};

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

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

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

Assert.Single(dict);
Expand Down Expand Up @@ -986,7 +1028,7 @@ public void Arguments_Sets_TargetType_Given_Type()
new KeyValuePair<string, string>("bb", "2")
};

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

Assert.NotNull(a.TargetType);
Assert.Equal(GetType(), a.TargetType);
Expand Down Expand Up @@ -1015,7 +1057,7 @@ public void Value_Is_Replaced_Given_Mixed_Args_Long_First()
new KeyValuePair<string, string>("b", "2")
};

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

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

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

Assert.Single(dict);
Expand Down