Esempio n. 1
0
        /// <summary>
        /// Splits the option and argument into two separate strings.
        /// </summary>
        /// <param name="argument">
        /// The input argument to split.
        /// </param>
        /// <param name="option">
        /// The option part of the <paramref name="argument"/>.
        /// </param>
        /// <param name="value">
        /// The value part of the <paramref name="argument"/>, or <see cref="string.Empty"/> if
        /// there is no value.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="argument"/> is <c>null</c> or empty.</para>
        /// </exception>
        private static void SplitOptionAndArgument(string argument, out string option, out string value)
        {
            if (StringEx.IsNullOrWhiteSpace(argument))
            {
                throw new ArgumentNullException("argument");
            }

            if (argument.StartsWith("--", StringComparison.Ordinal))
            {
                int valueIndex = argument.IndexOf('=');
                if (valueIndex < 0)
                {
                    valueIndex = argument.IndexOf(':');
                }

                if (valueIndex < 0)
                {
                    option = argument;
                    value  = string.Empty;
                }
                else
                {
                    option = argument.Substring(0, valueIndex).Trim();
                    value  = argument.Substring(valueIndex + 1);
                }
            }
            else
            {
                option = argument.Substring(0, 2);
                value  = argument.Substring(2);
                if (value.StartsWith("=") || value.StartsWith(":"))
                {
                    value = value.Substring(1);
                }
            }
        }
Esempio n. 2
0
        /// <summary>
        /// Maps the arguments onto properties of the container.
        /// </summary>
        /// <param name="arguments">
        /// The collection of arguments to map onto properties on the <paramref name="container"/>.
        /// </param>
        /// <param name="container">
        /// The container object to map the <paramref name="arguments"/> onto.
        /// </param>
        /// <returns>
        /// Any leftover non-option arguments that couldn't be mapped. Note that if the container has
        /// a string collection property tagged with the <see cref="ArgumentsAttribute"/> attribute, the
        /// leftover arguments will be added to that property, and none will be returned from this method.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="arguments"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="container"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// <para><paramref name="container"/> is not the same <see cref="ContainerType"/> as the original type given to this <see cref="PropertyMap"/>.</para>
        /// </exception>
        /// <exception cref="UnknownOptionException">
        /// An unknown option was specified on the command line, one that was not declared in the container type.
        /// </exception>
        /// <exception cref="OptionSyntaxException">
        /// Argument starts with three or more minus signs, this is not legal.
        /// </exception>
        public string[] Map(IEnumerable <string> arguments, object container)
        {
            if (arguments == null)
            {
                throw new ArgumentNullException("arguments");
            }
            if (container == null)
            {
                throw new ArgumentNullException("container");
            }
            if (container.GetType() != _ContainerType)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "The given container was not the same type as the original type given to PropertyMap, this is an internal error (aka bug), original type was {0}, container type was {1}", _ContainerType, container.GetType()));
            }

            var leftovers     = new List <string>();
            int argumentIndex = 0;

            using (IEnumerator <string> argumentEnumerable = arguments.GetEnumerator())
            {
                while (argumentEnumerable.MoveNext())
                {
                    string argument = argumentEnumerable.Current;

                    if ((argument.StartsWith("--", StringComparison.Ordinal) || argument.StartsWith("-", StringComparison.Ordinal)) && !argument.StartsWith("---", StringComparison.Ordinal))
                    {
                        string option;
                        string value;

                        SplitOptionAndArgument(argument, out option, out value);

                        KeyValuePair <PropertyInfo, BaseOptionAttribute> entry;
                        if (_Properties.TryGetValue(option, out entry))
                        {
                            if (entry.Value.RequiresArgument)
                            {
                                if (StringEx.IsNullOrWhiteSpace(value))
                                {
                                    if (!argumentEnumerable.MoveNext())
                                    {
                                        throw new OptionSyntaxException(string.Format(CultureInfo.InvariantCulture, "option {0} requires an argument but none was provided", option));
                                    }

                                    if (argumentEnumerable.Current.StartsWith("-"))
                                    {
                                        string possibleOption;
                                        string possibleArgument;
                                        SplitOptionAndArgument(argumentEnumerable.Current, out possibleOption, out possibleArgument);
                                        if (_Properties.ContainsKey(possibleOption))
                                        {
                                            throw new OptionSyntaxException(string.Format(CultureInfo.InvariantCulture, "option {0} requires an argument but none was provided", option));
                                        }
                                    }

                                    value = argumentEnumerable.Current;
                                }
                            }

                            entry.Value.AssignValueToProperty(container, entry.Key, value);
                        }
                        else
                        {
                            throw new UnknownOptionException(string.Format(CultureInfo.InvariantCulture, "Unknown option {0}", argument));
                        }
                    }
                    else if (argument.StartsWith("-", StringComparison.Ordinal))
                    {
                        throw new OptionSyntaxException("Argument starts with three or more minus signs, this is not legal");
                    }
                    else if (argumentIndex < _ArgumentProperties.Count)
                    {
                        _ArgumentProperties[argumentIndex].SetValue(container, argument, null);
                        argumentIndex++;
                    }
                    else
                    {
                        leftovers.Add(argument);
                    }
                }
            }

            if (_ArgumentsProperty != null)
            {
                var argumentsProperty = _ArgumentsProperty.GetValue(container, null) as Collection <string>;
                if (argumentsProperty == null)
                {
                    if (_ArgumentsProperty.CanWrite)
                    {
                        argumentsProperty = new Collection <string>(new List <string>());
                        _ArgumentsProperty.SetValue(container, argumentsProperty, null);
                    }
                    else
                    {
                        throw new InvalidOperationException("The container has a property that has the ArgumentsAttribute attribute, but this property returns a null collection reference, and is not writeable");
                    }
                }

                foreach (string leftover in leftovers)
                {
                    argumentsProperty.Add(leftover);
                }
                return(new string[0]);
            }

            return(leftovers.ToArray());
        }
