1# @Author: Kristinita 2# @Date: 2025-03-17 20:26:53 3# @Last Modified by: Kristinita 4# @Last Modified time: 2025-10-30 20:08:44 5"""[OVERVIEW] Install binaries of non-Node and non-Python packages from GitHub releases. 6 7###################### 8# gh-release-install # 9###################### 10[OVERVIEW] gh-release-install — Python CLI utility for installation data from GitHub releases. 11https://github.com/jooola/gh-release-install 12 13[PURPOSE] Amazing Grace is the Node.js + Python project, but it also uses dependencies 14that not possible to install use Node.js and Python. 15One of the main principles of Amazing Grace is simplicity. 16So that users don’t have to pre-install any dependencies except Git, Node.js and Python, 17I installed non-Node and non-Python CLI dependencies use gh-release-install. 18 19[INFO] gh-release-install save binaries from GitHub releases to the PATH directory of Pipenv virtual environment. 20To launch commands, the user should use the syntax “pipenv run command”, 21for example, “pipenv run fd” or “pipenv run tidy”. 22 23 24[NOTE] gh-release-install hasn’t configuration file, “github_release_install.py” script is required: 25https://github.com/jooola/gh-release-install/issues/212 26 27[NOTE] As of March 2025, I can’t find good cross-platform package manager for GitHub releases. 28“fetch-github-release” and “install-release” doesn’t support Windows: 29https://github.com/terascope/fetch-github-release/issues/355 30https://github.com/Rishang/install-release 31I can’t run “grab-github-release”: 32https://github.com/prantlf/grab-github-release/issues/2 33""" 34from pathlib import Path 35from subprocess import Popen 36from sys import exit as sysexit 37from sys import platform, stderr 38 39# [LEARN][PYTHON] “sys.platform” — check current operating system. 40# Windows always has the value “win32”, macOS — “darwin”, Linux — “linux”: 41# https://docs.python.org/3/library/sys.html#sys.platform 42KIRA_PLATFORM_MACOS = "darwin" 43KIRA_PLATFORM_WINDOWS = "win32" 44 45 46def kira_save_binaries_from_github_releases() -> None: 47 """[FUNCTION_DESCRIPTION] Save non-Node/non-Python binaries from GitHub releases into Pipenv virtual environment. 48 49 Steps: 50 1. Set gh-release-install commands. 51 2. Run gh-release-install commands. 52 3. Exit “github_release_install.py” with the same exit code as exit codes of gh-release-install commands. 53 """ 54 kira_current_os_is_windows = platform == KIRA_PLATFORM_WINDOWS 55 kira_current_os_is_macos = platform == KIRA_PLATFORM_MACOS 56 57 # [NOTE] Pipenv has different paths for executable binaries for Linux/macOS and Windows. 58 # “.venv/bin” — is the path for Linux and macOS, “.venv/Scripts” — the path for Windows. 59 kira_destination_path = ".venv/Scripts" if kira_current_os_is_windows else ".venv/bin" 60 61 # [PURPOSE] Save binaries from unofficial HTML Tidy 5.9.20 release: 62 # https://github.com/Kristinita/tidy-html5/releases/tag/5.9.20 63 # Release contains solely binaries, not archives. 64 # 65 # [TIDY][NOTE] I created my own HTML Tidy release, because Tidy is no longer maintained, 66 # and the latest official Tidy versions contains critical bug: 67 # https://github.com/Kristinita/tidy-html5#2-why-it-was-created 68 # 69 # 70 # [INFO] Linux and macOS CLI command: 71 # gh-release-install Kristinita/tidy-html5 tidy .venv/bin --verbose 72 # 73 # Windows CLI command: 74 # gh-release-install Kristinita/tidy-html5 tidy.exe .venv/Scripts --verbose 75 kira_tidy_repository = "Kristinita/tidy-html5" 76 kira_tidy_executable_file = "tidy.exe" if kira_current_os_is_windows else "tidy" 77 78 # [LEARN][PYTHON][PERFORMANCE] Indexing tuples is faster than indexing lists. 79 # Use tuples, not lists, if you don’t need to modify your list: 80 # https://github.com/tonybaloney/perflint#w8301--use-tuple-instead-of-list-for-a-non-mutated-sequence-use-tuple-over-list 81 kira_gh_release_install_command_for_tidy = ( 82 "pipenv", "run", 83 "gh-release-install", kira_tidy_repository, kira_tidy_executable_file, kira_destination_path, 84 "--verbose") 85 86 # [PURPOSE] Save binaries from the latest fd release: 87 # https://github.com/sharkdp/fd/releases 88 # Archives unpacking is required. 89 # 90 # 91 # [INFO] Linux CLI command: 92 # gh-release-install sharkdp/fd "fd-{tag}-x86_64-unknown-linux-gnu.tar.gz" 93 # --extract "fd-{tag}-x86_64-unknown-linux-gnu/fd" .venv/bin/fd --verbose 94 # 95 # [INFO] macOS CLI command: 96 # gh-release-install sharkdp/fd "fd-{tag}-x86_64-apple-darwin.tar.gz" 97 # --extract "fd-{tag}-x86_64-apple-darwin.tar.gz/fd" .venv/bin/fd --verbose 98 # 99 # [INFO] Windows CLI command:100 # gh-release-install sharkdp/fd fd-{tag}-x86_64-pc-windows-gnu.zip101 # --extract fd-{tag}-x86_64-pc-windows-gnu/fd.exe .venv/Scripts/fd.exe --verbose102 kira_fd_repository = "sharkdp/fd"103 kira_fd_executable_file = "fd"104105 if kira_current_os_is_windows:106 kira_fd_archive = "fd-{tag}-x86_64-pc-windows-gnu.zip"107 kira_fd_extract = "fd-{tag}-x86_64-pc-windows-gnu/fd.exe"108 kira_fd_executable_file = "fd.exe"109110 elif kira_current_os_is_macos:111 kira_fd_archive = "fd-{tag}-x86_64-apple-darwin.tar.gz"112 kira_fd_extract = "fd-{tag}-x86_64-apple-darwin/fd"113114 else:115 kira_fd_archive = "fd-{tag}-x86_64-unknown-linux-gnu.tar.gz"116 kira_fd_extract = "fd-{tag}-x86_64-unknown-linux-gnu/fd"117118 # [LEARN][PYTHON][REFACTORING] Use “Path()” object instead of “os.path.join” for joining strings:119 # https://github.com/dosisod/refurb/blob/master/docs/checks.md#furb147-no-path-join120 kira_gh_release_install_command_for_fd = (121 "pipenv", "run",122 "gh-release-install", kira_fd_repository, kira_fd_archive,123 "--extract", kira_fd_extract, Path(kira_destination_path, kira_fd_executable_file),124 "--verbose")125126 # [PURPOSE] Run gh-release-install commands127 #128 # [LEARN][PYTHON] “subprocess.call” a blocking function — it waits the execution of a subprocess by default,129 # “subprocess.Popen” — isn’t a blocking function.130 # https://stackoverflow.com/a/59615896/5951529131 #132 # “subprocess.check_output” also a blocking function:133 # https://stackoverflow.com/a/40766307/5951529134 #135 # “subprocess.Popen” required, “subprocess.call” and “subprocess.check_output”136 # don’t allow running subprocesses in parallel.137 #138 # [PYLINT][NOTE] Use “subprocess.Popen” with “with” keyword to avoid “R1732” Pylint warning:139 # https://pylint.pycqa.org/en/latest/user_guide/messages/refactor/consider-using-with.html140 with Popen(kira_gh_release_install_command_for_tidy) as kira_tidy_process, Popen(141 kira_gh_release_install_command_for_fd) as kira_fd_process:142143 # [PURPOSE][LEARN][PYTHON] Wait until subprocesses are finished and return “returncode”:144 # https://stackoverflow.com/a/15108096/5951529145 # https://docs.python.org/3/library/subprocess.html#subprocess.Popen.wait146 kira_tidy_process.wait()147 kira_fd_process.wait()148149 # [PURPOSE][NOTE] “github_release_install.py” exits with exit code 0, if we’re not processing exit codes.150 # Desired behavior — if “kira_tidy_process” and/or “kira_fd_process” exited with non-zero exit code,151 # “github_release_install” should also exit with non-zero exit code.152 # The code below realize this behavior.153 #154 # [LEARN][PYTHON] “returncode” — get exit code of a subprocess:155 # https://stackoverflow.com/a/5631819/5951529156 kira_tidy_exit_code = kira_tidy_process.returncode157 kira_fd_exit_code = kira_fd_process.returncode158159 if kira_tidy_exit_code != 0:160161 # [LEARN][PYTHON] Prefer “stderr.write()” or “stdout.write()” to “print()” in a production code:162 # https://stackoverflow.com/a/2570272/5951529163 # https://stackoverflow.com/a/3264118/5951529164 stderr.write(f"Error: Tidy installation failed with exit code {kira_tidy_exit_code}")165 if kira_fd_exit_code != 0:166 stderr.write(f"Error: Fd installation failed with exit code {kira_fd_exit_code}")167168 if kira_tidy_exit_code != 0 or kira_fd_exit_code != 0:169170 # [LEARN][PYTHON][NOTE] “sys.exit()”, not “exit()” should be used in real Python scripts:171 # https://stackoverflow.com/a/6501134/5951529172 # https://stackoverflow.com/a/19747557/5951529173 sysexit(1)174 else:175 sysexit(0)176177178kira_save_binaries_from_github_releases()