/// <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(); }
public IEnumerator <Option> GetEnumerator() { foreach (var value in this.commandLine) { // If lastOption is not null, then it must be an option that can take an argument. Contract.Assert(this.lastOption == null || this.lastOption.Argument != OptionArgument.None); if (this.done) { Contract.Assert(this.lastOption == null); yield return(new Option { Argument = value }); continue; } if (this.lastOption == null || this.lastOption.Argument == OptionArgument.Optional) { if (value == "--") { // End-of-options marker if (this.lastOption != null) { // The last option was an option that takes an optional argument, without an argument. yield return(new Option { Definition = this.lastOption }); this.lastOption = null; } // All future parameters are positional arguments. this.done = true; continue; } if (value.StartsWith("--")) { // Long option if (this.lastOption != null) { // The last option was an option that takes an optional argument, without an argument. yield return(new Option { Definition = this.lastOption }); } string option = null; string argument = null; int argumentIndex = value.IndexOfAny(ArgumentDelimiters, 2); if (argumentIndex == -1) { option = value.Substring(2); } else { option = value.Substring(2, argumentIndex - 2); argument = value.Substring(argumentIndex + 1); } this.lastOption = this.definitions.FirstOrDefault(x => this.stringComparer.Equals(x.LongName, option)); if (this.lastOption == null) { throw new OptionParsingException.UnknownOptionException("Unknown option " + option + " in parameter " + value); } if (argument != null) { // There is an argument with this long option, so it is complete. if (this.lastOption.Argument == OptionArgument.None) { throw new OptionParsingException.OptionArgumentException("Option " + option + " cannot take an argument in parameter " + value); } yield return(new Option { Definition = this.lastOption, Argument = argument }); this.lastOption = null; continue; } if (this.lastOption.Argument == OptionArgument.None) { // This long option does not take an argument, so it is complete. yield return(new Option { Definition = this.lastOption }); this.lastOption = null; continue; } // This long option does take an argument. continue; } if (value.StartsWith("-")) { // Short option or short option run if (this.lastOption != null) { // The last option was an option that takes an optional argument, without an argument. yield return(new Option { Definition = this.lastOption }); } if (value.Length < 2) { throw new OptionParsingException.InvalidParameterException("Invalid parameter " + value); } string option = value[1].ToString(); this.lastOption = this.definitions.FirstOrDefault(x => this.stringComparer.Equals(x.ShortName.ToString(), option)); if (this.lastOption == null) { throw new OptionParsingException.UnknownOptionException("Unknown option " + option + " in parameter " + value); } // The first short option may either have an argument or start a short option run int argumentIndex = value.IndexOfAny(ArgumentDelimiters, 2); if (argumentIndex == 2) { // The first short option has an argument. if (this.lastOption.Argument == OptionArgument.None) { throw new OptionParsingException.OptionArgumentException("Option " + option + " cannot take an argument in parameter " + value); } yield return(new Option { Definition = this.lastOption, Argument = value.Substring(3) }); this.lastOption = null; } else if (argumentIndex != -1) { // There is an argument delimiter somewhere else in the parameter string. throw new OptionParsingException.InvalidParameterException("Invalid parameter " + value); } else if (value.Length == 2) { // The first short option is the only one. if (this.lastOption.Argument == OptionArgument.None) { yield return(new Option { Definition = this.lastOption }); this.lastOption = null; } } else { // This is a short option run; they must not take arguments. for (int i = 1; i != value.Length; ++i) { option = value[i].ToString(); this.lastOption = this.definitions.FirstOrDefault(x => this.stringComparer.Equals(x.ShortName.ToString(), option)); if (this.lastOption == null) { throw new OptionParsingException.UnknownOptionException("Unknown option " + option + " in parameter " + value); } if (this.lastOption.Argument == OptionArgument.Required) { throw new OptionParsingException.OptionArgumentException("Option " + option + " cannot be in a short option run (because it takes an argument) in parameter " + value); } yield return(new Option { Definition = this.lastOption }); this.lastOption = null; } } continue; } if (value.StartsWith("/")) { // Short or long option if (this.lastOption != null) { // The last option was an option that takes an optional argument, without an argument. yield return(new Option { Definition = this.lastOption }); } if (value.Length < 2) { throw new OptionParsingException.InvalidParameterException("Invalid parameter " + value); } string option = null; string argument = null; int argumentIndex = value.IndexOfAny(ArgumentDelimiters, 2); if (argumentIndex == -1) { option = value.Substring(1); } else { option = value.Substring(1, argumentIndex - 1); argument = value.Substring(argumentIndex + 1); } this.lastOption = this.definitions.FirstOrDefault(x => this.stringComparer.Equals(x.LongName, option) || this.stringComparer.Equals(x.ShortName.ToString(), option)); if (this.lastOption == null) { throw new OptionParsingException.UnknownOptionException("Unknown option " + option + " in parameter " + value); } if (argument != null) { // There is an argument with this option, so it is complete. if (this.lastOption.Argument == OptionArgument.None) { throw new OptionParsingException.OptionArgumentException("Option " + option + " cannot take an argument in parameter " + value); } yield return(new Option { Definition = this.lastOption, Argument = argument }); this.lastOption = null; continue; } if (this.lastOption.Argument == OptionArgument.None) { // This option does not take an argument, so it is complete. yield return(new Option { Definition = this.lastOption }); this.lastOption = null; continue; } // This option does take an argument. continue; } if (this.lastOption != null) { // The last option was an option that takes an optional argument, with an argument. yield return(new Option { Definition = this.lastOption, Argument = value }); this.lastOption = null; continue; } // The first positional argument this.done = true; yield return(new Option { Argument = value }); continue; } // Argument for a preceding option yield return(new Option { Definition = this.lastOption, Argument = value }); this.lastOption = null; continue; } if (this.lastOption != null) { if (this.lastOption.Argument == OptionArgument.Required) { throw new OptionParsingException.OptionArgumentException("Missing argument for option --" + this.lastOption.LongName); } yield return(new Option { Definition = this.lastOption }); } }