Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Core.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertSwitchStatementToSwitchExpression/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToAutoPropertyWhenPossible/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToAutoPropertyWithPrivateSetter/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToExtensionBlock/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToLambdaExpressionWhenPossible/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToUsingDeclaration/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=EnforceDoWhileStatementBraces/@EntryIndexedValue">WARNING</s:String>
Expand Down
4 changes: 4 additions & 0 deletions samples/CliHostSampleApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ private static ICliHost CreateCliHost()
{
return CliHostBuilder.Create()
.EnableHelp(HelpCommandKind.CommandOrArgument)
.PrintHeaderText(["[red]This is a sample cli host[/]"])
Comment thread
darthsharp marked this conversation as resolved.
.PrintHeaderMarkup(["[green]This is a second pre processor[/]"])
.PrintFooterText(["Bye bye"])
.PrintFooterMarkup(["[red]This is a second post processor[/]"])
.Build();
}
}
2 changes: 0 additions & 2 deletions source/Cli/CreativeCoders.Cli.Core/CliCommandAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,4 @@ public class CliCommandAttribute(string[] commands) : Attribute
public string Description { get; set; } = string.Empty;

public string[] AlternativeCommands { get; init; } = [];

public bool IsDefaultCommand { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace CreativeCoders.Cli.Core;

public enum CliProcessorExecutionCondition
{
Always,
OnlyOnHelp,
OnlyOnCommand
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using JetBrains.Annotations;

namespace CreativeCoders.Cli.Hosting;
namespace CreativeCoders.Cli.Core;

[PublicAPI]
public class CliResult(int exitCode)
Expand Down
11 changes: 11 additions & 0 deletions source/Cli/CreativeCoders.Cli.Core/ICliPostProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using JetBrains.Annotations;

namespace CreativeCoders.Cli.Core;

[PublicAPI]
public interface ICliPostProcessor
{
Task ExecuteAsync(CliResult cliResult);

CliProcessorExecutionCondition ExecutionCondition { get; }
}
11 changes: 11 additions & 0 deletions source/Cli/CreativeCoders.Cli.Core/ICliPreProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using JetBrains.Annotations;

namespace CreativeCoders.Cli.Core;

[PublicAPI]
public interface ICliPreProcessor
{
Task ExecuteAsync(string[] args);

CliProcessorExecutionCondition ExecutionCondition { get; }
}
4 changes: 4 additions & 0 deletions source/Cli/CreativeCoders.Cli.Hosting/CliExitCodes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ public static class CliExitCodes
public const int CommandResultUnknown = int.MinValue + 2;

public const int CommandOptionsInvalid = int.MinValue + 3;

public const int PreProcessorFailed = int.MinValue + 4;

public const int PostProcessorFailed = int.MinValue + 5;
}
125 changes: 125 additions & 0 deletions source/Cli/CreativeCoders.Cli.Hosting/CliHostBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using System.Diagnostics.CodeAnalysis;
using CreativeCoders.Cli.Core;
using CreativeCoders.Cli.Hosting.PreProcessors;

namespace CreativeCoders.Cli.Hosting;

[ExcludeFromCodeCoverage]
public static class CliHostBuilderExtensions
{
/// <summary>
/// Registers a pre-processor to print a header as plain text. The header is displayed when the CLI application
/// is executed, before the command processing, based on the specified execution condition.
/// </summary>
/// <param name="builder">
/// The <see cref="ICliHostBuilder"/> instance to which the pre-processor is being added.
/// </param>
/// <param name="lines">
/// An enumerable collection of strings representing the lines of text to be displayed in the header.
/// Each string represents an individual line.
/// </param>
/// <param name="executionCondition">
/// A value from <see cref="CliProcessorExecutionCondition"/> that determines when the header should be displayed.
/// Defaults to <see cref="CliProcessorExecutionCondition.Always"/>.
/// </param>
/// <returns>
/// The <see cref="ICliHostBuilder"/> instance for method chaining.
/// </returns>
public static ICliHostBuilder PrintHeaderText(this ICliHostBuilder builder, IEnumerable<string> lines,
CliProcessorExecutionCondition executionCondition = CliProcessorExecutionCondition.Always)
{
return builder.RegisterPreProcessor<PrintHeaderPreProcessor>(x =>
{
x.Lines = lines;
x.PlainText = true;
x.ExecutionCondition = executionCondition;
});
}

/// <summary>
/// Registers a pre-processor to print a header using markup text. The header is displayed during the CLI application
/// execution, before the command processing, based on the specified execution condition.
/// </summary>
/// <param name="builder">
/// The <see cref="ICliHostBuilder"/> instance to which the pre-processor is being added.
/// </param>
/// <param name="lines">
/// An enumerable collection of strings representing the lines of markup text to be displayed in the header.
/// The markup can include formatting instructions that are interpreted during rendering.
/// </param>
/// <param name="executionCondition">
/// A value from <see cref="CliProcessorExecutionCondition"/> that determines when the header should be displayed.
/// Defaults to <see cref="CliProcessorExecutionCondition.Always"/>.
/// </param>
/// <returns>
/// The <see cref="ICliHostBuilder"/> instance for method chaining.
/// </returns>
public static ICliHostBuilder PrintHeaderMarkup(this ICliHostBuilder builder, IEnumerable<string> lines,
CliProcessorExecutionCondition executionCondition = CliProcessorExecutionCondition.Always)
{
return builder.RegisterPreProcessor<PrintHeaderPreProcessor>(x =>
{
x.Lines = lines;
x.PlainText = false;
x.ExecutionCondition = executionCondition;
});
}

/// <summary>
/// Registers a post-processor to print a footer as plain text. The footer is displayed after the command processing
/// is complete, based on the specified execution condition.
/// </summary>
/// <param name="builder">
/// The <see cref="ICliHostBuilder"/> instance to which the post-processor is being added.
/// </param>
/// <param name="lines">
/// An enumerable collection of strings representing the lines of text to be displayed in the footer.
/// Each string represents an individual line.
/// </param>
/// <param name="executionCondition">
/// A value from <see cref="CliProcessorExecutionCondition"/> that determines when the footer should be displayed.
/// Defaults to <see cref="CliProcessorExecutionCondition.Always"/>.
/// </param>
/// <returns>
/// The <see cref="ICliHostBuilder"/> instance for method chaining.
/// </returns>
public static ICliHostBuilder PrintFooterText(this ICliHostBuilder builder, IEnumerable<string> lines,
CliProcessorExecutionCondition executionCondition = CliProcessorExecutionCondition.Always)
{
return builder.RegisterPostProcessor<PrintFooterPostProcessor>(x =>
{
x.Lines = lines;
x.PlainText = true;
x.ExecutionCondition = executionCondition;
});
}

/// <summary>
/// Registers a post-processor to print a footer using markup formatting. The footer is displayed
/// after the command execution, based on the specified execution condition.
/// </summary>
/// <param name="builder">
/// The <see cref="ICliHostBuilder"/> instance to which the post-processor is being added.
/// </param>
/// <param name="lines">
/// An enumerable collection of strings representing the lines of footer text to be displayed.
/// Each string supports markup formatting for better visual representation.
/// </param>
/// <param name="executionCondition">
/// A value from <see cref="CliProcessorExecutionCondition"/> that determines when the footer should be displayed.
/// Defaults to <see cref="CliProcessorExecutionCondition.Always"/>.
/// </param>
/// <returns>
/// The <see cref="ICliHostBuilder"/> instance for method chaining.
/// </returns>
public static ICliHostBuilder PrintFooterMarkup(this ICliHostBuilder builder, IEnumerable<string> lines,
CliProcessorExecutionCondition executionCondition = CliProcessorExecutionCondition.Always)
{
return builder.RegisterPostProcessor<PrintFooterPostProcessor>(x =>
{
x.Lines = lines;
x.PlainText = false;
x.ExecutionCondition = executionCondition;
});
}
}
6 changes: 6 additions & 0 deletions source/Cli/CreativeCoders.Cli.Hosting/CliHostSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace CreativeCoders.Cli.Hosting;

public class CliHostSettings
{
public bool UseValidation { get; init; }
}
98 changes: 90 additions & 8 deletions source/Cli/CreativeCoders.Cli.Hosting/DefaultCliHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ public class DefaultCliHost(
IAnsiConsole ansiConsole,
ICliCommandStore commandStore,
IServiceProvider serviceProvider,
ICliCommandHelpHandler commandHelpHandler)
ICliCommandHelpHandler commandHelpHandler,
IEnumerable<ICliPreProcessor> preProcessors,
IEnumerable<ICliPostProcessor> postProcessors)
: ICliHost
{
private readonly IServiceProvider _serviceProvider = Ensure.NotNull(serviceProvider);
Expand Down Expand Up @@ -110,18 +112,26 @@ public async Task<CliResult> RunAsync(string[] args)
{
if (_commandHelpHandler.ShouldPrintHelp(args))
{
await ExecuteHelpPreProcessorsAsync(args).ConfigureAwait(false);

_commandHelpHandler.PrintHelp(args);

return new CliResult(CliExitCodes.Success);
var cliHelpResult = new CliResult(CliExitCodes.Success);

return await ExecuteHelpPostProcessorsAsync(cliHelpResult).ConfigureAwait(false);
}

await ExecuteCommandPreProcessorsAsync(args).ConfigureAwait(false);

var (command, optionsArgs, commandInfo) = CreateCliCommand(args);

var commandContext = _serviceProvider.GetRequiredService<ICliCommandContext>();
commandContext.AllArgs = args;
commandContext.OptionsArgs = optionsArgs;

return await ExecuteAsync(commandInfo, command, optionsArgs).ConfigureAwait(false);
var cliResult = await ExecuteAsync(commandInfo, command, optionsArgs).ConfigureAwait(false);

return await ExecuteCommandPostProcessorsAsync(cliResult).ConfigureAwait(false);
}
catch (CliCommandConstructionFailedException e)
{
Expand Down Expand Up @@ -153,6 +163,83 @@ public async Task<CliResult> RunAsync(string[] args)

return new CliResult(e.ExitCode);
}
catch (CliExitException e)
{
_ansiConsole.MarkupLine($"[red]{e.Message}[/]");

return new CliResult(e.ExitCode);
}
}

