// Copyright 2009 (c) Charles Cook <charlescook@cookcomputing.com> using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; namespace CookComputing { class NOptFunc { //-------------------------------------------------------------------------/ public static object Run(MethodInfo mi, string[] args) { return Run(null, mi, args); } //-------------------------------------------------------------------------/ public static object Run(object instance, MethodInfo mi, string[] args) { var result = AnalyzeParameters(mi); ParseArgs(args, result.Item1, result.Item2); object[] parameters = result.Item1.Select(v => v.Value).Concat( result.Item2.Select(v => v.Value)).ToArray(); return mi.Invoke(instance, parameters); } //-------------------------------------------------------------------------/ private static Tuple<List<Positional>, List<Option>> AnalyzeParameters( MethodInfo mi) { var positionals = new List<Positional>(); var options = new List<Option>(); var pis = mi.GetParameters(); bool gotPositionalArray = false; foreach (var pi in pis) { if (pi.RawDefaultValue is DBNull) { if (pi.ParameterType.IsArray) gotPositionalArray = true; else if (gotPositionalArray) throw new Exception("positional after positional array"); positionals.Add(new Positional() { Type = pi.ParameterType }); } else { var option = new Option(); option.ArgType = pi.ParameterType; option.Default = pi.RawDefaultValue; option.Value = option.Default; string longName = pi.Name.Replace('_', '-'); char shortName = MatchShortName(pi.Name, options); OptionAttribute attr = (OptionAttribute)Attribute.GetCustomAttribute( pi, typeof(OptionAttribute)); if (attr != null) { if (attr.Long != null) { if (options.Select(opt => opt.Long).Contains(attr.Long)) throw new Exception("attributed long name in use"); else option.Long = attr.Long; } else option.Long = longName; if (attr.Short != default(char)) { if (options.Select(opt => opt.Short).Contains(attr.Short)) throw new Exception("attributed short name in use"); else option.Short = attr.Short; } else option.Short = shortName; } else { option.Long = longName; option.Short = shortName; } if (shortName == default(char)) throw new Exception("unable to find suitable short name"); options.Add(option); } } return new Tuple<List<Positional>, List<Option>>(positionals, options); } //-------------------------------------------------------------------------/ private static char MatchShortName(string name, List<Option> options) { return name.Except(options.Select(opt => opt.Short)).FirstOrDefault(); } //-------------------------------------------------------------------------/ private static void ParseArgs(string[] args, List<Positional> positionals, List<Option> options) { var argEnumerator = args.AsEnumerable().GetEnumerator(); var posEnumerator = positionals.GetEnumerator() as IEnumerator<Positional>; posEnumerator.MoveNext(); while (argEnumerator.MoveNext() && argEnumerator.Current != null) { string arg = argEnumerator.Current; if (arg.StartsWith("--")) { if (arg.Length == 2) throw new Exception("Invalid option"); ProcessLongOption(arg.Substring(2), argEnumerator, options); } else if (arg.StartsWith("-")) { if (arg.Length == 1) throw new Exception("Invalid option"); ProcessShortOption(arg.Substring(1), argEnumerator, options); } else ProcessPositional(arg, posEnumerator); } if (posEnumerator.Current != null && !posEnumerator.Current.Type.IsArray) throw new Exception("missing positional argument"); Positional last; if (positionals.Count > 0 && (last = positionals.Last()).Value is ArrayList) { Type elemType = last.Type.GetElementType(); last.Value = (last.Value as ArrayList).ToArray(elemType); } } //-------------------------------------------------------------------------/ private static void ProcessLongOption(string arg, IEnumerator<string> argEnumerator, List<Option> options) { int idx = arg.IndexOf("="); string name = idx > 0 ? arg.Substring(0, idx) : arg; var optional = options.Where(opt => opt.Long == name).FirstOrDefault(); if (optional == null) throw new Exception("invalid long option"); if (idx >= 0) { if (optional.ArgType == typeof(bool)) throw new Exception("long option boolean has a value"); optional.Value = arg.Substring(idx + 1); } else { if (optional.ArgType == typeof(bool)) optional.Value = !(bool)optional.Default; else { argEnumerator.MoveNext(); if (argEnumerator.Current == null) throw new Exception("missing value"); optional.Value = Convert(argEnumerator.Current, optional.ArgType); } } } //-------------------------------------------------------------------------/ private static void ProcessShortOption(string arg, IEnumerator<string> argEnumerator, List<Option> options) { var optional = options.Where( opt => opt.Short == arg[0]).FirstOrDefault(); if (optional == null) throw new Exception("invalid short option"); if (optional.ArgType == typeof(bool)) { optional.Value = !(bool)optional.Default; if (arg.Length > 1) ProcessShortOption(arg.Substring(1), argEnumerator, options); return; } arg = arg.Substring(1); if (arg != "") optional.Value = Convert(arg, optional.ArgType); else { argEnumerator.MoveNext(); if (argEnumerator.Current == null) throw new Exception("missing value"); optional.Value = Convert(argEnumerator.Current, optional.ArgType); } } //-------------------------------------------------------------------------/ private static void ProcessPositional(string arg, IEnumerator<Positional> posEnumerator) { Positional positional = posEnumerator.Current; if (positional == null) throw new Exception("unexpected positional"); if (positional.Type.IsArray) { Type elemType = positional.Type.GetElementType(); object value = Convert(arg, elemType); if (positional.Value == null) positional.Value = new ArrayList(); (positional.Value as ArrayList).Add(value); } else { positional.Value = Convert(arg, positional.Type); posEnumerator.MoveNext(); } } //-------------------------------------------------------------------------/ private static object Convert(string arg, Type type) { if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { Type nullableType = type.GetGenericArguments()[0]; object obj = Convert(arg, nullableType); object ret = Activator.CreateInstance(type, new object[] { obj }); return ret; } else if (type.IsEnum) { object ret = Enum.Parse(type, arg); return ret; } else { object ret = System.Convert.ChangeType(arg, type); return ret; } } //-------------------------------------------------------------------------/ private class Positional { public Type Type { get; set; } public object Value { get; set; } } //-------------------------------------------------------------------------/ private class Option { public Type ArgType { get; set; } public char Short { get; set; } public string Long { get; set; } public object Default { get; set; } public object Value { get; set; } } //-------------------------------------------------------------------------/ } [AttributeUsage(AttributeTargets.Parameter)] public class OptionAttribute : Attribute { public OptionAttribute() { } public string Description { get; set; } public string Long { get; set; } public char Short { get; set; } } }