Skip to content

Commit

Permalink
ITypeResolver is now implicitly registered to itself & Merge TypeReso…
Browse files Browse the repository at this point in the history
…lver check
  • Loading branch information
JKamsker committed Sep 24, 2023
1 parent a911355 commit 20ea81b
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 93 deletions.
7 changes: 7 additions & 0 deletions examples/Cli/AutoCompletion/Program.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics;
using Spectre.Console.Cli;

namespace AutoCompletion;
Expand All @@ -14,6 +15,12 @@ internal static class Program
{
private static void Main(string[] args)
{
// If we just want to test the completion with f5 in visual studio
if (Debugger.IsAttached)
{
args = new[] { "cli", "complete", "\"Li\"" };
}

var app = new CommandApp();
app.Configure(config => config.AddCommand<LionCommand>("lion"));

Expand Down
187 changes: 102 additions & 85 deletions src/Spectre.Console.Cli/Internal/CommandExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,75 +8,75 @@ public CommandExecutor(ITypeRegistrar registrar)
{
_registrar = registrar ?? throw new ArgumentNullException(nameof(registrar));
_registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor));
}

}

public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}

args ??= new List<string>();

_registrar.RegisterInstance(typeof(IConfiguration), configuration);
_registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole());

// Register the help provider
var defaultHelpProvider = new HelpProvider(configuration.Settings);
_registrar.RegisterInstance(typeof(IHelpProvider), defaultHelpProvider);
}

args ??= new List<string>();

_registrar.RegisterInstance(typeof(IConfiguration), configuration);
_registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole());

// Register the help provider
var defaultHelpProvider = new HelpProvider(configuration.Settings);
_registrar.RegisterInstance(typeof(IHelpProvider), defaultHelpProvider);

// Create the command model.
var model = CommandModelBuilder.Build(configuration);
_registrar.RegisterInstance(typeof(CommandModel), model);
_registrar.RegisterDependencies(model);

// Asking for version? Kind of a hack, but it's alright.
// We should probably make this a bit better in the future.
if (args.Contains("-v") || args.Contains("--version"))
{
var console = configuration.Settings.Console.GetConsole();
console.WriteLine(ResolveApplicationVersion(configuration));
return 0;
}
_registrar.RegisterDependencies(model);

// Asking for version? Kind of a hack, but it's alright.
// We should probably make this a bit better in the future.
if (args.Contains("-v") || args.Contains("--version"))
{
var console = configuration.Settings.Console.GetConsole();
console.WriteLine(ResolveApplicationVersion(configuration));
return 0;
}

// Parse and map the model against the arguments.
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args);

// Register the arguments with the container.
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args);

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

