/// <summary> /// Parses the command line into a default-constructed arguments object. Option definitions are determined by the attributes on the properties of the arguments object. This method will call <see cref="IOptionArguments.Validate"/> on the returned value before returning. /// </summary> /// <typeparam name="T">The type of arguments object to initialize.</typeparam> /// <param name="commandLine">The command line to parse, not including the process name. If <c>null</c>, the process' command line is lexed by <see cref="ConsoleCommandLineLexer"/>.</param> /// <param name="parserCollection">A parser collection to use for parsing, or <c>null</c> to use the default parsers.</param> /// <param name="stringComparer">The string comparison to use when parsing options. If <c>null</c>, then the string comparer for the current culture is used.</param> /// <returns>The arguments object.</returns> public static T Parse <T>(IEnumerable <string> commandLine = null, SimpleParserCollection parserCollection = null, StringComparer stringComparer = null) where T : class, IOptionArguments, new() { T ret = new T(); ret.Parse(commandLine, parserCollection, stringComparer); return(ret); }
/// <summary> /// Parses the command line into an arguments object. Option definitions are determined by the attributes on the properties of <paramref name="argumentsObject"/>. This method will call <see cref="IOptionArguments.Validate"/>. /// </summary> /// <typeparam name="T">The type of arguments object to initialize.</typeparam> /// <param name="argumentsObject">The arguments object that is initialized. May not be <c>null</c>.</param> /// <param name="commandLine">The command line to parse, not including the process name. If <c>null</c>, the process' command line is lexed by <see cref="ConsoleCommandLineLexer"/>.</param> /// <param name="parserCollection">A parser collection to use for parsing, or <c>null</c> to use the default parsers.</param> /// <param name="stringComparer">The string comparison to use when parsing options. If <c>null</c>, then the string comparer for the current culture is used.</param> public static void Parse <T>(this T argumentsObject, IEnumerable <string> commandLine = null, SimpleParserCollection parserCollection = null, StringComparer stringComparer = null) where T : class, IOptionArguments { Contract.Requires(argumentsObject != null); if (parserCollection == null) { parserCollection = new SimpleParserCollection(); } if (stringComparer == null) { stringComparer = StringComparer.CurrentCulture; } // Generate option definitions from OptionAttributes on the arguments object. var options = new Dictionary <OptionDefinition, Action <string> >(); var positionalArguments = new List <Action <string> >(); Action <string> remainingPositionalArguments = null; var argumentsObjectType = argumentsObject.GetType(); foreach (var property in argumentsObjectType.GetProperties()) { var localProperty = property; // If the property specifies a [SimpleParser], then create a parser for that property. var parserOverrideAttribute = property.GetCustomAttributes(typeof(SimpleParserAttribute), true).OfType <SimpleParserAttribute>().FirstOrDefault(); var parserOverride = ((parserOverrideAttribute == null) ? null : Activator.CreateInstance(parserOverrideAttribute.ParserType)) as ISimpleParser; foreach (var attribute in property.GetCustomAttributes(true)) { // Handle [Option] attributes. var optionAttribute = attribute as OptionAttribute; if (optionAttribute != null) { var optionDefinition = new OptionDefinition { LongName = optionAttribute.LongName, ShortName = optionAttribute.ShortName, Argument = optionAttribute.Argument }; if (optionDefinition.Argument == OptionArgument.None) { // If the option takes no arguments, it must be applied to a boolean property. if (localProperty.PropertyType != typeof(bool)) { throw new InvalidOperationException("An OptionAttribute with no Argument may only be applied to a boolean property."); } // If the option is specified, set the property to true. options.Add(optionDefinition, _ => localProperty.SetOptionProperty(argumentsObject, true)); } else { // If the option takes an argument, then attempt to parse it to the correct type. options.Add(optionDefinition, parameter => { if (parameter == null) { return; } var value = parserOverride != null ? parserOverride.TryParse(parameter) : parserCollection.TryParse(parameter, localProperty.PropertyType); if (value == null) { throw new OptionParsingException.OptionArgumentException("Could not parse " + parameter + " as " + FriendlyTypeName(Nullable.GetUnderlyingType(localProperty.PropertyType) ?? localProperty.PropertyType)); } localProperty.SetOptionProperty(argumentsObject, value); }); } } // Handle [PositionalArgument] attributes. var positionalArgumentAttribute = attribute as PositionalArgumentAttribute; if (positionalArgumentAttribute != null) { if (positionalArguments.Count <= positionalArgumentAttribute.Index) { positionalArguments.AddRange(new Action <string> [positionalArgumentAttribute.Index - positionalArguments.Count + 1]); Contract.Assume(positionalArguments.Count > positionalArgumentAttribute.Index); } if (positionalArguments[positionalArgumentAttribute.Index] != null) { throw new InvalidOperationException("More than one property has a PositionalArgumentAttribute.Index of " + positionalArgumentAttribute.Index + "."); } // If the positional argument is specified, then attempt to parse it to the correct type. positionalArguments[positionalArgumentAttribute.Index] = parameter => { Contract.Assume(parameter != null); var value = parserOverride != null?parserOverride.TryParse(parameter) : parserCollection.TryParse(parameter, localProperty.PropertyType); if (value == null) { throw new OptionParsingException.OptionArgumentException("Could not parse " + parameter + " as " + FriendlyTypeName(Nullable.GetUnderlyingType(localProperty.PropertyType) ?? localProperty.PropertyType)); } localProperty.SetOptionProperty(argumentsObject, value); }; } // Handle [PositionalArguments] attributes. var positionalArgumentsAttribute = attribute as PositionalArgumentsAttribute; if (positionalArgumentsAttribute != null) { if (remainingPositionalArguments != null) { throw new InvalidOperationException("More than one property has a PositionalArgumentsAttribute."); } var addMethods = localProperty.PropertyType.GetMethods().Where(x => x.Name == "Add" && x.GetParameters().Length == 1); if (!addMethods.Any()) { throw new InvalidOperationException("Property with PositionalArgumentsAttribute does not implement an Add method taking exactly one parameter."); } if (addMethods.Count() != 1) { throw new InvalidOperationException("Property with PositionalArgumentsAttribute has more than one Add method taking exactly one parameter."); } var addMethod = addMethods.First(); // As the remaining positional arguments are specified, then attempt to parse it to the correct type and add it to the collection. remainingPositionalArguments = parameter => { Contract.Assume(parameter != null); var value = parserOverride != null?parserOverride.TryParse(parameter) : parserCollection.TryParse(parameter, addMethod.GetParameters()[0].ParameterType); if (value == null) { throw new OptionParsingException.OptionArgumentException("Could not parse " + parameter + " as " + FriendlyTypeName(Nullable.GetUnderlyingType(addMethod.GetParameters()[0].ParameterType) ?? addMethod.GetParameters()[0].ParameterType)); } addMethod.Invoke(localProperty.GetValue(argumentsObject, null), new[] { value }); }; } } } // Handle [OptionPresent] attributes. var optionDefinitions = options.Keys; foreach (var property in argumentsObjectType.GetProperties()) { var localProperty = property; var optionPresentAttribute = property.GetCustomAttributes(typeof(OptionPresentAttribute), true).OfType <OptionPresentAttribute>().FirstOrDefault(); if (optionPresentAttribute == null) { continue; } // This attribute must be applied to a boolean property. if (localProperty.PropertyType != typeof(bool)) { throw new InvalidOperationException("An OptionPresentAttribute may only be applied to a boolean property."); } OptionDefinition optionDefinition = null; if (optionPresentAttribute.LongName != null) { optionDefinition = optionDefinitions.FirstOrDefault(x => stringComparer.Equals(x.LongName, optionPresentAttribute.LongName)); } else { optionDefinition = optionDefinitions.FirstOrDefault(x => stringComparer.Equals(x.ShortNameAsString, optionPresentAttribute.ShortNameAsString)); } if (optionDefinition == null) { throw new InvalidOperationException("OptionPresentAttribute does not refer to an existing OptionAttribute for option " + optionPresentAttribute.Name); } // If the option is specified, set the property to true. options[optionDefinition] = (Action <string>)Delegate.Combine(options[optionDefinition], (Action <string>)(_ => localProperty.SetOptionProperty(argumentsObject, true))); } // Verify options. for (int i = 0; i != positionalArguments.Count; ++i) { if (positionalArguments[i] == null) { throw new InvalidOperationException("No property has a PositionalArgumentAttribute with Index of " + i + "."); } } if (remainingPositionalArguments == null) { throw new InvalidOperationException("No property has a PositionalArgumentsAttribute."); } // Parse the command line, filling in the property values. var parser = OptionParser.Parse(commandLine, optionDefinitions, stringComparer); int positionalArgumentIndex = 0; foreach (var option in parser) { Contract.Assume(option != null); if (option.Definition == null) { if (positionalArgumentIndex < positionalArguments.Count) { var action = positionalArguments[positionalArgumentIndex]; Contract.Assume(action != null); action(option.Argument); } else { remainingPositionalArguments(option.Argument); } ++positionalArgumentIndex; } else { var action = options[option.Definition]; Contract.Assume(action != null); action(option.Argument); } } // Have the arguments object perform its own validation. argumentsObject.Validate(); }
/// <summary> /// Parses the command line into an arguments object. The <see cref="Definitions"/> property is ignored; option definitions are determined by the attributes on the properties of <paramref name="argumentsObject"/>. /// </summary> /// <typeparam name="T">The type of arguments object to initialize.</typeparam> /// <param name="commandLine">The command line to parse.</param> /// <param name="argumentsObject">The arguments object that is initialized.</param> /// <param name="parserCollection">A parser collection to use for parsing, or <c>null</c> to use the default parsers.</param> private void Parse <T>(IEnumerable <string> commandLine, T argumentsObject, SimpleParserCollection parserCollection = null) where T : class, IOptionArguments { if (parserCollection == null) { parserCollection = new SimpleParserCollection(); } // Generate option definitions from OptionAttributes on the arguments object. var options = new Dictionary <OptionDefinition, Action <string> >(); var positionalArguments = new List <Action <string> >(); Action <string> remainingPositionalArguments = null; var argumentsObjectType = argumentsObject.GetType(); foreach (var property in argumentsObjectType.GetProperties()) { var localProperty = property; var parserOverrideAttribute = property.GetCustomAttributes(typeof(SimpleParserAttribute), true).OfType <SimpleParserAttribute>().FirstOrDefault(); var parserOverride = (parserOverrideAttribute == null) ? null : Activator.CreateInstance(parserOverrideAttribute.ParserType) as ISimpleParser; foreach (var attribute in property.GetCustomAttributes(true)) { var optionAttribute = attribute as OptionAttribute; if (optionAttribute != null) { var optionDefinition = new OptionDefinition { LongName = optionAttribute.LongName, ShortName = optionAttribute.ShortName, Argument = optionAttribute.Argument }; if (optionDefinition.Argument == OptionArgument.None) { if (localProperty.PropertyType != typeof(bool)) { throw new InvalidOperationException("An OptionAttribute with no Argument may only be applied to a boolean property."); } options.Add(optionDefinition, _ => localProperty.SetValue(argumentsObject, true, null)); } else { options.Add(optionDefinition, parameter => { var value = parserOverride != null ? parserOverride.TryParse(parameter) : parserCollection.TryParse(parameter, localProperty.PropertyType); if (value == null) { throw new OptionParsingException.OptionArgumentException("Could not parse " + parameter + " as " + localProperty.PropertyType.Name); } localProperty.SetValue(argumentsObject, value, null); }); } } var positionalArgumentAttribute = attribute as PositionalArgumentAttribute; if (positionalArgumentAttribute != null) { if (positionalArguments.Count <= positionalArgumentAttribute.Index) { positionalArguments.AddRange(EnumerableEx.Repeat((Action <string>)null, positionalArgumentAttribute.Index - positionalArguments.Count + 1)); } if (positionalArguments[positionalArgumentAttribute.Index] != null) { throw new InvalidOperationException("More than one property has a PositionalArgumentAttribute.Index of " + positionalArgumentAttribute.Index + "."); } positionalArguments[positionalArgumentAttribute.Index] = parameter => { var value = parserOverride != null?parserOverride.TryParse(parameter) : parserCollection.TryParse(parameter, localProperty.PropertyType); if (value == null) { throw new OptionParsingException.OptionArgumentException("Could not parse " + parameter + " as " + localProperty.PropertyType.Name); } localProperty.SetValue(argumentsObject, value, null); }; } var positionalArgumentsAttribute = attribute as PositionalArgumentsAttribute; if (positionalArgumentsAttribute != null) { if (remainingPositionalArguments != null) { throw new InvalidOperationException("More than one property has a PositionalArgumentsAttribute."); } var addMethods = localProperty.PropertyType.GetMethods().Where(x => x.Name == "Add" && x.GetParameters().Length == 1); if (!addMethods.Any()) { throw new InvalidOperationException("Property with PositionalArgumentsAttribute does not implement an Add method taking exactly one parameter."); } if (addMethods.Count() != 1) { throw new InvalidOperationException("Property with PositionalArgumentsAttribute has more than one Add method taking exactly one parameter."); } var addMethod = addMethods.First(); remainingPositionalArguments = parameter => { var value = parserOverride != null?parserOverride.TryParse(parameter) : parserCollection.TryParse(parameter, addMethod.GetParameters()[0].ParameterType); if (value == null) { throw new OptionParsingException.OptionArgumentException("Could not parse " + parameter + " as " + addMethod.GetParameters()[0].ParameterType.Name); } addMethod.Invoke(localProperty.GetValue(argumentsObject, null), new[] { value }); }; } } } // Verify options. for (int i = 0; i != positionalArguments.Count; ++i) { if (positionalArguments[i] == null) { throw new InvalidOperationException("No property has a PositionalArgumentAttribute with Index of " + i + "."); } } if (remainingPositionalArguments == null) { throw new InvalidOperationException("No property has a PositionalArgumentsAttribute."); } // Parse the command line, filling in the property values. var parser = new Parser(options.Keys, this.StringComparer, commandLine); int positionalArgumentIndex = 0; foreach (var option in parser) { if (option.Definition == null) { if (positionalArgumentIndex < positionalArguments.Count) { positionalArguments[positionalArgumentIndex](option.Argument); } else { remainingPositionalArguments(option.Argument); } ++positionalArgumentIndex; } else { options[option.Definition](option.Argument); } } argumentsObject.Validate(); }
/// <summary> /// Parses the command line into an arguments object. Option definitions are determined by the attributes on the properties of <paramref name="argumentsObject"/>. /// </summary> /// <typeparam name="T">The type of arguments object to initialize.</typeparam> /// <param name="commandLine">The command line to parse.</param> /// <param name="argumentsObject">The arguments object that is initialized.</param> /// <param name="parserCollection">A parser collection to use for parsing, or <c>null</c> to use the default parsers.</param> /// <param name="stringComparer">The string comparison to use when searching option definitions.</param> public static void Parse <T>(IEnumerable <string> commandLine, T argumentsObject, SimpleParserCollection parserCollection = null, StringComparer stringComparer = null) where T : class, IOptionArguments { new OptionParser { StringComparer = stringComparer }.Parse(commandLine, argumentsObject, parserCollection); }