Move Spectre.Console.Cli to it's own package

This commit is contained in:
Patrik Svensson
2022-05-14 22:56:36 +02:00
committed by Patrik Svensson
parent b600832e00
commit 36ca22ffac
262 changed files with 736 additions and 48 deletions

View File

@ -1,50 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// An attribute representing a command argument.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class CommandArgumentAttribute : Attribute
{
/// <summary>
/// Gets the argument position.
/// </summary>
/// <value>The argument position.</value>
public int Position { get; }
/// <summary>
/// Gets the value name of the argument.
/// </summary>
/// <value>The value name of the argument.</value>
public string ValueName { get; }
/// <summary>
/// Gets a value indicating whether the argument is required.
/// </summary>
/// <value>
/// <c>true</c> if the argument is required; otherwise, <c>false</c>.
/// </value>
public bool IsRequired { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CommandArgumentAttribute"/> class.
/// </summary>
/// <param name="position">The argument position.</param>
/// <param name="template">The argument template.</param>
public CommandArgumentAttribute(int position, string template)
{
if (template == null)
{
throw new ArgumentNullException(nameof(template));
}
// Parse the option template.
var result = TemplateParser.ParseArgumentTemplate(template);
// Assign the result.
Position = position;
ValueName = result.Value;
IsRequired = result.Required;
}
}

View File

@ -1,65 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// An attribute representing a command option.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class CommandOptionAttribute : Attribute
{
/// <summary>
/// Gets the long names of the option.
/// </summary>
/// <value>The option's long names.</value>
public IReadOnlyList<string> LongNames { get; }
/// <summary>
/// Gets the short names of the option.
/// </summary>
/// <value>The option's short names.</value>
public IReadOnlyList<string> ShortNames { get; }
/// <summary>
/// Gets the value name of the option.
/// </summary>
/// <value>The option's value name.</value>
public string? ValueName { get; }
/// <summary>
/// Gets a value indicating whether the value is optional.
/// </summary>
public bool ValueIsOptional { get; }
/// <summary>
/// Gets or sets a value indicating whether this option is hidden from the help text.
/// </summary>
public bool IsHidden { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="CommandOptionAttribute"/> class.
/// </summary>
/// <param name="template">The option template.</param>
public CommandOptionAttribute(string template)
{
if (template == null)
{
throw new ArgumentNullException(nameof(template));
}
// Parse the option template.
var result = TemplateParser.ParseOptionTemplate(template);
// Assign the result.
LongNames = result.LongNames;
ShortNames = result.ShortNames;
ValueName = result.Value;
ValueIsOptional = result.ValueIsOptional;
}
internal bool IsMatch(string name)
{
return
ShortNames.Contains(name, StringComparer.Ordinal) ||
LongNames.Contains(name, StringComparer.Ordinal);
}
}

View File

@ -1,28 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Specifies what type to use as a pair deconstructor for
/// the property this attribute is bound to.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class PairDeconstructorAttribute : Attribute
{
/// <summary>
/// Gets the <see cref="string"/> that represents the type of the
/// pair deconstructor class to use for data conversion for the
/// object this attribute is bound to.
/// </summary>
public Type Type { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PairDeconstructorAttribute"/> class.
/// </summary>
/// <param name="type">
/// A System.Type that represents the type of the pair deconstructor
/// class to use for data conversion for the object this attribute is bound to.
/// </param>
public PairDeconstructorAttribute(Type type)
{
Type = type ?? throw new ArgumentNullException(nameof(type));
}
}

View File

@ -1,31 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// A base class attribute used for parameter validation.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public abstract class ParameterValidationAttribute : Attribute
{
/// <summary>
/// Gets the validation error message.
/// </summary>
/// <value>The validation error message.</value>
public string ErrorMessage { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ParameterValidationAttribute"/> class.
/// </summary>
/// <param name="errorMessage">The validation error message.</param>
protected ParameterValidationAttribute(string errorMessage)
{
ErrorMessage = errorMessage;
}
/// <summary>
/// Validates the parameter value.
/// </summary>
/// <param name="context">The parameter context.</param>
/// <returns>The validation result.</returns>
public abstract ValidationResult Validate(CommandParameterContext context);
}

View File

@ -1,17 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// A base class attribute used for parameter completion.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public abstract class ParameterValueProviderAttribute : Attribute
{
/// <summary>
/// Gets a value for the parameter.
/// </summary>
/// <param name="context">The parameter context.</param>
/// <param name="result">The resulting value.</param>
/// <returns><c>true</c> if a value was provided; otherwise, <c>false</c>.</returns>
public abstract bool TryGetValue(CommandParameterContext context, out object? result);
}

View File

@ -1,32 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Base class for an asynchronous command with no settings.
/// </summary>
public abstract class AsyncCommand : ICommand<EmptyCommandSettings>
{
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="context">The command context.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
public abstract Task<int> ExecuteAsync(CommandContext context);
/// <inheritdoc/>
Task<int> ICommand<EmptyCommandSettings>.Execute(CommandContext context, EmptyCommandSettings settings)
{
return ExecuteAsync(context);
}
/// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
{
return ExecuteAsync(context);
}
/// <inheritdoc/>
ValidationResult ICommand.Validate(CommandContext context, CommandSettings settings)
{
return ValidationResult.Success();
}
}

View File

@ -1,47 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Base class for an asynchronous command.
/// </summary>
/// <typeparam name="TSettings">The settings type.</typeparam>
public abstract class AsyncCommand<TSettings> : ICommand<TSettings>
where TSettings : CommandSettings
{
/// <summary>
/// Validates the specified settings and remaining arguments.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>The validation result.</returns>
public virtual ValidationResult Validate(CommandContext context, TSettings settings)
{
return ValidationResult.Success();
}
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
public abstract Task<int> ExecuteAsync(CommandContext context, TSettings settings);
/// <inheritdoc/>
ValidationResult ICommand.Validate(CommandContext context, CommandSettings settings)
{
return Validate(context, (TSettings)settings);
}
/// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
{
Debug.Assert(settings is TSettings, "Command settings is of unexpected type.");
return ExecuteAsync(context, (TSettings)settings);
}
/// <inheritdoc/>
Task<int> ICommand<TSettings>.Execute(CommandContext context, TSettings settings)
{
return ExecuteAsync(context, settings);
}
}

View File

@ -1,28 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents case sensitivity.
/// </summary>
[Flags]
public enum CaseSensitivity
{
/// <summary>
/// Nothing is case sensitive.
/// </summary>
None = 0,
/// <summary>
/// Long options are case sensitive.
/// </summary>
LongOptions = 1,
/// <summary>
/// Commands are case sensitive.
/// </summary>
Commands = 2,
/// <summary>
/// Everything is case sensitive.
/// </summary>
All = LongOptions | Commands,
}

View File

@ -1,33 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Base class for a command without settings.
/// </summary>
/// <seealso cref="AsyncCommand"/>
public abstract class Command : ICommand<EmptyCommandSettings>
{
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="context">The command context.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
public abstract int Execute(CommandContext context);
/// <inheritdoc/>
Task<int> ICommand<EmptyCommandSettings>.Execute(CommandContext context, EmptyCommandSettings settings)
{
return Task.FromResult(Execute(context));
}
/// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
{
return Task.FromResult(Execute(context));
}
/// <inheritdoc/>
ValidationResult ICommand.Validate(CommandContext context, CommandSettings settings)
{
return ValidationResult.Success();
}
}

View File

@ -1,154 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// The entry point for a command line application.
/// </summary>
public sealed class CommandApp : ICommandApp
{
private readonly Configurator _configurator;
private readonly CommandExecutor _executor;
private bool _executed;
/// <summary>
/// Initializes a new instance of the <see cref="CommandApp"/> class.
/// </summary>
/// <param name="registrar">The registrar.</param>
public CommandApp(ITypeRegistrar? registrar = null)
{
registrar ??= new DefaultTypeRegistrar();
_configurator = new Configurator(registrar);
_executor = new CommandExecutor(registrar);
}
/// <summary>
/// Configures the command line application.
/// </summary>
/// <param name="configuration">The configuration.</param>
public void Configure(Action<IConfigurator> configuration)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
configuration(_configurator);
}
/// <summary>
/// Sets the default command.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
public void SetDefaultCommand<TCommand>()
where TCommand : class, ICommand
{
GetConfigurator().SetDefaultCommand<TCommand>();
}
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
public int Run(IEnumerable<string> args)
{
return RunAsync(args).GetAwaiter().GetResult();
}
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
public async Task<int> RunAsync(IEnumerable<string> args)
{
try
{
if (!_executed)
{
// Add built-in (hidden) commands.
_configurator.AddBranch(CliConstants.Commands.Branch, cli =>
{
cli.HideBranch();
cli.AddCommand<VersionCommand>(CliConstants.Commands.Version);
cli.AddCommand<XmlDocCommand>(CliConstants.Commands.XmlDoc);
cli.AddCommand<ExplainCommand>(CliConstants.Commands.Explain);
});
_executed = true;
}
return await _executor
.Execute(_configurator, args)
.ConfigureAwait(false);
}
catch (Exception ex)
{
// Should we always propagate when debugging?
if (Debugger.IsAttached
&& ex is CommandAppException appException
&& appException.AlwaysPropagateWhenDebugging)
{
throw;
}
if (_configurator.Settings.PropagateExceptions)
{
throw;
}
if (_configurator.Settings.ExceptionHandler != null)
{
return _configurator.Settings.ExceptionHandler(ex);
}
// Render the exception.
var pretty = GetRenderableErrorMessage(ex);
if (pretty != null)
{
_configurator.Settings.Console.SafeRender(pretty);
}
return -1;
}
}
internal Configurator GetConfigurator()
{
return _configurator;
}
private static List<IRenderable?>? GetRenderableErrorMessage(Exception ex, bool convert = true)
{
if (ex is CommandAppException renderable && renderable.Pretty != null)
{
return new List<IRenderable?> { renderable.Pretty };
}
if (convert)
{
var converted = new List<IRenderable?>
{
new Composer()
.Text("[red]Error:[/]")
.Space()
.Text(ex.Message.EscapeMarkup())
.LineBreak(),
};
// Got a renderable inner exception?
if (ex.InnerException != null)
{
var innerRenderable = GetRenderableErrorMessage(ex.InnerException, convert: false);
if (innerRenderable != null)
{
converted.AddRange(innerRenderable);
}
}
return converted;
}
return null;
}
}

View File

@ -1,26 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents errors that occur during application execution.
/// </summary>
public abstract class CommandAppException : Exception
{
/// <summary>
/// Gets the pretty formatted exception message.
/// </summary>
public IRenderable? Pretty { get; }
internal virtual bool AlwaysPropagateWhenDebugging => false;
internal CommandAppException(string message, IRenderable? pretty = null)
: base(message)
{
Pretty = pretty;
}
internal CommandAppException(string message, Exception ex, IRenderable? pretty = null)
: base(message, ex)
{
Pretty = pretty;
}
}

View File

@ -1,50 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// The entry point for a command line application with a default command.
/// </summary>
/// <typeparam name="TDefaultCommand">The type of the default command.</typeparam>
public sealed class CommandApp<TDefaultCommand> : ICommandApp
where TDefaultCommand : class, ICommand
{
private readonly CommandApp _app;
/// <summary>
/// Initializes a new instance of the <see cref="CommandApp{TDefaultCommand}"/> class.
/// </summary>
/// <param name="registrar">The registrar.</param>
public CommandApp(ITypeRegistrar? registrar = null)
{
_app = new CommandApp(registrar);
_app.GetConfigurator().SetDefaultCommand<TDefaultCommand>();
}
/// <summary>
/// Configures the command line application.
/// </summary>
/// <param name="configuration">The configuration.</param>
public void Configure(Action<IConfigurator> configuration)
{
_app.Configure(configuration);
}
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
public int Run(IEnumerable<string> args)
{
return _app.Run(args);
}
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
public Task<int> RunAsync(IEnumerable<string> args)
{
return _app.RunAsync(args);
}
}

View File

@ -1,76 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents errors that occur during configuration.
/// </summary>
public class CommandConfigurationException : CommandAppException
{
internal override bool AlwaysPropagateWhenDebugging => true;
internal CommandConfigurationException(string message, IRenderable? pretty = null)
: base(message, pretty)
{
}
internal CommandConfigurationException(string message, Exception ex, IRenderable? pretty = null)
: base(message, ex, pretty)
{
}
internal static CommandConfigurationException NoCommandConfigured()
{
return new CommandConfigurationException("No commands have been configured.");
}
internal static CommandConfigurationException CommandNameConflict(CommandInfo command, string alias)
{
return new CommandConfigurationException($"The alias '{alias}' for '{command.Name}' conflicts with another command.");
}
internal static CommandConfigurationException DuplicateOption(CommandInfo command, string[] options)
{
var keys = string.Join(", ", options.Select(x => x.Length > 1 ? $"--{x}" : $"-{x}"));
if (options.Length > 1)
{
return new CommandConfigurationException($"Options {keys} are duplicated in command '{command.Name}'.");
}
return new CommandConfigurationException($"Option {keys} is duplicated in command '{command.Name}'.");
}
internal static CommandConfigurationException BranchHasNoChildren(CommandInfo command)
{
throw new CommandConfigurationException($"The branch '{command.Name}' does not define any commands.");
}
internal static CommandConfigurationException TooManyVectorArguments(CommandInfo command)
{
throw new CommandConfigurationException($"The command '{command.Name}' specifies more than one vector argument.");
}
internal static CommandConfigurationException VectorArgumentNotSpecifiedLast(CommandInfo command)
{
throw new CommandConfigurationException($"The command '{command.Name}' specifies an argument vector that is not the last argument.");
}
internal static CommandConfigurationException OptionalOptionValueMustBeFlagWithValue(CommandOption option)
{
return new CommandConfigurationException($"The option '{option.GetOptionName()}' has an optional value but does not implement IFlagValue.");
}
internal static CommandConfigurationException OptionBothHasPairDeconstructorAndTypeParameter(CommandOption option)
{
return new CommandConfigurationException($"The option '{option.GetOptionName()}' is both marked as pair deconstructable and convertable.");
}
internal static CommandConfigurationException OptionTypeDoesNotSupportDeconstruction(CommandOption option)
{
return new CommandConfigurationException($"The option '{option.GetOptionName()}' is marked as " +
"pair deconstructable, but the underlying type does not support that.");
}
internal static CommandConfigurationException RequiredArgumentsCannotHaveDefaultValue(CommandArgument option)
{
return new CommandConfigurationException($"The required argument '{option.Value}' cannot have a default value.");
}
}

View File

@ -1,44 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command context.
/// </summary>
public sealed class CommandContext
{
/// <summary>
/// Gets the remaining arguments.
/// </summary>
/// <value>
/// The remaining arguments.
/// </value>
public IRemainingArguments Remaining { get; }
/// <summary>
/// Gets the name of the command.
/// </summary>
/// <value>
/// The name of the command.
/// </value>
public string Name { get; }
/// <summary>
/// Gets the data that was passed to the command during registration (if any).
/// </summary>
/// <value>
/// The command data.
/// </value>
public object? Data { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CommandContext"/> class.
/// </summary>
/// <param name="remaining">The remaining arguments.</param>
/// <param name="name">The command name.</param>
/// <param name="data">The command data.</param>
public CommandContext(IRemainingArguments remaining, string name, object? data)
{
Remaining = remaining ?? throw new System.ArgumentNullException(nameof(remaining));
Name = name ?? throw new System.ArgumentNullException(nameof(name));
Data = data;
}
}

View File

@ -1,48 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Base class for a command.
/// </summary>
/// <typeparam name="TSettings">The settings type.</typeparam>
/// <seealso cref="AsyncCommand{TSettings}"/>
public abstract class Command<TSettings> : ICommand<TSettings>
where TSettings : CommandSettings
{
/// <summary>
/// Validates the specified settings and remaining arguments.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>The validation result.</returns>
public virtual ValidationResult Validate([NotNull] CommandContext context, [NotNull] TSettings settings)
{
return ValidationResult.Success();
}
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
public abstract int Execute([NotNull] CommandContext context, [NotNull] TSettings settings);
/// <inheritdoc/>
ValidationResult ICommand.Validate(CommandContext context, CommandSettings settings)
{
return Validate(context, (TSettings)settings);
}
/// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
{
Debug.Assert(settings is TSettings, "Command settings is of unexpected type.");
return Task.FromResult(Execute(context, (TSettings)settings));
}
/// <inheritdoc/>
Task<int> ICommand<TSettings>.Execute(CommandContext context, TSettings settings)
{
return Task.FromResult(Execute(context, settings));
}
}

View File

@ -1,35 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a context for <see cref="ICommandParameterInfo"/> related operations.
/// </summary>
public sealed class CommandParameterContext
{
/// <summary>
/// Gets the parameter.
/// </summary>
public ICommandParameterInfo Parameter { get; }
/// <summary>
/// Gets the type resolver.
/// </summary>
public ITypeResolver Resolver { get; }
/// <summary>
/// Gets tje parameter value.
/// </summary>
public object? Value { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CommandParameterContext"/> class.
/// </summary>
/// <param name="parameter">The parameter.</param>
/// <param name="resolver">The type resolver.</param>
/// <param name="value">The parameter value.</param>
public CommandParameterContext(ICommandParameterInfo parameter, ITypeResolver resolver, object? value)
{
Parameter = parameter ?? throw new ArgumentNullException(nameof(parameter));
Resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));
Value = value;
}
}

View File

@ -1,121 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents errors that occur during parsing.
/// </summary>
public sealed class CommandParseException : CommandRuntimeException
{
internal CommandParseException(string message, IRenderable? pretty = null)
: base(message, pretty)
{
}
internal static CommandParseException CouldNotCreateSettings(Type settingsType)
{
return new CommandParseException($"Could not create settings of type '{settingsType.FullName}'.");
}
internal static CommandParseException CouldNotCreateCommand(Type? commandType)
{
if (commandType == null)
{
return new CommandParseException($"Could not create command. Command type is unknown.");
}
return new CommandParseException($"Could not create command of type '{commandType.FullName}'.");
}
internal static CommandParseException ExpectedTokenButFoundNull(CommandTreeToken.Kind expected)
{
return new CommandParseException($"Expected to find any token of type '{expected}' but found null instead.");
}
internal static CommandParseException ExpectedTokenButFoundOther(CommandTreeToken.Kind expected, CommandTreeToken.Kind found)
{
return new CommandParseException($"Expected to find token of type '{expected}' but found '{found}' instead.");
}
internal static CommandParseException OptionHasNoName(string input, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(input, token, "Option does not have a name.", "Did you forget the option name?");
}
internal static CommandParseException OptionValueWasExpected(string input, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(input, token, "Expected an option value.", "Did you forget the option value?");
}
internal static CommandParseException OptionHasNoValue(IEnumerable<string> args, CommandTreeToken token, CommandOption option)
{
return CommandLineParseExceptionFactory.Create(args, token, $"Option '{option.GetOptionName()}' is defined but no value has been provided.", "No value provided.");
}
internal static CommandParseException UnexpectedOption(IEnumerable<string> args, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(args, token, $"Unexpected option '{token.Value}'.", "Did you forget the command?");
}
internal static CommandParseException CannotAssignValueToFlag(IEnumerable<string> args, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(args, token, "Flags cannot be assigned a value.", "Can't assign value.");
}
internal static CommandParseException InvalidShortOptionName(string input, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(input, token, "Short option does not have a valid name.", "Not a valid name for a short option.");
}
internal static CommandParseException LongOptionNameIsMissing(TextBuffer reader, int position)
{
var token = new CommandTreeToken(CommandTreeToken.Kind.LongOption, position, string.Empty, "--");
return CommandLineParseExceptionFactory.Create(reader.Original, token, "Invalid long option name.", "Did you forget the option name?");
}
internal static CommandParseException LongOptionNameIsOneCharacter(TextBuffer reader, int position, string name)
{
var token = new CommandTreeToken(CommandTreeToken.Kind.LongOption, position, name, $"--{name}");
var reason = $"Did you mean -{name}?";
return CommandLineParseExceptionFactory.Create(reader.Original, token, "Invalid long option name.", reason);
}
internal static CommandParseException LongOptionNameStartWithDigit(TextBuffer reader, int position, string name)
{
var token = new CommandTreeToken(CommandTreeToken.Kind.LongOption, position, name, $"--{name}");
return CommandLineParseExceptionFactory.Create(reader.Original, token, "Invalid long option name.", "Option names cannot start with a digit.");
}
internal static CommandParseException LongOptionNameContainSymbol(TextBuffer reader, int position, char character)
{
var name = character.ToString(CultureInfo.InvariantCulture);
var token = new CommandTreeToken(CommandTreeToken.Kind.LongOption, position, name, name);
return CommandLineParseExceptionFactory.Create(reader.Original, token, "Invalid long option name.", "Invalid character.");
}
internal static CommandParseException UnterminatedQuote(string input, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(input, token, $"Encountered unterminated quoted string '{token.Value}'.", "Did you forget the closing quotation mark?");
}
internal static CommandParseException UnknownCommand(CommandModel model, CommandTree? node, IEnumerable<string> args, CommandTreeToken token)
{
var suggestion = CommandSuggestor.Suggest(model, node?.Command, token.Value);
var text = suggestion != null ? $"Did you mean '{suggestion.Name}'?" : "No such command.";
return CommandLineParseExceptionFactory.Create(args, token, $"Unknown command '{token.Value}'.", text);
}
internal static CommandParseException CouldNotMatchArgument(IEnumerable<string> args, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(args, token, $"Could not match '{token.Value}' with an argument.", "Could not match to argument.");
}
internal static CommandParseException UnknownOption(IEnumerable<string> args, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(args, token, $"Unknown option '{token.Value}'.", "Unknown option.");
}
internal static CommandParseException ValueIsNotInValidFormat(string value)
{
var text = $"[red]Error:[/] The value '[white]{value}[/]' is not in a correct format";
return new CommandParseException("Could not parse value", new Markup(text));
}
}

View File

@ -1,54 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents errors that occur during runtime.
/// </summary>
public class CommandRuntimeException : CommandAppException
{
internal CommandRuntimeException(string message, IRenderable? pretty = null)
: base(message, pretty)
{
}
internal CommandRuntimeException(string message, Exception ex, IRenderable? pretty = null)
: base(message, ex, pretty)
{
}
internal static CommandRuntimeException CouldNotResolveType(Type type, Exception? ex = null)
{
var message = $"Could not resolve type '{type.FullName}'.";
if (ex != null)
{
// TODO: Show internal stuff here.
return new CommandRuntimeException(message, ex);
}
return new CommandRuntimeException(message);
}
internal static CommandRuntimeException MissingRequiredArgument(CommandTree node, CommandArgument argument)
{
if (node.Command.Name == CliConstants.DefaultCommandName)
{
return new CommandRuntimeException($"Missing required argument '{argument.Value}'.");
}
return new CommandRuntimeException($"Command '{node.Command.Name}' is missing required argument '{argument.Value}'.");
}
internal static CommandRuntimeException NoConverterFound(CommandParameter parameter)
{
return new CommandRuntimeException($"Could not find converter for type '{parameter.ParameterType.FullName}'.");
}
internal static CommandRuntimeException ValidationFailed(ValidationResult result)
{
return new CommandRuntimeException(result.Message ?? "Unknown validation error.");
}
internal static Exception CouldNotGetSettingsType(Type commandType)
{
return new CommandRuntimeException($"Could not get settings type for command of type '{commandType.FullName}'.");
}
}

View File

@ -1,16 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Base class for command settings.
/// </summary>
public abstract class CommandSettings
{
/// <summary>
/// Performs validation of the settings.
/// </summary>
/// <returns>The validation result.</returns>
public virtual ValidationResult Validate()
{
return ValidationResult.Success();
}
}

View File

@ -1,156 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents errors related to parameter templates.
/// </summary>
public sealed class CommandTemplateException : CommandConfigurationException
{
/// <summary>
/// Gets the template that contains the error.
/// </summary>
public string Template { get; }
internal override bool AlwaysPropagateWhenDebugging => true;
internal CommandTemplateException(string message, string template, IRenderable pretty)
: base(message, pretty)
{
Template = template;
}
internal static CommandTemplateException UnexpectedCharacter(string template, int position, char character)
{
return CommandLineTemplateExceptionFactory.Create(
template,
new TemplateToken(TemplateToken.Kind.Unknown, position, $"{character}", $"{character}"),
$"Encountered unexpected character '{character}'.",
"Unexpected character.");
}
internal static CommandTemplateException UnterminatedValueName(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(
template, token,
$"Encountered unterminated value name '{token.Value}'.",
"Unterminated value name.");
}
internal static CommandTemplateException ArgumentCannotContainOptions(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(
template, token,
"Arguments can not contain options.",
"Not permitted.");
}
internal static CommandTemplateException MultipleValuesAreNotSupported(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"Multiple values are not supported.",
"Too many values.");
}
internal static CommandTemplateException ValuesMustHaveName(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"Values without name are not allowed.",
"Missing value name.");
}
internal static CommandTemplateException OptionsMustHaveName(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"Options without name are not allowed.",
"Missing option name.");
}
internal static CommandTemplateException OptionNamesCannotStartWithDigit(string template, TemplateToken token)
{
// Rewrite the token to point to the option name instead of the whole string.
token = new TemplateToken(
token.TokenKind,
token.TokenKind == TemplateToken.Kind.ShortName ? token.Position + 1 : token.Position + 2,
token.Value, token.Value);
return CommandLineTemplateExceptionFactory.Create(template, token,
"Option names cannot start with a digit.",
"Invalid option name.");
}
internal static CommandTemplateException InvalidCharacterInOptionName(string template, TemplateToken token, char character)
{
// Rewrite the token to point to the invalid character instead of the whole value.
var position = (token.TokenKind == TemplateToken.Kind.ShortName
? token.Position + 1
: token.Position + 2) + token.Value.OrdinalIndexOf(character);
token = new TemplateToken(
token.TokenKind, position,
token.Value, character.ToString(CultureInfo.InvariantCulture));
return CommandLineTemplateExceptionFactory.Create(template, token,
$"Encountered invalid character '{character}' in option name.",
"Invalid character.");
}
internal static CommandTemplateException LongOptionMustHaveMoreThanOneCharacter(string template, TemplateToken token)
{
// Rewrite the token to point to the option name instead of the whole option.
token = new TemplateToken(token.TokenKind, token.Position + 2, token.Value, token.Value);
return CommandLineTemplateExceptionFactory.Create(template, token,
"Long option names must consist of more than one character.",
"Invalid option name.");
}
internal static CommandTemplateException MultipleShortOptionNamesNotAllowed(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"Multiple short option names are not supported.",
"Too many short options.");
}
internal static CommandTemplateException ShortOptionMustOnlyBeOneCharacter(string template, TemplateToken token)
{
// Rewrite the token to point to the option name instead of the whole option.
token = new TemplateToken(token.TokenKind, token.Position + 1, token.Value, token.Value);
return CommandLineTemplateExceptionFactory.Create(template, token,
"Short option names can not be longer than one character.",
"Invalid option name.");
}
internal static CommandTemplateException MultipleOptionValuesAreNotSupported(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"Multiple option values are not supported.",
"Too many option values.");
}
internal static CommandTemplateException InvalidCharacterInValueName(string template, TemplateToken token, char character)
{
// Rewrite the token to point to the invalid character instead of the whole value.
token = new TemplateToken(
token.TokenKind,
token.Position + 1 + token.Value.OrdinalIndexOf(character),
token.Value, character.ToString(CultureInfo.InvariantCulture));
return CommandLineTemplateExceptionFactory.Create(template, token,
$"Encountered invalid character '{character}' in value name.",
"Invalid character.");
}
internal static CommandTemplateException MissingLongAndShortName(string template, TemplateToken? token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"No long or short name for option has been specified.",
"Missing option. Was this meant to be an argument?");
}
internal static CommandTemplateException ArgumentsMustHaveValueName(string template)
{
return CommandLineTemplateExceptionFactory.Create(template, null,
"Arguments must have a value name.",
"Missing value name.");
}
}

