mirror of
https://github.com/yavook/kiwi-scp.git
synced 2024-11-22 04:43:00 +00:00
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
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import attr
|
||||||
import click
|
import click
|
||||||
|
|
||||||
|
from ..config import Config
|
||||||
|
|
||||||
|
|
||||||
class KiwiCLI(click.MultiCommand):
|
class KiwiCLI(click.MultiCommand):
|
||||||
|
"""Command Line Interface spread over multiple files in this directory"""
|
||||||
|
|
||||||
def list_commands(self, ctx):
|
def list_commands(self, ctx):
|
||||||
result = []
|
"""list all the commands defined by cmd_*.py files in this directory"""
|
||||||
for filename in os.listdir(os.path.abspath(os.path.dirname(__file__))):
|
|
||||||
if filename.startswith("cmd_") and filename.endswith(".py"):
|
return (
|
||||||
result.append(filename[4:-3])
|
filename[4:-3]
|
||||||
result.sort()
|
for filename in os.listdir(os.path.abspath(os.path.dirname(__file__)))
|
||||||
return result
|
if filename.startswith("cmd_") and filename.endswith(".py")
|
||||||
|
)
|
||||||
|
|
||||||
def get_command(self, ctx, name):
|
def get_command(self, ctx, name):
|
||||||
|
"""import and return a specific command"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
mod = __import__(f"kiwi_scp.commands.cmd_{name}", None, None, ["cmd"])
|
mod = __import__(f"kiwi_scp.commands.cmd_{name}", None, None, ["cmd"])
|
||||||
except ImportError:
|
except ImportError:
|
||||||
return
|
return
|
||||||
return mod.cmd
|
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
|
import click
|
||||||
|
|
||||||
from ..config import Config
|
from .cli import KiwiCTX, pass_kiwi_ctx
|
||||||
|
|
||||||
|
|
||||||
@click.command(
|
@click.command(
|
||||||
"init",
|
"init",
|
||||||
short_help="Initializes a repo."
|
short_help="Initializes kiwi-scp"
|
||||||
)
|
)
|
||||||
@click.argument(
|
@click.argument(
|
||||||
"path",
|
"path",
|
||||||
required=False,
|
required=False,
|
||||||
type=click.Path(resolve_path=True)
|
type=click.Path(resolve_path=True)
|
||||||
)
|
)
|
||||||
@click.pass_context
|
@pass_kiwi_ctx
|
||||||
def cmd(ctx, path):
|
def cmd(ctx: KiwiCTX, path):
|
||||||
"""Initializes a repository."""
|
"""Initialize or reconfigure a kiwi-scp instance"""
|
||||||
kiwi: Config = ctx.obj["cfg"]
|
|
||||||
click.echo("Hello init")
|
click.echo(f"Hello init, kiwi version {ctx.config.version}")
|
||||||
click.echo(kiwi.kiwi_yml)
|
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import functools
|
||||||
import re
|
import re
|
||||||
from ipaddress import IPv4Network
|
from ipaddress import IPv4Network
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
@ -6,7 +7,7 @@ from typing import Optional, Dict, List, Any
|
||||||
import yaml
|
import yaml
|
||||||
from pydantic import BaseModel, constr, root_validator, validator
|
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
|
# indent yaml lists
|
||||||
|
@ -138,6 +139,20 @@ class Config(BaseModel):
|
||||||
cidr="10.22.46.0/24",
|
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
|
@property
|
||||||
def kiwi_dict(self) -> Dict[str, Any]:
|
def kiwi_dict(self) -> Dict[str, Any]:
|
||||||
"""write this object as a dictionary of strings"""
|
"""write this object as a dictionary of strings"""
|
||||||
|
@ -201,7 +216,7 @@ class Config(BaseModel):
|
||||||
try:
|
try:
|
||||||
return [str(value)]
|
return [str(value)]
|
||||||
|
|
||||||
except Exception as e:
|
except Exception:
|
||||||
# undefined format
|
# undefined format
|
||||||
raise ValueError("Invalid Shells Format")
|
raise ValueError("Invalid Shells Format")
|
||||||
|
|
||||||
|
@ -230,7 +245,7 @@ class Config(BaseModel):
|
||||||
# handle single project name
|
# handle single project name
|
||||||
result.append({"name": str(entry)})
|
result.append({"name": str(entry)})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception:
|
||||||
# undefined format
|
# undefined format
|
||||||
raise ValueError("Invalid Projects Format")
|
raise ValueError("Invalid Projects Format")
|
||||||
|
|
||||||
|
@ -246,7 +261,7 @@ class Config(BaseModel):
|
||||||
# handle as a single project name
|
# handle as a single project name
|
||||||
return [{"name": str(value)}]
|
return [{"name": str(value)}]
|
||||||
|
|
||||||
except Exception as e:
|
except Exception:
|
||||||
# undefined format
|
# undefined format
|
||||||
raise ValueError("Invalid Projects Format")
|
raise ValueError("Invalid Projects Format")
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,14 @@
|
||||||
import click
|
import click
|
||||||
import yaml
|
|
||||||
|
|
||||||
from kiwi_scp.commands.cli import KiwiCLI
|
from kiwi_scp.commands.cli import KiwiCLI
|
||||||
from kiwi_scp.config import Config
|
|
||||||
|
|
||||||
|
|
||||||
@click.command(cls=KiwiCLI)
|
@click.command(cls=KiwiCLI)
|
||||||
@click.pass_context
|
def main():
|
||||||
def main(ctx):
|
"""main entry point for command line interface"""
|
||||||
"""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)
|
|
||||||
|
|
||||||
click.echo("Hello main")
|
click.echo("Hello main")
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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"
|
name = "attrs"
|
||||||
version = "21.2.0"
|
version = "21.2.0"
|
||||||
description = "Classes Without Boilerplate"
|
description = "Classes Without Boilerplate"
|
||||||
category = "dev"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filelock"
|
name = "filelock"
|
||||||
version = "3.3.0"
|
version = "3.3.1"
|
||||||
description = "A platform independent file lock."
|
description = "A platform independent file lock."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -102,7 +102,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "importlib-resources"
|
name = "importlib-resources"
|
||||||
version = "5.2.2"
|
version = "5.3.0"
|
||||||
description = "Read resources from Python packages"
|
description = "Read resources from Python packages"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -113,7 +113,7 @@ zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
|
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]]
|
[[package]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
|
@ -283,7 +283,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.6.1"
|
python-versions = "^3.6.1"
|
||||||
content-hash = "eb1a3ab9af78ac7898062245858dbf9e9a27e82a6484f45f14b8db6c7fe812d6"
|
content-hash = "872024f4d335f920d178ef6b631785f4f6908f8f9f87d970932bd54dee7fd297"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
atomicwrites = [
|
atomicwrites = [
|
||||||
|
@ -315,16 +315,16 @@ distlib = [
|
||||||
{file = "distlib-0.3.3.zip", hash = "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05"},
|
{file = "distlib-0.3.3.zip", hash = "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05"},
|
||||||
]
|
]
|
||||||
filelock = [
|
filelock = [
|
||||||
{file = "filelock-3.3.0-py3-none-any.whl", hash = "sha256:bbc6a0382fe8ec4744ecdf6683a2e07f65eb10ff1aff53fc02a202565446cde0"},
|
{file = "filelock-3.3.1-py3-none-any.whl", hash = "sha256:2b5eb3589e7fdda14599e7eb1a50e09b4cc14f34ed98b8ba56d33bfaafcbef2f"},
|
||||||
{file = "filelock-3.3.0.tar.gz", hash = "sha256:8c7eab13dc442dc249e95158bcc12dec724465919bdc9831fdbf0660f03d1785"},
|
{file = "filelock-3.3.1.tar.gz", hash = "sha256:34a9f35f95c441e7b38209775d6e0337f9a3759f3565f6c5798f19618527c76f"},
|
||||||
]
|
]
|
||||||
importlib-metadata = [
|
importlib-metadata = [
|
||||||
{file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"},
|
{file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"},
|
||||||
{file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"},
|
{file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"},
|
||||||
]
|
]
|
||||||
importlib-resources = [
|
importlib-resources = [
|
||||||
{file = "importlib_resources-5.2.2-py3-none-any.whl", hash = "sha256:2480d8e07d1890056cb53c96e3de44fead9c62f2ba949b0f2e4c4345f4afa977"},
|
{file = "importlib_resources-5.3.0-py3-none-any.whl", hash = "sha256:7a65eb0d8ee98eedab76e6deb51195c67f8e575959f6356a6e15fd7e1148f2a3"},
|
||||||
{file = "importlib_resources-5.2.2.tar.gz", hash = "sha256:a65882a4d0fe5fbf702273456ba2ce74fe44892c25e42e057aca526b702a6d4b"},
|
{file = "importlib_resources-5.3.0.tar.gz", hash = "sha256:f2e58e721b505a79abe67f5868d99f8886aec8594c962c7490d0c22925f518da"},
|
||||||
]
|
]
|
||||||
iniconfig = [
|
iniconfig = [
|
||||||
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
|
{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]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.6.1"
|
python = "^3.6.1"
|
||||||
PyYAML = "^5.4.1"
|
attrs = "^21.2.0"
|
||||||
pydantic = "^1.8.2"
|
|
||||||
click = "^8.0.3"
|
click = "^8.0.3"
|
||||||
|
pydantic = "^1.8.2"
|
||||||
|
PyYAML = "^5.4.1"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
virtualenv = "^20.8.1"
|
|
||||||
pytest = "^6.2.5"
|
pytest = "^6.2.5"
|
||||||
toml = "^0.10.2"
|
toml = "^0.10.2"
|
||||||
|
virtualenv = "^20.8.1"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
kiwi = "kiwi_scp.scripts.kiwi:main"
|
kiwi = "kiwi_scp.scripts.kiwi:main"
|
||||||
|
|
Loading…
Reference in a new issue