Skip to content

Commit 698dfd9

Browse files
committed
Implement CLI interface using Spectre.Console.Cli
- Added Spectre.Console and Spectre.Console.Cli packages - Implemented CompareCommand with source/target assembly nomenclature - Created TypeRegistrar for DI integration - Updated Program.cs to use Spectre.Console.Cli - Added comprehensive test coverage for CLI - Updated Taskfile.yml with test report generation - Updated README.md with CLI usage examples
1 parent dddb3dc commit 698dfd9

File tree

8 files changed

+194
-123
lines changed

8 files changed

+194
-123
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,4 @@ node_modules/
9090
*.binlog
9191

9292
# Local History for Visual Studio
93-
.localhistory/
93+
.localhistory/test-report/

.kiro/specs/dotnet-api-diff/tasks.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ Each task should follow this git workflow:
9292
- **Git Workflow**: Create branch `feature/task-4.3-change-classification`, commit, push, and create PR
9393
- _Requirements: 3.1, 7.1, 7.2, 7.3, 7.4_
9494

95-
- [ ] 5. Create CLI interface and argument parsing
96-
- [ ] 5.1 Implement command-line interface using System.CommandLine
95+
- [x] 5. Create CLI interface and argument parsing
96+
- [x] 5.1 Implement command-line interface using Spectre.Console.Cli
9797
- Create CLI commands and options for assembly paths, configuration, output format
9898
- Implement help text and usage examples
9999
- Write integration tests for CLI argument parsing
@@ -108,11 +108,12 @@ Each task should follow this git workflow:
108108
- _Requirements: 3.2, 3.3, 5.1, 5.2, 5.3, 5.4_
109109

110110
- [ ] 6. Implement output formatting and reporting
111-
- [ ] 6.1 Create console output formatter
112-
- Implement ConsoleFormatter with colored output for additions, removals, modifications
111+
- [ ] 6.1 Create ReportGenerator and console output formatter
112+
- Implement ReportGenerator interface with support for multiple formats
113+
- Create ConsoleFormatter with colored output for additions, removals, modifications
113114
- Add summary statistics and breaking change indicators
114115
- Write unit tests for console output formatting
115-
- **Git Workflow**: Create branch `feature/task-6.1-console-formatter`, commit, push, and create PR
116+
- **Git Workflow**: Create branch `feature/task-6.1-report-generator`, commit, push, and create PR
116117
- _Requirements: 4.4, 8.1, 8.4_
117118

118119
- [ ] 6.2 Implement JSON output formatter

README.md

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ task coverage
6666
task coverage:view
6767

6868
# Run the application with arguments
69-
task run -- --old old.dll --new new.dll
69+
task run -- compare source.dll target.dll
7070

7171
# Run CI build sequence
7272
task ci
@@ -76,22 +76,36 @@ task ci
7676

7777
```bash
7878
# Basic comparison
79-
dotnet run -- --old path/to/old/assembly.dll --new path/to/new/assembly.dll
79+
dotnet run -- compare source.dll target.dll
8080

8181
# Generate JSON report
82-
dotnet run -- --old old.dll --new new.dll --format json --output report.json
82+
dotnet run -- compare source.dll target.dll --output json
8383

84-
# Show only breaking changes
85-
dotnet run -- --old old.dll --new new.dll --breaking-only
84+
# Generate markdown report
85+
dotnet run -- compare source.dll target.dll --output markdown
86+
87+
# Filter to specific namespaces
88+
dotnet run -- compare source.dll target.dll --filter System.Collections
89+
90+
# Use configuration file
91+
dotnet run -- compare source.dll target.dll --config config.json
92+
93+
# Enable verbose output
94+
dotnet run -- compare source.dll target.dll --verbose
8695
```
8796

8897
## Command Line Options
8998

90-
- `--old, -o`: Path to the original assembly
91-
- `--new, -n`: Path to the new assembly to compare against
92-
- `--format, -f`: Output format (console, json, xml, html, markdown)
93-
- `--output, -out`: Output file path (optional, defaults to console)
94-
- `--breaking-only, -b`: Show only breaking changes
99+
### Compare Command
100+
101+
- `<sourceAssembly>`: Path to the source/baseline assembly (required)
102+
- `<targetAssembly>`: Path to the target/current assembly (required)
103+
- `--config, -c`: Path to configuration file
104+
- `--output, -o`: Output format (console, json, markdown)
105+
- `--filter, -f`: Filter to specific namespaces (can be specified multiple times)
106+
- `--exclude, -e`: Exclude types matching pattern (can be specified multiple times)
107+
- `--no-color`: Disable colored output
108+
- `--verbose, -v`: Enable verbose output
95109
- `--help, -h`: Show help information
96110

97111
## Project Structure

