using System.CommandLine; using Microsoft.Extensions.DependencyInjection; using MycroForge.CLI.Commands.Interfaces; using MycroForge.CLI.Features; namespace MycroForge.CLI.Commands; public partial class MycroForge { public class Init : Command, ISubCommandOf { #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 private static readonly Argument NameArgument = new(name: "name", description: "The name of your project"); private static readonly Option EntryPoint = new(name: "--entrypoint", description: "The name of the entrypoint file"); private static readonly Option BranchOption = new(name: "--branch", description: "The name of the initial git branch"); private static readonly Option> FeaturesOption = new(name: "--features", description: "The features to include") { AllowMultipleArgumentsPerToken = true }; private readonly IServiceProvider _services; private readonly List _features; public Init(IServiceProvider services) : base("init", "Initialize a new project") { AddArgument(NameArgument); AddOption(EntryPoint); AddOption(BranchOption); AddOption(FeaturesOption); this.SetHandler(ExecuteAsync, NameArgument, EntryPoint, BranchOption, FeaturesOption); _services = services; _features = _services.GetServices().ToList(); } private async Task ExecuteAsync(string name, string entrypoint, string branch, IEnumerable features) { var featuresList = features.ToList(); Validate(featuresList); await Initialize(name, entrypoint, branch, featuresList); } private void Validate(List features) { foreach (var feature in features) if (_features.All(f => f.Name != feature)) throw new Exception($"Feature {feature} was not found."); } private async Task Initialize(string name, string entrypoint, string branch, List features) { // Create the project directory and change the directory for the ProjectContext var projectRoot = await CreateDirectory(name); var ctx = _services.GetRequiredService(); ctx.ChangeDirectory(projectRoot); // Create the config file and initialize the config await ctx.CreateFile("m4g.json", "{}"); await ctx.LoadConfig(force: true); // Create the entrypoint file entrypoint = string.IsNullOrEmpty(entrypoint) ? "main.py" : entrypoint; await ctx.CreateFile(entrypoint, string.Empty); ctx.Config.Entrypoint = entrypoint; // Create the default .gitignore await ctx.CreateFile(".gitignore", GitIgnore); // Create the venv await Bash.ExecuteAsync($"python3 -m venv {Path.Combine(projectRoot, ".venv")}"); // Initialize git var _branch = string.IsNullOrEmpty(branch) ? "main" : branch; await Bash.ExecuteAsync($"git -c init.defaultBranch={_branch} init {projectRoot}"); // Initialize features if (features.Count > 0) await InitializeFeatures(ctx, features); Console.WriteLine($"Directory {projectRoot} was successfully initialized"); } private async Task CreateDirectory(string name) { var directory = Path.Combine(Directory.GetCurrentDirectory(), name); if (Directory.Exists(directory)) throw new Exception($"Directory {directory} already exists."); Console.WriteLine($"Creating directory {directory}"); Directory.CreateDirectory(directory); await Bash.ExecuteAsync($"chmod -R 777 {directory}"); return directory; } private async Task InitializeFeatures(ProjectContext projectCtx, List features) { foreach (var feature in features) await _features.First(p => p.Name == feature).ExecuteAsync(projectCtx); } } }