private async Task ExecuteHelpPreProcessorsAsync(string[] args)
{
CliProcessorExecutionCondition[] conditions =
[CliProcessorExecutionCondition.OnlyOnHelp, CliProcessorExecutionCondition.Always];

await ExecutePreProcessorsAsync(preProcessors, conditions, args)
.ConfigureAwait(false);
}

private async Task ExecuteCommandPreProcessorsAsync(string[] args)
{
CliProcessorExecutionCondition[] conditions =
[CliProcessorExecutionCondition.OnlyOnCommand, CliProcessorExecutionCondition.Always];

await ExecutePreProcessorsAsync(preProcessors, conditions, args)
.ConfigureAwait(false);
}

private static async Task ExecutePreProcessorsAsync(IEnumerable<ICliPreProcessor> preProcessors,
CliProcessorExecutionCondition[] conditions, string[] args)
{
foreach (var preProcessor in preProcessors.Where(x => conditions.Contains(x.ExecutionCondition)))
{
try
{
await preProcessor.ExecuteAsync(args).ConfigureAwait(false);
}
catch (Exception e)
{
throw new CliPreProcessorException(preProcessor,
$"PreProcessor execution failed: {e.Message}", e);
}
}
}

private async Task<CliResult> ExecuteCommandPostProcessorsAsync(CliResult cliResult)
{
CliProcessorExecutionCondition[] conditions =
[CliProcessorExecutionCondition.OnlyOnCommand, CliProcessorExecutionCondition.Always];

return await ExecutePostProcessorsAsync(postProcessors, conditions, cliResult).ConfigureAwait(false);
}

