Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@ public CommandArgumentAttribute(int position, string template)
// Assign the result.
Position = position;
ValueName = result.Value;
IsRequired = result.Required;
IsRequired = result.IsRequired;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public sealed class CommandOptionAttribute : Attribute
/// </summary>
public bool ValueIsOptional { get; }

/// <summary>
/// Gets a value indicating whether the value is required.
/// </summary>
public bool IsRequired { get; }

/// <summary>
/// Gets or sets a value indicating whether this option is hidden from the help text.
/// </summary>
Expand All @@ -39,7 +44,8 @@ public sealed class CommandOptionAttribute : Attribute
/// Initializes a new instance of the <see cref="CommandOptionAttribute"/> class.
/// </summary>
/// <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)
{
Expand All @@ -54,6 +60,7 @@ public CommandOptionAttribute(string template)
ShortNames = result.ShortNames;
ValueName = result.Value;
ValueIsOptional = result.ValueIsOptional;
IsRequired = isRequired;
}

internal bool IsMatch(string name)
Expand Down
10 changes: 10 additions & 0 deletions src/Spectre.Console.Cli/CommandRuntimeException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ internal static CommandRuntimeException MissingRequiredArgument(CommandTree node
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}'.");
Expand Down
93 changes: 65 additions & 28 deletions src/Spectre.Console.Cli/Help/HelpProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ public static IReadOnlyList<HelpArgument> Get(ICommandInfo? command)
{
var arguments = new List<HelpArgument>();
arguments.AddRange(command?.Parameters?.OfType<ICommandArgument>()?.Select(
x => new HelpArgument(x.Value, x.Position, x.Required, x.Description))
?? Array.Empty<HelpArgument>());
x => new HelpArgument(x.Value, x.Position, x.IsRequired, x.Description))
?? Array.Empty<HelpArgument>());
return arguments;
}
}
Expand All @@ -65,15 +65,20 @@ private sealed class HelpOption
public string? Long { get; }
public string? Value { get; }
public bool? ValueIsOptional { get; }
public bool IsRequired { get; }
public string? Description { get; }
public object? DefaultValue { get; }

private HelpOption(string? @short, string? @long, string? @value, bool? valueIsOptional, string? description, object? defaultValue)
private HelpOption(
string? @short, string? @long, string? @value,
bool? valueIsOptional, bool isRequired,
string? description, object? defaultValue)
{
Short = @short;
Long = @long;
Value = value;
ValueIsOptional = valueIsOptional;
IsRequired = isRequired;
Description = description;
DefaultValue = defaultValue;
}
Expand All @@ -85,7 +90,8 @@ public static IReadOnlyList<HelpOption> Get(
{
var parameters = new List<HelpOption>
{
new HelpOption("h", "help", null, null, resources.PrintHelpDescription, null),
new HelpOption("h", "help", null, null, false,
resources.PrintHelpDescription, null),
};

// Version information applies to the entire CLI application.
Expand All @@ -107,17 +113,18 @@ public static IReadOnlyList<HelpOption> Get(
// Only show the version option if there is an application version set.
if (model.ApplicationVersion != null)
{
parameters.Add(new HelpOption("v", "version", null, null, resources.PrintVersionDescription, null));
parameters.Add(new HelpOption("v", "version", null, null, false,
resources.PrintVersionDescription, null));
}
}
}

parameters.AddRange(command?.Parameters.OfType<ICommandOption>().Where(o => !o.IsHidden).Select(o =>
new HelpOption(
o.ShortNames.FirstOrDefault(), o.LongNames.FirstOrDefault(),
o.ValueName, o.ValueIsOptional, o.Description,
o.IsFlag && o.DefaultValue?.Value is false ? null : o.DefaultValue?.Value))
?? Array.Empty<HelpOption>());
new HelpOption(
o.ShortNames.FirstOrDefault(), o.LongNames.FirstOrDefault(),
o.ValueName, o.ValueIsOptional, o.IsRequired, o.Description,
o.IsFlag && o.DefaultValue?.Value is false ? null : o.DefaultValue?.Value))
?? Array.Empty<HelpOption>());
return parameters;
}
}
Expand Down Expand Up @@ -215,7 +222,9 @@ public virtual IEnumerable<IRenderable> GetUsage(ICommandModel model, ICommandIn
{
if (isCurrent)
{
parameters.Add(NewComposer().Style(helpStyles?.Usage?.CurrentCommand ?? Style.Plain, $"{current.Name}"));
parameters.Add(NewComposer().Style(
helpStyles?.Usage?.CurrentCommand ?? Style.Plain,
$"{current.Name}"));
}
else
{
Expand All @@ -228,38 +237,46 @@ public virtual IEnumerable<IRenderable> GetUsage(ICommandModel model, ICommandIn
if (isCurrent)
{
foreach (var argument in current.Parameters.OfType<ICommandArgument>()
.Where(a => a.Required).OrderBy(a => a.Position).ToArray())
.Where(a => a.IsRequired).OrderBy(a => a.Position).ToArray())
{
parameters.Add(NewComposer().Style(helpStyles?.Usage?.RequiredArgument ?? Style.Plain, $"<{argument.Value}>"));
parameters.Add(NewComposer().Style(
helpStyles?.Usage?.RequiredArgument ?? Style.Plain,
$"<{argument.Value}>"));
}
}

var optionalArguments = current.Parameters.OfType<ICommandArgument>().Where(x => !x.Required).ToArray();
var optionalArguments = current.Parameters.OfType<ICommandArgument>().Where(x => !x.IsRequired)
.ToArray();
if (optionalArguments.Length > 0 || !isCurrent)
{
foreach (var optionalArgument in optionalArguments)
{
parameters.Add(NewComposer().Style(helpStyles?.Usage?.OptionalArgument ?? Style.Plain, $"[{optionalArgument.Value}]"));
parameters.Add(NewComposer().Style(
helpStyles?.Usage?.OptionalArgument ?? Style.Plain,
$"[{optionalArgument.Value}]"));
}
}
}

if (isCurrent)
{
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Options ?? Style.Plain, $"[{resources.Options}]"));
parameters.Add(NewComposer()
.Style(helpStyles?.Usage?.Options ?? Style.Plain, $"[{resources.Options}]"));
}
}

