Skip to content

Commit

Permalink
Merge branch 'spectreconsole:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxAtoms committed Apr 3, 2023
2 parents 1334319 + 714cf17 commit 4dcbd30
Show file tree
Hide file tree
Showing 24 changed files with 1,052 additions and 269 deletions.
10 changes: 9 additions & 1 deletion src/Spectre.Console.Cli/ICommandAppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,15 @@ public interface ICommandAppSettings
/// <summary>
/// Gets or sets a value indicating whether or not parsing is strict.
/// </summary>
bool StrictParsing { get; set; }
bool StrictParsing { get; set; }

/// <summary>
/// Gets or sets a value indicating whether or not flags found on the commnd line
/// that would normally result in a <see cref="CommandParseException"/> being thrown
/// during parsing with the message "Flags cannot be assigned a value."
/// should instead be added to the remaining arguments collection.
/// </summary>
bool ConvertFlagsToRemainingArguments { get; set; }

/// <summary>
/// Gets or sets a value indicating whether or not exceptions should be propagated.
Expand Down
2 changes: 1 addition & 1 deletion src/Spectre.Console.Cli/IConfigurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public interface IConfigurator
/// </summary>
/// <param name="args">The example arguments.</param>
void AddExample(string[] args);


/// <summary>
/// Adds a command.
/// </summary>
Expand Down
12 changes: 12 additions & 0 deletions src/Spectre.Console.Cli/IConfiguratorOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ public interface IConfigurator<in TSettings>
/// <param name="args">The example arguments.</param>
void AddExample(string[] args);

/// <summary>
/// Adds a default command.
/// </summary>
/// <remarks>
/// This is the command that will run if the user doesn't specify one on the command line.
/// It must be able to execute successfully by itself ie. without requiring any command line
/// arguments, flags or option values.
/// </remarks>
/// <typeparam name="TDefaultCommand">The default command type.</typeparam>
void SetDefaultCommand<TDefaultCommand>()
where TDefaultCommand : class, ICommandLimiter<TSettings>;

/// <summary>
/// Marks the branch as hidden.
/// Hidden branches do not show up in help documentation or
Expand Down
35 changes: 31 additions & 4 deletions src/Spectre.Console.Cli/Internal/CommandExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,10 @@ public async Task<int> Execute(IConfiguration configuration, IEnumerable<string>
}

// Parse and map the model against the arguments.
var parser = new CommandTreeParser(model, configuration.Settings);
var parsedResult = parser.Parse(args);
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args);

// Currently the root?
if (parsedResult.Tree == null)
if (parsedResult?.Tree == null)
{
// Display help.
configuration.Settings.Console.SafeRender(HelpWriter.Write(model, configuration.Settings.ShowOptionDefaultValues));
Expand All @@ -75,6 +73,7 @@ public async Task<int> Execute(IConfiguration configuration, IEnumerable<string>
}

// Register the arguments with the container.
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);

// Create the resolver and the context.
Expand All @@ -86,6 +85,34 @@ public async Task<int> Execute(IConfiguration configuration, IEnumerable<string>
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
}
}

private CommandTreeParserResult? ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IEnumerable<string> args)
{
var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments);

var parserContext = new CommandTreeParserContext(args, settings.ParsingMode);
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
var parsedResult = parser.Parse(parserContext, tokenizerResult);

var lastParsedLeaf = parsedResult?.Tree?.GetLeafCommand();
var lastParsedCommand = lastParsedLeaf?.Command;
if (lastParsedLeaf != null && lastParsedCommand != null &&
lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp &&
lastParsedCommand.DefaultCommand != null)
{
// Insert this branch's default command into the command line
// arguments and try again to see if it will parse.
var argsWithDefaultCommand = new List<string>(args);

argsWithDefaultCommand.Insert(tokenizerResult.Tokens.Position, lastParsedCommand.DefaultCommand.Name);

parserContext = new CommandTreeParserContext(argsWithDefaultCommand, settings.ParsingMode);
tokenizerResult = CommandTreeTokenizer.Tokenize(argsWithDefaultCommand);
parsedResult = parser.Parse(parserContext, tokenizerResult);
}

return parsedResult;
}

private static string ResolveApplicationVersion(IConfiguration configuration)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ internal sealed class CommandAppSettings : ICommandAppSettings
public bool PropagateExceptions { get; set; }
public bool ValidateExamples { get; set; }
public bool TrimTrailingPeriod { get; set; } = true;
public bool StrictParsing { get; set; }
public bool StrictParsing { get; set; }
public bool ConvertFlagsToRemainingArguments { get; set; } = false;

public ParsingMode ParsingMode =>
StrictParsing ? ParsingMode.Strict : ParsingMode.Relaxed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public Configurator(ITypeRegistrar registrar)
public void AddExample(string[] args)
{
Examples.Add(args);
}
}

