mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-16 08:52:50 +08:00
parent
70da3f40ff
commit
714cf179cb
@ -51,6 +51,14 @@ public interface ICommandAppSettings
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
bool StrictParsing { get; set; }
|
bool StrictParsing { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not flags found on the commnd line
|
||||||
|
/// that would normally result in a <see cref="CommandParseException"/> being thrown
|
||||||
|
/// during parsing with the message "Flags cannot be assigned a value."
|
||||||
|
/// should instead be added to the remaining arguments collection.
|
||||||
|
/// </summary>
|
||||||
|
bool ConvertFlagsToRemainingArguments { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether or not exceptions should be propagated.
|
/// Gets or sets a value indicating whether or not exceptions should be propagated.
|
||||||
/// <para>Setting this to <c>true</c> will disable default Exception handling and
|
/// <para>Setting this to <c>true</c> will disable default Exception handling and
|
||||||
|
@ -19,6 +19,18 @@ public interface IConfigurator<in TSettings>
|
|||||||
/// <param name="args">The example arguments.</param>
|
/// <param name="args">The example arguments.</param>
|
||||||
void AddExample(string[] args);
|
void AddExample(string[] args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a default command.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is the command that will run if the user doesn't specify one on the command line.
|
||||||
|
/// It must be able to execute successfully by itself ie. without requiring any command line
|
||||||
|
/// arguments, flags or option values.
|
||||||
|
/// </remarks>
|
||||||
|
/// <typeparam name="TDefaultCommand">The default command type.</typeparam>
|
||||||
|
void SetDefaultCommand<TDefaultCommand>()
|
||||||
|
where TDefaultCommand : class, ICommandLimiter<TSettings>;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Marks the branch as hidden.
|
/// Marks the branch as hidden.
|
||||||
/// Hidden branches do not show up in help documentation or
|
/// Hidden branches do not show up in help documentation or
|
||||||
|
@ -45,12 +45,10 @@ internal sealed class CommandExecutor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse and map the model against the arguments.
|
// Parse and map the model against the arguments.
|
||||||
var parser = new CommandTreeParser(model, configuration.Settings);
|
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args);
|
||||||
var parsedResult = parser.Parse(args);
|
|
||||||
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
|
|
||||||
|
|
||||||
// Currently the root?
|
// Currently the root?
|
||||||
if (parsedResult.Tree == null)
|
if (parsedResult?.Tree == null)
|
||||||
{
|
{
|
||||||
// Display help.
|
// Display help.
|
||||||
configuration.Settings.Console.SafeRender(HelpWriter.Write(model, configuration.Settings.ShowOptionDefaultValues));
|
configuration.Settings.Console.SafeRender(HelpWriter.Write(model, configuration.Settings.ShowOptionDefaultValues));
|
||||||
@ -75,6 +73,7 @@ internal sealed class CommandExecutor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Register the arguments with the container.
|
// Register the arguments with the container.
|
||||||
|
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
|
||||||
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);
|
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);
|
||||||
|
|
||||||
// Create the resolver and the context.
|
// Create the resolver and the context.
|
||||||
@ -87,6 +86,34 @@ internal sealed class CommandExecutor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CommandTreeParserResult? ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IEnumerable<string> args)
|
||||||
|
{
|
||||||
|
var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments);
|
||||||
|
|
||||||
|
var parserContext = new CommandTreeParserContext(args, settings.ParsingMode);
|
||||||
|
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
|
||||||
|
var parsedResult = parser.Parse(parserContext, tokenizerResult);
|
||||||
|
|
||||||
|
var lastParsedLeaf = parsedResult?.Tree?.GetLeafCommand();
|
||||||
|
var lastParsedCommand = lastParsedLeaf?.Command;
|
||||||
|
if (lastParsedLeaf != null && lastParsedCommand != null &&
|
||||||
|
lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp &&
|
||||||
|
lastParsedCommand.DefaultCommand != null)
|
||||||
|
{
|
||||||
|
// Insert this branch's default command into the command line
|
||||||
|
// arguments and try again to see if it will parse.
|
||||||
|
var argsWithDefaultCommand = new List<string>(args);
|
||||||
|
|
||||||
|
argsWithDefaultCommand.Insert(tokenizerResult.Tokens.Position, lastParsedCommand.DefaultCommand.Name);
|
||||||
|
|
||||||
|
parserContext = new CommandTreeParserContext(argsWithDefaultCommand, settings.ParsingMode);
|
||||||
|
tokenizerResult = CommandTreeTokenizer.Tokenize(argsWithDefaultCommand);
|
||||||
|
parsedResult = parser.Parse(parserContext, tokenizerResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedResult;
|
||||||
|
}
|
||||||
|
|
||||||
private static string ResolveApplicationVersion(IConfiguration configuration)
|
private static string ResolveApplicationVersion(IConfiguration configuration)
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
|
@ -13,6 +13,7 @@ internal sealed class CommandAppSettings : ICommandAppSettings
|
|||||||
public bool ValidateExamples { get; set; }
|
public bool ValidateExamples { get; set; }
|
||||||
public bool TrimTrailingPeriod { get; set; } = true;
|
public bool TrimTrailingPeriod { get; set; } = true;
|
||||||
public bool StrictParsing { get; set; }
|
public bool StrictParsing { get; set; }
|
||||||
|
public bool ConvertFlagsToRemainingArguments { get; set; } = false;
|
||||||
|
|
||||||
public ParsingMode ParsingMode =>
|
public ParsingMode ParsingMode =>
|
||||||
StrictParsing ? ParsingMode.Strict : ParsingMode.Relaxed;
|
StrictParsing ? ParsingMode.Strict : ParsingMode.Relaxed;
|
||||||
|
@ -36,7 +36,7 @@ internal sealed class Configurator : IUnsafeConfigurator, IConfigurator, IConfig
|
|||||||
public ICommandConfigurator AddCommand<TCommand>(string name)
|
public ICommandConfigurator AddCommand<TCommand>(string name)
|
||||||
where TCommand : class, ICommand
|
where TCommand : class, ICommand
|
||||||
{
|
{
|
||||||
var command = Commands.AddAndReturn(ConfiguredCommand.FromType<TCommand>(name, false));
|
var command = Commands.AddAndReturn(ConfiguredCommand.FromType<TCommand>(name, isDefaultCommand: false));
|
||||||
return new CommandConfigurator(command);
|
return new CommandConfigurator(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,14 @@ internal sealed class Configurator<TSettings> : IUnsafeBranchConfigurator, IConf
|
|||||||
_command.Examples.Add(args);
|
_command.Examples.Add(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetDefaultCommand<TDefaultCommand>()
|
||||||
|
where TDefaultCommand : class, ICommandLimiter<TSettings>
|
||||||
|
{
|
||||||
|
var defaultCommand = ConfiguredCommand.FromType<TDefaultCommand>(
|
||||||
|
CliConstants.DefaultCommandName, isDefaultCommand: true);
|
||||||
|
_command.Children.Add(defaultCommand);
|
||||||
|
}
|
||||||
|
|
||||||
public void HideBranch()
|
public void HideBranch()
|
||||||
{
|
{
|
||||||
_command.IsHidden = true;
|
_command.IsHidden = true;
|
||||||
@ -30,7 +38,7 @@ internal sealed class Configurator<TSettings> : IUnsafeBranchConfigurator, IConf
|
|||||||
public ICommandConfigurator AddCommand<TCommand>(string name)
|
public ICommandConfigurator AddCommand<TCommand>(string name)
|
||||||
where TCommand : class, ICommandLimiter<TSettings>
|
where TCommand : class, ICommandLimiter<TSettings>
|
||||||
{
|
{
|
||||||
var command = ConfiguredCommand.FromType<TCommand>(name);
|
var command = ConfiguredCommand.FromType<TCommand>(name, isDefaultCommand: false);
|
||||||
var configurator = new CommandConfigurator(command);
|
var configurator = new CommandConfigurator(command);
|
||||||
|
|
||||||
_command.Children.Add(command);
|
_command.Children.Add(command);
|
||||||
|
@ -29,6 +29,9 @@ internal sealed class ConfiguredCommand
|
|||||||
Delegate = @delegate;
|
Delegate = @delegate;
|
||||||
IsDefaultCommand = isDefaultCommand;
|
IsDefaultCommand = isDefaultCommand;
|
||||||
|
|
||||||
|
// Default commands are always created as hidden.
|
||||||
|
IsHidden = IsDefaultCommand;
|
||||||
|
|
||||||
Children = new List<ConfiguredCommand>();
|
Children = new List<ConfiguredCommand>();
|
||||||
Examples = new List<string[]>();
|
Examples = new List<string[]>();
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,6 @@ internal static class TypeRegistrarExtensions
|
|||||||
{
|
{
|
||||||
var stack = new Stack<CommandInfo>();
|
var stack = new Stack<CommandInfo>();
|
||||||
model.Commands.ForEach(c => stack.Push(c));
|
model.Commands.ForEach(c => stack.Push(c));
|
||||||
if (model.DefaultCommand != null)
|
|
||||||
{
|
|
||||||
stack.Push(model.DefaultCommand);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (stack.Count > 0)
|
while (stack.Count > 0)
|
||||||
{
|
{
|
||||||
|
@ -10,7 +10,6 @@ internal sealed class CommandInfo : ICommandContainer
|
|||||||
public Type SettingsType { get; }
|
public Type SettingsType { get; }
|
||||||
public Func<CommandContext, CommandSettings, int>? Delegate { get; }
|
public Func<CommandContext, CommandSettings, int>? Delegate { get; }
|
||||||
public bool IsDefaultCommand { get; }
|
public bool IsDefaultCommand { get; }
|
||||||
public bool IsHidden { get; }
|
|
||||||
public CommandInfo? Parent { get; }
|
public CommandInfo? Parent { get; }
|
||||||
public IList<CommandInfo> Children { get; }
|
public IList<CommandInfo> Children { get; }
|
||||||
public IList<CommandParameter> Parameters { get; }
|
public IList<CommandParameter> Parameters { get; }
|
||||||
@ -19,6 +18,10 @@ internal sealed class CommandInfo : ICommandContainer
|
|||||||
public bool IsBranch => CommandType == null && Delegate == null;
|
public bool IsBranch => CommandType == null && Delegate == null;
|
||||||
IList<CommandInfo> ICommandContainer.Commands => Children;
|
IList<CommandInfo> ICommandContainer.Commands => Children;
|
||||||
|
|
||||||
|
// only branches can have a default command
|
||||||
|
public CommandInfo? DefaultCommand => IsBranch ? Children.FirstOrDefault(c => c.IsDefaultCommand) : null;
|
||||||
|
public bool IsHidden { get; }
|
||||||
|
|
||||||
public CommandInfo(CommandInfo? parent, ConfiguredCommand prototype)
|
public CommandInfo(CommandInfo? parent, ConfiguredCommand prototype)
|
||||||
{
|
{
|
||||||
Parent = parent;
|
Parent = parent;
|
||||||
|
@ -4,21 +4,20 @@ internal sealed class CommandModel : ICommandContainer
|
|||||||
{
|
{
|
||||||
public string? ApplicationName { get; }
|
public string? ApplicationName { get; }
|
||||||
public ParsingMode ParsingMode { get; }
|
public ParsingMode ParsingMode { get; }
|
||||||
public CommandInfo? DefaultCommand { get; }
|
|
||||||
public IList<CommandInfo> Commands { get; }
|
public IList<CommandInfo> Commands { get; }
|
||||||
public IList<string[]> Examples { get; }
|
public IList<string[]> Examples { get; }
|
||||||
public bool TrimTrailingPeriod { get; }
|
public bool TrimTrailingPeriod { get; }
|
||||||
|
|
||||||
|
public CommandInfo? DefaultCommand => Commands.FirstOrDefault(c => c.IsDefaultCommand);
|
||||||
|
|
||||||
public CommandModel(
|
public CommandModel(
|
||||||
CommandAppSettings settings,
|
CommandAppSettings settings,
|
||||||
CommandInfo? defaultCommand,
|
|
||||||
IEnumerable<CommandInfo> commands,
|
IEnumerable<CommandInfo> commands,
|
||||||
IEnumerable<string[]> examples)
|
IEnumerable<string[]> examples)
|
||||||
{
|
{
|
||||||
ApplicationName = settings.ApplicationName;
|
ApplicationName = settings.ApplicationName;
|
||||||
ParsingMode = settings.ParsingMode;
|
ParsingMode = settings.ParsingMode;
|
||||||
TrimTrailingPeriod = settings.TrimTrailingPeriod;
|
TrimTrailingPeriod = settings.TrimTrailingPeriod;
|
||||||
DefaultCommand = defaultCommand;
|
|
||||||
Commands = new List<CommandInfo>(commands ?? Array.Empty<CommandInfo>());
|
Commands = new List<CommandInfo>(commands ?? Array.Empty<CommandInfo>());
|
||||||
Examples = new List<string[]>(examples ?? Array.Empty<string[]>());
|
Examples = new List<string[]>(examples ?? Array.Empty<string[]>());
|
||||||
}
|
}
|
||||||
|
@ -25,18 +25,19 @@ internal static class CommandModelBuilder
|
|||||||
result.Add(Build(null, command));
|
result.Add(Build(null, command));
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultCommand = default(CommandInfo);
|
|
||||||
if (configuration.DefaultCommand != null)
|
if (configuration.DefaultCommand != null)
|
||||||
{
|
{
|
||||||
// Add the examples from the configuration to the default command.
|
// Add the examples from the configuration to the default command.
|
||||||
configuration.DefaultCommand.Examples.AddRange(configuration.Examples);
|
configuration.DefaultCommand.Examples.AddRange(configuration.Examples);
|
||||||
|
|
||||||
// Build the default command.
|
// Build the default command.
|
||||||
defaultCommand = Build(null, configuration.DefaultCommand);
|
var defaultCommand = Build(null, configuration.DefaultCommand);
|
||||||
|
|
||||||
|
result.Add(defaultCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the command model and validate it.
|
// Create the command model and validate it.
|
||||||
var model = new CommandModel(configuration.Settings, defaultCommand, result, configuration.Examples);
|
var model = new CommandModel(configuration.Settings, result, configuration.Examples);
|
||||||
CommandModelValidator.Validate(model, configuration.Settings);
|
CommandModelValidator.Validate(model, configuration.Settings);
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
|
@ -14,7 +14,7 @@ internal static class CommandModelValidator
|
|||||||
throw new ArgumentNullException(nameof(settings));
|
throw new ArgumentNullException(nameof(settings));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.Commands.Count == 0 && model.DefaultCommand == null)
|
if (model.Commands.Count == 0)
|
||||||
{
|
{
|
||||||
throw CommandConfigurationException.NoCommandConfigured();
|
throw CommandConfigurationException.NoCommandConfigured();
|
||||||
}
|
}
|
||||||
@ -31,7 +31,6 @@ internal static class CommandModelValidator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Validate(model.DefaultCommand);
|
|
||||||
foreach (var command in model.Commands)
|
foreach (var command in model.Commands)
|
||||||
{
|
{
|
||||||
Validate(command);
|
Validate(command);
|
||||||
@ -147,7 +146,7 @@ internal static class CommandModelValidator
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var parser = new CommandTreeParser(model, settings, ParsingMode.Strict);
|
var parser = new CommandTreeParser(model, settings.CaseSensitivity, ParsingMode.Strict);
|
||||||
parser.Parse(example);
|
parser.Parse(example);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -9,4 +9,12 @@ internal interface ICommandContainer
|
|||||||
/// Gets all commands in the container.
|
/// Gets all commands in the container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IList<CommandInfo> Commands { get; }
|
IList<CommandInfo> Commands { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the default command for the container.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Returns null if a default command has not been set.
|
||||||
|
/// </remarks>
|
||||||
|
CommandInfo? DefaultCommand { get; }
|
||||||
}
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
using static Spectre.Console.Cli.CommandTreeTokenizer;
|
||||||
|
|
||||||
namespace Spectre.Console.Cli;
|
namespace Spectre.Console.Cli;
|
||||||
|
|
||||||
internal class CommandTreeParser
|
internal class CommandTreeParser
|
||||||
@ -5,6 +7,7 @@ internal class CommandTreeParser
|
|||||||
private readonly CommandModel _configuration;
|
private readonly CommandModel _configuration;
|
||||||
private readonly ParsingMode _parsingMode;
|
private readonly ParsingMode _parsingMode;
|
||||||
private readonly CommandOptionAttribute _help;
|
private readonly CommandOptionAttribute _help;
|
||||||
|
private readonly bool _convertFlagsToRemainingArguments;
|
||||||
|
|
||||||
public CaseSensitivity CaseSensitivity { get; }
|
public CaseSensitivity CaseSensitivity { get; }
|
||||||
|
|
||||||
@ -14,25 +17,26 @@ internal class CommandTreeParser
|
|||||||
Remaining = 1,
|
Remaining = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandTreeParser(CommandModel configuration, ICommandAppSettings settings, ParsingMode? parsingMode = null)
|
public CommandTreeParser(CommandModel configuration, CaseSensitivity caseSensitivity, ParsingMode? parsingMode = null, bool? convertFlagsToRemainingArguments = null)
|
||||||
{
|
{
|
||||||
if (settings is null)
|
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(settings));
|
|
||||||
}
|
|
||||||
|
|
||||||
_configuration = configuration;
|
|
||||||
_parsingMode = parsingMode ?? _configuration.ParsingMode;
|
_parsingMode = parsingMode ?? _configuration.ParsingMode;
|
||||||
_help = new CommandOptionAttribute("-h|--help");
|
_help = new CommandOptionAttribute("-h|--help");
|
||||||
|
_convertFlagsToRemainingArguments = convertFlagsToRemainingArguments ?? false;
|
||||||
|
|
||||||
CaseSensitivity = settings.CaseSensitivity;
|
CaseSensitivity = caseSensitivity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandTreeParserResult Parse(IEnumerable<string> args)
|
public CommandTreeParserResult Parse(IEnumerable<string> args)
|
||||||
{
|
{
|
||||||
var context = new CommandTreeParserContext(args, _parsingMode);
|
var parserContext = new CommandTreeParserContext(args, _parsingMode);
|
||||||
|
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
|
||||||
|
|
||||||
var tokenizerResult = CommandTreeTokenizer.Tokenize(context.Arguments);
|
return Parse(parserContext, tokenizerResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandTreeParserResult Parse(CommandTreeParserContext context, CommandTreeTokenizerResult tokenizerResult)
|
||||||
|
{
|
||||||
var tokens = tokenizerResult.Tokens;
|
var tokens = tokenizerResult.Tokens;
|
||||||
var rawRemaining = tokenizerResult.Remaining;
|
var rawRemaining = tokenizerResult.Remaining;
|
||||||
|
|
||||||
@ -255,9 +259,7 @@ internal class CommandTreeParser
|
|||||||
var option = node.FindOption(token.Value, isLongOption, CaseSensitivity);
|
var option = node.FindOption(token.Value, isLongOption, CaseSensitivity);
|
||||||
if (option != null)
|
if (option != null)
|
||||||
{
|
{
|
||||||
node.Mapped.Add(new MappedCommandParameter(
|
ParseOptionValue(context, stream, token, node, option);
|
||||||
option, ParseOptionValue(context, stream, token, node, option)));
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,7 +273,7 @@ internal class CommandTreeParser
|
|||||||
|
|
||||||
if (context.State == State.Remaining)
|
if (context.State == State.Remaining)
|
||||||
{
|
{
|
||||||
ParseOptionValue(context, stream, token, node, null);
|
ParseOptionValue(context, stream, token, node);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,17 +283,19 @@ internal class CommandTreeParser
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ParseOptionValue(context, stream, token, node, null);
|
ParseOptionValue(context, stream, token, node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? ParseOptionValue(
|
private void ParseOptionValue(
|
||||||
CommandTreeParserContext context,
|
CommandTreeParserContext context,
|
||||||
CommandTreeTokenStream stream,
|
CommandTreeTokenStream stream,
|
||||||
CommandTreeToken token,
|
CommandTreeToken token,
|
||||||
CommandTree current,
|
CommandTree current,
|
||||||
CommandParameter? parameter)
|
CommandParameter? parameter = null)
|
||||||
{
|
{
|
||||||
|
bool addToMappedCommandParameters = parameter != null;
|
||||||
|
|
||||||
var value = default(string);
|
var value = default(string);
|
||||||
|
|
||||||
// Parse the value of the token (if any).
|
// Parse the value of the token (if any).
|
||||||
@ -325,7 +329,21 @@ internal class CommandTreeParser
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Flags cannot be assigned a value.
|
// Flags cannot be assigned a value.
|
||||||
throw CommandParseException.CannotAssignValueToFlag(context.Arguments, token);
|
if (_convertFlagsToRemainingArguments)
|
||||||
|
{
|
||||||
|
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
||||||
|
|
||||||
|
context.AddRemainingArgument(token.Value, value);
|
||||||
|
|
||||||
|
// Prevent the option and it's non-boolean value from being added to
|
||||||
|
// mapped parameters (otherwise an exception will be thrown later
|
||||||
|
// when binding the value to the flag in the comand settings)
|
||||||
|
addToMappedCommandParameters = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw CommandParseException.CannotAssignValueToFlag(context.Arguments, token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -358,7 +376,8 @@ internal class CommandTreeParser
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (context.State == State.Remaining || context.ParsingMode == ParsingMode.Relaxed)
|
if (parameter == null && // Only add tokens which have not been matched to a command parameter
|
||||||
|
(context.State == State.Remaining || context.ParsingMode == ParsingMode.Relaxed))
|
||||||
{
|
{
|
||||||
context.AddRemainingArgument(token.Value, null);
|
context.AddRemainingArgument(token.Value, null);
|
||||||
}
|
}
|
||||||
@ -379,10 +398,12 @@ internal class CommandTreeParser
|
|||||||
{
|
{
|
||||||
if (parameter.IsFlagValue())
|
if (parameter.IsFlagValue())
|
||||||
{
|
{
|
||||||
return null;
|
value = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw CommandParseException.OptionHasNoValue(context.Arguments, token, option);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw CommandParseException.OptionHasNoValue(context.Arguments, token, option);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -394,6 +415,9 @@ internal class CommandTreeParser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
if (parameter != null && addToMappedCommandParameters)
|
||||||
|
{
|
||||||
|
current.Mapped.Add(new MappedCommandParameter(parameter, value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -30,15 +30,12 @@ internal class CommandTreeParserContext
|
|||||||
|
|
||||||
public void AddRemainingArgument(string key, string? value)
|
public void AddRemainingArgument(string key, string? value)
|
||||||
{
|
{
|
||||||
if (State == CommandTreeParser.State.Remaining || ParsingMode == ParsingMode.Relaxed)
|
if (!_remaining.ContainsKey(key))
|
||||||
{
|
{
|
||||||
if (!_remaining.ContainsKey(key))
|
_remaining.Add(key, new List<string?>());
|
||||||
{
|
|
||||||
_remaining.Add(key, new List<string?>());
|
|
||||||
}
|
|
||||||
|
|
||||||
_remaining[key].Add(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_remaining[key].Add(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("Style", "IDE0004:Remove Unnecessary Cast", Justification = "Bug in analyzer?")]
|
[SuppressMessage("Style", "IDE0004:Remove Unnecessary Cast", Justification = "Bug in analyzer?")]
|
||||||
|
@ -6,6 +6,7 @@ internal sealed class CommandTreeTokenStream : IReadOnlyList<CommandTreeToken>
|
|||||||
private int _position;
|
private int _position;
|
||||||
|
|
||||||
public int Count => _tokens.Count;
|
public int Count => _tokens.Count;
|
||||||
|
public int Position => _position;
|
||||||
|
|
||||||
public CommandTreeToken this[int index] => _tokens[index];
|
public CommandTreeToken this[int index] => _tokens[index];
|
||||||
|
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Model>
|
||||||
|
<!--ANIMAL-->
|
||||||
|
<Command Name="animal" IsBranch="true" Settings="Spectre.Console.Tests.Data.AnimalSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Argument Name="LEGS" Position="0" Required="false" Kind="scalar" ClrType="System.Int32">
|
||||||
|
<Description>The number of legs.</Description>
|
||||||
|
<Validators>
|
||||||
|
<Validator ClrType="Spectre.Console.Tests.Data.EvenNumberValidatorAttribute" Message="Animals must have an even number of legs." />
|
||||||
|
<Validator ClrType="Spectre.Console.Tests.Data.PositiveNumberValidatorAttribute" Message="Number of legs must be greater than 0." />
|
||||||
|
</Validators>
|
||||||
|
</Argument>
|
||||||
|
<Option Short="a" Long="alive,not-dead" Value="NULL" Required="false" Kind="flag" ClrType="System.Boolean">
|
||||||
|
<Description>Indicates whether or not the animal is alive.</Description>
|
||||||
|
</Option>
|
||||||
|
</Parameters>
|
||||||
|
<!--MAMMAL-->
|
||||||
|
<Command Name="mammal" IsBranch="true" Settings="Spectre.Console.Tests.Data.MammalSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Option Short="n,p" Long="name,pet-name" Value="VALUE" Required="false" Kind="scalar" ClrType="System.String" />
|
||||||
|
</Parameters>
|
||||||
|
<!--__DEFAULT_COMMAND-->
|
||||||
|
<Command Name="__default_command" IsBranch="false" ClrType="Spectre.Console.Tests.Data.HorseCommand" Settings="Spectre.Console.Tests.Data.HorseSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Option Short="d" Long="day" Value="NULL" Required="false" Kind="scalar" ClrType="System.DayOfWeek" />
|
||||||
|
<Option Short="" Long="directory" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.DirectoryInfo" />
|
||||||
|
<Option Short="" Long="file" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.FileInfo" />
|
||||||
|
</Parameters>
|
||||||
|
</Command>
|
||||||
|
</Command>
|
||||||
|
</Command>
|
||||||
|
</Model>
|
@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Model>
|
||||||
|
<!--ANIMAL-->
|
||||||
|
<Command Name="animal" IsBranch="true" Settings="Spectre.Console.Tests.Data.AnimalSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Argument Name="LEGS" Position="0" Required="false" Kind="scalar" ClrType="System.Int32">
|
||||||
|
<Description>The number of legs.</Description>
|
||||||
|
<Validators>
|
||||||
|
<Validator ClrType="Spectre.Console.Tests.Data.EvenNumberValidatorAttribute" Message="Animals must have an even number of legs." />
|
||||||
|
<Validator ClrType="Spectre.Console.Tests.Data.PositiveNumberValidatorAttribute" Message="Number of legs must be greater than 0." />
|
||||||
|
</Validators>
|
||||||
|
</Argument>
|
||||||
|
<Option Short="a" Long="alive,not-dead" Value="NULL" Required="false" Kind="flag" ClrType="System.Boolean">
|
||||||
|
<Description>Indicates whether or not the animal is alive.</Description>
|
||||||
|
</Option>
|
||||||
|
</Parameters>
|
||||||
|
<!--DOG-->
|
||||||
|
<Command Name="dog" IsBranch="false" ClrType="Spectre.Console.Tests.Data.DogCommand" Settings="Spectre.Console.Tests.Data.DogSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Argument Name="AGE" Position="0" Required="true" Kind="scalar" ClrType="System.Int32" />
|
||||||
|
<Option Short="g" Long="good-boy" Value="NULL" Required="false" Kind="flag" ClrType="System.Boolean" />
|
||||||
|
<Option Short="n,p" Long="name,pet-name" Value="VALUE" Required="false" Kind="scalar" ClrType="System.String" />
|
||||||
|
</Parameters>
|
||||||
|
</Command>
|
||||||
|
<!--__DEFAULT_COMMAND-->
|
||||||
|
<Command Name="__default_command" IsBranch="false" ClrType="Spectre.Console.Tests.Data.HorseCommand" Settings="Spectre.Console.Tests.Data.HorseSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Option Short="d" Long="day" Value="NULL" Required="false" Kind="scalar" ClrType="System.DayOfWeek" />
|
||||||
|
<Option Short="" Long="directory" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.DirectoryInfo" />
|
||||||
|
<Option Short="" Long="file" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.FileInfo" />
|
||||||
|
<Option Short="n,p" Long="name,pet-name" Value="VALUE" Required="false" Kind="scalar" ClrType="System.String" />
|
||||||
|
</Parameters>
|
||||||
|
</Command>
|
||||||
|
</Command>
|
||||||
|
</Model>
|
@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Model>
|
||||||
|
<!--DEFAULT COMMAND-->
|
||||||
|
<Command Name="__default_command" IsBranch="false" IsDefault="true" ClrType="Spectre.Console.Tests.Data.EmptyCommand" Settings="Spectre.Console.Cli.EmptyCommandSettings" />
|
||||||
|
<!--ANIMAL-->
|
||||||
|
<Command Name="animal" IsBranch="true" Settings="Spectre.Console.Tests.Data.AnimalSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Argument Name="LEGS" Position="0" Required="false" Kind="scalar" ClrType="System.Int32">
|
||||||
|
<Description>The number of legs.</Description>
|
||||||
|
<Validators>
|
||||||
|
<Validator ClrType="Spectre.Console.Tests.Data.EvenNumberValidatorAttribute" Message="Animals must have an even number of legs." />
|
||||||
|
<Validator ClrType="Spectre.Console.Tests.Data.PositiveNumberValidatorAttribute" Message="Number of legs must be greater than 0." />
|
||||||
|
</Validators>
|
||||||
|
</Argument>
|
||||||
|
<Option Short="a" Long="alive,not-dead" Value="NULL" Required="false" Kind="flag" ClrType="System.Boolean">
|
||||||
|
<Description>Indicates whether or not the animal is alive.</Description>
|
||||||
|
</Option>
|
||||||
|
</Parameters>
|
||||||
|
<!--DOG-->
|
||||||
|
<Command Name="dog" IsBranch="false" ClrType="Spectre.Console.Tests.Data.DogCommand" Settings="Spectre.Console.Tests.Data.DogSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Argument Name="AGE" Position="0" Required="true" Kind="scalar" ClrType="System.Int32" />
|
||||||
|
<Option Short="g" Long="good-boy" Value="NULL" Required="false" Kind="flag" ClrType="System.Boolean" />
|
||||||
|
<Option Short="n,p" Long="name,pet-name" Value="VALUE" Required="false" Kind="scalar" ClrType="System.String" />
|
||||||
|
</Parameters>
|
||||||
|
</Command>
|
||||||
|
<!--__DEFAULT_COMMAND-->
|
||||||
|
<Command Name="__default_command" IsBranch="false" ClrType="Spectre.Console.Tests.Data.HorseCommand" Settings="Spectre.Console.Tests.Data.HorseSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Option Short="d" Long="day" Value="NULL" Required="false" Kind="scalar" ClrType="System.DayOfWeek" />
|
||||||
|
<Option Short="" Long="directory" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.DirectoryInfo" />
|
||||||
|
<Option Short="" Long="file" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.FileInfo" />
|
||||||
|
<Option Short="n,p" Long="name,pet-name" Value="VALUE" Required="false" Kind="scalar" ClrType="System.String" />
|
||||||
|
</Parameters>
|
||||||
|
</Command>
|
||||||
|
</Command>
|
||||||
|
</Model>
|
351
test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Branches.cs
Normal file
351
test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Branches.cs
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
namespace Spectre.Console.Tests.Unit.Cli;
|
||||||
|
|
||||||
|
public sealed partial class CommandAppTests
|
||||||
|
{
|
||||||
|
public sealed class Branches
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Run_The_Default_Command_On_Branch()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.SetDefaultCommand<CatCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "4",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<CatSettings>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Throw_When_No_Default_Command_On_Branch()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal => { });
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() =>
|
||||||
|
{
|
||||||
|
app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "4",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<CommandConfigurationException>().And(ex =>
|
||||||
|
{
|
||||||
|
ex.Message.ShouldBe("The branch 'animal' does not define any commands.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1515:SingleLineCommentMustBePrecededByBlankLine", Justification = "Helps to illustrate the expected behaviour of this unit test.")]
|
||||||
|
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1005:SingleLineCommentsMustBeginWithSingleSpace", Justification = "Helps to illustrate the expected behaviour of this unit test.")]
|
||||||
|
[Fact]
|
||||||
|
public void Should_Be_Unable_To_Parse_Default_Command_Arguments_Relaxed_Parsing()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.SetDefaultCommand<CatCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
// The CommandTreeParser should be unable to determine which command line
|
||||||
|
// arguments belong to the branch and which belong to the branch's
|
||||||
|
// default command (once inserted).
|
||||||
|
"animal", "4", "--name", "Kitty",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<CatSettings>().And(cat =>
|
||||||
|
{
|
||||||
|
cat.Legs.ShouldBe(4);
|
||||||
|
//cat.Name.ShouldBe("Kitty"); //<-- Should normally be correct, but instead name will be added to the remaining arguments (see below).
|
||||||
|
});
|
||||||
|
result.Context.Remaining.Parsed.Count.ShouldBe(1);
|
||||||
|
result.Context.ShouldHaveRemainingArgument("name", values: new[] { "Kitty", });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Be_Unable_To_Parse_Default_Command_Arguments_Strict_Parsing()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.UseStrictParsing();
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.SetDefaultCommand<CatCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() =>
|
||||||
|
{
|
||||||
|
app.Run(new[]
|
||||||
|
{
|
||||||
|
// The CommandTreeParser should be unable to determine which command line
|
||||||
|
// arguments belong to the branch and which belong to the branch's
|
||||||
|
// default command (once inserted).
|
||||||
|
"animal", "4", "--name", "Kitty",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<CommandParseException>().And(ex =>
|
||||||
|
{
|
||||||
|
ex.Message.ShouldBe("Unknown option 'name'.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Run_The_Default_Command_On_Branch_On_Branch()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddBranch<MammalSettings>("mammal", mammal =>
|
||||||
|
{
|
||||||
|
mammal.SetDefaultCommand<CatCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "4", "mammal",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<CatSettings>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Run_The_Default_Command_On_Branch_On_Branch_With_Arguments()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddBranch<MammalSettings>("mammal", mammal =>
|
||||||
|
{
|
||||||
|
mammal.SetDefaultCommand<CatCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "4", "mammal", "--name", "Kitty",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<CatSettings>().And(cat =>
|
||||||
|
{
|
||||||
|
cat.Legs.ShouldBe(4);
|
||||||
|
cat.Name.ShouldBe("Kitty");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Run_The_Default_Command_Not_The_Named_Command_On_Branch()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddCommand<DogCommand>("dog");
|
||||||
|
|
||||||
|
animal.SetDefaultCommand<CatCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "4",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<CatSettings>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Run_The_Named_Command_Not_The_Default_Command_On_Branch()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddCommand<DogCommand>("dog");
|
||||||
|
|
||||||
|
animal.SetDefaultCommand<LionCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "4", "dog", "12", "--good-boy", "--name", "Rufus",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
||||||
|
{
|
||||||
|
dog.Legs.ShouldBe(4);
|
||||||
|
dog.Age.ShouldBe(12);
|
||||||
|
dog.GoodBoy.ShouldBe(true);
|
||||||
|
dog.Name.ShouldBe("Rufus");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Allow_Multiple_Branches_Multiple_Commands()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddBranch<MammalSettings>("mammal", mammal =>
|
||||||
|
{
|
||||||
|
mammal.AddCommand<DogCommand>("dog");
|
||||||
|
mammal.AddCommand<CatCommand>("cat");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "--alive", "mammal", "--name",
|
||||||
|
"Rufus", "dog", "12", "--good-boy",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
||||||
|
{
|
||||||
|
dog.Age.ShouldBe(12);
|
||||||
|
dog.GoodBoy.ShouldBe(true);
|
||||||
|
dog.Name.ShouldBe("Rufus");
|
||||||
|
dog.IsAlive.ShouldBe(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Allow_Single_Branch_Multiple_Commands()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddCommand<DogCommand>("dog");
|
||||||
|
animal.AddCommand<CatCommand>("cat");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "dog", "12", "--good-boy",
|
||||||
|
"--name", "Rufus",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
||||||
|
{
|
||||||
|
dog.Age.ShouldBe(12);
|
||||||
|
dog.GoodBoy.ShouldBe(true);
|
||||||
|
dog.Name.ShouldBe("Rufus");
|
||||||
|
dog.IsAlive.ShouldBe(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Allow_Single_Branch_Single_Command()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddCommand<DogCommand>("dog");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "4", "dog", "12", "--good-boy",
|
||||||
|
"--name", "Rufus",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
||||||
|
{
|
||||||
|
dog.Legs.ShouldBe(4);
|
||||||
|
dog.Age.ShouldBe(12);
|
||||||
|
dog.GoodBoy.ShouldBe(true);
|
||||||
|
dog.IsAlive.ShouldBe(false);
|
||||||
|
dog.Name.ShouldBe("Rufus");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,166 @@ public sealed partial class CommandAppTests
|
|||||||
{
|
{
|
||||||
public sealed class Remaining
|
public sealed class Remaining
|
||||||
{
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("-a")]
|
||||||
|
[InlineData("--alive")]
|
||||||
|
public void Should_Not_Add_Known_Flags_To_Remaining_Arguments_RelaxedParsing(string knownFlag)
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<DogCommand>("dog");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"dog", "12", "4",
|
||||||
|
knownFlag,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
||||||
|
{
|
||||||
|
dog.IsAlive.ShouldBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
result.Context.Remaining.Parsed.Count.ShouldBe(0);
|
||||||
|
result.Context.Remaining.Raw.Count.ShouldBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("-r")]
|
||||||
|
[InlineData("--romeo")]
|
||||||
|
public void Should_Add_Unknown_Flags_To_Remaining_Arguments_RelaxedParsing(string unknownFlag)
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<DogCommand>("dog");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"dog", "12", "4",
|
||||||
|
unknownFlag,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.Context.Remaining.Parsed.Count.ShouldBe(1);
|
||||||
|
result.Context.ShouldHaveRemainingArgument(unknownFlag.TrimStart('-'), values: new[] { (string)null });
|
||||||
|
result.Context.Remaining.Raw.Count.ShouldBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Add_Unknown_Flags_When_Grouped_To_Remaining_Arguments_RelaxedParsing()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<DogCommand>("dog");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"dog", "12", "4",
|
||||||
|
"-agr",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.Context.Remaining.Parsed.Count.ShouldBe(1);
|
||||||
|
result.Context.ShouldHaveRemainingArgument("r", values: new[] { (string)null });
|
||||||
|
result.Context.Remaining.Raw.Count.ShouldBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("-a")]
|
||||||
|
[InlineData("--alive")]
|
||||||
|
public void Should_Not_Add_Known_Flags_To_Remaining_Arguments_StrictParsing(string knownFlag)
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.UseStrictParsing();
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<DogCommand>("dog");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"dog", "12", "4",
|
||||||
|
knownFlag,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.Context.Remaining.Parsed.Count.ShouldBe(0);
|
||||||
|
result.Context.Remaining.Raw.Count.ShouldBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("-r")]
|
||||||
|
[InlineData("--romeo")]
|
||||||
|
public void Should_Not_Add_Unknown_Flags_To_Remaining_Arguments_StrictParsing(string unknownFlag)
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.UseStrictParsing();
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<DogCommand>("dog");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() => app.Run(new[]
|
||||||
|
{
|
||||||
|
"dog", "12", "4",
|
||||||
|
unknownFlag,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<CommandParseException>().And(ex =>
|
||||||
|
{
|
||||||
|
ex.Message.ShouldBe($"Unknown option '{unknownFlag.TrimStart('-')}'.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Not_Add_Unknown_Flags_When_Grouped_To_Remaining_Arguments_StrictParsing()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.UseStrictParsing();
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<DogCommand>("dog");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() => app.Run(new[]
|
||||||
|
{
|
||||||
|
"dog", "12", "4",
|
||||||
|
"-agr",
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<CommandParseException>().And(ex =>
|
||||||
|
{
|
||||||
|
ex.Message.ShouldBe($"Unknown option 'r'.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Register_Remaining_Parsed_Arguments_With_Context()
|
public void Should_Register_Remaining_Parsed_Arguments_With_Context()
|
||||||
{
|
{
|
||||||
@ -95,5 +255,34 @@ public sealed partial class CommandAppTests
|
|||||||
result.Context.Remaining.Raw[1].ShouldBe("\"set && pause\"");
|
result.Context.Remaining.Raw[1].ShouldBe("\"set && pause\"");
|
||||||
result.Context.Remaining.Raw[2].ShouldBe("Name=\" -Rufus --' ");
|
result.Context.Remaining.Raw[2].ShouldBe("Name=\" -Rufus --' ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true)]
|
||||||
|
[InlineData(false)]
|
||||||
|
public void Should_Convert_Flags_To_Remaining_Arguments_If_Cannot_Be_Assigned(bool useStrictParsing)
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.Settings.ConvertFlagsToRemainingArguments = true;
|
||||||
|
config.Settings.StrictParsing = useStrictParsing;
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<DogCommand>("dog");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"dog", "12", "4",
|
||||||
|
"--good-boy=Please be good Rufus!",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.Context.Remaining.Parsed.Count.ShouldBe(1);
|
||||||
|
result.Context.ShouldHaveRemainingArgument("good-boy", values: new[] { "Please be good Rufus!" });
|
||||||
|
|
||||||
|
result.Context.Remaining.Raw.Count.ShouldBe(0); // nb. there are no "raw" remaining arguments on the command line
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,6 +130,77 @@ public sealed partial class CommandAppTests
|
|||||||
return Verifier.Verify(result.Output);
|
return Verifier.Verify(result.Output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Expectation("Test_7")]
|
||||||
|
public Task Should_Dump_Correct_Model_For_Model_With_Single_Branch_Single_Branch_Default_Command()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var fixture = new CommandAppTester();
|
||||||
|
fixture.Configure(configuration =>
|
||||||
|
{
|
||||||
|
configuration.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddBranch<MammalSettings>("mammal", mammal =>
|
||||||
|
{
|
||||||
|
mammal.SetDefaultCommand<HorseCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = fixture.Run(Constants.XmlDocCommand);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
return Verifier.Verify(result.Output);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Expectation("Test_8")]
|
||||||
|
public Task Should_Dump_Correct_Model_For_Model_With_Single_Branch_Single_Command_Default_Command()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var fixture = new CommandAppTester();
|
||||||
|
fixture.Configure(configuration =>
|
||||||
|
{
|
||||||
|
configuration.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddCommand<DogCommand>("dog");
|
||||||
|
|
||||||
|
animal.SetDefaultCommand<HorseCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = fixture.Run(Constants.XmlDocCommand);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
return Verifier.Verify(result.Output);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Expectation("Test_9")]
|
||||||
|
public Task Should_Dump_Correct_Model_For_Model_With_Default_Command_Single_Branch_Single_Command_Default_Command()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var fixture = new CommandAppTester();
|
||||||
|
fixture.SetDefaultCommand<EmptyCommand>();
|
||||||
|
fixture.Configure(configuration =>
|
||||||
|
{
|
||||||
|
configuration.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddCommand<DogCommand>("dog");
|
||||||
|
|
||||||
|
animal.SetDefaultCommand<HorseCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = fixture.Run(Constants.XmlDocCommand);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
return Verifier.Verify(result.Output);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Expectation("Hidden_Command_Options")]
|
[Expectation("Hidden_Command_Options")]
|
||||||
public Task Should_Not_Dump_Hidden_Options_On_A_Command()
|
public Task Should_Not_Dump_Hidden_Options_On_A_Command()
|
||||||
|
@ -4,42 +4,6 @@ public sealed partial class CommandAppTests
|
|||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Pass_Case_1()
|
public void Should_Pass_Case_1()
|
||||||
{
|
|
||||||
// Given
|
|
||||||
var app = new CommandAppTester();
|
|
||||||
app.Configure(config =>
|
|
||||||
{
|
|
||||||
config.PropagateExceptions();
|
|
||||||
config.AddBranch<AnimalSettings>("animal", animal =>
|
|
||||||
{
|
|
||||||
animal.AddBranch<MammalSettings>("mammal", mammal =>
|
|
||||||
{
|
|
||||||
mammal.AddCommand<DogCommand>("dog");
|
|
||||||
mammal.AddCommand<HorseCommand>("horse");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// When
|
|
||||||
var result = app.Run(new[]
|
|
||||||
{
|
|
||||||
"animal", "--alive", "mammal", "--name",
|
|
||||||
"Rufus", "dog", "12", "--good-boy",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then
|
|
||||||
result.ExitCode.ShouldBe(0);
|
|
||||||
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
|
||||||
{
|
|
||||||
dog.Age.ShouldBe(12);
|
|
||||||
dog.GoodBoy.ShouldBe(true);
|
|
||||||
dog.Name.ShouldBe("Rufus");
|
|
||||||
dog.IsAlive.ShouldBe(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Should_Pass_Case_2()
|
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var app = new CommandAppTester();
|
var app = new CommandAppTester();
|
||||||
@ -52,8 +16,8 @@ public sealed partial class CommandAppTests
|
|||||||
// When
|
// When
|
||||||
var result = app.Run(new[]
|
var result = app.Run(new[]
|
||||||
{
|
{
|
||||||
"dog", "12", "4", "--good-boy",
|
"dog", "12", "4", "--good-boy",
|
||||||
"--name", "Rufus", "--alive",
|
"--name", "Rufus", "--alive",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
@ -69,7 +33,7 @@ public sealed partial class CommandAppTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Pass_Case_3()
|
public void Should_Pass_Case_2()
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var app = new CommandAppTester();
|
var app = new CommandAppTester();
|
||||||
@ -197,7 +161,7 @@ public sealed partial class CommandAppTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Pass_Case_7()
|
public void Should_Pass_Case_3()
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var app = new CommandAppTester();
|
var app = new CommandAppTester();
|
||||||
@ -904,6 +868,86 @@ public sealed partial class CommandAppTests
|
|||||||
result.Context.ShouldHaveRemainingArgument("foo", values: new[] { (string)null });
|
result.Context.ShouldHaveRemainingArgument("foo", values: new[] { (string)null });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Run_The_Default_Command()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.SetDefaultCommand<DogCommand>();
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"4", "12", "--good-boy", "--name", "Rufus",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
||||||
|
{
|
||||||
|
dog.Legs.ShouldBe(4);
|
||||||
|
dog.Age.ShouldBe(12);
|
||||||
|
dog.GoodBoy.ShouldBe(true);
|
||||||
|
dog.Name.ShouldBe("Rufus");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Run_The_Default_Command_Not_The_Named_Command()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<HorseCommand>("horse");
|
||||||
|
});
|
||||||
|
app.SetDefaultCommand<DogCommand>();
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"4", "12", "--good-boy", "--name", "Rufus",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
||||||
|
{
|
||||||
|
dog.Legs.ShouldBe(4);
|
||||||
|
dog.Age.ShouldBe(12);
|
||||||
|
dog.GoodBoy.ShouldBe(true);
|
||||||
|
dog.Name.ShouldBe("Rufus");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Run_The_Named_Command_Not_The_Default_Command()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<HorseCommand>("horse");
|
||||||
|
});
|
||||||
|
app.SetDefaultCommand<DogCommand>();
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"horse", "4", "--name", "Arkle",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<HorseSettings>().And(horse =>
|
||||||
|
{
|
||||||
|
horse.Legs.ShouldBe(4);
|
||||||
|
horse.Name.ShouldBe("Arkle");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Set_Command_Name_In_Context()
|
public void Should_Set_Command_Name_In_Context()
|
||||||
{
|
{
|
||||||
@ -1081,67 +1125,4 @@ public sealed partial class CommandAppTests
|
|||||||
data.ShouldBe(2);
|
data.ShouldBe(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class Remaining_Arguments
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void Should_Register_Remaining_Parsed_Arguments_With_Context()
|
|
||||||
{
|
|
||||||
// Given
|
|
||||||
var app = new CommandAppTester();
|
|
||||||
app.Configure(config =>
|
|
||||||
{
|
|
||||||
config.PropagateExceptions();
|
|
||||||
config.AddBranch<AnimalSettings>("animal", animal =>
|
|
||||||
{
|
|
||||||
animal.AddCommand<DogCommand>("dog");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// When
|
|
||||||
var result = app.Run(new[]
|
|
||||||
{
|
|
||||||
"animal", "4", "dog", "12", "--",
|
|
||||||
"--foo", "bar", "--foo", "baz",
|
|
||||||
"-bar", "\"baz\"", "qux",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then
|
|
||||||
result.Context.Remaining.Parsed.Count.ShouldBe(4);
|
|
||||||
result.Context.ShouldHaveRemainingArgument("foo", values: new[] { "bar", "baz" });
|
|
||||||
result.Context.ShouldHaveRemainingArgument("b", values: new[] { (string)null });
|
|
||||||
result.Context.ShouldHaveRemainingArgument("a", values: new[] { (string)null });
|
|
||||||
result.Context.ShouldHaveRemainingArgument("r", values: new[] { (string)null });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Should_Register_Remaining_Raw_Arguments_With_Context()
|
|
||||||
{
|
|
||||||
// Given
|
|
||||||
var app = new CommandAppTester();
|
|
||||||
app.Configure(config =>
|
|
||||||
{
|
|
||||||
config.PropagateExceptions();
|
|
||||||
config.AddBranch<AnimalSettings>("animal", animal =>
|
|
||||||
{
|
|
||||||
animal.AddCommand<DogCommand>("dog");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// When
|
|
||||||
var result = app.Run(new[]
|
|
||||||
{
|
|
||||||
"animal", "4", "dog", "12", "--",
|
|
||||||
"--foo", "bar", "-bar", "\"baz\"", "qux",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then
|
|
||||||
result.Context.Remaining.Raw.Count.ShouldBe(5);
|
|
||||||
result.Context.Remaining.Raw[0].ShouldBe("--foo");
|
|
||||||
result.Context.Remaining.Raw[1].ShouldBe("bar");
|
|
||||||
result.Context.Remaining.Raw[2].ShouldBe("-bar");
|
|
||||||
result.Context.Remaining.Raw[3].ShouldBe("\"baz\"");
|
|
||||||
result.Context.Remaining.Raw[4].ShouldBe("qux");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user