From cca5053c34508ef0bcb78e17dc2ac9bce4050368 Mon Sep 17 00:00:00 2001 From: pythoninja Date: Sat, 16 Mar 2024 17:24:03 +0300 Subject: [PATCH] feat(cli): migrate to cyclopts --- poetry.lock | 176 ++++++++++++++++++++++++++++++++++++++----------- pyproject.toml | 2 +- sshgen/cli.py | 43 +++++------- 3 files changed, 154 insertions(+), 67 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1613ec6..1f8db16 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,24 @@ # This file is automatically @generated by Poetry 1.8.1 and should not be changed by hand. +[[package]] +name = "attrs" +version = "23.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + [[package]] name = "cfgv" version = "3.4.0" @@ -12,29 +31,22 @@ files = [ ] [[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" +name = "cyclopts" +version = "2.4.2" +description = "" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8,<4.0" files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + {file = "cyclopts-2.4.2-py3-none-any.whl", hash = "sha256:589e6a4cec93cff83466ef357a9a79b6388b4b74c9e9e8d542b239579a8d8dbf"}, + {file = "cyclopts-2.4.2.tar.gz", hash = "sha256:8655182500e80d60452c617f56df5dc1d47e8d929ee48b7d62cb702f5642cab0"}, ] [package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] +attrs = ">=23.1.0" +docstring-parser = ">=0.15,<0.16" +rich = ">=13.6.0" +rich-rst = ">=1.2.0,<2.0.0" +typing-extensions = {version = ">=4.8.0", markers = "python_full_version >= \"3.8.0\" and python_full_version < \"4.0.0\""} [[package]] name = "distlib" @@ -47,6 +59,28 @@ files = [ {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] +[[package]] +name = "docstring-parser" +version = "0.15" +description = "Parse Python docstrings in reST, Google and Numpydoc format" +optional = false +python-versions = ">=3.6,<4.0" +files = [ + {file = "docstring_parser-0.15-py3-none-any.whl", hash = "sha256:d1679b86250d269d06a99670924d6bce45adc00b08069dae8c47d98e89b667a9"}, + {file = "docstring_parser-0.15.tar.gz", hash = "sha256:48ddc093e8b1865899956fcc03b03e66bb7240c310fac5af81814580c55bf682"}, +] + +[[package]] +name = "docutils" +version = "0.20.1" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, + {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, +] + [[package]] name = "filelock" version = "3.13.1" @@ -77,6 +111,41 @@ files = [ [package.extras] license = ["ukkonen"] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + [[package]] name = "nodeenv" version = "1.8.0" @@ -124,6 +193,21 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" +[[package]] +name = "pygments" +version = "2.17.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, +] + +[package.extras] +plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] + [[package]] name = "pyyaml" version = "6.0.1" @@ -184,6 +268,39 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] +[[package]] +name = "rich" +version = "13.7.1" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, + {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "rich-rst" +version = "1.2.0" +description = "A beautiful reStructuredText renderer for rich" +optional = false +python-versions = ">=3.6" +files = [ + {file = "rich-rst-1.2.0.tar.gz", hash = "sha256:12e3962fd2ed99f5361beab8abb35b87b1d2a8d3a14cb705bc70d2eb2fa81ddd"}, + {file = "rich_rst-1.2.0-py3-none-any.whl", hash = "sha256:49903d68fc7019d672145397719970c23ebcd7617c664bc26004607e83e4db6d"}, +] + +[package.dependencies] +docutils = "*" +rich = ">=12.0.0" + [[package]] name = "ruamel-yaml" version = "0.18.6" @@ -303,27 +420,6 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] -[[package]] -name = "typer" -version = "0.9.0" -description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -optional = false -python-versions = ">=3.6" -files = [ - {file = "typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee"}, - {file = "typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2"}, -] - -[package.dependencies] -click = ">=7.1.1,<9.0.0" -typing-extensions = ">=3.7.4.3" - -[package.extras] -all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] -dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] -doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] -test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] - [[package]] name = "typing-extensions" version = "4.10.0" @@ -358,4 +454,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "31b0da9517f8d167060a033a80b3e8b0ec119f680738c2b7e8344dbde26c5afe" +content-hash = "3dcdd675fce25c7f688776e281f6492cb0767f00316fc9e6bc80a0c3f468be18" diff --git a/pyproject.toml b/pyproject.toml index d11bb9f..43b409f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ sshgen = "sshgen.cli:app" [tool.poetry.dependencies] python = "^3.12" ruamel-yaml = ">=0.17.31,<0.19.0" -typer = "^0.9.0" +cyclopts = "^2.4.2" [tool.poetry.group.dev.dependencies] ruff = "*" diff --git a/sshgen/cli.py b/sshgen/cli.py index 897b0e6..0d9efbf 100644 --- a/sshgen/cli.py +++ b/sshgen/cli.py @@ -1,7 +1,8 @@ #!/usr/bin/env python -from typing import Annotated, Optional +from typing import Annotated -import typer +from cyclopts import App, Group +from cyclopts import Parameter from sshgen import __app_name__, __version__ from sshgen.logger import init_logger @@ -9,17 +10,20 @@ from sshgen.utils.app import AppUtils from sshgen.utils.file import FileUtils -app = typer.Typer(no_args_is_help=True) +app = App(version=f"{__app_name__} v{__version__}", version_flags=["--version"], help_flags=["--help"]) +app.meta.group_parameters = Group("Debug Output") -@app.command("generate") +@app.command(name="generate") def generate_hosts_file( - hosts_file: Annotated[str, typer.Option("--hosts-file", "-h")] = "./hosts.yml", - output: Annotated[str, typer.Option("--output", "-o")] = "./config", + hosts_file: Annotated[str, Parameter(name=["--hosts-file", "-h"], allow_leading_hyphen=True)] = "./hosts.yml", + output: Annotated[str, Parameter(["--output", "-o"])] = "./config", ) -> None: """ Command to generate SSH configuration file. + By default, it uses file hosts.yml placed in your working directory and outputs to the file named "config". + Example usage: sshgen generate -o my_ssh_config """ @@ -30,37 +34,24 @@ def generate_hosts_file( sshgen.generate_ssh_config() -def _version_callback(value: bool) -> None: - if value: - typer.echo(f"{__app_name__} v{__version__}") - raise typer.Exit() - - -# noinspection PyUnusedLocal -@app.callback() +@app.meta.default() def main( + *tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)], verbose: Annotated[ - Optional[bool], - typer.Option( - "--verbose", - is_eager=True, - envvar=["SSHGEN_VERBOSE", "SSHGEN_DEBUG"], - help="Switch log level to DEBUG, default is INFO.", - ), + bool, Parameter(name="--verbose", show_default=False, negative="", env_var=["SSHGEN_VERBOSE", "SSHGEN_DEBUG"]) ] = False, - version: Annotated[ - Optional[bool], - typer.Option("-v", "--version", is_eager=True, callback=_version_callback), - ] = None, ) -> None: """ sshgen generates SSH configuration file based on an Ansible hosts file. """ + if verbose: init_logger(level=LogLevel.DEBUG) else: init_logger(level=LogLevel.INFO) + app(tokens) + if __name__ == "__main__": - app() + app.meta()