Adding entity linking

This commit is contained in:
mdnapo 2024-04-23 08:56:01 +02:00
parent 3a46e20d38
commit abd116b032
17 changed files with 236 additions and 94 deletions

View File

@ -2,5 +2,5 @@
public class ArgsContext public class ArgsContext
{ {
public string[] Args { get; init; } public string[] Args { get; init; } = Array.Empty<string>();
} }

View File

@ -21,12 +21,17 @@ public static class Bash
if (process is null) if (process is null)
throw new NullReferenceException("Could not initialize bash process."); 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.BeginOutputReadLine();
process.ErrorDataReceived += (sender, args) => 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)) if (!string.IsNullOrEmpty(args.Data))
Console.WriteLine(args.Data); Console.WriteLine(args.Data);
}; };
@ -42,6 +47,6 @@ public static class Bash
await process.WaitForExitAsync(); await process.WaitForExitAsync();
if (process.ExitCode != 0) if (process.ExitCode != 0)
Console.WriteLine($"Process exited with status code {process.ExitCode}."); Console.WriteLine($"Process finished with exit code {process.ExitCode}.");
} }
} }

View File

@ -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);
}
}

View File

@ -14,20 +14,4 @@ public partial class MycroForge
AddCommand((subCommandOf as Command)!); AddCommand((subCommandOf as Command)!);
} }
} }
public class Run : Command, ISubCommandOf<MycroForge>
{
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"
]);
}
}
} }

View File

@ -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<Link>
{
private static readonly Argument<string> NameArgument =
new(name: "primary", description: "The left side of the relation");
private static readonly Option<string> ToOneOption =
new(name: "--to-one", description: "The right side of the relation");
private static readonly Option<string> 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()
{
}
}
}
}
}

View File

@ -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<Link>
{
private static readonly Argument<string> NameArgument =
new(name: "primary", description: "The left side of the relation");
private static readonly Option<string> ToOneOption =
new(name: "--to-one", description: "The right side of the relation");
private static readonly Option<string> 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()
{
}
}
}
}
}

View File

@ -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<Entity>
{
public Link(IEnumerable<ISubCommandOf<Link>> commands) :
base("link", "Define relationships between entities")
{
foreach (var command in commands)
AddCommand((command as Command)!);
}
}
}
}

View File

@ -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<MycroForge>
{
public Entity(IEnumerable<ISubCommandOf<Entity>> commands) :
base("entity", "Manage the entities in your project")
{
foreach (var command in commands.Cast<Command>())
AddCommand(command);
}
}
}

View File

@ -9,19 +9,22 @@ public partial class MycroForge
{ {
public partial class Generate public partial class Generate
{ {
// ReSharper disable once MemberHidesStaticFromOuterClass
public class Entity : Command, ISubCommandOf<Generate> public class Entity : Command, ISubCommandOf<Generate>
{ {
private static readonly string[] Template = 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", "from orm.entities.entity_base import EntityBase",
"", "",
"class %class_name%(EntityBase):", "class %class_name%(EntityBase):",
"\t__tablename__ = \"%table_name%\"", "\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:", "\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<string> NameArgument = private static readonly Argument<string> NameArgument =

View File

@ -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<MycroForge>
{
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);
}
}
}

View File

@ -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<MycroForge>
{
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"
]);
}
}
}

View File

