-
Notifications
You must be signed in to change notification settings - Fork 0
2 feat run parsing folder #29
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
Changes from all commits
6782591
c215963
d10f1b2
e9a1961
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -482,3 +482,6 @@ $RECYCLE.BIN/ | |
|
|
||
| # Vim temporary swap files | ||
| *.swp | ||
|
|
||
| # Generated schema files | ||
| *.dbml | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,60 @@ | ||||||||||||||
| using System.ComponentModel; | ||||||||||||||
| using AL2DBML.Application.Interfaces; | ||||||||||||||
| using AL2DBML.CLI.Services; | ||||||||||||||
| using AL2DBML.CLI.Strategies; | ||||||||||||||
| using Spectre.Console; | ||||||||||||||
| using Spectre.Console.Cli; | ||||||||||||||
|
|
||||||||||||||
| namespace AL2DBML.CLI.Commands; | ||||||||||||||
|
|
||||||||||||||
| public class GenerateSettings : CommandSettings | ||||||||||||||
| { | ||||||||||||||
| [CommandOption("-i|--input <INPUT_PATH>")] | ||||||||||||||
| [Description("The path to the AL project folder, AL file, or vscode workspace file of AL projects to parse.")] | ||||||||||||||
| public string InputPath { get; init; } = "."; | ||||||||||||||
|
|
||||||||||||||
| [CommandOption("-o|--output <OUTPUT_PATH>")] | ||||||||||||||
| [Description("The path to the output directory.")] | ||||||||||||||
| public string OutputPath { get; init; } = "."; | ||||||||||||||
|
|
||||||||||||||
| [CommandOption("-n|--name <OUTPUT_NAME>")] | ||||||||||||||
| [Description("The name of the output file (without extension). 'schema' by default.")] | ||||||||||||||
| public string OutputName { get; init; } = "schema"; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| public class GenerateCommand : AsyncCommand<GenerateSettings> | ||||||||||||||
| { | ||||||||||||||
| private readonly IAlParser _alParser; | ||||||||||||||
| private readonly IDBMLWriter _dbmlWriter; | ||||||||||||||
| private readonly IParsingTracker _tracker; | ||||||||||||||
|
|
||||||||||||||
| public GenerateCommand(IAlParser alParser, IDBMLWriter dbmlWriter, IParsingTracker tracker) | ||||||||||||||
| { | ||||||||||||||
| _alParser = alParser; | ||||||||||||||
| _dbmlWriter = dbmlWriter; | ||||||||||||||
| _tracker = tracker; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| protected override async Task<int> ExecuteAsync(CommandContext context, GenerateSettings settings, CancellationToken cancellationToken) | ||||||||||||||
| { | ||||||||||||||
| var inputType = FileSystemService.GetInputType(settings.InputPath); | ||||||||||||||
| if (inputType == Enums.InputType.NotSupported) | ||||||||||||||
| { | ||||||||||||||
| AnsiConsole.MarkupLine("[red]Error:[/] Unsupported input type. Please provide a valid directory, AL file, or vscode workspace file."); | ||||||||||||||
| return -1; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| var outputPath = Path.Combine(settings.OutputPath, $"{settings.OutputName}.dbml"); | ||||||||||||||
|
||||||||||||||
| var outputPath = Path.Combine(settings.OutputPath, $"{settings.OutputName}.dbml"); | |
| var outputPath = settings.OutputPath; | |
| if (!outputPath.EndsWith(".dbml", StringComparison.OrdinalIgnoreCase)) | |
| { | |
| outputPath = Path.Combine(settings.OutputPath, $"{settings.OutputName}.dbml"); | |
| } |
Copilot
AI
Mar 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because IAlParser is now registered as a singleton and accumulates schema state, this command should clear parser state at the start of execution to avoid leaking results between multiple generate invocations in the same process (or between tests/hosted runs). Call _alParser.ClearOutputSchema() before executing the selected input strategy.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| namespace AL2DBML.CLI.Enums; | ||
|
|
||
| public enum InputType | ||
| { | ||
| Directory, | ||
| ALFile, | ||
| WorkspaceFile, | ||
| NotSupported | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,41 @@ | ||
| // See https://aka.ms/new-console-template for more information | ||
| Console.WriteLine("Hello, World!"); | ||
| using AL2DBML.CLI.Commands; | ||
| using AL2DBML.CLI.Services; | ||
| using AL2DBML.DI; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Spectre.Console.Cli; | ||
|
|
||
|
|
||
|
|
||
| // Register services | ||
| var services = new ServiceCollection(); | ||
| services | ||
| .AddAL2Dbml() | ||
| .AddScoped<GenerateCommand>() | ||
| .AddScoped<IParsingTracker, ParsingTracker>(); | ||
|
|
||
| var registrar = new TypeRegistrar(services); | ||
|
|
||
| var app = new CommandApp(registrar); | ||
|
|
||
| app.Configure(config => | ||
| { | ||
| config.AddCommand<GenerateCommand>("generate"); | ||
| }); | ||
|
|
||
| return await app.RunAsync(args); | ||
|
|
||
| public sealed class TypeRegistrar(IServiceCollection services) : ITypeRegistrar | ||
| { | ||
| public ITypeResolver Build() => new TypeResolver(services.BuildServiceProvider().CreateScope().ServiceProvider); | ||
|
|
||
| public void Register(Type service, Type implementation) => services.AddScoped(service, implementation); | ||
|
|
||
| public void RegisterInstance(Type service, object implementation) => services.AddSingleton(service, implementation); | ||
|
|
||
| public void RegisterLazy(Type service, Func<object> factory) => services.AddScoped(service, _ => factory()); | ||
| } | ||
|
|
||
| public sealed class TypeResolver(IServiceProvider provider) : ITypeResolver | ||
| { | ||
| public object? Resolve(Type? type) => type == null ? null : provider.GetService(type); | ||
| } | ||
|
Comment on lines
+10
to
+41
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| using AL2DBML.CLI.Enums; | ||
|
|
||
| namespace AL2DBML.CLI.Services; | ||
|
|
||
| public static class FileSystemService | ||
| { | ||
| public static InputType GetInputType(string path) | ||
| { | ||
| if (Directory.Exists(path)) | ||
| return InputType.Directory; | ||
|
|
||
| if (File.Exists(path)) | ||
| { | ||
| var extension = Path.GetExtension(path); | ||
| if (extension.Equals(".al", StringComparison.OrdinalIgnoreCase)) | ||
| return InputType.ALFile; | ||
| if (extension.Equals(".code-workspace", StringComparison.OrdinalIgnoreCase)) | ||
| return InputType.WorkspaceFile; | ||
| } | ||
|
|
||
| return InputType.NotSupported; | ||
| } | ||
|
|
||
| public static List<string> ScanDirectory(string directoryPath) | ||
| { | ||
| return Directory.GetFiles(directoryPath, "*.al", SearchOption.AllDirectories) | ||
| .ToList(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| namespace AL2DBML.CLI.Services; | ||
|
|
||
| public interface IParsingTracker | ||
| { | ||
| void RecordFile(); | ||
| int FileCount { get; } | ||
| TimeSpan Elapsed { get; } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| using System.Diagnostics; | ||
|
|
||
| namespace AL2DBML.CLI.Services; | ||
|
|
||
| public class ParsingTracker : IParsingTracker | ||
| { | ||
| private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); | ||
| private int _fileCount; | ||
|
|
||
| public void RecordFile() => _fileCount++; | ||
| public int FileCount => _fileCount; | ||
| public TimeSpan Elapsed => _stopwatch.Elapsed; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| using AL2DBML.Application.Interfaces; | ||
| using AL2DBML.CLI.Services; | ||
| using AL2DBML.Core.Models; | ||
|
|
||
| namespace AL2DBML.CLI.Strategies; | ||
|
|
||
| public class FolderInputStrategy : IInputStrategy | ||
| { | ||
| private readonly IAlParser _alParser; | ||
| private readonly IParsingTracker _tracker; | ||
|
|
||
| public FolderInputStrategy(IAlParser alParser, IParsingTracker tracker) | ||
| { | ||
| _alParser = alParser; | ||
| _tracker = tracker; | ||
| } | ||
|
|
||
| public OutputSchema Execute(string inputPath) | ||
| { | ||
| var files = FileSystemService.ScanDirectory(inputPath); | ||
| var singleFileStrategy = new SingleFileInputStrategy(_alParser, _tracker); | ||
|
|
||
| foreach (var file in files) | ||
| singleFileStrategy.Execute(file); | ||
|
|
||
| return _alParser.GetOutputSchema(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| using AL2DBML.Core.Models; | ||
|
|
||
| namespace AL2DBML.CLI.Strategies; | ||
|
|
||
| public interface IInputStrategy | ||
| { | ||
| OutputSchema Execute(string inputPath); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| using AL2DBML.Application.Interfaces; | ||
| using AL2DBML.CLI.Enums; | ||
| using AL2DBML.CLI.Services; | ||
|
|
||
| namespace AL2DBML.CLI.Strategies; | ||
|
|
||
| public class InputStrategyFactory | ||
| { | ||
| public IInputStrategy Strategy { get; } | ||
| public InputStrategyFactory(InputType inputType, IAlParser alParser, IParsingTracker tracker) | ||
| { | ||
| switch (inputType) | ||
| { | ||
| case InputType.Directory: | ||
| Strategy = new FolderInputStrategy(alParser, tracker); | ||
| break; | ||
| case InputType.ALFile: | ||
| Strategy = new SingleFileInputStrategy(alParser, tracker); | ||
| break; | ||
| case InputType.WorkspaceFile: | ||
| Strategy = new WorkspaceInputStrategy(alParser, tracker); | ||
| break; | ||
| default: | ||
| throw new NotSupportedException($"Input type {inputType} is not supported."); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| using AL2DBML.Application.Interfaces; | ||
| using AL2DBML.CLI.Services; | ||
| using AL2DBML.Core.Enums; | ||
| using AL2DBML.Core.Models; | ||
|
|
||
| namespace AL2DBML.CLI.Strategies; | ||
|
|
||
| public class SingleFileInputStrategy : IInputStrategy | ||
| { | ||
| private readonly IAlParser _alParser; | ||
| private readonly IParsingTracker _tracker; | ||
|
|
||
| public SingleFileInputStrategy(IAlParser alParser, IParsingTracker tracker) | ||
| { | ||
| _alParser = alParser; | ||
| _tracker = tracker; | ||
| } | ||
|
|
||
| public OutputSchema Execute(string inputPath) | ||
| { | ||
| var content = File.ReadAllText(inputPath); | ||
| var fileType = _alParser.DetectFileType(content); | ||
| switch (fileType) | ||
| { | ||
| case AlFileType.Enum: | ||
| _alParser.ParseEnum(content); | ||
| _tracker.RecordFile(); | ||
| break; | ||
| case AlFileType.EnumExtension: | ||
| _alParser.ParseEnumExtension(content); | ||
| _tracker.RecordFile(); | ||
| break; | ||
| case AlFileType.Table: | ||
| _alParser.ParseTable(content); | ||
| _tracker.RecordFile(); | ||
| break; | ||
| case AlFileType.TableExtension: | ||
| _alParser.ParseTableExtension(content); | ||
| _tracker.RecordFile(); | ||
| break; | ||
| // Unknown: skip silently (unsupported files in a folder) | ||
| } | ||
| return _alParser.GetOutputSchema(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,53 @@ | ||||||||||||||
| using System.Text.Json; | ||||||||||||||
| using AL2DBML.Application.Interfaces; | ||||||||||||||
| using AL2DBML.CLI.Services; | ||||||||||||||
| using AL2DBML.Core.Models; | ||||||||||||||
| using Spectre.Console; | ||||||||||||||
|
|
||||||||||||||
| namespace AL2DBML.CLI.Strategies; | ||||||||||||||
|
|
||||||||||||||
| class WorkspaceInputStrategy : IInputStrategy | ||||||||||||||
| { | ||||||||||||||
| private readonly IAlParser _alParser; | ||||||||||||||
| private readonly IParsingTracker _tracker; | ||||||||||||||
|
|
||||||||||||||
| public WorkspaceInputStrategy(IAlParser alParser, IParsingTracker tracker) | ||||||||||||||
| { | ||||||||||||||
| _alParser = alParser; | ||||||||||||||
| _tracker = tracker; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| public OutputSchema Execute(string inputPath) | ||||||||||||||
| { | ||||||||||||||
| // Read the vscode workspace file to get the list of projects | ||||||||||||||
| var workspaceContent = File.ReadAllText(inputPath); | ||||||||||||||
| using var workspaceJson = JsonDocument.Parse(workspaceContent); | ||||||||||||||
| if (!workspaceJson.RootElement.TryGetProperty("folders", out var folders)) | ||||||||||||||
| { | ||||||||||||||
| throw new InvalidDataException("Invalid workspace file: 'folders' property not found."); | ||||||||||||||
| } | ||||||||||||||
|
||||||||||||||
| } | |
| } | |
| if (folders.ValueKind != JsonValueKind.Array) | |
| { | |
| throw new InvalidDataException("Invalid workspace file: 'folders' property must be an array."); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inputTypeis anAL2DBML.CLI.Enums.InputType, but the comparison usesEnums.InputType.NotSupported, which won’t resolve from this namespace/import set and should fail to compile. Compare againstInputType.NotSupported(add the appropriate using) or fully-qualifyAL2DBML.CLI.Enums.InputType.NotSupported.