View File

@ -1,259 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Contains extensions for <see cref="IConfigurator"/>
/// and <see cref="IConfigurator{TSettings}"/>.
/// </summary>
public static class ConfiguratorExtensions
{
/// <summary>
/// Sets the name of the application.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the application.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetApplicationName(this IConfigurator configurator, string name)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.ApplicationName = name;
return configurator;
}
/// <summary>
/// Overrides the auto-detected version of the application.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="version">The version of application.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetApplicationVersion(this IConfigurator configurator, string version)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.ApplicationVersion = version;
return configurator;
}
/// <summary>
/// Configures the console.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="console">The console.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator ConfigureConsole(this IConfigurator configurator, IAnsiConsole console)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.Console = console;
return configurator;
}
/// <summary>
/// Sets the parsing mode to strict.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator UseStrictParsing(this IConfigurator configurator)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.StrictParsing = true;
return configurator;
}
/// <summary>
/// Tells the command line application to propagate all
/// exceptions to the user.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator PropagateExceptions(this IConfigurator configurator)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.PropagateExceptions = true;
return configurator;
}
/// <summary>
/// Configures case sensitivity.
/// </summary>
/// <param name="configurator">The configuration.</param>
/// <param name="sensitivity">The case sensitivity.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator CaseSensitivity(this IConfigurator configurator, CaseSensitivity sensitivity)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.CaseSensitivity = sensitivity;
return configurator;
}
/// <summary>
/// Tells the command line application to validate all
/// examples before running the application.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator ValidateExamples(this IConfigurator configurator)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.ValidateExamples = true;
return configurator;
}
/// <summary>
/// Sets the command interceptor to be used.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="interceptor">A <see cref="ICommandInterceptor"/>.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetInterceptor(this IConfigurator configurator, ICommandInterceptor interceptor)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.Interceptor = interceptor;
return configurator;
}
/// <summary>
/// Adds a command branch.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command branch.</param>
/// <param name="action">The command branch configuration.</param>
public static void AddBranch(
this IConfigurator configurator,
string name,
Action<IConfigurator<CommandSettings>> action)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.AddBranch(name, action);
}
/// <summary>
/// Adds a command branch.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command branch.</param>
/// <param name="action">The command branch configuration.</param>
public static void AddBranch<TSettings>(
this IConfigurator<TSettings> configurator,
string name,
Action<IConfigurator<TSettings>> action)
where TSettings : CommandSettings
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.AddBranch(name, action);
}
/// <summary>
/// Adds a command without settings that executes a delegate.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command.</param>
/// <param name="func">The delegate to execute as part of command execution.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
public static ICommandConfigurator AddDelegate(
this IConfigurator configurator,
string name,
Func<CommandContext, int> func)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
return configurator.AddDelegate<EmptyCommandSettings>(name, (c, _) => func(c));
}
/// <summary>
/// Adds a command without settings that executes a delegate.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command.</param>
/// <param name="func">The delegate to execute as part of command execution.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
public static ICommandConfigurator AddDelegate<TSettings>(
this IConfigurator<TSettings> configurator,
string name,
Func<CommandContext, int> func)
where TSettings : CommandSettings
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
return configurator.AddDelegate<TSettings>(name, (c, _) => func(c));
}
/// <summary>
/// Sets the ExceptionsHandler.
/// <para>Setting <see cref="ICommandAppSettings.ExceptionHandler"/> this way will use the
/// default exit code of -1.</para>
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="exceptionHandler">The Action that handles the exception.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetExceptionHandler(this IConfigurator configurator, Action<Exception> exceptionHandler)
{
return configurator.SetExceptionHandler(ex =>
{
exceptionHandler(ex);
return -1;
});
}
/// <summary>
/// Sets the ExceptionsHandler.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="exceptionHandler">The Action that handles the exception.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetExceptionHandler(this IConfigurator configurator, Func<Exception, int>? exceptionHandler)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.ExceptionHandler = exceptionHandler;
return configurator;
}
}

View File

@ -1,8 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents empty settings.
/// </summary>
public sealed class EmptyCommandSettings : CommandSettings
{
}

View File

@ -1,55 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Implementation of a flag with an optional value.
/// </summary>
/// <typeparam name="T">The flag's element type.</typeparam>
public sealed class FlagValue<T> : IFlagValue
{
/// <summary>
/// Gets or sets a value indicating whether or not the flag was set or not.
/// </summary>
public bool IsSet { get; set; }
#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
/// <summary>
/// Gets or sets the flag's value.
/// </summary>
public T Value { get; set; }
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
/// <inheritdoc/>
Type IFlagValue.Type => typeof(T);
/// <inheritdoc/>
object? IFlagValue.Value
{
get => Value;
set
{
#pragma warning disable CS8601 // Possible null reference assignment.
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
Value = (T)value;
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning restore CS8601 // Possible null reference assignment.
}
}
/// <inheritdoc/>
public override string ToString()
{
var flag = (IFlagValue)this;
if (flag.Value != null)
{
return string.Format(
CultureInfo.InvariantCulture,
"Set={0}, Value={1}",
IsSet,
flag.Value.ToString());
}
return string.Format(
CultureInfo.InvariantCulture,
"Set={0}", IsSet);
}
}

View File

@ -1,23 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command.
/// </summary>
public interface ICommand
{
/// <summary>
/// Validates the specified settings and remaining arguments.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>The validation result.</returns>
ValidationResult Validate(CommandContext context, CommandSettings settings);
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>The validation result.</returns>
Task<int> Execute(CommandContext context, CommandSettings settings);
}

View File

@ -1,27 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command line application.
/// </summary>
public interface ICommandApp
{
/// <summary>
/// Configures the command line application.
/// </summary>
/// <param name="configuration">The configuration.</param>
void Configure(Action<IConfigurator> configuration);
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
int Run(IEnumerable<string> args);
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
Task<int> RunAsync(IEnumerable<string> args);
}

View File

