diff --git a/bench/app.py b/bench/app.py index f9ab0b821..baf68115e 100755 --- a/bench/app.py +++ b/bench/app.py @@ -30,6 +30,8 @@ restart_supervisor_processes, restart_systemd_processes, ) +from bench.utils.render import step + logger = logging.getLogger(bench.PROJECT_NAME) @@ -137,6 +139,7 @@ def __init__( self.bench = bench super().__init__(name, branch, *args, **kwargs) + @step(title="Fetching App {repo}", success="App {repo} Fetched") def get(self): branch = f"--branch {self.tag}" if self.tag else "" shallow = "--depth 1" if self.bench.shallow_clone else "" @@ -150,6 +153,7 @@ def get(self): cwd=os.path.join(self.bench.name, "apps"), ) + @step(title="Archiving App {repo}", success="App {repo} Archived") def remove(self): active_app_path = os.path.join("apps", self.repo) archived_path = os.path.join("archived", "apps") @@ -160,6 +164,7 @@ def remove(self): log(f"App moved from {active_app_path} to {archived_app_path}") shutil.move(active_app_path, archived_app_path) + @step(title="Installing App {repo}", success="App {repo} Installed") def install(self, skip_assets=False, verbose=True): from bench.utils.app import get_app_name @@ -174,6 +179,7 @@ def install(self, skip_assets=False, verbose=True): app=app_name, bench_path=self.bench.name, verbose=verbose, skip_assets=skip_assets, ) + @step(title="Uninstalling App {repo}", success="App {repo} Uninstalled") def uninstall(self): env_python = get_env_cmd("python", bench_path=self.bench.name) self.bench.run(f"{env_python} -m pip uninstall -y {self.repo}") diff --git a/bench/bench.py b/bench/bench.py index a3b2cb552..c9e160863 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -27,6 +27,7 @@ get_venv_path, get_env_cmd, ) +from bench.utils.render import step if TYPE_CHECKING: @@ -119,6 +120,7 @@ def uninstall(self, app): # self.build() - removed because it seems unnecessary self.reload() + @step(title="Building Bench Assets", success="Bench Assets Built") def build(self): # build assets & stuff run_frappe_cmd("build", bench_path=self.name) @@ -214,12 +216,14 @@ def __init__(self, bench: Bench): self.bench = bench self.cwd = self.bench.cwd + @step(title="Setting Up Directories", success="Directories Set Up") def dirs(self): os.makedirs(self.bench.name, exist_ok=True) for dirname in paths_in_bench: os.makedirs(os.path.join(self.bench.name, dirname), exist_ok=True) + @step(title="Setting Up Environment", success="Environment Set Up") def env(self, python="python3"): """Setup env folder - create env if not exists @@ -238,6 +242,7 @@ def env(self, python="python3"): if os.path.exists(frappe): self.run(f"{env_python} -m pip install -q -U -e {frappe}") + @step(title="Setting Up Bench Config", success="Bench Config Set Up") def config(self, redis=True, procfile=True): """Setup config folder - create pids folder @@ -260,12 +265,14 @@ def logging(self): return setup_logging(bench_path=self.bench.name) + @step(title="Setting Up Bench Patches", success="Bench Patches Set Up") def patches(self): shutil.copy( os.path.join(os.path.dirname(os.path.abspath(__file__)), "patches", "patches.txt"), os.path.join(self.bench.name, "patches.txt"), ) + @step(title="Setting Up Backups Cronjob", success="Backups Cronjob Set Up") def backups(self): # TODO: to something better for logging data? - maybe a wrapper that auto-logs with more context logger.log("setting up backups") @@ -288,6 +295,7 @@ def backups(self): logger.log("backups were set up") + @step(title="Setting Up Bench Dependencies", success="Bench Dependencies Set Up") def requirements(self): from bench.utils.bench import update_requirements update_requirements(bench=self.bench) diff --git a/bench/cli.py b/bench/cli.py index c628d9dd5..4df81ec62 100755 --- a/bench/cli.py +++ b/bench/cli.py @@ -29,9 +29,13 @@ ) from bench.utils.bench import get_env_cmd -fancy = True -from_command_line = False +# these variables are used to show dynamic outputs on the terminal +fancy = False bench.LOG_BUFFER = [] + +# set when commands are executed via the CLI +from_command_line = False + change_uid_msg = "You should not run this command as root" src = os.path.dirname(__file__) diff --git a/bench/utils/render.py b/bench/utils/render.py new file mode 100644 index 000000000..484171554 --- /dev/null +++ b/bench/utils/render.py @@ -0,0 +1,65 @@ +# imports - standard imports +import sys +from io import StringIO + +# imports - third party imports +import click + + +class Capturing(list): + """ + Util to consume the stdout encompassed in it and push it to a list + + with Capturing() as output: + subprocess.check_output("ls", shell=True) + + print(output) + # ["b'Applications\\nDesktop\\nDocuments\\nDownloads\\n'"] + """ + + def __enter__(self): + self._stdout = sys.stdout + sys.stdout = self._stringio = StringIO() + return self + + def __exit__(self, *args): + self.extend(self._stringio.getvalue().splitlines()) + del self._stringio # free up some memory + sys.stdout = self._stdout + + +def step(title: str = None, success: str = None): + """Supposed to be wrapped around the smallest possible atomic step in a given operation. + For instance, `building assets` is a step in the update operation. + """ + + def innfn(fn): + def wrapper_fn(*args, **kwargs): + import bench.cli + + if bench.cli.from_command_line and bench.cli.fancy: + kw = args[0].__dict__ + + _title = f"{click.style('⏼', fg='bright_yellow')} {title.format(**kw)}" + click.secho(_title) + + retval = fn(*args) + + if bench.cli.from_command_line and bench.cli.fancy: + click.clear() + + for l in bench.LOG_BUFFER: + click.secho(l["message"], fg=l["color"]) + + _success = f"{click.style('✔', fg='green')} {success.format(**kw)}" + click.echo(_success) + + bench.LOG_BUFFER.append( + {"message": _success, "color": None,} + ) + + return retval + + return wrapper_fn + + return innfn