Skip to content

Commit 36cb36c

Browse files
committed
implements cli application infrastructure
Introduces a CLI application infrastructure, including the implementation of `ICliApp` and related dependencies. This provides a structured approach for running CLI commands. Additionally, the command and root command implementations are adjusted to accommodate the new infrastructure.
1 parent 8a00c7e commit 36cb36c

File tree

9 files changed

+151
-88
lines changed

9 files changed

+151
-88
lines changed

new-cli/GitVersion.Cli.Generator.Tests/SystemCommandlineGeneratorTests.cs

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ namespace {{Constants.GeneratedNamespaceName}};
3737
3838
public class TestCommandImpl : Command, ICommandImpl
3939
{
40-
public string CommandName => nameof(TestCommandImpl);
41-
public string ParentCommandName => string.Empty;
40+
public string CommandImplName => nameof(TestCommandImpl);
41+
public string ParentCommandImplName => string.Empty;
4242
// Options list
4343
protected readonly Option<string> OutputFileOption;
4444
@@ -84,6 +84,7 @@ public class CommandsModule : IGitVersionModule
8484
public void RegisterTypes(IServiceCollection services)
8585
{
8686
services.AddSingleton<RootCommandImpl>();
87+
services.AddSingleton<ICliApp, CliAppImpl>();
8788
services.AddSingleton<TestCommand>();
8889
services.AddSingleton<ICommandImpl, TestCommandImpl>();
8990
}
@@ -97,23 +98,28 @@ public void RegisterTypes(IServiceCollection services)
9798
using System.CommandLine;
9899
99100
using {{Constants.CommonNamespaceName}};
101+
using GitVersion.Extensions;
102+
100103
namespace {{Constants.GeneratedNamespaceName}};
101104
102-
public class RootCommandImpl : RootCommand
105+
public class RootCommandImpl(IEnumerable<ICommandImpl> commands) : RootCommand
103106
{
104-
public RootCommandImpl(IEnumerable<ICommandImpl> commands)
107+
private readonly IEnumerable<ICommandImpl> _commands = commands.NotNull();
108+
109+
public void Configure()
105110
{
106-
var map = commands.ToDictionary(c => c.CommandName);
111+
var map = _commands.ToDictionary(c => c.CommandImplName);
107112
foreach (var command in map.Values)
108113
{
109114
AddCommand(command, map);
110115
}
111116
}
112-
private void AddCommand(ICommandImpl command, IDictionary<string, ICommandImpl> map)
117+
118+
private void AddCommand(ICommandImpl command, Dictionary<string, ICommandImpl> map)
113119
{
114-
if (!string.IsNullOrWhiteSpace(command.ParentCommandName))
120+
if (!string.IsNullOrWhiteSpace(command.ParentCommandImplName))
115121
{
116-
var parent = map[command.ParentCommandName] as Command;
122+
var parent = map[command.ParentCommandImplName] as Command;
117123
parent?.Add((Command)command);
118124
}
119125
else
@@ -122,6 +128,46 @@ private void AddCommand(ICommandImpl command, IDictionary<string, ICommandImpl>
122128
}
123129
}
124130
}
131+
""";
132+
133+
/*language=cs*/
134+
private const string ExpectedCliAppImplText =
135+
$$"""
136+
{{Constants.GeneratedHeader}}
137+
using System.CommandLine;
138+
using GitVersion.Extensions;
139+
using {{Constants.InfrastructureNamespaceName}};
140+
141+
namespace {{Constants.GeneratedNamespaceName}};
142+
143+
internal class CliAppImpl : ICliApp
144+
{
145+
private readonly RootCommandImpl _rootCommand;
146+
147+
public CliAppImpl(RootCommandImpl rootCommand)
148+
{
149+
_rootCommand = rootCommand.NotNull();
150+
_rootCommand.Configure();
151+
}
152+
153+
public Task<int> RunAsync(string[] args, CancellationToken cancellationToken)
154+
{
155+
// Note: there are 2 locations to watch for the dotnet-suggest tool
156+
// - sentinel file:
157+
// $env:TEMP\system-commandline-sentinel-files\ and
158+
// - registration file:
159+
// $env:LOCALAPPDATA\.dotnet-suggest-registration.txt or $HOME/.dotnet-suggest-registration.txt
160+
161+
var parseResult = _rootCommand.Parse(args);
162+
163+
var logFile = parseResult.GetValue<FileInfo?>(GitVersionSettings.LogFileOption);
164+
var verbosity = parseResult.GetValue<Verbosity?>(GitVersionSettings.VerbosityOption) ?? Verbosity.Normal;
165+
166+
// LoggingEnricher.Configure(logFile?.FullName, verbosity);
167+
168+
return parseResult.InvokeAsync(cancellationToken: cancellationToken);
169+
}
170+
}
125171
""";
126172

127173
/*language=cs*/
@@ -178,6 +224,7 @@ public async Task ValidateGeneratedCommandImplementation()
178224
(generatorType,"TestCommandImpl.g.cs", ExpectedCommandImplText),
179225
(generatorType,"CommandsModule.g.cs", ExpectedCommandsModuleText),
180226
(generatorType,"RootCommandImpl.g.cs", ExpectedRootCommandImplText),
227+
(generatorType,"CliAppImpl.g.cs", ExpectedCliAppImplText),
181228
},
182229
ReferenceAssemblies = ReferenceAssemblies.Net.Net90,
183230
AdditionalReferences =

new-cli/GitVersion.Cli.Generator/SystemCommandlineContent.cs

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ namespace {{GeneratedNamespaceName}};
1414
1515
public class {{Model.CommandTypeName}}Impl : Command, ICommandImpl
1616
{
17-
public string CommandName => nameof({{Model.CommandTypeName}}Impl);
17+
public string CommandImplName => nameof({{Model.CommandTypeName}}Impl);
1818
{{- if (Model.ParentCommand | string.empty) }}
19-
public string ParentCommandName => string.Empty;
19+
public string ParentCommandImplName => string.Empty;
2020
{{- else }}
21-
public string ParentCommandName => nameof({{Model.ParentCommand}}Impl);
21+
public string ParentCommandImplName => nameof({{Model.ParentCommand}}Impl);
2222
{{ end }}
2323
{{- $settingsProperties = Model.SettingsProperties | array.sort "Name" }}
2424
// Options list
@@ -64,23 +64,28 @@ Task<int> Run(ParseResult parseResult, CancellationToken cancellationToken)
6464
using System.CommandLine;
6565
6666
using {{CommonNamespaceName}};
67+
using GitVersion.Extensions;
68+
6769
namespace {{GeneratedNamespaceName}};
6870
69-
public class RootCommandImpl : RootCommand
71+
public class RootCommandImpl(IEnumerable<ICommandImpl> commands) : RootCommand
7072
{
71-
public RootCommandImpl(IEnumerable<ICommandImpl> commands)
73+
private readonly IEnumerable<ICommandImpl> _commands = commands.NotNull();
74+
75+
public void Configure()
7276
{
73-
var map = commands.ToDictionary(c => c.CommandName);
77+
var map = _commands.ToDictionary(c => c.CommandImplName);
7478
foreach (var command in map.Values)
7579
{
7680
AddCommand(command, map);
7781
}
7882
}
79-
private void AddCommand(ICommandImpl command, IDictionary<string, ICommandImpl> map)
83+
84+
private void AddCommand(ICommandImpl command, Dictionary<string, ICommandImpl> map)
8085
{
81-
if (!string.IsNullOrWhiteSpace(command.ParentCommandName))
86+
if (!string.IsNullOrWhiteSpace(command.ParentCommandImplName))
8287
{
83-
var parent = map[command.ParentCommandName] as Command;
88+
var parent = map[command.ParentCommandImplName] as Command;
8489
parent?.Add((Command)command);
8590
}
8691
else
@@ -108,11 +113,51 @@ public void RegisterTypes(IServiceCollection services)
108113
{
109114
{{- $commands = Model | array.sort "CommandTypeName" }}
110115
services.AddSingleton<RootCommandImpl>();
116+
services.AddSingleton<ICliApp, CliAppImpl>();
111117
{{~ for $command in $commands ~}}
112118
services.AddSingleton<{{ if $command.CommandTypeNamespace != CommandNamespaceName }}{{$command.CommandTypeNamespace}}.{{ end }}{{$command.CommandTypeName}}>();
113119
services.AddSingleton<ICommandImpl, {{$command.CommandTypeName}}Impl>();
114120
{{~ end ~}}
115121
}
116122
}
123+
""";
124+
125+
/*language=cs*/
126+
public const string CliAppContent = $$$"""
127+
{{{Constants.GeneratedHeader}}}
128+
using System.CommandLine;
129+
using GitVersion.Extensions;
130+
using {{InfrastructureNamespaceName}};
131+
132+
namespace {{GeneratedNamespaceName}};
133+
134+
internal class CliAppImpl : ICliApp
135+
{
136+
private readonly RootCommandImpl _rootCommand;
137+
138+
public CliAppImpl(RootCommandImpl rootCommand)
139+
{
140+
_rootCommand = rootCommand.NotNull();
141+
_rootCommand.Configure();
142+
}
143+
144+
public Task<int> RunAsync(string[] args, CancellationToken cancellationToken)
145+
{
146+
// Note: there are 2 locations to watch for the dotnet-suggest tool
147+
// - sentinel file:
148+
// $env:TEMP\system-commandline-sentinel-files\ and
149+
// - registration file:
150+
// $env:LOCALAPPDATA\.dotnet-suggest-registration.txt or $HOME/.dotnet-suggest-registration.txt
151+
152+
var parseResult = _rootCommand.Parse(args);
153+
154+
var logFile = parseResult.GetValue<FileInfo?>(GitVersionSettings.LogFileOption);
155+
var verbosity = parseResult.GetValue<Verbosity?>(GitVersionSettings.VerbosityOption) ?? Verbosity.Normal;
156+
157+
// LoggingEnricher.Configure(logFile?.FullName, verbosity);
158+
159+
return parseResult.InvokeAsync(cancellationToken: cancellationToken);
160+
}
161+
}
117162
""";
118163
}

