Skip to content

Commit fd24f38

Browse files
authored
Merge pull request #49 from jpdillingham/develop
Finish refactor of ArgumentDictionary for mixed short and long list arguments
2 parents f158d0e + ee2b897 commit fd24f38

File tree

3 files changed

+216
-119
lines changed

3 files changed

+216
-119
lines changed

examples/Example/Program.cs

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,8 @@ namespace Examples
88
/// <summary>
99
/// Provides an eval/print loop for command line argument strings.
1010
/// </summary>
11-
internal class Program
11+
internal static class Program
1212
{
13-
#region Private Properties
14-
15-
/// <summary>
16-
/// Gets or sets a value indicating whether show the help.
17-
/// </summary>
18-
[Argument('h', "help", "Gets or sets a value indicating whether show the help.")]
19-
private static bool Help { get; set; }
20-
2113
/// <summary>
2214
/// Gets or sets a value indicating whether the Bool argument was supplied.
2315
/// </summary>
@@ -30,6 +22,12 @@ internal class Program
3022
[Argument('f', "float", "Gets or sets the value of the Double argument.")]
3123
private static double Double { get; set; }
3224

25+
/// <summary>
26+
/// Gets or sets a value indicating whether show the help.
27+
/// </summary>
28+
[Argument('h', "help", "Gets or sets a value indicating whether show the help.")]
29+
private static bool Help { get; set; }
30+
3331
/// <summary>
3432
/// Gets or sets the value of the Int argument.
3533
/// </summary>
@@ -54,9 +52,16 @@ internal class Program
5452
[Argument('s', "string")]
5553
private static string String { get; set; }
5654

57-
#endregion Private Properties
58-
59-
#region Private Methods
55+
/// <summary>
56+
/// Returns a "pretty" string representation of the provided Type; specifically, corrects the naming of generic Types
57+
/// and appends the type parameters for the type to the name as it appears in the code editor.
58+
/// </summary>
59+
/// <param name="type">The type for which the colloquial name should be created.</param>
60+
/// <returns>A "pretty" string representation of the provided Type.</returns>
61+
public static string ToColloquialString(this Type type)
62+
{
63+
return (!type.IsGenericType ? type.Name : type.Name.Split('`')[0] + "<" + String.Join(", ", type.GetGenericArguments().Select(a => a.ToColloquialString())) + ">");
64+
}
6065

6166
/// <summary>
6267
/// Application entry point
@@ -152,22 +157,22 @@ private static void Reset()
152157
}
153158

154159
/// <summary>
155-
/// Show help for arguments.
160+
/// Show help for arguments.
156161
/// </summary>
157162
private static void ShowHelp()
158163
{
159164
var helpAttributes = Arguments.GetArgumentInfo(typeof(Program));
160165

161-
Console.WriteLine("Short\tLong\tFunction");
162-
Console.WriteLine("-----\t----\t--------");
166+
var maxLen = helpAttributes.Select(a => a.Property.PropertyType.ToColloquialString()).OrderByDescending(s => s.Length).FirstOrDefault().Length;
167+
168+
Console.WriteLine($"Short\tLong\t{"Type".PadRight(maxLen)}\tFunction");
169+
Console.WriteLine($"-----\t----\t{"----".PadRight(maxLen)}\t--------");
163170

164171
foreach (var item in helpAttributes)
165172
{
166-
var result = item.ShortName + "\t" + item.LongName + "\t" + item.HelpText;
173+
var result = item.ShortName + "\t" + item.LongName + "\t" + item.Property.PropertyType.ToColloquialString().PadRight(maxLen) + "\t" + item.HelpText;
167174
Console.WriteLine(result);
168175
}
169176
}
170-
171-
#endregion Private Methods
172177
}
173178
}

src/Utility.CommandLine.Arguments/Arguments.cs

Lines changed: 109 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,52 @@ public ArgumentAttribute(char shortName, string longName, string helpText = null
121121
public char ShortName { get; set; }
122122
}
123123

124+
/// <summary>
125+
/// Encapsulates argument names and help text.
126+
/// </summary>
127+
public class ArgumentInfo
128+
{
129+
/// <summary>
130+
/// Initializes a new instance of the <see cref="ArgumentInfo"/> class.
131+
/// </summary>
132+
/// <param name="shortName">The short name of the argument.</param>
133+
/// <param name="longName">The long name of the argument.</param>
134+
/// <param name="helpText">The help text for the argument.</param>
135+
/// <param name="property">The property with which the argument is associated.</param>
136+
public ArgumentInfo(char shortName, string longName, string helpText, PropertyInfo property)
137+
{
138+
ShortName = shortName;
139+
LongName = longName;
140+
HelpText = helpText;
141+
Property = property;
142+
}
143+
144+
/// <summary>
145+
/// Gets the help text for the argument.
146+
/// </summary>
147+
public string HelpText { get; }
148+
149+
/// <summary>
150+
/// Gets a value indicating whether the argument backing Type is a collection.
151+
/// </summary>
152+
public bool IsCollection => Property.PropertyType.IsArray || (Property.PropertyType.IsGenericType && Property.PropertyType.GetGenericTypeDefinition() == typeof(List<>));
153+
154+
/// <summary>
155+
/// Gets the long name of the argument.
156+
/// </summary>
157+
public string LongName { get; }
158+
159+
/// <summary>
160+
/// Gets the property with which the argument is associated.
161+
/// </summary>
162+
public PropertyInfo Property { get; }
163+
164+
/// <summary>
165+
/// Gets the short name of the argument.
166+
/// </summary>
167+
public char ShortName { get; }
168+
}
169+
124170
/// <summary>
125171
/// Provides static methods used to retrieve the command line arguments and operands with which the application was
126172
/// started, as well as a Type to contain them.
@@ -162,21 +208,25 @@ private Arguments(string commandLineString, List<KeyValuePair<string, string>> a
162208
}
163209

