From abd116b03209ffdc42fe6a76ab8f57c77b1219c6 Mon Sep 17 00:00:00 2001 From: mdnapo Date: Tue, 23 Apr 2024 08:56:01 +0200 Subject: [PATCH] Adding entity linking --- MycroForge.CLI/ArgsContext.cs | 2 +- MycroForge.CLI/Bash.cs | 11 +++- MycroForge.CLI/CodeGen/EntityLinker.cs | 16 +++++ MycroForge.CLI/Commands/MycroForge.Add.cs | 16 ----- .../Commands/MycroForge.Entity.Link.Many.cs | 37 ++++++++++++ .../Commands/MycroForge.Entity.Link.One.cs | 38 ++++++++++++ .../Commands/MycroForge.Entity.Link.cs | 20 +++++++ MycroForge.CLI/Commands/MycroForge.Entity.cs | 17 ++++++ .../Commands/MycroForge.Generate.Entity.cs | 9 ++- MycroForge.CLI/Commands/MycroForge.Rewrite.cs | 24 -------- MycroForge.CLI/Commands/MycroForge.Run.cs | 23 +++++++ .../Extensions/ServiceCollectionExtensions.cs | 6 ++ MycroForge.CLI/Features/Orm.cs | 14 +++-- MycroForge.CLI/MycroForge.CLI.csproj | 3 + MycroForge.CLI/Program.cs | 60 +++++++++---------- MycroForge.CLI/ProjectContext.cs | 22 +++---- MycroForge.CLI/scripts/user.py | 12 ++++ 17 files changed, 236 insertions(+), 94 deletions(-) create mode 100644 MycroForge.CLI/CodeGen/EntityLinker.cs create mode 100644 MycroForge.CLI/Commands/MycroForge.Entity.Link.Many.cs create mode 100644 MycroForge.CLI/Commands/MycroForge.Entity.Link.One.cs create mode 100644 MycroForge.CLI/Commands/MycroForge.Entity.Link.cs create mode 100644 MycroForge.CLI/Commands/MycroForge.Entity.cs delete mode 100644 MycroForge.CLI/Commands/MycroForge.Rewrite.cs create mode 100644 MycroForge.CLI/Commands/MycroForge.Run.cs create mode 100644 MycroForge.CLI/scripts/user.py diff --git a/MycroForge.CLI/ArgsContext.cs b/MycroForge.CLI/ArgsContext.cs index 6680650..9295203 100644 --- a/MycroForge.CLI/ArgsContext.cs +++ b/MycroForge.CLI/ArgsContext.cs @@ -2,5 +2,5 @@ public class ArgsContext { - public string[] Args { get; init; } + public string[] Args { get; init; } = Array.Empty(); } \ No newline at end of file diff --git a/MycroForge.CLI/Bash.cs b/MycroForge.CLI/Bash.cs index 2a0e74e..2e79381 100644 --- a/MycroForge.CLI/Bash.cs +++ b/MycroForge.CLI/Bash.cs @@ -21,12 +21,17 @@ public static class Bash if (process is null) throw new NullReferenceException("Could not initialize bash process."); - process.OutputDataReceived += (sender, args) => { Console.WriteLine(args.Data); }; + process.OutputDataReceived += (sender, args) => + { + // Only print data when it's not empty to prevent noise in the shell + if (!string.IsNullOrEmpty(args.Data)) + Console.WriteLine(args.Data); + }; process.BeginOutputReadLine(); process.ErrorDataReceived += (sender, args) => { - // Only print error data when it's not empty to prevent noise in the shell + // Only print data when it's not empty to prevent noise in the shell if (!string.IsNullOrEmpty(args.Data)) Console.WriteLine(args.Data); }; @@ -42,6 +47,6 @@ public static class Bash await process.WaitForExitAsync(); if (process.ExitCode != 0) - Console.WriteLine($"Process exited with status code {process.ExitCode}."); + Console.WriteLine($"Process finished with exit code {process.ExitCode}."); } } \ No newline at end of file diff --git a/MycroForge.CLI/CodeGen/EntityLinker.cs b/MycroForge.CLI/CodeGen/EntityLinker.cs new file mode 100644 index 0000000..2f035b3 --- /dev/null +++ b/MycroForge.CLI/CodeGen/EntityLinker.cs @@ -0,0 +1,16 @@ +using MycroForge.Parsing; + +namespace MycroForge.CLI.CodeGen; + +public class EntityLinker : PythonSourceModifier +{ + public EntityLinker(string source) : base(source) + { + } + + public override object? VisitAssignment(PythonParser.AssignmentContext context) + { + Console.WriteLine(GetOriginalText(context)); + return base.VisitAssignment(context); + } +} diff --git a/MycroForge.CLI/Commands/MycroForge.Add.cs b/MycroForge.CLI/Commands/MycroForge.Add.cs index 68b3f78..b45d690 100644 --- a/MycroForge.CLI/Commands/MycroForge.Add.cs +++ b/MycroForge.CLI/Commands/MycroForge.Add.cs @@ -14,20 +14,4 @@ public partial class MycroForge AddCommand((subCommandOf as Command)!); } } - - public class Run : Command, ISubCommandOf - { - public Run() : base("run", "Run your app") - { - this.SetHandler(ExecuteAsync); - } - - private async Task ExecuteAsync() - { - await Bash.ExecuteAsync([ - "source .venv/bin/activate", - "uvicorn main:app --reload" - ]); - } - } } \ No newline at end of file diff --git a/MycroForge.CLI/Commands/MycroForge.Entity.Link.Many.cs b/MycroForge.CLI/Commands/MycroForge.Entity.Link.Many.cs new file mode 100644 index 0000000..304a0bc --- /dev/null +++ b/MycroForge.CLI/Commands/MycroForge.Entity.Link.Many.cs @@ -0,0 +1,37 @@ +using System.CommandLine; +using MycroForge.CLI.Commands.Interfaces; + +namespace MycroForge.CLI.Commands; + +public partial class MycroForge +{ + public partial class Entity + { + public partial class Link + { + public class Many : Command, ISubCommandOf + { + private static readonly Argument NameArgument = + new(name: "primary", description: "The left side of the relation"); + + private static readonly Option ToOneOption = + new(name: "--to-one", description: "The right side of the relation"); + + private static readonly Option ToManyOption = + new(name: "--to-many", description: "The right side of the relation"); + + public Many() : base("many", "Define a n:m relation") + { + AddArgument(NameArgument); + AddOption(ToOneOption); + AddOption(ToManyOption); + this.SetHandler(ExecuteAsync); + } + + private async Task ExecuteAsync() + { + } + } + } + } +} \ No newline at end of file diff --git a/MycroForge.CLI/Commands/MycroForge.Entity.Link.One.cs b/MycroForge.CLI/Commands/MycroForge.Entity.Link.One.cs new file mode 100644 index 0000000..75820d3 --- /dev/null +++ b/MycroForge.CLI/Commands/MycroForge.Entity.Link.One.cs @@ -0,0 +1,38 @@ +using System.CommandLine; +using MycroForge.CLI.Commands.Interfaces; + +namespace MycroForge.CLI.Commands; + +public partial class MycroForge +{ + public partial class Entity + { + public partial class Link + { + public class One : Command, ISubCommandOf + { + private static readonly Argument NameArgument = + new(name: "primary", description: "The left side of the relation"); + + private static readonly Option ToOneOption = + new(name: "--to-one", description: "The right side of the relation"); + + private static readonly Option ToManyOption = + new(name: "--to-many", description: "The right side of the relation"); + + + public One() : base("one", "Define a 1:n relation") + { + AddArgument(NameArgument); + AddOption(ToOneOption); + AddOption(ToManyOption); + this.SetHandler(ExecuteAsync); + } + + private async Task ExecuteAsync() + { + } + } + } + } +} \ No newline at end of file diff --git a/MycroForge.CLI/Commands/MycroForge.Entity.Link.cs b/MycroForge.CLI/Commands/MycroForge.Entity.Link.cs new file mode 100644 index 0000000..c12d2db --- /dev/null +++ b/MycroForge.CLI/Commands/MycroForge.Entity.Link.cs @@ -0,0 +1,20 @@ +using System.CommandLine; +using MycroForge.CLI.Commands.Interfaces; + +namespace MycroForge.CLI.Commands; + +public partial class MycroForge +{ + public partial class Entity + { + public partial class Link : Command, ISubCommandOf + { + public Link(IEnumerable> commands) : + base("link", "Define relationships between entities") + { + foreach (var command in commands) + AddCommand((command as Command)!); + } + } + } +} \ No newline at end of file diff --git a/MycroForge.CLI/Commands/MycroForge.Entity.cs b/MycroForge.CLI/Commands/MycroForge.Entity.cs new file mode 100644 index 0000000..158f23c --- /dev/null +++ b/MycroForge.CLI/Commands/MycroForge.Entity.cs @@ -0,0 +1,17 @@ +using System.CommandLine; +using MycroForge.CLI.Commands.Interfaces; + +namespace MycroForge.CLI.Commands; + +public partial class MycroForge +{ + public partial class Entity : Command, ISubCommandOf + { + public Entity(IEnumerable> commands) : + base("entity", "Manage the entities in your project") + { + foreach (var command in commands.Cast()) + AddCommand(command); + } + } +} \ No newline at end of file diff --git a/MycroForge.CLI/Commands/MycroForge.Generate.Entity.cs b/MycroForge.CLI/Commands/MycroForge.Generate.Entity.cs index 1bfcda1..f6ec7af 100644 --- a/MycroForge.CLI/Commands/MycroForge.Generate.Entity.cs +++ b/MycroForge.CLI/Commands/MycroForge.Generate.Entity.cs @@ -9,19 +9,22 @@ public partial class MycroForge { public partial class Generate { + // ReSharper disable once MemberHidesStaticFromOuterClass public class Entity : Command, ISubCommandOf { private static readonly string[] Template = [ - "from sqlalchemy import INTEGER, Column, String", + "from sqlalchemy import String", + "from sqlalchemy.orm import Mapped, mapped_column", "from orm.entities.entity_base import EntityBase", "", "class %class_name%(EntityBase):", "\t__tablename__ = \"%table_name%\"", - "\tid = Column(INTEGER, primary_key=True)", + "\tid: Mapped[int] = mapped_column(primary_key=True)", + "\tvalue: Mapped[str] = mapped_column(String(255))", "", "\tdef __repr__(self) -> str:", - "\t\treturn f\"%class_name%(id={self.id!r})\"" + "\t\treturn f\"%class_name%(id={self.id!r}, value={self.value!r})\"" ]; private static readonly Argument NameArgument = diff --git a/MycroForge.CLI/Commands/MycroForge.Rewrite.cs b/MycroForge.CLI/Commands/MycroForge.Rewrite.cs deleted file mode 100644 index 4b48362..0000000 --- a/MycroForge.CLI/Commands/MycroForge.Rewrite.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.CommandLine; -using MycroForge.CLI.Commands.Interfaces; - -namespace MycroForge.CLI.Commands; - -public partial class MycroForge -{ - public class Rewrite : Command, ISubCommandOf - { - public Rewrite() : base("rewrite", "Test a python source rewriter.") - { - this.SetHandler(ExecuteAsync); - } - - private async Task ExecuteAsync() - { - // var path = Path.Combine(Directory.GetCurrentDirectory(), "main.py"); - // var source = await File.ReadAllTextAsync(path); - // var rewriter = new TestRewriter(source); - // var rewrite = rewriter.Rewrite(); - // await File.WriteAllTextAsync(path, rewrite); - } - } -} diff --git a/MycroForge.CLI/Commands/MycroForge.Run.cs b/MycroForge.CLI/Commands/MycroForge.Run.cs new file mode 100644 index 0000000..04b918f --- /dev/null +++ b/MycroForge.CLI/Commands/MycroForge.Run.cs @@ -0,0 +1,23 @@ +using System.CommandLine; +using MycroForge.CLI.Commands.Interfaces; + +namespace MycroForge.CLI.Commands; + +public partial class MycroForge +{ + public class Run : Command, ISubCommandOf + { + public Run() : base("run", "Run your app") + { + this.SetHandler(ExecuteAsync); + } + + private async Task ExecuteAsync() + { + await Bash.ExecuteAsync([ + "source .venv/bin/activate", + "uvicorn main:app --reload" + ]); + } + } +} \ No newline at end of file diff --git a/MycroForge.CLI/Extensions/ServiceCollectionExtensions.cs b/MycroForge.CLI/Extensions/ServiceCollectionExtensions.cs index f00f5ff..a48c7ce 100644 --- a/MycroForge.CLI/Extensions/ServiceCollectionExtensions.cs +++ b/MycroForge.CLI/Extensions/ServiceCollectionExtensions.cs @@ -41,6 +41,12 @@ public static class ServiceCollectionExtensions services.AddScoped, Commands.MycroForge.Generate.Router>(); services.AddScoped, Commands.MycroForge.Generate.Migration>(); + // Register "m4g entity" + services.AddScoped, Commands.MycroForge.Entity>(); + services.AddScoped, Commands.MycroForge.Entity.Link>(); + services.AddScoped, Commands.MycroForge.Entity.Link.One>(); + services.AddScoped, Commands.MycroForge.Entity.Link.Many>(); + // Register "m4g migrations" services.AddScoped, Commands.MycroForge.Migrations>(); services.AddScoped, Commands.MycroForge.Migrations.Apply>(); diff --git a/MycroForge.CLI/Features/Orm.cs b/MycroForge.CLI/Features/Orm.cs index 8422a2c..29e2b29 100644 --- a/MycroForge.CLI/Features/Orm.cs +++ b/MycroForge.CLI/Features/Orm.cs @@ -36,16 +36,18 @@ public sealed class Orm : IFeature private static readonly string[] User = [ - "from sqlalchemy import INTEGER, Column, String", + "from sqlalchemy import String", + "from sqlalchemy.orm import Mapped, mapped_column", "from orm.entities.entity_base import EntityBase", "", "class User(EntityBase):", "\t__tablename__ = \"users\"", - "\tid = Column(INTEGER, primary_key=True)", - "\tfirstname = Column(String(255))", - "\tlastname = Column(String(255))\n", - "def __repr__(self) -> str:", - "\treturn f\"User(id={self.id!r}, firstname={self.firstname!r}, lastname={self.lastname!r})\"" + "\tid: Mapped[int] = mapped_column(primary_key=True)", + "\tfirstname: Mapped[str] = mapped_column(String(255))", + "\tlastname: Mapped[str] = mapped_column(String(255))", + "", + "\tdef __repr__(self) -> str:", + "\t\treturn f\"User(id={self.id!r}, firstname={self.firstname!r}, lastname={self.lastname!r})\"" ]; #endregion diff --git a/MycroForge.CLI/MycroForge.CLI.csproj b/MycroForge.CLI/MycroForge.CLI.csproj index 4c2bd4d..af751a5 100644 --- a/MycroForge.CLI/MycroForge.CLI.csproj +++ b/MycroForge.CLI/MycroForge.CLI.csproj @@ -27,6 +27,9 @@ PreserveNewest + + PreserveNewest + diff --git a/MycroForge.CLI/Program.cs b/MycroForge.CLI/Program.cs index c84234f..6adf526 100644 --- a/MycroForge.CLI/Program.cs +++ b/MycroForge.CLI/Program.cs @@ -1,32 +1,32 @@ -using System.CommandLine; -using MycroForge.CLI; +// using System.CommandLine; +// using MycroForge.CLI; +// using MycroForge.CLI.Exceptions; +// using MycroForge.CLI.Extensions; +// using Microsoft.Extensions.DependencyInjection; +// using Microsoft.Extensions.Hosting; +// +// using var host = Host +// .CreateDefaultBuilder() +// .ConfigureServices((_, services) => +// { +// services +// .AddServices(args) +// .AddCommands(); +// }) +// .Build(); +// +// try +// { +// var ctx = host.Services.GetRequiredService(); +// await ctx.LoadConfig(); +// await host.Services.GetRequiredService().InvokeAsync(args); +// await ctx.SaveConfig(); +// } +// catch(Exception e) +// { +// Console.WriteLine(e.Message); +// } + using MycroForge.CLI.CodeGen; -using MycroForge.CLI.Exceptions; -using MycroForge.CLI.Extensions; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -using var host = Host - .CreateDefaultBuilder() - .ConfigureServices((_, services) => - { - services - .AddServices(args) - .AddCommands(); - }) - .Build(); - -try -{ - var ctx = host.Services.GetRequiredService(); - await ctx.LoadConfig(); - await host.Services.GetRequiredService().InvokeAsync(args); - await ctx.SaveConfig(); -} -catch -{ - // Console.WriteLine(e.Message); -} - -// var src = new OrmEnvInitializer(await File.ReadAllTextAsync("scripts/env.py")).Rewrite(); +var src = new EntityLinker(await File.ReadAllTextAsync("scripts/user.py")).Rewrite(); // Console.WriteLine(src); \ No newline at end of file diff --git a/MycroForge.CLI/ProjectContext.cs b/MycroForge.CLI/ProjectContext.cs index 6e6eceb..acb7647 100644 --- a/MycroForge.CLI/ProjectContext.cs +++ b/MycroForge.CLI/ProjectContext.cs @@ -20,9 +20,9 @@ public class ProjectContext { if (_argsContext.Args is ["init", ..] - or ["-?", ..] - or ["-h", ..] - or ["--help"] + or ["-?", ..] or [.., "-?"] + or ["-h", ..] or [.., "-h"] + or ["--help"] or [.., "--help"] or ["--version"] && !force) return; @@ -53,14 +53,6 @@ public class ProjectContext await Bash.ExecuteAsync($"chmod 777 {fullPath}"); } - public async Task WriteFile(string path, params string[] content) - { - var fullPath = Path.Combine(RootDirectory, path); - var fileInfo = new FileInfo(fullPath); - Directory.CreateDirectory(fileInfo.Directory!.FullName); - await File.WriteAllTextAsync(fullPath, string.Join("\n", content)); - } - public async Task ReadFile(string path) { var fullPath = Path.Combine(RootDirectory, path); @@ -72,6 +64,14 @@ public class ProjectContext return await File.ReadAllTextAsync(fullPath); } + public async Task WriteFile(string path, params string[] content) + { + var fullPath = Path.Combine(RootDirectory, path); + var fileInfo = new FileInfo(fullPath); + Directory.CreateDirectory(fileInfo.Directory!.FullName); + await File.WriteAllTextAsync(fullPath, string.Join("\n", content)); + } + public async Task SaveConfig() { if (Config is not null) diff --git a/MycroForge.CLI/scripts/user.py b/MycroForge.CLI/scripts/user.py new file mode 100644 index 0000000..ca87f76 --- /dev/null +++ b/MycroForge.CLI/scripts/user.py @@ -0,0 +1,12 @@ +from sqlalchemy import String +from sqlalchemy.orm import Mapped, mapped_column +from orm.entities.entity_base import EntityBase + +class User(EntityBase): + __tablename__ = "users" + id: Mapped[int] = mapped_column(primary_key=True) + firstname: Mapped[str] = mapped_column(String(255)) + lastname: Mapped[str] = mapped_column(String(255)) + + def __repr__(self) -> str: + return f"User(id={self.id!r}, firstname={self.firstname!r}, lastname={self.lastname!r})" \ No newline at end of file