Skip to content

Commit

Permalink
Throw exceptions in Run when building the application.
Browse files Browse the repository at this point in the history
  • Loading branch information
mayuki committed Jan 9, 2022
1 parent 0e366a8 commit 14746de
Show file tree
Hide file tree
Showing 27 changed files with 298 additions and 265 deletions.
73 changes: 73 additions & 0 deletions src/Cocona.Core/Command/CoconaBootstrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Cocona.Application;
using Cocona.Command.Dispatcher;
using Cocona.CommandLine;
using Cocona.Internal;
using Cocona.Resources;

namespace Cocona.Command;

public class CoconaBootstrapper : ICoconaBootstrapper
{
private readonly ICoconaCommandLineArgumentProvider _commandLineArgumentProvider;
private readonly ICoconaCommandProvider _commandProvider;
private readonly ICoconaCommandResolver _commandResolver;
private readonly ICoconaCommandDispatcher _dispatcher;
private readonly ICoconaConsoleProvider _console;

private CommandCollection? _commandCollection;
public CoconaBootstrapper(
ICoconaCommandLineArgumentProvider commandLineArgumentProvider,
ICoconaCommandProvider commandProvider,
ICoconaCommandResolver commandResolver,
ICoconaCommandDispatcher dispatcher,
ICoconaConsoleProvider console
)
{
_commandLineArgumentProvider = commandLineArgumentProvider;
_commandProvider = commandProvider;
_commandResolver = commandResolver;
_dispatcher = dispatcher;
_console = console;
}

public void Initialize()
{
_commandCollection = _commandProvider.GetCommandCollection();
}

public async ValueTask<int> RunAsync(CancellationToken cancellationToken)
{
try
{
var resolved = _commandResolver.ParseAndResolve(_commandCollection ?? throw new CoconaException("Call Initialize before RunAsync"), _commandLineArgumentProvider.GetArguments());
return await _dispatcher.DispatchAsync(resolved, cancellationToken);
}
catch (CommandNotFoundException cmdNotFoundEx)
{
if (string.IsNullOrWhiteSpace(cmdNotFoundEx.Command))
{
_console.Error.WriteLine(string.Format(Strings.Dispatcher_Error_CommandNotFound, cmdNotFoundEx.Message));
}
else
{
_console.Error.WriteLine(string.Format(Strings.Dispatcher_Error_NotACommand, cmdNotFoundEx.Command));
}

var similarCommands = cmdNotFoundEx.ImplementedCommands.All.Where(x => Levenshtein.GetDistance(cmdNotFoundEx.Command.ToLowerInvariant(), x.Name.ToLowerInvariant()) < 3).ToArray();
if (similarCommands.Any())
{
_console.Error.WriteLine();
_console.Error.WriteLine(Strings.Dispatcher_Error_SimilarCommands);
foreach (var c in similarCommands)
{
_console.Error.WriteLine($" {c.Name}");
}
}

return 1;
}
}
}
6 changes: 1 addition & 5 deletions src/Cocona.Core/Command/CoconaCommandResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,20 @@ namespace Cocona.Command
{
public class CoconaCommandResolver : ICoconaCommandResolver
{
private readonly ICoconaCommandProvider _commandProvider;
private readonly ICoconaCommandLineParser _commandLineParser;
private readonly ICoconaCommandMatcher _commandMatcher;

public CoconaCommandResolver(
ICoconaCommandProvider commandProvider,
ICoconaCommandLineParser commandLineParser,
ICoconaCommandMatcher commandMatcher
)
{
_commandProvider = commandProvider;
_commandLineParser = commandLineParser;
_commandMatcher = commandMatcher;
}

public CommandResolverResult ParseAndResolve(IReadOnlyList<string> args)
public CommandResolverResult ParseAndResolve(CommandCollection commandCollection, IReadOnlyList<string> args)
{
var commandCollection = _commandProvider.GetCommandCollection();
var subCommandStack = new List<CommandDescriptor>();

Retry:
Expand Down
21 changes: 7 additions & 14 deletions src/Cocona.Core/Command/Dispatcher/CoconaCommandDispatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,31 @@ namespace Cocona.Command.Dispatcher
public class CoconaCommandDispatcher : ICoconaCommandDispatcher
{
private readonly IServiceProvider _serviceProvider;
private readonly ICoconaCommandLineArgumentProvider _commandLineArgumentProvider;
private readonly ICoconaCommandResolver _commandResolver;
private readonly ICoconaCommandDispatcherPipelineBuilder _dispatcherPipelineBuilder;
private readonly ICoconaInstanceActivator _activator;
private readonly ICoconaAppContextAccessor _appContext;

public CoconaCommandDispatcher(
IServiceProvider serviceProvider,
ICoconaCommandLineArgumentProvider commandLineArgumentProvider,
ICoconaCommandResolver commandResolver,
ICoconaCommandDispatcherPipelineBuilder dispatcherPipelineBuilder,
ICoconaInstanceActivator activator,
ICoconaAppContextAccessor appContext
)
{
_serviceProvider = serviceProvider;
_commandLineArgumentProvider = commandLineArgumentProvider;
_commandResolver = commandResolver;
_dispatcherPipelineBuilder = dispatcherPipelineBuilder;
_activator = activator;
_appContext = appContext;
}

public async ValueTask<int> DispatchAsync(CancellationToken cancellationToken)
public async ValueTask<int> DispatchAsync(CommandResolverResult commandResolverResult, CancellationToken cancellationToken)
{
var result = _commandResolver.ParseAndResolve(_commandLineArgumentProvider.GetArguments());
if (result.Success)
if (commandResolverResult.Success)
{
// Found a command and dispatch.
var parsedCommandLine = result.ParsedCommandLine!;
var matchedCommand = result.MatchedCommand!;
var subCommandStack = result.SubCommandStack!;
var parsedCommandLine = commandResolverResult.ParsedCommandLine!;
var matchedCommand = commandResolverResult.MatchedCommand!;
var subCommandStack = commandResolverResult.SubCommandStack!;

var dispatchAsync = _dispatcherPipelineBuilder.Build();

Expand All @@ -63,7 +56,7 @@ public async ValueTask<int> DispatchAsync(CancellationToken cancellationToken)

// Set CoconaAppContext
_appContext.Current = new CoconaAppContext(matchedCommand, cancellationToken);
_appContext.Current.Features.Set<ICoconaCommandFeature>(new CoconaCommandFeature(result.CommandCollection, matchedCommand, subCommandStack, commandInstance));
_appContext.Current.Features.Set<ICoconaCommandFeature>(new CoconaCommandFeature(commandResolverResult.CommandCollection, matchedCommand, subCommandStack, commandInstance));

// Dispatch the command
try
Expand All @@ -90,7 +83,7 @@ public async ValueTask<int> DispatchAsync(CancellationToken cancellationToken)
{
throw new CommandNotFoundException(
string.Empty,
result.CommandCollection,
commandResolverResult.CommandCollection,
Strings.Command_NotYetImplemented
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System.Threading;
using System.Threading;
using System.Threading.Tasks;

namespace Cocona.Command.Dispatcher
{
public interface ICoconaCommandDispatcher
{
ValueTask<int> DispatchAsync(CancellationToken cancellationToken = default);
ValueTask<int> DispatchAsync(CommandResolverResult commandResolverResult, CancellationToken cancellationToken = default);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using Cocona.Application;
using Cocona.Application;
using Cocona.Command.Binder;
using Cocona.Help;
using Cocona.Internal;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Cocona.Resources;

namespace Cocona.Command.Dispatcher.Middlewares
{
Expand Down Expand Up @@ -40,6 +42,13 @@ public override async ValueTask<int> DispatchAsync(CommandDispatchContext ctx)
}
return exitEx.ExitCode;
}
catch (Exception ex)
{
_console.Error.WriteLine($"Unhandled Exception: {ex.GetType().FullName}: {ex.Message}");
_console.Error.WriteLine(ex.StackTrace);

return 1;
}
}
}
}
14 changes: 14 additions & 0 deletions src/Cocona.Core/Command/ICoconaBootstrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Cocona.Command
{
public interface ICoconaBootstrapper
{
void Initialize();
ValueTask<int> RunAsync(CancellationToken cancellationToken);
}
}
2 changes: 1 addition & 1 deletion src/Cocona.Core/Command/ICoconaCommandResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ namespace Cocona.Command
{
public interface ICoconaCommandResolver
{
CommandResolverResult ParseAndResolve(IReadOnlyList<string> args);
CommandResolverResult ParseAndResolve(CommandCollection commandCollection, IReadOnlyList<string> args);
}
}
27 changes: 27 additions & 0 deletions src/Cocona.Core/Resources/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions src/Cocona.Core/Resources/Strings.ja-jp.resx
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,13 @@
<data name="OptionLikeCommand_MethodNotFound" xml:space="preserve">
<value>オプション風コマンド '{0}' のメソッドが '{1}' に見つかりませんでした。</value>
</data>
<data name="Dispatcher_Error_CommandNotFound" xml:space="preserve">
<value>エラー: {0}</value>
</data>
<data name="Dispatcher_Error_NotACommand" xml:space="preserve">
<value>エラー: '{0}' はコマンドではありません。 '--help' で使用方法を確認できます。</value>
</data>
<data name="Dispatcher_Error_SimilarCommands" xml:space="preserve">
<value>類似するコマンド:</value>
</data>
</root>
9 changes: 9 additions & 0 deletions src/Cocona.Core/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,13 @@
<data name="Help_Description_AllowedValues" xml:space="preserve">
<value>Allowed values</value>
</data>
<data name="Dispatcher_Error_CommandNotFound" xml:space="preserve">
<value>Error: {0}</value>
</data>
<data name="Dispatcher_Error_NotACommand" xml:space="preserve">
<value>Error: '{0}' is not a command. See '--help' for usage.</value>
</data>
<data name="Dispatcher_Error_SimilarCommands" xml:space="preserve">
<value>Similar commands:</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,24 @@ public class CoconaCompletionCandidates : ICoconaCompletionCandidates
private readonly ICoconaCompletionCandidatesMetadataFactory _completionCandidatesMetadataFactory;
private readonly ICoconaCompletionCandidatesProviderFactory _completionCandidatesProviderFactory;
private readonly ICoconaCommandResolver _commandResolver;
private readonly ICoconaCommandProvider _commandProvider;

public CoconaCompletionCandidates(
ICoconaCompletionCandidatesMetadataFactory completionCandidatesMetadataFactory,
ICoconaCompletionCandidatesProviderFactory completionCandidatesProviderFactory,
ICoconaCommandResolver commandResolver
ICoconaCommandResolver commandResolver,
ICoconaCommandProvider commandProvider
)
{
_completionCandidatesMetadataFactory = completionCandidatesMetadataFactory;
_completionCandidatesProviderFactory = completionCandidatesProviderFactory;
_commandResolver = commandResolver;
_commandProvider = commandProvider;
}

public IReadOnlyList<CompletionCandidateValue> GetOnTheFlyCandidates(string paramName, IReadOnlyList<string> args, int curPos, string? candidateHint)
{
var result = _commandResolver.ParseAndResolve(args);
var result = _commandResolver.ParseAndResolve(_commandProvider.GetCommandCollection(), args);

if (result.Success)
{
Expand Down
5 changes: 0 additions & 5 deletions src/Cocona.Lite/CoconaLiteAppOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ namespace Cocona
/// </summary>
public class CoconaLiteAppOptions
{
/// <summary>
/// Sets or gets whether Cocona or a command throws an exception, handle it and exit normally. The default value is true.
/// </summary>
public bool HandleExceptionAtRuntime { get; set; } = true;

/// <summary>
/// If the type has public methods, Cocona treats as a command. The default value is true.
/// </summary>
Expand Down
Loading

0 comments on commit 14746de

Please sign in to comment.