Skip to content

✨Split the command handler and theirs data #172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions .github/workflows/buildAndPublish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ jobs:
run: dotnet build -c Release -p:Version=${{ steps.version.outputs.version}}

- name: Run tests
run: dotnet test -c Release --no-build
run: dotnet test -c Release --no-build -l trx --results-directory TestResults

- name: Upload dotnet test results
uses: dorny/test-reporter@v1.8.0
uses: dorny/test-reporter@v1.9.0
if: ${{ !cancelled() }}
with:
name: .NET Tests
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/generateDocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Build documenation site
uses: nunit/docfx-action@v3.1.0
uses: nunit/docfx-action@v3.2.0
with:
args: docs/docfx.json
- name: Upload documentation archive
Expand Down
57 changes: 29 additions & 28 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
<Project>
<PropertyGroup>
<Product>MGR.CommandLineParser</Product>
<TargetFramework>netstandard2.0</TargetFramework>
<DocumentationFile>bin\$(Configuration)\netstandard2.0\$(MSBuildProjectName).xml</DocumentationFile>
<LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute>
<GenerateAssemblyDescriptionAttribute>false</GenerateAssemblyDescriptionAttribute>
<WarningsAsErrors />
<DebugType>portable</DebugType>
<IsPackable>true</IsPackable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PropertyGroup>
<Product>MGR.CommandLineParser</Product>
<TargetFramework>netstandard2.0</TargetFramework>
<DocumentationFile>bin\$(Configuration)\netstandard2.0\$(MSBuildProjectName).xml</DocumentationFile>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute>
<GenerateAssemblyDescriptionAttribute>false</GenerateAssemblyDescriptionAttribute>
<WarningsAsErrors />
<DebugType>portable</DebugType>
<IsPackable>true</IsPackable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>

<Authors>Matthias GROSPERRIN</Authors>
<Company></Company>
<PackageProjectUrl>https://github.com/mgrosperrin/commandlineparser</PackageProjectUrl>
<RepositoryUrl>https://github.com/mgrosperrin/commandlineparser</RepositoryUrl>
<Description>MGR.CommandLineParser is a multi-command line parser. It uses System.ComponentModel.DataAnnotations to declare the commands.</Description>
<RepositoryType>git</RepositoryType>
<Copyright>Copyright © Matthias GROSPERRIN</Copyright>
<NeutralLanguage>en-US</NeutralLanguage>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<VSTestLogger>trx%3bLogFileName=$(MSBuildProjectName).trx</VSTestLogger>
<VSTestResultsDirectory>$(MSBuildThisFileDirectory)/TestResults/$(TargetFramework)</VSTestResultsDirectory>
</PropertyGroup>
<Authors>Matthias GROSPERRIN</Authors>
<Company></Company>
<PackageProjectUrl>https://github.com/mgrosperrin/commandlineparser</PackageProjectUrl>
<RepositoryUrl>https://github.com/mgrosperrin/commandlineparser</RepositoryUrl>
<Description>MGR.CommandLineParser is a multi-command line parser. It uses System.ComponentModel.DataAnnotations to declare the commands.</Description>
<RepositoryType>git</RepositoryType>
<Copyright>Copyright © Matthias GROSPERRIN</Copyright>
<NeutralLanguage>en-US</NeutralLanguage>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<VSTestLogger>trx%3bLogFileName=$(MSBuildProjectName).trx</VSTestLogger>
<VSTestResultsDirectory>$(MSBuildThisFileDirectory)/TestResults/$(TargetFramework)</VSTestResultsDirectory>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<Optimize>true</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<Optimize>true</Optimize>
</PropertyGroup>

</Project>
12 changes: 5 additions & 7 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,25 @@
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
<DotNetMainVersion>8.0.0</DotNetMainVersion>
<DotNetLastVersion>8.0.0</DotNetLastVersion>
</PropertyGroup>