if (command.IsBranch && command.DefaultCommand == null)
{
// The user must specify the command
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Command ?? Style.Plain, $"<{resources.Command}>"));
parameters.Add(NewComposer()
.Style(helpStyles?.Usage?.Command ?? Style.Plain, $"<{resources.Command}>"));
}
else if (command.IsBranch && command.DefaultCommand != null && command.Commands.Count > 0)
{
// We are on a branch with a default command
// The user can optionally specify the command
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Command ?? Style.Plain, $"[{resources.Command}]"));
parameters.Add(NewComposer()
.Style(helpStyles?.Usage?.Command ?? Style.Plain, $"[{resources.Command}]"));
}
else if (command.IsDefaultCommand)
{
Expand All @@ -269,7 +286,8 @@ public virtual IEnumerable<IRenderable> GetUsage(ICommandModel model, ICommandIn
{
// Commands other than the default are present
// So make these optional in the usage statement
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Command ?? Style.Plain, $"[{resources.Command}]"));
parameters.Add(NewComposer()
.Style(helpStyles?.Usage?.Command ?? Style.Plain, $"[{resources.Command}]"));
}
}
}
Expand Down Expand Up @@ -338,7 +356,8 @@ public virtual IEnumerable<IRenderable> GetExamples(ICommandModel model, IComman
for (var index = 0; index < Math.Min(maxExamples, examples.Count); index++)
{
var args = string.Join(" ", examples[index]);
composer.Tab().Text(model.ApplicationName).Space().Style(helpStyles?.Examples?.Arguments ?? Style.Plain, args);
composer.Tab().Text(model.ApplicationName).Space()
.Style(helpStyles?.Examples?.Arguments ?? Style.Plain, args);
composer.LineBreak();
}

Expand All @@ -364,7 +383,8 @@ public virtual IEnumerable<IRenderable> GetArguments(ICommandModel model, IComma

var result = new List<IRenderable>
{
NewComposer().LineBreak().Style(helpStyles?.Arguments?.Header ?? Style.Plain, $"{resources.Arguments}:").LineBreak(),
NewComposer().LineBreak().Style(helpStyles?.Arguments?.Header ?? Style.Plain, $"{resources.Arguments}:")
.LineBreak(),
};

var grid = new Grid();
Expand Down Expand Up @@ -407,7 +427,8 @@ public virtual IEnumerable<IRenderable> GetOptions(ICommandModel model, ICommand

var result = new List<IRenderable>
{
NewComposer().LineBreak().Style(helpStyles?.Options?.Header ?? Style.Plain, $"{resources.Options}:").LineBreak(),
NewComposer().LineBreak().Style(helpStyles?.Options?.Header ?? Style.Plain, $"{resources.Options}:")
.LineBreak(),
};

var helpOptions = parameters.ToArray();
Expand Down Expand Up @@ -439,7 +460,15 @@ public virtual IEnumerable<IRenderable> GetOptions(ICommandModel model, ICommand
columns.Add(GetDefaultValueForOption(option.DefaultValue));
}

columns.Add(NewComposer().Text(NormalizeDescription(option.Description)));
var description = option.Description;
if (option.IsRequired)
{
description = string.IsNullOrWhiteSpace(description)
? "[i]Required[/]"
: description.TrimEnd('.') + ". [i]Required[/]";
}

columns.Add(NewComposer().Text(NormalizeDescription(description)));

grid.AddRow(columns.ToArray());
}
Expand Down Expand Up @@ -470,7 +499,8 @@ public virtual IEnumerable<IRenderable> GetCommands(ICommandModel model, IComman

var result = new List<IRenderable>
{
NewComposer().LineBreak().Style(helpStyles?.Commands?.Header ?? Style.Plain, $"{resources.Commands}:").LineBreak(),
NewComposer().LineBreak().Style(helpStyles?.Commands?.Header ?? Style.Plain, $"{resources.Commands}:")
.LineBreak(),
};

var grid = new Grid();
Expand Down Expand Up @@ -546,11 +576,11 @@ private IRenderable GetOptionParts(HelpOption option)
composer.Text(" ");
if (option.ValueIsOptional ?? false)
{
composer.Style(helpStyles?.Options?.OptionalOption ?? Style.Plain, $"[{option.Value}]");
composer.Style(helpStyles?.Options?.OptionalOptionValue ?? Style.Plain, $"[{option.Value}]");
}
else
{
composer.Style(helpStyles?.Options?.RequiredOption ?? Style.Plain, $"<{option.Value}>");
composer.Style(helpStyles?.Options?.RequiredOptionValue ?? Style.Plain, $"<{option.Value}>");
}
}

Expand All @@ -564,8 +594,15 @@ private Composer GetDefaultValueForOption(object? defaultValue)
null => NewComposer().Text(" "),
"" => NewComposer().Text(" "),
Array { Length: 0 } => NewComposer().Text(" "),
Array array => NewComposer().Join(", ", array.Cast<object>().Select(o => NewComposer().Style(helpStyles?.Options?.DefaultValue ?? Style.Plain, o.ToString() ?? string.Empty))),
_ => NewComposer().Style(helpStyles?.Options?.DefaultValue ?? Style.Plain, defaultValue?.ToString() ?? string.Empty),
Array array => NewComposer().Join(
", ",
array.Cast<object>().Select(o =>
NewComposer().Style(
helpStyles?.Options?.DefaultValue ?? Style.Plain,
o.ToString() ?? string.Empty))),
_ => NewComposer().Style(
helpStyles?.Options?.DefaultValue ?? Style.Plain,
defaultValue?.ToString() ?? string.Empty),
};
}

