mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-07-05 03:58:16 +08:00
fixed line-endings
This commit is contained in:

committed by
Patrik Svensson

parent
989c0b9904
commit
44300c871f
@ -1,146 +1,146 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal sealed class CommandExecutor
|
||||
{
|
||||
private readonly ITypeRegistrar _registrar;
|
||||
|
||||
public CommandExecutor(ITypeRegistrar registrar)
|
||||
{
|
||||
_registrar = registrar ?? throw new ArgumentNullException(nameof(registrar));
|
||||
_registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor));
|
||||
}
|
||||
|
||||
public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args)
|
||||
{
|
||||
if (configuration == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configuration));
|
||||
}
|
||||
|
||||
args ??= new List<string>();
|
||||
|
||||
_registrar.RegisterInstance(typeof(IConfiguration), configuration);
|
||||
_registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole());
|
||||
|
||||
// Create the command model.
|
||||
var model = CommandModelBuilder.Build(configuration);
|
||||
_registrar.RegisterInstance(typeof(CommandModel), model);
|
||||
_registrar.RegisterDependencies(model);
|
||||
|
||||
// Asking for version? Kind of a hack, but it's alright.
|
||||
// We should probably make this a bit better in the future.
|
||||
if (args.Contains("-v") || args.Contains("--version"))
|
||||
{
|
||||
var console = configuration.Settings.Console.GetConsole();
|
||||
console.WriteLine(ResolveApplicationVersion(configuration));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Parse and map the model against the arguments.
|
||||
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args);
|
||||
|
||||
// Register the arguments with the container.
|
||||
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
|
||||
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);
|
||||
|
||||
// Create the resolver.
|
||||
using (var resolver = new TypeResolverAdapter(_registrar.Build()))
|
||||
{
|
||||
// Get the registered help provider, falling back to the default provider
|
||||
// if no custom implementations have been registered.
|
||||
var helpProviders = resolver.Resolve(typeof(IEnumerable<IHelpProvider>)) as IEnumerable<IHelpProvider>;
|
||||
var helpProvider = helpProviders?.LastOrDefault() ?? new HelpProvider(configuration.Settings);
|
||||
|
||||
// Currently the root?
|
||||
if (parsedResult?.Tree == null)
|
||||
{
|
||||
// Display help.
|
||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, null));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the command to execute.
|
||||
var leaf = parsedResult.Tree.GetLeafCommand();
|
||||
if (leaf.Command.IsBranch || leaf.ShowHelp)
|
||||
{
|
||||
// Branches can't be executed. Show help.
|
||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
|
||||
return leaf.ShowHelp ? 0 : 1;
|
||||
}
|
||||
|
||||
// Is this the default and is it called without arguments when there are required arguments?
|
||||
if (leaf.Command.IsDefaultCommand && args.Count() == 0 && leaf.Command.Parameters.Any(p => p.Required))
|
||||
{
|
||||
// Display help for default command.
|
||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create the content.
|
||||
var context = new CommandContext(parsedResult.Remaining, leaf.Command.Name, leaf.Command.Data);
|
||||
|
||||
// Execute the command tree.
|
||||
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
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;
|
||||
}
|
||||
#pragma warning restore CS8603 // Possible null reference return.
|
||||
|
||||
private static string ResolveApplicationVersion(IConfiguration configuration)
|
||||
{
|
||||
return
|
||||
configuration.Settings.ApplicationVersion ?? // potential override
|
||||
VersionHelper.GetVersion(Assembly.GetEntryAssembly());
|
||||
}
|
||||
|
||||
private static Task<int> Execute(
|
||||
CommandTree leaf,
|
||||
CommandTree tree,
|
||||
CommandContext context,
|
||||
ITypeResolver resolver,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
// Bind the command tree against the settings.
|
||||
var settings = CommandBinder.Bind(tree, leaf.Command.SettingsType, resolver);
|
||||
configuration.Settings.Interceptor?.Intercept(context, settings);
|
||||
|
||||
// Create and validate the command.
|
||||
var command = leaf.CreateCommand(resolver);
|
||||
var validationResult = command.Validate(context, settings);
|
||||
if (!validationResult.Successful)
|
||||
{
|
||||
throw CommandRuntimeException.ValidationFailed(validationResult);
|
||||
}
|
||||
|
||||
// Execute the command.
|
||||
return command.Execute(context, settings);
|
||||
}
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal sealed class CommandExecutor
|
||||
{
|
||||
private readonly ITypeRegistrar _registrar;
|
||||
|
||||
public CommandExecutor(ITypeRegistrar registrar)
|
||||
{
|
||||
_registrar = registrar ?? throw new ArgumentNullException(nameof(registrar));
|
||||
_registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor));
|
||||
}
|
||||
|
||||
public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args)
|
||||
{
|
||||
if (configuration == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configuration));
|
||||
}
|
||||
|
||||
args ??= new List<string>();
|
||||
|
||||
_registrar.RegisterInstance(typeof(IConfiguration), configuration);
|
||||
_registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole());
|
||||
|
||||
// Create the command model.
|
||||
var model = CommandModelBuilder.Build(configuration);
|
||||
_registrar.RegisterInstance(typeof(CommandModel), model);
|
||||
_registrar.RegisterDependencies(model);
|
||||
|
||||
// Asking for version? Kind of a hack, but it's alright.
|
||||
// We should probably make this a bit better in the future.
|
||||
if (args.Contains("-v") || args.Contains("--version"))
|
||||
{
|
||||
var console = configuration.Settings.Console.GetConsole();
|
||||
console.WriteLine(ResolveApplicationVersion(configuration));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Parse and map the model against the arguments.
|
||||
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args);
|
||||
|
||||
// Register the arguments with the container.
|
||||
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
|
||||
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);
|
||||
|
||||
// Create the resolver.
|
||||
using (var resolver = new TypeResolverAdapter(_registrar.Build()))
|
||||
{
|
||||
// Get the registered help provider, falling back to the default provider
|
||||
// if no custom implementations have been registered.
|
||||
var helpProviders = resolver.Resolve(typeof(IEnumerable<IHelpProvider>)) as IEnumerable<IHelpProvider>;
|
||||
var helpProvider = helpProviders?.LastOrDefault() ?? new HelpProvider(configuration.Settings);
|
||||
|
||||
// Currently the root?
|
||||
if (parsedResult?.Tree == null)
|
||||
{
|
||||
// Display help.
|
||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, null));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the command to execute.
|
||||
var leaf = parsedResult.Tree.GetLeafCommand();
|
||||
if (leaf.Command.IsBranch || leaf.ShowHelp)
|
||||
{
|
||||
// Branches can't be executed. Show help.
|
||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
|
||||
return leaf.ShowHelp ? 0 : 1;
|
||||
}
|
||||
|
||||
// Is this the default and is it called without arguments when there are required arguments?
|
||||
if (leaf.Command.IsDefaultCommand && args.Count() == 0 && leaf.Command.Parameters.Any(p => p.Required))
|
||||
{
|
||||
// Display help for default command.
|
||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create the content.
|
||||
var context = new CommandContext(parsedResult.Remaining, leaf.Command.Name, leaf.Command.Data);
|
||||
|
||||
// Execute the command tree.
|
||||
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
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;
|
||||
}
|
||||
#pragma warning restore CS8603 // Possible null reference return.
|
||||
|
||||
private static string ResolveApplicationVersion(IConfiguration configuration)
|
||||
{
|
||||
return
|
||||
configuration.Settings.ApplicationVersion ?? // potential override
|
||||
VersionHelper.GetVersion(Assembly.GetEntryAssembly());
|
||||
}
|
||||
|
||||
private static Task<int> Execute(
|
||||
CommandTree leaf,
|
||||
CommandTree tree,
|
||||
CommandContext context,
|
||||
ITypeResolver resolver,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
// Bind the command tree against the settings.
|
||||
var settings = CommandBinder.Bind(tree, leaf.Command.SettingsType, resolver);
|
||||
configuration.Settings.Interceptor?.Intercept(context, settings);
|
||||
|
||||
// Create and validate the command.
|
||||
var command = leaf.CreateCommand(resolver);
|
||||
var validationResult = command.Validate(context, settings);
|
||||
if (!validationResult.Successful)
|
||||
{
|
||||
throw CommandRuntimeException.ValidationFailed(validationResult);
|
||||
}
|
||||
|
||||
// Execute the command.
|
||||
return command.Execute(context, settings);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal sealed class BranchConfigurator : IBranchConfigurator
|
||||
{
|
||||
|
@ -5,16 +5,16 @@ internal sealed class CommandAppSettings : ICommandAppSettings
|
||||
public CultureInfo? Culture { get; set; }
|
||||
public string? ApplicationName { get; set; }
|
||||
public string? ApplicationVersion { get; set; }
|
||||
public int MaximumIndirectExamples { get; set; }
|
||||
public bool ShowOptionDefaultValues { get; set; }
|
||||
public int MaximumIndirectExamples { get; set; }
|
||||
public bool ShowOptionDefaultValues { get; set; }
|
||||
public IAnsiConsole? Console { get; set; }
|
||||
public ICommandInterceptor? Interceptor { get; set; }
|
||||
public ITypeRegistrarFrontend Registrar { get; set; }
|
||||
public CaseSensitivity CaseSensitivity { get; set; }
|
||||
public bool PropagateExceptions { get; set; }
|
||||
public bool ValidateExamples { get; set; }
|
||||
public bool TrimTrailingPeriod { get; set; } = true;
|
||||
public bool StrictParsing { get; set; }
|
||||
public bool TrimTrailingPeriod { get; set; } = true;
|
||||
public bool StrictParsing { get; set; }
|
||||
public bool ConvertFlagsToRemainingArguments { get; set; } = false;
|
||||
|
||||
public ParsingMode ParsingMode =>
|
||||
@ -26,7 +26,7 @@ internal sealed class CommandAppSettings : ICommandAppSettings
|
||||
{
|
||||
Registrar = new TypeRegistrar(registrar);
|
||||
CaseSensitivity = CaseSensitivity.All;
|
||||
ShowOptionDefaultValues = true;
|
||||
ShowOptionDefaultValues = true;
|
||||
MaximumIndirectExamples = 5;
|
||||
}
|
||||
|
||||
|
@ -19,19 +19,19 @@ internal sealed class Configurator : IUnsafeConfigurator, IConfigurator, IConfig
|
||||
Settings = new CommandAppSettings(registrar);
|
||||
Examples = new List<string[]>();
|
||||
}
|
||||
|
||||
public void SetHelpProvider(IHelpProvider helpProvider)
|
||||
{
|
||||
// Register the help provider
|
||||
_registrar.RegisterInstance(typeof(IHelpProvider), helpProvider);
|
||||
}
|
||||
|
||||
public void SetHelpProvider<T>()
|
||||
where T : IHelpProvider
|
||||
{
|
||||
// Register the help provider
|
||||
_registrar.Register(typeof(IHelpProvider), typeof(T));
|
||||
}
|
||||
|
||||
public void SetHelpProvider(IHelpProvider helpProvider)
|
||||
{
|
||||
// Register the help provider
|
||||
_registrar.RegisterInstance(typeof(IHelpProvider), helpProvider);
|
||||
}
|
||||
|
||||
public void SetHelpProvider<T>()
|
||||
where T : IHelpProvider
|
||||
{
|
||||
// Register the help provider
|
||||
_registrar.Register(typeof(IHelpProvider), typeof(T));
|
||||
}
|
||||
|
||||
public void AddExample(params string[] args)
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
|
||||
internal sealed class CommandInfo : ICommandContainer, ICommandInfo
|
||||
{
|
||||
public string Name { get; }
|
||||
@ -20,14 +20,14 @@ internal sealed class CommandInfo : ICommandContainer, ICommandInfo
|
||||
|
||||
// only branches can have a default command
|
||||
public CommandInfo? DefaultCommand => IsBranch ? Children.FirstOrDefault(c => c.IsDefaultCommand) : null;
|
||||
public bool IsHidden { get; }
|
||||
|
||||
IReadOnlyList<ICommandInfo> Help.ICommandContainer.Commands => Children.Cast<ICommandInfo>().ToList();
|
||||
ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand;
|
||||
IReadOnlyList<ICommandParameter> ICommandInfo.Parameters => Parameters.Cast<ICommandParameter>().ToList();
|
||||
ICommandInfo? ICommandInfo.Parent => Parent;
|
||||
IReadOnlyList<string[]> Help.ICommandContainer.Examples => (IReadOnlyList<string[]>)Examples;
|
||||
|
||||
public bool IsHidden { get; }
|
||||
|
||||
IReadOnlyList<ICommandInfo> Help.ICommandContainer.Commands => Children.Cast<ICommandInfo>().ToList();
|
||||
ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand;
|
||||
IReadOnlyList<ICommandParameter> ICommandInfo.Parameters => Parameters.Cast<ICommandParameter>().ToList();
|
||||
ICommandInfo? ICommandInfo.Parent => Parent;
|
||||
IReadOnlyList<string[]> Help.ICommandContainer.Examples => (IReadOnlyList<string[]>)Examples;
|
||||
|
||||
public CommandInfo(CommandInfo? parent, ConfiguredCommand prototype)
|
||||
{
|
||||
Parent = parent;
|
||||
@ -54,5 +54,5 @@ internal sealed class CommandInfo : ICommandContainer, ICommandInfo
|
||||
Description = description.Description;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +1,17 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
|
||||
internal sealed class CommandModel : ICommandContainer, ICommandModel
|
||||
{
|
||||
public string? ApplicationName { get; }
|
||||
public ParsingMode ParsingMode { get; }
|
||||
public IList<CommandInfo> Commands { get; }
|
||||
public IList<string[]> Examples { get; }
|
||||
|
||||
public IList<string[]> Examples { get; }
|
||||
|
||||
public CommandInfo? DefaultCommand => Commands.FirstOrDefault(c => c.IsDefaultCommand);
|
||||
|
||||
string ICommandModel.ApplicationName => GetApplicationName(ApplicationName);
|
||||
IReadOnlyList<ICommandInfo> Help.ICommandContainer.Commands => Commands.Cast<ICommandInfo>().ToList();
|
||||
ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand;
|
||||
ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand;
|
||||
IReadOnlyList<string[]> Help.ICommandContainer.Examples => (IReadOnlyList<string[]>)Examples;
|
||||
|
||||
public CommandModel(
|
||||
@ -22,18 +22,18 @@ internal sealed class CommandModel : ICommandContainer, ICommandModel
|
||||
ApplicationName = settings.ApplicationName;
|
||||
ParsingMode = settings.ParsingMode;
|
||||
Commands = new List<CommandInfo>(commands ?? Array.Empty<CommandInfo>());
|
||||
Examples = new List<string[]>(examples ?? Array.Empty<string[]>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the application.
|
||||
/// If the provided <paramref name="applicationName"/> is not null or empty,
|
||||
/// it is returned. Otherwise the name of the current application
|
||||
/// is determined based on the executable file's name.
|
||||
/// </summary>
|
||||
/// <param name="applicationName">The optional name of the application.</param>
|
||||
/// <returns>
|
||||
/// The name of the application, or a default value of "?" if no valid application name can be determined.
|
||||
Examples = new List<string[]>(examples ?? Array.Empty<string[]>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the application.
|
||||
/// If the provided <paramref name="applicationName"/> is not null or empty,
|
||||
/// it is returned. Otherwise the name of the current application
|
||||
/// is determined based on the executable file's name.
|
||||
/// </summary>
|
||||
/// <param name="applicationName">The optional name of the application.</param>
|
||||
/// <returns>
|
||||
/// The name of the application, or a default value of "?" if no valid application name can be determined.
|
||||
/// </returns>
|
||||
private static string GetApplicationName(string? applicationName)
|
||||
{
|
||||
@ -45,7 +45,7 @@ internal sealed class CommandModel : ICommandContainer, ICommandModel
|
||||
|
||||
private static string? GetApplicationFile()
|
||||
{
|
||||
var location = Assembly.GetEntryAssembly()?.Location;
|
||||
var location = Assembly.GetEntryAssembly()?.Location;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(location))
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
|
||||
internal static class CommandModelBuilder
|
||||
{
|
||||
// Consider removing this in favor for value tuples at some point.
|
||||
@ -31,8 +31,8 @@ internal static class CommandModelBuilder
|
||||
configuration.DefaultCommand.Examples.AddRange(configuration.Examples);
|
||||
|
||||
// Build the default command.
|
||||
var defaultCommand = Build(null, configuration.DefaultCommand);
|
||||
|
||||
var defaultCommand = Build(null, configuration.DefaultCommand);
|
||||
|
||||
result.Add(defaultCommand);
|
||||
}
|
||||
|
||||
@ -55,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.
|
||||
|
@ -1,5 +1,5 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
|
||||
internal abstract class CommandParameter : ICommandParameterInfo, ICommandParameter
|
||||
{
|
||||
public Guid Id { get; }
|
||||
@ -17,10 +17,10 @@ internal abstract class CommandParameter : ICommandParameterInfo, ICommandParame
|
||||
public string PropertyName => Property.Name;
|
||||
|
||||
public virtual bool WantRawValue => ParameterType.IsPairDeconstructable()
|
||||
&& (PairDeconstructor != null || Converter == null);
|
||||
|
||||
public bool IsFlag => ParameterKind == ParameterKind.Flag;
|
||||
|
||||
&& (PairDeconstructor != null || Converter == null);
|
||||
|
||||
public bool IsFlag => ParameterKind == ParameterKind.Flag;
|
||||
|
||||
protected CommandParameter(
|
||||
Type parameterType, ParameterKind parameterKind, PropertyInfo property,
|
||||
string? description, TypeConverterAttribute? converter,
|
||||
|
@ -8,13 +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>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns null if a default command has not been set.
|
||||
/// </remarks>
|
||||
CommandInfo? DefaultCommand { get; }
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
using static Spectre.Console.Cli.CommandTreeTokenizer;
|
||||
|
||||
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; }
|
||||
@ -21,20 +21,20 @@ internal class CommandTreeParser
|
||||
{
|
||||
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
_parsingMode = parsingMode ?? _configuration.ParsingMode;
|
||||
_help = new CommandOptionAttribute("-h|--help");
|
||||
_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);
|
||||
}
|
||||
|
||||
{
|
||||
var parserContext = new CommandTreeParserContext(args, _parsingMode);
|
||||
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
|
||||
|
||||
return Parse(parserContext, tokenizerResult);
|
||||
}
|
||||
|
||||
public CommandTreeParserResult Parse(CommandTreeParserContext context, CommandTreeTokenizerResult tokenizerResult)
|
||||
{
|
||||
var tokens = tokenizerResult.Tokens;
|
||||
@ -258,8 +258,8 @@ internal class CommandTreeParser
|
||||
// Find the option.
|
||||
var option = node.FindOption(token.Value, isLongOption, CaseSensitivity);
|
||||
if (option != null)
|
||||
{
|
||||
ParseOptionValue(context, stream, token, node, option);
|
||||
{
|
||||
ParseOptionValue(context, stream, token, node, option);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -291,10 +291,10 @@ internal class CommandTreeParser
|
||||
CommandTreeParserContext context,
|
||||
CommandTreeTokenStream stream,
|
||||
CommandTreeToken token,
|
||||
CommandTree current,
|
||||
CommandTree current,
|
||||
CommandParameter? parameter = null)
|
||||
{
|
||||
bool addToMappedCommandParameters = parameter != null;
|
||||
{
|
||||
bool addToMappedCommandParameters = parameter != null;
|
||||
|
||||
var value = default(string);
|
||||
|
||||
@ -312,49 +312,49 @@ internal class CommandTreeParser
|
||||
{
|
||||
// Is this a command?
|
||||
if (current.Command.FindCommand(valueToken.Value, CaseSensitivity) == null)
|
||||
{
|
||||
if (parameter != null)
|
||||
{
|
||||
if (parameter.ParameterKind == ParameterKind.Flag)
|
||||
{
|
||||
if (!CliConstants.AcceptedBooleanValues.Contains(valueToken.Value, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!valueToken.HadSeparator)
|
||||
{
|
||||
// Do nothing
|
||||
// - assume valueToken is unrelated to the flag parameter (ie. we've parsed it unnecessarily)
|
||||
// - rely on the "No value?" code below to set the flag to its default value
|
||||
// - valueToken will be handled on the next pass of the parser
|
||||
}
|
||||
else
|
||||
{
|
||||
// Flags cannot be assigned a value.
|
||||
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
|
||||
{
|
||||
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
||||
}
|
||||
{
|
||||
if (parameter != null)
|
||||
{
|
||||
if (parameter.ParameterKind == ParameterKind.Flag)
|
||||
{
|
||||
if (!CliConstants.AcceptedBooleanValues.Contains(valueToken.Value, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!valueToken.HadSeparator)
|
||||
{
|
||||
// Do nothing
|
||||
// - assume valueToken is unrelated to the flag parameter (ie. we've parsed it unnecessarily)
|
||||
// - rely on the "No value?" code below to set the flag to its default value
|
||||
// - valueToken will be handled on the next pass of the parser
|
||||
}
|
||||
else
|
||||
{
|
||||
// Flags cannot be assigned a value.
|
||||
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
|
||||
{
|
||||
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -370,13 +370,13 @@ internal class CommandTreeParser
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
context.AddRemainingArgument(token.Value, parseValue ? valueToken.Value : null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parameter == null && // Only add tokens which have not been matched to a command parameter
|
||||
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);
|
||||
@ -399,10 +399,10 @@ internal class CommandTreeParser
|
||||
if (parameter.IsFlagValue())
|
||||
{
|
||||
value = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw CommandParseException.OptionHasNoValue(context.Arguments, token, option);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw CommandParseException.OptionHasNoValue(context.Arguments, token, option);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -415,9 +415,9 @@ internal class CommandTreeParser
|
||||
}
|
||||
}
|
||||
|
||||
if (parameter != null && addToMappedCommandParameters)
|
||||
{
|
||||
current.Mapped.Add(new MappedCommandParameter(parameter, value));
|
||||
if (parameter != null && addToMappedCommandParameters)
|
||||
{
|
||||
current.Mapped.Add(new MappedCommandParameter(parameter, value));
|
||||
}
|
||||
}
|
||||
}
|
@ -26,15 +26,15 @@ internal class CommandTreeParserContext
|
||||
public void IncreaseArgumentPosition()
|
||||
{
|
||||
CurrentArgumentPosition++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void AddRemainingArgument(string key, string? value)
|
||||
{
|
||||
if (!_remaining.ContainsKey(key))
|
||||
{
|
||||
_remaining.Add(key, new List<string?>());
|
||||
}
|
||||
|
||||
{
|
||||
if (!_remaining.ContainsKey(key))
|
||||
{
|
||||
_remaining.Add(key, new List<string?>());
|
||||
}
|
||||
|
||||
_remaining[key].Add(value);
|
||||
}
|
||||
|
||||
|
@ -6,11 +6,11 @@ internal sealed class CommandTreeToken
|
||||
public int Position { get; }
|
||||
public string Value { get; }
|
||||
public string Representation { get; }
|
||||
public bool IsGrouped { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a separater was encountered immediately before the <see cref="CommandTreeToken.Value"/>.
|
||||
/// </summary>
|
||||
public bool IsGrouped { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a separater was encountered immediately before the <see cref="CommandTreeToken.Value"/>.
|
||||
/// </summary>
|
||||
public bool HadSeparator { get; set; }
|
||||
|
||||
public enum Kind
|
||||
|
@ -5,7 +5,7 @@ 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];
|
||||
|
@ -29,13 +29,13 @@ internal static class CommandTreeTokenizer
|
||||
var context = new CommandTreeTokenizerContext();
|
||||
|
||||
foreach (var arg in args)
|
||||
{
|
||||
if (string.IsNullOrEmpty(arg))
|
||||
{
|
||||
// Null strings in the args array are still represented as tokens
|
||||
tokens.Add(new CommandTreeToken(CommandTreeToken.Kind.String, position, string.Empty, string.Empty));
|
||||
continue;
|
||||
}
|
||||
{
|
||||
if (string.IsNullOrEmpty(arg))
|
||||
{
|
||||
// Null strings in the args array are still represented as tokens
|
||||
tokens.Add(new CommandTreeToken(CommandTreeToken.Kind.String, position, string.Empty, string.Empty));
|
||||
continue;
|
||||
}
|
||||
|
||||
var start = position;
|
||||
var reader = new TextBuffer(previousReader, arg);
|
||||
@ -55,30 +55,30 @@ internal static class CommandTreeTokenizer
|
||||
}
|
||||
|
||||
private static int ParseToken(CommandTreeTokenizerContext context, TextBuffer reader, int position, int start, List<CommandTreeToken> tokens)
|
||||
{
|
||||
if (!reader.ReachedEnd && reader.Peek() == '-')
|
||||
{
|
||||
// Option
|
||||
tokens.AddRange(ScanOptions(context, reader));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Command or argument
|
||||
while (reader.Peek() != -1)
|
||||
{
|
||||
if (reader.ReachedEnd)
|
||||
{
|
||||
position += reader.Position - start;
|
||||
break;
|
||||
}
|
||||
|
||||
tokens.Add(ScanString(context, reader));
|
||||
|
||||
// Flush remaining tokens
|
||||
context.FlushRemaining();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if (!reader.ReachedEnd && reader.Peek() == '-')
|
||||
{
|
||||
// Option
|
||||
tokens.AddRange(ScanOptions(context, reader));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Command or argument
|
||||
while (reader.Peek() != -1)
|
||||
{
|
||||
if (reader.ReachedEnd)
|
||||
{
|
||||
position += reader.Position - start;
|
||||
break;
|
||||
}
|
||||
|
||||
tokens.Add(ScanString(context, reader));
|
||||
|
||||
// Flush remaining tokens
|
||||
context.FlushRemaining();
|
||||
}
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
@ -102,7 +102,7 @@ internal static class CommandTreeTokenizer
|
||||
builder.Append(current);
|
||||
}
|
||||
|
||||
var value = builder.ToString();
|
||||
var value = builder.ToString();
|
||||
return new CommandTreeToken(CommandTreeToken.Kind.String, position, value, value);
|
||||
}
|
||||
|
||||
@ -149,8 +149,8 @@ internal static class CommandTreeTokenizer
|
||||
var token = new CommandTreeToken(CommandTreeToken.Kind.String, reader.Position, "=", "=");
|
||||
throw CommandParseException.OptionValueWasExpected(reader.Original, token);
|
||||
}
|
||||
|
||||
var tokenValue = ScanString(context, reader);
|
||||
|
||||
var tokenValue = ScanString(context, reader);
|
||||
tokenValue.HadSeparator = true;
|
||||
result.Add(tokenValue);
|
||||
}
|
||||
@ -193,7 +193,7 @@ internal static class CommandTreeTokenizer
|
||||
// be tokenized as strings. This block handles parsing those cases, but we only allow this
|
||||
// when the digit is the first character in the token (i.e. "-a1" is always an error), hence the
|
||||
// result.Count == 0 check above.
|
||||
string value = string.Empty;
|
||||
string value = string.Empty;
|
||||
|
||||
while (!reader.ReachedEnd)
|
||||
{
|
||||
@ -212,12 +212,12 @@ internal static class CommandTreeTokenizer
|
||||
result.Add(new CommandTreeToken(CommandTreeToken.Kind.String, position, value, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
// Create a token representing the short option.
|
||||
var representation = current.ToString(CultureInfo.InvariantCulture);
|
||||
var tokenPosition = position + 1 + result.Count;
|
||||
var token = new CommandTreeToken(CommandTreeToken.Kind.ShortOption, tokenPosition, representation, representation);
|
||||
|
||||
var representation = current.ToString(CultureInfo.InvariantCulture);
|
||||
var tokenPosition = position + 1 + result.Count;
|
||||
var token = new CommandTreeToken(CommandTreeToken.Kind.ShortOption, tokenPosition, representation, representation);
|
||||
|
||||
throw CommandParseException.InvalidShortOptionName(reader.Original, token);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user