public ConfiguredCommand SetDefaultCommand<TDefaultCommand>()
where TDefaultCommand : class, ICommand
Expand All @@ -36,7 +36,7 @@ public ConfiguredCommand SetDefaultCommand<TDefaultCommand>()
public ICommandConfigurator AddCommand<TCommand>(string name)
where TCommand : class, ICommand
{
var command = Commands.AddAndReturn(ConfiguredCommand.FromType<TCommand>(name, false));
var command = Commands.AddAndReturn(ConfiguredCommand.FromType<TCommand>(name, isDefaultCommand: false));
return new CommandConfigurator(command);
}

Expand Down
190 changes: 99 additions & 91 deletions src/Spectre.Console.Cli/Internal/Configuration/ConfiguratorOfT.cs
Original file line number Diff line number Diff line change
@@ -1,92 +1,100 @@
namespace Spectre.Console.Cli;

internal sealed class Configurator<TSettings> : IUnsafeBranchConfigurator, IConfigurator<TSettings>
where TSettings : CommandSettings
{
private readonly ConfiguredCommand _command;
private readonly ITypeRegistrar? _registrar;

public Configurator(ConfiguredCommand command, ITypeRegistrar? registrar)
{
_command = command;
_registrar = registrar;
}

public void SetDescription(string description)
{
_command.Description = description;
}

public void AddExample(string[] args)
{
_command.Examples.Add(args);
}

public void HideBranch()
{
_command.IsHidden = true;
}

public ICommandConfigurator AddCommand<TCommand>(string name)
where TCommand : class, ICommandLimiter<TSettings>
{
var command = ConfiguredCommand.FromType<TCommand>(name);
var configurator = new CommandConfigurator(command);

_command.Children.Add(command);
return configurator;
}

public ICommandConfigurator AddDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, int> func)
where TDerivedSettings : TSettings
{
var command = ConfiguredCommand.FromDelegate<TDerivedSettings>(
name, (context, settings) => func(context, (TDerivedSettings)settings));

_command.Children.Add(command);
return new CommandConfigurator(command);
}

public IBranchConfigurator AddBranch<TDerivedSettings>(string name, Action<IConfigurator<TDerivedSettings>> action)
where TDerivedSettings : TSettings
{
var command = ConfiguredCommand.FromBranch<TDerivedSettings>(name);
action(new Configurator<TDerivedSettings>(command, _registrar));
var added = _command.Children.AddAndReturn(command);
return new BranchConfigurator(added);
}

ICommandConfigurator IUnsafeConfigurator.AddCommand(string name, Type command)
{
var method = GetType().GetMethod("AddCommand");
if (method == null)
{
throw new CommandConfigurationException("Could not find AddCommand by reflection.");
}

method = method.MakeGenericMethod(command);

if (!(method.Invoke(this, new object[] { name }) is ICommandConfigurator result))
{
throw new CommandConfigurationException("Invoking AddCommand returned null.");
}

return result;
}

IBranchConfigurator IUnsafeConfigurator.AddBranch(string name, Type settings, Action<IUnsafeBranchConfigurator> action)
{
var command = ConfiguredCommand.FromBranch(settings, name);

// Create the configurator.
var configuratorType = typeof(Configurator<>).MakeGenericType(settings);
if (!(Activator.CreateInstance(configuratorType, new object?[] { command, _registrar }) is IUnsafeBranchConfigurator configurator))
{
throw new CommandConfigurationException("Could not create configurator by reflection.");
}

action(configurator);
var added = _command.Children.AddAndReturn(command);
return new BranchConfigurator(added);
}
namespace Spectre.Console.Cli;

internal sealed class Configurator<TSettings> : IUnsafeBranchConfigurator, IConfigurator<TSettings>
where TSettings : CommandSettings
{
private readonly ConfiguredCommand _command;
private readonly ITypeRegistrar? _registrar;

public Configurator(ConfiguredCommand command, ITypeRegistrar? registrar)
{
_command = command;
_registrar = registrar;
}

public void SetDescription(string description)
{
_command.Description = description;
}

public void AddExample(string[] args)
{
_command.Examples.Add(args);
}

public void SetDefaultCommand<TDefaultCommand>()
where TDefaultCommand : class, ICommandLimiter<TSettings>
{
var defaultCommand = ConfiguredCommand.FromType<TDefaultCommand>(
CliConstants.DefaultCommandName, isDefaultCommand: true);
_command.Children.Add(defaultCommand);
}

public void HideBranch()
{
_command.IsHidden = true;
}

public ICommandConfigurator AddCommand<TCommand>(string name)
where TCommand : class, ICommandLimiter<TSettings>
{
var command = ConfiguredCommand.FromType<TCommand>(name, isDefaultCommand: false);
var configurator = new CommandConfigurator(command);

_command.Children.Add(command);
return configurator;
}

public ICommandConfigurator AddDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, int> func)
where TDerivedSettings : TSettings
{
var command = ConfiguredCommand.FromDelegate<TDerivedSettings>(
name, (context, settings) => func(context, (TDerivedSettings)settings));

_command.Children.Add(command);
return new CommandConfigurator(command);
}

