commandline
commandline copied to clipboard
Enhance to deal wtih csv's/arrays
I would like to be able to pass arrays to my app. To do so I have to use string valued options and parse them myself. Is it possible to add the ability do deal with them?
e.g.,
-S[item1,item2,item3]
etc.
and/or allow for a custom parser. Sometimes I use different methods to specify multiple values. e.g., -SKey:Value
and I have to parse it by hand using split.
Basically we could specify a class or struct that can have not only the values but a custom parser.
struct CompoundOption { string key; int value; CompoundOption Parse(string o) { var c = new CompoundOption(); c.key = o.Split(':')[0]; c.value = o.Split(':')[1]; return c } }
or whatever
What's wrong with -S item1 item2 item3
with an IEnumerable<string>
parameter?
[Option('v')]
public IEnumerable<string> Vals { get; set; }
Nothing except one still has to do conversion. What if you have a mix up enums, strings, and ints? returning a tuple of the correct type would be better. In any case, the real issue is allowing for key/value or larger types.
I'm having some issues with your library. Mainly, I like to use complex arguments but grouping is not handled properly.
e.g., -s[arg1|arg 2] is parsed as [arg1|arg
I think, one has to use -s= just to get it to work too.
I wrote some helper routines to help parse csv's and some complex arguments a bit easier. The idea is that we can parse them in to a typed value. The code was thrown together so it's not great but should demonstrate the idea. Basically by passing it something like [A|B,C|D] it it will parse it in to a list of tuple(A, B), tuple(C,D) with the correct types. Unfortunately if they contain spaces or happen not to be perfect, the parser doesn't return something usable. I think it is simply due to the parser not being aware of groups.
e.g., -s["A B C"|"D E F"] is parsed correctly but -s[A B C|D E F] returns [A
But it's clear and less verbose to allow the second case. I would suggest that you add an option to parse the groups like that for the 4 types: (), [], {}, <> + `, ", ' if you don't have them already. They should all be optional because they are not always warranted.
also, if you can understand the code below and rewrite it to be more robust then you could use it to automatically parse your complex types easier. I mainly use them to parse enums and strings
ParseCSVOptionsArray<string, string, int>(options.Search, new string[] { "&|:" });
will parse stuff like [a|b,c|d,e|f:3] in to 3 tuples of 3 values each, tuple(a,b,0), tuple(c,d,0), tuple(e,f,3). By supplying an enum, we are only allowed to supply names from the enum. The main use is that these are typed and it's one line of code.
The main feature it is missing as of yet is storing the custom separator. They are meant for boolean type of specifications. a|b means a or b, a&b means a & b, but one must keep track of the separator.
static List<T> ParseCSVOptionsArray<T>(string str, string[] seperatorTree = null)
{
List<T> res2 = new List<T>();
var res = ParseCSVOptionsArray<T, int, int, int>(str, seperatorTree, 1);
foreach (var v in res) res2.Add(v.Item1);
return res2;
}
static List<Tuple<T1, T2>> ParseCSVOptionsArray<T1, T2>(string str, string[] seperatorTree = null)
{
List<Tuple<T1, T2>> res2 = new List<Tuple<T1, T2>>();
var res = ParseCSVOptionsArray<T1, T2, int, int>(str, seperatorTree, 2);
foreach (var v in res) res2.Add(new Tuple<T1, T2>(v.Item1, v.Item2));
return res2;
}
static List<Tuple<T1, T2, T3>> ParseCSVOptionsArray<T1, T2, T3>(string str, string[] seperatorTree = null)
{
List<Tuple<T1, T2, T3>> res2 = new List<Tuple<T1, T2, T3>>();
var res = ParseCSVOptionsArray<T1, T2, T3, int>(str, seperatorTree, 3);
foreach (var v in res) res2.Add(new Tuple<T1, T2, T3>(v.Item1, v.Item2, v.Item3));
return res2;
}
static List<Tuple<T1, T2, T3, T4>> ParseCSVOptionsArray<T1, T2, T3, T4>(string str, string[] seperatorTree = null, int numArgs = -1)
{
if (numArgs == -1) numArgs = 4;
var gArgs = new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) };
var results = new List<Tuple<T1, T2, T3, T4>>();
if (str == null || str == "") return results;
str = str.ToLower();
if (seperatorTree == null) seperatorTree = new string[] { ":" };
str = str.Trim('[', ']', '<', '>', '{', '}', '"');
var csv = str.Split(',');
for (int k = 0; k < csv.Length; k++)
{
var res = new List<object>();
var val = csv[k];
var val2 = "";
int j = 0;
while (val != null && val != "" && numArgs > j && val.Split(seperatorTree[Math.Min(seperatorTree.Length - 1, j)].ToArray()).Length > 0)
{
var seperators = seperatorTree[Math.Min(seperatorTree.Length - 1, j)].ToArray();
if (!(val.Length - (val2.Length + 1) > 0))
break;
if (val2 != "")
val = val.Substring(val2.Length + 1);
if (seperators.Contains(val[0])) val = " " + val;
val2 = val.Split(seperatorTree[Math.Min(seperatorTree.Length-1, j)].ToArray())[0];
var mi = (new List<MethodInfo>(gArgs[j].GetMethods()).Find((m) => { return (m.Name == "TryParse") && (m.GetParameters().Length == 2 + ((gArgs[j].IsPrimitive) ? 2 : 0)); })) ?? (new List<MethodInfo>(gArgs[j].BaseType.GetMethods()).Find((m) => { return (m.Name == "TryParse") && (m.GetParameters().Length == 2 + ((gArgs[j].IsEnum) ? 1 : 0)); }));
// use TryParse on types
if (mi != null)
{
if (mi.IsGenericMethod)
mi = mi.MakeGenericMethod(gArgs[j]);
var v = Activator.CreateInstance(gArgs[j], new object[] { });
var args = new object[] { val2, v };
if (gArgs[j].IsPrimitive)
args = new object[] { (val2.Length > 2 && val2[0] == '0' && val2[1] == 'x') ? val2.Substring(2) : val2, (val2.Length > 2 && val2[0] == '0' && val2[1] == 'x') ? NumberStyles.HexNumber : NumberStyles.Number, CultureInfo.InvariantCulture, v };
if (gArgs[j].IsEnum)
args = new object[] { val2, true, v };
if (mi != null && (bool)mi.Invoke(null, args))
{
// v holds the converted value of the enum
if (gArgs[j].IsEnum)
res.Add(args[2]);
else
res.Add(args[1 + ((gArgs[j].IsPrimitive) ? 2 : 0)]);
}
else
{
Console.WriteLine("Error parsing csv's: Parsing Failed! ");
Environment.Exit(-2);
}
} else if (gArgs[j] == typeof(string) || gArgs[j] == typeof(char))
{
res.Add(val2);
}
else
{
Console.WriteLine("Error parsing csv's, No Conversion Implemented!");
Environment.Exit(-2);
}
j++;
}
int M = res.Count;
for (int i = 0; i < 4 - M; i++) res.Add(null);
// Generic Tuple Create
dynamic tup = (new List<MethodInfo>(typeof(Tuple).GetMethods()).Find((m) => { return (m.Name == "Create") && (m.GetParameters().Length == gArgs.Length); })).MakeGenericMethod(gArgs).Invoke(null, res.ToArray());
if (tup == null)
{
Console.WriteLine("Fatal error parsing csv's");
Environment.Exit(-2);
}
results.Add(tup);
}
return results;
}