mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-16 00:42:51 +08:00
Add parameter value provider support
Adds support for parameter value providers which makes it possible to set custom values for parameters.
This commit is contained in:
parent
d1d94cdebe
commit
1dd1945297
@ -11,19 +11,19 @@ namespace Spectre.Console.Tests.Data
|
||||
{
|
||||
}
|
||||
|
||||
public override ValidationResult Validate(ICommandParameterInfo info, object value)
|
||||
public override ValidationResult Validate(CommandParameterContext context)
|
||||
{
|
||||
if (value is int integer)
|
||||
if (context.Value is int integer)
|
||||
{
|
||||
if (integer % 2 == 0)
|
||||
{
|
||||
return ValidationResult.Success();
|
||||
}
|
||||
|
||||
return ValidationResult.Error($"Number is not even ({info?.PropertyName}).");
|
||||
return ValidationResult.Error($"Number is not even ({context.Parameter.PropertyName}).");
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Parameter is not a number ({info?.PropertyName}).");
|
||||
throw new InvalidOperationException($"Parameter is not a number ({context.Parameter.PropertyName}).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,19 +11,19 @@ namespace Spectre.Console.Tests.Data
|
||||
{
|
||||
}
|
||||
|
||||
public override ValidationResult Validate(ICommandParameterInfo info, object value)
|
||||
public override ValidationResult Validate(CommandParameterContext context)
|
||||
{
|
||||
if (value is int integer)
|
||||
if (context.Value is int integer)
|
||||
{
|
||||
if (integer > 0)
|
||||
{
|
||||
return ValidationResult.Success();
|
||||
}
|
||||
|
||||
return ValidationResult.Error($"Number is not greater than 0 ({info?.PropertyName}).");
|
||||
return ValidationResult.Error($"Number is not greater than 0 ({context.Parameter.PropertyName}).");
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Parameter is not a number ({info?.PropertyName}).");
|
||||
throw new InvalidOperationException($"Parameter is not a number ({context.Parameter.PropertyName}).");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
using System.Linq;
|
||||
using Shouldly;
|
||||
using Spectre.Console.Cli;
|
||||
using Spectre.Console.Testing;
|
||||
using Spectre.Console.Tests.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit.Cli
|
||||
{
|
||||
public sealed partial class CommandAppTests
|
||||
{
|
||||
public sealed class ValueProviders
|
||||
{
|
||||
public sealed class ValueProviderSettings : CommandSettings
|
||||
{
|
||||
[CommandOption("-f|--foo <VALUE>")]
|
||||
[IntegerValueProvider(32)]
|
||||
public int Foo { get; set; }
|
||||
}
|
||||
|
||||
public sealed class IntegerValueProvider : ParameterValueProviderAttribute
|
||||
{
|
||||
private readonly int _value;
|
||||
|
||||
public IntegerValueProvider(int value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public override bool TryGetValue(CommandParameterContext context, out object result)
|
||||
{
|
||||
if (context.Parameter.ParameterType == typeof(int))
|
||||
{
|
||||
if (context.Value == null)
|
||||
{
|
||||
result = _value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Use_Provided_Value_If_No_Value_Was_Specified()
|
||||
{
|
||||
// Given
|
||||
var app = new CommandAppTester();
|
||||
app.SetDefaultCommand<GenericCommand<ValueProviderSettings>>();
|
||||
app.Configure(config =>
|
||||
{
|
||||
config.PropagateExceptions();
|
||||
});
|
||||
|
||||
// When
|
||||
var result = app.Run();
|
||||
|
||||
// Then
|
||||
result.Settings.ShouldBeOfType<ValueProviderSettings>().And(settings =>
|
||||
{
|
||||
settings.Foo.ShouldBe(32);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Override_Value_If_Value_Was_Specified()
|
||||
{
|
||||
// Given
|
||||
var app = new CommandAppTester();
|
||||
app.SetDefaultCommand<GenericCommand<ValueProviderSettings>>();
|
||||
app.Configure(config =>
|
||||
{
|
||||
config.PropagateExceptions();
|
||||
});
|
||||
|
||||
// When
|
||||
var result = app.Run("--foo", "12");
|
||||
|
||||
// Then
|
||||
result.Settings.ShouldBeOfType<ValueProviderSettings>().And(settings =>
|
||||
{
|
||||
settings.Foo.ShouldBe(12);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -27,9 +27,8 @@ namespace Spectre.Console.Cli
|
||||
/// <summary>
|
||||
/// Validates the parameter value.
|
||||
/// </summary>
|
||||
/// <param name="info">The parameter info.</param>
|
||||
/// <param name="value">The parameter value.</param>
|
||||
/// <param name="context">The parameter context.</param>
|
||||
/// <returns>The validation result.</returns>
|
||||
public abstract ValidationResult Validate(ICommandParameterInfo info, object? value);
|
||||
public abstract ValidationResult Validate(CommandParameterContext context);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Cli
|
||||
{
|
||||
/// <summary>
|
||||
/// An base class attribute used for parameter completion.
|
||||
/// </summary>
|
||||
/// <seealso cref="Attribute" />
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
||||
public abstract class ParameterValueProviderAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value for the parameter.
|
||||
/// </summary>
|
||||
/// <param name="context">The parameter context.</param>
|
||||
/// <param name="result">The resulting value.</param>
|
||||
/// <returns><c>true</c> if a value was provided; otherwise, <c>false</c>.</returns>
|
||||
public abstract bool TryGetValue(CommandParameterContext context, out object? result);
|
||||
}
|
||||
}
|
38
src/Spectre.Console/Cli/CommandParameterContext.cs
Normal file
38
src/Spectre.Console/Cli/CommandParameterContext.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Cli
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a context for <see cref="ICommandParameterInfo"/> related operations.
|
||||
/// </summary>
|
||||
public sealed class CommandParameterContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the parameter.
|
||||
/// </summary>
|
||||
public ICommandParameterInfo Parameter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type resolver.
|
||||
/// </summary>
|
||||
public ITypeResolver Resolver { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets tje parameter value.
|
||||
/// </summary>
|
||||
public object? Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CommandParameterContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The parameter.</param>
|
||||
/// <param name="resolver">The type resolver.</param>
|
||||
/// <param name="value">The parameter value.</param>
|
||||
public CommandParameterContext(ICommandParameterInfo parameter, ITypeResolver resolver, object? value)
|
||||
{
|
||||
Parameter = parameter ?? throw new ArgumentNullException(nameof(parameter));
|
||||
Resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Cli
|
||||
{
|
||||
/// <summary>
|
||||
@ -9,12 +11,17 @@ namespace Spectre.Console.Cli
|
||||
/// Gets the property name.
|
||||
/// </summary>
|
||||
/// <value>The property name.</value>
|
||||
public abstract string PropertyName { get; }
|
||||
public string PropertyName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameter type.
|
||||
/// </summary>
|
||||
public Type ParameterType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description.
|
||||
/// </summary>
|
||||
/// <value>The description.</value>
|
||||
public abstract string? Description { get; }
|
||||
public string? Description { get; }
|
||||
}
|
||||
}
|
@ -18,6 +18,18 @@ namespace Spectre.Console.Cli
|
||||
// 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))
|
||||
{
|
||||
lookup.SetValue(parameter, result);
|
||||
CommandValidator.ValidateParameter(parameter, lookup, resolver);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (parameter.IsFlagValue())
|
||||
{
|
||||
// Set the flag value to an empty, not set instance.
|
||||
@ -42,7 +54,7 @@ namespace Spectre.Console.Cli
|
||||
}
|
||||
|
||||
binder.Bind(parameter, resolver, value);
|
||||
CommandValidator.ValidateParameter(parameter, lookup);
|
||||
CommandValidator.ValidateParameter(parameter, lookup, resolver);
|
||||
}
|
||||
else if (Nullable.GetUnderlyingType(parameter.ParameterType) != null ||
|
||||
!parameter.ParameterType.IsValueType)
|
||||
@ -88,7 +100,17 @@ namespace Spectre.Console.Cli
|
||||
}
|
||||
}
|
||||
|
||||
CommandValidator.ValidateParameter(mapped.Parameter, lookup);
|
||||
// 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;
|
||||
|
@ -23,12 +23,13 @@ namespace Spectre.Console.Cli
|
||||
}
|
||||
}
|
||||
|
||||
public static void ValidateParameter(CommandParameter parameter, CommandValueLookup settings)
|
||||
public static void ValidateParameter(CommandParameter parameter, CommandValueLookup settings, ITypeResolver resolver)
|
||||
{
|
||||
var assignedValue = settings.GetValue(parameter);
|
||||
foreach (var validator in parameter.Validators)
|
||||
{
|
||||
var validationResult = validator.Validate(parameter, assignedValue);
|
||||
var context = new CommandParameterContext(parameter, resolver, assignedValue);
|
||||
var validationResult = validator.Validate(context);
|
||||
if (!validationResult.Successful)
|
||||
{
|
||||
// If there is a error message specified in the parameter validator attribute,
|
||||
|
@ -13,9 +13,10 @@ namespace Spectre.Console.Cli
|
||||
public CommandArgument(
|
||||
Type parameterType, ParameterKind parameterKind, PropertyInfo property, string? description,
|
||||
TypeConverterAttribute? converter, DefaultValueAttribute? defaultValue,
|
||||
CommandArgumentAttribute argument, IEnumerable<ParameterValidationAttribute> validators)
|
||||
CommandArgumentAttribute argument, ParameterValueProviderAttribute? valueProvider,
|
||||
IEnumerable<ParameterValidationAttribute> validators)
|
||||
: base(parameterType, parameterKind, property, description, converter, defaultValue,
|
||||
null, validators, argument.IsRequired)
|
||||
null, valueProvider, validators, argument.IsRequired)
|
||||
{
|
||||
Value = argument.ValueName;
|
||||
Position = argument.Position;
|
||||
|
@ -182,6 +182,7 @@ namespace Spectre.Console.Cli
|
||||
var description = property.GetCustomAttribute<DescriptionAttribute>();
|
||||
var converter = property.GetCustomAttribute<TypeConverterAttribute>();
|
||||
var deconstructor = property.GetCustomAttribute<PairDeconstructorAttribute>();
|
||||
var valueProvider = property.GetCustomAttribute<ParameterValueProviderAttribute>();
|
||||
var validators = property.GetCustomAttributes<ParameterValidationAttribute>(true);
|
||||
var defaultValue = property.GetCustomAttribute<DefaultValueAttribute>();
|
||||
|
||||
@ -194,7 +195,8 @@ namespace Spectre.Console.Cli
|
||||
|
||||
return new CommandOption(property.PropertyType, kind,
|
||||
property, description?.Description, converter, deconstructor,
|
||||
attribute, validators, defaultValue, attribute.ValueIsOptional);
|
||||
attribute, valueProvider, validators, defaultValue,
|
||||
attribute.ValueIsOptional);
|
||||
}
|
||||
|
||||
private static CommandArgument BuildArgumentParameter(PropertyInfo property, CommandArgumentAttribute attribute)
|
||||
@ -202,6 +204,7 @@ namespace Spectre.Console.Cli
|
||||
var description = property.GetCustomAttribute<DescriptionAttribute>();
|
||||
var converter = property.GetCustomAttribute<TypeConverterAttribute>();
|
||||
var defaultValue = property.GetCustomAttribute<DefaultValueAttribute>();
|
||||
var valueProvider = property.GetCustomAttribute<ParameterValueProviderAttribute>();
|
||||
var validators = property.GetCustomAttributes<ParameterValidationAttribute>(true);
|
||||
|
||||
var kind = GetParameterKind(property.PropertyType);
|
||||
@ -209,7 +212,8 @@ namespace Spectre.Console.Cli
|
||||
return new CommandArgument(
|
||||
property.PropertyType, kind, property,
|
||||
description?.Description, converter,
|
||||
defaultValue, attribute, validators);
|
||||
defaultValue, attribute, valueProvider,
|
||||
validators);
|
||||
}
|
||||
|
||||
private static ParameterKind GetOptionKind(
|
||||
|
@ -16,10 +16,11 @@ namespace Spectre.Console.Cli
|
||||
public CommandOption(
|
||||
Type parameterType, ParameterKind parameterKind, PropertyInfo property, string? description,
|
||||
TypeConverterAttribute? converter, PairDeconstructorAttribute? deconstructor,
|
||||
CommandOptionAttribute optionAttribute, IEnumerable<ParameterValidationAttribute> validators,
|
||||
CommandOptionAttribute optionAttribute, ParameterValueProviderAttribute? valueProvider,
|
||||
IEnumerable<ParameterValidationAttribute> validators,
|
||||
DefaultValueAttribute? defaultValue, bool valueIsOptional)
|
||||
: base(parameterType, parameterKind, property, description, converter,
|
||||
defaultValue, deconstructor, validators, false)
|
||||
defaultValue, deconstructor, valueProvider, validators, false)
|
||||
{
|
||||
LongNames = optionAttribute.LongNames;
|
||||
ShortNames = optionAttribute.ShortNames;
|
||||
|
@ -17,6 +17,7 @@ namespace Spectre.Console.Cli
|
||||
public TypeConverterAttribute? Converter { get; }
|
||||
public PairDeconstructorAttribute? PairDeconstructor { get; }
|
||||
public List<ParameterValidationAttribute> Validators { get; }
|
||||
public ParameterValueProviderAttribute? ValueProvider { get; }
|
||||
public bool Required { get; set; }
|
||||
public string PropertyName => Property.Name;
|
||||
|
||||
@ -28,6 +29,7 @@ namespace Spectre.Console.Cli
|
||||
string? description, TypeConverterAttribute? converter,
|
||||
DefaultValueAttribute? defaultValue,
|
||||
PairDeconstructorAttribute? deconstuctor,
|
||||
ParameterValueProviderAttribute? valueProvider,
|
||||
IEnumerable<ParameterValidationAttribute> validators, bool required)
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
@ -38,6 +40,7 @@ namespace Spectre.Console.Cli
|
||||
Converter = converter;
|
||||
DefaultValue = defaultValue;
|
||||
PairDeconstructor = deconstuctor;
|
||||
ValueProvider = valueProvider;
|
||||
Validators = new List<ParameterValidationAttribute>(validators ?? Array.Empty<ParameterValidationAttribute>());
|
||||
Required = required;
|
||||
}
|
||||
|
@ -28,10 +28,6 @@
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Cli\" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<AnnotatedReferenceAssemblyVersion>3.0.0</AnnotatedReferenceAssemblyVersion>
|
||||
<GenerateNullableAttributes>False</GenerateNullableAttributes>
|
||||
|
Loading…
x
Reference in New Issue
Block a user