public IBranchConfigurator AddBranch<TDerivedSettings>(string name, Action<IConfigurator<TDerivedSettings>> action)
where TDerivedSettings : TSettings
{
var command = ConfiguredCommand.FromBranch<TDerivedSettings>(name);
action(new Configurator<TDerivedSettings>(command, _registrar));
var added = _command.Children.AddAndReturn(command);
return new BranchConfigurator(added);
}

ICommandConfigurator IUnsafeConfigurator.AddCommand(string name, Type command)
{
var method = GetType().GetMethod("AddCommand");
if (method == null)
{
throw new CommandConfigurationException("Could not find AddCommand by reflection.");
}

method = method.MakeGenericMethod(command);

if (!(method.Invoke(this, new object[] { name }) is ICommandConfigurator result))
{
throw new CommandConfigurationException("Invoking AddCommand returned null.");
}

return result;
}

IBranchConfigurator IUnsafeConfigurator.AddBranch(string name, Type settings, Action<IUnsafeBranchConfigurator> action)
{
var command = ConfiguredCommand.FromBranch(settings, name);

// Create the configurator.
var configuratorType = typeof(Configurator<>).MakeGenericType(settings);
if (!(Activator.CreateInstance(configuratorType, new object?[] { command, _registrar }) is IUnsafeBranchConfigurator configurator))
{
throw new CommandConfigurationException("Could not create configurator by reflection.");
}

action(configurator);
var added = _command.Children.AddAndReturn(command);
return new BranchConfigurator(added);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ private ConfiguredCommand(
CommandType = commandType;
SettingsType = settingsType;
Delegate = @delegate;
IsDefaultCommand = isDefaultCommand;
IsDefaultCommand = isDefaultCommand;

// Default commands are always created as hidden.
IsHidden = IsDefaultCommand;

Children = new List<ConfiguredCommand>();
Examples = new List<string[]>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ public static void RegisterDependencies(this ITypeRegistrar registrar, CommandMo
{
var stack = new Stack<CommandInfo>();
model.Commands.ForEach(c => stack.Push(c));
if (model.DefaultCommand != null)
{
stack.Push(model.DefaultCommand);
}

while (stack.Count > 0)
{
Expand Down
9 changes: 6 additions & 3 deletions src/Spectre.Console.Cli/Internal/Modelling/CommandInfo.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace Spectre.Console.Cli;

internal sealed class CommandInfo : ICommandContainer
{
{
public string Name { get; }
public HashSet<string> Aliases { get; }
public string? Description { get; }
Expand All @@ -10,14 +10,17 @@ internal sealed class CommandInfo : ICommandContainer
public Type SettingsType { get; }
public Func<CommandContext, CommandSettings, int>? Delegate { get; }
public bool IsDefaultCommand { get; }
public bool IsHidden { get; }
public CommandInfo? Parent { get; }
public IList<CommandInfo> Children { get; }
public IList<CommandParameter> Parameters { get; }
public IList<string[]> Examples { get; }

public bool IsBranch => CommandType == null && Delegate == null;
IList<CommandInfo> ICommandContainer.Commands => Children;
IList<CommandInfo> ICommandContainer.Commands => Children;

// only branches can have a default command
public CommandInfo? DefaultCommand => IsBranch ? Children.FirstOrDefault(c => c.IsDefaultCommand) : null;
public bool IsHidden { get; }

public CommandInfo(CommandInfo? parent, ConfiguredCommand prototype)
{
Expand Down
5 changes: 2 additions & 3 deletions src/Spectre.Console.Cli/Internal/Modelling/CommandModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,20 @@ internal sealed class CommandModel : ICommandContainer
{
public string? ApplicationName { get; }
public ParsingMode ParsingMode { get; }
public CommandInfo? DefaultCommand { get; }
public IList<CommandInfo> Commands { get; }
public IList<string[]> Examples { get; }
public bool TrimTrailingPeriod { get; }

public CommandInfo? DefaultCommand => Commands.FirstOrDefault(c => c.IsDefaultCommand);

public CommandModel(
CommandAppSettings settings,
CommandInfo? defaultCommand,
IEnumerable<CommandInfo> commands,
IEnumerable<string[]> examples)
{
ApplicationName = settings.ApplicationName;
ParsingMode = settings.ParsingMode;
TrimTrailingPeriod = settings.TrimTrailingPeriod;
DefaultCommand = defaultCommand;
Commands = new List<CommandInfo>(commands ?? Array.Empty<CommandInfo>());
Examples = new List<string[]>(examples ?? Array.Empty<string[]>());
}
Expand Down
Loading

0 comments on commit 4dcbd30

Please sign in to comment.