Taskfile.yml

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ vars:
88
PROJECT: src/DotNetApiDiff/DotNetApiDiff.csproj
99
TEST_PROJECT: tests/DotNetApiDiff.Tests/DotNetApiDiff.Tests.csproj
1010
COVERAGE_DIR: coverage-report
11-
REPORT_DIR: reports
11+
TEST_REPORT_DIR: test-report
1212

1313
tasks:
1414
default:
@@ -52,6 +52,41 @@ tasks:
5252
cmds:
5353
- dotnet test {{.TEST_PROJECT}} --filter "Category=Integration" --configuration Release
5454

55+
test:report:
56+
desc: Run tests with detailed HTML report
57+
cmds:
58+
- mkdir -p {{.TEST_REPORT_DIR}}
59+
- dotnet test {{.TEST_PROJECT}} --results-directory {{.TEST_REPORT_DIR}} --logger "html;LogFileName=test-results.html"
60+
- echo "Test report generated in {{.TEST_REPORT_DIR}}/test-results.html"
61+
62+
ensure-test-report:
63+
desc: Ensure test report exists
64+
internal: true
65+
cmds:
66+
- |
67+
if [ ! -f "{{.TEST_REPORT_DIR}}/test-results.html" ]; then
68+
echo "Test report not found. Generating it now..."
69+
task test:report
70+
fi
71+
72+
test:report:view:
73+
desc: Open test report in default browser (generates report if it doesn't exist)
74+
cmds:
75+
- task: ensure-test-report
76+
- |
77+
{{if eq OS "windows"}}
78+
cmd.exe /c start {{.TEST_REPORT_DIR}}/test-results.html
79+
{{else if eq OS "darwin"}}
80+
open {{.TEST_REPORT_DIR}}/test-results.html
81+
{{else}}
82+
xdg-open {{.TEST_REPORT_DIR}}/test-results.html
83+
{{end}}
84+
85+
test:watch:
86+
desc: Run tests in watch mode (rerun on file changes)
87+
cmds:
88+
- dotnet watch test --project {{.TEST_PROJECT}}
89+
5590
coverage:
5691
desc: Generate code coverage report
5792
cmds:
@@ -65,9 +100,28 @@ tasks:
65100
- dotnet tool restore
66101
- dotnet reportgenerator -reports:tests/DotNetApiDiff.Tests/coverage.info -targetdir:{{.COVERAGE_DIR}} -reporttypes:Html
67102

103+
coverage:report:detailed:
104+
desc: Generate detailed HTML report with multiple formats
105+
cmds:
106+
- dotnet tool restore
107+
- dotnet reportgenerator -reports:tests/DotNetApiDiff.Tests/coverage.info -targetdir:{{.COVERAGE_DIR}} -reporttypes:"Html;HtmlSummary;Cobertura;TextSummary"
108+
- echo "Coverage report generated in {{.COVERAGE_DIR}}"
109+
- cat {{.COVERAGE_DIR}}/Summary.txt
110+
111+
ensure-coverage-report:
112+
desc: Ensure coverage report exists
113+
internal: true
114+
cmds:
115+
- |
116+
if [ ! -f "{{.COVERAGE_DIR}}/index.html" ]; then
117+
echo "Coverage report not found. Generating it now..."
118+
task coverage
119+
fi
120+
68121
coverage:view:
69-
desc: Open coverage report in default browser
122+
desc: Open coverage report in default browser (generates report if it doesn't exist)
70123
cmds:
124+
- task: ensure-coverage-report
71125
- |
72126
{{if eq OS "windows"}}
73127
cmd.exe /c start {{.COVERAGE_DIR}}/index.html

src/DotNetApiDiff/Commands/CompareCommand.cs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ namespace DotNetApiDiff.Commands;
1616
/// </summary>
1717
public class CompareCommandSettings : CommandSettings
1818
{
19-
[CommandArgument(0, "<oldAssembly>")]
20-
[Description("Path to the old/baseline assembly")]
21-
public required string OldAssemblyPath { get; init; }
19+
[CommandArgument(0, "<sourceAssembly>")]
20+
[Description("Path to the source/baseline assembly")]
21+
public required string SourceAssemblyPath { get; init; }
2222

23-
[CommandArgument(1, "<newAssembly>")]
24-
[Description("Path to the new/current assembly")]
25-
public required string NewAssemblyPath { get; init; }
23+
[CommandArgument(1, "<targetAssembly>")]
24+
[Description("Path to the target/current assembly")]
25+
public required string TargetAssemblyPath { get; init; }
2626

2727
[CommandOption("-c|--config <configFile>")]
2828
[Description("Path to configuration file")]
@@ -76,16 +76,16 @@ public CompareCommand(IServiceProvider serviceProvider)
7676
/// <returns>ValidationResult indicating success or failure</returns>
7777
public override ValidationResult Validate([NotNull] CommandContext context, [NotNull] CompareCommandSettings settings)
7878
{
79-
// Validate old assembly path
80-
if (!File.Exists(settings.OldAssemblyPath))
79+
// Validate source assembly path
80+
if (!File.Exists(settings.SourceAssemblyPath))
8181
{
82-
return ValidationResult.Error($"Old assembly file not found: {settings.OldAssemblyPath}");
82+
return ValidationResult.Error($"Source assembly file not found: {settings.SourceAssemblyPath}");
8383
}
8484

85-
// Validate new assembly path
86-
if (!File.Exists(settings.NewAssemblyPath))
85+
// Validate target assembly path
86+
if (!File.Exists(settings.TargetAssemblyPath))
8787
{
88-
return ValidationResult.Error($"New assembly file not found: {settings.NewAssemblyPath}");
88+
return ValidationResult.Error($"Target assembly file not found: {settings.TargetAssemblyPath}");
8989
}
9090

9191
// Validate config file if specified
@@ -152,23 +152,23 @@ public override int Execute([NotNull] CommandContext context, [NotNull] CompareC
152152
}
153153

154154
// Load assemblies
155-
logger.LogInformation("Loading old assembly: {Path}", settings.OldAssemblyPath);
156-
logger.LogInformation("Loading new assembly: {Path}", settings.NewAssemblyPath);
155+
logger.LogInformation("Loading source assembly: {Path}", settings.SourceAssemblyPath);
156+
logger.LogInformation("Loading target assembly: {Path}", settings.TargetAssemblyPath);
157157

158158
var assemblyLoader = _serviceProvider.GetRequiredService<IAssemblyLoader>();
159-
var oldAssembly = assemblyLoader.LoadAssembly(settings.OldAssemblyPath);
160-
var newAssembly = assemblyLoader.LoadAssembly(settings.NewAssemblyPath);
159+
var sourceAssembly = assemblyLoader.LoadAssembly(settings.SourceAssemblyPath);
160+
var targetAssembly = assemblyLoader.LoadAssembly(settings.TargetAssemblyPath);
161161

162162
// Extract API information
163163
logger.LogInformation("Extracting API information from assemblies");
164164
var apiExtractor = _serviceProvider.GetRequiredService<IApiExtractor>();
165-
var oldApi = apiExtractor.ExtractApiMembers(oldAssembly);
166-
var newApi = apiExtractor.ExtractApiMembers(newAssembly);
165+
var sourceApi = apiExtractor.ExtractApiMembers(sourceAssembly);
166+
var targetApi = apiExtractor.ExtractApiMembers(targetAssembly);
167167

168168
// Compare APIs
169169
logger.LogInformation("Comparing APIs");
170170
var apiComparer = _serviceProvider.GetRequiredService<IApiComparer>();
171-
var comparisonResult = apiComparer.CompareAssemblies(oldAssembly, newAssembly);
171+
var comparisonResult = apiComparer.CompareAssemblies(sourceAssembly, targetAssembly);
172172

173173
// Create ApiComparison from ComparisonResult
174174
var comparison = new Models.ApiComparison

src/DotNetApiDiff/DotNetApiDiff.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
1313
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" />
1414
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
15+
<PackageReference Include="Spectre.Console" Version="0.48.0" />
16+
<PackageReference Include="Spectre.Console.Cli" Version="0.48.0" />
1517
<PackageReference Include="System.Reflection.Metadata" Version="8.0.1" />
1618
</ItemGroup>
1719

src/DotNetApiDiff/Program.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ public static int Main(string[] args)
3737
{
3838
config.SetApplicationName("dotnet-api-diff");
3939

40-
config.AddExample(new[] { "compare", "old.dll", "new.dll" });
41-
config.AddExample(new[] { "compare", "old.dll", "new.dll", "--output", "json" });
42-
config.AddExample(new[] { "compare", "old.dll", "new.dll", "--config", "config.json" });
40+
config.AddExample(new[] { "compare", "source.dll", "target.dll" });
41+
config.AddExample(new[] { "compare", "source.dll", "target.dll", "--output", "json" });
42+
config.AddExample(new[] { "compare", "source.dll", "target.dll", "--config", "config.json" });
4343

4444
config.SetExceptionHandler(ex =>
4545
{
@@ -50,9 +50,9 @@ public static int Main(string[] args)
5050
// Register the compare command
5151
config.AddCommand<CompareCommand>("compare")
5252
.WithDescription("Compare two .NET assemblies and report API differences")
53-
.WithExample(new[] { "compare", "old.dll", "new.dll" })
54-
.WithExample(new[] { "compare", "old.dll", "new.dll", "--output", "json" })
55-
.WithExample(new[] { "compare", "old.dll", "new.dll", "--filter", "System.Collections" });
53+
.WithExample(new[] { "compare", "source.dll", "target.dll" })
54+
.WithExample(new[] { "compare", "source.dll", "target.dll", "--output", "json" })
55+
.WithExample(new[] { "compare", "source.dll", "target.dll", "--filter", "System.Collections" });
5656
});
5757

5858
return app.Run(args);

0 commit comments

Comments
 (0)