@ -1,61 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command line application settings.
/// </summary>
public interface ICommandAppSettings
{
/// <summary>
/// Gets or sets the application name.
/// </summary>
string? ApplicationName { get; set; }
/// <summary>
/// Gets or sets the application version (use it to override auto-detected value).
/// </summary>
string? ApplicationVersion { get; set; }
/// <summary>
/// Gets or sets the <see cref="IAnsiConsole"/>.
/// </summary>
IAnsiConsole? Console { get; set; }
/// <summary>
/// Gets or sets the <see cref="ICommandInterceptor"/> used
/// to intercept settings before it's being sent to the command.
/// </summary>
ICommandInterceptor? Interceptor { get; set; }
/// <summary>
/// Gets the type registrar.
/// </summary>
ITypeRegistrarFrontend Registrar { get; }
/// <summary>
/// Gets or sets case sensitivity.
/// </summary>
CaseSensitivity CaseSensitivity { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not parsing is strict.
/// </summary>
bool StrictParsing { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not exceptions should be propagated.
/// <para>Setting this to <c>true</c> will disable default Exception handling and
/// any <see cref="ExceptionHandler"/>, if set.</para>
/// </summary>
bool PropagateExceptions { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not examples should be validated.
/// </summary>
bool ValidateExamples { get; set; }
/// <summary>
/// Gets or sets a handler for Exceptions.
/// <para>This handler will not be called, if <see cref="PropagateExceptions"/> is set to <c>true</c>.</para>
/// </summary>
public Func<Exception, int>? ExceptionHandler { get; set; }
}

View File

@ -1,43 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command configurator.
/// </summary>
public interface ICommandConfigurator
{
/// <summary>
/// Adds an example of how to use the command.
/// </summary>
/// <param name="args">The example arguments.</param>
/// <returns>The same <see cref="ICommandConfigurator"/> instance so that multiple calls can be chained.</returns>
ICommandConfigurator WithExample(string[] args);
/// <summary>
/// Adds an alias (an alternative name) to the command being configured.
/// </summary>
/// <param name="name">The alias to add to the command being configured.</param>
/// <returns>The same <see cref="ICommandConfigurator"/> instance so that multiple calls can be chained.</returns>
ICommandConfigurator WithAlias(string name);
/// <summary>
/// Sets the description of the command.
/// </summary>
/// <param name="description">The command description.</param>
/// <returns>The same <see cref="ICommandConfigurator"/> instance so that multiple calls can be chained.</returns>
ICommandConfigurator WithDescription(string description);
/// <summary>
/// Sets data that will be passed to the command via the <see cref="CommandContext"/>.
/// </summary>
/// <param name="data">The data to pass to the command.</param>
/// <returns>The same <see cref="ICommandConfigurator"/> instance so that multiple calls can be chained.</returns>
ICommandConfigurator WithData(object data);
/// <summary>
/// Marks the command as hidden.
/// Hidden commands do not show up in help documentation or
/// generated XML models.
/// </summary>
/// <returns>The same <see cref="ICommandConfigurator"/> instance so that multiple calls can be chained.</returns>
ICommandConfigurator IsHidden();
}

View File

@ -1,16 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command settings interceptor that
/// will intercept command settings before it's
/// passed to a command.
/// </summary>
public interface ICommandInterceptor
{
/// <summary>
/// Intercepts command information before it's passed to a command.
/// </summary>
/// <param name="context">The intercepted <see cref="CommandContext"/>.</param>
/// <param name="settings">The intercepted <see cref="CommandSettings"/>.</param>
void Intercept(CommandContext context, CommandSettings settings);
}

View File

@ -1,11 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command limiter.
/// </summary>
/// <typeparam name="TSettings">The type of the settings to limit to.</typeparam>
/// <seealso cref="ICommand" />
public interface ICommandLimiter<out TSettings> : ICommand
where TSettings : CommandSettings
{
}

View File

@ -1,17 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command.
/// </summary>
/// <typeparam name="TSettings">The settings type.</typeparam>
public interface ICommand<TSettings> : ICommandLimiter<TSettings>
where TSettings : CommandSettings
{
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
Task<int> Execute(CommandContext context, TSettings settings);
}

View File

@ -1,24 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command parameter.
/// </summary>
public interface ICommandParameterInfo
{
/// <summary>
/// Gets the property name.
/// </summary>
/// <value>The property name.</value>
public string PropertyName { get; }
/// <summary>
/// Gets the parameter type.
/// </summary>
public Type ParameterType { get; }
/// <summary>
/// Gets the description.
/// </summary>
/// <value>The description.</value>
public string? Description { get; }
}

View File

@ -1,46 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a configurator.
/// </summary>
public interface IConfigurator
{
/// <summary>
/// Gets the command app settings.
/// </summary>
public ICommandAppSettings Settings { get; }
/// <summary>
/// Adds an example of how to use the application.
/// </summary>
/// <param name="args">The example arguments.</param>
void AddExample(string[] args);
/// <summary>
/// Adds a command.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
/// <param name="name">The name of the command.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
ICommandConfigurator AddCommand<TCommand>(string name)
where TCommand : class, ICommand;
/// <summary>
/// Adds a command that executes a delegate.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
/// <param name="name">The name of the command.</param>
/// <param name="func">The delegate to execute as part of command execution.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
ICommandConfigurator AddDelegate<TSettings>(string name, Func<CommandContext, TSettings, int> func)
where TSettings : CommandSettings;
/// <summary>
/// Adds a command branch.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
/// <param name="name">The name of the command branch.</param>
/// <param name="action">The command branch configurator.</param>
void AddBranch<TSettings>(string name, Action<IConfigurator<TSettings>> action)
where TSettings : CommandSettings;
}

View File

@ -1,56 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a configurator for specific settings.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
public interface IConfigurator<in TSettings>
where TSettings : CommandSettings
{
/// <summary>
/// Sets the description of the branch.
/// </summary>
/// <param name="description">The description of the branch.</param>
void SetDescription(string description);
/// <summary>
/// Adds an example of how to use the branch.
/// </summary>
/// <param name="args">The example arguments.</param>
void AddExample(string[] args);
/// <summary>
/// Marks the branch as hidden.
/// Hidden branches do not show up in help documentation or
/// generated XML models.
/// </summary>
void HideBranch();
/// <summary>
/// Adds a command.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
/// <param name="name">The name of the command.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
ICommandConfigurator AddCommand<TCommand>(string name)
where TCommand : class, ICommandLimiter<TSettings>;
/// <summary>
/// Adds a command that executes a delegate.
/// </summary>
/// <typeparam name="TDerivedSettings">The derived command setting type.</typeparam>
/// <param name="name">The name of the command.</param>
/// <param name="func">The delegate to execute as part of command execution.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
ICommandConfigurator AddDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, int> func)
where TDerivedSettings : TSettings;
/// <summary>
/// Adds a command branch.
/// </summary>
/// <typeparam name="TDerivedSettings">The derived command setting type.</typeparam>
/// <param name="name">The name of the command branch.</param>
/// <param name="action">The command branch configuration.</param>
void AddBranch<TDerivedSettings>(string name, Action<IConfigurator<TDerivedSettings>> action)
where TDerivedSettings : TSettings;
}

View File

@ -1,22 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a flag with an optional value.
/// </summary>
public interface IFlagValue
{
/// <summary>
/// Gets or sets a value indicating whether or not the flag was set or not.
/// </summary>
bool IsSet { get; set; }
/// <summary>
/// Gets the flag's element type.
/// </summary>
Type Type { get; }
/// <summary>
/// Gets or sets the flag's value.
/// </summary>
object? Value { get; set; }
}

View File

@ -1,17 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents the remaining arguments.
/// </summary>
public interface IRemainingArguments
{
/// <summary>
/// Gets the parsed remaining arguments.
/// </summary>
ILookup<string, string?> Parsed { get; }
/// <summary>
/// Gets the raw, non-parsed remaining arguments.
/// </summary>
IReadOnlyList<string> Raw { get; }
}

View File

@ -1,35 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a type registrar.
/// </summary>
public interface ITypeRegistrar
{
/// <summary>
/// Registers the specified service.
/// </summary>
/// <param name="service">The service.</param>
/// <param name="implementation">The implementation.</param>
void Register(Type service, Type implementation);
/// <summary>
/// Registers the specified instance.
/// </summary>
/// <param name="service">The service.</param>
/// <param name="implementation">The implementation.</param>
void RegisterInstance(Type service, object implementation);
/// <summary>
/// Registers the specified instance lazily.
/// </summary>
/// <param name="service">The service.</param>
/// <param name="factory">The factory that creates the implementation.</param>
void RegisterLazy(Type service, Func<object> factory);
/// <summary>
/// Builds the type resolver representing the registrations
/// specified in the current instance.
/// </summary>
/// <returns>A type resolver.</returns>
ITypeResolver Build();
}

View File

@ -1,31 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a user friendly frontend for a <see cref="ITypeRegistrar"/>.
/// </summary>
public interface ITypeRegistrarFrontend
{
/// <summary>
/// Registers the type with the type registrar as a singleton.
/// </summary>
/// <typeparam name="TService">The exposed service type.</typeparam>
/// <typeparam name="TImplementation">The implementing type.</typeparam>
void Register<TService, TImplementation>()
where TImplementation : TService;
/// <summary>
/// Registers the specified instance with the type registrar as a singleton.
/// </summary>
/// <typeparam name="TImplementation">The type of the instance.</typeparam>
/// <param name="instance">The instance to register.</param>
void RegisterInstance<TImplementation>(TImplementation instance);
/// <summary>
/// Registers the specified instance with the type registrar as a singleton.
/// </summary>
/// <typeparam name="TService">The exposed service type.</typeparam>
/// <typeparam name="TImplementation"> implementing type.</typeparam>
/// <param name="instance">The instance to register.</param>
void RegisterInstance<TService, TImplementation>(TImplementation instance)
where TImplementation : TService;
}

View File

@ -1,14 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a type resolver.
/// </summary>
public interface ITypeResolver
{
/// <summary>
/// Resolves an instance of the specified type.
/// </summary>
/// <param name="type">The type to resolve.</param>
/// <returns>An instance of the specified type, or <c>null</c> if no registration for the specified type exists.</returns>
object? Resolve(Type? type);
}

View File

@ -1,50 +0,0 @@
namespace Spectre.Console.Cli;
internal static class CommandConstructorBinder
{
public static CommandSettings CreateSettings(CommandValueLookup lookup, ConstructorInfo constructor, ITypeResolver resolver)
{
if (constructor.DeclaringType == null)
{
throw new InvalidOperationException("Cannot create settings since constructor have no declaring type.");
}
var parameters = new List<object?>();
var mapped = new HashSet<Guid>();
foreach (var parameter in constructor.GetParameters())
{
if (lookup.TryGetParameterWithName(parameter.Name, out var result))
{
parameters.Add(result.Value);
mapped.Add(result.Parameter.Id);
}
else
{
var value = resolver.Resolve(parameter.ParameterType);
if (value == null)
{
throw CommandRuntimeException.CouldNotResolveType(parameter.ParameterType);
}
parameters.Add(value);
}
}
// Create the settings.
if (!(Activator.CreateInstance(constructor.DeclaringType, parameters.ToArray()) is CommandSettings settings))
{
throw new InvalidOperationException("Could not create settings");
}
// Try to do property injection for parameters that wasn't injected.
foreach (var (parameter, value) in lookup)
{
if (!mapped.Contains(parameter.Id) && parameter.Property.SetMethod != null)
{
parameter.Property.SetValue(settings, value);
}
}
return settings;
}
}

View File

@ -1,41 +0,0 @@
namespace Spectre.Console.Cli;
internal static class CommandPropertyBinder
{
public static CommandSettings CreateSettings(CommandValueLookup lookup, Type settingsType, ITypeResolver resolver)
{
var settings = CreateSettings(resolver, settingsType);
foreach (var (parameter, value) in lookup)
{
if (value != default)
{
parameter.Property.SetValue(settings, value);
}
}
// Validate the settings.
var validationResult = settings.Validate();
if (!validationResult.Successful)
{
throw CommandRuntimeException.ValidationFailed(validationResult);
}
return settings;
}
private static CommandSettings CreateSettings(ITypeResolver resolver, Type settingsType)
{
if (resolver.Resolve(settingsType) is CommandSettings settings)
{
return settings;
}
if (Activator.CreateInstance(settingsType) is CommandSettings instance)
{
return instance;
}
throw CommandParseException.CouldNotCreateSettings(settingsType);
}
}

View File

@ -1,115 +0,0 @@
namespace Spectre.Console.Cli;
internal sealed class CommandValueBinder
{
private readonly CommandValueLookup _lookup;
public CommandValueBinder(CommandValueLookup lookup)
{
_lookup = lookup;
}
public void Bind(CommandParameter parameter, ITypeResolver resolver, object? value)
{
if (parameter.ParameterKind == ParameterKind.Pair)
{
value = GetLookup(parameter, resolver, value);
}
else if (parameter.ParameterKind == ParameterKind.Vector)
{
value = GetArray(parameter, value);
}
else if (parameter.ParameterKind == ParameterKind.FlagWithValue)
{
value = GetFlag(parameter, value);
}
_lookup.SetValue(parameter, value);
}
private object GetLookup(CommandParameter parameter, ITypeResolver resolver, object? value)
{
var genericTypes = parameter.Property.PropertyType.GetGenericArguments();
var multimap = (IMultiMap?)_lookup.GetValue(parameter);
if (multimap == null)
{
multimap = Activator.CreateInstance(typeof(MultiMap<,>).MakeGenericType(genericTypes[0], genericTypes[1])) as IMultiMap;
if (multimap == null)
{
throw new InvalidOperationException("Could not create multimap");
}
}
// Create deconstructor.
var deconstructorType = parameter.PairDeconstructor?.Type ?? typeof(DefaultPairDeconstructor);
if (!(resolver.Resolve(deconstructorType) is IPairDeconstructor deconstructor))
{
if (!(Activator.CreateInstance(deconstructorType) is IPairDeconstructor activatedDeconstructor))
{
throw new InvalidOperationException($"Could not create pair deconstructor.");
}
deconstructor = activatedDeconstructor;
}
// Deconstruct and add to multimap.
var pair = deconstructor.Deconstruct(resolver, genericTypes[0], genericTypes[1], value as string);
if (pair.Key != null)
{
multimap.Add(pair);
}
return multimap;
}
private object GetArray(CommandParameter parameter, object? value)
{
// Add a new item to the array
var array = (Array?)_lookup.GetValue(parameter);
Array newArray;
var elementType = parameter.Property.PropertyType.GetElementType();
if (elementType == null)
{
throw new InvalidOperationException("Could not get property type.");
}
if (array == null)
{
newArray = Array.CreateInstance(elementType, 1);
}
else
{
newArray = Array.CreateInstance(elementType, array.Length + 1);
array.CopyTo(newArray, 0);
}
newArray.SetValue(value, newArray.Length - 1);
return newArray;
}
private object GetFlag(CommandParameter parameter, object? value)
{
var flagValue = (IFlagValue?)_lookup.GetValue(parameter);
if (flagValue == null)
{
flagValue = (IFlagValue?)Activator.CreateInstance(parameter.ParameterType);
if (flagValue == null)
{
throw new InvalidOperationException("Could not create flag value.");
}
}
if (value != null)
{
// Null means set, but not with a valid value.
flagValue.Value = value;
}
// If the parameter was mapped, then it's set.
flagValue.IsSet = true;
return flagValue;
}
}

View File

@ -1,57 +0,0 @@
namespace Spectre.Console.Cli;
internal sealed class CommandValueLookup : IEnumerable<(CommandParameter Parameter, object? Value)>
{
private readonly Dictionary<Guid, (CommandParameter Parameter, object? Value)> _lookup;
public CommandValueLookup()
{
_lookup = new Dictionary<Guid, (CommandParameter, object?)>();
}
public IEnumerator<(CommandParameter Parameter, object? Value)> GetEnumerator()
{
foreach (var pair in _lookup)
{
yield return pair.Value;
}
}
public bool HasParameterWithName(string? name)
{
if (name == null)
{
return false;
}
return _lookup.Values.Any(pair => pair.Parameter.PropertyName.Equals(name, StringComparison.OrdinalIgnoreCase));
}
public bool TryGetParameterWithName(string? name, out (CommandParameter Parameter, object? Value) result)
{
if (HasParameterWithName(name))
{
result = _lookup.Values.FirstOrDefault(pair => pair.Parameter.PropertyName.Equals(name, StringComparison.OrdinalIgnoreCase));
return true;
}
result = default;
return false;
}
public object? GetValue(CommandParameter parameter)
{
_lookup.TryGetValue(parameter.Id, out var result);
return result.Value;
}
public void SetValue(CommandParameter parameter, object? value)
{
_lookup[parameter.Id] = (parameter, value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

View File

@ -1,162 +0,0 @@
namespace Spectre.Console.Cli;
internal static class CommandValueResolver
{
public static CommandValueLookup GetParameterValues(CommandTree? tree, ITypeResolver resolver)
{
var lookup = new CommandValueLookup();
var binder = new CommandValueBinder(lookup);
CommandValidator.ValidateRequiredParameters(tree);
while (tree != null)
{
// Process unmapped parameters.
foreach (var parameter in tree.Unmapped)
{
// Got a value provider?
if (parameter.ValueProvider != null)
{
var context = new CommandParameterContext(parameter, resolver, null);
if (parameter.ValueProvider.TryGetValue(context, out var result))
{
result = ConvertValue(resolver, lookup, binder, parameter, result);
lookup.SetValue(parameter, result);
CommandValidator.ValidateParameter(parameter, lookup, resolver);
continue;
}
}
if (parameter.IsFlagValue())
{
// Set the flag value to an empty, not set instance.
var instance = Activator.CreateInstance(parameter.ParameterType);
lookup.SetValue(parameter, instance);
}
else
{
// Is this an option with a default value?
if (parameter.DefaultValue != null)
{
var value = parameter.DefaultValue?.Value;
value = ConvertValue(resolver, lookup, binder, parameter, value);
binder.Bind(parameter, resolver, value);
CommandValidator.ValidateParameter(parameter, lookup, resolver);
}
else if (Nullable.GetUnderlyingType(parameter.ParameterType) != null ||
!parameter.ParameterType.IsValueType)
{
lookup.SetValue(parameter, null);
}
}
}
// Process mapped parameters.
foreach (var mapped in tree.Mapped)
{
if (mapped.Parameter.WantRawValue)
{
// Just try to assign the raw value.
binder.Bind(mapped.Parameter, resolver, mapped.Value);
}
else
{
if (mapped.Parameter.IsFlagValue() && mapped.Value == null)
{
if (mapped.Parameter is CommandOption option && option.DefaultValue != null)
{
// Set the default value.
binder.Bind(mapped.Parameter, resolver, option.DefaultValue?.Value);
}
else
{
// Set the flag but not the value.
binder.Bind(mapped.Parameter, resolver, null);
}
}
else
{
var converter = GetConverter(lookup, binder, resolver, mapped.Parameter);
if (converter == null)
{
throw CommandRuntimeException.NoConverterFound(mapped.Parameter);
}
// Assign the value to the parameter.
binder.Bind(mapped.Parameter, resolver, converter.ConvertFromInvariantString(mapped.Value));
}
}
// Got a value provider?
if (mapped.Parameter.ValueProvider != null)
{
var context = new CommandParameterContext(mapped.Parameter, resolver, mapped.Value);
if (mapped.Parameter.ValueProvider.TryGetValue(context, out var result))
{
lookup.SetValue(mapped.Parameter, result);
}
}
CommandValidator.ValidateParameter(mapped.Parameter, lookup, resolver);
}
tree = tree.Next;
}
return lookup;
}
private static object? ConvertValue(ITypeResolver resolver, CommandValueLookup lookup, CommandValueBinder binder, CommandParameter parameter, object? result)
{
if (result != null && result.GetType() != parameter.ParameterType)
{
var converter = GetConverter(lookup, binder, resolver, parameter);
if (converter != null)
{
result = converter.ConvertFrom(result);
}
}
return result;
}
[SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "It's OK")]
private static TypeConverter? GetConverter(CommandValueLookup lookup, CommandValueBinder binder, ITypeResolver resolver, CommandParameter parameter)
{
if (parameter.Converter == null)
{
if (parameter.ParameterType.IsArray)
{
// Return a converter for each array item (not the whole array)
return TypeDescriptor.GetConverter(parameter.ParameterType.GetElementType());
}
if (parameter.IsFlagValue())
{
// Is the optional value instanciated?
var value = lookup.GetValue(parameter) as IFlagValue;
if (value == null)
{
// Try to assign it with a null value.
// This will create the optional value instance without a value.
binder.Bind(parameter, resolver, null);
value = lookup.GetValue(parameter) as IFlagValue;
if (value == null)
{
throw new InvalidOperationException("Could not intialize optional value.");
}
}
// Return a converter for the flag element type.
return TypeDescriptor.GetConverter(value.Type);
}
return TypeDescriptor.GetConverter(parameter.ParameterType);
}
var type = Type.GetType(parameter.Converter.ConverterTypeName);
return resolver.Resolve(type) as TypeConverter;
}
}

View File

@ -1,13 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Representation of a multi map.
/// </summary>
internal interface IMultiMap
{
/// <summary>
/// Adds a key and a value to the multi map.
/// </summary>
/// <param name="pair">The pair to add.</param>
void Add((object? Key, object? Value) pair);
}

View File

@ -1,168 +0,0 @@
namespace Spectre.Console.Cli;
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
internal sealed class MultiMap<TKey, TValue> : IMultiMap, ILookup<TKey, TValue>, IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>
where TKey : notnull
{
private readonly IDictionary<TKey, MultiMapGrouping> _lookup;
private readonly IDictionary<TKey, TValue> _dictionary;
public int Count => _lookup.Count;
public bool IsReadOnly => false;
public ICollection<TKey> Keys => _lookup.Keys;
public ICollection<TValue> Values => _dictionary.Values;
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => _lookup.Keys;
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => _dictionary.Values;
TValue IReadOnlyDictionary<TKey, TValue>.this[TKey key] => _dictionary[key];
TValue IDictionary<TKey, TValue>.this[TKey key]
{
get
{
return _dictionary[key];
}
set
{
Add(key, value);
}
}
public IEnumerable<TValue> this[TKey key]
{
get
{
if (_lookup.TryGetValue(key, out var group))
{
return group;
}
return Array.Empty<TValue>();
}
}
public MultiMap()
{
_lookup = new Dictionary<TKey, MultiMapGrouping>();
_dictionary = new Dictionary<TKey, TValue>();
}
private sealed class MultiMapGrouping : IGrouping<TKey, TValue>
{
private readonly List<TValue> _items;
public TKey Key { get; }
public MultiMapGrouping(TKey key, List<TValue> items)
{
Key = key;
_items = items;
}
public void Add(TValue value)
{
_items.Add(value);
}
public IEnumerator<TValue> GetEnumerator()
{
return _items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public bool Contains(TKey key)
{
return _lookup.ContainsKey(key);
}
public IEnumerator<IGrouping<TKey, TValue>> GetEnumerator()
{
foreach (var group in _lookup.Values)
{
yield return group;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(TKey key, TValue value)
{
if (!_lookup.ContainsKey(key))
{
_lookup[key] = new MultiMapGrouping(key, new List<TValue>());
}
_lookup[key].Add(value);
_dictionary[key] = value;
}
public bool ContainsKey(TKey key)
{
return Contains(key);
}
public bool Remove(TKey key)
{
return _lookup.Remove(key);
}
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
{
return _dictionary.TryGetValue(key, out value);
}
public void Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}
public void Clear()
{
_lookup.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return Contains(item.Key);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
_dictionary.CopyTo(array, arrayIndex);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return Remove(item.Key);
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return _dictionary.GetEnumerator();
}
public void Add((object? Key, object? Value) pair)
{
if (pair.Key != null)
{
#pragma warning disable CS8604 // Possible null reference argument of value.
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
Add((TKey)pair.Key, (TValue)pair.Value);
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning restore CS8604 // Possible null reference argument of value.
}
}
}

View File

@ -1,28 +0,0 @@
namespace Spectre.Console.Cli;
internal static class CommandBinder
{
public static CommandSettings Bind(CommandTree? tree, Type settingsType, ITypeResolver resolver)
{
var lookup = CommandValueResolver.GetParameterValues(tree, resolver);
// Got a constructor with at least one name corresponding to a settings?
foreach (var constructor in settingsType.GetConstructors())
{
var parameters = constructor.GetParameters();
if (parameters.Length > 0)
{
foreach (var parameter in parameters)
{
if (lookup.HasParameterWithName(parameter?.Name))
{
// Use constructor injection.
return CommandConstructorBinder.CreateSettings(lookup, constructor, resolver);
}
}
}
}
return CommandPropertyBinder.CreateSettings(lookup, settingsType, resolver);
}
}

View File

@ -1,111 +0,0 @@
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));
}
_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);
// No default command?
if (model.DefaultCommand == null)
{
// Got at least one argument?
var firstArgument = args.FirstOrDefault();
if (firstArgument != null)
{
// Asking for version? Kind of a hack, but it's alright.
// We should probably make this a bit better in the future.
if (firstArgument.Equals("--version", StringComparison.OrdinalIgnoreCase) ||
firstArgument.Equals("-v", StringComparison.OrdinalIgnoreCase))
{
var console = configuration.Settings.Console.GetConsole();
console.WriteLine(ResolveApplicationVersion(configuration));
return 0;
}
}
}
// 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);
// Currently the root?
if (parsedResult.Tree == null)
{
// Display help.
configuration.Settings.Console.SafeRender(HelpWriter.Write(model));
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(HelpWriter.WriteCommand(model, leaf.Command));
return leaf.ShowHelp ? 0 : 1;
}
// Register the arguments with the container.
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);
// Create the resolver and the context.
using (var resolver = new TypeResolverAdapter(_registrar.Build()))
{
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);
}
}
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);
}
}