<ItemGroup>
<PackageVersion Include="coverlet.collector" Version="6.0.1">
<PackageVersion Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageVersion Include="Moq" Version="4.20.70" />
<PackageVersion Include="Seq.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageVersion Include="xunit" Version="2.7.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.7">
<PackageVersion Include="xunit" Version="2.7.1" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageVersion>
Expand Down
19 changes: 19 additions & 0 deletions MGR.CommandLineParser.sln
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MGR.CommandLineParser", "src\MGR.CommandLineParser\MGR.CommandLineParser.csproj", "{EF6019C2-2C4D-4874-BBBF-D76CA30F7E8D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{569ED12A-DA23-4457-B304-6B00C0CB335E}"
ProjectSection(SolutionItems) = preProject
tests\Directory.Build.props = tests\Directory.Build.props
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MGR.CommandLineParser.UnitTests", "tests\MGR.CommandLineParser.UnitTests\MGR.CommandLineParser.UnitTests.csproj", "{22F60D05-F6DB-4410-80AD-E558EF1BDF6F}"
EndProject
Expand Down Expand Up @@ -41,6 +44,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MGR.CommandLineParser.Hosti
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs", "docs\docs.csproj", "{405858FA-1E78-48C7-9915-B558D0F15CAE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{AF37E9B0-D41E-4CC2-8641-61FA6B28362B}"
ProjectSection(SolutionItems) = preProject
.github\dependabot.yml = .github\dependabot.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{BCD738AE-36C9-4CD9-B0F5-EAB942B193DC}"
ProjectSection(SolutionItems) = preProject
.github\workflows\buildAndPublish.yml = .github\workflows\buildAndPublish.yml
.github\workflows\ci.yml = .github\workflows\ci.yml
.github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml
.github\workflows\generateDocs.yml = .github\workflows\generateDocs.yml
.github\workflows\release.yml = .github\workflows\release.yml
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -92,6 +109,8 @@ Global
{9F5A0142-E0A1-45A2-961E-55611B4440AD} = {FB795A12-C939-492F-9377-4C468D01EF3C}
{40EAA8E2-7AFE-4ED1-A961-35BD7E424985} = {FB795A12-C939-492F-9377-4C468D01EF3C}
{405858FA-1E78-48C7-9915-B558D0F15CAE} = {172E24C2-BF99-4DA9-A7DF-8C0BB44C138D}
{AF37E9B0-D41E-4CC2-8641-61FA6B28362B} = {EF849FD3-293F-4CC8-B227-9680CF929A66}
{BCD738AE-36C9-4CD9-B0F5-EAB942B193DC} = {AF37E9B0-D41E-4CC2-8641-61FA6B28362B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {827031C5-AE76-4B4C-9503-E13F15B497E9}
Expand Down
40 changes: 26 additions & 14 deletions docs/class-based/create-class-based-command.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,44 @@
# Create a class-based command

## Implement `ICommand`
To create a class-based command, you have to create two classes:
1. one that defines the data associated with the command,
2. one that implements the logic of the command.

To create a class-based command, you have to create a class that implements the interface `MGR.CommandLineParser.Command.ICommand` (or [inherit `CommandBase`](#inherit-from-commandbase)).
The interface provides two members:
Depending on what basic features you need for your command, you can choose to implement the logic of the command by inheriting from the abstract class `CommandBase<THelpedCommandData>` or by implementing the interface `ICommandHandler<TCommandData>`.

1. a property `Arguments` (of type `IList<string>`) that will receive all arguments not mapped to an option,
2. a method `ExecuteAsync` (that take no parameters and return a `Task<int>`) that is called to execute the command. The returned value is the status code that should be returned as exit code.
## Inherit from `CommandBase<THelpedCommandData>`
The abstract class `CommandBase<THelpedCommandData>` provides implementation for :

## Inherit from `CommandBase`
You can inherit from `MGR.CommandLineParser.Command.CommandBase`.
This abstract class provides:
- an `--help` option,
- access to the current `IServiceProvider` via the property `ServiceProvider`,
- access to the current [IConsole](../extensibility/console.md) via the property `Console`.

- implementation of an `--help` option,
- access to the current `IServiceProvider`,
- access to the current [IConsole](../extensibility/console.md)
- access to the [ICommandType](/api/MGR.CommandLineParser.Extensibility.Command.ICommandType.html) (which describes the current command)
When deriving from this class, you have to implement the `ExecuteCommandAsync` that is called when the value of the `--help` option is `false`.

You also have to make your command data class inherit from `MGR.CommandLineParser.Command.HelpedCommandData` to have the help option implemented.
It will also give you access to the [ICommandType](/api/MGR.CommandLineParser.Extensibility.Command.ICommandType.html) which describes the current command.

When deriving from this class, you have to implement the `ExecuteCommandAsync` that is called when the value of the `--help` option is ```false```.
## Implement `ICommandHandler` and `CommandData`

If you don't need these features, you can implement the interface `ICommandHandler<TCommandData>`.

The interface defines a method `ExecuteAsync` to implement which is called to execute the command.
It takes two parameters:
1. The data of the command, of type of the generic type argument `TCommandData`,
2. A `CancellationToken` used to stop the execution of the command.

The method returns a `Task<int>` that represent the status code of the execution of the command (usually the exit code of the program).

The "data" class must inherit `CommandData`, that provides a property `Arguments` (of type `IList<string>`) that will receive all arguments not mapped to an option.
To define your own opptions, you have to add properties to this class.

## Customize your command

[Learn how to customize your command](customize-class-based-command.md).

## Add options to you command

You can add options to your command by simply adding properties to your class.
You can add options to your command by simply adding properties to your "command data" class.
The properties can have any type, you just have to be sure there is a [converter](../extensibility/converter.md) for this type (see the [list of built-in converters](../extensibility/built-in-converters.md)).

You can customize the behavior of the options with some annotations (the parser supports the annotations from [System.ComponentModel.DataAnnotations](https://docs.microsoft.com/dotnet/api/system.componentmodel.dataannotations)):
Expand Down
3 changes: 1 addition & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ It provides an extensible mechanism to provide define commands
and is able to automatically generate help/usage output for all commands.
Built-in providers lets you define command by:

- [creating a class](class-based/create-class-based-command.md) that implements `MGR.CommandLineParser.Command.ICommand` or inherits `MGR.CommandLineParser.Command.CommandBase` (provides some basic behavior for commands like support of `--help` option)
- [creating classes](class-based/create-class-based-command.md) that implements `MGR.CommandLineParser.Command.ICommandHandler` for the logic of your command and `MGR.CommandLineParser.Command.CommandData` for the data associated to the command, or inherits `MGR.CommandLineParser.Command.CommandBase` (provides some basic behavior for commands like support of `--help` option)
- [dynamically defining a command that uses a lambda](lambda/create-a-lambda-based-command.md) as execution (via the package `MGR.CommandLineParser.Command.Lambda)

The general syntax on the command line is:
Expand All @@ -19,7 +19,6 @@ by a space (` `) or a colon (`:`).

Arguments is a list of non-option string that is passed to the command.


There is also some ways to customize others parts of the parser:

- how to display information to the user: the [`IConsole` interface](extensibility/console.md)
Expand Down
21 changes: 0 additions & 21 deletions generate_docs.ps1

This file was deleted.

47 changes: 23 additions & 24 deletions samples/SimpleApp/HostBuilderCalls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,31 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace SimpleApp
namespace SimpleApp;

internal static class HostBuilderCalls
{
internal static class HostBuilderCalls
internal static async Task CreateAndCallDefaultParserBuilderAsync()
{
internal static async Task CreateAndCallDefaultParserBuilderAsync()
{
var arguments = new[] { "run", "--opt", "14" };
Console.WriteLine("Parse: '{0}'", string.Join(" ", arguments));
var arguments = new[] { "run", "--opt", "14" };
Console.WriteLine("Parse: '{0}'", string.Join(" ", arguments));

var hostBuilder = new HostBuilder();
hostBuilder.ConfigureParser(builder =>
{
builder.AddCommand("run",
commandBuilder =>
{
commandBuilder.AddOption<int>("opt", "o",
o => o.Required());
},
context =>
{
var opt = context.GetOptionValue<int>("opt");
return Task.FromResult(opt);
});
});
var parsingResult = await hostBuilder.ParseCommandLineAndExecuteAsync(arguments, CancellationToken.None);
Console.WriteLine("Execution result: {0}", parsingResult);
}
var hostBuilder = new HostBuilder();
hostBuilder.ConfigureParser(builder =>
{
builder.AddCommand("run",
commandBuilder =>
{
commandBuilder.AddOption<int>("opt", "o",
o => o.Required());
},
(context, cancellationToken) =>
{
var opt = context.GetOptionValue<int>("opt");
return Task.FromResult(opt);
});
});
var parsingResult = await hostBuilder.ParseCommandLineAndExecuteAsync(arguments, CancellationToken.None);
Console.WriteLine("Execution result: {0}", parsingResult);
}
}
Loading