new-cli/GitVersion.Cli.Generator/SystemCommandlineGenerator.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,14 @@ internal override void GenerateSourceCode(SourceProductionContext context, Immut
3939
Constants.CommonNamespaceName
4040
}, member => member.Name);
4141
context.AddSource("RootCommandImpl.g.cs", string.Join("\n", rootCommandHandlerSource));
42+
43+
var cliAppTemplate = Template.Parse(SystemCommandlineContent.CliAppContent);
44+
var cliAppSource = cliAppTemplate.Render(new
45+
{
46+
Constants.GeneratedNamespaceName,
47+
Constants.InfrastructureNamespaceName,
48+
Constants.CommonNamespaceName
49+
}, member => member.Name);
50+
context.AddSource("CliAppImpl.g.cs", string.Join("\n", cliAppSource));
4251
}
4352
}

new-cli/GitVersion.Cli/Program.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
using GitVersion;
22
using GitVersion.Extensions;
3+
using GitVersion.Generated;
34
using GitVersion.Git;
45
using GitVersion.Infrastructure;
5-
using GitVersion.SystemCommandline;
66
using Microsoft.Extensions.DependencyInjection;
77

88
var modules = new IGitVersionModule[]
99
{
1010
new CoreModule(),
1111
new LibGit2SharpCoreModule(),
12-
new CliModule()
12+
new CommandsModule()
1313
};
1414

