- Refactored init & features
- Extended documentation
This commit is contained in:
39
MycroForge.CLI/Commands/MycroForge.Add.Api.cs
Normal file
39
MycroForge.CLI/Commands/MycroForge.Add.Api.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.CLI.Features;
|
||||
using MycroForge.Core;
|
||||
using MycroForge.Core.Contract;
|
||||
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
public partial class Add
|
||||
{
|
||||
public class Api : Command, ISubCommandOf<Add>
|
||||
{
|
||||
private static readonly Option<int> ApiPortOption = new(name: "--api-port", description: "The API port");
|
||||
|
||||
private readonly ProjectContext _context;
|
||||
private readonly OptionsContainer _optionsContainer;
|
||||
private readonly List<IFeature> _features;
|
||||
|
||||
public Api(ProjectContext context, OptionsContainer optionsContainer, IEnumerable<IFeature> features) :
|
||||
base(Features.Api.FeatureName, "Add FastAPI to the project")
|
||||
{
|
||||
_context = context;
|
||||
_optionsContainer = optionsContainer;
|
||||
_features = features.ToList();
|
||||
|
||||
AddOption(ApiPortOption);
|
||||
this.SetHandler(ExecuteAsync, ApiPortOption);
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync(int apiPort)
|
||||
{
|
||||
_optionsContainer.Set(new Features.Api.Options { ApiPort = apiPort });
|
||||
var feature = _features.First(f => f.Name == Features.Api.FeatureName);
|
||||
await feature.ExecuteAsync(_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
48
MycroForge.CLI/Commands/MycroForge.Add.Db.cs
Normal file
48
MycroForge.CLI/Commands/MycroForge.Add.Db.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.CLI.Features;
|
||||
using MycroForge.Core;
|
||||
using MycroForge.Core.Contract;
|
||||
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
public partial class Add
|
||||
{
|
||||
public class Db : Command, ISubCommandOf<Add>
|
||||
{
|
||||
private static readonly Option<int> DbhPortOption = new(
|
||||
aliases: ["--database-host-port", "--dbh-port"],
|
||||
description: "The database host port"
|
||||
);
|
||||
|
||||
private static readonly Option<int> DbuPortOption = new(
|
||||
aliases: ["--database-ui-port", "--dbu-port"],
|
||||
description: "The database UI port"
|
||||
);
|
||||
|
||||
private readonly ProjectContext _context;
|
||||
private readonly OptionsContainer _optionsContainer;
|
||||
private readonly List<IFeature> _features;
|
||||
|
||||
public Db(ProjectContext context, OptionsContainer optionsContainer, IEnumerable<IFeature> features) :
|
||||
base(Features.Db.FeatureName, "Add SQLAlchemy & Alembic to the project")
|
||||
{
|
||||
_context = context;
|
||||
_optionsContainer = optionsContainer;
|
||||
_features = features.ToList();
|
||||
|
||||
AddOption(DbhPortOption);
|
||||
AddOption(DbuPortOption);
|
||||
this.SetHandler(ExecuteAsync, DbhPortOption, DbuPortOption);
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync(int dbhPort, int dbuPort)
|
||||
{
|
||||
_optionsContainer.Set(new Features.Db.Options { DbhPort = dbhPort, DbuPort = dbuPort });
|
||||
var feature = _features.First(f => f.Name == Features.Db.FeatureName);
|
||||
await feature.ExecuteAsync(_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
MycroForge.CLI/Commands/MycroForge.Add.Git.cs
Normal file
32
MycroForge.CLI/Commands/MycroForge.Add.Git.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.CLI.Features;
|
||||
using MycroForge.Core;
|
||||
using MycroForge.Core.Contract;
|
||||
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
public partial class Add
|
||||
{
|
||||
public class Git : Command, ISubCommandOf<Add>
|
||||
{
|
||||
private readonly ProjectContext _context;
|
||||
private readonly List<IFeature> _features;
|
||||
|
||||
public Git(ProjectContext context, IEnumerable<IFeature> features) :
|
||||
base(Features.Git.FeatureName, "Add git to the project")
|
||||
{
|
||||
_context = context;
|
||||
_features = features.ToList();
|
||||
this.SetHandler(ExecuteAsync);
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync()
|
||||
{
|
||||
var feature = _features.First(f => f.Name == Features.Git.FeatureName);
|
||||
await feature.ExecuteAsync(_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
MycroForge.CLI/Commands/MycroForge.Add.GitIgnore.cs
Normal file
32
MycroForge.CLI/Commands/MycroForge.Add.GitIgnore.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.CLI.Features;
|
||||
using MycroForge.Core;
|
||||
using MycroForge.Core.Contract;
|
||||
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
public partial class Add
|
||||
{
|
||||
public class GitIgnore : Command, ISubCommandOf<Add>
|
||||
{
|
||||
private readonly ProjectContext _context;
|
||||
private readonly List<IFeature> _features;
|
||||
|
||||
public GitIgnore(ProjectContext context, IEnumerable<IFeature> features) :
|
||||
base(Features.GitIgnore.FeatureName, "Add a default .gitignore file to the project")
|
||||
{
|
||||
_context = context;
|
||||
_features = features.ToList();
|
||||
this.SetHandler(ExecuteAsync);
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync()
|
||||
{
|
||||
var feature = _features.First(f => f.Name == Features.GitIgnore.FeatureName);
|
||||
await feature.ExecuteAsync(_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
MycroForge.CLI/Commands/MycroForge.Add.cs
Normal file
17
MycroForge.CLI/Commands/MycroForge.Add.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.Core.Contract;
|
||||
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
public partial class Add : Command, ISubCommandOf<MycroForge>
|
||||
{
|
||||
public Add(IEnumerable<ISubCommandOf<Add>> commands) :
|
||||
base("add", "Add features to the project")
|
||||
{
|
||||
foreach (var command in commands.Cast<Command>())
|
||||
AddCommand(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,12 @@ public partial class MycroForge
|
||||
{
|
||||
public partial class Db
|
||||
{
|
||||
public partial class Run : Command, ISubCommandOf<Db>
|
||||
public class Run : Command, ISubCommandOf<Db>
|
||||
{
|
||||
private readonly ProjectContext _context;
|
||||
|
||||
public Run(ProjectContext context) :
|
||||
base("run", $"Runs {Features.Db.FeatureName}.docker-compose.yml")
|
||||
base("run", $"Runs the services defined in {Features.Db.FeatureName}.docker-compose.yml")
|
||||
{
|
||||
_context = context;
|
||||
this.SetHandler(ExecuteAsync);
|
||||
@@ -22,7 +22,7 @@ public partial class MycroForge
|
||||
private async Task ExecuteAsync()
|
||||
{
|
||||
var config = await _context.LoadConfig();
|
||||
var env = $"DB_PORT={config.Db.DbPort} PMA_PORT={config.Db.PmaPort}";
|
||||
var env = $"DBH_PORT={config.Db.DbhPort} DBU_PORT={config.Db.DbuPort}";
|
||||
await _context.Bash($"{env} docker compose -f {Features.Db.FeatureName}.docker-compose.yml up -d");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ public partial class MycroForge
|
||||
private async Task ExecuteAsync()
|
||||
{
|
||||
var config = await _context.LoadConfig();
|
||||
var env = $"DB_PORT={config.Db.DbPort} PMA_PORT={config.Db.PmaPort}";
|
||||
var env = $"DB_PORT={config.Db.DbhPort} PMA_PORT={config.Db.DbuPort}";
|
||||
await _context.Bash($"{env} docker compose -f {Features.Db.FeatureName}.docker-compose.yml down");
|
||||
}
|
||||
}
|
||||
|
||||
21
MycroForge.CLI/Commands/MycroForge.Init.Binder.cs
Normal file
21
MycroForge.CLI/Commands/MycroForge.Init.Binder.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.CommandLine.Binding;
|
||||
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
public partial class Init
|
||||
{
|
||||
public class Binder : BinderBase<Options>
|
||||
{
|
||||
protected override Options GetBoundValue(BindingContext ctx) => new()
|
||||
{
|
||||
Name = ctx.ParseResult.GetValueForArgument(NameArgument),
|
||||
Without = ctx.ParseResult.GetValueForOption(WithoutOption),
|
||||
ApiPort = ctx.ParseResult.GetValueForOption(ApiPortOption),
|
||||
DbhPort = ctx.ParseResult.GetValueForOption(DbhPortOption),
|
||||
DbuPort = ctx.ParseResult.GetValueForOption(DbuPortOption),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
27
MycroForge.CLI/Commands/MycroForge.Init.Options.cs
Normal file
27
MycroForge.CLI/Commands/MycroForge.Init.Options.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
public partial class Init
|
||||
{
|
||||
public class Options
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public IEnumerable<string>? Without { get; set; }
|
||||
public int? ApiPort { get; set; }
|
||||
public int? DbhPort { get; set; }
|
||||
public int? DbuPort { get; set; }
|
||||
|
||||
public Features.Api.Options ApiOptions => new()
|
||||
{
|
||||
ApiPort = ApiPort <= 0 ? 8000 : ApiPort
|
||||
};
|
||||
|
||||
public Features.Db.Options DbOptions => new()
|
||||
{
|
||||
DbhPort = DbhPort <= 0 ? 5050 : DbhPort,
|
||||
DbuPort = DbuPort <= 0 ? 5051 : DbhPort
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,58 +7,12 @@ namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
#region GitIgnore
|
||||
|
||||
private static readonly string[] GitIgnore =
|
||||
[
|
||||
"# Byte-compiled / optimized / DLL files", "__pycache__/", "*.py[cod]", "*$py.class", "# C extensions",
|
||||
"*.so", "# Distribution / packaging", ".Python", "build/", "develop-eggs/", "dist/", "downloads/", "eggs/",
|
||||
".eggs/", "lib/", "lib64/", "parts/", "sdist/", "var/", "wheels/", "share/python-wheels/", "*.egg-info/",
|
||||
".installed.cfg", "*.egg", "MANIFEST", "# PyInstaller",
|
||||
"# Usually these files are written by a python script from a template",
|
||||
"# before PyInstaller builds the exe, so as to inject date/other infos into it.", "*.manifest", "*.spec",
|
||||
"# Installer logs", "pip-log.txt", "pip-delete-this-directory.txt", "# Unit test / coverage reports",
|
||||
"htmlcov/", ".tox/", ".nox/", ".coverage", ".coverage.*", ".cache", "nosetests.xml", "coverage.xml",
|
||||
"*.cover", "*.py,cover", ".hypothesis/", ".pytest_cache/", "cover/", "# Translations", "*.mo", "*.pot",
|
||||
"# Django stuff:", "*.log", "local_settings.py", "db.sqlite3", "db.sqlite3-journal", "# Flask stuff:",
|
||||
"instance/", ".webassets-cache", "# Scrapy stuff:", ".scrapy", "# Sphinx documentation", "docs/_build/",
|
||||
"# PyBuilder", ".pybuilder/", "target/", "# Jupyter Notebook", ".ipynb_checkpoints", "# IPython",
|
||||
"profile_default/", "ipython_config.py", "# pyenv",
|
||||
"# For a library or package, you might want to ignore these files since the code is",
|
||||
"# intended to run in multiple environments; otherwise, check them in:", "# .python-version", "# pipenv",
|
||||
"# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.",
|
||||
"# However, in case of collaboration, if having platform-specific dependencies or dependencies",
|
||||
"# having no cross-platform support, pipenv may install dependencies that don't work, or not",
|
||||
"# install all needed dependencies.", "#Pipfile.lock", "# poetry",
|
||||
"# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.",
|
||||
"# This is especially recommended for binary packages to ensure reproducibility, and is more",
|
||||
"# commonly ignored for libraries.",
|
||||
"# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control",
|
||||
"#poetry.lock", "# pdm",
|
||||
"# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.",
|
||||
"#pdm.lock",
|
||||
"# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it",
|
||||
"# in version control.", "# https://pdm.fming.dev/#use-with-ide", ".pdm.toml",
|
||||
"# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm", "__pypackages__/",
|
||||
"# Celery stuff", "celerybeat-schedule", "celerybeat.pid", "# SageMath parsed files", "*.sage.py",
|
||||
"# Environments", ".env", ".venv", "env/", "venv/", "ENV/", "env.bak/", "venv.bak/",
|
||||
"# Spyder project settings", ".spyderproject", ".spyproject", "# Rope project settings", ".ropeproject",
|
||||
"# mkdocs documentation", "/site", "# mypy", ".mypy_cache/", ".dmypy.json", "dmypy.json",
|
||||
"# Pyre type checker", ".pyre/", "# pytype static type analyzer", ".pytype/", "# Cython debug symbols",
|
||||
"cython_debug/", "# PyCharm",
|
||||
"# JetBrains specific template is maintained in a separate JetBrains.gitignore that can",
|
||||
"# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore",
|
||||
"# and can be added to the global gitignore or merged into this file. For a more nuclear",
|
||||
"# option (not recommended) you can uncomment the following to ignore the entire idea folder.", "#.idea/"
|
||||
];
|
||||
|
||||
#endregion
|
||||
|
||||
public class Init : Command, ISubCommandOf<MycroForge>
|
||||
public partial class Init : Command, ISubCommandOf<MycroForge>
|
||||
{
|
||||
private static readonly string[] DefaultFeatures =
|
||||
[
|
||||
Features.Git.FeatureName,
|
||||
Features.GitIgnore.FeatureName,
|
||||
Features.Api.FeatureName,
|
||||
Features.Db.FeatureName
|
||||
];
|
||||
@@ -72,44 +26,61 @@ public partial class MycroForge
|
||||
AllowMultipleArgumentsPerToken = true
|
||||
}.FromAmong(DefaultFeatures);
|
||||
|
||||
private static readonly Option<int> ApiPortOption =
|
||||
new(name: "--api-port", description: "The API port");
|
||||
|
||||
private static readonly Option<int> DbhPortOption = new(
|
||||
aliases: ["--database-host-port", "--dbh-port"],
|
||||
description: "The database host port"
|
||||
);
|
||||
|
||||
private static readonly Option<int> DbuPortOption = new(
|
||||
aliases: ["--database-ui-port", "--dbu-port"],
|
||||
description: "The database UI port"
|
||||
);
|
||||
|
||||
private readonly ProjectContext _context;
|
||||
private readonly List<IFeature> _features;
|
||||
private readonly OptionsContainer _optionsContainer;
|
||||
|
||||
public Init(ProjectContext context, IEnumerable<IFeature> features) :
|
||||
public Init(ProjectContext context, OptionsContainer optionsContainer, IEnumerable<IFeature> features) :
|
||||
base("init", "Initialize a new project")
|
||||
{
|
||||
_context = context;
|
||||
_optionsContainer = optionsContainer;
|
||||
_features = features.ToList();
|
||||
|
||||
AddArgument(NameArgument);
|
||||
AddOption(WithoutOption);
|
||||
this.SetHandler(ExecuteAsync, NameArgument, WithoutOption);
|
||||
AddOption(ApiPortOption);
|
||||
AddOption(DbhPortOption);
|
||||
AddOption(DbuPortOption);
|
||||
|
||||
this.SetHandler(ExecuteAsync, new Binder());
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync(string name, IEnumerable<string> without)
|
||||
private async Task ExecuteAsync(Options options)
|
||||
{
|
||||
// Validate excluded features
|
||||
var withoutList = without.ToList();
|
||||
var withoutList = (options.Without ?? Enumerable.Empty<string>()).ToList();
|
||||
foreach (var feature in withoutList)
|
||||
if (_features.All(f => f.Name != feature))
|
||||
throw new Exception($"Feature {feature} does not exist.");
|
||||
|
||||
// Create the project directory and change the directory for the ProjectContext
|
||||
var projectRoot = await CreateDirectory(name);
|
||||
var projectRoot = await CreateDirectory(options.Name);
|
||||
_context.ChangeRootDirectory(projectRoot);
|
||||
|
||||
// Create the config file and initialize the config
|
||||
await _context.CreateFile("m4g.json", "{}");
|
||||
|
||||
// Create the entrypoint file
|
||||
await _context.CreateFile("main.py");
|
||||
|
||||
// Create the default .gitignore folder
|
||||
await _context.CreateFile(".gitignore", GitIgnore);
|
||||
|
||||
// Create the venv
|
||||
await _context.Bash($"python3 -m venv {Path.Combine(projectRoot, ".venv")}");
|
||||
|
||||
// Pass feature arguments to the ArgsContainer
|
||||
_optionsContainer.Set(options.ApiOptions);
|
||||
_optionsContainer.Set(options.DbOptions);
|
||||
|
||||
// Initialize default features
|
||||
foreach (var feature in _features.Where(f => DefaultFeatures.Contains(f.Name)))
|
||||
{
|
||||
|
||||
@@ -24,9 +24,17 @@ public partial class MycroForge
|
||||
|
||||
private async Task ExecuteAsync(IEnumerable<string> packages)
|
||||
{
|
||||
var packs = packages.ToArray();
|
||||
|
||||
if (packs.Length == 0)
|
||||
{
|
||||
Console.WriteLine("m4g install requires at least one package.");
|
||||
return;
|
||||
}
|
||||
|
||||
await _context.Bash(
|
||||
"source .venv/bin/activate",
|
||||
$"pip install {string.Join(' ', packages)}",
|
||||
$"pip install {string.Join(' ', packs)}",
|
||||
"pip freeze > requirements.txt"
|
||||
);
|
||||
}
|
||||
|
||||
25
MycroForge.CLI/Commands/OptionsContainer.cs
Normal file
25
MycroForge.CLI/Commands/OptionsContainer.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public class OptionsContainer
|
||||
{
|
||||
private readonly Dictionary<Type, object> _args = new();
|
||||
|
||||
public void Set<T>(T? args)
|
||||
{
|
||||
if (args is null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
_args[args.GetType()] = args;
|
||||
}
|
||||
|
||||
public T Get<T>()
|
||||
{
|
||||
if (!_args.ContainsKey(typeof(T)))
|
||||
throw new KeyNotFoundException();
|
||||
|
||||
if (_args[typeof(T)] is not T args)
|
||||
throw new InvalidCastException();
|
||||
|
||||
return args;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user