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
13 changes: 13 additions & 0 deletions source/Cli/CreativeCoders.Cli.Core/CommandResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@

namespace CreativeCoders.Cli.Core;

/// <summary>
/// Represents the result of executing a command.
/// </summary>
[PublicAPI]
public class CommandResult
{
/// <summary>
/// Represents a successful result of a command execution with an implicit default exit code of 0.
/// </summary>
public static CommandResult Success { get; } = new CommandResult();

public CommandResult() { }

public CommandResult(int exitCode)
Expand All @@ -13,4 +21,9 @@ public CommandResult(int exitCode)
}

public int ExitCode { get; init; }

public static implicit operator CommandResult(int exitCode)
=> exitCode == 0
? Success
: new CommandResult(exitCode);
}
11 changes: 11 additions & 0 deletions source/Cli/CreativeCoders.Cli.Core/ICliCommandContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,21 @@

namespace CreativeCoders.Cli.Core;

/// <summary>
/// Represents the context for a CLI command, providing access to arguments passed to the command.
/// </summary>
[PublicAPI]
public interface ICliCommandContext
{
/// <summary>
/// Gets or sets all arguments passed to the CLI app, including commands, options and positional arguments.
/// This property provides full visibility of the input arguments passed to the CLI app for further processing.
/// </summary>
public string[] AllArgs { get; set; }

/// <summary>
/// Gets or sets the arguments that represent options passed to the CLI command.
/// This property contains only the arguments classified as options and excludes CLI inputs such as commands.
/// </summary>
public string[] OptionsArgs { get; set; }
}
23 changes: 23 additions & 0 deletions source/Cli/CreativeCoders.Cli.Core/OptionsValidationResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,27 @@ public class OptionsValidationResult(bool isValid, IEnumerable<string>? messages
/// Gets the validation messages.
/// </summary>
public IEnumerable<string> Messages { get; } = messages ?? [];

/// <summary>
/// Creates a predefined valid result for options validation where
/// the validation is successful without any messages.
/// </summary>
/// <returns>
/// An <see cref="OptionsValidationResult"/> instance representing a successful validation result
/// with an empty collection of messages.
/// </returns>
public static OptionsValidationResult Valid() => new OptionsValidationResult(true);

/// <summary>
/// Creates a predefined invalid result for options validation where
/// the validation fails and may include associated validation messages.
/// </summary>
/// <param name="messages">An optional collection of validation messages explaining the failure.
/// If null, the result will contain an empty collection of messages.</param>
/// <returns>
/// An <see cref="OptionsValidationResult"/> instance representing an unsuccessful validation result
/// with the provided validation messages or an empty collection if none are specified.
/// </returns>
public static OptionsValidationResult Invalid(IEnumerable<string>? messages)
=> new OptionsValidationResult(false, messages);
}
9 changes: 9 additions & 0 deletions source/Cli/CreativeCoders.Cli.Hosting/ICliHost.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
namespace CreativeCoders.Cli.Hosting;