View File

@ -1,7 +0,0 @@
namespace Spectre.Console.Cli;
internal enum CommandPart
{
CommandName,
LongOption,
}

View File

@ -1,73 +0,0 @@
namespace Spectre.Console.Cli;
internal static class CommandSuggestor
{
private const float SmallestDistance = 2f;
public static CommandInfo? Suggest(CommandModel model, CommandInfo? command, string name)
{
var result = (CommandInfo?)null;
var container = command ?? (ICommandContainer)model;
if (command?.IsDefaultCommand ?? false)
{
// Default commands have no children,
// so use the root commands here.
container = model;
}
var score = float.MaxValue;
foreach (var child in container.Commands.Where(x => !x.IsHidden))
{
var temp = Score(child.Name, name);
if (temp < score)
{
score = temp;
result = child;
}
}
if (score <= SmallestDistance)
{
return result;
}
return null;
}
private static float Score(string source, string target)
{
source = source.ToUpperInvariant();
target = target.ToUpperInvariant();
var n = source.Length;
var m = target.Length;
if (n == 0)
{
return m;
}
if (m == 0)
{
return n;
}
var d = new int[n + 1, m + 1];
Enumerable.Range(0, n + 1).ToList().ForEach(i => d[i, 0] = i);
Enumerable.Range(0, m + 1).ToList().ForEach(i => d[0, i] = i);
for (var sourceIndex = 1; sourceIndex <= n; sourceIndex++)
{
for (var targetIndex = 1; targetIndex <= m; targetIndex++)
{
var cost = (target[targetIndex - 1] == source[sourceIndex - 1]) ? 0 : 1;
d[sourceIndex, targetIndex] = Math.Min(
Math.Min(d[sourceIndex - 1, targetIndex] + 1, d[sourceIndex, targetIndex - 1] + 1),
d[sourceIndex - 1, targetIndex - 1] + cost);
}
}
return d[n, m];
}
}

View File

@ -1,45 +0,0 @@
namespace Spectre.Console.Cli;
internal static class CommandValidator
{
public static void ValidateRequiredParameters(CommandTree? tree)
{
var node = tree?.GetRootCommand();
while (node != null)
{
foreach (var parameter in node.Unmapped)
{
if (parameter.Required)
{
switch (parameter)
{
case CommandArgument argument:
throw CommandRuntimeException.MissingRequiredArgument(node, argument);
}
}
}
node = node.Next;
}
}
public static void ValidateParameter(CommandParameter parameter, CommandValueLookup settings, ITypeResolver resolver)
{
var assignedValue = settings.GetValue(parameter);
foreach (var validator in parameter.Validators)
{
var context = new CommandParameterContext(parameter, resolver, assignedValue);
var validationResult = validator.Validate(context);
if (!validationResult.Successful)
{
// If there is an error message specified in the parameter validator attribute,
// then use that one, otherwise use the validation result.
var result = string.IsNullOrWhiteSpace(validator.ErrorMessage)
? validationResult
: ValidationResult.Error(validator.ErrorMessage);
throw CommandRuntimeException.ValidationFailed(result);
}
}
}
}

View File

@ -1,264 +0,0 @@
namespace Spectre.Console.Cli;
[Description("Displays diagnostics about CLI configurations")]
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
internal sealed class ExplainCommand : Command<ExplainCommand.Settings>
{
private readonly CommandModel _commandModel;
private readonly IAnsiConsole _writer;
public ExplainCommand(IConfiguration configuration, CommandModel commandModel)
{
_commandModel = commandModel ?? throw new ArgumentNullException(nameof(commandModel));
_writer = configuration.Settings.Console.GetConsole();
}
public sealed class Settings : CommandSettings
{
public Settings(string[]? commands, bool? detailed, bool includeHidden)
{
Commands = commands;
Detailed = detailed;
IncludeHidden = includeHidden;
}
[CommandArgument(0, "[command]")]
public string[]? Commands { get; }
[Description("Include detailed information about the commands.")]
[CommandOption("-d|--detailed")]
public bool? Detailed { get; }
[Description("Include hidden commands and options.")]
[CommandOption("--hidden")]
public bool IncludeHidden { get; }
}
public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)
{
var tree = new Tree("CLI Configuration");
tree.AddNode(ValueMarkup("Application Name", _commandModel.ApplicationName, "no application name"));
tree.AddNode(ValueMarkup("Parsing Mode", _commandModel.ParsingMode.ToString()));
if (settings.Commands == null || settings.Commands.Length == 0)
{
// If there is a default command we'll want to include it in the list too.
var commands = _commandModel.DefaultCommand != null
? new[] { _commandModel.DefaultCommand }.Concat(_commandModel.Commands)
: _commandModel.Commands;
AddCommands(
tree.AddNode(ParentMarkup("Commands")),
commands,
settings.Detailed ?? false,
settings.IncludeHidden);
}
else
{
var currentCommandTier = _commandModel.Commands;
CommandInfo? currentCommand = null;
foreach (var command in settings.Commands)
{
currentCommand = currentCommandTier
.SingleOrDefault(i =>
i.Name.Equals(command, StringComparison.CurrentCultureIgnoreCase) ||
i.Aliases
.Any(alias => alias.Equals(command, StringComparison.CurrentCultureIgnoreCase)));
if (currentCommand == null)
{
break;
}
currentCommandTier = currentCommand.Children;
}
if (currentCommand == null)
{
throw new Exception($"Command {string.Join(" ", settings.Commands)} not found");
}
AddCommands(
tree.AddNode(ParentMarkup("Commands")),
new[] { currentCommand },
settings.Detailed ?? true,
settings.IncludeHidden);
}
_writer.Write(tree);
return 0;
}
private IRenderable ValueMarkup(string key, string value)
{
return new Markup($"{key}: [blue]{value.EscapeMarkup()}[/]");
}
private IRenderable ValueMarkup(string key, string? value, string noValueText)
{
if (string.IsNullOrWhiteSpace(value))
{
return new Markup($"{key}: [grey]({noValueText.EscapeMarkup()})[/]");
}
var table = new Table().NoBorder().HideHeaders().AddColumns("key", "value");
table.AddRow($"{key}", $"[blue]{value.EscapeMarkup()}[/]");
return table;
}
private string ParentMarkup(string description)
{
return $"[yellow]{description.EscapeMarkup()}[/]";
}
private void AddStringList(TreeNode node, string description, IList<string>? strings)
{
if (strings == null || strings.Count == 0)
{
return;
}
var parentNode = node.AddNode(ParentMarkup(description));
foreach (var s in strings)
{
parentNode.AddNode(s);
}
}
private void AddCommands(TreeNode node, IEnumerable<CommandInfo> commands, bool detailed, bool includeHidden)
{
foreach (var command in commands)
{
if (!includeHidden && command.IsHidden)
{
continue;
}
var commandName = $"[green]{command.Name}[/]";
if (command.IsDefaultCommand)
{
commandName += " (default)";
}
var commandNode = node.AddNode(commandName);
commandNode.AddNode(ValueMarkup("Description", command.Description, "no description"));
if (command.IsHidden)
{
commandNode.AddNode(ValueMarkup("IsHidden", command.IsHidden.ToString()));
}
if (!command.IsBranch)
{
commandNode.AddNode(ValueMarkup("Type", command.CommandType?.ToString(), "no command type"));
commandNode.AddNode(ValueMarkup("Settings Type", command.SettingsType.ToString()));
}
if (command.Parameters.Count > 0)
{
var parametersNode = commandNode.AddNode(ParentMarkup("Parameters"));
foreach (var parameter in command.Parameters)
{
AddParameter(parametersNode, parameter, detailed, includeHidden);
}
}
AddStringList(commandNode, "Aliases", command.Aliases.ToList());
AddStringList(commandNode, "Examples", command.Examples.Select(i => string.Join(" ", i)).ToList());
if (command.Children.Count > 0)
{
var childNode = commandNode.AddNode(ParentMarkup("Child Commands"));
AddCommands(childNode, command.Children, detailed, includeHidden);
}
}
}
private void AddParameter(TreeNode parametersNode, CommandParameter parameter, bool detailed, bool includeHidden)
{
if (!includeHidden && parameter.IsHidden)
{
return;
}
if (!detailed)
{
parametersNode.AddNode(
$"{parameter.PropertyName} [purple]{GetShortOptions(parameter)}[/] [grey]{parameter.Property.PropertyType.ToString().EscapeMarkup()}[/]");
return;
}
var parameterNode = parametersNode.AddNode(
$"{parameter.PropertyName} [grey]{parameter.Property.PropertyType.ToString().EscapeMarkup()}[/]");
parameterNode.AddNode(ValueMarkup("Description", parameter.Description, "no description"));
parameterNode.AddNode(ValueMarkup("Parameter Kind", parameter.ParameterKind.ToString()));
if (parameter is CommandOption commandOptionParameter)
{
if (commandOptionParameter.IsShadowed)
{
parameterNode.AddNode(ValueMarkup("IsShadowed", commandOptionParameter.IsShadowed.ToString()));
}
if (commandOptionParameter.LongNames.Count > 0)
{
parameterNode.AddNode(ValueMarkup(
"Long Names",
string.Join("|", commandOptionParameter.LongNames.Select(i => $"--{i}"))));
parameterNode.AddNode(ValueMarkup(
"Short Names",
string.Join("|", commandOptionParameter.ShortNames.Select(i => $"-{i}"))));
}
}
else if (parameter is CommandArgument commandArgumentParameter)
{
parameterNode.AddNode(ValueMarkup("Position", commandArgumentParameter.Position.ToString()));
parameterNode.AddNode(ValueMarkup("Value", commandArgumentParameter.Value));
}
parameterNode.AddNode(ValueMarkup("Required", parameter.Required.ToString()));
if (parameter.Converter != null)
{
parameterNode.AddNode(ValueMarkup(
"Converter", $"\"{parameter.Converter.ConverterTypeName}\""));
}
if (parameter.DefaultValue != null)
{
parameterNode.AddNode(ValueMarkup(
"Default Value", $"\"{parameter.DefaultValue.Value}\""));
}
if (parameter.PairDeconstructor != null)
{
parameterNode.AddNode(ValueMarkup("Pair Deconstructor", parameter.PairDeconstructor.Type.ToString()));
}
AddStringList(
parameterNode,
"Validators",
parameter.Validators.Select(i => i.GetType().ToString()).ToList());
}
private static string GetShortOptions(CommandParameter parameter)
{
if (parameter is CommandOption commandOptionParameter)
{
return string.Join(
" | ",
commandOptionParameter.LongNames.Select(i => $"--{i}")
.Concat(commandOptionParameter.ShortNames.Select(i => $"-{i}")));
}
if (parameter is CommandArgument commandArgumentParameter)
{
return $"{commandArgumentParameter.Value} position {commandArgumentParameter.Position}";
}
return string.Empty;
}
}

View File

@ -1,30 +0,0 @@
namespace Spectre.Console.Cli;
[Description("Displays the CLI library version")]
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
internal sealed class VersionCommand : Command<VersionCommand.Settings>
{
private readonly IAnsiConsole _writer;
public VersionCommand(IConfiguration configuration)
{
_writer = configuration.Settings.Console.GetConsole();
}
public sealed class Settings : CommandSettings
{
}
public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)
{
_writer.MarkupLine(
"[yellow]Spectre.Cli[/] version [aqua]{0}[/]",
VersionHelper.GetVersion(typeof(VersionCommand)?.Assembly));
_writer.MarkupLine(
"[yellow]Spectre.Console[/] version [aqua]{0}[/]",
VersionHelper.GetVersion(typeof(IAnsiConsole)?.Assembly));
return 0;
}
}

View File

@ -1,175 +0,0 @@
namespace Spectre.Console.Cli;
[Description("Generates an XML representation of the CLI configuration.")]
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings>
{
private readonly CommandModel _model;
private readonly IAnsiConsole _writer;
public XmlDocCommand(IConfiguration configuration, CommandModel model)
{
_model = model ?? throw new ArgumentNullException(nameof(model));
_writer = configuration.Settings.Console.GetConsole();
}
public sealed class Settings : CommandSettings
{
}
public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)
{
_writer.Write(Serialize(_model), Style.Plain);
return 0;
}
public static string Serialize(CommandModel model)
{
var settings = new XmlWriterSettings
{
Indent = true,
IndentChars = " ",
NewLineChars = "\n",
OmitXmlDeclaration = false,
Encoding = Encoding.UTF8,
};
using (var buffer = new StringWriterWithEncoding(Encoding.UTF8))
using (var xmlWriter = XmlWriter.Create(buffer, settings))
{
SerializeModel(model).WriteTo(xmlWriter);
xmlWriter.Flush();
return buffer.GetStringBuilder().ToString();
}
}
private static XmlDocument SerializeModel(CommandModel model)
{
var document = new XmlDocument();
var root = document.CreateElement("Model");
if (model.DefaultCommand != null)
{
root.AppendChild(document.CreateComment("DEFAULT COMMAND"));
root.AppendChild(CreateCommandNode(document, model.DefaultCommand, isDefaultCommand: true));
}
foreach (var command in model.Commands.Where(x => !x.IsHidden))
{
root.AppendChild(document.CreateComment(command.Name.ToUpperInvariant()));
root.AppendChild(CreateCommandNode(document, command));
}
document.AppendChild(root);
return document;
}
private static XmlNode CreateCommandNode(XmlDocument doc, CommandInfo command, bool isDefaultCommand = false)
{
var node = doc.CreateElement("Command");
// Attributes
node.SetNullableAttribute("Name", command.Name);
node.SetBooleanAttribute("IsBranch", command.IsBranch);
if (isDefaultCommand)
{
node.SetBooleanAttribute("IsDefault", true);
}
if (command.CommandType != null)
{
node.SetNullableAttribute("ClrType", command.CommandType?.FullName);
}
node.SetNullableAttribute("Settings", command.SettingsType?.FullName);
// Parameters
if (command.Parameters.Count > 0)
{
var parameterRootNode = doc.CreateElement("Parameters");
foreach (var parameter in CreateParameterNodes(doc, command))
{
parameterRootNode.AppendChild(parameter);
}
node.AppendChild(parameterRootNode);
}
// Commands
foreach (var childCommand in command.Children)
{
node.AppendChild(doc.CreateComment(childCommand.Name.ToUpperInvariant()));
node.AppendChild(CreateCommandNode(doc, childCommand));
}
return node;
}
private static IEnumerable<XmlNode> CreateParameterNodes(XmlDocument document, CommandInfo command)
{
// Arguments
foreach (var argument in command.Parameters.OfType<CommandArgument>().OrderBy(x => x.Position))
{
var node = document.CreateElement("Argument");
node.SetNullableAttribute("Name", argument.Value);
node.SetAttribute("Position", argument.Position.ToString(CultureInfo.InvariantCulture));
node.SetBooleanAttribute("Required", argument.Required);
node.SetEnumAttribute("Kind", argument.ParameterKind);
node.SetNullableAttribute("ClrType", argument.ParameterType?.FullName);
if (!string.IsNullOrWhiteSpace(argument.Description))
{
var descriptionNode = document.CreateElement("Description");
descriptionNode.InnerText = argument.Description;
node.AppendChild(descriptionNode);
}
if (argument.Validators.Count > 0)
{
var validatorRootNode = document.CreateElement("Validators");
foreach (var validator in argument.Validators.OrderBy(x => x.GetType().FullName))
{
var validatorNode = document.CreateElement("Validator");
validatorNode.SetNullableAttribute("ClrType", validator.GetType().FullName);
validatorNode.SetNullableAttribute("Message", validator.ErrorMessage);
validatorRootNode.AppendChild(validatorNode);
}
node.AppendChild(validatorRootNode);
}
yield return node;
}
// Options
foreach (var option in command.Parameters.OfType<CommandOption>()
.Where(x => !x.IsHidden)
.OrderBy(x => string.Join(",", x.LongNames))
.ThenBy(x => string.Join(",", x.ShortNames)))
{
var node = document.CreateElement("Option");
if (option.IsShadowed)
{
node.SetBooleanAttribute("Shadowed", true);
}
node.SetNullableAttribute("Short", option.ShortNames);
node.SetNullableAttribute("Long", option.LongNames);
node.SetNullableAttribute("Value", option.ValueName);
node.SetBooleanAttribute("Required", option.Required);
node.SetEnumAttribute("Kind", option.ParameterKind);
node.SetNullableAttribute("ClrType", option.ParameterType?.FullName);
if (!string.IsNullOrWhiteSpace(option.Description))
{
var descriptionNode = document.CreateElement("Description");
descriptionNode.InnerText = option.Description;
node.AppendChild(descriptionNode);
}
yield return node;
}
}
}

View File

@ -1,100 +0,0 @@
namespace Spectre.Console.Cli;
internal sealed class Composer : IRenderable
{
private readonly StringBuilder _content;
public Composer()
{
_content = new StringBuilder();
}
public Composer Text(string text)
{
_content.Append(text);
return this;
}
public Composer Style(string style, string text)
{
_content.Append('[').Append(style).Append(']');
_content.Append(text.EscapeMarkup());
_content.Append("[/]");
return this;
}
public Composer Style(string style, Action<Composer> action)
{
_content.Append('[').Append(style).Append(']');
action(this);
_content.Append("[/]");
return this;
}
public Composer Space()
{
return Spaces(1);
}
public Composer Spaces(int count)
{
return Repeat(' ', count);
}
public Composer Tab()
{
return Tabs(1);
}
public Composer Tabs(int count)
{
return Spaces(count * 4);
}
public Composer Repeat(char character, int count)
{
_content.Append(new string(character, count));
return this;
}
public Composer LineBreak()
{
return LineBreaks(1);
}
public Composer LineBreaks(int count)
{
for (var i = 0; i < count; i++)
{
_content.Append(Environment.NewLine);
}
return this;
}
public Composer Join(string separator, IEnumerable<string> composers)
{
if (composers != null)
{
Space();
Text(string.Join(separator, composers));
}
return this;
}
public Measurement Measure(RenderContext context, int maxWidth)
{
return ((IRenderable)new Markup(_content.ToString())).Measure(context, maxWidth);
}
public IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{
return ((IRenderable)new Markup(_content.ToString())).Render(context, maxWidth);
}
public override string ToString()
{
return _content.ToString();
}
}

