Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extras and interpolation #15

Merged
merged 3 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 16 additions & 17 deletions cliffy/cli.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
## CLI to generate CLIs
import contextlib
from typing import TextIO

import rich_click as click
from rich.console import Console
from rich.syntax import Syntax
from rich_click.rich_group import RichGroup
try:
import rich_click as click
from rich.console import Console
from rich.syntax import Syntax
from rich_click.rich_group import RichGroup as AliasGroup
except ImportError:
import click
from .rich import Console, Syntax
from click import Group as AliasGroup

from .helper import print_rich_table, write_to_file
from .homer import Homer
Expand All @@ -15,16 +21,14 @@
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])


class RichAliasedGroup(RichGroup):
class AliasedGroup(AliasGroup):
def get_command(self, ctx, cmd_name):
try:
with contextlib.suppress(KeyError):
cmd_name = ALIASES[cmd_name].name
except KeyError:
pass
return super().get_command(ctx, cmd_name or "")


@click.group(context_settings=CONTEXT_SETTINGS, cls=RichAliasedGroup)
@click.group(context_settings=CONTEXT_SETTINGS, cls=AliasedGroup)
@click.version_option()
def cli() -> None:
pass
Expand All @@ -48,8 +52,7 @@ def load(manifests: list[TextIO]) -> None:
def update(cli_names: list[str]) -> None:
"""Reloads CLI by name"""
for cli_name in cli_names:
cli_metadata = Homer.get_cli_metadata(cli_name)
if cli_metadata:
if cli_metadata := Homer.get_cli_metadata(cli_name):
T = Transformer(open(cli_metadata.runner_path, "r"))
Loader.load_cli(T.cli)
Homer.save_cli_metadata(cli_metadata.runner_path, T.cli)
Expand All @@ -65,9 +68,8 @@ def update(cli_names: list[str]) -> None:
def render(manifest: TextIO) -> None:
"""Render the CLI manifest generation as code"""
T = Transformer(manifest)
syntax = Syntax(T.cli.code, "python", theme="monokai", line_numbers=False)
console = Console()
console.print(syntax)
console.print(T.cli.code, overflow="fold", emoji=False, markup=False)
click.secho(f"# Rendered {T.cli.name} CLI v{T.cli.version} ~", fg="green")


Expand Down Expand Up @@ -103,10 +105,7 @@ def init(cli_name: str, version: str, render: bool, raw: bool) -> None:
def list_clis() -> None:
"List all CLIs loaded"
cols = ["Name", "Version", "Manifest"]
rows = []
for metadata in Homer.get_clis():
rows.append([metadata.cli_name, metadata.version, metadata.runner_path])

rows = [[metadata.cli_name, metadata.version, metadata.runner_path] for metadata in Homer.get_clis()]
print_rich_table(cols, rows, styles=["cyan", "magenta", "green"])


Expand Down
4 changes: 1 addition & 3 deletions cliffy/commander.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,7 @@ def add_greedy_commands(self) -> None:
lazy_command_name = greedy_command.name.replace('(*)', group)
lazy_command_script = greedy_command.script.replace('{(*)}', group)

# lazy load the greedy args
greedy_command_args = self.manifest.args.get(greedy_command.name)
if greedy_command_args:
if greedy_command_args := self.manifest.args.get(greedy_command.name):
self.manifest.args[lazy_command_name] = greedy_command_args

# lazy parse
Expand Down
2 changes: 1 addition & 1 deletion cliffy/commanders/typer.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def add_base_cli(self) -> None:

def version_callback(value: bool):
if value:
print(__cli_name__ + ", " + __version__)
print(f"{{__cli_name__}}, {{__version__}}")
raise typer.Exit()

@cli.callback()
Expand Down
13 changes: 9 additions & 4 deletions cliffy/helper.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import operator
import os
import platform
import subprocess
import sys
from pathlib import Path
from typing import Optional

from packaging import version
from pydantic import BaseModel
from rich.console import Console
from rich.table import Table

