-
Notifications
You must be signed in to change notification settings - Fork 8
Updated to allow a PATH argument in the executioin of a command #23
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
Merged
Merged
Updated to allow a PATH argument in the executioin of a command #23
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
c39402c
Updated to allow a PATH argument in the executioin of a command
csharpfritz 84f4a18
converted commandArgument to a record
csharpfritz 12a49f0
Add path argument handling and related unit tests for plugins
csharpfritz 106bbac
Updated docs
csharpfritz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 |
|---|---|---|
|
|
@@ -50,6 +50,8 @@ dotnet run -- --help | |
| - Bill of Materials (BOM) generation | ||
| - NuGet package vulnerability scanning | ||
| - Multiple output formats (console, markdown) | ||
| - Path argument support (`-p` / `--path`) for all analysis commands | ||
| - Command-specific help with argument documentation | ||
|
|
||
| ## Current Commands | ||
|
|
||
|
|
@@ -61,13 +63,13 @@ codemedic --version # Show version | |
|
|
||
| # Analysis commands | ||
| codemedic health # Repository health dashboard | ||
| codemedic health --format markdown | ||
| codemedic health -p /path/to/repo --format markdown | ||
|
|
||
| codemedic bom # Bill of Materials | ||
| codemedic bom --format md > bom.md | ||
| codemedic bom --path /path/to/repo --format md > bom.md | ||
|
|
||
| codemedic vulnerabilities # Scan for NuGet vulnerabilities | ||
| codemedic vulnerabilities --format markdown > vulns.md | ||
| codemedic vulnerabilities -p /path/to/repo --format markdown > vulns.md | ||
| ``` | ||
|
|
||
| ## Technology Stack | ||
|
|
||
This file contains hidden or 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 |
|---|---|---|
|
|
@@ -231,6 +231,123 @@ public class PluginMetadata | |
|
|
||
| --- | ||
|
|
||
| ## Command Registration System | ||
|
|
||
| Analysis engine plugins can register CLI commands with arguments using the `RegisterCommands()` method. This enables plugins to expose their functionality through dedicated CLI commands with proper argument parsing and help integration. | ||
|
|
||
| ### CommandRegistration & CommandArgument | ||
|
|
||
| ```csharp | ||
| namespace CodeMedic.Abstractions.Plugins; | ||
|
|
||
|
/// |
||
| /// Represents a command that can be registered with the CLI. | ||
| /// | ||
| public class CommandRegistration | ||
| { | ||
| public required string Name { get; init; } // Command name (e.g., "health") | ||
| public required string Description { get; init; } // Command description for help | ||
|
public required Func |
||
| public string[]? Examples { get; init; } // Usage examples | ||
| public CommandArgument[]? Arguments { get; init; } // Command arguments | ||
| } | ||
|
|
||
|
/// |
||
| /// Represents a command-line argument specification. | ||
| /// | ||
| public record CommandArgument( | ||
| string Description, // Required: what this argument does | ||
| string? ShortName = null, // Short form: "p" for "-p" | ||
| string? LongName = null, // Long form: "path" for "--path" | ||
| bool IsRequired = false, // Whether argument is mandatory | ||
| bool HasValue = true, // Whether argument takes a value | ||
| string? DefaultValue = null, // Default value description | ||
| string? ValueName = null); // Value type for help display | ||
| ``` | ||
|
|
||
| ### Command Registration Features | ||
|
|
||
| - **Automatic Help Integration**: Commands with arguments automatically appear in `--help` output | ||
| - **Command-Specific Help**: `codemedic mycommand --help` shows detailed argument information | ||
| - **Argument Parsing**: Use built-in utilities like `IdentifyTargetPathFromArgs()` for common patterns | ||
| - **Rich Help Display**: Arguments show descriptions, default values, and usage examples | ||
|
|
||
| ### Built-in Path Argument Support | ||
|
|
||
| All current analysis plugins support the standard path argument pattern: | ||
|
|
||
| ```bash | ||
| # Current directory (default) | ||
| codemedic health | ||
|
|
||
| # Specific path (short form) | ||
| codemedic health -p /path/to/repo | ||
|
|
||
| # Specific path (long form) | ||
| codemedic health --path /path/to/repo | ||
|
|
||
| # Combined with format | ||
| codemedic bom -p ../other-project --format markdown | ||
| ``` | ||
|
|
||
| ### Example: Plugin with Command Registration | ||
|
|
||
| ```csharp | ||
| public class MyAnalysisPlugin : IAnalysisEnginePlugin | ||
| { | ||
| // ... other plugin implementation ... | ||
|
|
||
| public CommandRegistration[]? RegisterCommands() | ||
| { | ||
| return | ||
| [ | ||
| new CommandRegistration | ||
| { | ||
| Name = "myanalysis", | ||
| Description = "Run my custom analysis", | ||
| Handler = ExecuteMyAnalysisAsync, | ||
| Arguments = | ||
| [ | ||
| new CommandArgument( | ||
| Description: "Path to the repository to analyze", | ||
| ShortName: "p", | ||
| LongName: "path", | ||
| ValueName: "path", | ||
| DefaultValue: "current directory"), | ||
| new CommandArgument( | ||
| Description: "Enable verbose output", | ||
| ShortName: "v", | ||
| LongName: "verbose", | ||
| HasValue: false) // Flag argument | ||
| ], | ||
| Examples = | ||
| [ | ||
| "codemedic myanalysis", | ||
| "codemedic myanalysis -p /path/to/repo", | ||
| "codemedic myanalysis --path /path/to/repo --verbose", | ||
| "codemedic myanalysis -p . -v --format markdown" | ||
| ] | ||
| } | ||
| ]; | ||
| } | ||
|
|
||
|
private async Task |
||
| { | ||
| // Parse path argument using built-in extension | ||
| var targetPath = args.IdentifyTargetPathFromArgs(); | ||
|
|
||
| // Run analysis on the specified path | ||
| var result = await AnalyzeAsync(targetPath); | ||
|
|
||
| // Render results | ||
| renderer.RenderReport(result); | ||
| return 0; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Plugin Development Workflow | ||
|
|
||
| ### Step 1: Create the Plugin Project | ||
|
|
||
This file contains hidden or 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,3 +1,5 @@ | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace CodeMedic.Abstractions.Plugins; | ||
|
|
||
|
/// |
||
|
|
@@ -24,4 +26,28 @@ public class CommandRegistration | |
| /// Gets or sets example usage strings for help text. | ||
| /// | ||
| public string[]? Examples { get; init; } | ||
|
|
||
|
/// |
||
| /// Gets or sets the command arguments specification. | ||
| /// | ||
| public CommandArgument[]? Arguments { get; init; } | ||
| } | ||
|
|
||
|
/// |
||
| /// Represents a command-line argument specification. | ||
| /// | ||
| /// The description of what this argument does. | ||
| /// The short name of the argument (e.g., "p" for "-p"). | ||
| /// The long name of the argument (e.g., "path" for "--path"). | ||
| /// Whether this argument is required. | ||
| /// Whether this argument takes a value. | ||
| /// The default value for this argument. | ||
| /// The value type name for help display (e.g., "path", "format", "count"). | ||
| public record CommandArgument( | ||
| string Description, | ||
| string? ShortName = null, | ||
| string? LongName = null, | ||
| bool IsRequired = false, | ||
| bool HasValue = true, | ||
| string? DefaultValue = null, | ||
| string? ValueName = null); | ||
This file contains hidden or 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 |
|---|---|---|
|
|
@@ -29,8 +29,8 @@ public static async Task |
|
| _pluginLoader = new PluginLoader(); | ||
| await _pluginLoader.LoadInternalPluginsAsync(); | ||
|
|
||
| // No arguments or help requested | ||
| if (args.Length == 0 || args.Contains("--help") || args.Contains("-h") || args.Contains("help")) | ||
| // No arguments or general help requested | ||
| if (args.Length == 0 || args[0] == "--help" || args[0] == "-h" || args[0] == "help") | ||
| { | ||
| console.RenderBanner(version); | ||
| RenderHelp(); | ||
|
|
@@ -51,6 +51,15 @@ public static async Task |
|
|
|
||
| if (commandRegistration != null) | ||
| { | ||
| // Check for command-specific help | ||
| var commandArgs = args.Skip(1).ToArray(); | ||
| if (commandArgs.Contains("--help") || commandArgs.Contains("-h")) | ||
| { | ||
| console.RenderBanner(version); | ||
| RenderCommandHelp(commandRegistration); | ||
| return 0; | ||
| } | ||
|
|
||
| // Parse --format argument (default: console) | ||
| string format = "console"; | ||
| var commandArgsList = args.Skip(1).ToList(); | ||
|
|
@@ -114,10 +123,36 @@ private static void RenderHelp() | |
| AnsiConsole.MarkupLine(" [green]codemedic[/] [cyan]--version[/]"); | ||
| AnsiConsole.WriteLine(); | ||
|
|
||
| AnsiConsole.MarkupLine("[dim]Options:[/]"); | ||
| AnsiConsole.MarkupLine("[dim]Global Options:[/]"); | ||
|
AnsiConsole.MarkupLine(" [yellow]--format |
||
| AnsiConsole.WriteLine(); | ||
|
|
||
| // Show command-specific arguments | ||
| if (_pluginLoader != null) | ||
| { | ||
| foreach (var command in _pluginLoader.Commands.Values.OrderBy(c => c.Name)) | ||
| { | ||
| if (command.Arguments != null && command.Arguments.Length > 0) | ||
| { | ||
| AnsiConsole.MarkupLine($"[dim]{command.Name} Command Options:[/]"); | ||
|
|
||
| foreach (var arg in command.Arguments) | ||
| { | ||
| var shortName = !string.IsNullOrEmpty(arg.ShortName) ? $"-{arg.ShortName}" : ""; | ||
| var longName = !string.IsNullOrEmpty(arg.LongName) ? $"--{arg.LongName}" : ""; | ||
| var names = string.Join(", ", new[] { shortName, longName }.Where(s => !string.IsNullOrEmpty(s))); | ||
|
|
||
| var valuePart = arg.HasValue && !string.IsNullOrEmpty(arg.ValueName) ? $" <{arg.ValueName}>" : ""; | ||
| var requiredIndicator = arg.IsRequired ? " [red](required)[/]" : ""; | ||
| var defaultPart = !string.IsNullOrEmpty(arg.DefaultValue) ? $" (default: {arg.DefaultValue})" : ""; | ||
|
|
||
| AnsiConsole.MarkupLine($" [yellow]{names}{valuePart}[/] {arg.Description}{requiredIndicator}{defaultPart}"); | ||
| } | ||
| AnsiConsole.WriteLine(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| AnsiConsole.MarkupLine("[dim]Examples:[/]"); | ||
|
|
||
| // Display examples from plugins | ||
|
|
@@ -138,6 +173,77 @@ private static void RenderHelp() | |
| AnsiConsole.MarkupLine(" [green]codemedic --version[/]"); | ||
| } | ||
|
|
||
|
/// |
||
| /// Renders help text for a specific command. | ||
| /// | ||
| private static void RenderCommandHelp(CodeMedic.Abstractions.Plugins.CommandRegi stration command) | ||
| { | ||
| AnsiConsole.MarkupLine($"[bold]Command: {command.Name}[/]"); | ||
| AnsiConsole.WriteLine(); | ||
| AnsiConsole.MarkupLine($"{command.Description}"); | ||
| AnsiConsole.WriteLine(); | ||
|
|
||
| AnsiConsole.MarkupLine("[dim]Usage:[/]"); | ||
| var usage = $"codemedic {command.Name}"; | ||
|
|
||
| if (command.Arguments != null && command.Arguments.Length > 0) | ||
| { | ||
| foreach (var arg in command.Arguments) | ||
| { | ||
| var argName = !string.IsNullOrEmpty(arg.LongName) ? $"--{arg.LongName}" : $"-{arg.ShortName}"; | ||
| var valuePart = arg.HasValue && !string.IsNullOrEmpty(arg.ValueName) ? $" <{arg.ValueName}>" : ""; | ||
| var optionalWrapper = arg.IsRequired ? "" : "[ ]"; | ||
|
|
||
| if (arg.IsRequired) | ||
| { | ||
| usage += $" {argName}{valuePart}"; | ||
| } | ||
| else | ||
| { | ||
| usage += $" [{argName}{valuePart}]"; | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
usage += " [--format |
||
| AnsiConsole.MarkupLine($" [green]{usage.EscapeMarkup()}[/]"); | ||
| AnsiConsole.WriteLine(); | ||
|
|
||
| if (command.Arguments != null && command.Arguments.Length > 0) | ||
| { | ||
| AnsiConsole.MarkupLine("[dim]Command Options:[/]"); | ||
|
|
||
| foreach (var arg in command.Arguments) | ||
| { | ||
| var shortName = !string.IsNullOrEmpty(arg.ShortName) ? $"-{arg.ShortName}" : ""; | ||
| var longName = !string.IsNullOrEmpty(arg.LongName) ? $"--{arg.LongName}" : ""; | ||
| var names = string.Join(", ", new[] { shortName, longName }.Where(s => !string.IsNullOrEmpty(s))); | ||
|
|
||
| var valuePart = arg.HasValue && !string.IsNullOrEmpty(arg.ValueName) ? $" <{arg.ValueName}>" : ""; | ||
| var requiredIndicator = arg.IsRequired ? " [red](required)[/]" : ""; | ||
| var defaultPart = !string.IsNullOrEmpty(arg.DefaultValue) ? $" (default: {arg.DefaultValue})" : ""; | ||
|
|
||
| AnsiConsole.MarkupLine($" [yellow]{(names + valuePart).EscapeMarkup()}[/] {arg.Description.EscapeMarkup()}{requiredIndicator}{defaultPart.EscapeMarkup()}"); | ||
| } | ||
| AnsiConsole.WriteLine(); | ||
| } | ||
|
|
||
| AnsiConsole.MarkupLine("[dim]Global Options:[/]"); | ||
|
AnsiConsole.MarkupLine(" [yellow]--format |
||
| AnsiConsole.MarkupLine(" [yellow]-h, --help[/] Show this help message"); | ||
| AnsiConsole.WriteLine(); | ||
|
|
||
| if (command.Examples != null && command.Examples.Length > 0) | ||
| { | ||
| AnsiConsole.MarkupLine("[dim]Examples:[/]"); | ||
| foreach (var example in command.Examples) | ||
| { | ||
| AnsiConsole.MarkupLine($" [green]{example.EscapeMarkup()}[/]"); | ||
| } | ||
| AnsiConsole.WriteLine(); | ||
| } | ||
| } | ||
|
|
||
|
/// |
||
| /// Renders information about loaded plugins. | ||
| /// | ||
|
|
||
This file contains hidden or 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 |
|---|---|---|
|
|
@@ -60,10 +60,21 @@ public async Task | |
| Name = "bom", | ||
| Description = "Generate bill of materials report", | ||
| Handler = ExecuteBomCommandAsync, | ||
| Arguments = | ||
| [ | ||
| new CommandArgument( | ||
| Description: "Path to the repository to analyze", | ||
| ShortName: "p", | ||
| LongName: "path", | ||
| HasValue: true, | ||
| ValueName: "path", | ||
| DefaultValue: "current directory") | ||
| ], | ||
| Examples = | ||
| [ | ||
| "codemedic bom", | ||
| "codemedic bom --format markdown", | ||
| "codemedic bom -p /path/to/repo", | ||
| "codemedic bom --path /path/to/repo --format markdown", | ||
| "codemedic bom --format md > bom.md" | ||
| ] | ||
| } | ||
|
|
||
This file contains hidden or 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 |
|---|---|---|
|
|
@@ -56,10 +56,21 @@ public async Task | |
| Name = "health", | ||
| Description = "Display repository health dashboard", | ||
| Handler = ExecuteHealthCommandAsync, | ||
| Arguments = | ||
| [ | ||
| new CommandArgument( | ||
| Description: "Path to the repository to analyze", | ||
| ShortName: "p", | ||
| LongName: "path", | ||
| HasValue: true, | ||
| ValueName: "path", | ||
| DefaultValue: "current directory") | ||
| ], | ||
| Examples = | ||
| [ | ||
| "codemedic health", | ||
| "codemedic health --format markdown", | ||
| "codemedic health -p /path/to/repo", | ||
| "codemedic health --path /path/to/repo --format markdown", | ||
| "codemedic health --format md > report.md" | ||
| ] | ||
| } | ||
|
|
@@ -71,14 +82,7 @@ private async Task |
|
| try | ||
| { | ||
| // Parse arguments (target path only) | ||
| string? targetPath = null; | ||
| for (int i = 0; i < args.Length; i++) | ||
| { | ||
| if (!args[i].StartsWith("--")) | ||
| { | ||
| targetPath = args[i]; | ||
| } | ||
| } | ||
| string? targetPath = args.IdentifyTargetPathFromArgs(); | ||
|
|
||
| _limitPackageLists = renderer is ConsoleRenderer; | ||
|
|
||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.