mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-07-05 12:08:16 +08:00
Move Spectre.Console.Cli to it's own package
This commit is contained in:

committed by
Patrik Svensson

parent
b600832e00
commit
36ca22ffac
@ -0,0 +1,50 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal static class CommandConstructorBinder
|
||||
{
|
||||
public static CommandSettings CreateSettings(CommandValueLookup lookup, ConstructorInfo constructor, ITypeResolver resolver)
|
||||
{
|
||||
if (constructor.DeclaringType == null)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot create settings since constructor have no declaring type.");
|
||||
}
|
||||
|
||||
var parameters = new List<object?>();
|
||||
var mapped = new HashSet<Guid>();
|
||||
foreach (var parameter in constructor.GetParameters())
|
||||
{
|
||||
if (lookup.TryGetParameterWithName(parameter.Name, out var result))
|
||||
{
|
||||
parameters.Add(result.Value);
|
||||
mapped.Add(result.Parameter.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = resolver.Resolve(parameter.ParameterType);
|
||||
if (value == null)
|
||||
{
|
||||
throw CommandRuntimeException.CouldNotResolveType(parameter.ParameterType);
|
||||
}
|
||||
|
||||
parameters.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the settings.
|
||||
if (!(Activator.CreateInstance(constructor.DeclaringType, parameters.ToArray()) is CommandSettings settings))
|
||||
{
|
||||
throw new InvalidOperationException("Could not create settings");
|
||||
}
|
||||
|
||||
// Try to do property injection for parameters that wasn't injected.
|
||||
foreach (var (parameter, value) in lookup)
|
||||
{
|
||||
if (!mapped.Contains(parameter.Id) && parameter.Property.SetMethod != null)
|
||||
{
|
||||
parameter.Property.SetValue(settings, value);
|
||||
}
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal static class CommandPropertyBinder
|
||||
{
|
||||
public static CommandSettings CreateSettings(CommandValueLookup lookup, Type settingsType, ITypeResolver resolver)
|
||||
{
|
||||
var settings = CreateSettings(resolver, settingsType);
|
||||
|
||||
foreach (var (parameter, value) in lookup)
|
||||
{
|
||||
if (value != default)
|
||||
{
|
||||
parameter.Property.SetValue(settings, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the settings.
|
||||
var validationResult = settings.Validate();
|
||||
if (!validationResult.Successful)
|
||||
{
|
||||
throw CommandRuntimeException.ValidationFailed(validationResult);
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static CommandSettings CreateSettings(ITypeResolver resolver, Type settingsType)
|
||||
{
|
||||
if (resolver.Resolve(settingsType) is CommandSettings settings)
|
||||
{
|
||||
return settings;
|
||||
}
|
||||
|
||||
if (Activator.CreateInstance(settingsType) is CommandSettings instance)
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
|
||||
throw CommandParseException.CouldNotCreateSettings(settingsType);
|
||||
}
|
||||
}
|
115
src/Spectre.Console.Cli/Internal/Binding/CommandValueBinder.cs
Normal file
115
src/Spectre.Console.Cli/Internal/Binding/CommandValueBinder.cs
Normal file
@ -0,0 +1,115 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal sealed class CommandValueBinder
|
||||
{
|
||||
private readonly CommandValueLookup _lookup;
|
||||
|
||||
public CommandValueBinder(CommandValueLookup lookup)
|
||||
{
|
||||
_lookup = lookup;
|
||||
}
|
||||
|
||||
public void Bind(CommandParameter parameter, ITypeResolver resolver, object? value)
|
||||
{
|
||||
if (parameter.ParameterKind == ParameterKind.Pair)
|
||||
{
|
||||
value = GetLookup(parameter, resolver, value);
|
||||
}
|
||||
else if (parameter.ParameterKind == ParameterKind.Vector)
|
||||
{
|
||||
value = GetArray(parameter, value);
|
||||
}
|
||||
else if (parameter.ParameterKind == ParameterKind.FlagWithValue)
|
||||
{
|
||||
value = GetFlag(parameter, value);
|
||||
}
|
||||
|
||||
_lookup.SetValue(parameter, value);
|
||||
}
|
||||
|
||||
private object GetLookup(CommandParameter parameter, ITypeResolver resolver, object? value)
|
||||
{
|
||||
var genericTypes = parameter.Property.PropertyType.GetGenericArguments();
|
||||
|
||||
var multimap = (IMultiMap?)_lookup.GetValue(parameter);
|
||||
if (multimap == null)
|
||||
{
|
||||
multimap = Activator.CreateInstance(typeof(MultiMap<,>).MakeGenericType(genericTypes[0], genericTypes[1])) as IMultiMap;
|
||||
if (multimap == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not create multimap");
|
||||
}
|
||||
}
|
||||
|
||||
// Create deconstructor.
|
||||
var deconstructorType = parameter.PairDeconstructor?.Type ?? typeof(DefaultPairDeconstructor);
|
||||
if (!(resolver.Resolve(deconstructorType) is IPairDeconstructor deconstructor))
|
||||
{
|
||||
if (!(Activator.CreateInstance(deconstructorType) is IPairDeconstructor activatedDeconstructor))
|
||||
{
|
||||
throw new InvalidOperationException($"Could not create pair deconstructor.");
|
||||
}
|
||||
|
||||
deconstructor = activatedDeconstructor;
|
||||
}
|
||||
|
||||
// Deconstruct and add to multimap.
|
||||
var pair = deconstructor.Deconstruct(resolver, genericTypes[0], genericTypes[1], value as string);
|
||||
if (pair.Key != null)
|
||||
{
|
||||
multimap.Add(pair);
|
||||
}
|
||||
|
||||
return multimap;
|
||||
}
|
||||
|
||||
private object GetArray(CommandParameter parameter, object? value)
|
||||
{
|
||||
// Add a new item to the array
|
||||
var array = (Array?)_lookup.GetValue(parameter);
|
||||
Array newArray;
|
||||
|
||||
var elementType = parameter.Property.PropertyType.GetElementType();
|
||||
if (elementType == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not get property type.");
|
||||
}
|
||||
|
||||
if (array == null)
|
||||
{
|
||||
newArray = Array.CreateInstance(elementType, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
newArray = Array.CreateInstance(elementType, array.Length + 1);
|
||||
array.CopyTo(newArray, 0);
|
||||
}
|
||||
|
||||
newArray.SetValue(value, newArray.Length - 1);
|
||||
return newArray;
|
||||
}
|
||||
|
||||
private object GetFlag(CommandParameter parameter, object? value)
|
||||
{
|
||||
var flagValue = (IFlagValue?)_lookup.GetValue(parameter);
|
||||
if (flagValue == null)
|
||||
{
|
||||
flagValue = (IFlagValue?)Activator.CreateInstance(parameter.ParameterType);
|
||||
if (flagValue == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not create flag value.");
|
||||
}
|
||||
}
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
// Null means set, but not with a valid value.
|
||||
flagValue.Value = value;
|
||||
}
|
||||
|
||||
// If the parameter was mapped, then it's set.
|
||||
flagValue.IsSet = true;
|
||||
|
||||
return flagValue;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal sealed class CommandValueLookup : IEnumerable<(CommandParameter Parameter, object? Value)>
|
||||
{
|
||||
private readonly Dictionary<Guid, (CommandParameter Parameter, object? Value)> _lookup;
|
||||
|
||||
public CommandValueLookup()
|
||||
{
|
||||
_lookup = new Dictionary<Guid, (CommandParameter, object?)>();
|
||||
}
|
||||
|
||||
public IEnumerator<(CommandParameter Parameter, object? Value)> GetEnumerator()
|
||||
{
|
||||
foreach (var pair in _lookup)
|
||||
{
|
||||
yield return pair.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasParameterWithName(string? name)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _lookup.Values.Any(pair => pair.Parameter.PropertyName.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public bool TryGetParameterWithName(string? name, out (CommandParameter Parameter, object? Value) result)
|
||||
{
|
||||
if (HasParameterWithName(name))
|
||||
{
|
||||
result = _lookup.Values.FirstOrDefault(pair => pair.Parameter.PropertyName.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
return true;
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public object? GetValue(CommandParameter parameter)
|
||||
{
|
||||
_lookup.TryGetValue(parameter.Id, out var result);
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
public void SetValue(CommandParameter parameter, object? value)
|
||||
{
|
||||
_lookup[parameter.Id] = (parameter, value);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
162
src/Spectre.Console.Cli/Internal/Binding/CommandValueResolver.cs
Normal file
162
src/Spectre.Console.Cli/Internal/Binding/CommandValueResolver.cs
Normal file
@ -0,0 +1,162 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal static class CommandValueResolver
|
||||
{
|
||||
public static CommandValueLookup GetParameterValues(CommandTree? tree, ITypeResolver resolver)
|
||||
{
|
||||
var lookup = new CommandValueLookup();
|
||||
var binder = new CommandValueBinder(lookup);
|
||||
|
||||
CommandValidator.ValidateRequiredParameters(tree);
|
||||
|
||||
while (tree != null)
|
||||
{
|
||||
// Process unmapped parameters.
|
||||
foreach (var parameter in tree.Unmapped)
|
||||
{
|
||||
// Got a value provider?
|
||||
if (parameter.ValueProvider != null)
|
||||
{
|
||||
var context = new CommandParameterContext(parameter, resolver, null);
|
||||
if (parameter.ValueProvider.TryGetValue(context, out var result))
|
||||
{
|
||||
result = ConvertValue(resolver, lookup, binder, parameter, result);
|
||||
|
||||
lookup.SetValue(parameter, result);
|
||||
CommandValidator.ValidateParameter(parameter, lookup, resolver);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (parameter.IsFlagValue())
|
||||
{
|
||||
// Set the flag value to an empty, not set instance.
|
||||
var instance = Activator.CreateInstance(parameter.ParameterType);
|
||||
lookup.SetValue(parameter, instance);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Is this an option with a default value?
|
||||
if (parameter.DefaultValue != null)
|
||||
{
|
||||
var value = parameter.DefaultValue?.Value;
|
||||
value = ConvertValue(resolver, lookup, binder, parameter, value);
|
||||
|
||||
binder.Bind(parameter, resolver, value);
|
||||
CommandValidator.ValidateParameter(parameter, lookup, resolver);
|
||||
}
|
||||
else if (Nullable.GetUnderlyingType(parameter.ParameterType) != null ||
|
||||
!parameter.ParameterType.IsValueType)
|
||||
{
|
||||
lookup.SetValue(parameter, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process mapped parameters.
|
||||
foreach (var mapped in tree.Mapped)
|
||||
{
|
||||
if (mapped.Parameter.WantRawValue)
|
||||
{
|
||||
// Just try to assign the raw value.
|
||||
binder.Bind(mapped.Parameter, resolver, mapped.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mapped.Parameter.IsFlagValue() && mapped.Value == null)
|
||||
{
|
||||
if (mapped.Parameter is CommandOption option && option.DefaultValue != null)
|
||||
{
|
||||
// Set the default value.
|
||||
binder.Bind(mapped.Parameter, resolver, option.DefaultValue?.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the flag but not the value.
|
||||
binder.Bind(mapped.Parameter, resolver, null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var converter = GetConverter(lookup, binder, resolver, mapped.Parameter);
|
||||
if (converter == null)
|
||||
{
|
||||
throw CommandRuntimeException.NoConverterFound(mapped.Parameter);
|
||||
}
|
||||
|
||||
// Assign the value to the parameter.
|
||||
binder.Bind(mapped.Parameter, resolver, converter.ConvertFromInvariantString(mapped.Value));
|
||||
}
|
||||
}
|
||||
|
||||
// Got a value provider?
|
||||
if (mapped.Parameter.ValueProvider != null)
|
||||
{
|
||||
var context = new CommandParameterContext(mapped.Parameter, resolver, mapped.Value);
|
||||
if (mapped.Parameter.ValueProvider.TryGetValue(context, out var result))
|
||||
{
|
||||
lookup.SetValue(mapped.Parameter, result);
|
||||
}
|
||||
}
|
||||
|
||||
CommandValidator.ValidateParameter(mapped.Parameter, lookup, resolver);
|
||||
}
|
||||
|
||||
tree = tree.Next;
|
||||
}
|
||||
|
||||
return lookup;
|
||||
}
|
||||
|
||||
private static object? ConvertValue(ITypeResolver resolver, CommandValueLookup lookup, CommandValueBinder binder, CommandParameter parameter, object? result)
|
||||
{
|
||||
if (result != null && result.GetType() != parameter.ParameterType)
|
||||
{
|
||||
var converter = GetConverter(lookup, binder, resolver, parameter);
|
||||
if (converter != null)
|
||||
{
|
||||
result = converter.ConvertFrom(result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "It's OK")]
|
||||
private static TypeConverter? GetConverter(CommandValueLookup lookup, CommandValueBinder binder, ITypeResolver resolver, CommandParameter parameter)
|
||||
{
|
||||
if (parameter.Converter == null)
|
||||
{
|
||||
if (parameter.ParameterType.IsArray)
|
||||
{
|
||||
// Return a converter for each array item (not the whole array)
|
||||
return TypeDescriptor.GetConverter(parameter.ParameterType.GetElementType());
|
||||
}
|
||||
|
||||
if (parameter.IsFlagValue())
|
||||
{
|
||||
// Is the optional value instanciated?
|
||||
var value = lookup.GetValue(parameter) as IFlagValue;
|
||||
if (value == null)
|
||||
{
|
||||
// Try to assign it with a null value.
|
||||
// This will create the optional value instance without a value.
|
||||
binder.Bind(parameter, resolver, null);
|
||||
value = lookup.GetValue(parameter) as IFlagValue;
|
||||
if (value == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not intialize optional value.");
|
||||
}
|
||||
}
|
||||
|
||||
// Return a converter for the flag element type.
|
||||
return TypeDescriptor.GetConverter(value.Type);
|
||||
}
|
||||
|
||||
return TypeDescriptor.GetConverter(parameter.ParameterType);
|
||||
}
|
||||
|
||||
var type = Type.GetType(parameter.Converter.ConverterTypeName);
|
||||
return resolver.Resolve(type) as TypeConverter;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user