-
-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: CLI Argument parsing (#615)
This adds "System.CommandLine" as default parser and spectre.console for beautiful output. BREAKING CHANGE: generator commands may define a csproj or sln file on where the entities or other elements are located. If no file is provided, the current directory is searched and the command fails if none is found.
- Loading branch information
Showing
14 changed files
with
458 additions
and
298 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using System.CommandLine; | ||
|
||
namespace KubeOps.Cli; | ||
|
||
internal static class Arguments | ||
{ | ||
public static readonly Argument<FileInfo> SolutionOrProjectFile = new( | ||
"sln/csproj file", | ||
() => | ||
{ | ||
var projectFile | ||
= Directory.EnumerateFiles( | ||
Directory.GetCurrentDirectory(), | ||
"*.csproj") | ||
.Select(f => new FileInfo(f)) | ||
.FirstOrDefault(); | ||
var slnFile | ||
= Directory.EnumerateFiles( | ||
Directory.GetCurrentDirectory(), | ||
"*.sln") | ||
.Select(f => new FileInfo(f)) | ||
.FirstOrDefault(); | ||
|
||
return (projectFile, slnFile) switch | ||
{ | ||
({ } prj, _) => prj, | ||
(_, { } sln) => sln, | ||
_ => throw new FileNotFoundException( | ||
"No *.csproj or *.sln file found in current directory.", | ||
Directory.GetCurrentDirectory()), | ||
}; | ||
}, | ||
"A solution or project file where entities are located. " + | ||
"If omitted, the current directory is searched for a *.csproj or *.sln file. " + | ||
"If an *.sln file is used, all projects in the solution (with the newest framework) will be searched for entities. " + | ||
"This behaviour can be filtered by using the --project and --target-framework option."); | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,83 +1,80 @@ | ||
using KubeOps.Abstractions.Kustomize; | ||
using System.CommandLine; | ||
using System.CommandLine.Help; | ||
using System.CommandLine.Invocation; | ||
|
||
using KubeOps.Abstractions.Kustomize; | ||
using KubeOps.Cli.Output; | ||
using KubeOps.Cli.SyntaxObjects; | ||
using KubeOps.Cli.Roslyn; | ||
|
||
using McMaster.Extensions.CommandLineUtils; | ||
using Spectre.Console; | ||
|
||
namespace KubeOps.Cli.Commands.Generator; | ||
|
||
[Command("crd", "crds", Description = "Generates the needed CRD for kubernetes. (Aliases: crds)")] | ||
internal class CrdGenerator | ||
internal static class CrdGenerator | ||
{ | ||
private readonly ConsoleOutput _output; | ||
private readonly ResultOutput _result; | ||
|
||
public CrdGenerator(ConsoleOutput output, ResultOutput result) | ||
public static Command Command | ||
{ | ||
_output = output; | ||
_result = result; | ||
} | ||
|
||
[Option( | ||
Description = "The path the command will write the files to. If empty, prints output to console.", | ||
LongName = "out")] | ||
public string? OutputPath { get; set; } | ||
|
||
[Option( | ||
CommandOptionType.SingleValue, | ||
Description = "Sets the output format for the generator.")] | ||
public OutputFormat Format { get; set; } | ||
get | ||
{ | ||
var cmd = new Command("crd", "Generates CRDs for Kubernetes based on a solution or project.") | ||
{ | ||
Options.OutputFormat, | ||
Options.OutputPath, | ||
Options.SolutionProjectRegex, | ||
Options.TargetFramework, | ||
Arguments.SolutionOrProjectFile, | ||
}; | ||
cmd.AddAlias("crds"); | ||
cmd.AddAlias("c"); | ||
cmd.SetHandler(ctx => Handler(AnsiConsole.Console, ctx)); | ||
|
||
[Argument( | ||
0, | ||
Description = | ||
"Path to a *.csproj file to generate the CRD from. " + | ||
"If omitted, the current directory is searched for one and the command fails if none is found.")] | ||
public string? ProjectFile { get; set; } | ||
return cmd; | ||
} | ||
} | ||
|
||
public async Task<int> OnExecuteAsync() | ||
internal static async Task Handler(IAnsiConsole console, InvocationContext ctx) | ||
{ | ||
_result.Format = Format; | ||
var projectFile = ProjectFile ?? | ||
Directory.EnumerateFiles( | ||
Directory.GetCurrentDirectory(), | ||
"*.csproj") | ||
.FirstOrDefault(); | ||
if (projectFile == null) | ||
{ | ||
_output.WriteLine( | ||
"No *.csproj file found. Either specify one or run the command in a directory with one.", | ||
ConsoleColor.Red); | ||
return ExitCodes.Error; | ||
} | ||
var file = ctx.ParseResult.GetValueForArgument(Arguments.SolutionOrProjectFile); | ||
var outPath = ctx.ParseResult.GetValueForOption(Options.OutputPath); | ||
var format = ctx.ParseResult.GetValueForOption(Options.OutputFormat); | ||
|
||
_output.WriteLine($"Generate CRDs from project: {projectFile}."); | ||
var parser = file.Extension switch | ||
{ | ||
".csproj" => await AssemblyParser.ForProject(console, file), | ||
".sln" => await AssemblyParser.ForSolution( | ||
console, | ||
file, | ||
ctx.ParseResult.GetValueForOption(Options.SolutionProjectRegex), | ||
ctx.ParseResult.GetValueForOption(Options.TargetFramework)), | ||
_ => throw new NotSupportedException("Only *.csproj and *.sln files are supported."), | ||
}; | ||
var result = new ResultOutput(console, format); | ||
|
||
var parser = new ProjectParser(projectFile); | ||
var crds = Transpiler.Crds.Transpile(await parser.Entities().ToListAsync()).ToList(); | ||
console.WriteLine($"Generate CRDs for {file.Name}."); | ||
var crds = Transpiler.Crds.Transpile(parser.Entities()).ToList(); | ||
foreach (var crd in crds) | ||
{ | ||
_result.Add($"{crd.Metadata.Name.Replace('.', '_')}.{Format.ToString().ToLowerInvariant()}", crd); | ||
result.Add($"{crd.Metadata.Name.Replace('.', '_')}.{format.ToString().ToLowerInvariant()}", crd); | ||
} | ||
|
||
_result.Add( | ||
$"kustomization.{Format.ToString().ToLowerInvariant()}", | ||
result.Add( | ||
$"kustomization.{format.ToString().ToLowerInvariant()}", | ||
new KustomizationConfig | ||
{ | ||
Resources = crds | ||
.ConvertAll(crd => $"{crd.Metadata.Name.Replace('.', '_')}.{Format.ToString().ToLower()}"), | ||
.ConvertAll(crd => $"{crd.Metadata.Name.Replace('.', '_')}.{format.ToString().ToLower()}"), | ||
CommonLabels = new Dictionary<string, string> { { "operator-element", "crd" } }, | ||
}); | ||
|
||
if (OutputPath is not null) | ||
if (outPath is not null) | ||
{ | ||
await _result.Write(OutputPath); | ||
await result.Write(outPath); | ||
} | ||
else | ||
{ | ||
_result.Write(); | ||
result.Write(); | ||
} | ||
|
||
return ExitCodes.Success; | ||
ctx.ExitCode = ExitCodes.Success; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,24 @@ | ||
using McMaster.Extensions.CommandLineUtils; | ||
using System.CommandLine; | ||
using System.CommandLine.Help; | ||
|
||
namespace KubeOps.Cli.Commands.Generator; | ||
|
||
[Command("generator", "gen", "g", Description = "Generates elements related to an operator. (Aliases: gen, g)")] | ||
[Subcommand(typeof(CrdGenerator))] | ||
[Subcommand(typeof(RbacGenerator))] | ||
internal class Generator | ||
internal static class Generator | ||
{ | ||
public int OnExecute(CommandLineApplication app) | ||
public static Command Command | ||
{ | ||
app.ShowHelp(); | ||
return ExitCodes.UsageError; | ||
get | ||
{ | ||
var cmd = new Command("generator", "Generates elements related to an operator.") | ||
{ | ||
CrdGenerator.Command, | ||
RbacGenerator.Command, | ||
}; | ||
cmd.AddAlias("gen"); | ||
cmd.AddAlias("g"); | ||
cmd.SetHandler(ctx => ctx.HelpBuilder.Write(cmd, Console.Out)); | ||
|
||
return cmd; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,70 +1,65 @@ | ||
using KubeOps.Cli.Output; | ||
using KubeOps.Cli.SyntaxObjects; | ||
using System.CommandLine; | ||
using System.CommandLine.Help; | ||
using System.CommandLine.Invocation; | ||
|
||
using McMaster.Extensions.CommandLineUtils; | ||
using KubeOps.Abstractions.Kustomize; | ||
using KubeOps.Cli.Output; | ||
using KubeOps.Cli.Roslyn; | ||
|
||
using Spectre.Console; | ||
|
||
namespace KubeOps.Cli.Commands.Generator; | ||
|
||
[Command("rbac", "r", Description = "Generates rbac roles for the operator. (Aliases: r)")] | ||
internal class RbacGenerator | ||
internal static class RbacGenerator | ||
{ | ||
private readonly ConsoleOutput _output; | ||
private readonly ResultOutput _result; | ||
|
||
public RbacGenerator(ConsoleOutput output, ResultOutput result) | ||
{ | ||
_output = output; | ||
_result = result; | ||
} | ||
|
||
[Option( | ||
Description = "The path the command will write the files to. If empty, prints output to console.", | ||
LongName = "out")] | ||
public string? OutputPath { get; set; } | ||
|
||
[Option( | ||
CommandOptionType.SingleValue, | ||
Description = "Sets the output format for the generator.")] | ||
public OutputFormat Format { get; set; } | ||
|
||
[Argument( | ||
0, | ||
Description = | ||
"Path to a *.csproj file to generate the CRD from. " + | ||
"If omitted, the current directory is searched for one and the command fails if none is found.")] | ||
public string? ProjectFile { get; set; } | ||
|
||
public async Task<int> OnExecuteAsync() | ||
public static Command Command | ||
{ | ||
_result.Format = Format; | ||
var projectFile = ProjectFile ?? | ||
Directory.EnumerateFiles( | ||
Directory.GetCurrentDirectory(), | ||
"*.csproj") | ||
.FirstOrDefault(); | ||
if (projectFile == null) | ||
get | ||
{ | ||
_output.WriteLine( | ||
"No *.csproj file found. Either specify one or run the command in a directory with one.", | ||
ConsoleColor.Red); | ||
return ExitCodes.Error; | ||
var cmd = new Command("rbac", "Generates rbac roles for the operator project or solution.") | ||
{ | ||
Options.OutputFormat, | ||
Options.OutputPath, | ||
Options.SolutionProjectRegex, | ||
Options.TargetFramework, | ||
Arguments.SolutionOrProjectFile, | ||
}; | ||
cmd.AddAlias("r"); | ||
cmd.SetHandler(ctx => Handler(AnsiConsole.Console, ctx)); | ||
|
||
return cmd; | ||
} | ||
} | ||
|
||
_output.WriteLine($"Generate CRDs from project: {projectFile}."); | ||
internal static async Task Handler(IAnsiConsole console, InvocationContext ctx) | ||
{ | ||
var file = ctx.ParseResult.GetValueForArgument(Arguments.SolutionOrProjectFile); | ||
var outPath = ctx.ParseResult.GetValueForOption(Options.OutputPath); | ||
var format = ctx.ParseResult.GetValueForOption(Options.OutputFormat); | ||
|
||
var parser = new ProjectParser(projectFile); | ||
var attributes = await parser.RbacAttributes().ToListAsync(); | ||
_result.Add("file.yaml", Transpiler.Rbac.Transpile(attributes)); | ||
var parser = file.Extension switch | ||
{ | ||
".csproj" => await AssemblyParser.ForProject(console, file), | ||
".sln" => await AssemblyParser.ForSolution( | ||
console, | ||
file, | ||
ctx.ParseResult.GetValueForOption(Options.SolutionProjectRegex), | ||
ctx.ParseResult.GetValueForOption(Options.TargetFramework)), | ||
_ => throw new NotSupportedException("Only *.csproj and *.sln files are supported."), | ||
}; | ||
var result = new ResultOutput(console, format); | ||
console.WriteLine($"Generate RBAC roles for {file.Name}."); | ||
result.Add("file.yaml", Transpiler.Rbac.Transpile(parser.RbacAttributes())); | ||
|
||
if (OutputPath is not null) | ||
if (outPath is not null) | ||
{ | ||
await _result.Write(OutputPath); | ||
await result.Write(outPath); | ||
} | ||
else | ||
{ | ||
_result.Write(); | ||
result.Write(); | ||
} | ||
|
||
return ExitCodes.Success; | ||
ctx.ExitCode = ExitCodes.Success; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.