164210
/// <summary>
165-
/// Gets the target Type, if applicable.
211+
/// Gets a dictionary containing the arguments and values specified in the command line arguments with which the
212+
/// application was started.
166213
/// </summary>
167-
public Type TargetType { get; }
214+
/// <remarks>
215+
/// This dictionary contains argument key/value pairs compiled from the <see cref="ArgumentList"/> and checked against
216+
/// the <see cref="TargetType"/> to combine duplicated pairs into lists where the backing property is a collection, and
217+
/// to overwrite where the backing property is not a collection.
218+
/// </remarks>
219+
public Dictionary<string, object> ArgumentDictionary { get; }
168220

169221
/// <summary>
170222
/// Gets the list of arguments specified in the command line arguments with which the application was started.
171223
/// </summary>
224+
/// <remarks>
225+
/// This list contains each argument key/value pair as supplied in the original string, preserving the original order
226+
/// and any duplicated pairs.
227+
/// </remarks>
172228
public List<KeyValuePair<string, string>> ArgumentList { get; }
173229

174-
/// <summary>
175-
/// Gets a dictionary containing the arguments and values specified in the command line arguments with which the
176-
/// application was started.
177-
/// </summary>
178-
public Dictionary<string, object> ArgumentDictionary { get; }
179-
180230
/// <summary>
181231
/// Gets the command line string from which the arguments were parsed.
182232
/// </summary>
@@ -187,6 +237,11 @@ private Arguments(string commandLineString, List<KeyValuePair<string, string>> a
187237
/// </summary>
188238
public List<string> OperandList { get; private set; }
189239

240+
/// <summary>
241+
/// Gets the target Type, if applicable.
242+
/// </summary>
243+
public Type TargetType { get; }
244+
190245
/// <summary>
191246
/// Gets the argument value corresponding to the specified <paramref name="index"/>.
192247
/// </summary>
@@ -201,7 +256,8 @@ public object this[int index]
201256
}
202257

203258
/// <summary>
204-
/// Gets the argument value corresponding to the specified <paramref name="key"/> from the <see cref="ArgumentDictionary"/> property.
259+
/// Gets the argument value corresponding to the specified <paramref name="key"/> from the
260+
/// <see cref="ArgumentDictionary"/> property.
205261
/// </summary>
206262
/// <param name="key">The key for which the value is to be retrieved.</param>
207263
/// <returns>The argument value corresponding to the specified key.</returns>
@@ -213,43 +269,6 @@ public object this[string key]
213269
}
214270
}
215271

216-
private static Dictionary<string, object> GetArgumentDictionary(List<KeyValuePair<string, string>> argumentList, Type targetType = null)
217-
{
218-
var dict = new ConcurrentDictionary<string, object>();
219-
var argumentInfo = targetType == null ? new List<ArgumentInfo>() : GetArgumentInfo(targetType);
220-
221-
foreach (var arg in argumentList)
222-
{
223-
var info = argumentInfo.Where(i => i.ShortName.ToString() == arg.Key || i.LongName == arg.Key).SingleOrDefault();
224-
225-
if (info != default(ArgumentInfo))
226-
{
227-
bool added = false;
228-
229-
foreach (var k in new[] { info.ShortName.ToString(), info.LongName })
230-
{
231-
if (dict.ContainsKey(k))
232-
{
233-
dict.AddOrUpdate(k, arg.Value, (key, existingValue) => info.IsCollection ? ((List<object>)existingValue).Concat(new[] { arg.Value }).ToList() : (object)arg.Value);
234-
added = true;
235-
break;
236-
}
237-
}
238-
239-
if (!added)
240-
{
241-
dict.TryAdd(arg.Key, info.IsCollection ? new List<object>(new[] { arg.Value }) : (object)arg.Value);
242-
}
243-
}
244-
else
245-
{
246-
dict.AddOrUpdate(arg.Key, arg.Value, (key, existingValue) => arg.Value);
247-
}
248-
}
249-
250-
return dict.ToDictionary(a => a.Key, a => a.Value);
251-
}
252-
253272
/// <summary>
254273
/// Retrieves a collection of <see cref="ArgumentInfo"/> gathered from properties in the target <paramref name="type"/>
255274
/// marked with the <see cref="ArgumentAttribute"/><see cref="Attribute"/> along with the short and long names and help text.
@@ -268,13 +287,11 @@ private static Dictionary<string, object> GetArgumentDictionary(List<KeyValuePai
268287

269288
if (attribute != default(CustomAttributeData))
270289
{
271-
retVal.Add(new ArgumentInfo()
272-
{
273-
ShortName = (char)attribute.ConstructorArguments[0].Value,
274-
LongName = (string)attribute.ConstructorArguments[1].Value,
275-
HelpText = (string)attribute.ConstructorArguments[2].Value,
276-
Type = property.PropertyType,
277-
});
290+
retVal.Add(new ArgumentInfo(
291+
shortName: (char)attribute.ConstructorArguments[0].Value,
292+
longName: (string)attribute.ConstructorArguments[1].Value,
293+
helpText: (string)attribute.ConstructorArguments[2].Value,
294+
property: property));
278295
}
279296
}
280297

