mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-10-31 09:09:25 +08:00 
			
		
		
		
	| @@ -49,7 +49,15 @@ public interface ICommandAppSettings | ||||
|     /// <summary> | ||||
|     /// Gets or sets a value indicating whether or not parsing is strict. | ||||
|     /// </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> | ||||
|     /// Gets or sets a value indicating whether or not exceptions should be propagated. | ||||
|   | ||||
| @@ -15,7 +15,7 @@ public interface IConfigurator | ||||
|     /// </summary> | ||||
|     /// <param name="args">The example arguments.</param> | ||||
|     void AddExample(string[] args); | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Adds a command. | ||||
|     /// </summary> | ||||
|   | ||||
| @@ -19,6 +19,18 @@ public interface IConfigurator<in TSettings> | ||||
|     /// <param name="args">The example arguments.</param> | ||||
|     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> | ||||
|     /// Marks the branch as hidden. | ||||
|     /// 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. | ||||
|         var parser = new CommandTreeParser(model, configuration.Settings); | ||||
|         var parsedResult = parser.Parse(args); | ||||
|         _registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult); | ||||
|         var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args); | ||||
|  | ||||
|         // Currently the root? | ||||
|         if (parsedResult.Tree == null) | ||||
|         if (parsedResult?.Tree == null) | ||||
|         { | ||||
|             // Display help. | ||||
|             configuration.Settings.Console.SafeRender(HelpWriter.Write(model, configuration.Settings.ShowOptionDefaultValues)); | ||||
| @@ -75,6 +73,7 @@ internal sealed class CommandExecutor | ||||
|         } | ||||
|  | ||||
|         // Register the arguments with the container. | ||||
|         _registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult); | ||||
|         _registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining); | ||||
|  | ||||
|         // Create the resolver and the context. | ||||
| @@ -86,6 +85,34 @@ internal sealed class CommandExecutor | ||||
|             return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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) | ||||
|     { | ||||
|   | ||||
| @@ -12,7 +12,8 @@ internal sealed class CommandAppSettings : ICommandAppSettings | ||||
|     public bool PropagateExceptions { get; set; } | ||||
|     public bool ValidateExamples { get; set; } | ||||
|     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 => | ||||
|         StrictParsing ? ParsingMode.Strict : ParsingMode.Relaxed; | ||||
|   | ||||
| @@ -23,7 +23,7 @@ internal sealed class Configurator : IUnsafeConfigurator, IConfigurator, IConfig | ||||
|     public void AddExample(string[] args) | ||||
|     { | ||||
|         Examples.Add(args); | ||||
|     } | ||||
|     } | ||||
|  | ||||
|     public ConfiguredCommand SetDefaultCommand<TDefaultCommand>() | ||||
|         where TDefaultCommand : class, ICommand | ||||
| @@ -36,7 +36,7 @@ internal sealed class Configurator : IUnsafeConfigurator, IConfigurator, IConfig | ||||
|     public ICommandConfigurator AddCommand<TCommand>(string name) | ||||
|         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); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -1,92 +1,100 @@ | ||||
| namespace Spectre.Console.Cli; | ||||
|  | ||||
| internal sealed class Configurator<TSettings> : IUnsafeBranchConfigurator, IConfigurator<TSettings> | ||||
|     where TSettings : CommandSettings | ||||
| { | ||||
|     private readonly ConfiguredCommand _command; | ||||
|     private readonly ITypeRegistrar? _registrar; | ||||
|  | ||||
|     public Configurator(ConfiguredCommand command, ITypeRegistrar? registrar) | ||||
|     { | ||||
|         _command = command; | ||||
|         _registrar = registrar; | ||||
|     } | ||||
|  | ||||
|     public void SetDescription(string description) | ||||
|     { | ||||
|         _command.Description = description; | ||||
|     } | ||||
|  | ||||
|     public void AddExample(string[] args) | ||||
|     { | ||||
|         _command.Examples.Add(args); | ||||
|     } | ||||
|  | ||||
|     public void HideBranch() | ||||
|     { | ||||
|         _command.IsHidden = true; | ||||
|     } | ||||
|  | ||||
|     public ICommandConfigurator AddCommand<TCommand>(string name) | ||||
|         where TCommand : class, ICommandLimiter<TSettings> | ||||
|     { | ||||
|         var command = ConfiguredCommand.FromType<TCommand>(name); | ||||
|         var configurator = new CommandConfigurator(command); | ||||
|  | ||||
|         _command.Children.Add(command); | ||||
|         return configurator; | ||||
|     } | ||||
|  | ||||
|     public ICommandConfigurator AddDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, int> func) | ||||
|         where TDerivedSettings : TSettings | ||||
|     { | ||||
|         var command = ConfiguredCommand.FromDelegate<TDerivedSettings>( | ||||
|             name, (context, settings) => func(context, (TDerivedSettings)settings)); | ||||
|  | ||||
|         _command.Children.Add(command); | ||||
|         return new CommandConfigurator(command); | ||||
|     } | ||||
|  | ||||
|     public IBranchConfigurator AddBranch<TDerivedSettings>(string name, Action<IConfigurator<TDerivedSettings>> action) | ||||
|         where TDerivedSettings : TSettings | ||||
|     { | ||||
|         var command = ConfiguredCommand.FromBranch<TDerivedSettings>(name); | ||||
|         action(new Configurator<TDerivedSettings>(command, _registrar)); | ||||
|         var added = _command.Children.AddAndReturn(command); | ||||
|         return new BranchConfigurator(added); | ||||
|     } | ||||
|  | ||||
|     ICommandConfigurator IUnsafeConfigurator.AddCommand(string name, Type command) | ||||
|     { | ||||
|         var method = GetType().GetMethod("AddCommand"); | ||||
|         if (method == null) | ||||
|         { | ||||
|             throw new CommandConfigurationException("Could not find AddCommand by reflection."); | ||||
|         } | ||||
|  | ||||
|         method = method.MakeGenericMethod(command); | ||||
|  | ||||
|         if (!(method.Invoke(this, new object[] { name }) is ICommandConfigurator result)) | ||||
|         { | ||||
|             throw new CommandConfigurationException("Invoking AddCommand returned null."); | ||||
|         } | ||||
|  | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     IBranchConfigurator IUnsafeConfigurator.AddBranch(string name, Type settings, Action<IUnsafeBranchConfigurator> action) | ||||
|     { | ||||
|         var command = ConfiguredCommand.FromBranch(settings, name); | ||||
|  | ||||
|         // Create the configurator. | ||||
|         var configuratorType = typeof(Configurator<>).MakeGenericType(settings); | ||||
|         if (!(Activator.CreateInstance(configuratorType, new object?[] { command, _registrar }) is IUnsafeBranchConfigurator configurator)) | ||||
|         { | ||||
|             throw new CommandConfigurationException("Could not create configurator by reflection."); | ||||
|         } | ||||
|  | ||||
|         action(configurator); | ||||
|         var added = _command.Children.AddAndReturn(command); | ||||
|         return new BranchConfigurator(added); | ||||
|     } | ||||
| namespace Spectre.Console.Cli; | ||||
|  | ||||
| internal sealed class Configurator<TSettings> : IUnsafeBranchConfigurator, IConfigurator<TSettings> | ||||
|     where TSettings : CommandSettings | ||||
| { | ||||
|     private readonly ConfiguredCommand _command; | ||||
|     private readonly ITypeRegistrar? _registrar; | ||||
|  | ||||
|     public Configurator(ConfiguredCommand command, ITypeRegistrar? registrar) | ||||
|     { | ||||
|         _command = command; | ||||
|         _registrar = registrar; | ||||
|     } | ||||
|  | ||||
|     public void SetDescription(string description) | ||||
|     { | ||||
|         _command.Description = description; | ||||
|     } | ||||
|  | ||||
|     public void AddExample(string[] 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() | ||||
|     { | ||||
|         _command.IsHidden = true; | ||||
|     } | ||||
|  | ||||
|     public ICommandConfigurator AddCommand<TCommand>(string name) | ||||
|         where TCommand : class, ICommandLimiter<TSettings> | ||||
|     { | ||||
|         var command = ConfiguredCommand.FromType<TCommand>(name, isDefaultCommand: false); | ||||
|         var configurator = new CommandConfigurator(command); | ||||
|  | ||||
|         _command.Children.Add(command); | ||||
|         return configurator; | ||||
|     } | ||||
|  | ||||
|     public ICommandConfigurator AddDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, int> func) | ||||
|         where TDerivedSettings : TSettings | ||||
|     { | ||||
|         var command = ConfiguredCommand.FromDelegate<TDerivedSettings>( | ||||
|             name, (context, settings) => func(context, (TDerivedSettings)settings)); | ||||
|  | ||||
|         _command.Children.Add(command); | ||||
|         return new CommandConfigurator(command); | ||||
|     } | ||||
|  | ||||
|     public IBranchConfigurator AddBranch<TDerivedSettings>(string name, Action<IConfigurator<TDerivedSettings>> action) | ||||
|         where TDerivedSettings : TSettings | ||||
|     { | ||||
|         var command = ConfiguredCommand.FromBranch<TDerivedSettings>(name); | ||||
|         action(new Configurator<TDerivedSettings>(command, _registrar)); | ||||
|         var added = _command.Children.AddAndReturn(command); | ||||
|         return new BranchConfigurator(added); | ||||
|     } | ||||
|  | ||||
|     ICommandConfigurator IUnsafeConfigurator.AddCommand(string name, Type command) | ||||
|     { | ||||
|         var method = GetType().GetMethod("AddCommand"); | ||||
|         if (method == null) | ||||
|         { | ||||
|             throw new CommandConfigurationException("Could not find AddCommand by reflection."); | ||||
|         } | ||||
|  | ||||
|         method = method.MakeGenericMethod(command); | ||||
|  | ||||
|         if (!(method.Invoke(this, new object[] { name }) is ICommandConfigurator result)) | ||||
|         { | ||||
|             throw new CommandConfigurationException("Invoking AddCommand returned null."); | ||||
|         } | ||||
|  | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     IBranchConfigurator IUnsafeConfigurator.AddBranch(string name, Type settings, Action<IUnsafeBranchConfigurator> action) | ||||
|     { | ||||
|         var command = ConfiguredCommand.FromBranch(settings, name); | ||||
|  | ||||
|         // Create the configurator. | ||||
|         var configuratorType = typeof(Configurator<>).MakeGenericType(settings); | ||||
|         if (!(Activator.CreateInstance(configuratorType, new object?[] { command, _registrar }) is IUnsafeBranchConfigurator configurator)) | ||||
|         { | ||||
|             throw new CommandConfigurationException("Could not create configurator by reflection."); | ||||
|         } | ||||
|  | ||||
|         action(configurator); | ||||
|         var added = _command.Children.AddAndReturn(command); | ||||
|         return new BranchConfigurator(added); | ||||
|     } | ||||
| @@ -27,7 +27,10 @@ internal sealed class ConfiguredCommand | ||||
|         CommandType = commandType; | ||||
|         SettingsType = settingsType; | ||||
|         Delegate = @delegate; | ||||
|         IsDefaultCommand = isDefaultCommand; | ||||
|         IsDefaultCommand = isDefaultCommand; | ||||
|  | ||||
|         // Default commands are always created as hidden. | ||||
|         IsHidden = IsDefaultCommand; | ||||
|  | ||||
|         Children = new List<ConfiguredCommand>(); | ||||
|         Examples = new List<string[]>(); | ||||
|   | ||||
| @@ -6,10 +6,6 @@ internal static class TypeRegistrarExtensions | ||||
|     { | ||||
|         var stack = new Stack<CommandInfo>(); | ||||
|         model.Commands.ForEach(c => stack.Push(c)); | ||||
|         if (model.DefaultCommand != null) | ||||
|         { | ||||
|             stack.Push(model.DefaultCommand); | ||||
|         } | ||||
|  | ||||
|         while (stack.Count > 0) | ||||
|         { | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| namespace Spectre.Console.Cli; | ||||
|  | ||||
| internal sealed class CommandInfo : ICommandContainer | ||||
| { | ||||
| { | ||||
|     public string Name { get; } | ||||
|     public HashSet<string> Aliases { get; } | ||||
|     public string? Description { get; } | ||||
| @@ -10,14 +10,17 @@ internal sealed class CommandInfo : ICommandContainer | ||||
|     public Type SettingsType { get; } | ||||
|     public Func<CommandContext, CommandSettings, int>? Delegate { get; } | ||||
|     public bool IsDefaultCommand { get; } | ||||
|     public bool IsHidden { get; } | ||||
|     public CommandInfo? Parent { get; } | ||||
|     public IList<CommandInfo> Children { get; } | ||||
|     public IList<CommandParameter> Parameters { get; } | ||||
|     public IList<string[]> Examples { get; } | ||||
|  | ||||
|     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) | ||||
|     { | ||||
|   | ||||
| @@ -4,21 +4,20 @@ internal sealed class CommandModel : ICommandContainer | ||||
| { | ||||
|     public string? ApplicationName { get; } | ||||
|     public ParsingMode ParsingMode { get; } | ||||
|     public CommandInfo? DefaultCommand { get; } | ||||
|     public IList<CommandInfo> Commands { get; } | ||||
|     public IList<string[]> Examples { get; } | ||||
|     public bool TrimTrailingPeriod { get; } | ||||
|  | ||||
|     public CommandInfo? DefaultCommand => Commands.FirstOrDefault(c => c.IsDefaultCommand); | ||||
|  | ||||
|     public CommandModel( | ||||
|         CommandAppSettings settings, | ||||
|         CommandInfo? defaultCommand, | ||||
|         IEnumerable<CommandInfo> commands, | ||||
|         IEnumerable<string[]> examples) | ||||
|     { | ||||
|         ApplicationName = settings.ApplicationName; | ||||
|         ParsingMode = settings.ParsingMode; | ||||
|         TrimTrailingPeriod = settings.TrimTrailingPeriod; | ||||
|         DefaultCommand = defaultCommand; | ||||
|         Commands = new List<CommandInfo>(commands ?? Array.Empty<CommandInfo>()); | ||||
|         Examples = new List<string[]>(examples ?? Array.Empty<string[]>()); | ||||
|     } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| namespace Spectre.Console.Cli; | ||||
|  | ||||
|  | ||||
| internal static class CommandModelBuilder | ||||
| { | ||||
|     // Consider removing this in favor for value tuples at some point. | ||||
| @@ -25,18 +25,19 @@ internal static class CommandModelBuilder | ||||
|             result.Add(Build(null, command)); | ||||
|         } | ||||
|  | ||||
|         var defaultCommand = default(CommandInfo); | ||||
|         if (configuration.DefaultCommand != null) | ||||
|         { | ||||
|             // Add the examples from the configuration to the default command. | ||||
|             configuration.DefaultCommand.Examples.AddRange(configuration.Examples); | ||||
|  | ||||
|             // 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. | ||||
|         var model = new CommandModel(configuration.Settings, defaultCommand, result, configuration.Examples); | ||||
|         var model = new CommandModel(configuration.Settings, result, configuration.Examples); | ||||
|         CommandModelValidator.Validate(model, configuration.Settings); | ||||
|  | ||||
|         return model; | ||||
| @@ -54,7 +55,7 @@ internal static class CommandModelBuilder | ||||
|         foreach (var childCommand in command.Children) | ||||
|         { | ||||
|             var child = Build(info, childCommand); | ||||
|             info.Children.Add(child); | ||||
|             info.Children.Add(child); | ||||
|         } | ||||
|  | ||||
|         // Normalize argument positions. | ||||
|   | ||||
| @@ -14,7 +14,7 @@ internal static class CommandModelValidator | ||||
|             throw new ArgumentNullException(nameof(settings)); | ||||
|         } | ||||
|  | ||||
|         if (model.Commands.Count == 0 && model.DefaultCommand == null) | ||||
|         if (model.Commands.Count == 0) | ||||
|         { | ||||
|             throw CommandConfigurationException.NoCommandConfigured(); | ||||
|         } | ||||
| @@ -31,7 +31,6 @@ internal static class CommandModelValidator | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         Validate(model.DefaultCommand); | ||||
|         foreach (var command in model.Commands) | ||||
|         { | ||||
|             Validate(command); | ||||
| @@ -147,7 +146,7 @@ internal static class CommandModelValidator | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 var parser = new CommandTreeParser(model, settings, ParsingMode.Strict); | ||||
|                 var parser = new CommandTreeParser(model, settings.CaseSensitivity, ParsingMode.Strict); | ||||
|                 parser.Parse(example); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|   | ||||
| @@ -8,5 +8,13 @@ internal interface ICommandContainer | ||||
|     /// <summary> | ||||
|     /// Gets all commands in the container. | ||||
|     /// </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,10 +1,13 @@ | ||||
| using static Spectre.Console.Cli.CommandTreeTokenizer; | ||||
|  | ||||
| namespace Spectre.Console.Cli; | ||||
|  | ||||
| internal class CommandTreeParser | ||||
| { | ||||
|     private readonly CommandModel _configuration; | ||||
|     private readonly ParsingMode _parsingMode; | ||||
|     private readonly CommandOptionAttribute _help; | ||||
|     private readonly CommandOptionAttribute _help; | ||||
|     private readonly bool _convertFlagsToRemainingArguments; | ||||
|  | ||||
|     public CaseSensitivity CaseSensitivity { get; } | ||||
|  | ||||
| @@ -14,25 +17,26 @@ internal class CommandTreeParser | ||||
|         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) | ||||
|         { | ||||
|             throw new ArgumentNullException(nameof(settings)); | ||||
|         } | ||||
|  | ||||
|         _configuration = configuration; | ||||
|         _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); | ||||
|         _parsingMode = parsingMode ?? _configuration.ParsingMode; | ||||
|         _help = new CommandOptionAttribute("-h|--help"); | ||||
|  | ||||
|         CaseSensitivity = settings.CaseSensitivity; | ||||
|     } | ||||
|         _help = new CommandOptionAttribute("-h|--help"); | ||||
|         _convertFlagsToRemainingArguments = convertFlagsToRemainingArguments ?? false; | ||||
|  | ||||
|         CaseSensitivity = caseSensitivity; | ||||
|     } | ||||
|  | ||||
|     public CommandTreeParserResult Parse(IEnumerable<string> args) | ||||
|     { | ||||
|         var parserContext = new CommandTreeParserContext(args, _parsingMode); | ||||
|         var tokenizerResult = CommandTreeTokenizer.Tokenize(args); | ||||
|  | ||||
|         return Parse(parserContext, tokenizerResult); | ||||
|     } | ||||
|  | ||||
|     public CommandTreeParserResult Parse(CommandTreeParserContext context, CommandTreeTokenizerResult tokenizerResult) | ||||
|     { | ||||
|         var context = new CommandTreeParserContext(args, _parsingMode); | ||||
|  | ||||
|         var tokenizerResult = CommandTreeTokenizer.Tokenize(context.Arguments); | ||||
|         var tokens = tokenizerResult.Tokens; | ||||
|         var rawRemaining = tokenizerResult.Remaining; | ||||
|  | ||||
| @@ -254,10 +258,8 @@ internal class CommandTreeParser | ||||
|             // Find the option. | ||||
|             var option = node.FindOption(token.Value, isLongOption, CaseSensitivity); | ||||
|             if (option != null) | ||||
|             { | ||||
|                 node.Mapped.Add(new MappedCommandParameter( | ||||
|                     option, ParseOptionValue(context, stream, token, node, option))); | ||||
|  | ||||
|             { | ||||
|                 ParseOptionValue(context, stream, token, node, option); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
| @@ -271,7 +273,7 @@ internal class CommandTreeParser | ||||
|  | ||||
|         if (context.State == State.Remaining) | ||||
|         { | ||||
|             ParseOptionValue(context, stream, token, node, null); | ||||
|             ParseOptionValue(context, stream, token, node); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
| @@ -281,17 +283,19 @@ internal class CommandTreeParser | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             ParseOptionValue(context, stream, token, node, null); | ||||
|             ParseOptionValue(context, stream, token, node); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private string? ParseOptionValue( | ||||
|     private void ParseOptionValue( | ||||
|         CommandTreeParserContext context, | ||||
|         CommandTreeTokenStream stream, | ||||
|         CommandTreeToken token, | ||||
|         CommandTree current, | ||||
|         CommandParameter? parameter) | ||||
|     { | ||||
|         CommandTree current, | ||||
|         CommandParameter? parameter = null) | ||||
|     { | ||||
|         bool addToMappedCommandParameters = parameter != null; | ||||
|  | ||||
|         var value = default(string); | ||||
|  | ||||
|         // Parse the value of the token (if any). | ||||
| @@ -325,7 +329,21 @@ internal class CommandTreeParser | ||||
|                                 else | ||||
|                                 { | ||||
|                                     // 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 | ||||
| @@ -352,13 +370,14 @@ internal class CommandTreeParser | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|             { | ||||
|                 context.AddRemainingArgument(token.Value, parseValue ? valueToken.Value : null); | ||||
|             } | ||||
|         } | ||||
|         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); | ||||
|             } | ||||
| @@ -379,10 +398,12 @@ internal class CommandTreeParser | ||||
|                     { | ||||
|                         if (parameter.IsFlagValue()) | ||||
|                         { | ||||
|                             return null; | ||||
|                             value = null; | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             throw CommandParseException.OptionHasNoValue(context.Arguments, token, option); | ||||
|                         } | ||||
|  | ||||
|                         throw CommandParseException.OptionHasNoValue(context.Arguments, token, option); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
| @@ -394,6 +415,9 @@ internal class CommandTreeParser | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return value; | ||||
|         if (parameter != null && addToMappedCommandParameters) | ||||
|         { | ||||
|             current.Mapped.Add(new MappedCommandParameter(parameter, value)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -26,19 +26,16 @@ internal class CommandTreeParserContext | ||||
|     public void IncreaseArgumentPosition() | ||||
|     { | ||||
|         CurrentArgumentPosition++; | ||||
|     } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public void AddRemainingArgument(string key, string? value) | ||||
|     { | ||||
|         if (State == CommandTreeParser.State.Remaining || ParsingMode == ParsingMode.Relaxed) | ||||
|         { | ||||
|             if (!_remaining.ContainsKey(key)) | ||||
|             { | ||||
|                 _remaining.Add(key, new List<string?>()); | ||||
|             } | ||||
|  | ||||
|             _remaining[key].Add(value); | ||||
|         } | ||||
|     { | ||||
|         if (!_remaining.ContainsKey(key)) | ||||
|         { | ||||
|             _remaining.Add(key, new List<string?>()); | ||||
|         } | ||||
|  | ||||
|         _remaining[key].Add(value); | ||||
|     } | ||||
|  | ||||
|     [SuppressMessage("Style", "IDE0004:Remove Unnecessary Cast", Justification = "Bug in analyzer?")] | ||||
|   | ||||
| @@ -5,7 +5,8 @@ internal sealed class CommandTreeTokenStream : IReadOnlyList<CommandTreeToken> | ||||
|     private readonly List<CommandTreeToken> _tokens; | ||||
|     private int _position; | ||||
|  | ||||
|     public int Count => _tokens.Count; | ||||
|     public int Count => _tokens.Count; | ||||
|     public int Position => _position; | ||||
|  | ||||
|     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"); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -3,7 +3,167 @@ namespace Spectre.Console.Tests.Unit.Cli; | ||||
| public sealed partial class CommandAppTests | ||||
| { | ||||
|     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] | ||||
|         public void Should_Register_Remaining_Parsed_Arguments_With_Context() | ||||
|         { | ||||
| @@ -94,6 +254,35 @@ public sealed partial class CommandAppTests | ||||
|             result.Context.Remaining.Raw[0].ShouldBe("/c"); | ||||
|             result.Context.Remaining.Raw[1].ShouldBe("\"set && pause\""); | ||||
|             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); | ||||
|         } | ||||
|  | ||||
|         [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] | ||||
|         [Expectation("Hidden_Command_Options")] | ||||
|         public Task Should_Not_Dump_Hidden_Options_On_A_Command() | ||||
|   | ||||
| @@ -4,42 +4,6 @@ public sealed partial class CommandAppTests | ||||
| { | ||||
|     [Fact] | ||||
|     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 | ||||
|         var app = new CommandAppTester(); | ||||
| @@ -52,8 +16,8 @@ public sealed partial class CommandAppTests | ||||
|         // When | ||||
|         var result = app.Run(new[] | ||||
|         { | ||||
|             "dog", "12", "4", "--good-boy", | ||||
|             "--name", "Rufus", "--alive", | ||||
|                 "dog", "12", "4", "--good-boy", | ||||
|                 "--name", "Rufus", "--alive", | ||||
|         }); | ||||
|  | ||||
|         // Then | ||||
| @@ -69,7 +33,7 @@ public sealed partial class CommandAppTests | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public void Should_Pass_Case_3() | ||||
|     public void Should_Pass_Case_2() | ||||
|     { | ||||
|         // Given | ||||
|         var app = new CommandAppTester(); | ||||
| @@ -197,7 +161,7 @@ public sealed partial class CommandAppTests | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public void Should_Pass_Case_7() | ||||
|     public void Should_Pass_Case_3() | ||||
|     { | ||||
|         // Given | ||||
|         var app = new CommandAppTester(); | ||||
| @@ -904,6 +868,86 @@ public sealed partial class CommandAppTests | ||||
|         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] | ||||
|     public void Should_Set_Command_Name_In_Context() | ||||
|     { | ||||
| @@ -1081,67 +1125,4 @@ public sealed partial class CommandAppTests | ||||
|             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"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Frank Ray
					Frank Ray