1515
var cts = new CancellationTokenSource();
1616
Console.CancelKeyPress += (_, _) => cts.Cancel();
1717

1818
await using var serviceProvider = RegisterModules(modules);
19-
var app = serviceProvider.GetRequiredService<IGitVersionAppRunner>();
19+
var app = serviceProvider.GetRequiredService<ICliApp>();
2020

2121
var result = await app.RunAsync(args, cts.Token).ConfigureAwait(false);
2222
if (!Console.IsInputRedirected) Console.ReadKey();

new-cli/GitVersion.Cli/SystemCommandline/CliModule.cs

Lines changed: 0 additions & 15 deletions
This file was deleted.

new-cli/GitVersion.Cli/SystemCommandline/GitVersionAppRunner.cs

Lines changed: 0 additions & 40 deletions
This file was deleted.

new-cli/GitVersion.Common.Command/ICommand.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ public interface ICommand<in T>
77

88
public interface ICommandImpl
99
{
10-
string CommandName { get; }
11-
string ParentCommandName { get; }
10+
string CommandImplName { get; }
11+
string ParentCommandImplName { get; }
1212
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
namespace GitVersion;
1+
namespace GitVersion.Infrastructure;
22

3-
internal interface IGitVersionAppRunner
3+
public interface ICliApp
44
{
55
Task<int> RunAsync(string[] args, CancellationToken cancellationToken);
66
}

new-cli/GitVersion.Core/Infrastructure/LoggingEnricher.cs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,49 @@ public class LoggingEnricher : ILogEventEnricher
77
{
88
public static readonly LoggingLevelSwitch LogLevel = new();
99
private string? _cachedLogFilePath;
10-
private LogEventProperty? _cachedLogFilePathProperty;
10+
private LogEventProperty? _cachedLogFilePathProp;
1111

1212
// this path and level will be set by the LogInterceptor.cs after parsing the settings
13-
public static string Path = string.Empty;
13+
private static string _path = string.Empty;
1414

1515
public const string LogFilePathPropertyName = "LogFilePath";
1616

17-
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
17+
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propFactory)
1818
{
1919
// the settings might not have a path, or we might not be within a command, in which case
2020
// we won't have the setting, so a default value for the log file will be required
21-
LogEventProperty logFilePathProperty;
21+
LogEventProperty logFilePathProp;
2222

23-
if (_cachedLogFilePathProperty != null && Path.Equals(_cachedLogFilePath))
23+
if (_cachedLogFilePathProp != null && _path.Equals(_cachedLogFilePath))
2424
{
2525
// The Path hasn't changed, so let's use the cached property
26-
logFilePathProperty = _cachedLogFilePathProperty;
26+
logFilePathProp = _cachedLogFilePathProp;
2727
}
2828
else
2929
{
3030
// We've got a new path for the log. Let's create a new property
3131
// and cache it for future log events to use
32-
_cachedLogFilePath = Path;
33-
_cachedLogFilePathProperty = logFilePathProperty = propertyFactory.CreateProperty(LogFilePathPropertyName, Path);
32+
_cachedLogFilePath = _path;
33+
_cachedLogFilePathProp = logFilePathProp = propFactory.CreateProperty(LogFilePathPropertyName, _path);
3434
}
3535

36-
logEvent.AddPropertyIfAbsent(logFilePathProperty);
36+
logEvent.AddPropertyIfAbsent(logFilePathProp);
3737
}
38+
39+
public static void Configure(string? logFile, Verbosity verbosity)
40+
{
41+
if (!string.IsNullOrWhiteSpace(logFile)) _path = logFile;
42+
LogLevel.MinimumLevel = GetLevelForVerbosity(verbosity);
43+
}
44+
45+
private static LogEventLevel GetLevelForVerbosity(Verbosity verbosity) => VerbosityMaps[verbosity];
46+
47+
private static readonly Dictionary<Verbosity, LogEventLevel> VerbosityMaps = new()
48+
{
49+
{ Verbosity.Verbose, LogEventLevel.Verbose },
50+
{ Verbosity.Diagnostic, LogEventLevel.Debug },
51+
{ Verbosity.Normal, LogEventLevel.Information },
52+
{ Verbosity.Minimal, LogEventLevel.Warning },
53+
{ Verbosity.Quiet, LogEventLevel.Error },
54+
};
3855
}

0 commit comments

Comments
 (0)