private async Task<CliResult> ExecuteHelpPostProcessorsAsync(CliResult cliResult)
{
CliProcessorExecutionCondition[] conditions =
[CliProcessorExecutionCondition.OnlyOnHelp, CliProcessorExecutionCondition.Always];

return await ExecutePostProcessorsAsync(postProcessors, conditions, cliResult).ConfigureAwait(false);
}

private static async Task<CliResult> ExecutePostProcessorsAsync(
IEnumerable<ICliPostProcessor> postProcessors, CliProcessorExecutionCondition[] conditions,
CliResult cliResult)
{
foreach (var postProcessor in postProcessors.Where(x => conditions.Contains(x.ExecutionCondition)))
{
try
{
await postProcessor.ExecuteAsync(cliResult).ConfigureAwait(false);
}
catch (Exception e)
{
throw new CliPostProcessorException(postProcessor,
$"PostProcessor execution failed: {e.Message}", e);
}
}

return cliResult;
}

/// <inheritdoc />
Expand Down Expand Up @@ -181,8 +268,3 @@ private void PrintNearestMatch(string[] args)
_commandHelpHandler.PrintHelpFor(findCommandGroupNodeResult.Node.ChildNodes);
}
}

public class CliHostSettings
{
public bool UseValidation { get; init; }
}
Loading