/// <summary>
/// Represents a Command Line Interface (CLI) host that provides functionality to execute
/// commands and handle related tasks.
/// </summary>
public interface ICliHost
{
/// <summary>
/// Executes the CLI host with the given arguments and returns the result of the operation.
/// </summary>
/// <param name="args">The command line arguments provided to the host.</param>
/// <returns>A <see cref="CliResult"/> object containing the exit code of the executed command.</returns>
Task<CliResult> RunAsync(string[] args);

/// <summary>
Expand Down
52 changes: 52 additions & 0 deletions source/Cli/CreativeCoders.Cli.Hosting/ICliHostBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,54 @@

namespace CreativeCoders.Cli.Hosting;

/// <summary>
/// Defines a builder for configuring and creating an instance of <see cref="ICliHost"/>.
/// Provides methods to set up services, command context, assembly scanning, validation, and help functionality
/// for a Command Line Interface (CLI) application.
/// </summary>
[PublicAPI]
public interface ICliHostBuilder
{
/// <summary>
/// Configures a custom command context for the CLI application.
/// </summary>
/// <typeparam name="TContext">The type of the context to be used. Must implement <see cref="ICliCommandContext"/>.</typeparam>
/// <param name="configure">
/// An optional action to configure the context instance. The action receives the service provider
/// and the instance of <typeparamref name="TContext"/> as parameters.
/// </param>
/// <returns>The same <see cref="ICliHostBuilder"/> instance.</returns>
/// <exception cref="InvalidOperationException">Thrown if the context is already configured.</exception>
ICliHostBuilder UseContext<TContext>(Action<IServiceProvider, TContext>? configure = null)
where TContext : class, ICliCommandContext;

/// <summary>
/// Configures additional services for the CLI application.
/// </summary>
/// <param name="configureServices">
/// An action that receives an <see cref="IServiceCollection"/> to add or modify services for the application.
/// </param>
/// <returns>The same <see cref="ICliHostBuilder"/> instance, enabling method chaining.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="configureServices"/> is null.</exception>
ICliHostBuilder ConfigureServices(Action<IServiceCollection> configureServices);

/// <summary>
/// Registers specified assemblies for command scanning within the CLI application.
/// </summary>
/// <param name="assemblies">
/// An array of <see cref="Assembly"/> instances to be scanned for commands.
/// </param>
/// <returns>The same <see cref="ICliHostBuilder"/> instance.</returns>
ICliHostBuilder ScanAssemblies(params Assembly[] assemblies);

/// <summary>
/// Enables help functionality for the CLI application by specifying the type of help commands to be supported.
/// </summary>
/// <param name="commandKind">
/// Defines the type of help commands that can be used within the application.
/// This can be a command-specific help, argument-specific help, or both, as specified by the values in <see cref="HelpCommandKind"/>.
/// </param>
/// <returns>The same <see cref="ICliHostBuilder"/> instance to allow for method chaining.</returns>
ICliHostBuilder EnableHelp(HelpCommandKind commandKind);

/// <summary>
Expand All @@ -25,7 +63,21 @@ ICliHostBuilder UseContext<TContext>(Action<IServiceProvider, TContext>? configu
/// <returns>The same <see cref="ICliHostBuilder"/> instance.</returns>
ICliHostBuilder UseValidation(bool useValidation = true);

/// <summary>
/// Specifies whether the entry assembly should be excluded from the scanning process
/// when configuring the CLI application.
/// </summary>
/// <param name="skipScanEntryAssembly">
/// A boolean value indicating whether to skip scanning the entry assembly. Defaults to <c>true</c>.
/// If set to <c>false</c>, the entry assembly will be included in the assembly scanning process.
/// </param>
/// <returns>The same <see cref="ICliHostBuilder"/> instance.</returns>
ICliHostBuilder SkipScanEntryAssembly(bool skipScanEntryAssembly = true);

/// <summary>
/// Builds and creates an instance of <see cref="ICliHost"/> configured through the current builder.
/// </summary>
/// <returns>An instance of <see cref="ICliHost"/> that represents the configured Command Line Interface (CLI) application.</returns>
/// <exception cref="InvalidOperationException">Thrown if the build process encounters an invalid state or configuration.</exception>
ICliHost Build();
}
35 changes: 35 additions & 0 deletions tests/CreativeCoders.Cli.Tests/Core/CommandResultTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using AwesomeAssertions;
using CreativeCoders.Cli.Core;
using Xunit;

namespace CreativeCoders.Cli.Tests.Core;

public class CommandResultTests
{
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(-1)]
[InlineData(int.MaxValue)]
[InlineData(int.MinValue)]
public void ImplicitOperator_FromInt_CreatesCommandResultWithExitCode(int exitCode)
{
// Act
var result = (CommandResult)exitCode;

// Assert
result.ExitCode
.Should().Be(exitCode);
}

[Fact]
public void Success_ReturnsResultWithExitCodeZero()
{
// Act
var result = CommandResult.Success;

// Assert
result.ExitCode
.Should().Be(0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using AwesomeAssertions;
using CreativeCoders.Cli.Core;
using Xunit;

namespace CreativeCoders.Cli.Tests.Core;

public class OptionsValidationResultTests
{
[Fact]
public void StaticValid_Call_ReturnsValidResult()
{
// Act
var result = OptionsValidationResult.Valid();

// Assert
result.IsValid
.Should().BeTrue();

result.Messages
.Should().BeEmpty();
}

[Fact]
public void StaticInvalid_Call_ReturnsInvalidResultWithMessages()
{
// Arrange
const string message0 = "Test";
const string message1 = "qwertz2";

var messages = new[] { message0, message1 };

// Act
var result = OptionsValidationResult.Invalid(messages);

// Assert
result.IsValid
.Should().BeFalse();

result.Messages
.Should().BeEquivalentTo(message0, message1);
}

[Fact]
public void StaticInvalid_CallWithMessagesNull_ReturnsInvalidResultWithEmptyMessages()
{
// Act
var result = OptionsValidationResult.Invalid(null);

// Assert
result.IsValid
.Should().BeFalse();

result.Messages
.Should().BeEmpty();
}
}
Loading