View File

@ -1,128 +0,0 @@
namespace Spectre.Console.Cli;
internal abstract class ComponentActivator
{
public abstract object Activate(DefaultTypeResolver container);
public abstract ComponentActivator CreateCopy();
}
internal class CachingActivator : ComponentActivator
{
private readonly ComponentActivator _activator;
private object? _result;
public CachingActivator(ComponentActivator activator)
{
_activator = activator ?? throw new ArgumentNullException(nameof(activator));
_result = null;
}
public override object Activate(DefaultTypeResolver container)
{
return _result ??= _activator.Activate(container);
}
public override ComponentActivator CreateCopy()
{
return new CachingActivator(_activator.CreateCopy());
}
}
internal sealed class InstanceActivator : ComponentActivator
{
private readonly object _instance;
public InstanceActivator(object instance)
{
_instance = instance;
}
public override object Activate(DefaultTypeResolver container)
{
return _instance;
}
public override ComponentActivator CreateCopy()
{
return new InstanceActivator(_instance);
}
}
internal sealed class ReflectionActivator : ComponentActivator
{
private readonly Type _type;
private readonly ConstructorInfo _constructor;
private readonly List<ParameterInfo> _parameters;
public ReflectionActivator(Type type)
{
_type = type;
_constructor = GetGreediestConstructor(type);
_parameters = new List<ParameterInfo>();
foreach (var parameter in _constructor.GetParameters())
{
_parameters.Add(parameter);
}
}
public override object Activate(DefaultTypeResolver container)
{
var parameters = new object?[_parameters.Count];
for (var i = 0; i < _parameters.Count; i++)
{
var parameter = _parameters[i];
if (parameter.ParameterType == typeof(DefaultTypeResolver))
{
parameters[i] = container;
}
else
{
var resolved = container.Resolve(parameter.ParameterType);
if (resolved == null)
{
if (!parameter.IsOptional)
{
throw new InvalidOperationException($"Could not find registration for '{parameter.ParameterType.FullName}'.");
}
parameters[i] = null;
}
else
{
parameters[i] = resolved;
}
}
}
return _constructor.Invoke(parameters);
}
public override ComponentActivator CreateCopy()
{
return new ReflectionActivator(_type);
}
private static ConstructorInfo GetGreediestConstructor(Type type)
{
ConstructorInfo? current = null;
var count = -1;
foreach (var constructor in type.GetTypeInfo().GetConstructors())
{
var parameters = constructor.GetParameters();
if (parameters.Length > count)
{
count = parameters.Length;
current = constructor;
}
}
if (current == null)
{
throw new InvalidOperationException($"Could not find a constructor for '{type.FullName}'.");
}
return current;
}
}

View File

@ -1,27 +0,0 @@
namespace Spectre.Console.Cli;
internal sealed class ComponentRegistration
{
public Type ImplementationType { get; }
public ComponentActivator Activator { get; }
public IReadOnlyList<Type> RegistrationTypes { get; }
public ComponentRegistration(Type type, ComponentActivator activator, IEnumerable<Type>? registrationTypes = null)
{
var registrations = new List<Type>(registrationTypes ?? Array.Empty<Type>());
if (registrations.Count == 0)
{
// Every registration needs at least one registration type.
registrations.Add(type);
}
ImplementationType = type;
RegistrationTypes = registrations;
Activator = activator ?? throw new ArgumentNullException(nameof(activator));
}
public ComponentRegistration CreateCopy()
{
return new ComponentRegistration(ImplementationType, Activator.CreateCopy(), RegistrationTypes);
}
}

View File

@ -1,55 +0,0 @@
namespace Spectre.Console.Cli;
internal sealed class ComponentRegistry : IDisposable
{
private readonly Dictionary<Type, HashSet<ComponentRegistration>> _registrations;
public ComponentRegistry()
{
_registrations = new Dictionary<Type, HashSet<ComponentRegistration>>();
}
public ComponentRegistry CreateCopy()
{
var registry = new ComponentRegistry();
foreach (var registration in _registrations.SelectMany(p => p.Value))
{
registry.Register(registration.CreateCopy());
}
return registry;
}
public void Dispose()
{
foreach (var registration in _registrations)
{
registration.Value.Clear();
}
_registrations.Clear();
}
public void Register(ComponentRegistration registration)
{
foreach (var type in new HashSet<Type>(registration.RegistrationTypes))
{
if (!_registrations.ContainsKey(type))
{
_registrations.Add(type, new HashSet<ComponentRegistration>());
}
_registrations[type].Add(registration);
}
}
public ICollection<ComponentRegistration> GetRegistrations(Type type)
{
if (_registrations.ContainsKey(type))
{
return _registrations[type];
}
return new List<ComponentRegistration>();
}
}

View File

@ -1,51 +0,0 @@
namespace Spectre.Console.Cli;
internal sealed class DefaultTypeRegistrar : ITypeRegistrar
{
private readonly Queue<Action<ComponentRegistry>> _registry;
public DefaultTypeRegistrar()
{
_registry = new Queue<Action<ComponentRegistry>>();
}
public ITypeResolver Build()
{
var container = new DefaultTypeResolver();
while (_registry.Count > 0)
{
var action = _registry.Dequeue();
action(container.Registry);
}
return container;
}
public void Register(Type service, Type implementation)
{
var registration = new ComponentRegistration(implementation, new ReflectionActivator(implementation), new[] { service });
_registry.Enqueue(registry => registry.Register(registration));
}
public void RegisterInstance(Type service, object implementation)
{
var registration = new ComponentRegistration(service, new CachingActivator(new InstanceActivator(implementation)));
_registry.Enqueue(registry => registry.Register(registration));
}
public void RegisterLazy(Type service, Func<object> factory)
{
if (factory is null)
{
throw new ArgumentNullException(nameof(factory));
}
_registry.Enqueue(registry =>
{
var activator = new CachingActivator(new InstanceActivator(factory()));
var registration = new ComponentRegistration(service, activator);
registry.Register(registration);
});
}
}

View File

@ -1,62 +0,0 @@
namespace Spectre.Console.Cli;
internal sealed class DefaultTypeResolver : IDisposable, ITypeResolver
{
public ComponentRegistry Registry { get; }
public DefaultTypeResolver()
: this(null)
{
}
public DefaultTypeResolver(ComponentRegistry? registry)
{
Registry = registry ?? new ComponentRegistry();
}
public void Dispose()
{
Registry.Dispose();
}
public object? Resolve(Type? type)
{
if (type == null)
{
return null;
}
var isEnumerable = false;
if (type.IsGenericType)
{
if (type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
isEnumerable = true;
type = type.GenericTypeArguments[0];
}
}
var registrations = Registry.GetRegistrations(type);
if (registrations != null)
{
if (isEnumerable)
{
var result = Array.CreateInstance(type, registrations.Count);
for (var index = 0; index < registrations.Count; index++)
{
var registration = registrations.ElementAt(index);
result.SetValue(Resolve(registration), index);
}
return result;
}
}
return Resolve(registrations?.LastOrDefault());
}
public object? Resolve(ComponentRegistration? registration)
{
return registration?.Activator?.Activate(this);
}
}

View File

@ -1,44 +0,0 @@
namespace Spectre.Console.Cli;
internal sealed class CommandAppSettings : ICommandAppSettings
{
public string? ApplicationName { get; set; }
public string? ApplicationVersion { 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 StrictParsing { get; set; }
public ParsingMode ParsingMode =>
StrictParsing ? ParsingMode.Strict : ParsingMode.Relaxed;
public Func<Exception, int>? ExceptionHandler { get; set; }
public CommandAppSettings(ITypeRegistrar registrar)
{
Registrar = new TypeRegistrar(registrar);
CaseSensitivity = CaseSensitivity.All;
}
public bool IsTrue(Func<CommandAppSettings, bool> func, string environmentVariableName)
{
if (func(this))
{
return true;
}
var environmentVariable = Environment.GetEnvironmentVariable(environmentVariableName);
if (!string.IsNullOrWhiteSpace(environmentVariable))
{
if (environmentVariable.Equals("True", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
}

View File

@ -1,41 +0,0 @@
namespace Spectre.Console.Cli;
internal sealed class CommandConfigurator : ICommandConfigurator
{
public ConfiguredCommand Command { get; }
public CommandConfigurator(ConfiguredCommand command)
{
Command = command;
}
public ICommandConfigurator WithExample(string[] args)
{
Command.Examples.Add(args);
return this;
}
public ICommandConfigurator WithAlias(string alias)
{
Command.Aliases.Add(alias);
return this;
}
public ICommandConfigurator WithDescription(string description)
{
Command.Description = description;
return this;
}
public ICommandConfigurator WithData(object data)
{
Command.Data = data;
return this;
}
public ICommandConfigurator IsHidden()
{
Command.IsHidden = true;
return this;
}
}

View File

@ -1,38 +0,0 @@
namespace Spectre.Console.Cli;
internal static class ConfigurationHelper
{
public static Type? GetSettingsType(Type commandType)
{
if (typeof(ICommand).GetTypeInfo().IsAssignableFrom(commandType) &&
GetGenericTypeArguments(commandType, typeof(ICommand<>), out var result))
{
return result[0];
}
return null;
}
private static bool GetGenericTypeArguments(Type? type, Type genericType,
[NotNullWhen(true)] out Type[]? genericTypeArguments)
{
while (type != null)
{
foreach (var @interface in type.GetTypeInfo().GetInterfaces())
{
if (!@interface.GetTypeInfo().IsGenericType || @interface.GetGenericTypeDefinition() != genericType)
{
continue;
}
genericTypeArguments = @interface.GenericTypeArguments;
return true;
}
type = type.GetTypeInfo().BaseType;
}
genericTypeArguments = null;
return false;
}
}

View File

@ -1,90 +0,0 @@
namespace Spectre.Console.Cli;
internal sealed class Configurator : IUnsafeConfigurator, IConfigurator, IConfiguration
{
private readonly ITypeRegistrar _registrar;
public IList<ConfiguredCommand> Commands { get; }
public CommandAppSettings Settings { get; }
public ConfiguredCommand? DefaultCommand { get; private set; }
public IList<string[]> Examples { get; }
ICommandAppSettings IConfigurator.Settings => Settings;
public Configurator(ITypeRegistrar registrar)
{
_registrar = registrar;
Commands = new List<ConfiguredCommand>();
Settings = new CommandAppSettings(registrar);
Examples = new List<string[]>();
}
public void AddExample(string[] args)
{
Examples.Add(args);
}
public void SetDefaultCommand<TDefaultCommand>()
where TDefaultCommand : class, ICommand
{
DefaultCommand = ConfiguredCommand.FromType<TDefaultCommand>(
CliConstants.DefaultCommandName, isDefaultCommand: true);
}
public ICommandConfigurator AddCommand<TCommand>(string name)
where TCommand : class, ICommand
{
var command = Commands.AddAndReturn(ConfiguredCommand.FromType<TCommand>(name, false));
return new CommandConfigurator(command);
}
public ICommandConfigurator AddDelegate<TSettings>(string name, Func<CommandContext, TSettings, int> func)
where TSettings : CommandSettings
{
var command = Commands.AddAndReturn(ConfiguredCommand.FromDelegate<TSettings>(
name, (context, settings) => func(context, (TSettings)settings)));
return new CommandConfigurator(command);
}
public void AddBranch<TSettings>(string name, Action<IConfigurator<TSettings>> action)
where TSettings : CommandSettings
{
var command = ConfiguredCommand.FromBranch<TSettings>(name);
action(new Configurator<TSettings>(command, _registrar));
Commands.Add(command);
}
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;
}
void 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);
Commands.Add(command);
}
}

View File

@ -1,90 +0,0 @@
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 void AddBranch<TDerivedSettings>(string name, Action<IConfigurator<TDerivedSettings>> action)
where TDerivedSettings : TSettings
{
var command = ConfiguredCommand.FromBranch<TDerivedSettings>(name);
action(new Configurator<TDerivedSettings>(command, _registrar));
_command.Children.Add(command);
}
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;
}
void 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);
_command.Children.Add(command);
}
}

View File

@ -1,65 +0,0 @@
namespace Spectre.Console.Cli;
internal sealed class ConfiguredCommand
{
public string Name { get; }
public HashSet<string> Aliases { get; }
public string? Description { get; set; }
public object? Data { get; set; }
public Type? CommandType { get; }
public Type SettingsType { get; }
public Func<CommandContext, CommandSettings, int>? Delegate { get; }
public bool IsDefaultCommand { get; }
public bool IsHidden { get; set; }
public IList<ConfiguredCommand> Children { get; }
public IList<string[]> Examples { get; }
private ConfiguredCommand(
string name,
Type? commandType,
Type settingsType,
Func<CommandContext, CommandSettings, int>? @delegate,
bool isDefaultCommand)
{
Name = name;
Aliases = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
CommandType = commandType;
SettingsType = settingsType;
Delegate = @delegate;
IsDefaultCommand = isDefaultCommand;
Children = new List<ConfiguredCommand>();
Examples = new List<string[]>();
}
public static ConfiguredCommand FromBranch(Type settings, string name)
{
return new ConfiguredCommand(name, null, settings, null, false);
}
public static ConfiguredCommand FromBranch<TSettings>(string name)
where TSettings : CommandSettings
{
return new ConfiguredCommand(name, null, typeof(TSettings), null, false);
}
public static ConfiguredCommand FromType<TCommand>(string name, bool isDefaultCommand = false)
where TCommand : class, ICommand
{
var settingsType = ConfigurationHelper.GetSettingsType(typeof(TCommand));
if (settingsType == null)
{
throw CommandRuntimeException.CouldNotGetSettingsType(typeof(TCommand));
}
return new ConfiguredCommand(name, typeof(TCommand), settingsType, null, isDefaultCommand);
}
public static ConfiguredCommand FromDelegate<TSettings>(
string name, Func<CommandContext, CommandSettings, int>? @delegate = null)
where TSettings : CommandSettings
{
return new ConfiguredCommand(name, null, typeof(TSettings), @delegate, false);
}
}

View File

@ -1,27 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a configuration.
/// </summary>
internal interface IConfiguration
{
/// <summary>
/// Gets the configured commands.
/// </summary>
IList<ConfiguredCommand> Commands { get; }
/// <summary>
/// Gets the settings for the configuration.
/// </summary>
CommandAppSettings Settings { get; }
/// <summary>
/// Gets the default command for the configuration.
/// </summary>
ConfiguredCommand? DefaultCommand { get; }
/// <summary>
/// Gets all examples for the configuration.
/// </summary>
IList<string[]> Examples { get; }
}

View File

@ -1,146 +0,0 @@
namespace Spectre.Console.Cli;
internal static class TemplateParser
{
public sealed class ArgumentResult
{
public string Value { get; set; }
public bool Required { get; set; }
public ArgumentResult(string value, bool required)
{
Value = value;
Required = required;
}
}
public sealed class OptionResult
{
public List<string> LongNames { get; set; }
public List<string> ShortNames { get; set; }
public string? Value { get; set; }
public bool ValueIsOptional { get; set; }
public OptionResult()
{
ShortNames = new List<string>();
LongNames = new List<string>();
}
}
public static ArgumentResult ParseArgumentTemplate(string template)
{
var valueName = default(string);
var required = false;
foreach (var token in TemplateTokenizer.Tokenize(template))
{
if (token.TokenKind == TemplateToken.Kind.ShortName ||
token.TokenKind == TemplateToken.Kind.LongName)
{
throw CommandTemplateException.ArgumentCannotContainOptions(template, token);
}
if (token.TokenKind == TemplateToken.Kind.OptionalValue ||
token.TokenKind == TemplateToken.Kind.RequiredValue)
{
if (!string.IsNullOrWhiteSpace(valueName))
{
throw CommandTemplateException.MultipleValuesAreNotSupported(template, token);
}
if (string.IsNullOrWhiteSpace(token.Value))
{
throw CommandTemplateException.ValuesMustHaveName(template, token);
}
valueName = token.Value;
required = token.TokenKind == TemplateToken.Kind.RequiredValue;
}
}
if (valueName == null)
{
throw CommandTemplateException.ArgumentsMustHaveValueName(template);
}
return new ArgumentResult(valueName, required);
}
public static OptionResult ParseOptionTemplate(string template)
{
var result = new OptionResult();
foreach (var token in TemplateTokenizer.Tokenize(template))
{
if (token.TokenKind == TemplateToken.Kind.LongName || token.TokenKind == TemplateToken.Kind.ShortName)
{
if (string.IsNullOrWhiteSpace(token.Value))
{
throw CommandTemplateException.OptionsMustHaveName(template, token);
}
if (char.IsDigit(token.Value[0]))
{
throw CommandTemplateException.OptionNamesCannotStartWithDigit(template, token);
}
foreach (var character in token.Value)
{
if (!char.IsLetterOrDigit(character) && character != '-' && character != '_')
{
throw CommandTemplateException.InvalidCharacterInOptionName(template, token, character);
}
}
}
if (token.TokenKind == TemplateToken.Kind.LongName)
{
if (token.Value.Length == 1)
{
throw CommandTemplateException.LongOptionMustHaveMoreThanOneCharacter(template, token);
}
result.LongNames.Add(token.Value);
}
if (token.TokenKind == TemplateToken.Kind.ShortName)
{
if (token.Value.Length > 1)
{
throw CommandTemplateException.ShortOptionMustOnlyBeOneCharacter(template, token);
}
result.ShortNames.Add(token.Value);
}
if (token.TokenKind == TemplateToken.Kind.RequiredValue ||
token.TokenKind == TemplateToken.Kind.OptionalValue)
{
if (!string.IsNullOrWhiteSpace(result.Value))
{
throw CommandTemplateException.MultipleOptionValuesAreNotSupported(template, token);
}
foreach (var character in token.Value)
{
if (!char.IsLetterOrDigit(character) &&
character != '=' && character != '-' && character != '_')
{
throw CommandTemplateException.InvalidCharacterInValueName(template, token, character);
}
}
result.Value = token.Value.ToUpperInvariant();
result.ValueIsOptional = token.TokenKind == TemplateToken.Kind.OptionalValue;
}
}
if (result.LongNames.Count == 0 &&
result.ShortNames.Count == 0)
{
throw CommandTemplateException.MissingLongAndShortName(template, null);
}
return result;
}
}

View File

@ -1,26 +0,0 @@
namespace Spectre.Console.Cli;
internal sealed class TemplateToken
{
public Kind TokenKind { get; }
public int Position { get; }
public string Value { get; }
public string Representation { get; }
public TemplateToken(Kind kind, int position, string value, string representation)
{
TokenKind = kind;
Position = position;
Value = value;
Representation = representation;
}
public enum Kind
{
Unknown = 0,
LongName,
ShortName,
RequiredValue,
OptionalValue,
}
}

View File

