mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-24 04:02:50 +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)
|
if (integer % 2 == 0)
|
||||||
{
|
{
|
||||||
return ValidationResult.Success();
|
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)
|
if (integer > 0)
|
||||||
{
|
{
|
||||||
return ValidationResult.Success();
|
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>
|
/// <summary>
|
||||||
/// Validates the parameter value.
|
/// Validates the parameter value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="info">The parameter info.</param>
|
/// <param name="context">The parameter context.</param>
|
||||||
/// <param name="value">The parameter value.</param>
|
|
||||||
/// <returns>The validation result.</returns>
|
/// <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
|
namespace Spectre.Console.Cli
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -9,12 +11,17 @@ namespace Spectre.Console.Cli
|
|||||||
/// Gets the property name.
|
/// Gets the property name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The property name.</value>
|
/// <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>
|
/// <summary>
|
||||||
/// Gets the description.
|
/// Gets the description.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The description.</value>
|
/// <value>The description.</value>
|
||||||
public abstract string? Description { get; }
|
public string? Description { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,6 +18,18 @@ namespace Spectre.Console.Cli
|
|||||||
// Process unmapped parameters.
|
// Process unmapped parameters.
|
||||||
foreach (var parameter in tree.Unmapped)
|
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())
|
if (parameter.IsFlagValue())
|
||||||
{
|
{
|
||||||
// Set the flag value to an empty, not set instance.
|
// Set the flag value to an empty, not set instance.
|
||||||
@ -42,7 +54,7 @@ namespace Spectre.Console.Cli
|
|||||||
}
|
}
|
||||||
|
|
||||||
binder.Bind(parameter, resolver, value);
|
binder.Bind(parameter, resolver, value);
|
||||||
CommandValidator.ValidateParameter(parameter, lookup);
|
CommandValidator.ValidateParameter(parameter, lookup, resolver);
|
||||||
}
|
}
|
||||||
else if (Nullable.GetUnderlyingType(parameter.ParameterType) != null ||
|
else if (Nullable.GetUnderlyingType(parameter.ParameterType) != null ||
|
||||||
!parameter.ParameterType.IsValueType)
|
!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;
|
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);
|
var assignedValue = settings.GetValue(parameter);
|
||||||
foreach (var validator in parameter.Validators)
|
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 (!validationResult.Successful)
|
||||||
{
|
{
|
||||||
// If there is a error message specified in the parameter validator attribute,
|
// If there is a error message specified in the parameter validator attribute,
|
||||||
|
@ -13,9 +13,10 @@ namespace Spectre.Console.Cli
|
|||||||
public CommandArgument(
|
public CommandArgument(
|
||||||
Type parameterType, ParameterKind parameterKind, PropertyInfo property, string? description,
|
Type parameterType, ParameterKind parameterKind, PropertyInfo property, string? description,
|
||||||
TypeConverterAttribute? converter, DefaultValueAttribute? defaultValue,
|
TypeConverterAttribute? converter, DefaultValueAttribute? defaultValue,
|
||||||
CommandArgumentAttribute argument, IEnumerable<ParameterValidationAttribute> validators)
|
CommandArgumentAttribute argument, ParameterValueProviderAttribute? valueProvider,
|
||||||
|
IEnumerable<ParameterValidationAttribute> validators)
|
||||||
: base(parameterType, parameterKind, property, description, converter, defaultValue,
|
: base(parameterType, parameterKind, property, description, converter, defaultValue,
|
||||||
null, validators, argument.IsRequired)
|
null, valueProvider, validators, argument.IsRequired)
|
||||||
{
|
{
|
||||||
Value = argument.ValueName;
|
Value = argument.ValueName;
|
||||||
Position = argument.Position;
|
Position = argument.Position;
|
||||||
|
@ -182,6 +182,7 @@ namespace Spectre.Console.Cli
|
|||||||
var description = property.GetCustomAttribute<DescriptionAttribute>();
|
var description = property.GetCustomAttribute<DescriptionAttribute>();
|
||||||
var converter = property.GetCustomAttribute<TypeConverterAttribute>();
|
var converter = property.GetCustomAttribute<TypeConverterAttribute>();
|
||||||
var deconstructor = property.GetCustomAttribute<PairDeconstructorAttribute>();
|
var deconstructor = property.GetCustomAttribute<PairDeconstructorAttribute>();
|
||||||
|
var valueProvider = property.GetCustomAttribute<ParameterValueProviderAttribute>();
|
||||||
var validators = property.GetCustomAttributes<ParameterValidationAttribute>(true);
|
var validators = property.GetCustomAttributes<ParameterValidationAttribute>(true);
|
||||||
var defaultValue = property.GetCustomAttribute<DefaultValueAttribute>();
|
var defaultValue = property.GetCustomAttribute<DefaultValueAttribute>();
|
||||||
|
|
||||||
@ -194,7 +195,8 @@ namespace Spectre.Console.Cli
|
|||||||
|
|
||||||
return new CommandOption(property.PropertyType, kind,
|
return new CommandOption(property.PropertyType, kind,
|
||||||
property, description?.Description, converter, deconstructor,
|
property, description?.Description, converter, deconstructor,
|
||||||
attribute, validators, defaultValue, attribute.ValueIsOptional);
|
attribute, valueProvider, validators, defaultValue,
|
||||||
|
attribute.ValueIsOptional);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CommandArgument BuildArgumentParameter(PropertyInfo property, CommandArgumentAttribute attribute)
|
private static CommandArgument BuildArgumentParameter(PropertyInfo property, CommandArgumentAttribute attribute)
|
||||||
@ -202,6 +204,7 @@ namespace Spectre.Console.Cli
|
|||||||
var description = property.GetCustomAttribute<DescriptionAttribute>();
|
var description = property.GetCustomAttribute<DescriptionAttribute>();
|
||||||
var converter = property.GetCustomAttribute<TypeConverterAttribute>();
|
var converter = property.GetCustomAttribute<TypeConverterAttribute>();
|
||||||
var defaultValue = property.GetCustomAttribute<DefaultValueAttribute>();
|
var defaultValue = property.GetCustomAttribute<DefaultValueAttribute>();
|
||||||
|
var valueProvider = property.GetCustomAttribute<ParameterValueProviderAttribute>();
|
||||||
var validators = property.GetCustomAttributes<ParameterValidationAttribute>(true);
|
var validators = property.GetCustomAttributes<ParameterValidationAttribute>(true);
|
||||||
|
|
||||||
var kind = GetParameterKind(property.PropertyType);
|
var kind = GetParameterKind(property.PropertyType);
|
||||||
@ -209,7 +212,8 @@ namespace Spectre.Console.Cli
|
|||||||
return new CommandArgument(
|
return new CommandArgument(
|
||||||
property.PropertyType, kind, property,
|
property.PropertyType, kind, property,
|
||||||
description?.Description, converter,
|
description?.Description, converter,
|
||||||
defaultValue, attribute, validators);
|
defaultValue, attribute, valueProvider,
|
||||||
|
validators);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ParameterKind GetOptionKind(
|
private static ParameterKind GetOptionKind(
|
||||||
|
@ -16,10 +16,11 @@ namespace Spectre.Console.Cli
|
|||||||
public CommandOption(
|
public CommandOption(
|
||||||
Type parameterType, ParameterKind parameterKind, PropertyInfo property, string? description,
|
Type parameterType, ParameterKind parameterKind, PropertyInfo property, string? description,
|
||||||
TypeConverterAttribute? converter, PairDeconstructorAttribute? deconstructor,
|
TypeConverterAttribute? converter, PairDeconstructorAttribute? deconstructor,
|
||||||
CommandOptionAttribute optionAttribute, IEnumerable<ParameterValidationAttribute> validators,
|
CommandOptionAttribute optionAttribute, ParameterValueProviderAttribute? valueProvider,
|
||||||
|
IEnumerable<ParameterValidationAttribute> validators,
|
||||||
DefaultValueAttribute? defaultValue, bool valueIsOptional)
|
DefaultValueAttribute? defaultValue, bool valueIsOptional)
|
||||||
: base(parameterType, parameterKind, property, description, converter,
|
: base(parameterType, parameterKind, property, description, converter,
|
||||||
defaultValue, deconstructor, validators, false)
|
defaultValue, deconstructor, valueProvider, validators, false)
|
||||||
{
|
{
|
||||||
LongNames = optionAttribute.LongNames;
|
LongNames = optionAttribute.LongNames;
|
||||||
ShortNames = optionAttribute.ShortNames;
|
ShortNames = optionAttribute.ShortNames;
|
||||||
|
@ -17,6 +17,7 @@ namespace Spectre.Console.Cli
|
|||||||
public TypeConverterAttribute? Converter { get; }
|
public TypeConverterAttribute? Converter { get; }
|
||||||
public PairDeconstructorAttribute? PairDeconstructor { get; }
|
public PairDeconstructorAttribute? PairDeconstructor { get; }
|
||||||
public List<ParameterValidationAttribute> Validators { get; }
|
public List<ParameterValidationAttribute> Validators { get; }
|
||||||
|
public ParameterValueProviderAttribute? ValueProvider { get; }
|
||||||
public bool Required { get; set; }
|
public bool Required { get; set; }
|
||||||
public string PropertyName => Property.Name;
|
public string PropertyName => Property.Name;
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ namespace Spectre.Console.Cli
|
|||||||
string? description, TypeConverterAttribute? converter,
|
string? description, TypeConverterAttribute? converter,
|
||||||
DefaultValueAttribute? defaultValue,
|
DefaultValueAttribute? defaultValue,
|
||||||
PairDeconstructorAttribute? deconstuctor,
|
PairDeconstructorAttribute? deconstuctor,
|
||||||
|
ParameterValueProviderAttribute? valueProvider,
|
||||||
IEnumerable<ParameterValidationAttribute> validators, bool required)
|
IEnumerable<ParameterValidationAttribute> validators, bool required)
|
||||||
{
|
{
|
||||||
Id = Guid.NewGuid();
|
Id = Guid.NewGuid();
|
||||||
@ -38,6 +40,7 @@ namespace Spectre.Console.Cli
|
|||||||
Converter = converter;
|
Converter = converter;
|
||||||
DefaultValue = defaultValue;
|
DefaultValue = defaultValue;
|
||||||
PairDeconstructor = deconstuctor;
|
PairDeconstructor = deconstuctor;
|
||||||
|
ValueProvider = valueProvider;
|
||||||
Validators = new List<ParameterValidationAttribute>(validators ?? Array.Empty<ParameterValidationAttribute>());
|
Validators = new List<ParameterValidationAttribute>(validators ?? Array.Empty<ParameterValidationAttribute>());
|
||||||
Required = required;
|
Required = required;
|
||||||
}
|
}
|
||||||
|
@ -28,10 +28,6 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Cli\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AnnotatedReferenceAssemblyVersion>3.0.0</AnnotatedReferenceAssemblyVersion>
|
<AnnotatedReferenceAssemblyVersion>3.0.0</AnnotatedReferenceAssemblyVersion>
|
||||||
<GenerateNullableAttributes>False</GenerateNullableAttributes>
|
<GenerateNullableAttributes>False</GenerateNullableAttributes>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user