@ -41,6 +41,12 @@ public static class ServiceCollectionExtensions
services.AddScoped<ISubCommandOf<Commands.MycroForge.Generate>, Commands.MycroForge.Generate.Router>(); services.AddScoped<ISubCommandOf<Commands.MycroForge.Generate>, Commands.MycroForge.Generate.Router>();
services.AddScoped<ISubCommandOf<Commands.MycroForge.Generate>, Commands.MycroForge.Generate.Migration>(); services.AddScoped<ISubCommandOf<Commands.MycroForge.Generate>, Commands.MycroForge.Generate.Migration>();
// Register "m4g entity"
services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Entity>();
services.AddScoped<ISubCommandOf<Commands.MycroForge.Entity>, Commands.MycroForge.Entity.Link>();
services.AddScoped<ISubCommandOf<Commands.MycroForge.Entity.Link>, Commands.MycroForge.Entity.Link.One>();
services.AddScoped<ISubCommandOf<Commands.MycroForge.Entity.Link>, Commands.MycroForge.Entity.Link.Many>();
// Register "m4g migrations" // Register "m4g migrations"
services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Migrations>(); services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Migrations>();
services.AddScoped<ISubCommandOf<Commands.MycroForge.Migrations>, Commands.MycroForge.Migrations.Apply>(); services.AddScoped<ISubCommandOf<Commands.MycroForge.Migrations>, Commands.MycroForge.Migrations.Apply>();

View File

@ -36,16 +36,18 @@ public sealed class Orm : IFeature
private static readonly string[] User = 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", "from orm.entities.entity_base import EntityBase",
"", "",
"class User(EntityBase):", "class User(EntityBase):",
"\t__tablename__ = \"users\"", "\t__tablename__ = \"users\"",
"\tid = Column(INTEGER, primary_key=True)", "\tid: Mapped[int] = mapped_column(primary_key=True)",
"\tfirstname = Column(String(255))", "\tfirstname: Mapped[str] = mapped_column(String(255))",
"\tlastname = Column(String(255))\n", "\tlastname: Mapped[str] = mapped_column(String(255))",
"def __repr__(self) -> str:", "",
"\treturn f\"User(id={self.id!r}, firstname={self.firstname!r}, lastname={self.lastname!r})\"" "\tdef __repr__(self) -> str:",
"\t\treturn f\"User(id={self.id!r}, firstname={self.firstname!r}, lastname={self.lastname!r})\""
]; ];
#endregion #endregion

View File

@ -27,6 +27,9 @@
<None Update="scripts\env.py"> <None Update="scripts\env.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Update="scripts\user.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,32 +1,32 @@
using System.CommandLine; // using System.CommandLine;
using MycroForge.CLI; // 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<ProjectContext>();
// await ctx.LoadConfig();
// await host.Services.GetRequiredService<MycroForge.CLI.Commands.MycroForge>().InvokeAsync(args);
// await ctx.SaveConfig();
// }
// catch(Exception e)
// {
// Console.WriteLine(e.Message);
// }
using MycroForge.CLI.CodeGen; using MycroForge.CLI.CodeGen;
using MycroForge.CLI.Exceptions; var src = new EntityLinker(await File.ReadAllTextAsync("scripts/user.py")).Rewrite();
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<ProjectContext>();
await ctx.LoadConfig();
await host.Services.GetRequiredService<MycroForge.CLI.Commands.MycroForge>().InvokeAsync(args);
await ctx.SaveConfig();
}
catch
{
// Console.WriteLine(e.Message);
}
// var src = new OrmEnvInitializer(await File.ReadAllTextAsync("scripts/env.py")).Rewrite();
// Console.WriteLine(src); // Console.WriteLine(src);

View File

@ -20,9 +20,9 @@ public class ProjectContext
{ {
if (_argsContext.Args if (_argsContext.Args
is ["init", ..] is ["init", ..]
or ["-?", ..] or ["-?", ..] or [.., "-?"]
or ["-h", ..] or ["-h", ..] or [.., "-h"]
or ["--help"] or ["--help"] or [.., "--help"]
or ["--version"] && !force) or ["--version"] && !force)
return; return;
@ -53,14 +53,6 @@ public class ProjectContext
await Bash.ExecuteAsync($"chmod 777 {fullPath}"); 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<string> ReadFile(string path) public async Task<string> ReadFile(string path)
{ {
var fullPath = Path.Combine(RootDirectory, path); var fullPath = Path.Combine(RootDirectory, path);
@ -72,6 +64,14 @@ public class ProjectContext
return await File.ReadAllTextAsync(fullPath); 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() public async Task SaveConfig()
{ {
if (Config is not null) if (Config is not null)

View File

@ -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})"