@ -1,131 +0,0 @@
namespace Spectre.Console.Cli;
internal static class TemplateTokenizer
{
public static IReadOnlyList<TemplateToken> Tokenize(string template)
{
using var buffer = new TextBuffer(template);
var result = new List<TemplateToken>();
while (!buffer.ReachedEnd)
{
EatWhitespace(buffer);
if (!buffer.TryPeek(out var character))
{
break;
}
if (character == '-')
{
result.Add(ReadOption(buffer));
}
else if (character == '|')
{
buffer.Consume('|');
}
else if (character == '<')
{
result.Add(ReadValue(buffer, true));
}
else if (character == '[')
{
result.Add(ReadValue(buffer, false));
}
else
{
throw CommandTemplateException.UnexpectedCharacter(buffer.Original, buffer.Position, character);
}
}
return result;
}
private static void EatWhitespace(TextBuffer buffer)
{
while (!buffer.ReachedEnd)
{
var character = buffer.Peek();
if (!char.IsWhiteSpace(character))
{
break;
}
buffer.Read();
}
}
private static TemplateToken ReadOption(TextBuffer buffer)
{
var position = buffer.Position;
buffer.Consume('-');
if (buffer.IsNext('-'))
{
buffer.Consume('-');
var longValue = ReadOptionName(buffer);
return new TemplateToken(TemplateToken.Kind.LongName, position, longValue, $"--{longValue}");
}
var shortValue = ReadOptionName(buffer);
return new TemplateToken(TemplateToken.Kind.ShortName, position, shortValue, $"-{shortValue}");
}
private static string ReadOptionName(TextBuffer buffer)
{
var builder = new StringBuilder();
while (!buffer.ReachedEnd)
{
var character = buffer.Peek();
if (char.IsWhiteSpace(character) || character == '|')
{
break;
}
builder.Append(buffer.Read());
}
return builder.ToString();
}
private static TemplateToken ReadValue(TextBuffer buffer, bool required)
{
var start = required ? '<' : '[';
var end = required ? '>' : ']';
var position = buffer.Position;
var kind = required ? TemplateToken.Kind.RequiredValue : TemplateToken.Kind.OptionalValue;
// Consume start of value character (< or [).
buffer.Consume(start);
var builder = new StringBuilder();
while (!buffer.ReachedEnd)
{
var character = buffer.Peek();
if (character == end)
{
break;
}
buffer.Read();
builder.Append(character);
}
if (buffer.ReachedEnd)
{
var name = builder.ToString();
var token = new TemplateToken(kind, position, name, $"{start}{name}");
throw CommandTemplateException.UnterminatedValueName(buffer.Original, token);
}
// Consume end of value character (> or ]).
buffer.Consume(end);
// Get the value (the text within the brackets).
var value = builder.ToString();
// Create a token and return it.
return new TemplateToken(kind, position, value, required ? $"<{value}>" : $"[{value}]");
}
}

View File

@ -1,22 +0,0 @@
namespace Spectre.Console.Cli;
internal static class CliConstants
{
public const string DefaultCommandName = "__default_command";
public const string True = "true";
public const string False = "false";
public static string[] AcceptedBooleanValues { get; } = new string[]
{
True,
False,
};
public static class Commands
{
public const string Branch = "cli";
public const string Version = "version";
public const string XmlDoc = "xmldoc";
public const string Explain = "explain";
}
}

View File

@ -1,71 +0,0 @@
namespace Spectre.Console.Cli;
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
internal sealed class DefaultPairDeconstructor : IPairDeconstructor
{
/// <inheritdoc/>
(object? Key, object? Value) IPairDeconstructor.Deconstruct(
ITypeResolver resolver,
Type keyType,
Type valueType,
string? value)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
var parts = value.Split(new[] { '=' }, StringSplitOptions.None);
if (parts.Length < 1 || parts.Length > 2)
{
throw CommandParseException.ValueIsNotInValidFormat(value);
}
var stringkey = parts[0];
var stringValue = parts.Length == 2 ? parts[1] : null;
if (stringValue == null)
{
// Got a default constructor?
if (valueType.IsValueType)
{
// Get the string variant of a default instance.
// Should not get null here, but compiler doesn't know that.
stringValue = Activator.CreateInstance(valueType)?.ToString() ?? string.Empty;
}
else
{
// Try with an empty string.
// Hopefully, the type converter knows how to convert it.
stringValue = string.Empty;
}
}
return (Parse(stringkey, keyType),
Parse(stringValue, valueType));
}
private static object? Parse(string value, Type targetType)
{
try
{
var converter = GetConverter(targetType);
return converter.ConvertFrom(value);
}
catch
{
// Can't convert something. Just give up and tell the user.
throw CommandParseException.ValueIsNotInValidFormat(value);
}
}
private static TypeConverter GetConverter(Type type)
{
var converter = TypeDescriptor.GetConverter(type);
if (converter != null)
{
return converter;
}
throw new CommandConfigurationException($"Could find a type converter for '{type.FullName}'.");
}
}

View File

@ -1,21 +0,0 @@
namespace Spectre.Console.Cli;
internal sealed class DelegateCommand : ICommand
{
private readonly Func<CommandContext, CommandSettings, int> _func;
public DelegateCommand(Func<CommandContext, CommandSettings, int> func)
{
_func = func;
}
public Task<int> Execute(CommandContext context, CommandSettings settings)
{
return Task.FromResult(_func(context, settings));
}
public ValidationResult Validate(CommandContext context, CommandSettings settings)
{
return ValidationResult.Success();
}
}

View File

@ -1,48 +0,0 @@
namespace Spectre.Console.Cli;
internal static class CommandLineParseExceptionFactory
{
internal static CommandParseException Create(string arguments, CommandTreeToken token, string message, string details)
{
return new CommandParseException(message, CreatePrettyMessage(arguments, token, message, details));
}
internal static CommandParseException Create(IEnumerable<string> arguments, CommandTreeToken token, string message, string details)
{
return new CommandParseException(message, CreatePrettyMessage(string.Join(" ", arguments), token, message, details));
}
private static IRenderable CreatePrettyMessage(string arguments, CommandTreeToken token, string message, string details)
{
var composer = new Composer();
var position = token?.Position ?? 0;
var value = token?.Representation ?? arguments;
// Header
composer.LineBreak();
composer.Style("red", "Error:");
composer.Space().Text(message.EscapeMarkup());
composer.LineBreak();
// Template
composer.LineBreak();
composer.Spaces(7).Text(arguments.EscapeMarkup());
// Error
composer.LineBreak();
composer.Spaces(7).Spaces(position);
composer.Style("red", error =>
{
error.Repeat('^', value.Length);
error.Space();
error.Text(details.TrimEnd('.').EscapeMarkup());
error.LineBreak();
});
composer.LineBreak();
return composer;
}
}

View File

@ -1,54 +0,0 @@
namespace Spectre.Console.Cli;
internal static class CommandLineTemplateExceptionFactory
{
internal static CommandTemplateException Create(string template, TemplateToken? token, string message, string details)
{
return new CommandTemplateException(message, template, CreatePrettyMessage(template, token, message, details));
}
private static IRenderable CreatePrettyMessage(string template, TemplateToken? token, string message, string details)
{
var composer = new Composer();
var position = token?.Position ?? 0;
var value = token?.Representation ?? template;
// Header
composer.LineBreak();
composer.Style("red", "Error:");
composer.Space().Text("An error occured when parsing template.");
composer.LineBreak();
composer.Spaces(7).Style("yellow", message.EscapeMarkup());
composer.LineBreak();
if (string.IsNullOrWhiteSpace(template))
{
// Error
composer.LineBreak();
composer.Style("red", message.EscapeMarkup());
composer.LineBreak();
}
else
{
// Template
composer.LineBreak();
composer.Spaces(7).Text(template.EscapeMarkup());
// Error
composer.LineBreak();
composer.Spaces(7).Spaces(position);
composer.Style("red", error =>
{
error.Repeat('^', value.Length);
error.Space();
error.Text(details.TrimEnd('.').EscapeMarkup());
error.LineBreak();
});
}
composer.LineBreak();
return composer;
}
}

View File

@ -1,37 +0,0 @@
namespace Spectre.Console.Cli;
internal static class AnsiConsoleExtensions
{
private static readonly Lazy<IAnsiConsole> _console;
static AnsiConsoleExtensions()
{
_console = new Lazy<IAnsiConsole>(() => AnsiConsole.Console);
}
public static IAnsiConsole GetConsole(this IAnsiConsole? console)
{
return console ?? _console.Value;
}
public static void SafeRender(this IAnsiConsole? console, IRenderable? renderable)
{
if (renderable != null)
{
console ??= _console.Value;
console.Write(renderable);
}
}
public static void SafeRender(this IAnsiConsole? console, IEnumerable<IRenderable?> renderables)
{
console ??= _console.Value;
foreach (var renderable in renderables)
{
if (renderable != null)
{
console.Write(renderable);
}
}
}
}

View File

@ -1,32 +0,0 @@
namespace Spectre.Console.Cli;
internal static class CaseSensitivityExtensions
{
public static StringComparison GetStringComparison(this CaseSensitivity caseSensitivity, CommandPart part)
{
if (part == CommandPart.CommandName && (caseSensitivity & CaseSensitivity.Commands) == 0)
{
return StringComparison.OrdinalIgnoreCase;
}
else if (part == CommandPart.LongOption && (caseSensitivity & CaseSensitivity.LongOptions) == 0)
{
return StringComparison.OrdinalIgnoreCase;
}
return StringComparison.Ordinal;
}
public static StringComparer GetStringComparer(this CaseSensitivity caseSensitivity, CommandPart part)
{
if (part == CommandPart.CommandName && (caseSensitivity & CaseSensitivity.Commands) == 0)
{
return StringComparer.OrdinalIgnoreCase;
}
else if (part == CommandPart.LongOption && (caseSensitivity & CaseSensitivity.LongOptions) == 0)
{
return StringComparer.OrdinalIgnoreCase;
}
return StringComparer.Ordinal;
}
}

View File

@ -1,51 +0,0 @@
namespace Spectre.Console.Cli;
internal static class ListExtensions
{
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
if (source != null && action != null)
{
foreach (var item in source)
{
action(item);
}
}
}
public static T AddAndReturn<T>(this IList<T> source, T item)
where T : class
{
source.Add(item);
return item;
}
public static void AddIfNotNull<T>(this IList<T> source, T? item)
where T : class
{
if (item != null)
{
source.Add(item);
}
}
public static void AddRangeIfNotNull<T>(this IList<T> source, IEnumerable<T?> items)
where T : class
{
foreach (var item in items)
{
if (item != null)
{
source.Add(item);
}
}
}
public static void AddRange<T>(this IList<T> source, IEnumerable<T> items)
{
foreach (var item in items)
{
source.Add(item);
}
}
}

View File

@ -1,13 +0,0 @@
namespace Spectre.Console.Cli;
internal static class StringExtensions
{
internal static int OrdinalIndexOf(this string text, char token)
{
#if NETSTANDARD2_0
return text.IndexOf(token);
#else
return text.IndexOf(token, System.StringComparison.Ordinal);
#endif
}
}

View File

@ -1,19 +0,0 @@
namespace Spectre.Console.Cli;
internal static class TypeExtensions
{
public static bool IsPairDeconstructable(this Type type)
{
if (type.IsGenericType)
{
if (type.GetGenericTypeDefinition() == typeof(ILookup<,>) ||
type.GetGenericTypeDefinition() == typeof(IDictionary<,>) ||
type.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>))
{
return true;
}
}
return false;
}
}

View File

@ -1,52 +0,0 @@
namespace Spectre.Console.Cli;
internal static class TypeRegistrarExtensions
{
public static void RegisterDependencies(this ITypeRegistrar registrar, CommandModel model)
{
var stack = new Stack<CommandInfo>();
model.Commands.ForEach(c => stack.Push(c));
if (model.DefaultCommand != null)
{
stack.Push(model.DefaultCommand);
}
while (stack.Count > 0)
{
var command = stack.Pop();
if (command.SettingsType == null)
{
// TODO: Error message
throw new InvalidOperationException("Command setting type cannot be null.");
}
if (command.CommandType != null)
{
registrar?.Register(command.CommandType, command.CommandType);
}
foreach (var parameter in command.Parameters)
{
var pairDeconstructor = parameter?.PairDeconstructor?.Type;
if (pairDeconstructor != null)
{
registrar?.Register(pairDeconstructor, pairDeconstructor);
}
var typeConverterTypeName = parameter?.Converter?.ConverterTypeName;
if (!string.IsNullOrWhiteSpace(typeConverterTypeName))
{
var typeConverterType = Type.GetType(typeConverterTypeName);
Debug.Assert(typeConverterType != null, "Could not create type");
registrar?.Register(typeConverterType, typeConverterType);
}
}
foreach (var child in command.Children)
{
stack.Push(child);
}
}
}
}

View File

@ -1,39 +0,0 @@
namespace Spectre.Console.Cli;
internal static class XmlElementExtensions
{
public static void SetNullableAttribute(this XmlElement element, string name, string? value)
{
element.SetAttribute(name, value ?? "NULL");
}
public static void SetNullableAttribute(this XmlElement element, string name, IEnumerable<string>? values)
{
if (values?.Any() != true)
{
element.SetAttribute(name, "NULL");
}
element.SetAttribute(name, string.Join(",", values ?? Enumerable.Empty<string>()));
}
public static void SetBooleanAttribute(this XmlElement element, string name, bool value)
{
element.SetAttribute(name, value ? "true" : "false");
}
public static void SetEnumAttribute(this XmlElement element, string name, Enum value)
{
var field = value.GetType().GetField(value.ToString());
if (field != null)
{
var attribute = field.GetCustomAttribute<DescriptionAttribute>(false);
if (attribute == null)
{
throw new InvalidOperationException("Enum is missing description.");
}
element.SetAttribute(name, attribute.Description);
}
}
}

View File

@ -1,373 +0,0 @@
namespace Spectre.Console.Cli;
internal static class HelpWriter
{
private sealed class HelpArgument
{
public string Name { get; }
public int Position { get; set; }
public bool Required { get; }
public string? Description { get; }
public HelpArgument(string name, int position, bool required, string? description)
{
Name = name;
Position = position;
Required = required;
Description = description;
}
public static IReadOnlyList<HelpArgument> Get(CommandInfo? command)
{
var arguments = new List<HelpArgument>();
arguments.AddRange(command?.Parameters?.OfType<CommandArgument>()?.Select(
x => new HelpArgument(x.Value, x.Position, x.Required, x.Description))
?? Array.Empty<HelpArgument>());
return arguments;
}
}
private sealed class HelpOption
{
public string Short { get; }
public string Long { get; }
public string? Value { get; }
public bool? ValueIsOptional { get; }
public string? Description { get; }
public HelpOption(string @short, string @long, string? @value, bool? valueIsOptional, string? description)
{
Short = @short;
Long = @long;
Value = value;
ValueIsOptional = valueIsOptional;
Description = description;
}
public static IReadOnlyList<HelpOption> Get(CommandModel model, CommandInfo? command)
{
var parameters = new List<HelpOption>();
parameters.Add(new HelpOption("h", "help", null, null, "Prints help information"));
// At the root and no default command?
if (command == null && model?.DefaultCommand == null)
{
parameters.Add(new HelpOption("v", "version", null, null, "Prints version information"));
}
parameters.AddRange(command?.Parameters.OfType<CommandOption>().Where(o => !o.IsHidden).Select(o =>
new HelpOption(
o.ShortNames.FirstOrDefault(), o.LongNames.FirstOrDefault(),
o.ValueName, o.ValueIsOptional, o.Description))
?? Array.Empty<HelpOption>());
return parameters;
}
}
public static IEnumerable<IRenderable> Write(CommandModel model)
{
return WriteCommand(model, null);
}
public static IEnumerable<IRenderable> WriteCommand(CommandModel model, CommandInfo? command)
{
var container = command as ICommandContainer ?? model;
var isDefaultCommand = command?.IsDefaultCommand ?? false;
var result = new List<IRenderable>();
result.AddRange(GetUsage(model, command));
result.AddRange(GetExamples(model, command));
result.AddRange(GetArguments(command));
result.AddRange(GetOptions(model, command));
result.AddRange(GetCommands(model, container, isDefaultCommand));
return result;
}
private static IEnumerable<IRenderable> GetUsage(CommandModel model, CommandInfo? command)
{
var composer = new Composer();
composer.Style("yellow", "USAGE:").LineBreak();
composer.Tab().Text(model.GetApplicationName());
var parameters = new List<string>();
if (command == null)
{
parameters.Add("[grey][[OPTIONS]][/]");
parameters.Add("[aqua]<COMMAND>[/]");
}
else
{
foreach (var current in command.Flatten())
{
var isCurrent = current == command;
if (!current.IsDefaultCommand)
{
if (isCurrent)
{
parameters.Add($"[underline]{current.Name.EscapeMarkup()}[/]");
}
else
{
parameters.Add($"{current.Name.EscapeMarkup()}");
}
}
if (current.Parameters.OfType<CommandArgument>().Any())
{
if (isCurrent)
{
foreach (var argument in current.Parameters.OfType<CommandArgument>()
.Where(a => a.Required).OrderBy(a => a.Position).ToArray())
{
parameters.Add($"[aqua]<{argument.Value.EscapeMarkup()}>[/]");
}
}
var optionalArguments = current.Parameters.OfType<CommandArgument>().Where(x => !x.Required).ToArray();
if (optionalArguments.Length > 0 || !isCurrent)
{
foreach (var optionalArgument in optionalArguments)
{
parameters.Add($"[silver][[{optionalArgument.Value.EscapeMarkup()}]][/]");
}
}
}
if (isCurrent)
{
parameters.Add("[grey][[OPTIONS]][/]");
}
}
if (command.IsBranch)
{
parameters.Add("[aqua]<COMMAND>[/]");
}
}
composer.Join(" ", parameters);
composer.LineBreak();
return new[]
{
composer,
};
}
private static IEnumerable<IRenderable> GetExamples(CommandModel model, CommandInfo? command)
{
var maxExamples = int.MaxValue;
var examples = command?.Examples ?? model.Examples ?? new List<string[]>();
if (examples.Count == 0)
{
// Since we're not checking direct examples,
// make sure that we limit the number of examples.
maxExamples = 5;
// Get the current root command.
var root = command ?? (ICommandContainer)model;
var queue = new Queue<ICommandContainer>(new[] { root });
// Traverse the command tree and look for examples.
// As soon as a node contains commands, bail.
while (queue.Count > 0)
{
var current = queue.Dequeue();
foreach (var cmd in current.Commands.Where(x => !x.IsHidden))
{
if (cmd.Examples.Count > 0)
{
examples.AddRange(cmd.Examples);
}
queue.Enqueue(cmd);
}
if (examples.Count >= maxExamples)
{
break;
}
}
}
if (examples.Count > 0)
{
var composer = new Composer();
composer.LineBreak();
composer.Style("yellow", "EXAMPLES:").LineBreak();
for (var index = 0; index < Math.Min(maxExamples, examples.Count); index++)
{
var args = string.Join(" ", examples[index]);
composer.Tab().Text(model.GetApplicationName()).Space().Style("grey", args);
composer.LineBreak();
}
return new[] { composer };
}
return Array.Empty<IRenderable>();
}
private static IEnumerable<IRenderable> GetArguments(CommandInfo? command)
{
var arguments = HelpArgument.Get(command);
if (arguments.Count == 0)
{
return Array.Empty<IRenderable>();
}
var result = new List<IRenderable>
{
new Markup(Environment.NewLine),
new Markup("[yellow]ARGUMENTS:[/]"),
new Markup(Environment.NewLine),
};
var grid = new Grid();
grid.AddColumn(new GridColumn { Padding = new Padding(4, 4), NoWrap = true });
grid.AddColumn(new GridColumn { Padding = new Padding(0, 0) });
foreach (var argument in arguments.Where(x => x.Required).OrderBy(x => x.Position))
{
grid.AddRow(
$"[silver]<{argument.Name.EscapeMarkup()}>[/]",
argument.Description?.TrimEnd('.') ?? " ");
}
foreach (var argument in arguments.Where(x => !x.Required).OrderBy(x => x.Position))
{
grid.AddRow(
$"[grey][[{argument.Name.EscapeMarkup()}]][/]",
argument.Description?.TrimEnd('.') ?? " ");
}
result.Add(grid);
return result;
}
private static IEnumerable<IRenderable> GetOptions(CommandModel model, CommandInfo? command)
{
// Collect all options into a single structure.
var parameters = HelpOption.Get(model, command);
if (parameters.Count == 0)
{
return Array.Empty<IRenderable>();
}
var result = new List<IRenderable>
{
new Markup(Environment.NewLine),
new Markup("[yellow]OPTIONS:[/]"),
new Markup(Environment.NewLine),
};
var grid = new Grid();
grid.AddColumn(new GridColumn { Padding = new Padding(4, 4), NoWrap = true });
grid.AddColumn(new GridColumn { Padding = new Padding(0, 0) });
static string GetOptionParts(HelpOption option)
{
var builder = new StringBuilder();
if (option.Short != null)
{
builder.Append('-').Append(option.Short.EscapeMarkup());
if (option.Long != null)
{
builder.Append(", ");
}
}
else
{
builder.Append(" ");
if (option.Long != null)
{
builder.Append(" ");
}
}
if (option.Long != null)
{
builder.Append("--").Append(option.Long.EscapeMarkup());
}
if (option.Value != null)
{
builder.Append(' ');
if (option.ValueIsOptional ?? false)
{
builder.Append("[grey][[").Append(option.Value.EscapeMarkup()).Append("]][/]");
}
else
{
builder.Append("[silver]<").Append(option.Value.EscapeMarkup()).Append(">[/]");
}
}
return builder.ToString();
}
foreach (var option in parameters.ToArray())
{
grid.AddRow(
GetOptionParts(option),
option.Description?.TrimEnd('.') ?? " ");
}
result.Add(grid);
return result;
}
private static IEnumerable<IRenderable> GetCommands(
CommandModel model,
ICommandContainer command,
bool isDefaultCommand)
{
var commands = isDefaultCommand ? model.Commands : command.Commands;
commands = commands.Where(x => !x.IsHidden).ToList();
if (commands.Count == 0)
{
return Array.Empty<IRenderable>();
}
var result = new List<IRenderable>
{
new Markup(Environment.NewLine),
new Markup("[yellow]COMMANDS:[/]"),
new Markup(Environment.NewLine),
};
var grid = new Grid();
grid.AddColumn(new GridColumn { Padding = new Padding(4, 4), NoWrap = true });
grid.AddColumn(new GridColumn { Padding = new Padding(0, 0) });
foreach (var child in commands)
{
var arguments = new Composer();
arguments.Style("silver", child.Name.EscapeMarkup());
arguments.Space();
foreach (var argument in HelpArgument.Get(child).Where(a => a.Required))
{
arguments.Style("silver", $"<{argument.Name.EscapeMarkup()}>");
arguments.Space();
}
grid.AddRow(
arguments.ToString().TrimEnd(),
child.Description?.TrimEnd('.') ?? " ");
}
result.Add(grid);
return result;
}
}

