From a91d8db66f22c6051bbc73719e13bd209f402354 Mon Sep 17 00:00:00 2001 From: gofrendi Date: Sun, 1 Dec 2024 12:18:15 +0700 Subject: [PATCH] Add asdf --- src/zrb/builtin/__init__.py | 6 ++ src/zrb/builtin/group.py | 9 +- src/zrb/builtin/setup/dev/asdf.py | 93 ++++++++++++++++++++ src/zrb/builtin/setup/dev/asdf_helper.py | 41 +++++++++ src/zrb/builtin/setup/system/latex/ubuntu.py | 18 ++++ src/zrb/builtin/setup/system/ubuntu.py | 28 ++++++ src/zrb/config.py | 3 + src/zrb/task/cmd_task.py | 22 +++-- src/zrb/task/rsync_task.py | 16 ++-- src/zrb/util/string/format.py | 6 +- 10 files changed, 226 insertions(+), 16 deletions(-) create mode 100644 src/zrb/builtin/setup/dev/asdf.py create mode 100644 src/zrb/builtin/setup/dev/asdf_helper.py create mode 100644 src/zrb/builtin/setup/system/latex/ubuntu.py create mode 100644 src/zrb/builtin/setup/system/ubuntu.py diff --git a/src/zrb/builtin/__init__.py b/src/zrb/builtin/__init__.py index 241ac6b8..022c21bf 100644 --- a/src/zrb/builtin/__init__.py +++ b/src/zrb/builtin/__init__.py @@ -12,6 +12,9 @@ from zrb.builtin.project.add.fastapp import add_fastapp_to_project from zrb.builtin.project.create.create import create_project from zrb.builtin.python import format_python_code +from zrb.builtin.setup.dev.asdf import setup_asdf +from zrb.builtin.setup.system.latex.ubuntu import setup_latex_on_ubuntu +from zrb.builtin.setup.system.ubuntu import setup_ubuntu from zrb.builtin.shell.autocomplete.bash import make_bash_autocomplete from zrb.builtin.shell.autocomplete.subcmd import get_shell_subcommands from zrb.builtin.shell.autocomplete.zsh import make_zsh_autocomplete @@ -41,3 +44,6 @@ assert todo_edit assert todo_complete assert todo_log +assert setup_ubuntu +assert setup_latex_on_ubuntu +assert setup_asdf diff --git a/src/zrb/builtin/group.py b/src/zrb/builtin/group.py index 1c237bee..502470ac 100644 --- a/src/zrb/builtin/group.py +++ b/src/zrb/builtin/group.py @@ -35,9 +35,14 @@ setup_group = cli.add_group(Group(name="setup", description="🛠️ Setup")) setup_system_group = setup_group.add_group( - Group(name="system", description="🛠️ Setup system") + Group(name="system", description="🔧 Setup system") +) +setup_latex_group = setup_system_group.add_group( + Group(name="latex", description="✍️ Setup LaTeX") +) +setup_dev_group = setup_group.add_group( + Group(name="dev", description="💻 Setup developer tools") ) -setup_dev_group = setup_group.add_group(Group(name="dev", description="🧑‍💻 Setup dev")) setup_service_group = setup_group.add_group( Group(name="services", description="🌐 Setup services") ) diff --git a/src/zrb/builtin/setup/dev/asdf.py b/src/zrb/builtin/setup/dev/asdf.py new file mode 100644 index 00000000..bf520804 --- /dev/null +++ b/src/zrb/builtin/setup/dev/asdf.py @@ -0,0 +1,93 @@ +import os + +from zrb.builtin.group import setup_dev_group +from zrb.builtin.setup.dev.asdf_helper import ( + check_asdf_dir, + get_install_prerequisites_cmd, + setup_asdf_ps_config, + setup_asdf_sh_config, +) +from zrb.context.any_context import AnyContext +from zrb.input.bool_input import BoolInput +from zrb.input.option_input import OptionInput +from zrb.task.cmd_task import CmdTask +from zrb.task.make_task import make_task +from zrb.task.task import Task + +install_asdf_prerequisites = CmdTask( + name="install-asdf-prerequisites", + input=[ + OptionInput( + name="package-manager", + description="Your package manager", + prompt="Your package manager", + options=["apt", "dnf", "pacman", "zypper", "brew", "spack"], + default_str="apt", + ), + BoolInput( + name="use-sudo", + description="Use sudo or not", + prompt="Need sudo", + default_str="yes", + ), + ], + cmd=get_install_prerequisites_cmd, +) + + +download_asdf = CmdTask( + name="download-asdf", + cmd="git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.1", + execute_condition=check_asdf_dir, +) +install_asdf_prerequisites >> download_asdf + + +@make_task( + name="setup-asdf-on-bash", + input=BoolInput( + name="setup-bash", + description="Setup bash", + prompt="Setup bash", + default_str="yes", + ), + execute_condition='{ctx.input["setup_bash"]}', + upstream=download_asdf, +) +def setup_asdf_on_bash(ctx: AnyContext): + setup_asdf_sh_config(os.path.expanduser(os.path.join("~", ".bashrc"))) + + +@make_task( + name="setup-asdf-on-zsh", + input=BoolInput( + name="setup-zsh", description="Setup zsh", prompt="Setup zsh", default_str="yes" + ), + execute_condition='{ctx.input["setup_zsh"]}', + upstream=download_asdf, +) +def setup_asdf_on_zsh(ctx: AnyContext): + setup_asdf_sh_config(os.path.expanduser(os.path.join("~", ".zshrc"))) + + +@make_task( + name="setup-asdf-on-powershell", + input=BoolInput( + name="setup-powershell", + description="Setup powershell", + prompt="Setup powershell", + default_str="yes", + ), + execute_condition='{ctx.input["setup_powershell"]}', + upstream=download_asdf, +) +def setup_asdf_on_powershell(ctx: AnyContext): + setup_asdf_ps_config( + os.path.expanduser(os.path.join("~", ".config", "powershell", "profile.ps1")) + ) + + +setup_asdf = setup_dev_group.add_task( + Task(name="setup-asdf", description="🧰 Setup `asdf`."), alias="asdf" +) +setup_asdf << [setup_asdf_on_bash, setup_asdf_on_zsh, setup_asdf_on_powershell] diff --git a/src/zrb/builtin/setup/dev/asdf_helper.py b/src/zrb/builtin/setup/dev/asdf_helper.py new file mode 100644 index 00000000..24cd26ea --- /dev/null +++ b/src/zrb/builtin/setup/dev/asdf_helper.py @@ -0,0 +1,41 @@ +import os + +from zrb.context.any_context import AnyContext + + +def get_install_prerequisites_cmd(ctx: AnyContext) -> str: + package_manager: str = ctx.input["package-manager"] + if package_manager in ["brew", "spack"]: + cmd = "{package_manager} install coreutils curl git" + elif package_manager == "pacman": + cmd = "{package_manager} -S curl git" + else: + cmd = "{package_manager} install curl git" + use_sudo: bool = ctx.input["use-sudo"] + if use_sudo: + return "sudo {cmd}" + return cmd + + +def check_asdf_dir(_: AnyContext): + asdf_dir = os.path.expanduser(os.path.join("~", ".asdf")) + return os.path.isdir(asdf_dir) + + +def setup_asdf_sh_config(file_path: str): + _setup_asdf_config(file_path, '. "$HOME/.asdf/asdf.sh"') + + +def setup_asdf_ps_config(file_path: str): + _setup_asdf_config(file_path, '. "$HOME/.asdf/asdf.ps1"') + + +def _setup_asdf_config(file_path: str, asdf_config: str): + dir_path = os.path.dirname(file_path) + os.makedirs(dir_path, exist_ok=True) + with open(file_path, "r") as f: + content = f.read() + if asdf_config in content: + return + with open(file_path, "a") as f: + f.write(f"\n{asdf_config}\n") diff --git a/src/zrb/builtin/setup/system/latex/ubuntu.py b/src/zrb/builtin/setup/system/latex/ubuntu.py new file mode 100644 index 00000000..f3874115 --- /dev/null +++ b/src/zrb/builtin/setup/system/latex/ubuntu.py @@ -0,0 +1,18 @@ +from zrb.builtin.group import setup_latex_group +from zrb.builtin.setup.system.ubuntu import setup_ubuntu +from zrb.task.cmd_task import CmdTask + +setup_latex_on_ubuntu = setup_latex_group.add_task( + CmdTask( + name="setup-latex-on-ubuntu", + description="🐧 Setup LaTeX on Ubuntu", + cmd=[ + "sudo apt install -y \\", + "texlive-full texlive-latex-base texlive-fonts-recommended \\", + "texlive-fonts-extra texlive-latex-extra", + ], + render_cmd=False, + ), + alias="ubuntu", +) +setup_ubuntu >> setup_latex_on_ubuntu diff --git a/src/zrb/builtin/setup/system/ubuntu.py b/src/zrb/builtin/setup/system/ubuntu.py new file mode 100644 index 00000000..43c6ce5d --- /dev/null +++ b/src/zrb/builtin/setup/system/ubuntu.py @@ -0,0 +1,28 @@ +from zrb.builtin.group import setup_system_group +from zrb.task.cmd_task import CmdTask + +update_ubuntu = CmdTask(name="update-ubuntu", cmd="sudo apt update", render_cmd=False) + +upgrade_todo = CmdTask( + name="upgrade-ubuntu", cmd="sudo apt upgrade -y", render_cmd=False +) +update_ubuntu >> upgrade_todo + +setup_ubuntu = setup_system_group.add_task( + CmdTask( + name="setup-ubuntu", + description="🐧 Setup ubuntu", + cmd=[ + "sudo apt install -y \\", + "build-essential python3-distutils libssl-dev zlib1g-dev \\" + "libbz2-dev libreadline-dev libsqlite3-dev libpq-dev python3-dev \\", + "llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libffi-dev \\", + "liblzma-dev python3-openssl libblas-dev liblapack-dev rustc \\", + "golang gfortran fd-find ripgrep wget curl git ncat zip unzip \\", + "cmake make tree tmux zsh neovim xdotool xsel", + ], + render_cmd=False, + ), + alias="ubuntu", +) +upgrade_todo >> setup_ubuntu diff --git a/src/zrb/config.py b/src/zrb/config.py index 446dd975..98065521 100644 --- a/src/zrb/config.py +++ b/src/zrb/config.py @@ -49,6 +49,9 @@ def _get_log_level(level: str) -> int: LOAD_BUILTIN = to_boolean(os.getenv("ZRB_LOAD_BUILTIN", "1")) ENV_PREFIX = os.getenv("ZRB_ENV", "") SHOW_PROMPT = to_boolean(os.getenv("ZRB_SHOW_PROMPT", "1")) +WARN_UNRECOMMENDED_COMMAND = to_boolean( + os.getenv("ZRB_WARN_UNRECOMMENDED_COMMAND", "1") +) SESSION_LOG_DIR = os.getenv( "ZRB_SESSION_LOG_DIR", os.path.expanduser(os.path.join("~", ".zrb-session")) ) diff --git a/src/zrb/task/cmd_task.py b/src/zrb/task/cmd_task.py index abef9470..b3798ee6 100644 --- a/src/zrb/task/cmd_task.py +++ b/src/zrb/task/cmd_task.py @@ -5,7 +5,7 @@ from zrb.attr.type import BoolAttr, IntAttr, StrAttr from zrb.cmd.cmd_result import CmdResult from zrb.cmd.cmd_val import AnyCmdVal, CmdVal, SingleCmdVal -from zrb.config import DEFAULT_SHELL +from zrb.config import DEFAULT_SHELL, WARN_UNRECOMMENDED_COMMAND from zrb.context.any_context import AnyContext from zrb.env.any_env import AnyEnv from zrb.input.any_input import AnyInput @@ -33,6 +33,7 @@ def __init__( remote_host: StrAttr | None = None, render_remote_host: bool = True, remote_port: IntAttr | None = None, + render_remote_port: bool = True, remote_user: StrAttr | None = None, render_remote_user: bool = True, remote_password: StrAttr | None = None, @@ -43,6 +44,7 @@ def __init__( render_cmd: bool = True, cwd: str | None = None, render_cwd: bool = True, + warn_unrecommended_command: bool | None = None, max_output_line: int = 1000, max_error_line: int = 1000, execute_condition: BoolAttr = True, @@ -84,6 +86,7 @@ def __init__( self._remote_host = remote_host self._render_remote_host = render_remote_host self._remote_port = remote_port + self._render_remote_port = render_remote_port self._remote_user = remote_user self._render_remote_user = render_remote_user self._remote_password = remote_password @@ -96,6 +99,7 @@ def __init__( self._render_cwd = render_cwd self._max_output_line = max_output_line self._max_error_line = max_error_line + self._should_warn_unrecommended_command = warn_unrecommended_command async def _exec_action(self, ctx: AnyContext) -> CmdResult: """Turn _cmd attribute into subprocess.Popen and execute it as task's action. @@ -116,7 +120,8 @@ async def _exec_action(self, ctx: AnyContext) -> CmdResult: env_map = self.__get_env_map(ctx) ctx.log_debug(f"Environment map: {env_map}") cmd_process = None - self.__warn_unrecommended_commands(ctx, shell, cmd_script) + if self._get_should_warn_unrecommended_commands(): + self._check_unrecommended_commands(ctx, shell, cmd_script) try: ctx.log_info("Running script") cmd_process = await asyncio.create_subprocess_exec( @@ -152,10 +157,15 @@ async def _exec_action(self, ctx: AnyContext) -> CmdResult: if cmd_process is not None and cmd_process.returncode is None: cmd_process.terminate() - def __warn_unrecommended_commands( + def _get_should_warn_unrecommended_commands(self): + if self._should_warn_unrecommended_command is None: + return WARN_UNRECOMMENDED_COMMAND + return self._should_warn_unrecommended_command + + def _check_unrecommended_commands( self, ctx: AnyContext, shell: str, cmd_script: str ): - if "bash" in shell or "zsh" in shell: + if shell.endswith("bash") or shell.endswith("zsh"): unrecommended_commands = check_unrecommended_commands(cmd_script) if unrecommended_commands: ctx.log_warning("The script contains unrecommended commands") @@ -207,7 +217,9 @@ def _get_remote_host(self, ctx: AnyContext) -> str: ) def _get_remote_port(self, ctx: AnyContext) -> int: - return get_int_attr(ctx, self._remote_port, 22, auto_render=True) + return get_int_attr( + ctx, self._remote_port, 22, auto_render=self._render_remote_port + ) def _get_remote_user(self, ctx: AnyContext) -> str: return get_str_attr( diff --git a/src/zrb/task/rsync_task.py b/src/zrb/task/rsync_task.py index bea370f1..77d840ab 100644 --- a/src/zrb/task/rsync_task.py +++ b/src/zrb/task/rsync_task.py @@ -24,13 +24,13 @@ def __init__( remote_host: StrAttr | None = None, auto_render_remote_host: bool = True, remote_port: IntAttr | None = None, - auto_render_remote_port: bool = True, + render_remote_port: bool = True, remote_user: StrAttr | None = None, - auto_render_remote_user: bool = True, + render_remote_user: bool = True, remote_password: StrAttr | None = None, - auto_render_remote_password: bool = True, + render_remote_password: bool = True, remote_ssh_key: StrAttr | None = None, - auto_render_remote_ssh_key: bool = True, + render_remote_ssh_key: bool = True, remote_source_path: StrAttr | None = None, render_remote_source_path: bool = True, remote_destination_path: StrAttr | None = None, @@ -63,13 +63,13 @@ def __init__( remote_host=remote_host, render_remote_host=auto_render_remote_host, remote_port=remote_port, - auto_render_remote_port=auto_render_remote_port, + auto_render_remote_port=render_remote_port, remote_user=remote_user, - render_remote_user=auto_render_remote_user, + render_remote_user=render_remote_user, remote_password=remote_password, - render_remote_password=auto_render_remote_password, + render_remote_password=render_remote_password, remote_ssh_key=remote_ssh_key, - render_remote_ssh_key=auto_render_remote_ssh_key, + render_remote_ssh_key=render_remote_ssh_key, cwd=cwd, render_cwd=auto_render_cwd, max_output_line=max_output_line, diff --git a/src/zrb/util/string/format.py b/src/zrb/util/string/format.py index 8c242505..b39b21e9 100644 --- a/src/zrb/util/string/format.py +++ b/src/zrb/util/string/format.py @@ -2,8 +2,12 @@ def fstring_format(template: str, data: dict[str, Any]) -> str: + # Escape any backslashes and quotes in the template + escaped_template = template.replace("\\", "\\\\").replace('"', '\\"') + # Construct the f-string expression + f_string_expr = f'f"{escaped_template}"' # Safely evaluate the template as a Python expression try: - return eval(f'f"""{template}"""', {}, data) + return eval(f_string_expr, {}, data) except Exception as e: raise ValueError(f"Failed to parse template: {template}: {e}")