Пример #1
0
        /// <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();
        }
Пример #2
0
            /// <summary>
            /// Parses the command line into options.
            /// </summary>
            /// <returns>The sequence of options specified on the command line.</returns>
            public IEnumerator <Option> GetEnumerator()
            {
                foreach (var value in this.commandLine)
                {
                    // If the option parsing is done, then all remaining command line elements are positional arguments.
                    if (this.done)
                    {
                        yield return(new Option {
                            Argument = value
                        });

                        continue;
                    }

                    if (this.lastOption != null && this.lastOption.Argument == OptionArgument.Required)
                    {
                        // Argument for a preceding option
                        yield return(new Option {
                            Definition = this.lastOption, Argument = value
                        });

                        this.lastOption = null;
                        continue;
                    }

                    // Either there is no last option, or the last option's argument is 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)
                        {
                            // No argument delimiters were found; the command line element is an option.
                            option = value.Substring(2);
                        }
                        else
                        {
                            // An argument delimiter was found; split the command line element into an option and its argument.
                            option   = value.Substring(2, argumentIndex - 2);
                            argument = value.Substring(argumentIndex + 1);
                        }

                        // Find the option in the option definitions.
                        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.ShortNameAsString, 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.ShortNameAsString, 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.ShortNameAsString, 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;
                }

                if (this.lastOption != null)
                {
                    if (this.lastOption.Argument == OptionArgument.Required)
                    {
                        throw new OptionParsingException.OptionArgumentException("Missing argument for option  " + this.lastOption.Name);
                    }

                    yield return(new Option {
                        Definition = this.lastOption
                    });
                }
            }