2
0
mirror of https://github.com/frappe/bench.git synced 2025-01-08 16:14:12 +00:00

refactor(cli): Commands Resolution

The implementations so far were hacks that worked for the most used
commands but broken down when challenged or expected to maintain
documented usages [eg: custom app commands].

The current implementation consists of a two step approach:
1. figure out command name that user is trying to execute
2. pass the directive to the app (bench, frappe or other) that consists of the cmd

---

For tackling #1, get_cmd_from_sysargv contains exhaustive rules that
cover all (that i know and ive come across) combinations of valid
frappe commands.

For problem #2, a simple check in click's Group object does the trick.

Tested with possible known commands with combinations of context flags
and params, with bench, frappe & external app's commands
This commit is contained in:
Gavin D'souza 2022-07-27 10:56:46 +05:30
parent beac865153
commit a6f196440a
2 changed files with 68 additions and 37 deletions

View File

@ -1,6 +1,8 @@
# imports - standard imports # imports - standard imports
import atexit import atexit
from contextlib import contextmanager
import json import json
from logging import Logger
import os import os
import pwd import pwd
import sys import sys
@ -25,7 +27,7 @@ from bench.utils import (
is_root, is_root,
log, log,
setup_logging, setup_logging,
parse_sys_argv, get_cmd_from_sysargv,
) )
from bench.utils.bench import get_env_cmd from bench.utils.bench import get_env_cmd
@ -35,23 +37,41 @@ verbose = False
is_envvar_warn_set = None is_envvar_warn_set = None
from_command_line = False # set when commands are executed via the CLI from_command_line = False # set when commands are executed via the CLI
bench.LOG_BUFFER = [] bench.LOG_BUFFER = []
sys_argv = None
change_uid_msg = "You should not run this command as root" change_uid_msg = "You should not run this command as root"
src = os.path.dirname(__file__) src = os.path.dirname(__file__)
@contextmanager
def execute_cmd(check_for_update=True, command: str = None, logger: Logger = None):
if check_for_update:
atexit.register(check_latest_version)
try:
yield
except BaseException as e:
return_code = getattr(e, "code", 1)
if isinstance(e, Exception):
click.secho(f"ERROR: {e}", fg="red")
if return_code:
logger.warning(f"{command} executed with exit code {return_code}")
raise e
def cli(): def cli():
global from_command_line, bench_config, is_envvar_warn_set, verbose, sys_argv global from_command_line, bench_config, is_envvar_warn_set, verbose
from_command_line = True from_command_line = True
command = " ".join(sys.argv) command = " ".join(sys.argv)
argv = set(sys.argv) argv = set(sys.argv)
is_envvar_warn_set = not (os.environ.get("BENCH_DEVELOPER") or os.environ.get("CI")) is_envvar_warn_set = not (os.environ.get("BENCH_DEVELOPER") or os.environ.get("CI"))
is_cli_command = len(sys.argv) > 1 and not argv.intersection({"src", "--version"}) is_cli_command = len(sys.argv) > 1 and not argv.intersection({"src", "--version"})
sys_argv = parse_sys_argv() cmd_from_sys = get_cmd_from_sysargv()
if "--verbose" in sys_argv.options: if "--verbose" in argv:
verbose = True verbose = True
change_working_directory() change_working_directory()
@ -69,8 +89,8 @@ def cli():
if ( if (
is_envvar_warn_set is_envvar_warn_set
and is_cli_command and is_cli_command
and is_dist_editable(bench.PROJECT_NAME)
and not bench_config.get("developer_mode") and not bench_config.get("developer_mode")
and is_dist_editable(bench.PROJECT_NAME)
): ):
log( log(
"bench is installed in editable mode!\n\nThis is not the recommended mode" "bench is installed in editable mode!\n\nThis is not the recommended mode"
@ -95,31 +115,14 @@ def cli():
print(get_frappe_help()) print(get_frappe_help())
return return
if ( if cmd_from_sys in bench_command.commands:
sys_argv.commands.intersection(get_cached_frappe_commands()) with execute_cmd(check_for_update=not is_cli_command, command=command, logger=logger):
or sys_argv.commands.intersection(get_frappe_commands()) bench_command()
): elif cmd_from_sys in get_frappe_commands():
frappe_cmd() frappe_cmd()
else:
if sys.argv[1] in Bench(".").apps:
app_cmd() app_cmd()
if not is_cli_command:
atexit.register(check_latest_version)
try:
bench_command()
except BaseException as e:
return_code = getattr(e, "code", 1)
if isinstance(e, Exception):
click.secho(f"ERROR: {e}", fg="red")
if return_code:
logger.warning(f"{command} executed with exit code {return_code}")
raise e
def check_uid(): def check_uid():
if cmd_requires_root() and not is_root(): if cmd_requires_root() and not is_root():

View File

@ -165,7 +165,7 @@ def which(executable: str, raise_err: bool = False) -> str:
return exec_ return exec_
def setup_logging(bench_path=".") -> "logger": def setup_logging(bench_path=".") -> logging.Logger:
LOG_LEVEL = 15 LOG_LEVEL = 15
logging.addLevelName(LOG_LEVEL, "LOG") logging.addLevelName(LOG_LEVEL, "LOG")
@ -529,13 +529,41 @@ class _dict(dict):
return _dict(dict(self).copy()) return _dict(dict(self).copy())
def parse_sys_argv(): def get_cmd_from_sysargv():
sys_argv = _dict(options=set(), commands=set()) """Identify and segregate tokens to options and command
for c in sys.argv[1:]: For Command: `bench --profile --site frappeframework.com migrate --no-backup`
if c.startswith("-"): sys.argv: ["/home/frappe/.local/bin/bench", "--profile", "--site", "frappeframework.com", "migrate", "--no-backup"]
sys_argv.options.add(c) Actual command run: migrate
else:
sys_argv.commands.add(c)
return sys_argv """
# context is passed as options to frappe's bench_helper
from bench.bench import Bench
frappe_context = _dict(
params={"--site"},
flags={"--verbose", "--profile", "--force"}
)
cmd_from_ctx = None
sys_argv = sys.argv[1:]
skip_next = False
for arg in sys_argv:
if skip_next:
skip_next = False
continue
if arg in frappe_context.flags:
continue
elif arg in frappe_context.params:
skip_next = True
continue
if sys_argv.index(arg) == 0 and arg in Bench(".").apps:
continue
cmd_from_ctx = arg
break
return cmd_from_ctx