more well-defined CLI with custom mutable context
This commit is contained in:
parent
217a5fa75b
commit
195fbd24fe
6 changed files with 73 additions and 40 deletions
|
@ -1,20 +1,45 @@
|
|||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import attr
|
||||
import click
|
||||
|
||||
from ..config import Config
|
||||
|
||||
|
||||
class KiwiCLI(click.MultiCommand):
|
||||
"""Command Line Interface spread over multiple files in this directory"""
|
||||
|
||||
def list_commands(self, ctx):
|
||||
result = []
|
||||
for filename in os.listdir(os.path.abspath(os.path.dirname(__file__))):
|
||||
if filename.startswith("cmd_") and filename.endswith(".py"):
|
||||
result.append(filename[4:-3])
|
||||
result.sort()
|
||||
return result
|
||||
"""list all the commands defined by cmd_*.py files in this directory"""
|
||||
|
||||
return (
|
||||
filename[4:-3]
|
||||
for filename in os.listdir(os.path.abspath(os.path.dirname(__file__)))
|
||||
if filename.startswith("cmd_") and filename.endswith(".py")
|
||||
)
|
||||
|
||||
def get_command(self, ctx, name):
|
||||
"""import and return a specific command"""
|
||||
|
||||
try:
|
||||
mod = __import__(f"kiwi_scp.commands.cmd_{name}", None, None, ["cmd"])
|
||||
except ImportError:
|
||||
return
|
||||
return mod.cmd
|
||||
|
||||
|
||||
@attr.s
|
||||
class KiwiCTX:
|
||||
"""this class is used as the commands' shared context"""
|
||||
|
||||
instance: Path = attr.ib(factory=lambda: Path('.'))
|
||||
|
||||
@property
|
||||
def config(self) -> Config:
|
||||
"""shorthand: get the current configuration"""
|
||||
|
||||
return Config.from_instance(self.instance)
|
||||
|
||||
|
||||
pass_kiwi_ctx = click.make_pass_decorator(KiwiCTX, ensure=True)
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
import click
|
||||
|
||||
from ..config import Config
|
||||
from .cli import KiwiCTX, pass_kiwi_ctx
|
||||
|
||||
|
||||
@click.command(
|
||||
"init",
|
||||
short_help="Initializes a repo."
|
||||
short_help="Initializes kiwi-scp"
|
||||
)
|
||||
@click.argument(
|
||||
"path",
|
||||
required=False,
|
||||
type=click.Path(resolve_path=True)
|
||||
)
|
||||
@click.pass_context
|
||||
def cmd(ctx, path):
|
||||
"""Initializes a repository."""
|
||||
kiwi: Config = ctx.obj["cfg"]
|
||||
click.echo("Hello init")
|
||||
click.echo(kiwi.kiwi_yml)
|
||||
@pass_kiwi_ctx
|
||||
def cmd(ctx: KiwiCTX, path):
|
||||
"""Initialize or reconfigure a kiwi-scp instance"""
|
||||
|
||||
click.echo(f"Hello init, kiwi version {ctx.config.version}")
|
||||
pass
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import functools
|
||||
import re
|
||||
from ipaddress import IPv4Network
|
||||
from pathlib import Path
|
||||
|
@ -6,7 +7,7 @@ from typing import Optional, Dict, List, Any
|
|||
import yaml
|
||||
from pydantic import BaseModel, constr, root_validator, validator
|
||||
|
||||
from ._constants import RE_SEMVER, RE_VARNAME, HEADER_KIWI_CONF_NAME
|
||||
from ._constants import RE_SEMVER, RE_VARNAME, HEADER_KIWI_CONF_NAME, KIWI_CONF_NAME
|
||||
|
||||
|
||||
# indent yaml lists
|
||||
|
@ -138,6 +139,20 @@ class Config(BaseModel):
|
|||
cidr="10.22.46.0/24",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@functools.lru_cache(maxsize=5)
|
||||
def from_instance(cls, instance: Path):
|
||||
"""parses an actual kiwi.yml from disk"""
|
||||
|
||||
try:
|
||||
with open(instance.joinpath(KIWI_CONF_NAME)) as kc:
|
||||
yml = yaml.safe_load(kc)
|
||||
return cls.parse_obj(yml)
|
||||
|
||||
except FileNotFoundError:
|
||||
# return the defaults if no kiwi.yml found
|
||||
return cls()
|
||||
|
||||
@property
|
||||
def kiwi_dict(self) -> Dict[str, Any]:
|
||||
"""write this object as a dictionary of strings"""
|
||||
|
@ -201,7 +216,7 @@ class Config(BaseModel):
|
|||
try:
|
||||
return [str(value)]
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
# undefined format
|
||||
raise ValueError("Invalid Shells Format")
|
||||
|
||||
|
@ -230,7 +245,7 @@ class Config(BaseModel):
|
|||
# handle single project name
|
||||
result.append({"name": str(entry)})
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
# undefined format
|
||||
raise ValueError("Invalid Projects Format")
|
||||
|
||||
|
@ -246,7 +261,7 @@ class Config(BaseModel):
|
|||
# handle as a single project name
|
||||
return [{"name": str(value)}]
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
# undefined format
|
||||
raise ValueError("Invalid Projects Format")
|
||||
|
||||
|
|
|
@ -1,21 +1,14 @@
|
|||
import click
|
||||
import yaml
|
||||
|
||||
from kiwi_scp.commands.cli import KiwiCLI
|
||||
from kiwi_scp.config import Config
|
||||
|
||||
|
||||
@click.command(cls=KiwiCLI)
|
||||
@click.pass_context
|
||||
def main(ctx):
|
||||
"""A complex command line interface."""
|
||||
|
||||
with open("./kiwi.yml") as kc:
|
||||
yml = yaml.safe_load(kc)
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj["cfg"] = Config(**yml)
|
||||
def main():
|
||||
"""main entry point for command line interface"""
|
||||
|
||||
click.echo("Hello main")
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
18
poetry.lock
generated
18
poetry.lock
generated
|
@ -10,7 +10,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|||
name = "attrs"
|
||||
version = "21.2.0"
|
||||
description = "Classes Without Boilerplate"
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
|
@ -73,7 +73,7 @@ python-versions = "*"
|
|||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.3.0"
|
||||
version = "3.3.1"
|
||||
description = "A platform independent file lock."
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -102,7 +102,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes
|
|||
|
||||
[[package]]
|
||||
name = "importlib-resources"
|
||||
version = "5.2.2"
|
||||
version = "5.3.0"
|
||||
description = "Read resources from Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -113,7 +113,7 @@ zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
|
|||
|
||||
[package.extras]
|
||||
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
|
||||
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"]
|
||||
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
|
@ -283,7 +283,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.6.1"
|
||||
content-hash = "eb1a3ab9af78ac7898062245858dbf9e9a27e82a6484f45f14b8db6c7fe812d6"
|
||||
content-hash = "872024f4d335f920d178ef6b631785f4f6908f8f9f87d970932bd54dee7fd297"
|
||||
|
||||
[metadata.files]
|
||||
atomicwrites = [
|
||||
|
@ -315,16 +315,16 @@ distlib = [
|
|||
{file = "distlib-0.3.3.zip", hash = "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05"},
|
||||
]
|
||||
filelock = [
|
||||
{file = "filelock-3.3.0-py3-none-any.whl", hash = "sha256:bbc6a0382fe8ec4744ecdf6683a2e07f65eb10ff1aff53fc02a202565446cde0"},
|
||||
{file = "filelock-3.3.0.tar.gz", hash = "sha256:8c7eab13dc442dc249e95158bcc12dec724465919bdc9831fdbf0660f03d1785"},
|
||||
{file = "filelock-3.3.1-py3-none-any.whl", hash = "sha256:2b5eb3589e7fdda14599e7eb1a50e09b4cc14f34ed98b8ba56d33bfaafcbef2f"},
|
||||
{file = "filelock-3.3.1.tar.gz", hash = "sha256:34a9f35f95c441e7b38209775d6e0337f9a3759f3565f6c5798f19618527c76f"},
|
||||
]
|
||||
importlib-metadata = [
|
||||
{file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"},
|
||||
{file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"},
|
||||
]
|
||||
importlib-resources = [
|
||||
{file = "importlib_resources-5.2.2-py3-none-any.whl", hash = "sha256:2480d8e07d1890056cb53c96e3de44fead9c62f2ba949b0f2e4c4345f4afa977"},
|
||||
{file = "importlib_resources-5.2.2.tar.gz", hash = "sha256:a65882a4d0fe5fbf702273456ba2ce74fe44892c25e42e057aca526b702a6d4b"},
|
||||
{file = "importlib_resources-5.3.0-py3-none-any.whl", hash = "sha256:7a65eb0d8ee98eedab76e6deb51195c67f8e575959f6356a6e15fd7e1148f2a3"},
|
||||
{file = "importlib_resources-5.3.0.tar.gz", hash = "sha256:f2e58e721b505a79abe67f5868d99f8886aec8594c962c7490d0c22925f518da"},
|
||||
]
|
||||
iniconfig = [
|
||||
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
|
||||
|
|
|
@ -6,14 +6,15 @@ authors = ["ldericher <40151420+ldericher@users.noreply.github.com>"]
|
|||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.6.1"
|
||||
PyYAML = "^5.4.1"
|
||||
pydantic = "^1.8.2"
|
||||
attrs = "^21.2.0"
|
||||
click = "^8.0.3"
|
||||
pydantic = "^1.8.2"
|
||||
PyYAML = "^5.4.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
virtualenv = "^20.8.1"
|
||||
pytest = "^6.2.5"
|
||||
toml = "^0.10.2"
|
||||
virtualenv = "^20.8.1"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
kiwi = "kiwi_scp.scripts.kiwi:main"
|
||||
|
|
Loading…
Reference in a new issue