@@ -413,16 +430,7 @@ public static void Populate(Type type, Arguments arguments, bool clearExistingVa
413430
}
414431
else if (propertyType.IsArray || (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(List<>)))
415432
{
416-
// if the property is an array or list, convert the value to an array or list of the matching type. start
417-
// by converting atomic values to a list containing a single value, just to simplify processing.
418-
if (valueIsList)
419-
{
420-
convertedValue = value;
421-
}
422-
else
423-
{
424-
convertedValue = new List<object>(new object[] { value });
425-
}
433+
convertedValue = value;
426434

427435
// next, create a list with the same type as the target property
428436
Type valueType;
@@ -526,6 +534,43 @@ private static void ClearProperties(Dictionary<string, PropertyInfo> properties)
526534
}
527535
}
528536

537+
private static Dictionary<string, object> GetArgumentDictionary(List<KeyValuePair<string, string>> argumentList, Type targetType = null)
538+
{
539+
var dict = new ConcurrentDictionary<string, object>();
540+
var argumentInfo = targetType == null ? new List<ArgumentInfo>() : GetArgumentInfo(targetType);
541+
542+
foreach (var arg in argumentList)
543+
{
544+
var info = argumentInfo.Where(i => i.ShortName.ToString() == arg.Key || i.LongName == arg.Key).SingleOrDefault();
545+
546+
if (info != default(ArgumentInfo))
547+
{
548+
bool added = false;
549+
550+
foreach (var k in new[] { info.ShortName.ToString(), info.LongName })
551+
{
552+
if (dict.ContainsKey(k))
553+
{
554+
dict.AddOrUpdate(k, arg.Value, (key, existingValue) => info.IsCollection ? ((List<object>)existingValue).Concat(new[] { arg.Value }).ToList() : (object)arg.Value);
555+
added = true;
556+
break;
557+
}
558+
}
559+
560+
if (!added)
561+
{
562+
dict.TryAdd(arg.Key, info.IsCollection ? new List<object>(new[] { arg.Value }) : (object)arg.Value);
563+
}
564+
}
565+
else
566+
{
567+
dict.AddOrUpdate(arg.Key, arg.Value, (key, existingValue) => arg.Value);
568+
}
569+
}
570+
571+
return dict.ToDictionary(a => a.Key, a => a.Value);
572+
}
573+
529574
private static List<KeyValuePair<string, string>> GetArgumentList(string commandLineString)
530575
{
531576
var argumentList = new List<KeyValuePair<string, string>>();
@@ -640,37 +685,6 @@ private static PropertyInfo GetOperandsProperty(Type type)
640685
}
641686
}
642687

643-
/// <summary>
644-
/// Encapsulates argument names and help text.
645-
/// </summary>
646-
public class ArgumentInfo
647-
{
648-
/// <summary>
649-
/// Gets or sets the help text for the argument.
650-
/// </summary>
651-
public string HelpText { get; set; }
652-
653-
/// <summary>
654-
/// Gets or sets the long name of the argument.
655-
/// </summary>
656-
public string LongName { get; set; }
657-
658-
/// <summary>
659-
/// Gets or sets the short name of the argument.
660-
/// </summary>
661-
public char ShortName { get; set; }
662-
663-
/// <summary>
664-
/// Gets or sets the backing Type of the argument.
665-
/// </summary>
666-
public Type Type { get; set; }
667-
668-
/// <summary>
669-
/// Gets a value indicating whether the argument backing Type is a collection.
670-
/// </summary>
671-
public bool IsCollection => Type.IsArray || (Type.IsGenericType && Type.GetGenericTypeDefinition() == typeof(List<>));
672-
}
673-
674688
/// <summary>
675689
/// Indicates that the property is to be used as the target for automatic population of command line operands when invoking
676690
/// the <see cref="Arguments.Populate(string, bool, string)"/> method.
@@ -679,4 +693,4 @@ public class ArgumentInfo
679693
public class OperandsAttribute : Attribute
680694
{
681695
}
682-
}
696+
}

0 commit comments

Comments
 (0)