Source/NuGetGallery.Operations/Infrastructure/CommandLineParser.cs (161 lines of code) (raw):
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using NuGetGallery.Operations;
using NuGetGallery.Operations.Common;
namespace NuGet
{
public class CommandLineParser
{
private readonly ICommandManager _commandManager;
// On Unix or MacOSX slash as a switch indicator would interfere with the path separator
[SuppressMessage("Microsoft.Performance", "CA1802:UseLiteralsWhereAppropriate")]
private static readonly bool _supportSlashAsSwitch = (Environment.OSVersion.Platform != PlatformID.Unix) && (Environment.OSVersion.Platform != PlatformID.MacOSX);
public CommandLineParser(ICommandManager manager)
{
_commandManager = manager;
}
public void ExtractOptions(ICommand command, IEnumerator<string> argsEnumerator)
{
List<string> arguments = new List<string>();
IDictionary<OptionAttribute, PropertyInfo> properties = _commandManager.GetCommandOptions(command);
while (true)
{
string option = GetNextCommandLineItem(argsEnumerator);
if (option == null)
{
break;
}
if (!(option.StartsWith("/", StringComparison.OrdinalIgnoreCase) && _supportSlashAsSwitch)
&& !option.StartsWith("-", StringComparison.OrdinalIgnoreCase))
{
arguments.Add(option);
continue;
}
string optionText = option.Substring(1);
string value = null;
if (optionText.EndsWith("-", StringComparison.OrdinalIgnoreCase))
{
optionText = optionText.TrimEnd('-');
value = "false";
}
var result = GetPartialOptionMatch(properties, prop => prop.Value.Name, prop => prop.Key.AltName, option, optionText);
PropertyInfo propInfo = result.Value;
if (propInfo.PropertyType == typeof(bool))
{
value = value ?? "true";
}
else
{
value = GetNextCommandLineItem(argsEnumerator);
}
if (value == null)
{
throw new CommandLineException(TaskResources.MissingOptionValueError, option);
}
AssignValue(command, propInfo, option, value);
}
command.Arguments.AddRange(arguments);
}
internal static void AssignValue(object command, PropertyInfo property, string option, object value)
{
try
{
if (TypeHelper.IsMultiValuedProperty(property))
{
// If we were able to look up a parent of type ICollection<>, perform a Add operation on it.
// Note that we expect the value is a string.
var stringValue = value as string;
Debug.Assert(stringValue != null);
dynamic list = property.GetValue(command, null);
// The parameter value is one or more semi-colon separated items that might support values also
// Example of a list value : nuget pack -option "foo;bar;baz"
// Example of a keyvalue value: nuget pack -option "foo=bar;baz=false"
foreach (var item in stringValue.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
if (TypeHelper.IsKeyValueProperty(property))
{
int eqIndex = item.IndexOf("=", StringComparison.OrdinalIgnoreCase);
if (eqIndex > -1)
{
string propertyKey = item.Substring(0, eqIndex);
string propertyValue = item.Substring(eqIndex + 1);
list.Add(propertyKey, propertyValue);
}
}
else
{
list.Add(item);
}
}
}
else if (TypeHelper.IsEnumProperty(property))
{
var enumValue = Enum.GetValues(property.PropertyType).Cast<object>();
value = GetPartialOptionMatch(enumValue, e => e.ToString(), e => e.ToString(), option, value.ToString());
property.SetValue(command, value, index: null);
}
else
{
property.SetValue(command, TypeHelper.ChangeType(value, property.PropertyType), index: null);
}
}
catch (CommandLineException)
{
throw;
}
catch
{
throw new CommandLineException(TaskResources.InvalidOptionValueError, option, value);
}
}
public ICommand ParseCommandLine(IEnumerable<string> commandLineArgs)
{
IEnumerator<string> argsEnumerator = commandLineArgs.GetEnumerator();
// Get the desired command name
string cmdName = GetNextCommandLineItem(argsEnumerator);
if (cmdName == null)
{
return null;
}
// Get the command based on the name
ICommand cmd = _commandManager.GetCommand(cmdName);
if (cmd == null)
{
throw new CommandLineException(TaskResources.UnknownCommandError, cmdName);
}
ExtractOptions(cmd, argsEnumerator);
return cmd;
}
public static string GetNextCommandLineItem(IEnumerator<string> argsEnumerator)
{
if (argsEnumerator == null || !argsEnumerator.MoveNext())
{
return null;
}
return argsEnumerator.Current;
}
private static TVal GetPartialOptionMatch<TVal>(IEnumerable<TVal> source, Func<TVal, string> getDisplayName, Func<TVal, string> getAltName, string option, string value)
{
var results = from item in source
where getDisplayName(item).StartsWith(value, StringComparison.OrdinalIgnoreCase) ||
(getAltName(item) ?? String.Empty).StartsWith(value, StringComparison.OrdinalIgnoreCase)
select item;
if (!results.Any())
{
throw new CommandLineException(TaskResources.UnknownOptionError, option);
}
var result = results.FirstOrDefault();
if (results.Skip(1).Any())
{
try
{
// When multiple results are found, if there's an exact match, return it.
result = results.First(c => value.Equals(getDisplayName(c), StringComparison.OrdinalIgnoreCase) ||
value.Equals(getAltName(c), StringComparison.OrdinalIgnoreCase));
}
catch (InvalidOperationException)
{
throw new CommandLineException(String.Format(CultureInfo.CurrentCulture, TaskResources.AmbiguousOption, value,
String.Join(" ", from c in results select getDisplayName(c))));
}
}
return result;
}
}
}