Esempio n. 3
0
        /// <summary>
        /// Extracts the command from the given arguments, and returns its position, in order to be able
        /// to remove the command from the argument stream before parsing the arguments for the
        /// given command object. The command is per definition the first non-option argument.
        /// </summary>
        /// <param name="arguments">
        /// The arguments to parse.
        /// </param>
        /// <param name="indexOfCommand">
        /// The index of the command found, or -1 if no command was found.
        /// </param>
        /// <returns>
        /// The command name, or <see cref="string.Empty"/> if no command was found.
        /// </returns>
        public string ExtractCommand(IEnumerable <string> arguments, out int indexOfCommand)
        {
            if (arguments == null)
            {
                throw new ArgumentNullException("arguments");
            }

            indexOfCommand = -1;
            using (IEnumerator <string> argumentEnumerable = arguments.GetEnumerator())
            {
                while (argumentEnumerable.MoveNext())
                {
                    indexOfCommand++;
                    string argument = argumentEnumerable.Current;

                    if ((argument.StartsWith("--", StringComparison.Ordinal) || argument.StartsWith("-", StringComparison.Ordinal)) && !argument.StartsWith("---", StringComparison.Ordinal))
                    {
                        string option;
                        string value;

                        SplitOptionAndArgument(argument, out option, out value);

                        KeyValuePair <PropertyInfo, BaseOptionAttribute> entry;
                        if (_Properties.TryGetValue(option, out entry))
                        {
                            if (entry.Value.RequiresArgument)
                            {
                                if (StringEx.IsNullOrWhiteSpace(value))
                                {
                                    if (!argumentEnumerable.MoveNext())
                                    {
                                        throw new OptionSyntaxException(string.Format(CultureInfo.InvariantCulture, "option {0} requires an argument but none was provided", option));
                                    }
                                    indexOfCommand++;

                                    if (argumentEnumerable.Current.StartsWith("-"))
                                    {
                                        string possibleOption;
                                        string possibleArgument;
                                        SplitOptionAndArgument(argumentEnumerable.Current, out possibleOption, out possibleArgument);
                                        if (_Properties.ContainsKey(possibleOption))
                                        {
                                            throw new OptionSyntaxException(string.Format(CultureInfo.InvariantCulture, "option {0} requires an argument but none was provided", option));
                                        }
                                    }

                                    value = argumentEnumerable.Current;
                                }
                            }
                        }
                        else
                        {
                            throw new UnknownOptionException(string.Format(CultureInfo.InvariantCulture, "Unknown option {0}", argument));
                        }
                    }
                    else if (argument.StartsWith("-", StringComparison.Ordinal))
                    {
                        throw new OptionSyntaxException("Argument starts with three or more minus signs, this is not legal");
                    }
                    else
                    {
                        return(argument);
                    }
                }
            }

            indexOfCommand = -1;
            return(string.Empty);
        }
