diff --git a/fuzzy_couscous/cli.py b/fuzzy_couscous/cli.py new file mode 100644 index 0000000..2522de7 --- /dev/null +++ b/fuzzy_couscous/cli.py @@ -0,0 +1,57 @@ +from dataclasses import dataclass, field +import cappa +from typing import Any, Literal +from typing_extensions import Annotated +import rich + + +from .commands import Make, HtmxExt + + + + +@cappa.command(help="Download the latest version of htmx.") +class Htmx: + pass + + + +@cappa.command(help="Clean up all migrations.") +class RmMigrations: + pass + + +@cappa.command(help="Remove poetry from your project.") +class RmPoetry: + pass + + +@cappa.command(help="Run multiple processes in parallel.") +class Work: + pass + + +@cappa.command(help="Update or create a .env file from a .env.template file.") +class WriteEnv: + pass + + +@cappa.command( + help="Initialize a new django project using the fuzzy-couscous project template.", + description="""This is a wrapper around the django-admin startproject command using my custom project template at + https://github.com/Tobi-De/fuzzy-couscous. This cli also includes some additional commands to make setting up + a new project faster. + """, +) +class Toji: + subcommand: cappa.Subcommands[ + Make | Htmx | HtmxExt | RmMigrations | RmPoetry | Work | WriteEnv + ] + + +def main(): + cappa.invoke(Toji) + + +if __name__ == "__main__": + main() diff --git a/fuzzy_couscous/commands/__init__.py b/fuzzy_couscous/commands/__init__.py index f4e6816..84e5e37 100644 --- a/fuzzy_couscous/commands/__init__.py +++ b/fuzzy_couscous/commands/__init__.py @@ -1,5 +1,6 @@ from .htmx import htmx # noqa -from .make import make_project # noqa +from .htmx_ext import HtmxExt # noqa +from .make import Make # noqa from .remove_poetry import remove_poetry # noqa from .rm_migrations import rm_migrations # noqa from .work import work # noqa diff --git a/fuzzy_couscous/commands/htmx_ext.py b/fuzzy_couscous/commands/htmx_ext.py new file mode 100644 index 0000000..2bb0c35 --- /dev/null +++ b/fuzzy_couscous/commands/htmx_ext.py @@ -0,0 +1,52 @@ +from typing import Any +import cappa + +from pathlib import Path + +THIRD_PARY_REGISTRY = { + "htmx-template": { + "file": "https://raw.githubusercontent.com/KatrinaKitten/htmx-template/master/htmx-template.min.js", + "url": "https://github.com/KatrinaKitten/htmx-template", + }, + "hx-take": { + "file": "https://github.com/oriol-martinez/hx-take/blob/main/dist/hx-take.min.js", + "url": "https://github.com/oriol-martinez/hx-take", + }, +} + + + +@cappa.command(help="Download one of htmx extensions.") +class HtmxExt: + name: str | None = cappa.Arg( + None, + help="The name of the extension to download.", + choices=THIRD_PARY_REGISTRY.keys(), + ) + version: str = cappa.Arg( + "latest", + short="-v", + long="--version", + help="The version of htmx to use to look for the extension.", + ) + output : Path = cappa.Arg(default=Path.cwd(), help="The directory to write the downloaded file to.", short="-o", long="--output") + + + def __call__(self) -> None: + if self.name: + self.download(self.name) + else: + self.list_all() + + + def download(self): + pass + + def list_all(self): + pass + + @classmethod + def official_regitry(cls, htmx_version: str): + base_url = f"https://unpkg.com/htmx.org@{htmx_version}/dist/ext/" + + diff --git a/fuzzy_couscous/commands/make.py b/fuzzy_couscous/commands/make.py index 63ec815..8bdf16d 100644 --- a/fuzzy_couscous/commands/make.py +++ b/fuzzy_couscous/commands/make.py @@ -19,119 +19,124 @@ from ..utils import RICH_INFO_MARKER from ..utils import RICH_SUCCESS_MARKER from ..utils import write_toml +from typing import Annotated +import cappa +import rich +from rich.prompt import Prompt -__all__ = ["make_project"] +__all__ = ["Make"] +try: + from enum import StrEnum +except ImportError: + from enum import Enum + + class StrEnum(str, Enum): + pass + + +class Branch(StrEnum): + main = "main" + tailwind = "tailwind" + bootstrap = "bootstrap" -def _get_user_git_infos() -> tuple[str, str] | None: - git_config_cmd = ["git", "config", "--global", "--get"] - try: - user_name_cmd = subprocess.run( - git_config_cmd + ["user.name"], capture_output=True, text=True - ) - user_email_cmd = subprocess.run( - git_config_cmd + ["user.email"], capture_output=True, text=True - ) - except FileNotFoundError: - return None - if user_email_cmd.returncode != 0: - return None - return ( - user_name_cmd.stdout.strip("\n"), - user_email_cmd.stdout.strip("\n"), - ) - - -def _set_authors_in_pyproject(file: Path, name: str, email: str) -> None: - config = read_toml(file) - deep_set(config, "tool.poetry.authors", [f"{name} <{email}>"]) - write_toml(file, config) - - -def make_project( - project_name: str = typer.Argument(..., callback=clean_project_name), - repo: str = typer.Option( - "Tobi-De/fuzzy-couscous", - "-r", - "--repo", - help="The github repository to pull the template from. The format to use is `username/repo`", - formats=["username/repo"], - ), - branch: Branch = typer.Option( - "main", "-b", "--branch", help="The github branch to use." - ), - skip_deps_install: bool = typer.Option( - False, - "-s", - "--skip-install", - flag_value=True, - help="Skip dependencies installation", - ), -): - """Initialize a new django project.""" - - version = django.get_version() - if int(version.split(".")[0]) < 4: - rich_print(f"{RICH_ERROR_MARKER} Django version must be greater than 4.0") - raise typer.Abort() - - if Path(project_name).exists(): - rich_print( - f"{RICH_ERROR_MARKER} A directory with the name {project_name} already exists in the current directory " - f":disappointed_face:" - ) - raise typer.Abort() - - with Progress( - SpinnerColumn(), - TextColumn("[progress.description]{task.description}"), - transient=True, - ) as progress: - progress.add_task( - description="Initializing your new django project... :sunglasses:", - total=None, - ) - if template_dir := get_template_dir(repo, branch): - # run the django-admin command - subprocess.run( - [ - "django-admin", - "startproject", - project_name, - "--template", - template_dir, - "-e=py,html,toml,md,json,js,sh", - "--exclude=docs", - "--exclude=fuzzy_couscous", - "--exclude=.github", - "--exclude=.idea", - ] +@cappa.command(help="Initialize a new django project.") +class Make: + @staticmethod + def clean_project_name(project_name: str) -> str: + return project_name.strip("/") + + project_name: Annotated[ + str, + cappa.Arg(parse=clean_project_name), + ] + branch: Annotated[Branch, cappa.Arg(default=Branch.main, short="-b", long="--branch")] + + def __post_init__(self): + self.project_path = Path(self.project_name) + + def __call__(self) -> None: + if self.project_path.exists(): + rich_print( + f"{RICH_ERROR_MARKER} A directory with the name {self.project_name} already exists in the current directory " + f":disappointed_face:" ) + raise cappa.Exit() + + + self.init_project() - else: - raise typer.Abort( - f"{RICH_ERROR_MARKER} Couldn't download or find the template to use, check your connection." + self.update_authors() + + if self.branch != "main": + self.apply_branch_patch() + + if ( + Prompt.ask( + "Do you want to install the dependencies? (y/N)", choices=["y", "n"] ) + == "y" + ): + self.install_dependencies() - project_dir = Path(project_name) - msg = f"{RICH_SUCCESS_MARKER} Project initialized, keep up the good work!\n" - msg += ( - f"{RICH_INFO_MARKER} If you like the project consider dropping a star at " - f"https://github.com/Tobi-De/fuzzy-couscous" - ) - - if user_infos := _get_user_git_infos(): - name, email = user_infos - _set_authors_in_pyproject( - project_dir / "pyproject.toml", name=name, email=email - ) + + msg = f"{RICH_SUCCESS_MARKER} Project initialized, keep up the good work!\n" msg += ( - f"\n{RICH_INFO_MARKER} A git global user configuration was found and used to update the authors in your " - f"pyproject.toml file." + f"{RICH_INFO_MARKER} If you like the project consider dropping a star at " + f"https://github.com/Tobi-De/fuzzy-couscous" ) - if not skip_deps_install: + rich_print(msg) + + def init_project(self) -> None: + with Progress( + SpinnerColumn(), + TextColumn("[progress.description]{task.description}"), + transient=True, + ) as progress: + progress.add_task( + description="Initializing your new django project... :sunglasses:", + total=None, + ) + + if template_dir := get_template_dir(self.branch): + # run the django-admin command + subprocess.run( + [ + "django-admin", + "startproject", + self.project_name, + "--template", + template_dir, + "-e=py,html,toml,md,json,js,sh", + "--exclude=docs", + "--exclude=fuzzy_couscous", + "--exclude=.github", + "--exclude=.idea", + ] + ) + + else: + raise typer.Abort( + f"{RICH_ERROR_MARKER} Couldn't download or find the template to use, check your connection." + ) + + def update_authors(self) -> None: + name, email = self.get_git_user_infos() + if not name: + name = Prompt.ask("Enter your name") + if not email: + email = Prompt.ask("Enter your email") + pyproject_file = self.project_path / "pyproject.toml" + project_config = read_toml(self.project_path / "pyproject.toml") + deep_set(project_config, "tool.poetry.authors", [f"{name} <{email}>"]) + write_toml(pyproject_file, project_config) + + def apply_branch_patch(self) -> None: + pass + + def install_dependencies(self) -> None: with Progress( SpinnerColumn(), TextColumn("[progress.description]{task.description}"), @@ -142,10 +147,27 @@ def make_project( ) subprocess.call( ["poetry install --with dev"], - cwd=project_dir, + cwd=self.project_path, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True, ) - rich_print(msg) + @staticmethod + def get_git_user_infos(): + git_config_cmd = ["git", "config", "--global", "--get"] + try: + user_name_cmd = subprocess.run( + git_config_cmd + ["user.name"], capture_output=True, text=True + ) + user_email_cmd = subprocess.run( + git_config_cmd + ["user.email"], capture_output=True, text=True + ) + except FileNotFoundError: + return None + if user_email_cmd.returncode != 0: + return None + return ( + user_name_cmd.stdout.strip("\n"), + user_email_cmd.stdout.strip("\n"), + ) diff --git a/fuzzy_couscous/config.py b/fuzzy_couscous/config.py index c6f5925..35ef780 100644 --- a/fuzzy_couscous/config.py +++ b/fuzzy_couscous/config.py @@ -34,7 +34,7 @@ def _get_project_template_folder(root_folder: Path) -> str: return str((root_folder / "templates" / "project_name").resolve(strict=True)) -def get_template_dir(repo: str, branch: Branch) -> str | None: +def get_template_dir(branch: Branch) -> str | None: app_dir = Path(typer.get_app_dir(APP_NAME)) app_dir.mkdir(exist_ok=True, parents=True) # check if connection is available @@ -48,11 +48,11 @@ def get_template_dir(repo: str, branch: Branch) -> str | None: templates_dir = app_dir / username templates_dir.mkdir(exist_ok=True, parents=True) - template_dir = templates_dir / f"{repository}-{branch}" + template_dir = templates_dir / f"{repo}-{branch}" try: # download the archive - archive_url = f"https://github.com/{repo}/archive/{branch}.zip" + archive_url = f"https://github.com/tobi-de/fuzzy-couscous/archive/main.zip" archive_path = app_dir / f"{secrets.token_urlsafe(32)}.zip" download_archive(archive_url, archive_path) except httpx.ConnectError: diff --git a/fuzzy_couscous/utils.py b/fuzzy_couscous/utils.py index 9572f04..32bfb07 100644 --- a/fuzzy_couscous/utils.py +++ b/fuzzy_couscous/utils.py @@ -3,6 +3,7 @@ import httpx import tomli_w +import subprocess RICH_SUCCESS_MARKER = "[green]SUCCESS:" RICH_ERROR_MARKER = "[red]ERROR:" diff --git a/poetry.lock b/poetry.lock index e91f7c8..54b0bdf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -66,6 +66,25 @@ files = [ {file = "bracex-2.4.tar.gz", hash = "sha256:a27eaf1df42cf561fed58b7a8f3fdf129d1ea16a81e1fadd1d17989bc6384beb"}, ] +[[package]] +name = "cappa" +version = "0.13.1" +description = "Declarative CLI argument parser." +optional = false +python-versions = ">=3.8,<4" +files = [ + {file = "cappa-0.13.1-py3-none-any.whl", hash = "sha256:18790d341cab6241765126fbea43c60438e0e0a1fc98a4485ed4605ffd3206eb"}, + {file = "cappa-0.13.1.tar.gz", hash = "sha256:6594d870a492413ec0ea044f3841f8b48005d3cf9f97a20e5474eeaa8c52c621"}, +] + +[package.dependencies] +rich = "*" +typing-extensions = ">=4.7.1" +typing-inspect = ">=0.9.0" + +[package.extras] +docstring = ["docstring-parser (>=0.15)"] + [[package]] name = "certifi" version = "2023.7.22" @@ -650,6 +669,17 @@ files = [ {file = "mkdocs_material_extensions-1.3.tar.gz", hash = "sha256:f0446091503acb110a7cab9349cbc90eeac51b58d1caa92a704a81ca1e24ddbd"}, ] +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "nodeenv" version = "1.8.0" @@ -1172,6 +1202,21 @@ files = [ {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] +[[package]] +name = "typing-inspect" +version = "0.9.0" +description = "Runtime inspection utilities for typing module." +optional = false +python-versions = "*" +files = [ + {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, + {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, +] + +[package.dependencies] +mypy-extensions = ">=0.3.0" +typing-extensions = ">=3.7.4" + [[package]] name = "tzdata" version = "2023.3" @@ -1275,4 +1320,4 @@ bracex = ">=2.1.1" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "d65916ffa735ead88b37debd37a578c620f4bc19fdecdfe8995682761159cf1d" +content-hash = "b107d842b58a8777f962dcecfdad63e5b4864cf17555061b8012ff6faf05062f" diff --git a/pyproject.toml b/pyproject.toml index 234fcb4..6093282 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ rich = "^13.6.0" httpx = "^0.25.1" honcho = "^1.1.0" hupper = "^1.12" +cappa = "^0.13.1" [tool.poetry.group.dev.dependencies] pre-commit = "^3.5.0" @@ -57,6 +58,7 @@ mkdocs-include-markdown-plugin = "6.0.4" [tool.poetry.scripts] fuzzy-couscous = "fuzzy_couscous.main:cli" cuzzy = "fuzzy_couscous.main:cli" +toji = "fuzzy_couscous.cli:main" [tool.poe.tasks] t = "poetry export -f requirements.txt --output tests/requirements.txt --without-hashes --with test"