Skip to content

Commit 5714eab

Browse files
committed
Changed the way argument key/value pairs are stored to allow for more than one argument value to be specified by specifying multiple keys on the command line.
1 parent 3aebaaf commit 5714eab

File tree

2 files changed

+108
-21
lines changed

2 files changed

+108
-21
lines changed

Examples/Program.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using Utility.CommandLine;
45

56
namespace Examples
@@ -20,6 +21,9 @@ internal class Program
2021
[Argument('i', "integer")]
2122
private static int Int { get; set; }
2223

24+
[Argument('l', "list")]
25+
private static List<int> List { get; set; }
26+
2327
[Operands]
2428
private static string[] Operands { get; set; }
2529

@@ -74,7 +78,7 @@ private static void Print(string commandLine)
7478
Console.WriteLine("\r\nArgument\tValue");
7579
Console.WriteLine("-------\t\t-------");
7680

77-
Dictionary<string, string> argumentDictionary = Arguments.Parse(commandLine).ArgumentDictionary;
81+
Dictionary<string, object> argumentDictionary = Arguments.Parse(commandLine).ArgumentDictionary;
7882

7983
foreach (string key in argumentDictionary.Keys)
8084
{
@@ -88,6 +92,7 @@ private static void Print(string commandLine)
8892
Console.WriteLine("Bool\t\t" + Bool);
8993
Console.WriteLine("Int\t\t" + Int);
9094
Console.WriteLine("Double\t\t" + Double);
95+
Console.WriteLine("List\t\t" + String.Join(",", List.Select(o => o.ToString()).ToArray()));
9196

9297
Console.WriteLine("\r\nOperands\n-------");
9398

Utility.CommandLine.Arguments/Arguments.cs

Lines changed: 102 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
▀▀ */
4747

4848
using System;
49+
using System.Collections;
4950
using System.Collections.Generic;
5051
using System.Diagnostics;
5152
using System.Diagnostics.CodeAnalysis;
@@ -66,17 +67,33 @@ public static class Extensions
6667

6768
/// <summary>
6869
/// Adds the specified key to the specified dictionary with the specified value, but only if the specified key is not
69-
/// already present in the dictionary.
70+
/// already present in the dictionary. If it is present, a list is created and the new value is added to the list,
71+
/// along with all subsequent values.
7072
/// </summary>
7173
/// <param name="dictionary">The dictionary to which they specified key and value are to be added.</param>
7274
/// <param name="key">The key to add to the dictionary.</param>
7375
/// <param name="value">The value corresponding to the specified key.</param>
74-
internal static void ExclusiveAdd(this Dictionary<string, string> dictionary, string key, string value)
76+
internal static void ExclusiveAdd(this Dictionary<string, object> dictionary, string key, object value)
7577
{
7678
if (!dictionary.ContainsKey(key))
7779
{
7880
dictionary.Add(key, value);
7981
}
82+
else
83+
{
84+
var type = dictionary[key].GetType();
85+
86+
if (dictionary[key].GetType() == typeof(List<object>))
87+
{
88+
((List<object>)dictionary[key]).Add(value);
89+
}
90+
else
91+
{
92+
object existingValue = dictionary[key];
93+
94+
dictionary[key] = new List<object>(new object[] { existingValue, value });
95+
}
96+
}
8097
}
8198

8299
#endregion Internal Methods
@@ -166,7 +183,7 @@ public class Arguments
166183
/// <param name="operandList">
167184
/// The list containing the operands specified in the command line arguments with which the application was started.
168185
/// </param>
169-
private Arguments(Dictionary<string, string> argumentDictionary, List<string> operandList)
186+
private Arguments(Dictionary<string, object> argumentDictionary, List<string> operandList)
170187
{
171188
ArgumentDictionary = argumentDictionary;
172189
OperandList = operandList;
@@ -180,7 +197,7 @@ private Arguments(Dictionary<string, string> argumentDictionary, List<string> op
180197
/// Gets a dictionary containing the arguments and values specified in the command line arguments with which the
181198
/// application was started.
182199
/// </summary>
183-
public Dictionary<string, string> ArgumentDictionary { get; private set; }
200+
public Dictionary<string, object> ArgumentDictionary { get; private set; }
184201

185202
/// <summary>
186203
/// Gets a list containing the operands specified in the command line arguments with which the application was started.
@@ -196,7 +213,7 @@ private Arguments(Dictionary<string, string> argumentDictionary, List<string> op
196213
/// </summary>
197214
/// <param name="index">The key for which the value is to be retrieved.</param>
198215
/// <returns>The argument value corresponding to the specified key.</returns>
199-
public string this[string index]
216+
public object this[string index]
200217
{
201218
get
202219
{
@@ -221,7 +238,7 @@ public string this[string index]
221238
{
222239
commandLineString = commandLineString == default(string) || commandLineString == string.Empty ? Environment.CommandLine : commandLineString;
223240

224-
Dictionary<string, string> argumentDictionary;
241+
Dictionary<string, object> argumentDictionary;
225242
List<string> operandList;
226243

227244
// use the strict operand regular expression to test for/extract the two halves of the string, if the operator is used.
@@ -287,7 +304,7 @@ public string this[string index]
287304
/// <param name="argumentDictionary">
288305
/// The dictionary containing the argument-value pairs with which the destination properties should be populated
289306
/// </param>
290-
public static void Populate(Dictionary<string, string> argumentDictionary)
307+
public static void Populate(Dictionary<string, object> argumentDictionary)
291308
{
292309
Populate(new StackFrame(1).GetMethod().DeclaringType, new Arguments(argumentDictionary, new List<string>()));
293310
}
@@ -319,31 +336,73 @@ public static void Populate(Type type, Arguments arguments)
319336
Type propertyType = property.PropertyType;
320337

321338
// retrieve the value from the argument dictionary
322-
string value = arguments.ArgumentDictionary[propertyName];
339+
object value = arguments.ArgumentDictionary[propertyName];
323340

324341
object convertedValue;
325342

326343
// if the type of the property is bool and the argument value is empty set the property value to true,
327344
// indicating the argument is present
328-
if (propertyType == typeof(bool) && value == string.Empty)
345+
if (propertyType == typeof(bool) && value.ToString() == string.Empty)
329346
{
330347
convertedValue = true;
331348
}
332-
else
349+
else if (propertyType.IsArray || (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(List<>)))
333350
{
334-
// try to cast the argument value string to the destination type
335-
try
351+
// if the property is an array or list, convert the value to an array or list of the matching type. start
352+
// by converting atomic values to a list containing a single value, just to simplify processing.
353+
if (value.GetType().IsGenericType && value.GetType().GetGenericTypeDefinition() == typeof(List<>))
354+
{
355+
convertedValue = value;
356+
}
357+
else
358+
{
359+
convertedValue = new List<object>(new object[] { value });
360+
}
361+
362+
// next, create a list with the same type as the target property
363+
Type valueType;
364+
365+
if (propertyType.IsArray)
366+
{
367+
valueType = propertyType.GetElementType();
368+
}
369+
else
370+
{
371+
valueType = propertyType.GetGenericArguments()[0];
372+
}
373+
374+
// create a list to store converted values
375+
Type valueListType = typeof(List<>).MakeGenericType(valueType);
376+
var valueList = (IList)Activator.CreateInstance(valueListType);
377+
378+
// populate the list
379+
foreach (object v in (List<object>)convertedValue)
336380
{
337-
convertedValue = Convert.ChangeType(value, propertyType);
381+
valueList.Add(ChangeType(v, propertyName, valueType));
338382
}
339-
catch (Exception ex)
383+
384+
// if the target property is an array, create one and populate it from the list this is surprisingly
385+
// difficult here because we created the source list with the Activator and ToArray() won't work easily.
386+
if (propertyType.IsArray)
340387
{
341-
// if the cast fails, throw an exception
342-
string message = $"Specified value '{value}' for argument '{propertyName}' (type: {property.PropertyType.Name}). ";
343-
message += "See inner exception for details.";
388+
var valueArray = Array.CreateInstance(propertyType.GetElementType(), valueList.Count);
389+
390+
for (int i = 0; i < valueArray.Length; i++)
391+
{
392+
valueArray.SetValue(valueList[i], i);
393+
}
344394

345-
throw new ArgumentException(message, ex);
395+
convertedValue = valueArray;
346396
}
397+
else
398+
{
399+
convertedValue = valueList;
400+
}
401+
}
402+
else
403+
{
404+
// if the target property Type is an atomic (non-array or list) Type, convert the value and populate it.
405+
convertedValue = ChangeType(value, propertyName, propertyType);
347406
}
348407

349408
// set the target properties' value to the converted value from the argument string
@@ -372,6 +431,29 @@ public static void Populate(Type type, Arguments arguments)
372431

373432
#region Private Methods
374433

434+
/// <summary>
435+
/// Converts the specified value for the specified argument to the specified Type.
436+
/// </summary>
437+
/// <param name="value">The value to convert.</param>
438+
/// <param name="argument">The argument for which the value is being converted.</param>
439+
/// <param name="toType">The Type to which the value is being converted.</param>
440+
/// <returns>The converted value.</returns>
441+
private static object ChangeType(object value, string argument, Type toType)
442+
{
443+
try
444+
{
445+
return Convert.ChangeType(value, toType);
446+
}
447+
catch (Exception ex)
448+
{
449+
// if the cast fails, throw an exception
450+
string message = $"Specified value '{value}' for argument '{argument}' (expected type: {toType}). ";
451+
message += "See inner exception for details.";
452+
453+
throw new ArgumentException(message, ex);
454+
}
455+
}
456+
375457
/// <summary>
376458
/// Populates and returns a dictionary containing the values specified in the command line arguments with which the
377459
/// application was started, keyed by argument name.
@@ -381,9 +463,9 @@ public static void Populate(Type type, Arguments arguments)
381463
/// The dictionary containing the arguments and values specified in the command line arguments with which the
382464
/// application was started.
383465
/// </returns>
384-
private static Dictionary<string, string> GetArgumentDictionary(string commandLineString)
466+
private static Dictionary<string, object> GetArgumentDictionary(string commandLineString)
385467
{
386-
Dictionary<string, string> argumentDictionary = new Dictionary<string, string>();
468+
Dictionary<string, object> argumentDictionary = new Dictionary<string, object>();
387469

388470
foreach (Match match in Regex.Matches(commandLineString, ArgumentRegEx))
389471
{

0 commit comments

Comments
 (0)