diff --git a/src/Spectre.Console.Cli/Annotations/CommandArgumentAttribute.cs b/src/Spectre.Console.Cli/Annotations/CommandArgumentAttribute.cs
index a3716059..8bf36453 100644
--- a/src/Spectre.Console.Cli/Annotations/CommandArgumentAttribute.cs
+++ b/src/Spectre.Console.Cli/Annotations/CommandArgumentAttribute.cs
@@ -45,6 +45,6 @@ public sealed class CommandArgumentAttribute : Attribute
// Assign the result.
Position = position;
ValueName = result.Value;
- IsRequired = result.Required;
+ IsRequired = result.IsRequired;
}
}
\ No newline at end of file
diff --git a/src/Spectre.Console.Cli/Annotations/CommandOptionAttribute.cs b/src/Spectre.Console.Cli/Annotations/CommandOptionAttribute.cs
index 098c3f46..c43b9270 100644
--- a/src/Spectre.Console.Cli/Annotations/CommandOptionAttribute.cs
+++ b/src/Spectre.Console.Cli/Annotations/CommandOptionAttribute.cs
@@ -30,6 +30,11 @@ public sealed class CommandOptionAttribute : Attribute
///
public bool ValueIsOptional { get; }
+ ///
+ /// Gets a value indicating whether the value is required.
+ ///
+ public bool IsRequired { get; }
+
///
/// Gets or sets a value indicating whether this option is hidden from the help text.
///
@@ -39,7 +44,8 @@ public sealed class CommandOptionAttribute : Attribute
/// Initializes a new instance of the class.
///
/// The option template.
- public CommandOptionAttribute(string template)
+ /// Indicates whether the option is required or not.
+ public CommandOptionAttribute(string template, bool isRequired = false)
{
if (template == null)
{
@@ -54,6 +60,7 @@ public sealed class CommandOptionAttribute : Attribute
ShortNames = result.ShortNames;
ValueName = result.Value;
ValueIsOptional = result.ValueIsOptional;
+ IsRequired = isRequired;
}
internal bool IsMatch(string name)
diff --git a/src/Spectre.Console.Cli/CommandRuntimeException.cs b/src/Spectre.Console.Cli/CommandRuntimeException.cs
index 669842f1..ca94dacf 100644
--- a/src/Spectre.Console.Cli/CommandRuntimeException.cs
+++ b/src/Spectre.Console.Cli/CommandRuntimeException.cs
@@ -37,6 +37,16 @@ public class CommandRuntimeException : CommandAppException
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)
{
return new CommandRuntimeException($"Could not find converter for type '{parameter.ParameterType.FullName}'.");
diff --git a/src/Spectre.Console.Cli/Internal/CommandExecutor.cs b/src/Spectre.Console.Cli/Internal/CommandExecutor.cs
index be599bc2..9d24ad06 100644
--- a/src/Spectre.Console.Cli/Internal/CommandExecutor.cs
+++ b/src/Spectre.Console.Cli/Internal/CommandExecutor.cs
@@ -103,7 +103,7 @@ internal sealed class CommandExecutor
}
// 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.
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
diff --git a/src/Spectre.Console.Cli/Internal/CommandValidator.cs b/src/Spectre.Console.Cli/Internal/CommandValidator.cs
index c6ce253e..0f523b84 100644
--- a/src/Spectre.Console.Cli/Internal/CommandValidator.cs
+++ b/src/Spectre.Console.Cli/Internal/CommandValidator.cs
@@ -9,12 +9,14 @@ internal static class CommandValidator
{
foreach (var parameter in node.Unmapped)
{
- if (parameter.Required)
+ if (parameter.IsRequired)
{
switch (parameter)
{
case CommandArgument argument:
throw CommandRuntimeException.MissingRequiredArgument(node, argument);
+ case CommandOption option:
+ throw CommandRuntimeException.MissingRequiredOption(node, option);
}
}
}
diff --git a/src/Spectre.Console.Cli/Internal/Commands/ExplainCommand.cs b/src/Spectre.Console.Cli/Internal/Commands/ExplainCommand.cs
index c2b1e594..c0de6e52 100644
--- a/src/Spectre.Console.Cli/Internal/Commands/ExplainCommand.cs
+++ b/src/Spectre.Console.Cli/Internal/Commands/ExplainCommand.cs
@@ -212,7 +212,7 @@ internal sealed class ExplainCommand : Command
parameterNode.AddNode(ValueMarkup("Value", commandArgumentParameter.Value));
}
- parameterNode.AddNode(ValueMarkup("Required", parameter.Required.ToString()));
+ parameterNode.AddNode(ValueMarkup("Required", parameter.IsRequired.ToString()));
if (parameter.Converter != null)
{
diff --git a/src/Spectre.Console.Cli/Internal/Commands/XmlDocCommand.cs b/src/Spectre.Console.Cli/Internal/Commands/XmlDocCommand.cs
index 7b7592ca..c5de6bbd 100644
--- a/src/Spectre.Console.Cli/Internal/Commands/XmlDocCommand.cs
+++ b/src/Spectre.Console.Cli/Internal/Commands/XmlDocCommand.cs
@@ -142,7 +142,7 @@ internal sealed class XmlDocCommand : Command
var node = document.CreateElement("Argument");
node.SetNullableAttribute("Name", argument.Value);
node.SetAttribute("Position", argument.Position.ToString(CultureInfo.InvariantCulture));
- node.SetBooleanAttribute("Required", argument.Required);
+ node.SetBooleanAttribute("Required", argument.IsRequired);
node.SetEnumAttribute("Kind", argument.ParameterKind);
node.SetNullableAttribute("ClrType", argument.ParameterType?.FullName);
@@ -186,7 +186,7 @@ internal sealed class XmlDocCommand : Command
node.SetNullableAttribute("Short", option.ShortNames);
node.SetNullableAttribute("Long", option.LongNames);
node.SetNullableAttribute("Value", option.ValueName);
- node.SetBooleanAttribute("Required", option.Required);
+ node.SetBooleanAttribute("Required", option.IsRequired);
node.SetEnumAttribute("Kind", option.ParameterKind);
node.SetNullableAttribute("ClrType", option.ParameterType?.FullName);
diff --git a/src/Spectre.Console.Cli/Internal/Configuration/TemplateParser.cs b/src/Spectre.Console.Cli/Internal/Configuration/TemplateParser.cs
index 12594728..6eb7c200 100644
--- a/src/Spectre.Console.Cli/Internal/Configuration/TemplateParser.cs
+++ b/src/Spectre.Console.Cli/Internal/Configuration/TemplateParser.cs
@@ -5,12 +5,12 @@ internal static class TemplateParser
public sealed class ArgumentResult
{
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;
- Required = required;
+ IsRequired = isRequired;
}
}
diff --git a/src/Spectre.Console.Cli/Internal/Modelling/CommandModelValidator.cs b/src/Spectre.Console.Cli/Internal/Modelling/CommandModelValidator.cs
index 86624f5f..a98c2529 100644
--- a/src/Spectre.Console.Cli/Internal/Modelling/CommandModelValidator.cs
+++ b/src/Spectre.Console.Cli/Internal/Modelling/CommandModelValidator.cs
@@ -86,7 +86,7 @@ internal static class CommandModelValidator
// Arguments
foreach (var argument in arguments)
{
- if (argument.Required && argument.DefaultValue != null)
+ if (argument.IsRequired && argument.DefaultValue != null)
{
throw CommandConfigurationException.RequiredArgumentsCannotHaveDefaultValue(argument);
}
diff --git a/src/Spectre.Console.Cli/Internal/Modelling/CommandOption.cs b/src/Spectre.Console.Cli/Internal/Modelling/CommandOption.cs
index 29e113d5..f2d6a9c9 100644
--- a/src/Spectre.Console.Cli/Internal/Modelling/CommandOption.cs
+++ b/src/Spectre.Console.Cli/Internal/Modelling/CommandOption.cs
@@ -14,8 +14,9 @@ internal sealed class CommandOption : CommandParameter, ICommandOption
CommandOptionAttribute optionAttribute, ParameterValueProviderAttribute? valueProvider,
IEnumerable validators,
DefaultValueAttribute? defaultValue, bool valueIsOptional)
- : base(parameterType, parameterKind, property, description, converter,
- defaultValue, deconstructor, valueProvider, validators, false, optionAttribute.IsHidden)
+ : base(parameterType, parameterKind, property, description, converter,
+ defaultValue, deconstructor, valueProvider, validators,
+ optionAttribute.IsRequired, optionAttribute.IsHidden)
{
LongNames = optionAttribute.LongNames;
ShortNames = optionAttribute.ShortNames;
diff --git a/src/Spectre.Console.Cli/Internal/Modelling/CommandParameter.cs b/src/Spectre.Console.Cli/Internal/Modelling/CommandParameter.cs
index e461d50c..1f8f0594 100644
--- a/src/Spectre.Console.Cli/Internal/Modelling/CommandParameter.cs
+++ b/src/Spectre.Console.Cli/Internal/Modelling/CommandParameter.cs
@@ -12,7 +12,7 @@ internal abstract class CommandParameter : ICommandParameterInfo, ICommandParame
public PairDeconstructorAttribute? PairDeconstructor { get; }
public List Validators { get; }
public ParameterValueProviderAttribute? ValueProvider { get; }
- public bool Required { get; set; }
+ public bool IsRequired { get; set; }
public bool IsHidden { get; }
public string PropertyName => Property.Name;
@@ -39,7 +39,7 @@ internal abstract class CommandParameter : ICommandParameterInfo, ICommandParame
PairDeconstructor = deconstructor;
ValueProvider = valueProvider;
Validators = new List(validators ?? Array.Empty());
- Required = required;
+ IsRequired = required;
IsHidden = isHidden;
}
diff --git a/src/Tests/Spectre.Console.Cli.Tests/Data/Settings/RequiredOptionsSettings.cs b/src/Tests/Spectre.Console.Cli.Tests/Data/Settings/RequiredOptionsSettings.cs
new file mode 100644
index 00000000..ff14ed95
--- /dev/null
+++ b/src/Tests/Spectre.Console.Cli.Tests/Data/Settings/RequiredOptionsSettings.cs
@@ -0,0 +1,7 @@
+namespace Spectre.Console.Tests.Data;
+
+public class RequiredOptionsSettings : CommandSettings
+{
+ [CommandOption("--foo ", true)]
+ public string Foo { get; set; }
+}
\ No newline at end of file
diff --git a/src/Tests/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Options.cs b/src/Tests/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Options.cs
new file mode 100644
index 00000000..4dde1d3f
--- /dev/null
+++ b/src/Tests/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Options.cs
@@ -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>("test");
+ config.PropagateExceptions();
+ });
+
+ // When
+ var result = Record.Exception(() => fixture.Run("test"));
+
+ // Then
+ result.ShouldBeOfType()
+ .And(ex =>
+ ex.Message.ShouldBe("Command 'test' is missing required argument 'foo'."));
+ }
+ }
+}
diff --git a/src/Tests/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Sensitivity.cs b/src/Tests/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Sensitivity.cs
index 87e3ca63..7c819acb 100644
--- a/src/Tests/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Sensitivity.cs
+++ b/src/Tests/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Sensitivity.cs
@@ -1,6 +1,6 @@
namespace Spectre.Console.Tests.Unit.Cli;
-public sealed partial class CommandApptests
+public sealed partial class CommandAppTests
{
[Fact]
public void Should_Treat_Commands_As_Case_Sensitive_If_Specified()