Added CRUD generation and a bunch of other stuff
This commit is contained in:
parent
3418f15103
commit
8f3bd334e8
145
MycroForge.CLI/CodeGen/CrudRouterGenerator.cs
Normal file
145
MycroForge.CLI/CodeGen/CrudRouterGenerator.cs
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
using Humanizer;
|
||||||
|
|
||||||
|
namespace MycroForge.CLI.CodeGen;
|
||||||
|
|
||||||
|
public class CrudRouterGenerator
|
||||||
|
{
|
||||||
|
private static readonly string[] Template =
|
||||||
|
[
|
||||||
|
"from typing import Annotated",
|
||||||
|
"from fastapi import APIRouter, Depends",
|
||||||
|
"from fastapi.responses import JSONResponse",
|
||||||
|
"from fastapi.encoders import jsonable_encoder",
|
||||||
|
"from %service_import_path% import %service_class_name%",
|
||||||
|
"from %create_entity_request_import_path% import %create_entity_request_class_name%",
|
||||||
|
"from %update_entity_request_import_path% import %update_entity_request_class_name%",
|
||||||
|
"",
|
||||||
|
"router = APIRouter()",
|
||||||
|
"",
|
||||||
|
"@router.get(\"/\")",
|
||||||
|
"async def list(",
|
||||||
|
"\tservice: Annotated[%service_class_name%, Depends(%service_class_name%)]",
|
||||||
|
"):",
|
||||||
|
"\ttry:",
|
||||||
|
"\t\tresult = await service.list()",
|
||||||
|
"\t\treturn JSONResponse(status_code=200, content=jsonable_encoder(result))",
|
||||||
|
"\texcept Exception as ex:",
|
||||||
|
"\t\tprint(str(ex))",
|
||||||
|
"\t\treturn JSONResponse(status_code=500, content=str(ex))",
|
||||||
|
"",
|
||||||
|
"@router.get(\"/{id}\")",
|
||||||
|
"async def get_by_id(",
|
||||||
|
"\tid: int,",
|
||||||
|
"\tservice: Annotated[%service_class_name%, Depends(%service_class_name%)]",
|
||||||
|
"):",
|
||||||
|
"\ttry:",
|
||||||
|
"\t\tresult = await service.get_by_id(id)",
|
||||||
|
"\t\treturn JSONResponse(status_code=200 if result is not None else 404, content=jsonable_encoder(result))",
|
||||||
|
"\texcept Exception as ex:",
|
||||||
|
"\t\tprint(str(ex))",
|
||||||
|
"\t\treturn JSONResponse(status_code=500, content=str(ex))",
|
||||||
|
"",
|
||||||
|
"@router.post(\"/\")",
|
||||||
|
"async def create(",
|
||||||
|
"\trequest: Create%entity_class_name%Request,",
|
||||||
|
"\tservice: Annotated[%service_class_name%, Depends(%service_class_name%)]",
|
||||||
|
"):",
|
||||||
|
"\ttry:",
|
||||||
|
"\t\tawait service.create(request.model_dump())",
|
||||||
|
"\t\treturn JSONResponse(status_code=201, content=None)",
|
||||||
|
"\texcept Exception as ex:",
|
||||||
|
"\t\tprint(str(ex))",
|
||||||
|
"\t\treturn JSONResponse(status_code=500, content=str(ex))",
|
||||||
|
"",
|
||||||
|
"@router.patch(\"/{id}\")",
|
||||||
|
"async def update(",
|
||||||
|
"\trequest: Update%entity_class_name%Request,",
|
||||||
|
"\tservice: Annotated[%service_class_name%, Depends(%service_class_name%)]",
|
||||||
|
"):",
|
||||||
|
"\ttry:",
|
||||||
|
"\t\tupdated = await service.update(request.model_dump(exclude_unset=True))",
|
||||||
|
"\t\treturn JSONResponse(status_code=204 if updated else 404, content=None)",
|
||||||
|
"\texcept Exception as ex:",
|
||||||
|
"\t\tprint(str(ex))",
|
||||||
|
"\t\treturn JSONResponse(status_code=500, content=str(ex))",
|
||||||
|
"",
|
||||||
|
"@router.delete(\"/{id}\")",
|
||||||
|
"async def delete(",
|
||||||
|
"\tid: int,",
|
||||||
|
"\tservice: Annotated[%service_class_name%, Depends(%service_class_name%)]",
|
||||||
|
"):",
|
||||||
|
"\ttry:",
|
||||||
|
"\t\tdeleted = await service.delete(id)",
|
||||||
|
"\t\treturn JSONResponse(status_code=204 if deleted else 404, content=None)",
|
||||||
|
"\texcept Exception as ex:",
|
||||||
|
"\t\tprint(str(ex))",
|
||||||
|
"\t\treturn JSONResponse(status_code=500, content=str(ex))",
|
||||||
|
];
|
||||||
|
|
||||||
|
private readonly ProjectContext _context;
|
||||||
|
|
||||||
|
public CrudRouterGenerator(ProjectContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Generate(string path, string entity)
|
||||||
|
{
|
||||||
|
var entitySnakeCaseName = entity.Underscore().ToLower();
|
||||||
|
var entityClassName = entity.Pascalize();
|
||||||
|
var serviceClassName = $"{entityClassName}Service";
|
||||||
|
var entityRoutePrefix = entity.Kebaberize().Pluralize().ToLower();
|
||||||
|
|
||||||
|
var servicesFolderPath = $"{Features.Api.FeatureName}/services/{path}";
|
||||||
|
var serviceFilePath = $"{servicesFolderPath}/{entitySnakeCaseName}_service.py";
|
||||||
|
var serviceImportPath = serviceFilePath
|
||||||
|
.Replace('/', '.')
|
||||||
|
.Replace('\\', '.')
|
||||||
|
.Replace(".py", string.Empty)
|
||||||
|
.Trim();
|
||||||
|
|
||||||
|
var routersFolderPath = $"{Features.Api.FeatureName}/routers/{path}";
|
||||||
|
var routerFilePath = $"{routersFolderPath}/{entitySnakeCaseName}.py";
|
||||||
|
var routerImportPath = routersFolderPath
|
||||||
|
.Replace('/', '.')
|
||||||
|
.Replace('\\', '.')
|
||||||
|
.Trim();
|
||||||
|
|
||||||
|
var requestsFolderPath = $"{Features.Api.FeatureName}/requests/{path}";
|
||||||
|
|
||||||
|
var createRequestImportPath = $"{requestsFolderPath}/Create{entityClassName}Request"
|
||||||
|
.Replace('/', '.')
|
||||||
|
.Replace('\\', '.')
|
||||||
|
.Underscore()
|
||||||
|
.ToLower();
|
||||||
|
var createRequestClassName = $"Create{entityClassName}Request";
|
||||||
|
|
||||||
|
var updateRequestImportPath = $"{requestsFolderPath}/Update{entityClassName}Request"
|
||||||
|
.Replace('/', '.')
|
||||||
|
.Replace('\\', '.')
|
||||||
|
.Underscore()
|
||||||
|
.ToLower();
|
||||||
|
var updateRequestClassName = $"Update{entityClassName}Request";
|
||||||
|
|
||||||
|
var router = string.Join("\n", Template)
|
||||||
|
.Replace("%service_import_path%", serviceImportPath)
|
||||||
|
.Replace("%entity_class_name%", entityClassName)
|
||||||
|
.Replace("%service_class_name%", serviceClassName)
|
||||||
|
.Replace("%create_entity_request_import_path%", createRequestImportPath)
|
||||||
|
.Replace("%create_entity_request_class_name%", createRequestClassName)
|
||||||
|
.Replace("%update_entity_request_import_path%", updateRequestImportPath)
|
||||||
|
.Replace("%update_entity_request_class_name%", updateRequestClassName);
|
||||||
|
|
||||||
|
await _context.CreateFile(routerFilePath, router);
|
||||||
|
|
||||||
|
var main = await _context.ReadFile("main.py");
|
||||||
|
|
||||||
|
main += string.Join('\n', [
|
||||||
|
"",
|
||||||
|
$"from {routerImportPath} import {entitySnakeCaseName}",
|
||||||
|
$"app.include_router(prefix=\"/{entityRoutePrefix}\", router={entitySnakeCaseName}.router)"
|
||||||
|
]);
|
||||||
|
|
||||||
|
await _context.WriteFile("main.py", main);
|
||||||
|
}
|
||||||
|
}
|
89
MycroForge.CLI/CodeGen/CrudServiceGenerator.cs
Normal file
89
MycroForge.CLI/CodeGen/CrudServiceGenerator.cs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
using Humanizer;
|
||||||
|
|
||||||
|
namespace MycroForge.CLI.CodeGen;
|
||||||
|
|
||||||
|
public class CrudServiceGenerator
|
||||||
|
{
|
||||||
|
private static readonly string[] Template =
|
||||||
|
[
|
||||||
|
"from typing import Any, Dict, List, Optional",
|
||||||
|
"from sqlalchemy import select",
|
||||||
|
$"from {Features.Db.FeatureName}.engine.async_session import async_session",
|
||||||
|
"from %entity_import_path% import %entity_class_name%",
|
||||||
|
"",
|
||||||
|
"class %entity_class_name%Service:",
|
||||||
|
"\tasync def list(self) -> List[%entity_class_name%]:",
|
||||||
|
"\t\tasync with async_session() as session:",
|
||||||
|
"\t\t\tstmt = select(%entity_class_name%)",
|
||||||
|
"\t\t\tresults = (await session.scalars(stmt)).all()",
|
||||||
|
"\t\t\treturn results",
|
||||||
|
"",
|
||||||
|
"\tasync def get_by_id(self, id: int) -> Optional[%entity_class_name%]:",
|
||||||
|
"\t\tasync with async_session() as session:",
|
||||||
|
"\t\t\tstmt = select(%entity_class_name%).where(%entity_class_name%.id == id)",
|
||||||
|
"\t\t\tresult = (await session.scalars(stmt)).first()",
|
||||||
|
"\t\t\treturn result",
|
||||||
|
"",
|
||||||
|
"\tasync def create(self, data: Dict[str, Any]) -> None:",
|
||||||
|
"\t\tasync with async_session() as session:",
|
||||||
|
"\t\t\tentity = %entity_class_name%(**data)",
|
||||||
|
"\t\t\tsession.add(entity)",
|
||||||
|
"\t\t\tawait session.commit()",
|
||||||
|
"",
|
||||||
|
"\tasync def update(self, id: int, data: Dict[str, Any]) -> bool:",
|
||||||
|
"\t\tasync with async_session() as session:",
|
||||||
|
"\t\t\tstmt = select(%entity_class_name%).where(%entity_class_name%.id == id)",
|
||||||
|
"\t\t\tentity = (await session.scalars(stmt)).first()",
|
||||||
|
"",
|
||||||
|
"\t\t\tif entity is None:",
|
||||||
|
"\t\t\t\treturn False",
|
||||||
|
"\t\t\telse:",
|
||||||
|
"\t\t\t\tfor key, value in data.items():",
|
||||||
|
"\t\t\t\t\tsetattr(entity, key, value)",
|
||||||
|
"\t\t\t\tawait session.commit()",
|
||||||
|
"\t\t\t\treturn True",
|
||||||
|
"",
|
||||||
|
"\tasync def delete(self, id: int) -> bool:",
|
||||||
|
"\t\tasync with async_session() as session:",
|
||||||
|
"\t\t\tstmt = select(%entity_class_name%).where(%entity_class_name%.id == id)",
|
||||||
|
"\t\t\tentity = (await session.scalars(stmt)).first()",
|
||||||
|
"",
|
||||||
|
"\t\t\tif entity is None:",
|
||||||
|
"\t\t\t\treturn False",
|
||||||
|
"\t\t\telse:",
|
||||||
|
"\t\t\t\tawait session.delete(entity)",
|
||||||
|
"\t\t\t\tawait session.commit()",
|
||||||
|
"\t\t\t\treturn True",
|
||||||
|
];
|
||||||
|
|
||||||
|
private readonly ProjectContext _context;
|
||||||
|
|
||||||
|
public CrudServiceGenerator(ProjectContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Generate(string path, string entity)
|
||||||
|
{
|
||||||
|
var entitySnakeCaseName = entity.Underscore().ToLower();
|
||||||
|
var entityClassName = entity.Pascalize();
|
||||||
|
|
||||||
|
var entitiesFolderPath = $"{Features.Db.FeatureName}/entities/{path}";
|
||||||
|
var entityFilePath = $"{entitiesFolderPath}/{entitySnakeCaseName}.py";
|
||||||
|
var entityImportPath = entityFilePath
|
||||||
|
.Replace('/', '.')
|
||||||
|
.Replace('\\', '.')
|
||||||
|
.Replace(".py", string.Empty)
|
||||||
|
.Trim();
|
||||||
|
|
||||||
|
var servicesFolderPath = $"{Features.Api.FeatureName}/services/{path}";
|
||||||
|
var serviceFilePath = $"{servicesFolderPath}/{entity.Underscore().ToLower()}_service.py";
|
||||||
|
|
||||||
|
var service = string.Join("\n", Template)
|
||||||
|
.Replace("%entity_import_path%", entityImportPath)
|
||||||
|
.Replace("%entity_class_name%", entityClassName)
|
||||||
|
;
|
||||||
|
|
||||||
|
await _context.CreateFile(serviceFilePath, service);
|
||||||
|
}
|
||||||
|
}
|
@ -32,8 +32,8 @@ public class DbEnvInitializer : PythonSourceModifier
|
|||||||
|
|
||||||
Rewrite(_alembicImport, [
|
Rewrite(_alembicImport, [
|
||||||
GetOriginalText(_alembicImport),
|
GetOriginalText(_alembicImport),
|
||||||
"from db.settings import DbSettings",
|
$"from {Features.Db.FeatureName}.settings import DbSettings",
|
||||||
"from db.entities.entity_base import EntityBase"
|
$"from {Features.Db.FeatureName}.entities.entity_base import EntityBase"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Rewrite(_targetMetaDataAssignment, "target_metadata = EntityBase.metadata");
|
Rewrite(_targetMetaDataAssignment, "target_metadata = EntityBase.metadata");
|
||||||
|
@ -26,7 +26,7 @@ public class DbEnvUpdater : PythonSourceModifier
|
|||||||
|
|
||||||
Rewrite(_lastImport, [
|
Rewrite(_lastImport, [
|
||||||
lastImportText,
|
lastImportText,
|
||||||
$"from db.entities.{_importPath} import {_className}"
|
$"from {Features.Db.FeatureName}.entities.{_importPath} import {_className}"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return Rewriter.GetText();
|
return Rewriter.GetText();
|
||||||
@ -36,7 +36,7 @@ public class DbEnvUpdater : PythonSourceModifier
|
|||||||
{
|
{
|
||||||
var text = GetOriginalText(context);
|
var text = GetOriginalText(context);
|
||||||
|
|
||||||
if (text.StartsWith("from db.entities"))
|
if (text.StartsWith($"from {Features.Db.FeatureName}.entities"))
|
||||||
_lastImport = context;
|
_lastImport = context;
|
||||||
|
|
||||||
return base.VisitImport_from(context);
|
return base.VisitImport_from(context);
|
||||||
|
@ -9,7 +9,7 @@ public partial class EntityLinker
|
|||||||
private static readonly string[] AssociationTable =
|
private static readonly string[] AssociationTable =
|
||||||
[
|
[
|
||||||
"from sqlalchemy import Column, ForeignKey, Table",
|
"from sqlalchemy import Column, ForeignKey, Table",
|
||||||
"from db.entities.entity_base import EntityBase",
|
$"from {Features.Db.FeatureName}.entities.entity_base import EntityBase",
|
||||||
"",
|
"",
|
||||||
"%left_entity%_%right_entity%_mapping = Table(",
|
"%left_entity%_%right_entity%_mapping = Table(",
|
||||||
"\t\"%left_entity%_%right_entity%_mapping\",",
|
"\t\"%left_entity%_%right_entity%_mapping\",",
|
||||||
|
@ -1,32 +1,15 @@
|
|||||||
using Antlr4.Runtime;
|
using Antlr4.Runtime;
|
||||||
using MycroForge.Parsing;
|
|
||||||
|
|
||||||
namespace MycroForge.CLI.CodeGen;
|
namespace MycroForge.CLI.CodeGen;
|
||||||
|
|
||||||
public abstract class PythonSourceModifier : PythonParserBaseVisitor<object?>
|
public abstract class PythonSourceModifier : PythonSourceVisitor
|
||||||
{
|
{
|
||||||
protected CommonTokenStream Stream { get; }
|
protected PythonSourceModifier(string source) : base(source)
|
||||||
protected PythonParser Parser { get; }
|
|
||||||
protected TokenStreamRewriter Rewriter { get; }
|
|
||||||
|
|
||||||
protected PythonSourceModifier(string source)
|
|
||||||
{
|
{
|
||||||
var input = new AntlrInputStream(source);
|
|
||||||
var lexer = new PythonLexer(input);
|
|
||||||
Stream = new CommonTokenStream(lexer);
|
|
||||||
Parser = new PythonParser(Stream);
|
|
||||||
Rewriter = new TokenStreamRewriter(Stream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract string Rewrite();
|
public abstract string Rewrite();
|
||||||
|
|
||||||
protected string GetOriginalText(ParserRuleContext context)
|
|
||||||
{
|
|
||||||
// The parser does not necessarily return the original source,
|
|
||||||
// so we return the text from Rewriter.TokenStream, since this is unmodified.
|
|
||||||
return Rewriter.TokenStream.GetText(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void Rewrite(ParserRuleContext context, params string[] text)
|
protected void Rewrite(ParserRuleContext context, params string[] text)
|
||||||
{
|
{
|
||||||
Rewriter.Replace(from: context.start, to: context.Stop, text: string.Join('\n', text));
|
Rewriter.Replace(from: context.start, to: context.Stop, text: string.Join('\n', text));
|
||||||
|
27
MycroForge.CLI/CodeGen/PythonSourceVisitor.cs
Normal file
27
MycroForge.CLI/CodeGen/PythonSourceVisitor.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using Antlr4.Runtime;
|
||||||
|
using MycroForge.Parsing;
|
||||||
|
|
||||||
|
namespace MycroForge.CLI.CodeGen;
|
||||||
|
|
||||||
|
public abstract class PythonSourceVisitor : PythonParserBaseVisitor<object?>
|
||||||
|
{
|
||||||
|
protected CommonTokenStream Stream { get; }
|
||||||
|
protected PythonParser Parser { get; }
|
||||||
|
protected TokenStreamRewriter Rewriter { get; }
|
||||||
|
|
||||||
|
protected PythonSourceVisitor(string source)
|
||||||
|
{
|
||||||
|
var input = new AntlrInputStream(source);
|
||||||
|
var lexer = new PythonLexer(input);
|
||||||
|
Stream = new CommonTokenStream(lexer);
|
||||||
|
Parser = new PythonParser(Stream);
|
||||||
|
Rewriter = new TokenStreamRewriter(Stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected string GetOriginalText(ParserRuleContext context)
|
||||||
|
{
|
||||||
|
// The parser does not necessarily return the original source,
|
||||||
|
// so we return the text from Rewriter.TokenStream, since this is unmodified.
|
||||||
|
return Rewriter.TokenStream.GetText(context);
|
||||||
|
}
|
||||||
|
}
|
155
MycroForge.CLI/CodeGen/RequestClassGenerator.cs
Normal file
155
MycroForge.CLI/CodeGen/RequestClassGenerator.cs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Humanizer;
|
||||||
|
|
||||||
|
namespace MycroForge.CLI.CodeGen;
|
||||||
|
|
||||||
|
public class RequestClassGenerator
|
||||||
|
{
|
||||||
|
public record Import(string Name, List<string> Types)
|
||||||
|
{
|
||||||
|
// The Match method accounts for generic types like List[str] or Dict[str, Any]
|
||||||
|
public bool Match(string type) => Types.Any(t => type == t || type.StartsWith(t));
|
||||||
|
public string FindType(string type) => Types.First(t => type == t || type.StartsWith(t));
|
||||||
|
};
|
||||||
|
|
||||||
|
public record Field(string Name, string Type);
|
||||||
|
|
||||||
|
public enum Type
|
||||||
|
{
|
||||||
|
Create,
|
||||||
|
Update
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly string[] Template =
|
||||||
|
[
|
||||||
|
"from pydantic import BaseModel",
|
||||||
|
"%imports%",
|
||||||
|
"",
|
||||||
|
"class %request_type%%entity_class_name%Request(BaseModel):",
|
||||||
|
"%fields%",
|
||||||
|
];
|
||||||
|
|
||||||
|
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 readonly ProjectContext _context;
|
||||||
|
|
||||||
|
public RequestClassGenerator(ProjectContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Generate(string path, string entity, Type type)
|
||||||
|
{
|
||||||
|
var entitySnakeCaseName = entity.Underscore().ToLower();
|
||||||
|
var entityClassName = entity.Pascalize();
|
||||||
|
var entitiesFolderPath = $"{Features.Db.FeatureName}/entities/{path}";
|
||||||
|
var entityFilePath = $"{entitiesFolderPath}/{entitySnakeCaseName}.py";
|
||||||
|
var entitySource = await _context.ReadFile(entityFilePath);
|
||||||
|
|
||||||
|
var fieldInfo = ReadFields(entitySource);
|
||||||
|
var fields = string.Join('\n', fieldInfo.Select(x => ToFieldString(x, type)));
|
||||||
|
|
||||||
|
var requestsFolderPath = $"{Features.Api.FeatureName}/requests/{path}";
|
||||||
|
var updateRequestFilePath =
|
||||||
|
$"{requestsFolderPath}/{type.ToString().ToLower()}_{entitySnakeCaseName}_request.py";
|
||||||
|
|
||||||
|
var service = string.Join("\n", Template)
|
||||||
|
.Replace("%imports%", GetImportString(entitySource, fieldInfo, type))
|
||||||
|
.Replace("%request_type%", type.ToString().Pascalize())
|
||||||
|
.Replace("%entity_class_name%", entityClassName)
|
||||||
|
.Replace("%fields%", fields)
|
||||||
|
;
|
||||||
|
|
||||||
|
await _context.CreateFile(updateRequestFilePath, service);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ToFieldString(Field field, Type type)
|
||||||
|
{
|
||||||
|
var @string = $"\t{field.Name}: ";
|
||||||
|
|
||||||
|
if (type == Type.Create)
|
||||||
|
{
|
||||||
|
@string += $"{field.Type} = None";
|
||||||
|
}
|
||||||
|
else if (type == Type.Update)
|
||||||
|
{
|
||||||
|
@string += $"Optional[{field.Type}] = None";
|
||||||
|
}
|
||||||
|
else throw new Exception($"Request type {type} is not supported.");
|
||||||
|
|
||||||
|
return @string;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetImportString(string entitySource, List<Field> fields, Type type)
|
||||||
|
{
|
||||||
|
var imports = GetImports(entitySource);
|
||||||
|
var importStringBuffer = type == Type.Create
|
||||||
|
? new Dictionary<string, List<string>>()
|
||||||
|
: new Dictionary<string, List<string>> { ["typing"] = ["Optional"] };
|
||||||
|
|
||||||
|
foreach (var field in fields)
|
||||||
|
{
|
||||||
|
if (imports.FirstOrDefault(i => i.Match(field.Type)) is Import import)
|
||||||
|
{
|
||||||
|
if (!importStringBuffer.ContainsKey(import.Name))
|
||||||
|
{
|
||||||
|
importStringBuffer.Add(import.Name, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
importStringBuffer[import.Name].Add(import.FindType(field.Type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join("\n", importStringBuffer.Select(
|
||||||
|
pair => $"from {pair.Key} import {string.Join(", ", pair.Value)}\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Field> ReadFields(string entitySource)
|
||||||
|
{
|
||||||
|
var fields = new List<Field>();
|
||||||
|
var matches = FieldInfoRegex.Matches(entitySource);
|
||||||
|
|
||||||
|
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.
|
||||||
|
var name = Clean(match.Groups[1].Value);
|
||||||
|
var type = Clean(match.Groups[2].Value);
|
||||||
|
fields.Add(new Field(name, type));
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Import> GetImports(string entitySource)
|
||||||
|
{
|
||||||
|
var imports = new List<Import>();
|
||||||
|
|
||||||
|
var matches = ImportInfoRegex.Matches(entitySource);
|
||||||
|
|
||||||
|
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.
|
||||||
|
var name = Clean(match.Groups[1].Value);
|
||||||
|
var types = Clean(match.Groups[2].Value)
|
||||||
|
.Split(',')
|
||||||
|
.Select(s => s.Trim())
|
||||||
|
.ToArray();
|
||||||
|
imports.Add(new Import(name, [..types]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imports.FirstOrDefault(i => i.Name == "typing") is Import typingImport)
|
||||||
|
{
|
||||||
|
typingImport.Types.AddRange(["Any", "Dict", "List", "Optional"]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
imports.Add(new("typing", ["Any", "Dict", "List", "Optional"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return imports;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Clean(string value) => value.Replace(" ", string.Empty).Trim();
|
||||||
|
}
|
45
MycroForge.CLI/Commands/MycroForge.Api.Generate.Crud.cs
Normal file
45
MycroForge.CLI/Commands/MycroForge.Api.Generate.Crud.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using System.CommandLine;
|
||||||
|
using MycroForge.CLI.CodeGen;
|
||||||
|
using MycroForge.CLI.Commands.Interfaces;
|
||||||
|
|
||||||
|
namespace MycroForge.CLI.Commands;
|
||||||
|
|
||||||
|
public partial class MycroForge
|
||||||
|
{
|
||||||
|
public partial class Api
|
||||||
|
{
|
||||||
|
public partial class Generate
|
||||||
|
{
|
||||||
|
public class Crud : Command, ISubCommandOf<Generate>
|
||||||
|
{
|
||||||
|
private static readonly Argument<string> EntityArgument =
|
||||||
|
new(name: "entity", description: "The entity to target");
|
||||||
|
|
||||||
|
private readonly ProjectContext _context;
|
||||||
|
|
||||||
|
public Crud(ProjectContext context)
|
||||||
|
: base("crud", "Generated CRUD functionality for an entity")
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
AddArgument(EntityArgument);
|
||||||
|
this.SetHandler(ExecuteAsync, EntityArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExecuteAsync(string entity)
|
||||||
|
{
|
||||||
|
var path = string.Empty;
|
||||||
|
if (entity.Split(':').Select(s => s.Trim()).ToArray() is { Length: 2 } fullName)
|
||||||
|
{
|
||||||
|
path = fullName[0];
|
||||||
|
entity = fullName[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
await new CrudServiceGenerator(_context).Generate(path, entity);
|
||||||
|
await new RequestClassGenerator(_context).Generate(path, entity, RequestClassGenerator.Type.Create);
|
||||||
|
await new RequestClassGenerator(_context).Generate(path, entity, RequestClassGenerator.Type.Update);
|
||||||
|
await new CrudRouterGenerator(_context).Generate(path, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using System.CommandLine;
|
using System.CommandLine;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using MycroForge.CLI.Commands.Interfaces;
|
using MycroForge.CLI.Commands.Interfaces;
|
||||||
|
using MycroForge.CLI.Extensions;
|
||||||
|
|
||||||
namespace MycroForge.CLI.Commands;
|
namespace MycroForge.CLI.Commands;
|
||||||
|
|
||||||
@ -40,16 +41,30 @@ public partial class MycroForge
|
|||||||
|
|
||||||
private async Task ExecuteAsync(string name)
|
private async Task ExecuteAsync(string name)
|
||||||
{
|
{
|
||||||
_context.AssertDirectoryExists($"{Features.Api.FeatureName}/routers");
|
var folderPath = $"{Features.Api.FeatureName}/routers";
|
||||||
|
|
||||||
var moduleName = name.Underscore();
|
_context.AssertDirectoryExists(folderPath);
|
||||||
await _context.CreateFile($"{Features.Api.FeatureName}/routers/{moduleName}.py", Template);
|
|
||||||
|
if (name.FullyQualifiedName() is { Length: 2 } fullName)
|
||||||
|
{
|
||||||
|
folderPath = Path.Join(folderPath, fullName[0]);
|
||||||
|
name = fullName[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
var moduleImportPath = folderPath.Replace('\\', '.').Replace('/', '.');
|
||||||
|
var moduleName = name.Underscore().ToLower();
|
||||||
|
var fileName = $"{moduleName}.py";
|
||||||
|
var filePath = Path.Join(folderPath, fileName);
|
||||||
|
|
||||||
|
await _context.CreateFile(filePath, Template);
|
||||||
|
|
||||||
var main = await _context.ReadFile("main.py");
|
var main = await _context.ReadFile("main.py");
|
||||||
|
|
||||||
main += string.Join('\n',
|
main += string.Join('\n',
|
||||||
$"\n\nfrom {Features.Api.FeatureName}.routers import {moduleName}",
|
$"\n\nfrom {moduleImportPath} import {moduleName}",
|
||||||
$"app.include_router(prefix=\"/{name.Kebaberize()}\", router={moduleName}.router)"
|
$"app.include_router(prefix=\"/{name.Kebaberize()}\", router={moduleName}.router)"
|
||||||
);
|
);
|
||||||
|
|
||||||
await _context.WriteFile("main.py", main);
|
await _context.WriteFile("main.py", main);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ using System.CommandLine;
|
|||||||
using Humanizer;
|
using Humanizer;
|
||||||
using MycroForge.CLI.CodeGen;
|
using MycroForge.CLI.CodeGen;
|
||||||
using MycroForge.CLI.Commands.Interfaces;
|
using MycroForge.CLI.Commands.Interfaces;
|
||||||
|
using MycroForge.CLI.Extensions;
|
||||||
|
|
||||||
namespace MycroForge.CLI.Commands;
|
namespace MycroForge.CLI.Commands;
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ public partial class MycroForge
|
|||||||
[
|
[
|
||||||
"from sqlalchemy import %type_imports%",
|
"from sqlalchemy import %type_imports%",
|
||||||
"from sqlalchemy.orm import Mapped, mapped_column",
|
"from sqlalchemy.orm import Mapped, mapped_column",
|
||||||
"from db.entities.entity_base import EntityBase",
|
$"from {Features.Db.FeatureName}.entities.entity_base import EntityBase",
|
||||||
"",
|
"",
|
||||||
"class %class_name%(EntityBase):",
|
"class %class_name%(EntityBase):",
|
||||||
"\t__tablename__ = \"%table_name%\"",
|
"\t__tablename__ = \"%table_name%\"",
|
||||||
@ -67,12 +68,13 @@ public partial class MycroForge
|
|||||||
|
|
||||||
private async Task ExecuteAsync(string name, IEnumerable<string> columns)
|
private async Task ExecuteAsync(string name, IEnumerable<string> columns)
|
||||||
{
|
{
|
||||||
_context.AssertDirectoryExists(Features.Db.FeatureName);
|
var folderPath = $"{Features.Db.FeatureName}/entities";
|
||||||
|
|
||||||
var path = string.Empty;
|
_context.AssertDirectoryExists(Features.Db.FeatureName);
|
||||||
if (name.Split(':').Select(s => s.Trim()).ToArray() is { Length: 2 } fullName)
|
|
||||||
|
if (name.FullyQualifiedName() is { Length: 2 } fullName)
|
||||||
{
|
{
|
||||||
path = fullName[0];
|
folderPath = Path.Join(folderPath, fullName[0]);
|
||||||
name = fullName[1];
|
name = fullName[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,12 +89,12 @@ public partial class MycroForge
|
|||||||
code = code.Replace("%table_name%", name.Underscore().ToLower().Pluralize());
|
code = code.Replace("%table_name%", name.Underscore().ToLower().Pluralize());
|
||||||
code = code.Replace("%column_definitions%", columnDefinitions);
|
code = code.Replace("%column_definitions%", columnDefinitions);
|
||||||
|
|
||||||
var folderPath = Path.Join($"{Features.Db.FeatureName}/entities", path);
|
// var folderPath = Path.Join(, path);
|
||||||
var fileName = $"{name.ToLower()}.py";
|
var fileName = $"{name.Underscore().ToLower()}.py";
|
||||||
var filePath = Path.Join(folderPath, fileName);
|
var filePath = Path.Join(folderPath, fileName);
|
||||||
await _context.CreateFile(filePath, code);
|
await _context.CreateFile(filePath, code);
|
||||||
|
|
||||||
var importPathParts = new[] { path, fileName.Replace(".py", "") }
|
var importPathParts = new[] { folderPath, fileName.Replace(".py", "") }
|
||||||
.Where(s => !string.IsNullOrEmpty(s));
|
.Where(s => !string.IsNullOrEmpty(s));
|
||||||
|
|
||||||
var importPath = string.Join('.', importPathParts)
|
var importPath = string.Join('.', importPathParts)
|
||||||
|
@ -32,7 +32,6 @@ public partial class MycroForge
|
|||||||
this.SetHandler(ExecuteAsync, LeftArgument, ToOneOption, ToManyOption);
|
this.SetHandler(ExecuteAsync, LeftArgument, ToOneOption, ToManyOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task ExecuteAsync(string left, string? toOneOption, string? toManyOption)
|
private async Task ExecuteAsync(string left, string? toOneOption, string? toManyOption)
|
||||||
{
|
{
|
||||||
if (toOneOption is not null && toManyOption is not null)
|
if (toOneOption is not null && toManyOption is not null)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.CommandLine;
|
using System.CommandLine;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using MycroForge.CLI.Commands.Interfaces;
|
using MycroForge.CLI.Commands.Interfaces;
|
||||||
|
using MycroForge.CLI.Extensions;
|
||||||
|
|
||||||
namespace MycroForge.CLI.Commands;
|
namespace MycroForge.CLI.Commands;
|
||||||
|
|
||||||
@ -13,36 +14,33 @@ public partial class MycroForge
|
|||||||
private static readonly Argument<string> NameArgument =
|
private static readonly Argument<string> NameArgument =
|
||||||
new(name: "name", description: "The name of the service");
|
new(name: "name", description: "The name of the service");
|
||||||
|
|
||||||
private static readonly Option<string> PathOption =
|
|
||||||
new(name: "--path", description: "The folder path of the service") { IsRequired = true };
|
|
||||||
|
|
||||||
private static readonly Option<bool> WithSessionOption =
|
private static readonly Option<bool> WithSessionOption =
|
||||||
new(name: "--with-session", description: "Create a service that uses database sessions");
|
new(name: "--with-session", description: "Create a service that uses database sessions");
|
||||||
|
|
||||||
|
|
||||||
private static readonly string[] DefaultTemplate =
|
private static readonly string[] DefaultTemplate =
|
||||||
[
|
[
|
||||||
"class %class_name%:",
|
"class %class_name%:",
|
||||||
"",
|
"",
|
||||||
"\tdef do_stuff(self, stuff: str) -> str:",
|
"\tdef hello(self, name: str) -> str:",
|
||||||
"\t\treturn f\"Hey, I'm doing stuff!\""
|
"\t\treturn f\"Hello, {str}!\""
|
||||||
];
|
];
|
||||||
|
|
||||||
private static readonly string[] WithSessionTemplate =
|
private static readonly string[] WithSessionTemplate =
|
||||||
[
|
[
|
||||||
"from db.engine.async_session import async_session",
|
"from typing import List",
|
||||||
"from sqlalchemy import select",
|
"from sqlalchemy import select",
|
||||||
"# from db.entities.some_entity import SomeEntity",
|
$"from {Features.Db.FeatureName}.engine.async_session import async_session",
|
||||||
|
$"# from {Features.Db.FeatureName}.entities.entity import Entity",
|
||||||
"",
|
"",
|
||||||
"class %class_name%Service:",
|
"class %class_name%:",
|
||||||
"",
|
"",
|
||||||
"\tasync def do_stuff(self, stuff: str) -> str:",
|
"\tasync def list(self, value: str) -> List[Entity]:",
|
||||||
"\t\tasync with async_session() as session:",
|
"\t\tasync with async_session() as session:",
|
||||||
"\t\t\t# stmt = select(User).where(SomeEntity.value == \"some_value\")",
|
"\t\t\t# stmt = select(User).where(Entity.value == value)",
|
||||||
"\t\t\t# results = (await session.scalars(stmt)).all()",
|
"\t\t\t# results = (await session.scalars(stmt)).all()",
|
||||||
"\t\t\t# print(len(results))",
|
"\t\t\t# return results",
|
||||||
"\t\t\tpass",
|
"\t\t\tpass",
|
||||||
"\t\treturn f\"Hey, I'm doing stuff!\""
|
"\t\treturn []"
|
||||||
];
|
];
|
||||||
|
|
||||||
private readonly ProjectContext _context;
|
private readonly ProjectContext _context;
|
||||||
@ -52,26 +50,25 @@ public partial class MycroForge
|
|||||||
_context = context;
|
_context = context;
|
||||||
AddAlias("s");
|
AddAlias("s");
|
||||||
AddArgument(NameArgument);
|
AddArgument(NameArgument);
|
||||||
AddOption(PathOption);
|
|
||||||
AddOption(WithSessionOption);
|
AddOption(WithSessionOption);
|
||||||
this.SetHandler(ExecuteAsync, NameArgument, PathOption, WithSessionOption);
|
this.SetHandler(ExecuteAsync, NameArgument, WithSessionOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecuteAsync(string name, string? path, bool withSession)
|
private async Task ExecuteAsync(string name, bool withSession)
|
||||||
{
|
{
|
||||||
var folderPath = "services";
|
var folderPath = string.Empty;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(path) && !path.Equals("."))
|
if (name.FullyQualifiedName() is { Length: 2} fullName)
|
||||||
{
|
{
|
||||||
folderPath = Path.Join(_context.RootDirectory, path);
|
folderPath = Path.Join(folderPath, fullName[0]);
|
||||||
Directory.CreateDirectory(folderPath);
|
name = fullName[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var filePath = Path.Join(folderPath, $"{name.Underscore().ToLower()}.py");
|
||||||
var className = Path.GetFileName(name).Pascalize();
|
var className = Path.GetFileName(name).Pascalize();
|
||||||
var code = string.Join('\n', withSession ? WithSessionTemplate : DefaultTemplate)
|
var code = string.Join('\n', withSession ? WithSessionTemplate : DefaultTemplate)
|
||||||
.Replace("%class_name%", className);
|
.Replace("%class_name%", className);
|
||||||
|
|
||||||
var filePath = Path.Join(folderPath, $"{name.Underscore().ToLower()}_service.py");
|
|
||||||
await _context.CreateFile(filePath, code);
|
await _context.CreateFile(filePath, code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.CommandLine;
|
using System.CommandLine;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using Microsoft.Scripting.Utils;
|
||||||
using MycroForge.CLI.Commands.Interfaces;
|
using MycroForge.CLI.Commands.Interfaces;
|
||||||
|
|
||||||
namespace MycroForge.CLI.Commands;
|
namespace MycroForge.CLI.Commands;
|
||||||
|
32
MycroForge.CLI/Commands/MycroForge.Script.List.cs
Normal file
32
MycroForge.CLI/Commands/MycroForge.Script.List.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using System.CommandLine;
|
||||||
|
using MycroForge.CLI.Commands.Interfaces;
|
||||||
|
|
||||||
|
namespace MycroForge.CLI.Commands;
|
||||||
|
|
||||||
|
public partial class MycroForge
|
||||||
|
{
|
||||||
|
public partial class Script
|
||||||
|
{
|
||||||
|
public class List : Command, ISubCommandOf<Script>
|
||||||
|
{
|
||||||
|
public List() : base("list", "Show available scripts")
|
||||||
|
{
|
||||||
|
this.SetHandler(Execute);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Execute()
|
||||||
|
{
|
||||||
|
var folder = Path.Join(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".m4g"
|
||||||
|
);
|
||||||
|
|
||||||
|
var files = Directory.GetFiles(folder)
|
||||||
|
.Select(Path.GetFileName)
|
||||||
|
.Select(p => p.Replace(".py", ""));
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
Console.WriteLine(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,12 +7,12 @@ public partial class MycroForge
|
|||||||
{
|
{
|
||||||
public partial class Script : Command, ISubCommandOf<MycroForge>
|
public partial class Script : Command, ISubCommandOf<MycroForge>
|
||||||
{
|
{
|
||||||
public Script(IEnumerable<ISubCommandOf<Script>> subCommands) :
|
public Script(IEnumerable<ISubCommandOf<Script>> commands) :
|
||||||
base("script", "Script related commands")
|
base("script", "Script related commands")
|
||||||
{
|
{
|
||||||
AddAlias("s");
|
AddAlias("s");
|
||||||
foreach (var subCommandOf in subCommands.Cast<Command>())
|
foreach (var command in commands.Cast<Command>())
|
||||||
AddCommand(subCommandOf);
|
AddCommand(command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -35,6 +35,7 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Api>, Commands.MycroForge.Api.Run>();
|
services.AddScoped<ISubCommandOf<Commands.MycroForge.Api>, Commands.MycroForge.Api.Run>();
|
||||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Api>, Commands.MycroForge.Api.Generate>();
|
services.AddScoped<ISubCommandOf<Commands.MycroForge.Api>, Commands.MycroForge.Api.Generate>();
|
||||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Api.Generate>, Commands.MycroForge.Api.Generate.Router>();
|
services.AddScoped<ISubCommandOf<Commands.MycroForge.Api.Generate>, Commands.MycroForge.Api.Generate.Router>();
|
||||||
|
services.AddScoped<ISubCommandOf<Commands.MycroForge.Api.Generate>, Commands.MycroForge.Api.Generate.Crud>();
|
||||||
|
|
||||||
// Register "m4g orm"
|
// Register "m4g orm"
|
||||||
services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Db>();
|
services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Db>();
|
||||||
@ -50,6 +51,7 @@ public static class ServiceCollectionExtensions
|
|||||||
// Register "m4g script"
|
// Register "m4g script"
|
||||||
services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Script>();
|
services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Script>();
|
||||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Script>, Commands.MycroForge.Script.Create>();
|
services.AddScoped<ISubCommandOf<Commands.MycroForge.Script>, Commands.MycroForge.Script.Create>();
|
||||||
|
services.AddScoped<ISubCommandOf<Commands.MycroForge.Script>, Commands.MycroForge.Script.List>();
|
||||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Script>, Commands.MycroForge.Script.Edit>();
|
services.AddScoped<ISubCommandOf<Commands.MycroForge.Script>, Commands.MycroForge.Script.Edit>();
|
||||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Script>, Commands.MycroForge.Script.Run>();
|
services.AddScoped<ISubCommandOf<Commands.MycroForge.Script>, Commands.MycroForge.Script.Run>();
|
||||||
|
|
||||||
|
@ -10,4 +10,9 @@ public static class StringExtensions
|
|||||||
var filePath = Path.Join(directoryPath, name.Underscore().ToLower());
|
var filePath = Path.Join(directoryPath, name.Underscore().ToLower());
|
||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string[] FullyQualifiedName(this string name)
|
||||||
|
{
|
||||||
|
return name.Split(':').Select(s => s.Trim()).ToArray();
|
||||||
|
}
|
||||||
}
|
}
|
@ -19,7 +19,7 @@ public sealed class Db : IFeature
|
|||||||
private static readonly string[] AsyncSession =
|
private static readonly string[] AsyncSession =
|
||||||
[
|
[
|
||||||
"from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine, AsyncSession",
|
"from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine, AsyncSession",
|
||||||
"from db.settings import DbSettings",
|
$"from {FeatureName}.settings import DbSettings",
|
||||||
"",
|
"",
|
||||||
"async_engine: AsyncEngine = create_async_engine(DbSettings.get_connectionstring())",
|
"async_engine: AsyncEngine = create_async_engine(DbSettings.get_connectionstring())",
|
||||||
"",
|
"",
|
||||||
@ -39,7 +39,7 @@ public sealed class Db : IFeature
|
|||||||
[
|
[
|
||||||
"from sqlalchemy import String",
|
"from sqlalchemy import String",
|
||||||
"from sqlalchemy.orm import Mapped, mapped_column",
|
"from sqlalchemy.orm import Mapped, mapped_column",
|
||||||
"from db.entities.entity_base import EntityBase",
|
$"from {FeatureName}.entities.entity_base import EntityBase",
|
||||||
"",
|
"",
|
||||||
"class User(EntityBase):",
|
"class User(EntityBase):",
|
||||||
"\t__tablename__ = \"users\"",
|
"\t__tablename__ = \"users\"",
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using MycroForge.CLI.Extensions;
|
using MycroForge.CLI.Extensions;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using MycroForge.CLI.CodeGen;
|
||||||
|
|
||||||
using var host = Host
|
using var host = Host
|
||||||
.CreateDefaultBuilder()
|
.CreateDefaultBuilder()
|
||||||
@ -22,3 +23,37 @@ catch(Exception e)
|
|||||||
{
|
{
|
||||||
Console.WriteLine(e.Message);
|
Console.WriteLine(e.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// var rewrite = new EntityFieldReader(string.Join("\n", [
|
||||||
|
// "from typing import Any, Dict",
|
||||||
|
// "from sqlalchemy import JSON, DateTime, String, func",
|
||||||
|
// "from sqlalchemy.orm import Mapped, mapped_column",
|
||||||
|
// "from sqlalchemy.dialects.mssql import TEXT",
|
||||||
|
// "from orm.entities.entity_base import EntityBase",
|
||||||
|
// "class Product(EntityBase):",
|
||||||
|
// "\t__tablename__ = \"products\"",
|
||||||
|
// "\tid: Mapped[int] = mapped_column(primary_key=True)",
|
||||||
|
// "\tmain_key: Mapped[str] = mapped_column(String(255), unique=True, nullable=False)",
|
||||||
|
// "\terp_key: Mapped[str] = mapped_column(String(255), unique=True, nullable=True)",
|
||||||
|
// "\tpim_key: Mapped[str] = mapped_column(String(255), unique=True, nullable=True)",
|
||||||
|
// "\twms_key: Mapped[str] = mapped_column(String(255), unique=True, nullable=True)",
|
||||||
|
// "\tshared_key: Mapped[str] = mapped_column(String(255), nullable=True)",
|
||||||
|
// "\taxis_1_code: Mapped[str] = mapped_column(String(255), nullable=True)",
|
||||||
|
// "\taxis_1_value: Mapped[str] = mapped_column(String(255), nullable=True)",
|
||||||
|
// "\taxis_2_code: Mapped[str] = mapped_column(String(255), nullable=True)",
|
||||||
|
// "\taxis_2_value: Mapped[str] = mapped_column(String(255), nullable=True)",
|
||||||
|
// "\taxis_3_code: Mapped[str] = mapped_column(String(255), nullable=True)",
|
||||||
|
// "\taxis_3_value: Mapped[str] = mapped_column(String(255), nullable=True)",
|
||||||
|
// "\tdata: Mapped[Dict[str, Any]] = mapped_column(JSON(), nullable=True)",
|
||||||
|
// "\tdata_string: Mapped[str] = mapped_column(TEXT(), nullable=True)",
|
||||||
|
// "\tcreated_at: Mapped[DateTime] = mapped_column(DateTime(timezone=True), default=func.now())",
|
||||||
|
// "\tupdated_at: Mapped[DateTime] = mapped_column(DateTime(timezone=True), default=func.now(), onupdate=func.now())",
|
||||||
|
// "def __repr__(self) -> str:",
|
||||||
|
// "\treturn f\"Product(id={self.id!r}, main_key={self.main_key!r}, shared_key={self.shared_key})\""
|
||||||
|
// ])).ReadFields();
|
||||||
|
//
|
||||||
|
// rewrite.ForEach(f =>
|
||||||
|
// {
|
||||||
|
// Console.WriteLine($"name={f.Name}, type={f.Type}");
|
||||||
|
// });
|
||||||
|
Loading…
Reference in New Issue
Block a user