Skip to content

Commit 86f2b96

Browse files
committed
added publish GitHub workflow, core components documented
1 parent af973e5 commit 86f2b96

File tree

9 files changed

+169
-6
lines changed

9 files changed

+169
-6
lines changed

.github/workflows/publish.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
2+
name: Publish Release
3+
4+
on:
5+
release:
6+
types: [published]
7+
env:
8+
PROJECT_NAME: CommandLineX
9+
10+
jobs:
11+
publish:
12+
runs-on: ubuntu-latest
13+
timeout-minutes: 15
14+
15+
steps:
16+
- uses: actions/checkout@v4
17+
- name: Setup .NET
18+
uses: actions/setup-dotnet@v4
19+
with:
20+
dotnet-version: 8.0.x
21+
# - name: Verify commit exists in origin/main
22+
# run: |
23+
# git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/*
24+
# git branch --remote --contains | grep origin/main
25+
- name: Set VERSION variable from tag
26+
shell: bash
27+
run: echo "VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV
28+
29+
- name: Build
30+
run: dotnet build -c Release /p:Version=${VERSION}
31+
- name: Test
32+
run: dotnet test -c Release /p:Version=${VERSION} --no-build
33+
- name: Pack
34+
run: dotnet pack -c Release /p:Version=${VERSION} --no-build --output .
35+
- name: Push NuGet
36+
run: dotnet nuget push "*.nupkg" -s https://api.nuget.org/v3/index.json -k ${NUGET_TOKEN} --skip-duplicate
37+
env:
38+
NUGET_TOKEN: ${{ secrets.NUGET_PUBLISH_TOKEN }}
39+
40+
- name: Publish Binaries to GitHub
41+
uses: softprops/action-gh-release@v2
42+
with:
43+
files: "*.nupkg"
44+
env:
45+
GITHUB_TOKEN: ${{ secrets.PACKAGE_WRITE_TOKEN }}

