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 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)
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}.");
}
}

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)!);
}
}
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
{
// ReSharper disable once MemberHidesStaticFromOuterClass
public class Entity : Command, ISubCommandOf<Generate>
{
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<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.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"
services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Migrations>();
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 =
[
"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

View File

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

View File

@ -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<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.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
{
// 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);

View File

@ -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<string> 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)

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