/// <summary> /// Determines if a argument's property should be set, and if it should, gets the strongly-typed object that it should be set to. /// </summary> /// <param name="name">The name of the argument we're looking for.</param> /// <param name="propertyType">The type of the property.</param> /// <param name="argumentAttributes">The ArgumentAttribute on the property.</param> /// <param name="value">The value that the argument should be set to, if the return value is true.</param> /// <returns>If true, the property should be set; if false, the property should be left alone.</returns> private bool WrangleNamedArgument(string name, Type propertyType, ArgumentAttribute argumentAttribute, IFormatProvider formatProvider, out object value) { var alias = argumentAttribute.Alias; var defaultValue = argumentAttribute.DefaultValue; // valid name for an argument? if (!ArgumentNameValid(name)) { throw new InvalidArgumentNameException(name); } // string value string strValue = null; // is the value defined? (full names override aliases) if (this.m_LongArgs.ContainsKey(name)) { strValue = m_LongArgs[name]; } else if (alias != '\0' && this.m_ShortArgs.ContainsKey(alias)) { strValue = m_ShortArgs[alias]; } else if (propertyType == typeof(bool)) // bools are always set false when omitted { value = false; return(true); } else if (defaultValue != null) // no value, fall back to default { value = defaultValue; return(true); } else // no value or default, leave the property or field unset. { value = null; return(false); } // bools are special, so if we have a value, we always return true here. if (propertyType == typeof(bool)) { value = true; return(true); } // We have a value, now we try to parse it. return(StringToObjectParser.ParseStringToObject(strValue, propertyType, formatProvider, out value)); }
/// <summary> /// Parses the string array, such as passed to the Main() function, /// and fills any matching properties or fields with /// ArgumentAttributes in a new T object. /// </summary> public static T Parse <T>(string[] rawArgs, ArgsParsingOptions parsingOptions = null) where T : new() { // default options if none provided if (parsingOptions == null) { parsingOptions = new ArgsParsingOptions(); } ////////// // step 1: parse rawArgs var parser = new ArgsParser(); // get the permitted dos-style value seperators var dosValueSeperators = parsingOptions.GetDosStyleValueSeperators(); // once set to false, no longer parses named arguments bool allowNamedArgs = true; foreach (string s in rawArgs) { // skip blank args if configured to if (string.IsNullOrEmpty(s) && !parsingOptions.AllowBlankArgs) { continue; } bool handleAsPositionalArgs = true; if (allowNamedArgs && s.Length >= 1) { if (parsingOptions.AllowDosStyle && s[0] == '/') // dos-style argument { string k, v; SplitOn(s.Substring(1), out k, out v, dosValueSeperators); parser.AddKeyValue(k, v); handleAsPositionalArgs = false; } else if (parsingOptions.AllowGnuStyle && s[0] == '-') // gnu-style argument { if (s == "-" || s == "--") { // force all remaining args to be positional allowNamedArgs = false; handleAsPositionalArgs = false; } else if (s[1] == '-') // s.Length must be > 1 becuse s != "-" && s[0] == '-' { string k, v; SplitOn(s.Substring(2), out k, out v, '='); parser.AddKeyValue(k, v); handleAsPositionalArgs = false; } else // series of short arguments { string k, v; SplitOn(s.Substring(1), out k, out v, '='); if (string.IsNullOrEmpty(v)) { // series of short arguments, like -a -b -c foreach (char c in k) { parser.AddKeyValue(c.ToString(), null); } } else { // single argument, like --abc=... parser.AddKeyValue(k, v); } handleAsPositionalArgs = false; } } } if (handleAsPositionalArgs) { parser.m_PositionalArgs.Add(s); if (allowNamedArgs && !parsingOptions.AllowNamedArgsAfterPoistionalArgs) { // don't allow any more named arguments now that we're parsing positional args allowNamedArgs = false; } } } ////////// // step 2: apply the parsed arguments to the final object var args = new T(); var argsType = args.GetType(); foreach (PropertyInfo prop in argsType.GetProperties()) { var argumentAttributes = prop.GetCustomAttributes <ArgumentAttribute>().ToArray(); var positionalArgumentAttributes = prop.GetCustomAttributes <PositionalArgumentAttribute>().ToArray(); var allPositionalsAttributes = prop.GetCustomAttributes <AllPositionalArgumentsAttribute>().ToArray(); if ((argumentAttributes.Length + positionalArgumentAttributes.Length + allPositionalsAttributes.Length) > 1) { throw new InvalidArgumentsClassException(string.Format("{0}: Multiple/conflicting attributes on {1}", argsType.FullName, prop.Name)); } else if (argumentAttributes.Length == 1) { var name = argumentAttributes[0].Name ?? prop.Name.ToLowerInvariant(); object value; if (parser.WrangleNamedArgument(name, prop.PropertyType, argumentAttributes[0], parsingOptions.FormatProvider, out value)) { prop.SetMethod.Invoke(args, new object[] { value }); } else if (argumentAttributes[0].DefaultValue != null) { if (prop.PropertyType.IsAssignableFrom(argumentAttributes[0].DefaultValue.GetType())) { prop.SetMethod.Invoke(args, new object[] { argumentAttributes[0].DefaultValue }); } else { throw new InvalidArgumentsClassException(string.Format("{0}: Incompatible default value on {1}", argsType.FullName, prop.Name)); } } } else if (positionalArgumentAttributes.Length == 1) { int n = positionalArgumentAttributes[0].N; if (n < 0) { n += parser.m_PositionalArgs.Count; } object value; if (n >= 0 && n < parser.m_PositionalArgs.Count && StringToObjectParser.ParseStringToObject(parser.m_PositionalArgs[n], prop.PropertyType, parsingOptions.FormatProvider, out value)) { prop.SetMethod.Invoke(args, new object[] { value }); } else if (positionalArgumentAttributes[0].DefaultValue != null) { if (prop.PropertyType.IsAssignableFrom(positionalArgumentAttributes[0].DefaultValue.GetType())) { prop.SetMethod.Invoke(args, new object[] { positionalArgumentAttributes[0].DefaultValue }); } else { throw new InvalidArgumentsClassException(string.Format("{0}: Incompatible default value on {1}", argsType.FullName, prop.Name)); } } } else if (allPositionalsAttributes.Length == 1) { if (prop.PropertyType.IsAssignableFrom(typeof(string[]))) { prop.SetMethod.Invoke(args, new object[] { parser.m_PositionalArgs.ToArray() }); } else { throw new InvalidArgumentsClassException(string.Format("{0}: AllPositionalArguments properties must be of type string[] or an interface string[] implements. {1} is not.", argsType.FullName, prop.Name)); } } } return(args); }