View File

@ -1,21 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a pair deconstructor.
/// </summary>
internal interface IPairDeconstructor
{
/// <summary>
/// Deconstructs the specified value into its components.
/// </summary>
/// <param name="resolver">The type resolver to use.</param>
/// <param name="keyType">The key type.</param>
/// <param name="valueType">The value type.</param>
/// <param name="value">The value to deconstruct.</param>
/// <returns>A deconstructed value.</returns>
(object? Key, object? Value) Deconstruct(
ITypeResolver resolver,
Type keyType,
Type valueType,
string? value);
}

View File

@ -1,19 +0,0 @@
namespace Spectre.Console.Cli;
internal sealed class CommandArgument : CommandParameter
{
public string Value { get; }
public int Position { get; set; }
public CommandArgument(
Type parameterType, ParameterKind parameterKind, PropertyInfo property, string? description,
TypeConverterAttribute? converter, DefaultValueAttribute? defaultValue,
CommandArgumentAttribute argument, ParameterValueProviderAttribute? valueProvider,
IEnumerable<ParameterValidationAttribute> validators)
: base(parameterType, parameterKind, property, description, converter, defaultValue,
null, valueProvider, validators, argument.IsRequired, false)
{
Value = argument.ValueName;
Position = argument.Position;
}
}

View File

@ -1,18 +0,0 @@
namespace Spectre.Console.Cli;
internal static class CommandContainerExtensions
{
public static CommandInfo? FindCommand(this ICommandContainer root, string name, CaseSensitivity sensitivity)
{
var result = root.Commands.FirstOrDefault(
c => c.Name.Equals(name, sensitivity.GetStringComparison(CommandPart.CommandName)));
if (result == null)
{
result = root.Commands.FirstOrDefault(
c => c.Aliases.Contains(name, sensitivity.GetStringComparer(CommandPart.CommandName)));
}
return result;
}
}

View File

@ -1,63 +0,0 @@
namespace Spectre.Console.Cli;
internal sealed class CommandInfo : ICommandContainer
{
public string Name { get; }
public HashSet<string> Aliases { get; }
public string? Description { get; }
public object? Data { get; }
public Type? CommandType { get; }
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;
public CommandInfo(CommandInfo? parent, ConfiguredCommand prototype)
{
Parent = parent;
Name = prototype.Name;
Aliases = new HashSet<string>(prototype.Aliases);
Description = prototype.Description;
Data = prototype.Data;
CommandType = prototype.CommandType;
SettingsType = prototype.SettingsType;
Delegate = prototype.Delegate;
IsDefaultCommand = prototype.IsDefaultCommand;
IsHidden = prototype.IsHidden;
Children = new List<CommandInfo>();
Parameters = new List<CommandParameter>();
Examples = prototype.Examples ?? new List<string[]>();
if (CommandType != null && string.IsNullOrWhiteSpace(Description))
{
var description = CommandType.GetCustomAttribute<DescriptionAttribute>();
if (description != null)
{
Description = description.Description;
}
}
}
public List<CommandInfo> Flatten()
{
var result = new Stack<CommandInfo>();
var current = this;
while (current != null)
{
result.Push(current);
current = current.Parent;
}
return result.ToList();
}
}

View File

@ -1,76 +0,0 @@
namespace Spectre.Console.Cli;
internal static class CommandInfoExtensions
{
public static bool HaveParentWithOption(this CommandInfo command, CommandOption option)
{
var parent = command?.Parent;
while (parent != null)
{
foreach (var parentOption in parent.Parameters.OfType<CommandOption>())
{
if (option.HaveSameBackingPropertyAs(parentOption))
{
return true;
}
}
parent = parent.Parent;
}
return false;
}
public static bool AllowParentOption(this CommandInfo command, CommandOption option)
{
// Got an immediate parent?
if (command?.Parent != null)
{
// Is the current node's settings type the same as the previous one?
if (command.SettingsType == command.Parent.SettingsType)
{
var parameters = command.Parent.Parameters.OfType<CommandOption>().ToArray();
if (parameters.Length > 0)
{
foreach (var parentOption in parameters)
{
// Is this the same one?
if (option.HaveSameBackingPropertyAs(parentOption))
{
// Is it part of the same settings class.
if (option.Property.DeclaringType == command.SettingsType)
{
// Allow it.
return true;
}
// Don't allow it.
return false;
}
}
}
}
}
return false;
}
public static bool HaveParentWithArgument(this CommandInfo command, CommandArgument argument)
{
var parent = command?.Parent;
while (parent != null)
{
foreach (var parentOption in parent.Parameters.OfType<CommandArgument>())
{
if (argument.HaveSameBackingPropertyAs(parentOption))
{
return true;
}
}
parent = parent.Parent;
}
return false;
}
}

View File

@ -1,44 +0,0 @@
namespace Spectre.Console.Cli;
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 CommandModel(
CommandAppSettings settings,
CommandInfo? defaultCommand,
IEnumerable<CommandInfo> commands,
IEnumerable<string[]> examples)
{
ApplicationName = settings.ApplicationName;
ParsingMode = settings.ParsingMode;
DefaultCommand = defaultCommand;
Commands = new List<CommandInfo>(commands ?? Array.Empty<CommandInfo>());
Examples = new List<string[]>(examples ?? Array.Empty<string[]>());
}
public string GetApplicationName()
{
return
ApplicationName ??
Path.GetFileName(GetApplicationFile()) ?? // null is propagated by GetFileName
"?";
}
private static string? GetApplicationFile()
{
var location = Assembly.GetEntryAssembly()?.Location;
if (string.IsNullOrWhiteSpace(location))
{
// this is special case for single file executable
// (Assembly.Location returns empty string)
return Process.GetCurrentProcess().MainModule?.FileName;
}
return location;
}
}

View File

@ -1,246 +0,0 @@
namespace Spectre.Console.Cli;
internal static class CommandModelBuilder
{
// Consider removing this in favor for value tuples at some point.
private sealed class OrderedProperties
{
public int Level { get; }
public int SortOrder { get; }
public PropertyInfo[] Properties { get; }
public OrderedProperties(int level, int sortOrder, PropertyInfo[] properties)
{
Level = level;
SortOrder = sortOrder;
Properties = properties;
}
}
public static CommandModel Build(IConfiguration configuration)
{
var result = new List<CommandInfo>();
foreach (var command in configuration.Commands)
{
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);
}
// Create the command model and validate it.
var model = new CommandModel(configuration.Settings, defaultCommand, result, configuration.Examples);
CommandModelValidator.Validate(model, configuration.Settings);
return model;
}
private static CommandInfo Build(CommandInfo? parent, ConfiguredCommand command)
{
var info = new CommandInfo(parent, command);
foreach (var parameter in GetParameters(info))
{
info.Parameters.Add(parameter);
}
foreach (var childCommand in command.Children)
{
var child = Build(info, childCommand);
info.Children.Add(child);
}
// Normalize argument positions.
var index = 0;
foreach (var argument in info.Parameters.OfType<CommandArgument>()
.OrderBy(argument => argument.Position))
{
argument.Position = index;
index++;
}
return info;
}
private static IEnumerable<CommandParameter> GetParameters(CommandInfo command)
{
var result = new List<CommandParameter>();
var argumentPosition = 0;
// We need to get parameters in order of the class where they were defined.
// We assign each inheritance level a value that is used to properly sort the
// arguments when iterating over them.
IEnumerable<OrderedProperties> GetPropertiesInOrder()
{
var current = command.SettingsType;
var level = 0;
var sortOrder = 0;
while (current.BaseType != null)
{
yield return new OrderedProperties(level, sortOrder, current.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public));
current = current.BaseType;
// Things get a little bit complicated now.
// Only consider a setting's base type part of the
// setting, if there isn't a parent command that implements
// the setting's base type. This might come back to bite us :)
var currentCommand = command.Parent;
while (currentCommand != null)
{
if (currentCommand.SettingsType == current)
{
level--;
break;
}
currentCommand = currentCommand.Parent;
}
sortOrder--;
}
}
var groups = GetPropertiesInOrder();
foreach (var group in groups.OrderBy(x => x.Level).ThenBy(x => x.SortOrder))
{
var parameters = new List<CommandParameter>();
foreach (var property in group.Properties)
{
if (property.IsDefined(typeof(CommandOptionAttribute)))
{
var attribute = property.GetCustomAttribute<CommandOptionAttribute>();
if (attribute != null)
{
var option = BuildOptionParameter(property, attribute);
// Any previous command has this option defined?
if (command.HaveParentWithOption(option))
{
// Do we allow it to exist on this command as well?
if (command.AllowParentOption(option))
{
option.IsShadowed = true;
parameters.Add(option);
}
}
else
{
// No parent have this option.
parameters.Add(option);
}
}
}
else if (property.IsDefined(typeof(CommandArgumentAttribute)))
{
var attribute = property.GetCustomAttribute<CommandArgumentAttribute>();
if (attribute != null)
{
var argument = BuildArgumentParameter(property, attribute);
// Any previous command has this argument defined?
// In that case, we should not assign the parameter to this command.
if (!command.HaveParentWithArgument(argument))
{
parameters.Add(argument);
}
}
}
}
// Update the position for the parameters.
foreach (var argument in parameters.OfType<CommandArgument>().OrderBy(x => x.Position))
{
argument.Position = argumentPosition++;
}
// Add all parameters to the result.
foreach (var groupResult in parameters)
{
result.Add(groupResult);
}
}
return result;
}
private static CommandOption BuildOptionParameter(PropertyInfo property, CommandOptionAttribute attribute)
{
var description = property.GetCustomAttribute<DescriptionAttribute>();
var converter = property.GetCustomAttribute<TypeConverterAttribute>();
var deconstructor = property.GetCustomAttribute<PairDeconstructorAttribute>();
var valueProvider = property.GetCustomAttribute<ParameterValueProviderAttribute>();
var validators = property.GetCustomAttributes<ParameterValidationAttribute>(true);
var defaultValue = property.GetCustomAttribute<DefaultValueAttribute>();
var kind = GetOptionKind(property.PropertyType, attribute, deconstructor, converter);
if (defaultValue == null && property.PropertyType == typeof(bool))
{
defaultValue = new DefaultValueAttribute(false);
}
return new CommandOption(property.PropertyType, kind,
property, description?.Description, converter, deconstructor,
attribute, valueProvider, validators, defaultValue,
attribute.ValueIsOptional);
}
private static CommandArgument BuildArgumentParameter(PropertyInfo property, CommandArgumentAttribute attribute)
{
var description = property.GetCustomAttribute<DescriptionAttribute>();
var converter = property.GetCustomAttribute<TypeConverterAttribute>();
var defaultValue = property.GetCustomAttribute<DefaultValueAttribute>();
var valueProvider = property.GetCustomAttribute<ParameterValueProviderAttribute>();
var validators = property.GetCustomAttributes<ParameterValidationAttribute>(true);
var kind = GetParameterKind(property.PropertyType);
return new CommandArgument(
property.PropertyType, kind, property,
description?.Description, converter,
defaultValue, attribute, valueProvider,
validators);
}
private static ParameterKind GetOptionKind(
Type type,
CommandOptionAttribute attribute,
PairDeconstructorAttribute? deconstructor,
TypeConverterAttribute? converter)
{
if (attribute.ValueIsOptional)
{
return ParameterKind.FlagWithValue;
}
if (type.IsPairDeconstructable() && (deconstructor != null || converter == null))
{
return ParameterKind.Pair;
}
return GetParameterKind(type);
}
private static ParameterKind GetParameterKind(Type type)
{
if (type == typeof(bool) || type == typeof(bool?))
{
return ParameterKind.Flag;
}
if (type.IsArray)
{
return ParameterKind.Vector;
}
return ParameterKind.Scalar;
}
}

View File

@ -1,189 +0,0 @@
namespace Spectre.Console.Cli;
internal static class CommandModelValidator
{
public static void Validate(CommandModel model, CommandAppSettings settings)
{
if (model is null)
{
throw new ArgumentNullException(nameof(model));
}
if (settings is null)
{
throw new ArgumentNullException(nameof(settings));
}
if (model.Commands.Count == 0 && model.DefaultCommand == null)
{
throw CommandConfigurationException.NoCommandConfigured();
}
foreach (var command in model.Commands)
{
// Alias collision?
foreach (var alias in command.Aliases)
{
if (model.Commands.Any(x => x.Name.Equals(alias, StringComparison.OrdinalIgnoreCase)))
{
throw CommandConfigurationException.CommandNameConflict(command, alias);
}
}
}
Validate(model.DefaultCommand);
foreach (var command in model.Commands)
{
Validate(command);
}
if (settings.ValidateExamples)
{
ValidateExamples(model, settings);
}
}
private static void Validate(CommandInfo? command)
{
if (command == null)
{
return;
}
// Get duplicate options for command.
var duplicateOptions = GetDuplicates(command);
if (duplicateOptions.Length > 0)
{
throw CommandConfigurationException.DuplicateOption(command, duplicateOptions);
}
// No children?
if (command.IsBranch && command.Children.Count == 0)
{
throw CommandConfigurationException.BranchHasNoChildren(command);
}
var arguments = command.Parameters
.OfType<CommandArgument>()
.OrderBy(x => x.Position)
.ToList();
// vector arguments?
if (arguments.Any(x => x.ParameterKind == ParameterKind.Vector))
{
// Multiple vector arguments for command?
if (arguments.Count(x => x.ParameterKind == ParameterKind.Vector) > 1)
{
throw CommandConfigurationException.TooManyVectorArguments(command);
}
// Make sure that the vector argument is specified last.
if (arguments.Last().ParameterKind != ParameterKind.Vector)
{
throw CommandConfigurationException.VectorArgumentNotSpecifiedLast(command);
}
}
// Arguments
foreach (var argument in arguments)
{
if (argument.Required && argument.DefaultValue != null)
{
throw CommandConfigurationException.RequiredArgumentsCannotHaveDefaultValue(argument);
}
}
// Options
var options = command.Parameters.OfType<CommandOption>();
foreach (var option in options)
{
// Pair deconstructable?
if (option.Property.PropertyType.IsPairDeconstructable())
{
if (option.PairDeconstructor != null && option.Converter != null)
{
throw CommandConfigurationException.OptionBothHasPairDeconstructorAndTypeParameter(option);
}
}
else if (option.PairDeconstructor != null)
{
throw CommandConfigurationException.OptionTypeDoesNotSupportDeconstruction(option);
}
// Optional options that are not flags?
if (option.ParameterKind == ParameterKind.FlagWithValue && !option.IsFlagValue())
{
throw CommandConfigurationException.OptionalOptionValueMustBeFlagWithValue(option);
}
}
// Validate child commands.
foreach (var childCommand in command.Children)
{
Validate(childCommand);
}
}
private static void ValidateExamples(CommandModel model, CommandAppSettings settings)
{
var examples = new List<string[]>();
examples.AddRangeIfNotNull(model.Examples);
// Get all examples.
var queue = new Queue<ICommandContainer>(new[] { model });
while (queue.Count > 0)
{
var current = queue.Dequeue();
foreach (var command in current.Commands)
{
examples.AddRangeIfNotNull(command.Examples);
queue.Enqueue(command);
}
}
// Validate all examples.
foreach (var example in examples)
{
try
{
var parser = new CommandTreeParser(model, settings, ParsingMode.Strict);
parser.Parse(example);
}
catch (Exception ex)
{
throw new CommandConfigurationException("Validation of examples failed.", ex);
}
}
}
private static string[] GetDuplicates(CommandInfo command)
{
var result = new Dictionary<string, int>(StringComparer.Ordinal);
void AddToResult(IEnumerable<string> keys)
{
foreach (var key in keys)
{
if (!string.IsNullOrWhiteSpace(key))
{
if (!result.ContainsKey(key))
{
result.Add(key, 0);
}
result[key]++;
}
}
}
foreach (var option in command.Parameters.OfType<CommandOption>())
{
AddToResult(option.ShortNames);
AddToResult(option.LongNames);
}
return result.Where(x => x.Value > 1)
.Select(x => x.Key).ToArray();
}
}

View File

