diff --git a/docs/BETA5-MIGRATION-EXAMPLE.cs b/docs/BETA5-MIGRATION-EXAMPLE.cs
new file mode 100644
index 0000000..79b3a32
--- /dev/null
+++ b/docs/BETA5-MIGRATION-EXAMPLE.cs
@@ -0,0 +1,138 @@
+// Example: System.CommandLine 2.0.0-beta5 Migration Pattern
+// This file demonstrates how the updated CommandHandler base class
+// will work with the new beta5 API patterns.
+
+using System.CommandLine;
+using System.CommandLine.Parsing;
+using Microsoft.Extensions.Hosting;
+
+namespace GenAIDBExplorer.Console.Examples;
+
+///
+/// Example showing how SetupCommand methods will work with beta5 API
+///
+public static class Beta5MigrationExample
+{
+ ///
+ /// Example of how the old beta4 SetupCommand method looked
+ ///
+ public static Command SetupCommand_Beta4_Pattern(IHost host)
+ {
+ var projectPathOption = new Option(
+ aliases: ["--project", "-p"],
+ description: "The path to the GenAI Database Explorer project."
+ )
+ {
+ IsRequired = true // Beta4 syntax
+ };
+
+ var command = new Command("example", "Example command");
+ command.AddOption(projectPathOption); // Beta4 syntax
+
+ // Beta4 handler pattern
+ command.SetHandler(async (DirectoryInfo projectPath) =>
+ {
+ var handler = host.Services.GetRequiredService();
+ var options = new ExampleCommandHandlerOptions(projectPath);
+ await handler.HandleAsync(options); // Uses TOptions
+ }, projectPathOption);
+
+ return command;
+ }
+
+ ///
+ /// Example of how the new beta5 SetupCommand method will look
+ ///
+ public static Command SetupCommand_Beta5_Pattern(IHost host)
+ {
+ var projectPathOption = new Option("--project", "-p")
+ {
+ Description = "The path to the GenAI Database Explorer project.",
+ Required = true // Beta5 syntax
+ };
+
+ var command = new Command("example", "Example command");
+ command.Options.Add(projectPathOption); // Beta5 syntax
+
+ // Beta5 handler pattern
+ command.SetAction(async (parseResult) =>
+ {
+ var handler = host.Services.GetRequiredService();
+ await handler.HandleAsync(parseResult); // Uses ParseResult - NEW!
+ });
+
+ return command;
+ }
+}
+
+///
+/// Example command handler showing both old and new patterns
+///
+public class ExampleCommandHandler : CommandHandler
+{
+ // Constructor would go here...
+
+ ///
+ /// Legacy method - continues to work as before
+ ///
+ public override async Task HandleAsync(ExampleCommandHandlerOptions commandOptions)
+ {
+ // Original implementation stays the same
+ var projectPath = commandOptions.ProjectPath;
+ // ... handle command logic
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// New method for beta5 compatibility - automatically provided by base class
+ /// The base class HandleAsync(ParseResult) calls this method to extract options
+ ///
+ protected override ExampleCommandHandlerOptions ExtractCommandOptions(ParseResult parseResult)
+ {
+ // Use utility methods from base class to extract values
+ var projectPath = GetOptionValue(parseResult, "--project");
+ return new ExampleCommandHandlerOptions(projectPath);
+ }
+}
+
+///
+/// Options class - no changes needed
+///
+public class ExampleCommandHandlerOptions : ICommandHandlerOptions
+{
+ public ExampleCommandHandlerOptions(DirectoryInfo projectPath)
+ {
+ ProjectPath = projectPath;
+ }
+
+ public DirectoryInfo ProjectPath { get; }
+}
+
+/*
+MIGRATION SUMMARY:
+
+WHAT CHANGES:
+1. SetupCommand methods update to use:
+ - Required = true instead of IsRequired = true
+ - Options.Add() instead of AddOption()
+ - SetAction() instead of SetHandler()
+ - ParseResult parameter instead of individual parameters
+
+2. Command handlers implement:
+ - ExtractCommandOptions(ParseResult) method
+ - Use GetOptionValue() utility methods
+
+WHAT STAYS THE SAME:
+1. HandleAsync(TOptions) method - no changes
+2. Options classes - no changes
+3. Command logic - no changes
+4. Dependency injection - no changes
+5. Tests - existing tests continue to work
+
+BENEFITS:
+- Zero breaking changes to existing code
+- Forward compatibility with beta5
+- Type safety preserved
+- Easy migration path
+- Better performance with beta5
+*/
\ No newline at end of file
diff --git a/docs/COMMANDLINE-BETA5-MIGRATION.md b/docs/COMMANDLINE-BETA5-MIGRATION.md
new file mode 100644
index 0000000..7845437
--- /dev/null
+++ b/docs/COMMANDLINE-BETA5-MIGRATION.md
@@ -0,0 +1,144 @@
+# System.CommandLine 2.0.0-beta5 Migration Guide
+
+This document outlines the changes made to support System.CommandLine 2.0.0-beta5 in the CommandHandler base class and provides guidance for developers working with command handlers.
+
+## Overview
+
+The CommandHandler base class has been updated to support both the legacy pattern (using strongly-typed options) and the new System.CommandLine 2.0.0-beta5 pattern (using ParseResult).
+
+## Key Changes
+
+### 1. ICommandHandler Interface
+
+The interface now includes two overloads:
+
+```csharp
+public interface ICommandHandler where TOptions : ICommandHandlerOptions
+{
+ // Legacy method - continues to work as before
+ Task HandleAsync(TOptions commandOptions);
+
+ // New method for beta5 compatibility
+ Task HandleAsync(ParseResult parseResult);
+}
+```
+
+### 2. CommandHandler Base Class
+
+The base class provides:
+
+- **Backward Compatibility**: Existing `HandleAsync(TOptions)` method continues to work
+- **Forward Compatibility**: New `HandleAsync(ParseResult)` method that delegates to the legacy method
+- **Extraction Framework**: `ExtractCommandOptions(ParseResult)` method for converting ParseResult to strongly-typed options
+- **Utility Methods**: Helper methods for extracting values from ParseResult
+
+### 3. Utility Methods
+
+The base class provides several utility methods for working with ParseResult:
+
+```csharp
+// Extract option value by Option reference
+protected static T GetOptionValue(ParseResult parseResult, Option option)
+
+// Extract option value by name
+protected static T GetOptionValue(ParseResult parseResult, string optionName)
+
+// Check if option was provided
+protected static bool HasOption(ParseResult parseResult, Option option)
+```
+
+## Implementation Pattern
+
+### For Command Handlers
+
+Each command handler should implement the `ExtractCommandOptions` method:
+
+```csharp
+public class MyCommandHandler : CommandHandler
+{
+ // ... constructor and existing HandleAsync(MyCommandHandlerOptions) method ...
+
+ protected override MyCommandHandlerOptions ExtractCommandOptions(ParseResult parseResult)
+ {
+ var projectPath = GetOptionValue(parseResult, "--project");
+ var someFlag = GetOptionValue(parseResult, "--some-flag");
+
+ return new MyCommandHandlerOptions(projectPath, someFlag);
+ }
+}
+```
+
+### For SetupCommand Methods (Future Migration)
+
+When migrating SetupCommand methods to beta5 (in issues #18-#24), the pattern will change from:
+
+**Beta4 Pattern:**
+```csharp
+command.SetHandler(async (DirectoryInfo projectPath, bool flag) =>
+{
+ var handler = host.Services.GetRequiredService();
+ var options = new MyCommandHandlerOptions(projectPath, flag);
+ await handler.HandleAsync(options);
+}, projectPathOption, flagOption);
+```
+
+**Beta5 Pattern:**
+```csharp
+command.SetAction(async (parseResult) =>
+{
+ var handler = host.Services.GetRequiredService();
+ await handler.HandleAsync(parseResult);
+});
+```
+
+## Migration Status
+
+### Completed
+- ✅ ICommandHandler interface updated with ParseResult overload
+- ✅ CommandHandler base class updated with extraction framework
+- ✅ InitProjectCommandHandler updated with ExtractCommandOptions
+- ✅ ExtractModelCommandHandler updated with ExtractCommandOptions
+- ✅ QueryModelCommandHandler updated with ExtractCommandOptions
+
+### Pending (Future Issues)
+- ⏳ DataDictionaryCommandHandler (Issue #18)
+- ⏳ EnrichModelCommandHandler (Issue #19)
+- ⏳ ExportModelCommandHandler (Issue #20)
+- ⏳ ShowObjectCommandHandler (Issue #21)
+- ⏳ Update SetupCommand methods to use SetAction (Issues #22-#24)
+
+## Benefits
+
+1. **Zero Breaking Changes**: Existing code continues to work unchanged
+2. **Future Ready**: New ParseResult pattern is ready for beta5 API usage
+3. **Consistent Pattern**: All command handlers follow the same extraction pattern
+4. **Type Safety**: Strongly-typed options are preserved throughout the application
+5. **Testability**: Both patterns can be easily unit tested
+
+## Testing
+
+The new functionality includes test coverage:
+
+```csharp
+[TestMethod]
+public async Task HandleAsync_WithParseResult_ShouldWork()
+{
+ // Arrange
+ var projectOption = new Option("--project", "Project path");
+ var rootCommand = new RootCommand();
+ rootCommand.AddOption(projectOption);
+ var parseResult = rootCommand.Parse(new[] { "--project", "/path/to/project" });
+
+ // Act
+ await handler.HandleAsync(parseResult);
+
+ // Assert - verify the command executed correctly
+}
+```
+
+## Next Steps
+
+1. Complete the migration of remaining command handlers
+2. Update SetupCommand methods to use SetAction (depends on issues #15 and #16)
+3. Update Program.cs command registration (issue #16)
+4. Update package reference to beta5 (issue #15)
\ No newline at end of file
diff --git a/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/CommandHandler.cs b/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/CommandHandler.cs
index d8cc9c1..b2e1d99 100644
--- a/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/CommandHandler.cs
+++ b/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/CommandHandler.cs
@@ -5,6 +5,8 @@
using GenAIDBExplorer.Core.SemanticModelProviders;
using GenAIDBExplorer.Core.SemanticProviders;
using Microsoft.Extensions.Logging;
+using System.CommandLine;
+using System.CommandLine.Parsing;
using System.Resources;
namespace GenAIDBExplorer.Console.CommandHandlers;
@@ -66,6 +68,30 @@ ILogger> logger
/// The command options that were provided to the command.
public abstract Task HandleAsync(TOptions commandOptions);
+ ///
+ /// Handles the command using ParseResult for System.CommandLine 2.0.0-beta5 compatibility.
+ /// This method extracts the options from ParseResult and calls the strongly-typed HandleAsync method.
+ ///
+ /// The parse result containing the parsed command-line arguments.
+ public virtual Task HandleAsync(ParseResult parseResult)
+ {
+ var commandOptions = ExtractCommandOptions(parseResult);
+ return HandleAsync(commandOptions);
+ }
+
+ ///
+ /// Extracts the command options from ParseResult.
+ /// Derived classes should override this method to extract their specific option types.
+ ///
+ /// The parse result containing the parsed command-line arguments.
+ /// The extracted command options.
+ protected virtual TOptions ExtractCommandOptions(ParseResult parseResult)
+ {
+ throw new NotImplementedException(
+ $"Command handler {GetType().Name} must override ExtractCommandOptions method " +
+ "to support ParseResult-based handling for System.CommandLine 2.0.0-beta5 compatibility.");
+ }
+
///
/// Asserts that the command options and its properties are valid.
///
@@ -193,4 +219,39 @@ protected Task ShowStoredProcedureDetailsAsync(SemanticModel semanticModel, stri
}
return Task.CompletedTask;
}
+
+ ///
+ /// Utility method to extract the value of an option from ParseResult.
+ ///
+ /// The type of the option value.
+ /// The parse result containing the parsed command-line arguments.
+ /// The option to extract the value for.
+ /// The extracted option value.
+ protected static T GetOptionValue(ParseResult parseResult, Option option)
+ {
+ return parseResult.GetValue(option);
+ }
+
+ ///
+ /// Utility method to extract the value of an option from ParseResult by name.
+ ///
+ /// The type of the option value.
+ /// The parse result containing the parsed command-line arguments.
+ /// The name of the option to extract the value for.
+ /// The extracted option value.
+ protected static T GetOptionValue(ParseResult parseResult, string optionName)
+ {
+ return parseResult.GetValue(optionName);
+ }
+
+ ///
+ /// Utility method to check if an option was provided in the command line.
+ ///
+ /// The parse result containing the parsed command-line arguments.
+ /// The option to check.
+ /// True if the option was provided, false otherwise.
+ protected static bool HasOption(ParseResult parseResult, Option option)
+ {
+ return parseResult.FindResultFor(option) != null;
+ }
}
\ No newline at end of file
diff --git a/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/ExtractModelCommandHandler.cs b/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/ExtractModelCommandHandler.cs
index 0db20fc..3e75487 100644
--- a/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/ExtractModelCommandHandler.cs
+++ b/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/ExtractModelCommandHandler.cs
@@ -6,6 +6,7 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.CommandLine;
+using System.CommandLine.Parsing;
using System.Resources;
namespace GenAIDBExplorer.Console.CommandHandlers;
@@ -105,4 +106,19 @@ public override async Task HandleAsync(ExtractModelCommandHandlerOptions command
_logger.LogInformation("{Message} '{ProjectPath}'", _resourceManagerLogMessages.GetString("ExtractSemanticModelComplete"), projectPath.FullName);
}
+
+ ///
+ /// Extracts the command options from ParseResult for System.CommandLine 2.0.0-beta5 compatibility.
+ ///
+ /// The parse result containing the parsed command-line arguments.
+ /// The extracted command options.
+ protected override ExtractModelCommandHandlerOptions ExtractCommandOptions(ParseResult parseResult)
+ {
+ var projectPath = GetOptionValue(parseResult, "--project");
+ var skipTables = GetOptionValue(parseResult, "--skipTables");
+ var skipViews = GetOptionValue(parseResult, "--skipViews");
+ var skipStoredProcedures = GetOptionValue(parseResult, "--skipStoredProcedures");
+
+ return new ExtractModelCommandHandlerOptions(projectPath, skipTables, skipViews, skipStoredProcedures);
+ }
}
\ No newline at end of file
diff --git a/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/ICommandHandler.cs b/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/ICommandHandler.cs
index b3028ba..cc3271d 100644
--- a/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/ICommandHandler.cs
+++ b/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/ICommandHandler.cs
@@ -1,6 +1,7 @@
using GenAIDBExplorer.Core.Models.SemanticModel;
using Microsoft.Extensions.Hosting;
using System.CommandLine;
+using System.CommandLine.Parsing;
namespace GenAIDBExplorer.Console.CommandHandlers;
@@ -13,8 +14,14 @@ namespace GenAIDBExplorer.Console.CommandHandlers;
public interface ICommandHandler where TOptions : ICommandHandlerOptions
{
///
- /// Handles the command.
+ /// Handles the command with strongly-typed options.
///
/// The command options that were provided to the command.
Task HandleAsync(TOptions commandOptions);
+
+ ///
+ /// Handles the command using ParseResult for System.CommandLine 2.0.0-beta5 compatibility.
+ ///
+ /// The parse result containing the parsed command-line arguments.
+ Task HandleAsync(ParseResult parseResult);
}
diff --git a/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/InitProjectCommandHandler.cs b/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/InitProjectCommandHandler.cs
index 04872e1..1f417a8 100644
--- a/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/InitProjectCommandHandler.cs
+++ b/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/InitProjectCommandHandler.cs
@@ -6,6 +6,7 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.CommandLine;
+using System.CommandLine.Parsing;
using System.Resources;
namespace GenAIDBExplorer.Console.CommandHandlers;
@@ -87,4 +88,16 @@ public override async Task HandleAsync(InitProjectCommandHandlerOptions commandO
_logger.LogInformation("{Message} '{ProjectPath}'", _resourceManagerLogMessages.GetString("InitializeProjectComplete"), projectPath.FullName);
await Task.CompletedTask;
}
+
+ ///
+ /// Extracts the command options from ParseResult for System.CommandLine 2.0.0-beta5 compatibility.
+ ///
+ /// The parse result containing the parsed command-line arguments.
+ /// The extracted command options.
+ protected override InitProjectCommandHandlerOptions ExtractCommandOptions(ParseResult parseResult)
+ {
+ // For init-project command, we expect a --project option
+ var projectPath = GetOptionValue(parseResult, "--project");
+ return new InitProjectCommandHandlerOptions(projectPath);
+ }
}
diff --git a/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/QueryModelCommandHandler.cs b/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/QueryModelCommandHandler.cs
index 8762ac0..7117de8 100644
--- a/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/QueryModelCommandHandler.cs
+++ b/src/GenAIDBExplorer/GenAIDBExplorer.Console/CommandHandlers/QueryModelCommandHandler.cs
@@ -7,6 +7,7 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.CommandLine;
+using System.CommandLine.Parsing;
using System.Resources;
namespace GenAIDBExplorer.Console.CommandHandlers;
@@ -76,4 +77,15 @@ public override async Task HandleAsync(QueryModelCommandHandlerOptions commandOp
await Task.CompletedTask;
}
+
+ ///
+ /// Extracts the command options from ParseResult for System.CommandLine 2.0.0-beta5 compatibility.
+ ///
+ /// The parse result containing the parsed command-line arguments.
+ /// The extracted command options.
+ protected override QueryModelCommandHandlerOptions ExtractCommandOptions(ParseResult parseResult)
+ {
+ var projectPath = GetOptionValue(parseResult, "--project");
+ return new QueryModelCommandHandlerOptions(projectPath);
+ }
}
diff --git a/src/GenAIDBExplorer/Tests/Unit/GenAIDBExplorer.Console.Test/InitProjectCommandHandler.Tests.cs b/src/GenAIDBExplorer/Tests/Unit/GenAIDBExplorer.Console.Test/InitProjectCommandHandler.Tests.cs
index 0cf49eb..3cdff4f 100644
--- a/src/GenAIDBExplorer/Tests/Unit/GenAIDBExplorer.Console.Test/InitProjectCommandHandler.Tests.cs
+++ b/src/GenAIDBExplorer/Tests/Unit/GenAIDBExplorer.Console.Test/InitProjectCommandHandler.Tests.cs
@@ -6,6 +6,7 @@
using GenAIDBExplorer.Core.SemanticModelProviders;
using Microsoft.Extensions.Logging;
using Moq;
+using System.CommandLine;
namespace GenAIDBExplorer.Console.Test;
@@ -129,4 +130,34 @@ public async Task HandleAsync_ShouldThrowArgumentNullException_WhenProjectPathIs
await act.Should().ThrowAsync();
_mockProject.Verify(p => p.InitializeProjectDirectory(It.IsAny()), Times.Never);
}
+
+ [TestMethod]
+ public async Task HandleAsync_WithParseResult_ShouldInitializeProjectDirectory_WhenProjectPathIsValid()
+ {
+ // Arrange
+ var projectPath = new DirectoryInfo(@"C:\ValidProjectPath");
+
+ // Create a mock ParseResult that simulates System.CommandLine 2.0.0-beta5 behavior
+ var projectOption = new Option("--project", "Project path");
+ var rootCommand = new RootCommand();
+ rootCommand.AddOption(projectOption);
+
+ // Parse the command line arguments to create a ParseResult
+ var parseResult = rootCommand.Parse(new[] { "--project", projectPath.FullName });
+
+ // Act
+ await _handler.HandleAsync(parseResult);
+
+ // Assert
+ _mockProject.Verify(p => p.InitializeProjectDirectory(projectPath), Times.Once);
+
+ _mockLogger.Verify(
+ x => x.Log(
+ LogLevel.Information,
+ It.IsAny(),
+ It.Is((v, t) => v.ToString().Contains($"Initializing project. '{projectPath.FullName}'")),
+ null,
+ It.IsAny>()),
+ Times.Once);
+ }
}
\ No newline at end of file