From 78f33454195e54aa4fddc18a006ef7b0f48cd1ea Mon Sep 17 00:00:00 2001 From: mdnapo Date: Wed, 29 May 2024 14:20:18 +0200 Subject: [PATCH] Intermediate commit --- .../{DbEnvUpdater.cs => DbEnvModifier.cs} | 4 +- .../CodeGen/EntityLinker.EntityModel.cs | 44 ++-- MycroForge.CLI/CodeGen/MainModifier.cs | 221 ++++++++++++++++++ .../CodeGen/RequestClassGenerator.cs | 10 +- .../Commands/MycroForge.Db.Generate.Entity.cs | 2 +- MycroForge.CLI/Features/Api.cs | 14 +- MycroForge.CLI/Features/Db.cs | 4 +- MycroForge.CLI/MycroForge.CLI.csproj | 1 - MycroForge.CLI/Program.cs | 44 ++-- 9 files changed, 289 insertions(+), 55 deletions(-) rename MycroForge.CLI/CodeGen/{DbEnvUpdater.cs => DbEnvModifier.cs} (85%) create mode 100644 MycroForge.CLI/CodeGen/MainModifier.cs diff --git a/MycroForge.CLI/CodeGen/DbEnvUpdater.cs b/MycroForge.CLI/CodeGen/DbEnvModifier.cs similarity index 85% rename from MycroForge.CLI/CodeGen/DbEnvUpdater.cs rename to MycroForge.CLI/CodeGen/DbEnvModifier.cs index 8c7875e..3a00ff1 100644 --- a/MycroForge.CLI/CodeGen/DbEnvUpdater.cs +++ b/MycroForge.CLI/CodeGen/DbEnvModifier.cs @@ -2,13 +2,13 @@ namespace MycroForge.CLI.CodeGen; -public class DbEnvUpdater : PythonSourceModifier +public class DbEnvModifier : PythonSourceModifier { private readonly string _importPath; private readonly string _className; private PythonParser.Import_fromContext? _lastImport; - public DbEnvUpdater(string source, string importPath, string className) : base(source) + public DbEnvModifier(string source, string importPath, string className) : base(source) { _importPath = importPath; _className = className; diff --git a/MycroForge.CLI/CodeGen/EntityLinker.EntityModel.cs b/MycroForge.CLI/CodeGen/EntityLinker.EntityModel.cs index 6ff1a07..22ac4af 100644 --- a/MycroForge.CLI/CodeGen/EntityLinker.EntityModel.cs +++ b/MycroForge.CLI/CodeGen/EntityLinker.EntityModel.cs @@ -10,22 +10,22 @@ public partial class EntityLinker private readonly string _className; private readonly string _path; - private readonly List _importCtxs; + private readonly List _importContexts; private readonly List _importsBuffer; - private PythonParser.Import_fromContext LastImport => _importCtxs.Last(); + private PythonParser.Import_fromContext LastImport => _importContexts.Last(); - private readonly List _classCtxs; - private PythonParser.AssignmentContext _tableCtx; + private readonly List _classContexts; + private PythonParser.AssignmentContext _tableContext; - private readonly List _columnCtxs; + private readonly List _columnContexts; private readonly List _columnsBuffer; - private PythonParser.AssignmentContext LastColumn => _columnCtxs.Last(); + private PythonParser.AssignmentContext LastColumn => _columnContexts.Last(); public string ClassName => _className; public string Path => _path; public string FieldName => _className.Underscore().ToLower(); - public string TableName => GetOriginalText(_tableCtx) + public string TableName => GetOriginalText(_tableContext) .Replace("__tablename__", string.Empty) .Replace("=", string.Empty) .Replace("\"", string.Empty) @@ -35,11 +35,11 @@ public partial class EntityLinker { _className = className; _path = path; - _importCtxs = new(); + _importContexts = new(); _importsBuffer = new(); - _classCtxs = new(); - _tableCtx = default!; - _columnCtxs = new(); + _classContexts = new(); + _tableContext = default!; + _columnContexts = new(); _columnsBuffer = new(); } @@ -49,10 +49,10 @@ public partial class EntityLinker Visit(tree); - if (!_classCtxs.Any(c => GetOriginalText(c).Contains(_className))) + if (!_classContexts.Any(c => GetOriginalText(c).Contains(_className))) throw new Exception($"Entity {_className} was not found in {_path}."); - if (_columnCtxs.Count == 0) + if (_columnContexts.Count == 0) throw new Exception($"Entity {_className} has no columns."); _importsBuffer.Add(GetOriginalText(LastImport)); @@ -64,7 +64,7 @@ public partial class EntityLinker private void InsertRelationshipImport() { - var relationship = _importCtxs.FirstOrDefault(import => + var relationship = _importContexts.FirstOrDefault(import => { var text = GetOriginalText(import); return text.Contains("sqlalchemy.orm") && text.Contains("relationship"); @@ -76,7 +76,7 @@ public partial class EntityLinker private void InsertForeignKeyImport() { - var foreignKey = _importCtxs.FirstOrDefault(import => + var foreignKey = _importContexts.FirstOrDefault(import => { var text = GetOriginalText(import); return text.Contains("sqlalchemy") && text.Contains("ForeignKey"); @@ -88,13 +88,13 @@ public partial class EntityLinker public override object? VisitImport_from(PythonParser.Import_fromContext context) { - _importCtxs.Add(context); + _importContexts.Add(context); return base.VisitImport_from(context); } public override object? VisitClass_def(PythonParser.Class_defContext context) { - _classCtxs.Add(context); + _classContexts.Add(context); return base.VisitClass_def(context); } @@ -103,10 +103,10 @@ public partial class EntityLinker var text = GetOriginalText(context); if (text.StartsWith("__tablename__")) - _tableCtx = context; + _tableContext = context; if (text.Contains("Mapped[")) - _columnCtxs.Add(context); + _columnContexts.Add(context); return base.VisitAssignment(context); } @@ -116,10 +116,10 @@ public partial class EntityLinker public void Import(string from, string import) { - var isExisting = _importCtxs.Select(GetOriginalText).Any(ctx => ctx.Contains(from) && ctx.Contains(import)); - var isBuffered = _importsBuffer.Any(txt => txt.Contains(from) && txt.Contains(import)); + var exists = _importContexts.Select(GetOriginalText).Any(ctx => ctx.Contains(from) && ctx.Contains(import)); + var buffered = _importsBuffer.Any(txt => txt.Contains(from) && txt.Contains(import)); - if (!isExisting && !isBuffered) + if (!exists && !buffered) _importsBuffer.Add($"from {from} import {import}"); } diff --git a/MycroForge.CLI/CodeGen/MainModifier.cs b/MycroForge.CLI/CodeGen/MainModifier.cs new file mode 100644 index 0000000..f4b591d --- /dev/null +++ b/MycroForge.CLI/CodeGen/MainModifier.cs @@ -0,0 +1,221 @@ +using Antlr4.Runtime.Tree; +using MycroForge.Parsing; + +namespace MycroForge.CLI.CodeGen; + +public class MainModifier : PythonSourceModifier +{ + private PythonParser.Import_fromContext? _lastEntityImport; + private PythonParser.Import_fromContext? _lastAssociationImport; + private PythonParser.Import_fromContext? _lastRouterImport; + private PythonParser.Import_fromContext? _lastRouterInclude; + + private readonly List _lastEntityImportBuffer; + private readonly List _lastAssociationImportBuffer; + private readonly List _lastRouterImportBuffer; + private readonly List _lastRouterIncludeBuffer; + + public MainModifier(string source) : base(source) + { + _lastEntityImportBuffer = new(); + _lastAssociationImportBuffer = new(); + _lastRouterImportBuffer = new(); + _lastRouterIncludeBuffer = new(); + } + + public void Initialize() + { + var tree = Parser.file_input(); + + Visit(tree); + + if (_lastEntityImport is not null) + _lastEntityImportBuffer.Add(GetOriginalText(_lastEntityImport)); + + if (_lastAssociationImport is not null) + _lastAssociationImportBuffer.Add(GetOriginalText(_lastAssociationImport)); + + if (_lastRouterImport is not null) + _lastRouterImportBuffer.Add(GetOriginalText(_lastRouterImport)); + + if (_lastRouterInclude is not null) + _lastRouterIncludeBuffer.Add(GetOriginalText(_lastRouterInclude)); + } + + private string ToImportString(string from, string import) => $"from {from} import {import}"; + + public void ImportEntity(string from, string import) + { + _lastEntityImportBuffer.Add(ToImportString(from, import)); + } + + public void ImportAssociation(string from, string import) + { + _lastAssociationImportBuffer.Add(ToImportString(from, import)); + } + + public void ImportRouter(string from, string import) + { + _lastRouterImportBuffer.Add(ToImportString(from, import)); + } + + public void IncludeRouter(string prefix, string router) + { + _lastRouterImportBuffer.Add($"app.include_router(prefix=\"/{prefix}\", router={router}.router)"); + // _lastRouterImportBuffer.Add($"app.include_router(prefix=\"/{prefix}\", router={router}.router)"); + } + + public override string Rewrite() + { + if (_lastEntityImport is not null) + Rewrite(_lastEntityImport, _lastEntityImportBuffer.ToArray()); + + if (_lastAssociationImport is not null) + Rewrite(_lastAssociationImport, _lastAssociationImportBuffer.ToArray()); + + if (_lastRouterImport is not null) + Rewrite(_lastRouterImport, _lastRouterImportBuffer.ToArray()); + + return Rewriter.GetText(); + } + + // public override object? VisitPrimary(PythonParser.PrimaryContext context) + // { + // // Console.WriteLine(GetOriginalText(context)); + // return base.VisitPrimary(context); + // } + // + // public override object? VisitName_or_attr(PythonParser.Name_or_attrContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // return base.VisitName_or_attr(context); + // } + // + // public override object? VisitStatement(PythonParser.StatementContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // return base.VisitStatement(context); + // } + + // public override object? VisitDotted_name(PythonParser.Dotted_nameContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // return base.VisitDotted_name(context); + // } + // + // public override object? VisitDotted_name(PythonParser.Dotted_nameContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // + // return base.VisitDotted_name(context); + // } + // + // public override object? VisitDotted_as_names(PythonParser.Dotted_as_namesContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // + // return base.VisitDotted_as_names(context); + // } + // + public override object? VisitErrorNode(IErrorNode node) + { + Console.WriteLine(node.GetText()); + return base.VisitErrorNode(node); + } + + // public override object? VisitValue_pattern(PythonParser.Value_patternContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // return base.VisitValue_pattern(context); + // } + // + // public override object? VisitStar_atom(PythonParser.Star_atomContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // return base.VisitStar_atom(context); + // } + // + // public override object? VisitExpression(PythonParser.ExpressionContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // return base.VisitExpression(context); + // } + // + // public override object? VisitT_primary(PythonParser.T_primaryContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // return base.VisitT_primary(context); + // } + + // public override object? VisitAttr(PythonParser.AttrContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // return base.VisitAttr(context); + // } + + // public override object? VisitT_primary(PythonParser.T_primaryContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // return base.VisitT_primary(context); + // } + + // public override object? VisitAwait_primary(PythonParser.Await_primaryContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // return base.VisitAwait_primary(context); + // } + + // public override object? VisitTarget_with_star_atom(PythonParser.Target_with_star_atomContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // return base.VisitTarget_with_star_atom(context); + // } + + // public override object? VisitSingle_subscript_attribute_target( + // PythonParser.Single_subscript_attribute_targetContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // return base.VisitSingle_subscript_attribute_target(context); + // } + // + // public override object? VisitSingle_target(PythonParser.Single_targetContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // return base.VisitSingle_target(context); + // } + + // public override object? VisitName_or_attr(PythonParser.Name_or_attrContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // return base.VisitName_or_attr(context); + // } + + // public override object? VisitNamed_expression(PythonParser.Named_expressionContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // + // return base.VisitNamed_expression(context); + // } + + // public override object? VisitPrimary(PythonParser.PrimaryContext context) + // { + // Console.WriteLine(GetOriginalText(context)); + // return base.VisitPrimary(context); + // } + + public override object? VisitImport_from(PythonParser.Import_fromContext context) + { + var text = GetOriginalText(context); + + if (text.StartsWith($"from {Features.Db.FeatureName}.entities.associations")) + _lastAssociationImport = context; + + if (text.StartsWith($"from {Features.Db.FeatureName}.entities")) + _lastEntityImport = context; + + if (text.StartsWith($"from {Features.Api.FeatureName}.routers")) + _lastRouterImport = context; + + return base.VisitImport_from(context); + } +} \ No newline at end of file diff --git a/MycroForge.CLI/CodeGen/RequestClassGenerator.cs b/MycroForge.CLI/CodeGen/RequestClassGenerator.cs index a3a0626..67b8b71 100644 --- a/MycroForge.CLI/CodeGen/RequestClassGenerator.cs +++ b/MycroForge.CLI/CodeGen/RequestClassGenerator.cs @@ -30,7 +30,7 @@ public class RequestClassGenerator ]; private static readonly Regex ImportInfoRegex = new(@"from\s+(.+)\s+import\s+(.+)"); - private static readonly Regex FieldInfoRegex = new(@"([_a-zA-Z-0-9]+)\s*:\s*Mapped\s*\[\s*(.+)\s*\]"); + private static readonly Regex FieldInfoRegex = new(@"([_a-zA-Z-0-9]+)\s*:\s*Mapped\s*\[\s*(.+)\s*\]\s*=\s*.+"); private readonly ProjectContext _context; @@ -113,7 +113,13 @@ public class RequestClassGenerator foreach (Match match in matches) { - // Index 0 contains the whole Regex match, so we ignore this, since we're only interested in the captured groups. + // Index 0 contains the full Regex match + var fullMatch = match.Groups[0].Value; + + // Ignore relationship fields, these need to be done manually + if (fullMatch.IndexOf("=", StringComparison.Ordinal) < + fullMatch.IndexOf("relationship(", StringComparison.Ordinal)) continue; + var name = Clean(match.Groups[1].Value); var type = Clean(match.Groups[2].Value); fields.Add(new Field(name, type)); diff --git a/MycroForge.CLI/Commands/MycroForge.Db.Generate.Entity.cs b/MycroForge.CLI/Commands/MycroForge.Db.Generate.Entity.cs index 9990f24..4d14650 100644 --- a/MycroForge.CLI/Commands/MycroForge.Db.Generate.Entity.cs +++ b/MycroForge.CLI/Commands/MycroForge.Db.Generate.Entity.cs @@ -104,7 +104,7 @@ public partial class MycroForge .ToLower(); var env = await _context.ReadFile($"{Features.Db.FeatureName}/env.py"); - env = new DbEnvUpdater(env, importPath, className).Rewrite(); + env = new DbEnvModifier(env, importPath, className).Rewrite(); await _context.WriteFile($"{Features.Db.FeatureName}/env.py", env); } diff --git a/MycroForge.CLI/Features/Api.cs b/MycroForge.CLI/Features/Api.cs index c1e1b98..abadfba 100644 --- a/MycroForge.CLI/Features/Api.cs +++ b/MycroForge.CLI/Features/Api.cs @@ -4,7 +4,7 @@ public sealed class Api : IFeature { #region Main - private static readonly string[] HelloRouter = + private static readonly string[] RouterTemplate = [ "from fastapi import APIRouter", "from fastapi.responses import JSONResponse", @@ -13,16 +13,18 @@ public sealed class Api : IFeature "router = APIRouter()", "", "@router.get(\"/{name}\")", - "async def greet(name: str):", + "async def hello(name: str):", "\treturn JSONResponse(status_code=200, content=jsonable_encoder({'greeting': f\"Hello, {name}!\"}))" ]; - private static readonly string[] Main = + private static readonly string[] MainTemplate = [ "from fastapi import FastAPI", + $"from {FeatureName}.routers import hello", + "", + "", "app = FastAPI()", "", - $"from {FeatureName}.routers import hello", "app.include_router(prefix=\"/hello\", router=hello.router)" ]; @@ -42,10 +44,10 @@ public sealed class Api : IFeature "python3 -m pip freeze > requirements.txt" ); - await context.CreateFile($"{FeatureName}/routers/hello.py", HelloRouter); + await context.CreateFile($"{FeatureName}/routers/hello.py", RouterTemplate); var main = await context.ReadFile("main.py"); - main = string.Join('\n', Main) + main; + main = string.Join('\n', MainTemplate) + main; await context.WriteFile("main.py", main); config.Api = new() diff --git a/MycroForge.CLI/Features/Db.cs b/MycroForge.CLI/Features/Db.cs index 699dbc6..230c031 100644 --- a/MycroForge.CLI/Features/Db.cs +++ b/MycroForge.CLI/Features/Db.cs @@ -70,7 +70,7 @@ public sealed class Db : IFeature var env = await context.ReadFile($"{FeatureName}/env.py"); env = new DbEnvInitializer(env).Rewrite(); - env = new DbEnvUpdater(env, "user", "User").Rewrite(); + // env = new DbEnvUpdater(env, "user", "User").Rewrite(); await context.WriteFile($"{FeatureName}/env.py", env); await context.CreateFile($"{FeatureName}/settings.py", Settings); @@ -79,7 +79,7 @@ public sealed class Db : IFeature await context.CreateFile($"{FeatureName}/entities/entity_base.py", EntityBase); - await context.CreateFile($"{FeatureName}/entities/user.py", User); + // await context.CreateFile($"{FeatureName}/entities/user.py", User); await context.SaveConfig(config); } diff --git a/MycroForge.CLI/MycroForge.CLI.csproj b/MycroForge.CLI/MycroForge.CLI.csproj index 27168ea..2ac810a 100644 --- a/MycroForge.CLI/MycroForge.CLI.csproj +++ b/MycroForge.CLI/MycroForge.CLI.csproj @@ -21,7 +21,6 @@ - diff --git a/MycroForge.CLI/Program.cs b/MycroForge.CLI/Program.cs index 4565f6c..834bfd0 100644 --- a/MycroForge.CLI/Program.cs +++ b/MycroForge.CLI/Program.cs @@ -4,25 +4,25 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using MycroForge.CLI.CodeGen; -using var host = Host - .CreateDefaultBuilder() - .ConfigureServices((_, services) => - { - services - .AddServices() - .AddCommands(); - }) - .Build(); - -try -{ - await host.Services.GetRequiredService() - .InvokeAsync(args.Length == 0 ? ["--help"] : args); -} -catch(Exception e) -{ - Console.WriteLine(e.Message); -} +// using var host = Host +// .CreateDefaultBuilder() +// .ConfigureServices((_, services) => +// { +// services +// .AddServices() +// .AddCommands(); +// }) +// .Build(); +// +// try +// { +// await host.Services.GetRequiredService() +// .InvokeAsync(args.Length == 0 ? ["--help"] : args); +// } +// catch(Exception e) +// { +// Console.WriteLine(e.Message); +// } // var rewrite = new EntityFieldReader(string.Join("\n", [ @@ -57,3 +57,9 @@ catch(Exception e) // { // Console.WriteLine($"name={f.Name}, type={f.Type}"); // }); + + +var main = new MainModifier(string.Join("\n", await File.ReadAllLinesAsync("scripts/user.py"))); +main.Initialize(); + +Console.WriteLine(main.Rewrite()); \ No newline at end of file