try:
from rich.console import Console
from rich.table import Table
except ImportError:
from .rich import Console, Table

HOME_PATH = str(Path.home())
PYTHON_BIN = f"{sys.exec_prefix}/bin"
PYTHON_BIN = f"{sys.exec_prefix}/Scripts" if platform.system() == "Windows" else f"{sys.exec_prefix}/bin"
PYTHON_EXECUTABLE = sys.executable
CLIFFY_CLI_DIR = f"{Path(__file__).parent.resolve()}/clis"
CLIFFY_HOME_PATH = f"{HOME_PATH}/.cliffy"
Expand Down Expand Up @@ -55,7 +60,7 @@ def make_executable(path: str) -> None:
def wrap_as_comment(text: str, split_on: Optional[str] = None) -> str:
if split_on:
joiner = "\n# "
return "# " + joiner.join(text.split(split_on))
return f"# {joiner.join(text.split(split_on))}"

return f"# {text}"

Expand Down
3 changes: 1 addition & 2 deletions cliffy/homer.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,7 @@ def get_cliffy_cli(cli_name: str) -> Optional[str]:
Returns:
Optional[str]: CLI metadata path
"""
cli_metadata_path = glob.glob(f"{CLIFFY_HOME_PATH}/*/{cli_name}.json")
if cli_metadata_path:
if cli_metadata_path := glob.glob(f"{CLIFFY_HOME_PATH}/*/{cli_name}.json"):
return cli_metadata_path[0]
return None

Expand Down
5 changes: 2 additions & 3 deletions cliffy/loader.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import contextlib
import os

from .commander import CLI
Expand Down Expand Up @@ -26,11 +27,9 @@ def load_cli(cls, cli: CLI) -> None:

@classmethod
def unload_cli(cls, cli_name) -> None:
try:
with contextlib.suppress(FileNotFoundError):
os.remove(cls.get_cli_script_path(cli_name))
os.remove(cls.get_cli_path(cli_name))
except FileNotFoundError:
pass

@staticmethod
def get_cli_path(cli_name) -> str:
Expand Down
27 changes: 7 additions & 20 deletions cliffy/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,10 @@ def parse_command_block(self, script: str):
## Bash commands start with $
if script.startswith('$'):
script = script.replace('$ ', '$', 1)
script = '>' + script[1:]
script = f'>{script[1:]}'
return " " * 4 + pybash.Transformer.transform_source(script)

parsed_script = ""
for line in script.split('\n'):
parsed_script += " " * 4 + line + "\n"
return parsed_script
return "".join(" " * 4 + line + "\n" for line in script.split('\n'))

def parse_command(self, block: Union[str, list[Union[str, dict[Literal['help'], str]]]]) -> str:
if isinstance(block, list):
Expand Down Expand Up @@ -87,7 +84,6 @@ def build_param_type(
return parsed_arg_type

def parse_arg(self, arg_name: str, arg_type: str) -> str:
parsed_arg_type = ""
is_required = self.is_param_required(arg_type)
default_val = self.get_default_param_val(arg_type)
param_type = 'Option' if self.is_param_option(arg_name) else 'Argument'
Expand All @@ -109,8 +105,7 @@ def parse_arg(self, arg_name: str, arg_type: str) -> str:
if arg_type in self.manifest.types:
return f"{arg_name}: {self.manifest.types[arg_type]},"

# otherwise parse it
parsed_arg_type = self.build_param_type(
return self.build_param_type(
arg_name,
arg_type,
typer_cls=param_type,
Expand All @@ -119,8 +114,6 @@ def parse_arg(self, arg_name: str, arg_type: str) -> str:
is_required=is_required,
)

return parsed_arg_type

def parse_args(self, command) -> str:
if not self.manifest.args:
return ""
Expand All @@ -132,7 +125,7 @@ def parse_args(self, command) -> str:
parsed_command_args = ""
for arg in command_args:
arg_name, arg_type = next(iter(arg.items()))
parsed_command_args += self.parse_arg(arg_name.strip(), arg_type.strip()) + ' '
parsed_command_args += f'{self.parse_arg(arg_name.strip(), arg_type.strip())} '

# strip the extra ", "
return parsed_command_args[:-2]
Expand All @@ -143,18 +136,12 @@ def get_command_func_name(self, command) -> str:

def get_parsed_command_name(self, command) -> str:
"""land.build -> build or sell -> sell"""
if '.' in command.name:
return command.name.split('.')[-1]

return command.name
return command.name.split('.')[-1] if '.' in command.name else command.name

def indent_block(self, block: str) -> str:
blocklines = block.splitlines()
indented_block = "\n".join([" " * 4 + line for line in blocklines])
return indented_block
return "\n".join([" " * 4 + line for line in blocklines])

def to_args(self, d: dict) -> str:
s = ""
for k, v in d.items():
s += f" {k}={v},"
s = "".join(f" {k}={v}," for k, v in d.items())
return s[:-1]
41 changes: 41 additions & 0 deletions cliffy/rich.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## Mimic rich API methods for rich-less support
import click


class Console:
def __init__(self) -> None:
pass

def print(self, text, **kwargs) -> None:
if isinstance(text, Table):
click.echo(text)
else:
print(text)


class Syntax:
def __init__(self, text, *args, **kwargs) -> None:
self.text = text

def __str__(self) -> str:
return self.text


class Table:
def __init__(self) -> None:
self.cols = []
self.rows = []

def add_column(self, col, style=None) -> None:
self.cols.append(col)

def add_row(self, *row) -> None:
self.rows.append([*row])

def __str__(self) -> str:
text = "".join([click.style(f"{col:10}", fg="magenta") for col in self.cols]) + "\n"
for row in self.rows:
for col in row:
text += f"{col:10}"
text += "\n"
return text
25 changes: 16 additions & 9 deletions cliffy/transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,19 @@ def validate_cli_requires(self) -> None:
if dep_spec.name not in installed_pip_packages:
raise SystemExit(f"MissingRequirement: CLI requires `{dep}`, please install it")

if dep_spec.version and dep_spec.operator:
if not compare_versions(installed_pip_packages[dep_spec.name], dep_spec.version, dep_spec.operator):
raise SystemExit(
f"MissingRequirement: CLI requires `{dep}`, "
f"found version {installed_pip_packages[dep_spec.name]}"
)
if (
dep_spec.version
and dep_spec.operator
and not compare_versions(
installed_pip_packages[dep_spec.name],
dep_spec.version,
dep_spec.operator,
)
):
raise SystemExit(
f"MissingRequirement: CLI requires `{dep}`, "
f"found version {installed_pip_packages[dep_spec.name]}"
)

def resolve_includes(self) -> dict:
include_transforms = map(self.resolve_include_by_path, set(self.command_config['includes']))
Expand All @@ -66,11 +73,11 @@ def resolve_include_by_path(cls, path) -> Self:
def load_manifest(manifest_io: TextIO) -> dict:
try:
manifest_path = os.path.realpath(manifest_io.name)
vars = yaml.safe_load(open(manifest_path, "r")).get('vars', {})
all_vars = yaml.safe_load(open(manifest_path, "r")).get('vars', {})
var_env = Environment(loader=BaseLoader())
interpolated_vars = {
var_env.from_string(str(k)).render(vars): var_env.from_string(str(v)).render(vars)
for k, v in vars.items()
var_env.from_string(str(k)).render(all_vars): var_env.from_string(str(v)).render(all_vars)
for k, v in all_vars.items()
}
manifest_env = Environment(loader=FileSystemLoader(manifest_path)).get_template("")
return yaml.safe_load(manifest_env.render(interpolated_vars))
Expand Down
14 changes: 14 additions & 0 deletions examples/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: template
version: 0.1.0

vars:
GLOBAL_VAR: hello

args:
hello:
- local_arg: str!
- -local_arg_2: str = ""

commands:
hello:
- $ {{GLOBAL_VAR}} f{local_arg} --f{local_arg_2}
Loading