from pathlib import Path
from typing import Optional, Any
from dotmap import DotMap
import envtoml
import subprocess
import termcolor
import re
import os
import sys
import click
from pudb import set_trace as bp
TEST_FLAGS = "-- --color always --nocapture "
RUST_BACKTRACE = "full"
MANIFEST_FILE = Path("Cargo.toml")
MANIFEST = DotMap(envtoml.load(MANIFEST_FILE.open()))
_NAME = MANIFEST.package.name
VERSION = MANIFEST.package.version
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
REGEX_VERSION = dict()
REGEX_VERSION.update(
dict.fromkeys(
["src/lib.rs", "README.md"],
{
"pattern": rf'(\[dependencies\][\r]?\n{_NAME}\s=\s)("[\d.]+")',
"repl": rf'\g<1>"{VERSION}"',
},
)
)
def export_environment_variables() -> None:
if os.environ.get("RUST_BACKTRACE") != RUST_BACKTRACE:
os.environ["RUST_BACKTRACE"] = RUST_BACKTRACE
def build(command: str, flags: str = "") -> None:
try:
subprocess.check_call(f"cargo {command} {flags}", shell=True)
except subprocess.CalledProcessError:
sys.exit(1)
def check_release(flags: str = "", release: bool = False) -> str:
if release:
flags += "--release "
return flags
def print_version(ctx: click.Context, param: Any, value: bool) -> None:
if not value or ctx.resilient_parsing:
return
click.echo(VERSION)
ctx.exit()
@click.group(context_settings=CONTEXT_SETTINGS, invoke_without_command=True)
@click.pass_context
@click.option(
"-v",
"--version",
is_flag=True,
callback=print_version,
expose_value=False,
is_eager=True,
help="Show version.",
)
@click.option(
"-r",
"--release",
is_flag=True,
help="Enable release mode.",
)
def cli(ctx: click.Context, release: bool) -> None:
ctx.ensure_object(dict)
ctx.obj["RELEASE"] = release
export_environment_variables()
if ctx.invoked_subcommand is None:
flags = check_release(release=release)
build("run", flags)
@cli.command()
@click.pass_context
@click.argument("name", required=False)
def example(ctx: click.Context, name: Optional[str] = None) -> None:
flags = check_release(release=ctx.obj["RELEASE"])
flags += "--example "
if name is not None:
flags += name
build("run", flags)
else:
list_examples = [example.stem for example in Path("examples").glob("*.rs")]
example_files = " " + "\n ".join(list_examples)
error_title = termcolor.colored("missing the name of the example", "red")
raised_error = f"{error_title}.\n\nAvailable examples:\n{example_files}"
raise TypeError(raised_error)
@cli.command()
@click.pass_context
@click.option(
"-a",
"--all",
"all_",
is_flag=True,
help="Test all targets: lib, bins, tests, benches, examples.",
)
def tests(ctx: click.Context, all_: bool) -> None:
flags = check_release(release=ctx.obj["RELEASE"])
if all_:
flags += "--all-targets "
else:
flags += "--test lib "
flags += TEST_FLAGS
build("test", flags)
@cli.command()
@click.pass_context
@click.option(
"-o",
"--open",
"open_",
is_flag=True,
help="Open the documentation in your browser after the build.",
)
def doc(ctx: click.Context, open_: bool) -> None:
flags = ""
if open_:
flags += "--open "
build("doc", flags)
@cli.command()
@click.pass_context
@click.option(
"-c",
"--check",
is_flag=True,
help="Check any warning or error before publishing.",
)
def publish(ctx: click.Context, check: bool) -> None:
flags = ""
if check:
flags += "--dry-run "
build("publish", flags)
@cli.command()
@click.pass_context
def update_versions(ctx: click.Context) -> None:
for file_to_check, regex in REGEX_VERSION.items():
path = Path(file_to_check)
contents = path.read_text()
result = re.findall(regex["pattern"], contents)
if len(result) != 1:
raise ValueError(
f"More than one match for the match pattern of {path.name} file"
)
contents = re.sub(regex["pattern"], regex["repl"], contents)
with path.open("w") as f:
f.write(contents)
def main() -> None:
cli(prog_name=__file__)
if __name__ == "__main__":
main()