CommandLineX.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ EndProject
1313
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub", "GitHub", "{8D85AE92-B925-47E0-9913-0CE8840E017C}"
1414
ProjectSection(SolutionItems) = preProject
1515
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
16+
.github\workflows\publish.yml = .github\workflows\publish.yml
1617
EndProjectSection
1718
EndProject
1819
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandLineX", "src\CommandLineX\CommandLineX.csproj", "{66FE8D23-FD3C-45CB-8C71-D54E48AD39CA}"

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ In the directory of your project execute
2020
dotnet add package diVISION.CommandLineX
2121
```
2222

23-
### Integrating The Library
23+
### Integrating The Library In Your Application
2424
1. Create `MyFirstAction.cs` (command action model) in your project directory:
2525
```cs
2626
using diVISION.CommandLineX;
@@ -29,11 +29,12 @@ dotnet add package diVISION.CommandLineX
2929
{
3030
public class MyFirstAction(ILogger<MyFirstAction> logger): ICommandAction
3131
{
32+
// injected by the host via DI
3233
private readonly ILogger<MyFirstAction> _logger = logger;
3334

34-
// do-amount argument
35+
// "do-amount" argument
3536
public IEnumerable<int> DoAmount { get; set; } = [0];
36-
// --directory option
37+
// "--directory" option
3738
public DirectoryInfo Directory { get; set; } = new (".");
3839

3940
public int Invoke(CommandActionContext context)
@@ -66,6 +67,7 @@ dotnet add package diVISION.CommandLineX
6667
})
6768
.ConfigureDefaults(null)
6869
.UseConsoleLifetime()
70+
// next 2 calls initalize CommandLine hosting
6971
.UseCommandLine(rootCmd)
7072
.UseCommandWithAction<MyFirstAction>(rootCmd, new("myFirst", "Doing things")
7173
{
@@ -112,4 +114,4 @@ dotnet build
112114
```
113115

114116
## Contributing
115-
All contributions to development and error fixing are welcome. Please always use `develop` branch for forks and pull requests, `main` is reserved for stable releases and critical vulnarability fixes only. All code changes should meet minimal code coverage requirements to be merged into `main` or `develop`, the coverage requirements are: lines - 95%, branches - 95%, methods - 100%.
117+
All contributions to development and error fixing are welcome. Please always use `develop` branch for forks and pull requests, `main` is reserved for stable releases and critical vulnarability fixes only. Please note: all code changes should meet minimal code coverage requirements to be merged into `main` or `develop`.

src/CommandLineX/CommandActionContext.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,22 @@
77

88
namespace diVISION.CommandLineX
99
{
10+
/// <summary>
11+
/// Context information collected by the framwork during parsing of the command line arguments.
12+
/// All bound command action models are passed this in the <c cref="ICommandAction.Invoke(CommandActionContext)">Invoke</c>
13+
/// or resp. <c cref="ICommandAction.InvokeAsync(CommandActionContext, CancellationToken)">InvokeAsync</c> call.
14+
/// </summary>
15+
/// <param name="parseResult"></param>
16+
/// <param name="unboundSymbols"></param>
1017
public class CommandActionContext(ParseResult parseResult, IEnumerable<Symbol> unboundSymbols)
1118
{
19+
/// <summary>
20+
/// Result of command line parsing.
21+
/// </summary>
1222
public ParseResult ParseResult => parseResult;
23+
/// <summary>
24+
/// Collection of symbols that have been defined by the bound <c cref="Command">Command</c> but could not be resolved in the model.
25+
/// </summary>
1326
public IEnumerable<Symbol> UnboundSymbols => unboundSymbols;
1427
}
1528
}

src/CommandLineX/CommandLineOptions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77

88
namespace diVISION.CommandLineX
99
{
10+
/// <summary>
11+
/// Container with configuration options for <c cref="System.CommandLine">System.CommandLine</c>.
12+
/// An instance of this class initialized with the defaults is passed to the configure action by
13+
/// <c cref="diVISION.CommandLineX.Hosting.CommmandLineHostingExtension.UseCommandLine(Microsoft.Extensions.Hosting.IHostBuilder, RootCommand, Action{CommandLineOptions}?)">CommmandLineHostingExtension.UseCommandLine</c>,
14+
/// the action then can modify the configuration as required.
15+
/// </summary>
1016
public class CommandLineOptions
1117
{
1218
public ParserConfiguration? ParserConfiguration { get; set; }

src/CommandLineX/CommandLineX.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<TargetFramework>net8.0</TargetFramework>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
8-
<Version>0.1.0-rc01</Version>
8+
<Version>0.1.0-rc02</Version>
99
<Description>Hosting and model binding extensions for System.CommandLine</Description>
1010
</PropertyGroup>
1111

src/CommandLineX/Hosting/CommmandLineHostingExtension.cs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,21 @@
1010

1111
namespace diVISION.CommandLineX.Hosting
1212
{
13+
/// <summary>
14+
/// <see cref="https://github.com/dotnet/command-line-api"><c>System.CommandLine</c></see> integration with <c>IHostBuilder</c> and <c>IHost</c>.
15+
/// </summary>
1316
public static class CommmandLineHostingExtension
1417
{
18+
/// <summary>
19+
/// Registers basic services neccessary to invoke commands from within a host.
20+
/// If <paramref name="configure"/> action is provided, it is called with <c>CommandLineOptions</c> instance
21+
/// where configuration for <c>System.CommandLine</c> can be set up. Call this method before any other extension is used.
22+
/// </summary>
23+
/// <seealso cref="CommandLineOptions"/>
24+
/// <param name="builder">host builder instance being extended</param>
25+
/// <param name="rootCommand" cref="RootCommand">root command to be invoked by host</param>
26+
/// <param name="configure">optional configure action</param>
27+
/// <returns><paramref name="builder"/></returns>
1528
public static IHostBuilder UseCommandLine(this IHostBuilder builder, RootCommand rootCommand, Action<CommandLineOptions>? configure = null)
1629
{
1730
var registry = new CommandActionRegistry();
@@ -37,6 +50,13 @@ public static IHostBuilder UseCommandLine(this IHostBuilder builder, RootCommand
3750
return builder;
3851
}
3952

53+
/// <summary>
54+
/// Registers <c cref="CommandLineInvocationContext">CommandLineInvocationContext</c> and <c cref="CommandLineHostedService">CommandLineHostedService</c>
55+
/// so that commands specified by <paramref name="args"/> will be parsed and invoked asynchronously at the host startup.
56+
/// </summary>
57+
/// <param name="builder">host builder instance being extended</param>
58+
/// <param name="args">command line arguments to parse</param>
59+
/// <returns><paramref name="builder"/></returns>
4060
public static IHostBuilder UseHostedCommandInvocation(this IHostBuilder builder, string[] args)
4161
{
4262
builder.ConfigureServices(services =>
@@ -47,6 +67,19 @@ public static IHostBuilder UseHostedCommandInvocation(this IHostBuilder builder,
4767
return builder;
4868
}
4969

70+
/// <summary>
71+
/// Adds specified <paramref name="command"/> to <paramref name="parent"/> command (ususally <c>RootCommand</c>) and binds <typeparamref name="TAction"/> model to the former.
72+
/// Requires comand line services to be set up by <c cref="UseCommandLine(IHostBuilder, RootCommand, Action{CommandLineOptions}?)">UseCommandLine</c> prior to this call.
73+
/// </summary>
74+
/// <seealso cref="UseCommandLine(IHostBuilder, RootCommand, Action{CommandLineOptions}?)"/>
75+
/// <typeparam name="TAction">action to bind</typeparam>
76+
/// <param name="builder">host builder instance being extended</param>
77+
/// <param name="parent">parent <c cref="Command">Command</c> to which <paramref name="command"/> will be added</param>
78+
/// <param name="command"><c cref="Command">Command</c> used for binding</param>
79+
/// <param name="runAsync">indicates whether <typeparamref name="TAction"/>.<c cref="ICommandAction.Invoke(CommandActionContext)">Invoke</c>
80+
/// or <typeparamref name="TAction"/>.<c cref="ICommandAction.InvokeAsync(CommandActionContext, CancellationToken)">InvokeAsync</c> is used to execute the action</param>
81+
/// <returns><paramref name="builder"/></returns>
82+
/// <exception cref="InvalidOperationException"></exception>
5083
public static IHostBuilder UseCommandWithAction<TAction>(this IHostBuilder builder, Command parent, Command command, bool runAsync = true)
5184
where TAction : class, ICommandAction
5285
{
@@ -90,6 +123,14 @@ public static IHostBuilder UseCommandWithAction<TAction>(this IHostBuilder build
90123
return builder;
91124
}
92125

126+
/// <summary>
127+
/// Starts the <paramref name="host"/>, parses command line <paramref name="args"/> and executes matching <c cref="ICommandAction.Invoke(CommandActionContext)">ICommandAction.Invoke</c>
128+
/// previously bound by <c cref="UseCommandWithAction{TAction}(IHostBuilder, Command, Command, bool)">UseCommandWithAction</c>. If no match exists an error message is displayed and an error code is returned.
129+
/// </summary>
130+
/// <seealso cref="UseCommandWithAction{TAction}(IHostBuilder, Command, Command, bool)"/>
131+
/// <param name="host">host instance being extended</param>
132+
/// <param name="args">command line arguments to parse</param>
133+
/// <returns>either result of <c cref="ICommandAction.Invoke(CommandActionContext)">ICommandAction.Invoke</c> or an error code</returns>
93134
public static int RunCommandLine(this IHost host, string[] args)
94135
{
95136
host.ThrowIfHostedCommandLine();
@@ -98,6 +139,15 @@ public static int RunCommandLine(this IHost host, string[] args)
98139
return invoker.Invoke(args);
99140
}
100141

142+
/// <summary>
143+
/// Starts the <paramref name="host"/>, parses command line <paramref name="args"/> and executes matching <c cref="ICommandAction.InvokeAsync(CommandActionContext, CancellationToken)">ICommandAction.InvokeAsync</c>
144+
/// previously bound by <c cref="UseCommandWithAction{TAction}(IHostBuilder, Command, Command, bool)">UseCommandWithAction</c>. If no match exists an error message is displayed and an error code is returned.
145+
/// </summary>
146+
/// <seealso cref="UseCommandWithAction{TAction}(IHostBuilder, Command, Command, bool)"/>
147+
/// <param name="host">host instance being extended</param>
148+
/// <param name="args">command line arguments to parse</param>
149+
/// <param name="cancellationToken">cancellation token passed to <c cref="ICommandAction.InvokeAsync(CommandActionContext, CancellationToken)">ICommandAction.InvokeAsync</c></param>
150+
/// <returns>either result of <c cref="ICommandAction.InvokeAsync(CommandActionContext, CancellationToken)">ICommandAction.InvokeAsync</c> or an error code</returns>
101151
public static async Task<int> RunCommandLineAsync(this IHost host, string[] args, CancellationToken cancellationToken = default)
102152
{
103153
host.ThrowIfHostedCommandLine();
@@ -106,6 +156,15 @@ public static async Task<int> RunCommandLineAsync(this IHost host, string[] args
106156
return await invoker.InvokeAsync(args, cancellationToken);
107157
}
108158

159+
/// <summary>
160+
/// Starts the <paramref name="host"/>, during the startup the <c cref="CommandLineHostedService">CommandLineHostedService</c>
161+
/// (registered by <c cref="UseHostedCommandInvocation(IHostBuilder, string[])">UseHostedCommandInvocation</c>)
162+
/// parses the command line and executes matching <c cref="ICommandAction.Invoke(CommandActionContext)">ICommandAction.Invoke</c>
163+
/// previously bound by <c cref="UseCommandWithAction{TAction}(IHostBuilder, Command, Command, bool)">UseCommandWithAction</c>. If no match exists an error message is displayed and an error code is returned.
164+
/// </summary>
165+
/// <seealso cref="UseHostedCommandInvocation(IHostBuilder, string[])"/>
166+
/// <param name="host">host instance being extended</param>
167+
/// <returns>either result of <c cref="ICommandAction.Invoke(CommandActionContext)">ICommandAction.Invoke</c> or an error code</returns>
109168
public static int RunCommandLineHosted(this IHost host)
110169
{
111170
host.CheckInvocationContext();
@@ -119,6 +178,16 @@ public static int RunCommandLineHosted(this IHost host)
119178
return Environment.ExitCode;
120179
}
121180

181+
/// <summary>
182+
/// Starts the <paramref name="host"/>, during the startup the <c cref="CommandLineHostedService">CommandLineHostedService</c>
183+
/// (registered by <c cref="UseHostedCommandInvocation(IHostBuilder, string[])">UseHostedCommandInvocation</c>)
184+
/// parses the command line and executes matching <c cref="ICommandAction.InvokeAsync(CommandActionContext, CancellationToken)">ICommandAction.InvokeAsync</c>
185+
/// previously bound by <c cref="UseCommandWithAction{TAction}(IHostBuilder, Command, Command, bool)">UseCommandWithAction</c>. If no match exists an error message is displayed and an error code is returned.
186+
/// </summary>
187+
/// <seealso cref="UseHostedCommandInvocation(IHostBuilder, string[])"/>
188+
/// <param name="host">host instance being extended</param>
189+
/// <param name="cancellationToken">cancellation token passed to the <paramref name="host"/></param>
190+
/// <returns>either result of <c cref="ICommandAction.InvokeAsync(CommandActionContext, CancellationToken)">ICommandAction.InvokeAsync</c> or an error code</returns>
122191
public static async Task<int> RunCommandLineHostedAsync(this IHost host, CancellationToken cancellationToken = default)
123192
{
124193
host.CheckInvocationContext();
@@ -133,6 +202,11 @@ public static async Task<int> RunCommandLineHostedAsync(this IHost host, Cancell
133202
return Environment.ExitCode;
134203
}
135204

205+
/// <summary>
206+
/// For internal use.
207+
/// </summary>
208+
/// <param name="host">host instance being extended</param>
209+
/// <exception cref="InvalidOperationException"></exception>
136210
public static void CheckInvocationContext(this IHost host)
137211
{
138212
if (null == host.Services.GetService<CommandLineInvocationContext>())
@@ -141,6 +215,11 @@ public static void CheckInvocationContext(this IHost host)
141215
}
142216
}
143217

218+
/// <summary>
219+
/// For internal use.
220+
/// </summary>
221+
/// <param name="host">host instance being extended</param>
222+
/// <exception cref="InvalidOperationException"></exception>
144223
public static void ThrowIfHostedCommandLine(this IHost host)
145224
{
146225
if (host.Services.GetService<IHostedService>() is CommandLineHostedService)

src/CommandLineX/ICommandAction.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,26 @@
55
**/
66
namespace diVISION.CommandLineX
77
{
8+
/// <summary>
9+
/// Interface for command action model binding.
10+
/// </summary>
811
public interface ICommandAction
912
{
13+
/// <summary>
14+
/// Called by the framework when a synchronous action binding matches the command line arguments.
15+
/// </summary>
16+
/// <seealso cref="CommandActionContext"/>
17+
/// <param name="context">current execution context</param>
18+
/// <returns></returns>
1019
int Invoke(CommandActionContext context);
20+
21+
/// <summary>
22+
/// Called by the framework when an asynchronous action binding matches the command line arguments.
23+
/// </summary>
24+
/// <seealso cref="CommandActionContext"/>
25+
/// <param name="context">urrent execution context</param>
26+
/// <param name="cancellationToken"></param>
27+
/// <returns></returns>
1128
Task<int> InvokeAsync(CommandActionContext context, CancellationToken cancellationToken = default);
1229
}
1330
}

test/CommandLineX.Tests/CommandLineX.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<LangVersion>latest</LangVersion>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
8-
<Version>0.1.0-rc01</Version>
8+
<Version>0.1.0-rc02</Version>
99
</PropertyGroup>
1010

1111
<ItemGroup>

0 commit comments

Comments
 (0)