Cleaned up project and added constraints for SQLAlchemy types in generate entity
This commit is contained in:
parent
91431fd996
commit
32b7a3c01c
@ -96,14 +96,18 @@ public partial class EntityLinker
|
||||
var left = await LoadEntity(_left);
|
||||
var right = await LoadEntity(_right);
|
||||
|
||||
var associationTable = string.Join('\n', AssociationTable);
|
||||
associationTable = associationTable
|
||||
var associationTable = string.Join('\n', AssociationTable)
|
||||
.Replace("%left_entity%", left.ClassName.Underscore().ToLower())
|
||||
.Replace("%right_entity%", right.ClassName.Underscore().ToLower())
|
||||
.Replace("%left_table%", left.TableName)
|
||||
.Replace("%right_table%", right.TableName);
|
||||
var associationTablePath =
|
||||
$"{Features.Db.FeatureName}/entities/associations/{left.TableName.Singularize()}_{right.TableName.Singularize()}_mapping.py";
|
||||
|
||||
var associationTablePath = Path.Join(
|
||||
Features.Db.FeatureName,
|
||||
"entities",
|
||||
"associations",
|
||||
$"{left.TableName.Singularize()}_{right.TableName.Singularize()}_mapping.py"
|
||||
);
|
||||
|
||||
await _context.CreateFile(associationTablePath, associationTable);
|
||||
|
||||
@ -136,21 +140,25 @@ public partial class EntityLinker
|
||||
var env = await _context.ReadFile($"{Features.Db.FeatureName}/env.py");
|
||||
env = new DbEnvModifier(env, associationTableImportPath, associationTableImportName).Rewrite();
|
||||
await _context.WriteFile($"{Features.Db.FeatureName}/env.py", env);
|
||||
|
||||
|
||||
var main = await _context.ReadFile("main.py");
|
||||
main = new MainModifier(main).Initialize().Import(associationTableImportPath, associationTableImportName).Rewrite();
|
||||
main = new MainModifier(main)
|
||||
.Initialize()
|
||||
.Import(associationTableImportPath, associationTableImportName)
|
||||
.Rewrite();
|
||||
|
||||
await _context.WriteFile("main.py", main);
|
||||
}
|
||||
|
||||
private async Task<EntityModel> LoadEntity(string name)
|
||||
{
|
||||
var fqn = new FullyQualifiedName(name);
|
||||
var path = $"{Features.Db.FeatureName}/entities";
|
||||
var path = Path.Join(Features.Db.FeatureName, "entities");
|
||||
|
||||
if (fqn.HasNamespace)
|
||||
path = Path.Combine(path, fqn.Namespace);
|
||||
path = Path.Join(path, fqn.Namespace);
|
||||
|
||||
path = Path.Combine(path, $"{fqn.SnakeCasedName}.py");
|
||||
path = Path.Join(path, $"{fqn.SnakeCasedName}.py");
|
||||
var entity = new EntityModel(fqn.PascalizedName, path, await _context.ReadFile(path));
|
||||
entity.Initialize();
|
||||
return entity;
|
||||
|
@ -7,14 +7,16 @@ namespace MycroForge.CLI.CodeGen;
|
||||
|
||||
public class RequestClassGenerator
|
||||
{
|
||||
public record Import(string Name, List<string> Types)
|
||||
private static readonly List<string> PythonTypingImports = ["Any", "Dict", "List", "Optional"];
|
||||
|
||||
private record Import(string Name, List<string> Types)
|
||||
{
|
||||
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);
|
||||
private record Field(string Name, string Type);
|
||||
|
||||
public enum Type
|
||||
{
|
||||
@ -47,7 +49,7 @@ public class RequestClassGenerator
|
||||
var entitySource = await _context.ReadFile(entityFilePath);
|
||||
var fieldInfo = ReadFields(entitySource);
|
||||
var fields = string.Join('\n', fieldInfo.Select(x => ToFieldString(x, type)));
|
||||
|
||||
|
||||
var requestFilePath = Path.Join(
|
||||
Features.Api.FeatureName,
|
||||
"requests",
|
||||
@ -100,10 +102,11 @@ public class RequestClassGenerator
|
||||
.Replace(" ", "");
|
||||
Console.WriteLine(str); // = "List,Dict,str,Any"
|
||||
*/
|
||||
var dissectedTypes = field.Type.Replace("[", ",")
|
||||
var dissectedTypes = field.Type
|
||||
.Replace("[", ",")
|
||||
.Replace("]", "")
|
||||
.Replace(" ", "")
|
||||
.Split();
|
||||
.Split(',');
|
||||
|
||||
foreach (var dissectedType in dissectedTypes)
|
||||
{
|
||||
@ -164,16 +167,16 @@ public class RequestClassGenerator
|
||||
.Split(',')
|
||||
.Select(s => s.Trim())
|
||||
.ToArray();
|
||||
imports.Add(new Import(name, [..types]));
|
||||
imports.Add(new Import(name, new List<string>(types)));
|
||||
}
|
||||
|
||||
if (imports.FirstOrDefault(i => i.Name == "typing") is Import typingImport)
|
||||
{
|
||||
typingImport.Types.AddRange(["Any", "Dict", "List", "Optional"]);
|
||||
typingImport.Types.AddRange(PythonTypingImports);
|
||||
}
|
||||
else
|
||||
{
|
||||
imports.Add(new("typing", ["Any", "Dict", "List", "Optional"]));
|
||||
imports.Add(new Import("typing", PythonTypingImports));
|
||||
}
|
||||
|
||||
return imports;
|
||||
|
12
MycroForge.CLI/Commands/Attributes/RequiresVenvAttribute.cs
Normal file
12
MycroForge.CLI/Commands/Attributes/RequiresVenvAttribute.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace MycroForge.CLI.Commands.Attributes;
|
||||
|
||||
public class RequiresVenvAttribute : Attribute
|
||||
{
|
||||
public string Path => System.IO.Path.Join(Environment.CurrentDirectory, ".venv");
|
||||
|
||||
public void RequireVenv(string command)
|
||||
{
|
||||
if (!File.Exists(System.IO.Path.Join(Environment.CurrentDirectory, Path)))
|
||||
throw new($"Command '{command}' requires directory {Path}");
|
||||
}
|
||||
}
|
@ -43,16 +43,16 @@ public partial class MycroForge
|
||||
private async Task ExecuteAsync(string name)
|
||||
{
|
||||
var fqn = new FullyQualifiedName(name);
|
||||
var routersFolderPath = $"{Features.Api.FeatureName}/routers";
|
||||
var routersFolderPath = Path.Join(Features.Api.FeatureName, "routers");
|
||||
|
||||
if (fqn.HasNamespace)
|
||||
routersFolderPath = Path.Combine(routersFolderPath, fqn.Namespace);
|
||||
|
||||
routersFolderPath = Path.Join(routersFolderPath, fqn.Namespace);
|
||||
|
||||
var fileName = $"{fqn.SnakeCasedName}.py";
|
||||
var filePath = Path.Combine(routersFolderPath, fileName);
|
||||
var filePath = Path.Join(routersFolderPath, fileName);
|
||||
|
||||
await _context.CreateFile(filePath, Template);
|
||||
|
||||
|
||||
var moduleImportPath = routersFolderPath
|
||||
.Replace('\\', '.')
|
||||
.Replace('/', '.');
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.CommandLine;
|
||||
using System.Text.RegularExpressions;
|
||||
using Humanizer;
|
||||
using MycroForge.CLI.CodeGen;
|
||||
using MycroForge.Core.Contract;
|
||||
@ -14,11 +15,65 @@ public partial class MycroForge
|
||||
{
|
||||
public class Entity : Command, ISubCommandOf<Generate>
|
||||
{
|
||||
private record ColumnDefinition(string Name, string NativeType, string OrmType);
|
||||
#region Hidden region
|
||||
|
||||
private static string[] SqlAlchemyTypes =
|
||||
[
|
||||
"BigInteger",
|
||||
"Boolean",
|
||||
"Date",
|
||||
"DateTime",
|
||||
"Enum",
|
||||
"Double",
|
||||
"Float",
|
||||
"Integer",
|
||||
"Interval",
|
||||
"LargeBinary",
|
||||
"MatchType",
|
||||
"Numeric",
|
||||
"PickleType",
|
||||
"SchemaType",
|
||||
"SmallInteger",
|
||||
"String",
|
||||
"Text",
|
||||
"Time",
|
||||
"Unicode",
|
||||
"UnicodeText",
|
||||
"Uuid",
|
||||
"ARRAY",
|
||||
"BIGINT",
|
||||
"BINARY",
|
||||
"BLOB",
|
||||
"BOOLEAN",
|
||||
"CHAR",
|
||||
"CLOB",
|
||||
"DATE",
|
||||
"DATETIME",
|
||||
"DECIMAL",
|
||||
"DOUBLE",
|
||||
"DOUBLE_PRECISION",
|
||||
"FLOAT",
|
||||
"INT",
|
||||
"JSON",
|
||||
"INTEGER",
|
||||
"NCHAR",
|
||||
"NVARCHAR",
|
||||
"NUMERIC",
|
||||
"REAL",
|
||||
"SMALLINT",
|
||||
"TEXT",
|
||||
"TIME",
|
||||
"TIMESTAMP",
|
||||
"UUID",
|
||||
"VARBINARY",
|
||||
"VARCHAR"
|
||||
];
|
||||
|
||||
private static readonly Regex SqlAlchemyTypeRegex = new(@".*\(.*\)");
|
||||
|
||||
private static readonly string[] Template =
|
||||
[
|
||||
"from sqlalchemy import %type_imports%",
|
||||
"from sqlalchemy import %sqlalchemy_imports%",
|
||||
"from sqlalchemy.orm import Mapped, mapped_column",
|
||||
$"from {Features.Db.FeatureName}.entities.entity_base import EntityBase",
|
||||
"",
|
||||
@ -55,6 +110,10 @@ public partial class MycroForge
|
||||
"\tfirst_name:str:String(255)",
|
||||
])) { AllowMultipleArgumentsPerToken = true };
|
||||
|
||||
#endregion
|
||||
|
||||
private record ColumnDefinition(string Name, string NativeType, string SqlAlchemyType);
|
||||
|
||||
private readonly ProjectContext _context;
|
||||
|
||||
public Entity(ProjectContext context) : base("entity", "Generate and database entity")
|
||||
@ -69,25 +128,27 @@ public partial class MycroForge
|
||||
private async Task ExecuteAsync(string name, IEnumerable<string> columns)
|
||||
{
|
||||
var fqn = new FullyQualifiedName(name);
|
||||
var folderPath = $"{Features.Db.FeatureName}/entities";
|
||||
var folderPath = Path.Join(Features.Db.FeatureName, "entities");
|
||||
|
||||
_context.AssertDirectoryExists(Features.Db.FeatureName);
|
||||
|
||||
if (fqn.HasNamespace)
|
||||
folderPath = Path.Combine(folderPath, fqn.Namespace);
|
||||
folderPath = Path.Join(folderPath, fqn.Namespace);
|
||||
|
||||
var _columns = GetColumnDefinitions(columns.ToArray());
|
||||
var typeImports = string.Join(", ", _columns.Select(c => c.OrmType.Split('(').First()).Distinct());
|
||||
var columnDefinitions = string.Join("\n\t", _columns.Select(ColumnToString));
|
||||
var sqlAlchemyColumn = GetColumnDefinitions(columns.ToArray());
|
||||
var distinctSqlAlchemyColumnTypes = sqlAlchemyColumn
|
||||
.Select(c => c.SqlAlchemyType.Split('(').First())
|
||||
.Distinct();
|
||||
|
||||
var sqlAlchemyImport = string.Join(", ", distinctSqlAlchemyColumnTypes);
|
||||
var columnDefinitions = string.Join("\n ", sqlAlchemyColumn.Select(ColumnToString));
|
||||
|
||||
var code = string.Join('\n', Template);
|
||||
code = code.Replace("%type_imports%", typeImports);
|
||||
code = code.Replace("%sqlalchemy_imports%", sqlAlchemyImport);
|
||||
code = code.Replace("%class_name%", fqn.PascalizedName);
|
||||
code = code.Replace("%table_name%", fqn.SnakeCasedName.Pluralize());
|
||||
code = code.Replace("%column_definitions%", columnDefinitions);
|
||||
|
||||
|
||||
var fileName = $"{fqn.SnakeCasedName}.py";
|
||||
var filePath = Path.Combine(folderPath, fileName);
|
||||
var filePath = Path.Join(folderPath, fileName);
|
||||
await _context.CreateFile(filePath, code);
|
||||
|
||||
var importPathParts = new[] { folderPath, fileName.Replace(".py", "") }
|
||||
@ -108,25 +169,58 @@ public partial class MycroForge
|
||||
await _context.WriteFile("main.py", main);
|
||||
}
|
||||
|
||||
private List<ColumnDefinition> GetColumnDefinitions(string[] fields)
|
||||
private List<ColumnDefinition> GetColumnDefinitions(string[] columns)
|
||||
{
|
||||
var definitions = new List<ColumnDefinition>();
|
||||
|
||||
foreach (var field in fields)
|
||||
foreach (var column in columns)
|
||||
{
|
||||
if (field.Split(':') is not { Length: 3 } definition)
|
||||
throw new Exception($"Field definition {field} is invalid.");
|
||||
if (column.Split(':') is not { Length: 3 } definition)
|
||||
throw new Exception($"Column definition {column} is invalid.");
|
||||
|
||||
definitions.Add(new ColumnDefinition(definition[0], definition[1], definition[2]));
|
||||
}
|
||||
|
||||
ValidateSqlAlchemyColumnTypes(definitions);
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
private static string ColumnToString(ColumnDefinition definition)
|
||||
private static void ValidateSqlAlchemyColumnTypes(List<ColumnDefinition> definitions)
|
||||
{
|
||||
return $"{definition.Name}: Mapped[{definition.NativeType}] = mapped_column({definition.OrmType})";
|
||||
foreach (var column in definitions)
|
||||
{
|
||||
if (!SqlAlchemyTypeRegex.IsMatch(column.SqlAlchemyType))
|
||||
{
|
||||
var message = new[]
|
||||
{
|
||||
$"SQLAlchemy column definition {column.SqlAlchemyType} was not properly defined.",
|
||||
"Add parentheses and specify parameters if required, an example is provided below.",
|
||||
" String(255)",
|
||||
"",
|
||||
"Available options are:",
|
||||
string.Join(Environment.NewLine, SqlAlchemyTypes.Select(type => $" - {type}"))
|
||||
};
|
||||
|
||||
throw new(string.Join(Environment.NewLine, message));
|
||||
}
|
||||
|
||||
var type = column.SqlAlchemyType.Split('(').First();
|
||||
|
||||
if (!SqlAlchemyTypes.Contains(type))
|
||||
{
|
||||
var message = string.Join(Environment.NewLine, [
|
||||
$"SQLAlchemy column type '{column.SqlAlchemyType}' is not valid, available options are:",
|
||||
string.Join(Environment.NewLine, SqlAlchemyTypes.Select(type => $" - {type}"))
|
||||
]);
|
||||
|
||||
throw new(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ColumnToString(ColumnDefinition definition) =>
|
||||
$"{definition.Name}: Mapped[{definition.NativeType}] = mapped_column({definition.SqlAlchemyType})";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,9 +59,9 @@ public partial class MycroForge
|
||||
var folderPath = string.Empty;
|
||||
|
||||
if (fqn.HasNamespace)
|
||||
folderPath = Path.Combine(folderPath, fqn.Namespace);
|
||||
folderPath = Path.Join(folderPath, fqn.Namespace);
|
||||
|
||||
var filePath = Path.Combine(folderPath, $"{fqn.SnakeCasedName}.py");
|
||||
var filePath = Path.Join(folderPath, $"{fqn.SnakeCasedName}.py");
|
||||
var template = withSession ? WithSessionTemplate : DefaultTemplate;
|
||||
var code = string.Join('\n', template)
|
||||
.Replace("%class_name%", fqn.PascalizedName);
|
||||
|
@ -81,7 +81,7 @@ public partial class MycroForge
|
||||
await _context.CreateFile("main.py");
|
||||
|
||||
// Create the venv
|
||||
await _context.Bash($"python3 -m venv {Path.Combine(projectRoot, ".venv")}");
|
||||
await _context.Bash($"python3 -m venv {Path.Join(projectRoot, ".venv")}");
|
||||
|
||||
// Pass feature arguments to the ArgsContainer
|
||||
_optionsContainer.Set(options.ApiOptions);
|
||||
@ -106,7 +106,7 @@ public partial class MycroForge
|
||||
|
||||
private async Task<string> CreateDirectory(string name)
|
||||
{
|
||||
var directory = Path.Combine(Directory.GetCurrentDirectory(), name);
|
||||
var directory = Path.Join(Directory.GetCurrentDirectory(), name);
|
||||
|
||||
if (Directory.Exists(directory))
|
||||
throw new Exception($"Directory {directory} already exists.");
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.CLI.Commands.Attributes;
|
||||
using MycroForge.Core;
|
||||
using MycroForge.Core.Contract;
|
||||
|
||||
@ -6,6 +7,7 @@ namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
[RequiresVenv]
|
||||
public class Install : Command, ISubCommandOf<MycroForge>
|
||||
{
|
||||
private static readonly Argument<IEnumerable<string>> PackagesArgument =
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.CLI.Commands.Attributes;
|
||||
using MycroForge.Core;
|
||||
using MycroForge.Core.Contract;
|
||||
|
||||
@ -6,6 +7,7 @@ namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
[RequiresVenv]
|
||||
public class Uninstall : Command, ISubCommandOf<MycroForge>
|
||||
{
|
||||
private static readonly Argument<IEnumerable<string>> PackagesArgument =
|
||||
|
@ -48,6 +48,9 @@ public static class CommandExtensions
|
||||
|
||||
else if (_command.GetRequiresPluginAttribute() is RequiresPluginAttribute requiresPluginAttribute)
|
||||
requiresPluginAttribute.RequirePluginProject(commandText);
|
||||
|
||||
else if (_command.GetRequiresVenvAttribute() is RequiresVenvAttribute requiresVenvAttribute)
|
||||
requiresVenvAttribute.RequireVenv(commandText);
|
||||
}
|
||||
|
||||
await next(context);
|
||||
@ -56,15 +59,6 @@ public static class CommandExtensions
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static RequiresFeatureAttribute? GetRequiresFeatureAttribute(this Command command) =>
|
||||
command.GetType().GetCustomAttribute<RequiresFeatureAttribute>();
|
||||
|
||||
private static RequiresFileAttribute? GetRequiresFileAttribute(this Command command) =>
|
||||
command.GetType().GetCustomAttribute<RequiresFileAttribute>();
|
||||
|
||||
private static RequiresPluginAttribute? GetRequiresPluginAttribute(this Command command) =>
|
||||
command.GetType().GetCustomAttribute<RequiresPluginAttribute>();
|
||||
|
||||
private static List<Command> GetCommandChain(this InvocationContext context)
|
||||
{
|
||||
var chain = new List<Command>();
|
||||
@ -87,4 +81,16 @@ public static class CommandExtensions
|
||||
|
||||
return chain;
|
||||
}
|
||||
|
||||
private static RequiresFeatureAttribute? GetRequiresFeatureAttribute(this Command command) =>
|
||||
command.GetType().GetCustomAttribute<RequiresFeatureAttribute>();
|
||||
|
||||
private static RequiresFileAttribute? GetRequiresFileAttribute(this Command command) =>
|
||||
command.GetType().GetCustomAttribute<RequiresFileAttribute>();
|
||||
|
||||
private static RequiresPluginAttribute? GetRequiresPluginAttribute(this Command command) =>
|
||||
command.GetType().GetCustomAttribute<RequiresPluginAttribute>();
|
||||
|
||||
private static RequiresVenvAttribute? GetRequiresVenvAttribute(this Command command) =>
|
||||
command.GetType().GetCustomAttribute<RequiresVenvAttribute>();
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.Core;
|
||||
using MycroForge.Core.Contract;
|
||||
using RootCommand = MycroForge.Core.RootCommand;
|
||||
|
||||
namespace My.Plugin;
|
||||
|
||||
public class HelloWorldCommand : Command, ISubCommandOf<RootCommand>
|
||||
{
|
||||
private readonly Argument<string> NameArgument =
|
||||
new(name: "name", description: "The name of the person to greet");
|
||||
|
||||
private readonly Option<bool> AllCapsOption =
|
||||
new(aliases: ["-a", "--all-caps"], description: "Print the name in all caps");
|
||||
|
||||
private readonly ProjectContext _context;
|
||||
|
||||
public HelloWorldCommand(ProjectContext context) :
|
||||
base("hello", "An example command generated by dotnet new using the m4gp template")
|
||||
{
|
||||
_context = context;
|
||||
AddArgument(NameArgument);
|
||||
AddOption(AllCapsOption);
|
||||
this.SetHandler(ExecuteAsync, NameArgument, AllCapsOption);
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync(string name, bool allCaps)
|
||||
{
|
||||
name = allCaps ? name.ToUpper() : name;
|
||||
|
||||
await _context.CreateFile("hello_world.txt",
|
||||
$"Hello {name}!",
|
||||
"This file was generated by your custom command!"
|
||||
);
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using MycroForge.Core.Contract;
|
||||
using RootCommand = MycroForge.Core.RootCommand;
|
||||
|
||||
namespace My.Plugin;
|
||||
|
||||
public class HelloWorldCommandPlugin : ICommandPlugin
|
||||
{
|
||||
public string Name => "My.Plugin";
|
||||
|
||||
public void RegisterServices(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<ISubCommandOf<RootCommand>, HelloWorldCommand>();
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include=".template.config\template.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MycroForge.Core" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -1,6 +0,0 @@
|
||||
namespace MycroForge.Core.Attributes;
|
||||
|
||||
public class DockerPlatformAttribute : Attribute
|
||||
{
|
||||
public string Platform { get; set; } = string.Empty;
|
||||
}
|
@ -9,7 +9,7 @@ public class ProjectContext
|
||||
{
|
||||
public string RootDirectory { get; private set; } = Environment.CurrentDirectory;
|
||||
public string AppName => Path.GetFileNameWithoutExtension(RootDirectory).Underscore().ToLower();
|
||||
private string ConfigPath => Path.Combine(RootDirectory, "m4g.json");
|
||||
private string ConfigPath => Path.Join(RootDirectory, "m4g.json");
|
||||
|
||||
|
||||
public async Task<ProjectConfig> LoadConfig(bool create = false)
|
||||
@ -37,21 +37,9 @@ public class ProjectContext
|
||||
RootDirectory = path;
|
||||
}
|
||||
|
||||
public void AssertDirectoryExists(string path)
|
||||
{
|
||||
var fullPath = Path.Combine(RootDirectory, path);
|
||||
|
||||
if (!Directory.Exists(fullPath))
|
||||
{
|
||||
throw new(string.Join('\n',
|
||||
$"{fullPath} does not exist, make sure you're in the correct directory."
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreateFile(string path, params string[] content)
|
||||
{
|
||||
var fullPath = Path.Combine(RootDirectory, path);
|
||||
var fullPath = Path.Join(RootDirectory, path);
|
||||
var fileInfo = new FileInfo(fullPath);
|
||||
|
||||
if (fileInfo.Exists) return;
|
||||
@ -64,7 +52,7 @@ public class ProjectContext
|
||||
|
||||
public async Task<string> ReadFile(string path)
|
||||
{
|
||||
var fullPath = Path.Combine(RootDirectory, path);
|
||||
var fullPath = Path.Join(RootDirectory, path);
|
||||
var fileInfo = new FileInfo(fullPath);
|
||||
|
||||
if (!fileInfo.Exists)
|
||||
@ -75,7 +63,7 @@ public class ProjectContext
|
||||
|
||||
public async Task WriteFile(string path, params string[] content)
|
||||
{
|
||||
var fullPath = Path.Combine(RootDirectory, path);
|
||||
var fullPath = Path.Join(RootDirectory, path);
|
||||
var fileInfo = new FileInfo(fullPath);
|
||||
Directory.CreateDirectory(fileInfo.Directory!.FullName);
|
||||
await File.WriteAllTextAsync(fullPath, string.Join("\n", content));
|
||||
@ -123,7 +111,6 @@ public class ProjectContext
|
||||
input.Close();
|
||||
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
Environment.ExitCode = process.ExitCode;
|
||||
}
|
||||
|
||||
|
@ -30,8 +30,8 @@ dotnet nuget add source --name devdisciples --username username --password passw
|
||||
|
||||
### TODO
|
||||
|
||||
- Fix `-c` option for `m4g db generate entity`
|
||||
- Add a CLI UI library
|
||||
- Clean up README files
|
||||
- Theme the site with a custom color scheme and icon/logos
|
||||
-
|
||||
- Mention assumed knowledge of both FastAPI & SQLAlchemy
|
||||
- Elaborate on terminology and best practices, like meaningful service names (also emphasize common sense?)
|
||||
|
Loading…
Reference in New Issue
Block a user