Expand Down
11 changes: 8 additions & 3 deletions src/Spectre.Console.Cli/Help/HelpProviderStyles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ public sealed class HelpProviderStyle
Header = "yellow",
DefaultValueHeader = "lime",
DefaultValue = "bold",
RequiredOption = "silver",
OptionalOption = "grey",
RequiredOptionValue = "silver",
OptionalOptionValue = "grey",
},
};
}
Expand Down Expand Up @@ -212,8 +212,13 @@ public sealed class OptionStyle
/// </summary>
public Style? RequiredOption { get; set; }

/// <summary>
/// Gets or sets the style for required option values.
/// </summary>
public Style? RequiredOptionValue { get; set; }

/// <summary>
/// Gets or sets the style for optional options.
/// </summary>
public Style? OptionalOption { get; set; }
public Style? OptionalOptionValue { get; set; }
}
2 changes: 1 addition & 1 deletion src/Spectre.Console.Cli/Help/ICommandParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public interface ICommandParameter
/// <summary>
/// Gets a value indicating whether the parameter is required.
/// </summary>
bool Required { get; }
bool IsRequired { get; }

/// <summary>
/// Gets the description of the parameter.
Expand Down
2 changes: 1 addition & 1 deletion src/Spectre.Console.Cli/Internal/CommandExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public async Task<int> Execute(IConfiguration configuration, IEnumerable<string>
}

// 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));
Expand Down
4 changes: 3 additions & 1 deletion src/Spectre.Console.Cli/Internal/CommandValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ public static void ValidateRequiredParameters(CommandTree? tree)
{
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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ private void AddParameter(TreeNode parametersNode, CommandParameter parameter, b
parameterNode.AddNode(ValueMarkup("Value", commandArgumentParameter.Value));
}

parameterNode.AddNode(ValueMarkup("Required", parameter.Required.ToString()));
parameterNode.AddNode(ValueMarkup("Required", parameter.IsRequired.ToString()));

if (parameter.Converter != null)
{
Expand Down
4 changes: 2 additions & 2 deletions src/Spectre.Console.Cli/Internal/Commands/XmlDocCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ private static IEnumerable<XmlNode> CreateParameterNodes(XmlDocument document, C
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);

Expand Down Expand Up @@ -186,7 +186,7 @@ private static IEnumerable<XmlNode> CreateParameterNodes(XmlDocument document, C
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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ private static void Validate(CommandInfo? command)
// Arguments
foreach (var argument in arguments)
{
if (argument.Required && argument.DefaultValue != null)
if (argument.IsRequired && argument.DefaultValue != null)
{
throw CommandConfigurationException.RequiredArgumentsCannotHaveDefaultValue(argument);
}
Expand Down
Loading