Example #1
0
        /// <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();
        }
Example #3
0
        /// <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();
        }
Example #4
0
 /// <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);
 }