mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-07-04 19:58:14 +08:00
Add support for required options
This commit is contained in:

committed by
Patrik Svensson

parent
d836ad1805
commit
67c3909bbb
@ -45,6 +45,6 @@ public sealed class CommandArgumentAttribute : Attribute
|
|||||||
// Assign the result.
|
// Assign the result.
|
||||||
Position = position;
|
Position = position;
|
||||||
ValueName = result.Value;
|
ValueName = result.Value;
|
||||||
IsRequired = result.Required;
|
IsRequired = result.IsRequired;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -30,6 +30,11 @@ public sealed class CommandOptionAttribute : Attribute
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ValueIsOptional { get; }
|
public bool ValueIsOptional { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the value is required.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRequired { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this option is hidden from the help text.
|
/// Gets or sets a value indicating whether this option is hidden from the help text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -39,7 +44,8 @@ public sealed class CommandOptionAttribute : Attribute
|
|||||||
/// Initializes a new instance of the <see cref="CommandOptionAttribute"/> class.
|
/// Initializes a new instance of the <see cref="CommandOptionAttribute"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="template">The option template.</param>
|
/// <param name="template">The option template.</param>
|
||||||
public CommandOptionAttribute(string template)
|
/// <param name="isRequired">Indicates whether the option is required or not.</param>
|
||||||
|
public CommandOptionAttribute(string template, bool isRequired = false)
|
||||||
{
|
{
|
||||||
if (template == null)
|
if (template == null)
|
||||||
{
|
{
|
||||||
@ -54,6 +60,7 @@ public sealed class CommandOptionAttribute : Attribute
|
|||||||
ShortNames = result.ShortNames;
|
ShortNames = result.ShortNames;
|
||||||
ValueName = result.Value;
|
ValueName = result.Value;
|
||||||
ValueIsOptional = result.ValueIsOptional;
|
ValueIsOptional = result.ValueIsOptional;
|
||||||
|
IsRequired = isRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool IsMatch(string name)
|
internal bool IsMatch(string name)
|
||||||
|
@ -37,6 +37,16 @@ public class CommandRuntimeException : CommandAppException
|
|||||||
return new CommandRuntimeException($"Command '{node.Command.Name}' is missing required argument '{argument.Value}'.");
|
return new CommandRuntimeException($"Command '{node.Command.Name}' is missing required argument '{argument.Value}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static CommandRuntimeException MissingRequiredOption(CommandTree node, CommandOption option)
|
||||||
|
{
|
||||||
|
if (node.Command.Name == CliConstants.DefaultCommandName)
|
||||||
|
{
|
||||||
|
return new CommandRuntimeException($"Missing required option '{option.GetOptionName()}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CommandRuntimeException($"Command '{node.Command.Name}' is missing required argument '{option.GetOptionName()}'.");
|
||||||
|
}
|
||||||
|
|
||||||
internal static CommandRuntimeException NoConverterFound(CommandParameter parameter)
|
internal static CommandRuntimeException NoConverterFound(CommandParameter parameter)
|
||||||
{
|
{
|
||||||
return new CommandRuntimeException($"Could not find converter for type '{parameter.ParameterType.FullName}'.");
|
return new CommandRuntimeException($"Could not find converter for type '{parameter.ParameterType.FullName}'.");
|
||||||
|
@ -103,7 +103,7 @@ internal sealed class CommandExecutor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Is this the default and is it called without arguments when there are required arguments?
|
// Is this the default and is it called without arguments when there are required arguments?
|
||||||
if (leaf.Command.IsDefaultCommand && arguments.Count == 0 && leaf.Command.Parameters.Any(p => p.Required))
|
if (leaf.Command.IsDefaultCommand && arguments.Count == 0 && leaf.Command.Parameters.Any(p => p.IsRequired))
|
||||||
{
|
{
|
||||||
// Display help for default command.
|
// Display help for default command.
|
||||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
|
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
|
||||||
|
@ -9,12 +9,14 @@ internal static class CommandValidator
|
|||||||
{
|
{
|
||||||
foreach (var parameter in node.Unmapped)
|
foreach (var parameter in node.Unmapped)
|
||||||
{
|
{
|
||||||
if (parameter.Required)
|
if (parameter.IsRequired)
|
||||||
{
|
{
|
||||||
switch (parameter)
|
switch (parameter)
|
||||||
{
|
{
|
||||||
case CommandArgument argument:
|
case CommandArgument argument:
|
||||||
throw CommandRuntimeException.MissingRequiredArgument(node, argument);
|
throw CommandRuntimeException.MissingRequiredArgument(node, argument);
|
||||||
|
case CommandOption option:
|
||||||
|
throw CommandRuntimeException.MissingRequiredOption(node, option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,7 +212,7 @@ internal sealed class ExplainCommand : Command<ExplainCommand.Settings>
|
|||||||
parameterNode.AddNode(ValueMarkup("Value", commandArgumentParameter.Value));
|
parameterNode.AddNode(ValueMarkup("Value", commandArgumentParameter.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
parameterNode.AddNode(ValueMarkup("Required", parameter.Required.ToString()));
|
parameterNode.AddNode(ValueMarkup("Required", parameter.IsRequired.ToString()));
|
||||||
|
|
||||||
if (parameter.Converter != null)
|
if (parameter.Converter != null)
|
||||||
{
|
{
|
||||||
|
@ -142,7 +142,7 @@ internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings>
|
|||||||
var node = document.CreateElement("Argument");
|
var node = document.CreateElement("Argument");
|
||||||
node.SetNullableAttribute("Name", argument.Value);
|
node.SetNullableAttribute("Name", argument.Value);
|
||||||
node.SetAttribute("Position", argument.Position.ToString(CultureInfo.InvariantCulture));
|
node.SetAttribute("Position", argument.Position.ToString(CultureInfo.InvariantCulture));
|
||||||
node.SetBooleanAttribute("Required", argument.Required);
|
node.SetBooleanAttribute("Required", argument.IsRequired);
|
||||||
node.SetEnumAttribute("Kind", argument.ParameterKind);
|
node.SetEnumAttribute("Kind", argument.ParameterKind);
|
||||||
node.SetNullableAttribute("ClrType", argument.ParameterType?.FullName);
|
node.SetNullableAttribute("ClrType", argument.ParameterType?.FullName);
|
||||||
|
|
||||||
@ -186,7 +186,7 @@ internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings>
|
|||||||
node.SetNullableAttribute("Short", option.ShortNames);
|
node.SetNullableAttribute("Short", option.ShortNames);
|
||||||
node.SetNullableAttribute("Long", option.LongNames);
|
node.SetNullableAttribute("Long", option.LongNames);
|
||||||
node.SetNullableAttribute("Value", option.ValueName);
|
node.SetNullableAttribute("Value", option.ValueName);
|
||||||
node.SetBooleanAttribute("Required", option.Required);
|
node.SetBooleanAttribute("Required", option.IsRequired);
|
||||||
node.SetEnumAttribute("Kind", option.ParameterKind);
|
node.SetEnumAttribute("Kind", option.ParameterKind);
|
||||||
node.SetNullableAttribute("ClrType", option.ParameterType?.FullName);
|
node.SetNullableAttribute("ClrType", option.ParameterType?.FullName);
|
||||||
|
|
||||||
|
@ -5,12 +5,12 @@ internal static class TemplateParser
|
|||||||
public sealed class ArgumentResult
|
public sealed class ArgumentResult
|
||||||
{
|
{
|
||||||
public string Value { get; set; }
|
public string Value { get; set; }
|
||||||
public bool Required { get; set; }
|
public bool IsRequired { get; set; }
|
||||||
|
|
||||||
public ArgumentResult(string value, bool required)
|
public ArgumentResult(string value, bool isRequired)
|
||||||
{
|
{
|
||||||
Value = value;
|
Value = value;
|
||||||
Required = required;
|
IsRequired = isRequired;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ internal static class CommandModelValidator
|
|||||||
// Arguments
|
// Arguments
|
||||||
foreach (var argument in arguments)
|
foreach (var argument in arguments)
|
||||||
{
|
{
|
||||||
if (argument.Required && argument.DefaultValue != null)
|
if (argument.IsRequired && argument.DefaultValue != null)
|
||||||
{
|
{
|
||||||
throw CommandConfigurationException.RequiredArgumentsCannotHaveDefaultValue(argument);
|
throw CommandConfigurationException.RequiredArgumentsCannotHaveDefaultValue(argument);
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,8 @@ internal sealed class CommandOption : CommandParameter, ICommandOption
|
|||||||
IEnumerable<ParameterValidationAttribute> validators,
|
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, valueProvider, validators, false, optionAttribute.IsHidden)
|
defaultValue, deconstructor, valueProvider, validators,
|
||||||
|
optionAttribute.IsRequired, optionAttribute.IsHidden)
|
||||||
{
|
{
|
||||||
LongNames = optionAttribute.LongNames;
|
LongNames = optionAttribute.LongNames;
|
||||||
ShortNames = optionAttribute.ShortNames;
|
ShortNames = optionAttribute.ShortNames;
|
||||||
|
@ -12,7 +12,7 @@ internal abstract class CommandParameter : ICommandParameterInfo, ICommandParame
|
|||||||
public PairDeconstructorAttribute? PairDeconstructor { get; }
|
public PairDeconstructorAttribute? PairDeconstructor { get; }
|
||||||
public List<ParameterValidationAttribute> Validators { get; }
|
public List<ParameterValidationAttribute> Validators { get; }
|
||||||
public ParameterValueProviderAttribute? ValueProvider { get; }
|
public ParameterValueProviderAttribute? ValueProvider { get; }
|
||||||
public bool Required { get; set; }
|
public bool IsRequired { get; set; }
|
||||||
public bool IsHidden { get; }
|
public bool IsHidden { get; }
|
||||||
public string PropertyName => Property.Name;
|
public string PropertyName => Property.Name;
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ internal abstract class CommandParameter : ICommandParameterInfo, ICommandParame
|
|||||||
PairDeconstructor = deconstructor;
|
PairDeconstructor = deconstructor;
|
||||||
ValueProvider = valueProvider;
|
ValueProvider = valueProvider;
|
||||||
Validators = new List<ParameterValidationAttribute>(validators ?? Array.Empty<ParameterValidationAttribute>());
|
Validators = new List<ParameterValidationAttribute>(validators ?? Array.Empty<ParameterValidationAttribute>());
|
||||||
Required = required;
|
IsRequired = required;
|
||||||
IsHidden = isHidden;
|
IsHidden = isHidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
namespace Spectre.Console.Tests.Data;
|
||||||
|
|
||||||
|
public class RequiredOptionsSettings : CommandSettings
|
||||||
|
{
|
||||||
|
[CommandOption("--foo <VALUE>", true)]
|
||||||
|
public string Foo { get; set; }
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
namespace Spectre.Console.Tests.Unit.Cli;
|
||||||
|
|
||||||
|
public sealed partial class CommandAppTests
|
||||||
|
{
|
||||||
|
public sealed class Options
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Throw_If_Required_Option_Is_Missing()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var fixture = new CommandAppTester();
|
||||||
|
fixture.Configure(config =>
|
||||||
|
{
|
||||||
|
config.AddCommand<GenericCommand<RequiredOptionsSettings>>("test");
|
||||||
|
config.PropagateExceptions();
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() => fixture.Run("test"));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<CommandRuntimeException>()
|
||||||
|
.And(ex =>
|
||||||
|
ex.Message.ShouldBe("Command 'test' is missing required argument 'foo'."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
namespace Spectre.Console.Tests.Unit.Cli;
|
namespace Spectre.Console.Tests.Unit.Cli;
|
||||||
|
|
||||||
public sealed partial class CommandApptests
|
public sealed partial class CommandAppTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Treat_Commands_As_Case_Sensitive_If_Specified()
|
public void Should_Treat_Commands_As_Case_Sensitive_If_Specified()
|
||||||
|
Reference in New Issue
Block a user