@ -1,30 +0,0 @@
namespace Spectre.Console.Cli;
internal sealed class CommandOption : CommandParameter
{
public IReadOnlyList<string> LongNames { get; }
public IReadOnlyList<string> ShortNames { get; }
public string? ValueName { get; }
public bool ValueIsOptional { get; }
public bool IsShadowed { get; set; }
public CommandOption(
Type parameterType, ParameterKind parameterKind, PropertyInfo property, string? description,
TypeConverterAttribute? converter, PairDeconstructorAttribute? deconstructor,
CommandOptionAttribute optionAttribute, ParameterValueProviderAttribute? valueProvider,
IEnumerable<ParameterValidationAttribute> validators,
DefaultValueAttribute? defaultValue, bool valueIsOptional)
: base(parameterType, parameterKind, property, description, converter,
defaultValue, deconstructor, valueProvider, validators, false, optionAttribute.IsHidden)
{
LongNames = optionAttribute.LongNames;
ShortNames = optionAttribute.ShortNames;
ValueName = optionAttribute.ValueName;
ValueIsOptional = valueIsOptional;
}
public string GetOptionName()
{
return LongNames.Count > 0 ? LongNames[0] : ShortNames[0];
}
}

View File

@ -1,149 +0,0 @@
namespace Spectre.Console.Cli;
internal abstract class CommandParameter : ICommandParameterInfo
{
public Guid Id { get; }
public Type ParameterType { get; }
public ParameterKind ParameterKind { get; }
public PropertyInfo Property { get; }
public string? Description { get; }
public DefaultValueAttribute? DefaultValue { get; }
public TypeConverterAttribute? Converter { get; }
public PairDeconstructorAttribute? PairDeconstructor { get; }
public List<ParameterValidationAttribute> Validators { get; }
public ParameterValueProviderAttribute? ValueProvider { get; }
public bool Required { get; set; }
public bool IsHidden { get; }
public string PropertyName => Property.Name;
public virtual bool WantRawValue => ParameterType.IsPairDeconstructable()
&& (PairDeconstructor != null || Converter == null);
protected CommandParameter(
Type parameterType, ParameterKind parameterKind, PropertyInfo property,
string? description, TypeConverterAttribute? converter,
DefaultValueAttribute? defaultValue,
PairDeconstructorAttribute? deconstructor,
ParameterValueProviderAttribute? valueProvider,
IEnumerable<ParameterValidationAttribute> validators, bool required, bool isHidden)
{
Id = Guid.NewGuid();
ParameterType = parameterType;
ParameterKind = parameterKind;
Property = property;
Description = description;
Converter = converter;
DefaultValue = defaultValue;
PairDeconstructor = deconstructor;
ValueProvider = valueProvider;
Validators = new List<ParameterValidationAttribute>(validators ?? Array.Empty<ParameterValidationAttribute>());
Required = required;
IsHidden = isHidden;
}
public bool IsFlagValue()
{
return ParameterType.GetInterfaces().Any(i => i == typeof(IFlagValue));
}
public bool HaveSameBackingPropertyAs(CommandParameter other)
{
return CommandParameterComparer.ByBackingProperty.Equals(this, other);
}
public void Assign(CommandSettings settings, ITypeResolver resolver, object? value)
{
// Is the property pair deconstructable?
// TODO: This needs to be better defined
if (Property.PropertyType.IsPairDeconstructable() && WantRawValue)
{
var genericTypes = Property.PropertyType.GetGenericArguments();
var multimap = (IMultiMap?)Property.GetValue(settings);
if (multimap == null)
{
multimap = Activator.CreateInstance(typeof(MultiMap<,>).MakeGenericType(genericTypes[0], genericTypes[1])) as IMultiMap;
if (multimap == null)
{
throw new InvalidOperationException("Could not create multimap");
}
}
// Create deconstructor.
var deconstructorType = PairDeconstructor?.Type ?? typeof(DefaultPairDeconstructor);
if (!(resolver.Resolve(deconstructorType) is IPairDeconstructor deconstructor))
{
if (!(Activator.CreateInstance(deconstructorType) is IPairDeconstructor activatedDeconstructor))
{
throw new InvalidOperationException($"Could not create pair deconstructor.");
}
deconstructor = activatedDeconstructor;
}
// Deconstruct and add to multimap.
var pair = deconstructor.Deconstruct(resolver, genericTypes[0], genericTypes[1], value as string);
if (pair.Key != null)
{
multimap.Add(pair);
}
value = multimap;
}
else if (Property.PropertyType.IsArray)
{
// Add a new item to the array
var array = (Array?)Property.GetValue(settings);
Array newArray;
var elementType = Property.PropertyType.GetElementType();
if (elementType == null)
{
throw new InvalidOperationException("Could not get property type.");
}
if (array == null)
{
newArray = Array.CreateInstance(elementType, 1);
}
else
{
newArray = Array.CreateInstance(elementType, array.Length + 1);
array.CopyTo(newArray, 0);
}
newArray.SetValue(value, newArray.Length - 1);
value = newArray;
}
else if (IsFlagValue())
{
var flagValue = (IFlagValue?)Property.GetValue(settings);
if (flagValue == null)
{
flagValue = (IFlagValue?)Activator.CreateInstance(ParameterType);
if (flagValue == null)
{
throw new InvalidOperationException("Could not create flag value.");
}
}
if (value != null)
{
// Null means set, but not with a valid value.
flagValue.Value = value;
}
// If the parameter was mapped, then it's set.
flagValue.IsSet = true;
value = flagValue;
}
Property.SetValue(settings, value);
}
public object? Get(CommandSettings settings)
{
return Property.GetValue(settings);
}
}

View File

@ -1,29 +0,0 @@
namespace Spectre.Console.Cli;
internal static class CommandParameterComparer
{
public static readonly ByBackingPropertyComparer ByBackingProperty = new ByBackingPropertyComparer();
public sealed class ByBackingPropertyComparer : IEqualityComparer<CommandParameter?>
{
public bool Equals(CommandParameter? x, CommandParameter? y)
{
if (x is null || y is null)
{
return false;
}
if (ReferenceEquals(x, y))
{
return true;
}
return x.Property.MetadataToken == y.Property.MetadataToken;
}
public int GetHashCode(CommandParameter? obj)
{
return obj?.Property?.MetadataToken.GetHashCode() ?? 0;
}
}
}

View File

@ -1,12 +0,0 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command container.
/// </summary>
internal interface ICommandContainer
{
/// <summary>
/// Gets all commands in the container.
/// </summary>
IList<CommandInfo> Commands { get; }
}

View File

@ -1,19 +0,0 @@
namespace Spectre.Console.Cli;
internal enum ParameterKind
{
[Description("flag")]
Flag = 0,
[Description("scalar")]
Scalar = 1,
[Description("vector")]
Vector = 2,
[Description("flagvalue")]
FlagWithValue = 3,
[Description("pair")]
Pair = 4,
}

View File

@ -1,34 +0,0 @@
namespace Spectre.Console.Cli;
internal sealed class CommandTree
{
public CommandInfo Command { get; }
public List<MappedCommandParameter> Mapped { get; }
public List<CommandParameter> Unmapped { get; }
public CommandTree? Parent { get; }
public CommandTree? Next { get; set; }
public bool ShowHelp { get; set; }
public CommandTree(CommandTree? parent, CommandInfo command)
{
Parent = parent;
Command = command;
Mapped = new List<MappedCommandParameter>();
Unmapped = new List<CommandParameter>();
}
public ICommand CreateCommand(ITypeResolver resolver)
{
if (Command.Delegate != null)
{
return new DelegateCommand(Command.Delegate);
}
if (resolver.Resolve(Command.CommandType) is ICommand command)
{
return command;
}
throw CommandParseException.CouldNotCreateCommand(Command.CommandType);
}
}

View File

@ -1,66 +0,0 @@
namespace Spectre.Console.Cli;
internal static class CommandTreeExtensions
{
public static CommandTree? GetRootCommand(this CommandTree node)
{
while (node.Parent != null)
{
node = node.Parent;
}
return node;
}
public static CommandTree GetLeafCommand(this CommandTree node)
{
while (node.Next != null)
{
node = node.Next;
}
return node;
}
public static bool HasArguments(this CommandTree tree)
{
return tree.Command.Parameters.OfType<CommandArgument>().Any();
}
public static CommandArgument? FindArgument(this CommandTree tree, int position)
{
return tree.Command.Parameters
.OfType<CommandArgument>()
.FirstOrDefault(c => c.Position == position);
}
public static CommandOption? FindOption(this CommandTree tree, string name, bool longOption, CaseSensitivity sensitivity)
{
return tree.Command.Parameters
.OfType<CommandOption>()
.FirstOrDefault(o => longOption
? o.LongNames.Contains(name, sensitivity.GetStringComparer(CommandPart.LongOption))
: o.ShortNames.Contains(name, StringComparer.Ordinal));
}
public static bool IsOptionMappedWithParent(this CommandTree tree, string name, bool longOption)
{
var node = tree.Parent;
while (node != null)
{
var option = node.Command?.Parameters.OfType<CommandOption>()
.FirstOrDefault(o => longOption
? o.LongNames.Contains(name, StringComparer.Ordinal)
: o.ShortNames.Contains(name, StringComparer.Ordinal));
if (option != null)
{
return node.Mapped.Any(p => p.Parameter == option);
}
node = node.Parent;
}
return false;
}
}

View File

@ -1,383 +0,0 @@
namespace Spectre.Console.Cli;
internal class CommandTreeParser
{
private readonly CommandModel _configuration;
private readonly ParsingMode _parsingMode;
private readonly CommandOptionAttribute _help;
public CaseSensitivity CaseSensitivity { get; }
public enum State
{
Normal = 0,
Remaining = 1,
}
public CommandTreeParser(CommandModel configuration, ICommandAppSettings settings, ParsingMode? parsingMode = null)
{
if (settings is null)
{
throw new ArgumentNullException(nameof(settings));
}
_configuration = configuration;
_parsingMode = parsingMode ?? _configuration.ParsingMode;
_help = new CommandOptionAttribute("-h|--help");
CaseSensitivity = settings.CaseSensitivity;
}
public CommandTreeParserResult Parse(IEnumerable<string> args)
{
var context = new CommandTreeParserContext(args, _parsingMode);
var tokenizerResult = CommandTreeTokenizer.Tokenize(context.Arguments);
var tokens = tokenizerResult.Tokens;
var rawRemaining = tokenizerResult.Remaining;
var result = default(CommandTree);
if (tokens.Count > 0)
{
// Not a command?
var token = tokens.Current;
if (token == null)
{
// Should not happen, but the compiler isn't
// smart enough to realize this...
throw new CommandRuntimeException("Could not get current token.");
}
if (token.TokenKind != CommandTreeToken.Kind.String)
{
// Got a default command?
if (_configuration.DefaultCommand != null)
{
result = ParseCommandParameters(context, _configuration.DefaultCommand, null, tokens);
return new CommandTreeParserResult(
result, new RemainingArguments(context.GetRemainingArguments(), rawRemaining));
}
// Show help?
if (_help?.IsMatch(token.Value) == true)
{
return new CommandTreeParserResult(
null, new RemainingArguments(context.GetRemainingArguments(), rawRemaining));
}
// Unexpected option.
throw CommandParseException.UnexpectedOption(context.Arguments, token);
}
// Does the token value match a command?
var command = _configuration.FindCommand(token.Value, CaseSensitivity);
if (command == null)
{
if (_configuration.DefaultCommand != null)
{
result = ParseCommandParameters(context, _configuration.DefaultCommand, null, tokens);
return new CommandTreeParserResult(
result, new RemainingArguments(context.GetRemainingArguments(), rawRemaining));
}
}
// Parse the command.
result = ParseCommand(context, _configuration, null, tokens);
}
else
{
// Is there a default command?
if (_configuration.DefaultCommand != null)
{
result = ParseCommandParameters(context, _configuration.DefaultCommand, null, tokens);
}
}
return new CommandTreeParserResult(
result, new RemainingArguments(context.GetRemainingArguments(), rawRemaining));
}
private CommandTree ParseCommand(
CommandTreeParserContext context,
ICommandContainer current,
CommandTree? parent,
CommandTreeTokenStream stream)
{
// Find the command.
var commandToken = stream.Consume(CommandTreeToken.Kind.String);
if (commandToken == null)
{
throw new CommandRuntimeException("Could not consume token when parsing command.");
}
var command = current.FindCommand(commandToken.Value, CaseSensitivity);
if (command == null)
{
throw CommandParseException.UnknownCommand(_configuration, parent, context.Arguments, commandToken);
}
return ParseCommandParameters(context, command, parent, stream);
}
private CommandTree ParseCommandParameters(
CommandTreeParserContext context,
CommandInfo command,
CommandTree? parent,
CommandTreeTokenStream stream)
{
context.ResetArgumentPosition();
var node = new CommandTree(parent, command);
while (stream.Peek() != null)
{
var token = stream.Peek();
if (token == null)
{
// Should not happen, but the compiler isn't
// smart enough to realize this...
throw new CommandRuntimeException("Could not get the next token.");
}
switch (token.TokenKind)
{
case CommandTreeToken.Kind.LongOption:
// Long option
ParseOption(context, stream, token, node, true);
break;
case CommandTreeToken.Kind.ShortOption:
// Short option
ParseOption(context, stream, token, node, false);
break;
case CommandTreeToken.Kind.String:
// Command
ParseString(context, stream, node);
break;
case CommandTreeToken.Kind.Remaining:
// Remaining
stream.Consume(CommandTreeToken.Kind.Remaining);
context.State = State.Remaining;
break;
default:
throw new InvalidOperationException($"Encountered unknown token ({token.TokenKind}).");
}
}
// Add unmapped parameters.
foreach (var parameter in node.Command.Parameters)
{
if (node.Mapped.All(m => m.Parameter != parameter))
{
node.Unmapped.Add(parameter);
}
}
return node;
}
private void ParseString(
CommandTreeParserContext context,
CommandTreeTokenStream stream,
CommandTree node)
{
if (context.State == State.Remaining)
{
stream.Consume(CommandTreeToken.Kind.String);
return;
}
var token = stream.Expect(CommandTreeToken.Kind.String);
// Command?
var command = node.Command.FindCommand(token.Value, CaseSensitivity);
if (command != null)
{
if (context.State == State.Normal)
{
node.Next = ParseCommand(context, node.Command, node, stream);
}
return;
}
// Current command has no arguments?
if (!node.HasArguments())
{
throw CommandParseException.UnknownCommand(_configuration, node, context.Arguments, token);
}
// Argument?
var parameter = node.FindArgument(context.CurrentArgumentPosition);
if (parameter == null)
{
// No parameters left. Any commands after this?
if (node.Command.Children.Count > 0 || node.Command.IsDefaultCommand)
{
throw CommandParseException.UnknownCommand(_configuration, node, context.Arguments, token);
}
throw CommandParseException.CouldNotMatchArgument(context.Arguments, token);
}
// Yes, this was an argument.
if (parameter.ParameterKind == ParameterKind.Vector)
{
// Vector
var current = stream.Current;
while (current?.TokenKind == CommandTreeToken.Kind.String)
{
var value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
node.Mapped.Add(new MappedCommandParameter(parameter, value));
current = stream.Current;
}
}
else
{
// Scalar
var value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
node.Mapped.Add(new MappedCommandParameter(parameter, value));
context.IncreaseArgumentPosition();
}
}
private void ParseOption(
CommandTreeParserContext context,
CommandTreeTokenStream stream,
CommandTreeToken token,
CommandTree node,
bool isLongOption)
{
// Consume the option token.
stream.Consume(isLongOption ? CommandTreeToken.Kind.LongOption : CommandTreeToken.Kind.ShortOption);
if (context.State == State.Normal)
{
// 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)));
return;
}
// Help?
if (_help?.IsMatch(token.Value) == true)
{
node.ShowHelp = true;
return;
}
}
if (context.State == State.Remaining)
{
ParseOptionValue(context, stream, token, node, null);
return;
}
if (context.ParsingMode == ParsingMode.Strict)
{
throw CommandParseException.UnknownOption(context.Arguments, token);
}
else
{
ParseOptionValue(context, stream, token, node, null);
}
}
private string? ParseOptionValue(
CommandTreeParserContext context,
CommandTreeTokenStream stream,
CommandTreeToken token,
CommandTree current,
CommandParameter? parameter)
{
var value = default(string);
// Parse the value of the token (if any).
var valueToken = stream.Peek();
if (valueToken?.TokenKind == CommandTreeToken.Kind.String)
{
var parseValue = true;
if (token.TokenKind == CommandTreeToken.Kind.ShortOption && token.IsGrouped)
{
parseValue = false;
}
if (context.State == State.Normal && parseValue)
{
// 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))
{
// Flags cannot be assigned a value.
throw CommandParseException.CannotAssignValueToFlag(context.Arguments, token);
}
}
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
}
else
{
// Unknown parameter value.
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
// In relaxed parsing mode?
if (context.ParsingMode == ParsingMode.Relaxed)
{
context.AddRemainingArgument(token.Value, value);
}
}
}
}
else
{
context.AddRemainingArgument(token.Value, parseValue ? valueToken.Value : null);
}
}
else
{
if (context.State == State.Remaining || context.ParsingMode == ParsingMode.Relaxed)
{
context.AddRemainingArgument(token.Value, null);
}
}
// No value?
if (context.State == State.Normal)
{
if (value == null && parameter != null)
{
if (parameter.ParameterKind == ParameterKind.Flag)
{
value = "true";
}
else
{
if (parameter is CommandOption option)
{
if (parameter.IsFlagValue())
{
return null;
}
throw CommandParseException.OptionHasNoValue(context.Arguments, token, option);
}
else
{
// This should not happen at all. If it does, it's because we've added a new
// option type which isn't a CommandOption for some reason.
throw new InvalidOperationException($"Found invalid parameter type '{parameter.GetType().FullName}'.");
}
}
}
}
return value;
}
}

View File

@ -1,51 +0,0 @@
namespace Spectre.Console.Cli;
internal class CommandTreeParserContext
{
private readonly List<string> _args;
private readonly Dictionary<string, List<string?>> _remaining;
public IReadOnlyList<string> Arguments => _args;
public int CurrentArgumentPosition { get; private set; }
public CommandTreeParser.State State { get; set; }
public ParsingMode ParsingMode { get; }
public CommandTreeParserContext(IEnumerable<string> args, ParsingMode parsingMode)
{
_args = new List<string>(args);
_remaining = new Dictionary<string, List<string?>>(StringComparer.Ordinal);
ParsingMode = parsingMode;
}
public void ResetArgumentPosition()
{
CurrentArgumentPosition = 0;
}
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);
}
}
[SuppressMessage("Style", "IDE0004:Remove Unnecessary Cast", Justification = "Bug in analyzer?")]
public ILookup<string, string?> GetRemainingArguments()
{
return _remaining
.SelectMany(pair => pair.Value, (pair, value) => new { pair.Key, value })
.ToLookup(pair => pair.Key, pair => (string?)pair.value);
}
}

View File

@ -1,14 +0,0 @@
namespace Spectre.Console.Cli;
// Consider removing this in favor for value tuples at some point.
internal sealed class CommandTreeParserResult
{
public CommandTree? Tree { get; }
public IRemainingArguments Remaining { get; }
public CommandTreeParserResult(CommandTree? tree, IRemainingArguments remaining)
{
Tree = tree;
Remaining = remaining;
}
}

Some files were not shown because too many files have changed in this diff Show More