Updated on 2024-01-23
Easily crack your command line arguments into a dictionary with this code
Update 2: For a more complete solution with a drop in code file, please see this linked article.
Update: I found and fixed a small bug, so if you copied this before, you may want to do so again, with my apologies.
I don't like having to rely on libraries that require a lot of buy in for what they do. Command line argument processing should be as simple as it can be, and no simpler.
With that in mind, I've created a single function that takes a dictionary preloaded with a sort of specification it uses to generate a command line parser for those arguments, such that it can take named arguments with "typed" parameters (on the command line for instance, the first argument could be an unnamed series of strings) or you can have a /bool switch in there. Whatever.
It's not perfect, and it doesn't do wizardry like creating a "using screen" for you. What it is, is something that satisfies the 80/20 rule and speeds up development for command line tools.
It can take arguments of string[]/ICollection
This can be copied wholesale into your Program class in your code:
/// <summary>
/// Cracks command line arguments
/// </summary>
/// <param name="defaultname">The key name of the default parameter</param>
/// <param name="args">The args passed to the entry point</param>
/// <param name="required">A collection of strings that indicate the required arguments
/// </param>
/// <param name="arguments">A dictionary of arguments,
/// with empty instances of various types for the values
/// which specify the "type" of the argument</param>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="InvalidProgramException"></exception>
public static void CrackArguments(string defaultname, string[] args,
ICollection<string> required, IDictionary<string, object> arguments)
{
var argi = 0;
if (!string.IsNullOrEmpty(defaultname))
{
if (args.Length == 0 || args[0][0] == '/')
{
if (required.Contains(defaultname))
throw new ArgumentException(string.Format
("<{0}> must be specified.", defaultname));
}
else
{
var o = arguments[defaultname];
var isarr = o is string[];
var iscol = o is ICollection<string>;
if (!isarr && !iscol && !(o is string))
throw new InvalidProgramException(string.Format
("Type for {0} must be string or a string collection or array",
defaultname));
for (; argi < args.Length; ++argi)
{
var arg = args[argi];
if (arg[0] == '/') break;
if (isarr)
{
var sa = new string[((string[])o).Length + 1];
Array.Copy((string[])o, sa, sa.Length - 1);
sa[sa.Length - 1] = arg;
arguments[defaultname] = sa;
o = sa;
}
else if (iscol)
{
((ICollection<string>)o).Add(arg);
}
else if ("" == (string)o)
{
arguments[defaultname] = arg;
}
else
throw new ArgumentException(string.Format
("Only one <{0}> value may be specified.", defaultname));
}
}
}
for (; argi < args.Length; ++argi)
{
var arg = args[argi];
if (string.IsNullOrWhiteSpace(arg) || arg[0] != '/')
{
throw new ArgumentException(string.Format
("Expected switch instead of {0}", arg));
}
arg = arg.Substring(1);
if (!char.IsLetterOrDigit(arg, 0))
throw new ArgumentException("Invalid switch /{0}", arg);
object o;
if (!arguments.TryGetValue(arg, out o))
{
throw new InvalidProgramException
(string.Format("Unknown switch /{0}", arg));
}
var isarr = o is string[];
var iscol = o is ICollection<string>;
var isbool = o is bool;
var isstr = o is string;
if (isarr || iscol)
{
while (++argi < args.Length)
{
var sarg = args[argi];
if (sarg[0] == '/')
break;
if (isarr)
{
var sa = new string[((string[])o).Length + 1];
Array.Copy((string[])o, sa, sa.Length - 1);
sa[sa.Length - 1] = sarg;
arguments[arg] = sa;
o=sa;
}
else if (iscol)
{
((ICollection<string>)o).Add(sarg);
}
}
}
else if (isstr)
{
if (argi == args.Length - 1)
throw new ArgumentException
(string.Format("Missing value for /{0}", arg));
var sarg = args[++argi];
if ("" == (string)o)
{
arguments[arg] = sarg;
}
else
throw new ArgumentException
(string.Format("Only one <{0}> value may be specified.", arg));
}
else if (isbool)
{
if ((bool)o)
{
throw new ArgumentException
(string.Format("Only one /{0} switch may be specified.", arg));
}
arguments[arg] = true;
}
else
throw new InvalidProgramException(string.Format("Type for {0}
must be a boolean, a string, a string collection or a string array", arg));
}
foreach (var arg in required)
{
if (!arguments.ContainsKey(arg))
{
throw new ArgumentException(string.Format
("Missing required switch /{0}", arg));
}
var o = arguments[arg];
if (null == o || ((o is string) && ((string)o) == "") ||
((o is System.Collections.ICollection) &&
((System.Collections.ICollection)o).Count == 0) /*||
((o is bool) && (!(bool)o))*/)
throw new ArgumentException
(string.Format("Missing required switch /{0}", arg));
}
}
It can take arguments of string[]/ICollection
/// <summary>
/// Cracks command line arguments
/// </summary>
/// <param name="defaultname">The key name of the default parameter</param>
/// <param name="args">The args passed to the entry point</param>
/// <param name="required">A collection of strings that indicate the
/// required arguments</param>
/// <param name="arguments">A dictionary of arguments,
/// with empty instances of various types for the values
/// which specify the "type" of the argument</param>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="InvalidProgramException"></exception>
public static void CrackArguments(string defaultname,
string[] args, ICollection<string> required, IDictionary<string, object> arguments)
{
var argi = 0;
if (!string.IsNullOrEmpty(defaultname))
{
if (args.Length == 0 || args[0][0] == '/')
{
if (required.Contains(defaultname))
throw new ArgumentException(string.Format
("<{0}> must be specified.", defaultname));
}
else
{
var o = arguments[defaultname];
Type et = o.GetType();
var isarr = et.IsArray;
MethodInfo coladd = null;
MethodInfo parse = null;
if (isarr)
{
et = et.GetElementType();
}
else
{
foreach (var it in et.GetInterfaces())
{
if (!it.IsGenericType) continue;
var tdef = it.GetGenericTypeDefinition();
if (typeof(ICollection<>) == tdef)
{
et = et.GenericTypeArguments[0];
coladd = it.GetMethod("Add",
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.Instance,
new Type[] { et });
}
}
}
TypeConverter conv = TypeDescriptor.GetConverter(et);
if (conv != null)
{
if (!conv.CanConvertFrom(typeof(string)))
{
conv = null;
}
}
if (conv == null && !isarr && coladd == null)
{
var bt = et;
while (parse == null && bt != null)
{
try
{
parse = bt.GetMethod("Parse", BindingFlags.Static |
BindingFlags.Public);
}
catch (AmbiguousMatchException)
{
parse = bt.GetMethod("Parse", BindingFlags.Static |
BindingFlags.Public, new Type[] { typeof(string) });
}
bt = bt.BaseType;
}
}
if (!isarr && coladd == null && !(o is string) && conv == null)
throw new InvalidProgramException(string.Format
("Type for {0} must be string or a collection,
array or convertible type", defaultname));
for (; argi < args.Length; ++argi)
{
var arg = args[argi];
if (arg[0] == '/') break;
if (isarr)
{
var arr = (Array)o;
var newArr = Array.CreateInstance(et, arr.Length + 1);
Array.Copy(arr, newArr, newArr.Length - 1);
object v;
v = arg;
if (conv == null)
{
if (parse != null)
{
v = parse.Invoke(null, new object[] { arg });
}
}
else
{
v = conv.ConvertFromInvariantString(arg);
}
newArr.SetValue(v, newArr.Length - 1);
arguments[defaultname] = newArr;
o = newArr;
}
else if (coladd != null)
{
object v;
v = arg;
if (conv == null)
{
if (parse != null)
{
v = parse.Invoke(null, new object[] { arg });
}
}
else
{
v = conv.ConvertFromInvariantString(arg);
}
coladd.Invoke(o, new object[] { v });
}
else if ("" == (string)o)
{
arguments[defaultname] = arg;
}
else if (conv != null)
{
arguments[defaultname] = conv.ConvertFromInvariantString(arg);
}
else if (parse != null)
{
arguments[defaultname] = parse.Invoke(null, new object[] { arg });
}
else
throw new ArgumentException(string.Format
("Only one <{0}> value may be specified.", defaultname));
}
}
}
for (; argi < args.Length; ++argi)
{
var arg = args[argi];
if (string.IsNullOrWhiteSpace(arg) || arg[0] != '/')
{
throw new ArgumentException(string.Format
("Expected switch instead of {0}", arg));
}
arg = arg.Substring(1);
if (!char.IsLetterOrDigit(arg, 0))
throw new ArgumentException("Invalid switch /{0}", arg);
object o;
if (!arguments.TryGetValue(arg, out o))
{
throw new InvalidProgramException(string.Format("Unknown switch /{0}", arg));
}
Type et = o.GetType();
var isarr = et.IsArray;
MethodInfo coladd = null;
MethodInfo parse = null;
var isbool = o is bool;
var isstr = o is string;
if (isarr)
{
et = et.GetElementType();
}
else
{
foreach (var it in et.GetInterfaces())
{
if (!it.IsGenericType) continue;
var tdef = it.GetGenericTypeDefinition();
if (typeof(ICollection<>) == tdef)
{
et = et.GenericTypeArguments[0];
coladd = it.GetMethod("Add", System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.Instance, new Type[] { et });
break;
}
}
}
TypeConverter conv = TypeDescriptor.GetConverter(et);
if (conv != null)
{
if (!conv.CanConvertFrom(typeof(string)))
{
conv = null;
}
}
if (conv == null)
{
var bt = et;
while (parse == null && bt != null)
{
try
{
parse = bt.GetMethod("Parse", BindingFlags.Static |
BindingFlags.Public);
}
catch (AmbiguousMatchException)
{
parse = bt.GetMethod("Parse", BindingFlags.Static |
BindingFlags.Public, new Type[] { typeof(string) });
}
bt = bt.BaseType;
}
}
if (isarr || coladd != null)
{
while (++argi < args.Length)
{
var sarg = args[argi];
if (sarg[0] == '/')
break;
if (isarr)
{
var arr = (Array)o;
var newArr = Array.CreateInstance(et, arr.Length + 1);
Array.Copy(newArr, arr, arr.Length - 1);
object v=sarg;
if (conv == null)
{
if (parse != null)
{
v = parse.Invoke(null, new object[] { sarg });
}
}
else
{
v = conv.ConvertFromInvariantString(sarg);
}
newArr.SetValue(v, arr.Length - 1);
}
else if (coladd != null)
{
object v=sarg;
if (conv == null)
{
if (parse != null)
{
v = parse.Invoke(null, new object[] { sarg });
}
}
else
{
v = conv.ConvertFromInvariantString(sarg);
}
coladd.Invoke(o, new object[] { v });
}
}
}
else if (isstr)
{
if (argi == args.Length - 1)
throw new ArgumentException(string.Format("Missing value for /{0}", arg));
var sarg = args[++argi];
if ("" == (string)o)
{
arguments[arg] = sarg;
}
else
throw new ArgumentException(string.Format
("Only one <{0}> value may be specified.", arg));
}
else if (isbool)
{
if ((bool)o)
{
throw new ArgumentException(string.Format
("Only one /{0} switch may be specified.", arg));
}
arguments[arg] = true;
}
else if (conv != null)
{
if (argi == args.Length - 1)
throw new ArgumentException(string.Format("Missing value for /{0}", arg));
arguments[arg] = conv.ConvertFromInvariantString(args[++argi]);
}
else if (parse != null)
{
arguments[arg] = parse.Invoke(o, new object[] { args[++argi] });
}
else
throw new InvalidProgramException(string.Format
("Type for {0} must be a boolean, a string, a string collection,
a string array, or a convertible type", arg));
}
foreach (var arg in required)
{
if (!arguments.ContainsKey(arg))
{
throw new ArgumentException(string.Format
("Missing required switch /{0}", arg));
}
var o = arguments[arg];
if (null == o || ((o is string) && ((string)o) == "") ||
((o is System.Collections.ICollection) &&
((System.Collections.ICollection)o).Count == 0) /*||
((o is bool) && (!(bool)o))*/)
throw new ArgumentException(string.Format
("Missing required switch /{0}", arg));
}
}
Simple Routine Command Line:
example.exe foo.txt bar.txt /output foobar.cs /ifstale
Advanced Routine Command Line
example.exe foo.txt bar.txt /output foobar.cs /id 5860F36D-6207-47F9-9909-62F2B403BBA8
/ips 192.168.0.104 192.168.0.200 /ifstale /count 5 /enum static /indices 5 6 7 8
static int Main(string[] args)
{
var arguments = new Dictionary<string, object>();
arguments.Add("inputs", new string[0]); // the input files (can be a List<string>)
arguments.Add("output", ""); // the output file
arguments.Add("ifstale", false);
// following is for advanced routine
//arguments.Add("id", Guid.Empty);
//arguments.Add("ips", new List<IPAddress>());
//arguments.Add("count", 0);
//arguments.Add("indices", new List<int>());
//arguments.Add("enum", System.Reflection.BindingFlags.Instance);
CrackArguments("inputs", args, new string[] { "inputs" }, arguments);
foreach (var entry in arguments)
{
Console.Write(entry.Key + ": ");
var v = entry.Value;
if (v is string)
{
Console.WriteLine((string)v);
}
else
if (v is System.Collections.IEnumerable)
{
var e = (System.Collections.IEnumerable)v;
Console.Write("Type: {0}: -> ", v.GetType());
var delim = "";
foreach (var item in e)
{
Console.Write(delim);
Console.Write("Type {0}: ", item?.GetType());
Console.Write(item);
delim = ", ";
}
Console.WriteLine();
}
else
{
Console.Write("Type {0}: ", v?.GetType());
Console.WriteLine(v);
}
}
return 0;
}
That's all there is to it! It supports TypeConverter and Parse() now but you can still extend it as you like.
Parse()
methods