Esempio n. 4
0
        /// <summary>
        /// Retrieves the help text for the given options container type.
        /// </summary>
        /// <param name="containerType">
        /// The type of options container to retrieve the help text for.
        /// </param>
        /// <returns>
        /// A collection of text lines, the help text.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="containerType"/> is <c>null</c>.</para>
        /// </exception>
        public static IEnumerable <string> GetHelp(Type containerType)
        {
            if (containerType == null)
            {
                throw new ArgumentNullException("containerType");
            }

            var map = new PropertyMap(containerType);

            // Command line help
            var parts         = new List <string>();
            int argumentIndex = 1;

            foreach (PropertyInfo prop in map.ArgumentProperties)
            {
                var    attr = (ArgumentAttribute)prop.GetCustomAttributes(typeof(ArgumentAttribute), true)[0];
                string name = attr.Name;
                if (StringEx.IsNullOrWhiteSpace(name))
                {
                    name = "ARG" + argumentIndex;
                }
                if (attr.Optional)
                {
                    parts.Add("[" + name + "]");
                }
                else
                {
                    parts.Add(name);
                }
                argumentIndex++;
            }

            if (map.ArgumentsProperty != null)
            {
                parts.Add("[ARG]...");
            }

            if (map.MappedProperties.Any())
            {
                parts.Add("[OPTIONS]...");
            }

            yield return(Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location).ToLower() + " " + string.Join(" ", parts.ToArray()));

            yield return(string.Empty);

            string[] containerHelp = GetHelpTextFor(containerType).ToArray();
            if (containerHelp.Length > 0)
            {
                foreach (string line in containerHelp)
                {
                    yield return(line);
                }
                yield return(string.Empty);
            }

            var argumentsWithDescription =
                (from entry in map.ArgumentProperties.Select((property, index) => new { property, index })
                 let descriptionAttr = entry.property.GetCustomAttributes(typeof(DescriptionAttribute), true).FirstOrDefault() as DescriptionAttribute
                                       where descriptionAttr != null
                                       let attr = (ArgumentAttribute)entry.property.GetCustomAttributes(typeof(ArgumentAttribute), true)[0]
                                                  let argName = !StringEx.IsNullOrWhiteSpace(attr.Name) ? attr.Name : ("ARG" + (entry.index + 1))
                                                                select new { argName, text = StringEx.SplitLines(descriptionAttr.Description).ToArray() }).ToArray();

            if (argumentsWithDescription.Length > 0)
            {
                yield return("arguments:");

                yield return(string.Empty);

                int maxLongLength = argumentsWithDescription.Max(p => p.argName.Length);

                foreach (var arg in argumentsWithDescription)
                {
                    int lines = arg.text.Length;
                    if (arg.argName.Length > 20)
                    {
                        yield return(" " + arg.argName);

                        var indent = new string(' ', maxLongLength + 3);
                        foreach (string line in arg.text)
                        {
                            yield return(indent + line);
                        }
                    }
                    else
                    {
                        yield return(" " + arg.argName.PadRight(maxLongLength, ' ') + "  " + arg.text[0]);

                        var indent = new string(' ', maxLongLength + 3);
                        foreach (string line in arg.text.Skip(1))
                        {
                            yield return(indent + line);
                        }
                    }
                }

                yield return(string.Empty);
            }

            var propertiesWithHelpText =
                (from propMap in map.MappedProperties
                 let text = GetHelpTextFor(propMap.Key).ToArray()
                            where text.Length > 0
                            select new
            {
                prop = propMap.Key, option = propMap.Value.Option, parameter = propMap.Value.ParameterName, text
            }
                 into entry
                 group entry by entry.prop
                 into g
                 select new
            {
                options = g.Select(e => e.option).OrderBy(o => o.Length).ToArray(), g.First().text, g.First().parameter
            }
                 into entry2
                 orderby(entry2.options.First() == "-h" || entry2.options.First() == "--help") ? 0 : 1, entry2.options.First()
                 let shortOption = entry2.options.Where(o => o.Length == 2).FirstOrDefault() ?? string.Empty
                                   let longOption = entry2.options.Where(o => o.Length > 2).FirstOrDefault() ?? string.Empty
                                                    select new
            {
                shortOption, longOption, entry2.options, entry2.parameter, entry2.text
            }).ToArray();

            if (propertiesWithHelpText.Length > 0)
            {
                yield return("options:");

                yield return(string.Empty);

                int maxLongLength = propertiesWithHelpText.Max(p => p.longOption.Length + p.parameter.Length);

                foreach (var prop in propertiesWithHelpText)
                {
                    int lines = prop.text.Length;
                    if (prop.longOption.Length > 20)
                    {
                        yield return(" " + (prop.shortOption.PadRight(2, ' ') + " " + prop.longOption + " " + prop.parameter).Trim());

                        var indent = new string(' ', 27);
                        foreach (string line in prop.text)
                        {
                            yield return(indent + line);
                        }
                    }
                    else
                    {
                        yield return(" " + prop.shortOption.PadRight(2, ' ') + " " + (prop.longOption + " " + prop.parameter).PadRight(20, ' ') + "  " + prop.text[0]);

                        var indent = new string(' ', 27);
                        foreach (string line in prop.text.Skip(1))
                        {
                            yield return(indent + line);
                        }
                    }
                }
            }
        }