// Create the resolver.
using (var resolver = new TypeResolverAdapter(_registrar.Build()))
{
// Get the registered help provider, falling back to the default provider
// registered above if no custom implementations have been registered.
var helpProvider = resolver.Resolve(typeof(IHelpProvider)) as IHelpProvider ?? defaultHelpProvider;

// Currently the root?
if (parsedResult?.Tree == null)
{
// Display help.
configuration.Settings.Console.SafeRender(helpProvider.Write(model, null));
return 0;
}

// Get the command to execute.
var leaf = parsedResult.Tree.GetLeafCommand();
if (leaf.Command.IsBranch || leaf.ShowHelp)
{
// Branches can't be executed. Show help.
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
return leaf.ShowHelp ? 0 : 1;
}

// Is this the default and is it called without arguments when there are required arguments?
if (leaf.Command.IsDefaultCommand && args.Count() == 0 && leaf.Command.Parameters.Any(p => p.Required))
{
// Display help for default command.
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
return 1;
using (var resolver = MakeTypeResolverAdapter(_registrar))
{
// Get the registered help provider, falling back to the default provider
// registered above if no custom implementations have been registered.
var helpProvider = resolver.Resolve(typeof(IHelpProvider)) as IHelpProvider ?? defaultHelpProvider;

// Currently the root?
if (parsedResult?.Tree == null)
{
// Display help.
configuration.Settings.Console.SafeRender(helpProvider.Write(model, null));
return 0;
}

// Get the command to execute.
var leaf = parsedResult.Tree.GetLeafCommand();
if (leaf.Command.IsBranch || leaf.ShowHelp)
{
// Branches can't be executed. Show help.
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
return leaf.ShowHelp ? 0 : 1;
}

// Is this the default and is it called without arguments when there are required arguments?
if (leaf.Command.IsDefaultCommand && args.Count() == 0 && leaf.Command.Parameters.Any(p => p.Required))
{
// Display help for default command.
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
return 1;
}

// Create the content.
Expand All @@ -85,38 +85,55 @@ public async Task<int> Execute(IConfiguration configuration, IEnumerable<string>
// Execute the command tree.
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
}
}

#pragma warning disable CS8603 // Possible null reference return.
private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IEnumerable<string> args)
{
var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments);

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

var lastParsedLeaf = parsedResult?.Tree?.GetLeafCommand();
var lastParsedCommand = lastParsedLeaf?.Command;
if (lastParsedLeaf != null && lastParsedCommand != null &&
lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp &&
}

#pragma warning disable CS8603 // Possible null reference return.
private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IEnumerable<string> args)
{
var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments);

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

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

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

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

return parsedResult;
}
#pragma warning restore CS8603 // Possible null reference return.

{
// 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;
}

/// <summary>
/// Builds a TypeResolver where the ITypeResolver service is registered to itself.
/// This only works when the DI framework is not as bad as the primitive one this library uses internally.
/// In case the internal one is used, we have a fallback that uses the built resolver directly.
/// </summary>
private TypeResolverAdapter MakeTypeResolverAdapter(ITypeRegistrar registrar)
{
var emptyResolver = new TypeResolverAdapter(null);
registrar.RegisterInstance(typeof(ITypeResolver), emptyResolver);

var builtResolver = registrar.Build();
emptyResolver.SetTypeResolver(builtResolver);

return emptyResolver;
}

#pragma warning restore CS8603 // Possible null reference return.

private static string ResolveApplicationVersion(IConfiguration configuration)
{
return
Expand Down
11 changes: 4 additions & 7 deletions src/Spectre.Console.Cli/Internal/Composition/Activators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ public override object Activate(DefaultTypeResolver container)
for (var i = 0; i < _parameters.Count; i++)
{
var parameter = _parameters[i];
if (parameter.ParameterType == typeof(DefaultTypeResolver))
var isTypeResolver = parameter.ParameterType == typeof(ITypeResolver)
|| parameter.ParameterType == typeof(DefaultTypeResolver);

if (isTypeResolver)
{
parameters[i] = container;
}
Expand All @@ -82,12 +85,6 @@ public override object Activate(DefaultTypeResolver container)
var resolved = container.Resolve(parameter.ParameterType);
if (resolved == null)
{
if (parameter.ParameterType == typeof(ITypeResolver))
{
parameters[i] = container;
continue;
}

if (!parameter.IsOptional)
{
throw new InvalidOperationException($"Could not find registration for '{parameter.ParameterType.FullName}'.");
Expand Down
12 changes: 11 additions & 1 deletion src/Spectre.Console.Cli/Internal/TypeResolverAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ namespace Spectre.Console.Cli;

internal sealed class TypeResolverAdapter : ITypeResolver, IDisposable
{
private readonly ITypeResolver? _resolver;
private ITypeResolver? _resolver;

public TypeResolverAdapter(ITypeResolver? resolver)
{
Expand Down Expand Up @@ -44,4 +44,14 @@ public void Dispose()
disposable.Dispose();
}
}

public void SetTypeResolver(ITypeResolver resolver)
{
if (_resolver is not null)
{
throw new InvalidOperationException("Cannot set type resolver more than once.");
}

_resolver = resolver;
}
}

0 comments on commit 20ea81b

Please sign in to comment.