Last active
March 1, 2026 16:30
-
-
Save holly/fdab4bade1955de90467ce72b586d02d to your computer and use it in GitHub Desktop.
Automatic installation script for ripgrep, bat, fd, glow, duf, eza, shellcheck, lua-language-server (using standard modules only)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Automatic installation script for ripgrep, bat, fd, glow, duf, eza, shellcheck, lua-language-server (using standard modules only) | |
| Fetches the latest versions from GitHub API, compares with existing versions, and updates them. | |
| Also automatically installs fish shell completion files. | |
| """ | |
| import os | |
| import sys | |
| import json | |
| import re | |
| import urllib.request | |
| import urllib.error | |
| import subprocess | |
| import tarfile | |
| import shutil | |
| import platform | |
| import stat | |
| from pathlib import Path | |
| # Configuration | |
| GITHUB_API_BASE_URL = "https://api.github.com/repos" | |
| GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN") # Get GitHub token from environment variable | |
| DOWNLOAD_DIR = Path.home() / "src" | |
| INSTALL_DIR = Path.home() / ".local" / "bin" | |
| TEMP_DIR_BASE = Path.home() / "tmp" | |
| FISH_COMPLETIONS_DIR = Path.home() / ".config" / "fish" / "completions" | |
| # Tool configurations | |
| TOOLS = [ | |
| { | |
| "name": "ripgrep", | |
| "owner": "BurntSushi", | |
| "binary_name": "rg", | |
| "version_pattern": r"ripgrep (\d+\.\d+\.\d+)", | |
| "completion_path": "complete/rg.fish", | |
| "file_pattern": "ripgrep-{version}-{arch}-unknown-linux-musl.tar.gz", | |
| "has_subdir": True | |
| }, | |
| { | |
| "name": "bat", | |
| "owner": "sharkdp", | |
| "binary_name": "bat", | |
| "version_pattern": r"bat (\d+\.\d+\.\d+)", | |
| "completion_path": "autocomplete/bat.fish", | |
| "file_pattern": "bat-v{version}-{arch}-unknown-linux-musl.tar.gz", | |
| "has_subdir": True | |
| }, | |
| { | |
| "name": "fd", | |
| "owner": "sharkdp", | |
| "binary_name": "fd", | |
| "version_pattern": r"fd (\d+\.\d+\.\d+)", | |
| "completion_path": "autocomplete/fd.fish", | |
| "file_pattern": "fd-v{version}-{arch}-unknown-linux-musl.tar.gz", | |
| "has_subdir": True | |
| }, | |
| { | |
| "name": "glow", | |
| "owner": "charmbracelet", | |
| "binary_name": "glow", | |
| "version_pattern": r"glow version (\d+\.\d+\.\d+)", | |
| "completion_path": "completions/glow.fish", | |
| "file_pattern": "glow_{version}_Linux_{arch}.tar.gz", | |
| "has_subdir": True | |
| }, | |
| { | |
| "name": "duf", | |
| "owner": "muesli", | |
| "binary_name": "duf", | |
| "version_pattern": r"duf (\d+\.\d+\.\d+)", | |
| "completion_path": "", # No fish completion file | |
| "file_pattern": "duf_{version}_linux_{arch}.tar.gz", | |
| "has_subdir": False # duf has no subdirectory | |
| }, | |
| { | |
| "name": "eza", | |
| "owner": "eza-community", | |
| "binary_name": "eza", | |
| "version_pattern": r"v(\d+\.\d+\.\d+)", # Matches format like v0.23.4 | |
| "completion_path": "", # No completion file | |
| "file_pattern": "eza_{arch}-unknown-linux-musl.tar.gz", | |
| "has_subdir": False | |
| }, | |
| { | |
| "name": "shellcheck", | |
| "owner": "koalaman", | |
| "binary_name": "shellcheck", | |
| "version_pattern": r"version: (\d+\.\d+\.\d+)", # Fixed: matches "version: 0.11.0" | |
| "completion_path": "", # No fish completion file | |
| "file_pattern": "shellcheck-v{version}.linux.{arch}.tar.gz", | |
| "has_subdir": True, | |
| "install_type": "extract_to_local" # Special install type: extract to ~/.local | |
| }, | |
| { | |
| "name": "lua-language-server", | |
| "owner": "LuaLS", | |
| "binary_name": "lua-language-server", | |
| "version_pattern": r"(\d+\.\d+\.\d+)", # Fixed: matches "3.17.1" | |
| "completion_path": "", # No fish completion file | |
| "file_pattern": "lua-language-server-{version}-linux-{lua_arch}.tar.gz", # Fixed: use lua_arch | |
| "has_subdir": True, | |
| "install_type": "extract_to_custom_dir", # Special install type: extract to custom directory | |
| "custom_dir": "lua-language-server", | |
| "binary_path": "bin/lua-language-server" # Path to binary inside extracted directory | |
| } | |
| ] | |
| def get_system_architecture(): | |
| """Get system architecture""" | |
| machine = platform.machine().lower() | |
| if machine in ("x86_64", "amd64"): | |
| return { | |
| "standard": "x86_64", | |
| "lua": "x64" | |
| } | |
| elif machine in ("aarch64", "arm64"): | |
| return { | |
| "standard": "aarch64", | |
| "lua": "arm64" | |
| } | |
| else: | |
| print(f"Warning: Unsupported architecture {machine} detected. Using x86_64.") | |
| return { | |
| "standard": "x86_64", | |
| "lua": "x64" | |
| } | |
| def main(): | |
| if len(sys.argv) != 2: | |
| print("Usage: ./tools_install.py [install|uninstall]") | |
| sys.exit(1) | |
| action = sys.argv[1] | |
| if action == "install": | |
| print("Starting automatic installation of ripgrep, bat, fd, glow, duf, eza, shellcheck, lua-language-server...") | |
| install_tools() | |
| elif action == "uninstall": | |
| print("Starting uninstallation of ripgrep, bat, fd, glow, duf, eza, shellcheck, lua-language-server...") | |
| uninstall_tools() | |
| else: | |
| print("Invalid action. Use 'install' or 'uninstall'.") | |
| sys.exit(1) | |
| def install_tools(): | |
| # Check if GitHub token is set | |
| if GITHUB_TOKEN: | |
| print("GitHub token is set") | |
| else: | |
| print("Warning: GitHub token is not set. You may encounter rate limits.") | |
| print("It's recommended to set a GitHub token in the GITHUB_TOKEN environment variable.") | |
| print("You can create a token at https://github.com/settings/tokens") | |
| # Get system architecture | |
| arch_info = get_system_architecture() | |
| print(f"System architecture: {arch_info['standard']}") | |
| # Create directories | |
| ensure_directories() | |
| try: | |
| # Install each tool | |
| for tool in TOOLS: | |
| print(f"\n--- Installing {tool['name']} ---") | |
| install_tool(tool, arch_info) | |
| print("\nInstallation completed") | |
| except Exception as e: | |
| print(f"An error occurred: {e}") | |
| sys.exit(1) | |
| def uninstall_tools(): | |
| try: | |
| for tool in TOOLS: | |
| print(f"\n--- Uninstalling {tool['name']} ---") | |
| uninstall_tool(tool) | |
| print("\nUninstallation completed") | |
| except Exception as e: | |
| print(f"An error occurred: {e}") | |
| sys.exit(1) | |
| def uninstall_tool(tool): | |
| """Uninstall the specified tool""" | |
| binary_path = INSTALL_DIR / tool["binary_name"] | |
| # Remove binary | |
| if binary_path.exists(): | |
| binary_path.unlink() | |
| print(f"Removed binary: {binary_path}") | |
| # Remove fish completion file | |
| if tool.get("completion_path", ""): | |
| completion_file = FISH_COMPLETIONS_DIR / f"{tool['binary_name']}.fish" | |
| if completion_file.exists(): | |
| completion_file.unlink() | |
| print(f"Removed fish completion file: {completion_file}") | |
| # Special handling for shellcheck | |
| if tool["name"] == "shellcheck": | |
| local_dir = Path.home() / ".local" | |
| shellcheck_dir = local_dir / f"shellcheck-v{get_current_version(tool['binary_name'], tool['version_pattern'])}" | |
| if shellcheck_dir.exists(): | |
| shutil.rmtree(shellcheck_dir) | |
| print(f"Removed shellcheck directory: {shellcheck_dir}") | |
| # Special handling for lua-language-server | |
| if tool["name"] == "lua-language-server": | |
| custom_dir = Path.home() / ".local" / tool["custom_dir"] | |
| if custom_dir.exists(): | |
| shutil.rmtree(custom_dir) | |
| print(f"Removed lua-language-server directory: {custom_dir}") | |
| def ensure_directories(): | |
| """Create necessary directories""" | |
| DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True) | |
| INSTALL_DIR.mkdir(parents=True, exist_ok=True) | |
| FISH_COMPLETIONS_DIR.mkdir(parents=True, exist_ok=True) | |
| # Create temporary directories for each tool | |
| for tool in TOOLS: | |
| temp_dir = TEMP_DIR_BASE / f"{tool['name']}_install" | |
| temp_dir.mkdir(parents=True, exist_ok=True) | |
| def install_tool(tool, arch_info): | |
| """Install the specified tool""" | |
| # Get latest version info | |
| api_url = f"{GITHUB_API_BASE_URL}/{tool['owner']}/{tool['name']}/releases/latest" | |
| # Use appropriate architecture for the tool | |
| if tool['name'] == 'lua-language-server': | |
| arch = arch_info['lua'] | |
| else: | |
| arch = arch_info['standard'] | |
| latest_version, download_url = get_latest_version_info(api_url, tool['name'], tool['file_pattern'], arch) | |
| print(f"Latest {tool['name']} version: {latest_version}") | |
| # Check current version | |
| current_version = get_current_version(tool['binary_name'], tool['version_pattern']) | |
| print(f"Current {tool['name']} version: {current_version}") | |
| # Compare versions | |
| if current_version and compare_versions(current_version, latest_version) >= 0: | |
| print(f"Latest version of {tool['name']} ({latest_version}) is already installed") | |
| return | |
| # Download and install | |
| download_and_install(tool, latest_version, download_url, arch) | |
| print(f"Installation of {tool['name']} {latest_version} completed") | |
| def get_latest_version_info(api_url, tool_name, file_pattern, arch): | |
| """Get latest version info and download URL from GitHub API""" | |
| try: | |
| # Set request headers | |
| req = urllib.request.Request(api_url) | |
| req.add_header('User-Agent', f'{tool_name}-installer/1.0') | |
| req.add_header('Accept', 'application/vnd.github.v3+json') | |
| # Add auth header if GitHub token is available | |
| if GITHUB_TOKEN: | |
| req.add_header('Authorization', f'token {GITHUB_TOKEN}') | |
| # Make API request | |
| with urllib.request.urlopen(req) as response: | |
| data = json.loads(response.read().decode('utf-8')) | |
| version = data['tag_name'].lstrip('v') # 'v15.1.0' -> '15.1.0' | |
| # Construct file pattern based on architecture | |
| expected_filename = file_pattern.format(version=version, arch=arch, lua_arch=arch) | |
| # Get release file URL | |
| download_url = None | |
| for asset in data['assets']: | |
| if asset['name'] == expected_filename: | |
| download_url = asset['browser_download_url'] | |
| break | |
| if not download_url: | |
| print(f"Warning: {expected_filename} not found") | |
| print("Available assets:") | |
| for asset in data['assets']: | |
| print(f" - {asset['name']}") | |
| raise Exception(f"No suitable release file found: {expected_filename}") | |
| return version, download_url | |
| except urllib.error.HTTPError as e: | |
| print(f"HTTP error occurred: {e.code} {e.reason}") | |
| if e.code == 403: | |
| print("You may have hit GitHub API rate limits") | |
| if not GITHUB_TOKEN: | |
| print("Setting a GitHub token in GITHUB_TOKEN environment variable will increase your rate limit") | |
| print("You can create a token at https://github.com/settings/tokens") | |
| sys.exit(1) | |
| except urllib.error.URLError as e: | |
| print(f"URL error occurred: {e.reason}") | |
| sys.exit(1) | |
| except json.JSONDecodeError: | |
| print("Failed to parse JSON") | |
| sys.exit(1) | |
| except Exception as e: | |
| print(f"Error during API access: {e}") | |
| sys.exit(1) | |
| def get_current_version(binary_name, version_pattern): | |
| """Get the version of the currently installed tool""" | |
| try: | |
| result = subprocess.run([binary_name, '--version'], capture_output=True, text=True, check=True) | |
| # Combine stdout and stderr | |
| output = result.stdout + result.stderr | |
| # Process line by line to handle multiline output | |
| lines = output.split('\n') | |
| # Try version pattern on each line | |
| for line in lines: | |
| match = re.search(version_pattern, line) | |
| if match: | |
| return match.group(1) | |
| # For debugging: show actual output | |
| print(f"Debug: Output of {binary_name} --version:") | |
| print(f"stdout: {result.stdout}") | |
| print(f"stderr: {result.stderr}") | |
| return None | |
| except (subprocess.CalledProcessError, FileNotFoundError): | |
| return None | |
| def compare_versions(v1, v2): | |
| """Compare version strings (returns -1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2)""" | |
| def normalize(v): | |
| return [int(x) for x in v.split('.')] | |
| v1_parts = normalize(v1) | |
| v2_parts = normalize(v2) | |
| # Match part counts | |
| max_len = max(len(v1_parts), len(v2_parts)) | |
| v1_parts.extend([0] * (max_len - len(v1_parts))) | |
| v2_parts.extend([0] * (max_len - len(v2_parts))) | |
| for i in range(max_len): | |
| if v1_parts[i] < v2_parts[i]: | |
| return -1 | |
| elif v1_parts[i] > v2_parts[i]: | |
| return 1 | |
| return 0 | |
| def safe_extract(tar, path): | |
| """Safely extract tar file (security measures)""" | |
| import pathlib | |
| # Get absolute path of extraction destination | |
| extract_path = pathlib.Path(path).resolve() | |
| for member in tar.getmembers(): | |
| # Normalize member path | |
| try: | |
| member_path = pathlib.Path(member.name) | |
| # Calculate absolute path of extraction destination | |
| full_path = (extract_path / member_path).resolve() | |
| # Verify extraction destination is within specified directory | |
| if extract_path not in full_path.parents and full_path != extract_path: | |
| print(f"Warning: Skipping member attempting directory traversal: {member.name}") | |
| continue | |
| except (ValueError, RuntimeError): | |
| print(f"Warning: Skipping member with invalid path: {member.name}") | |
| continue | |
| # Be especially careful with symbolic links | |
| if member.issym() or member.islnk(): | |
| print(f"Warning: Skipping symbolic link: {member.name}") | |
| continue | |
| # Keep original file mode for executable files | |
| if member.isfile() and member.mode & stat.S_IXUSR: | |
| # This is an executable file, keep its execute permissions | |
| pass | |
| elif member.isfile(): | |
| # Restrict file mode to safe values for non-executable files | |
| member.mode = member.mode & 0o644 | |
| # Use filter parameter for safe extraction in Python 3.8+ | |
| try: | |
| tar.extract(member, path=path, filter='data') | |
| except AttributeError: | |
| # For Python versions below 3.8, use traditional extraction | |
| tar.extract(member, path=path) | |
| def install_fish_completion(tool, version, temp_dir, arch): | |
| """Install fish shell completion file""" | |
| # If no completion file is configured, do nothing | |
| if not tool.get('completion_path', ''): | |
| print(f"No fish completion file available for {tool['name']}") | |
| return | |
| # Guess extracted directory name | |
| if tool['has_subdir']: | |
| if tool['name'] == 'ripgrep': | |
| extracted_dir = temp_dir / f"ripgrep-{version}-{arch}-unknown-linux-musl" | |
| elif tool['name'] == 'bat': | |
| extracted_dir = temp_dir / f"bat-v{version}-{arch}-unknown-linux-musl" | |
| elif tool['name'] == 'fd': | |
| extracted_dir = temp_dir / f"fd-v{version}-{arch}-unknown-linux-musl" | |
| elif tool['name'] == 'glow': | |
| extracted_dir = temp_dir / f"glow_{version}_Linux_{arch}" | |
| src_file = extracted_dir / tool['completion_path'] | |
| else: | |
| # If no subdirectory, it's directly in temp_dir | |
| src_file = temp_dir / tool['completion_path'] | |
| dest_file = FISH_COMPLETIONS_DIR / f"{tool['binary_name']}.fish" | |
| if src_file.exists(): | |
| print(f"Installing fish shell completion for {tool['name']}: {dest_file}") | |
| shutil.copy2(str(src_file), str(dest_file)) | |
| print(f"Fish shell completion for {tool['name']} installed successfully") | |
| else: | |
| print(f"Completion file for {tool['name']} not found: {src_file}") | |
| def download_and_install(tool, version, download_url, arch): | |
| """Download and install the tool""" | |
| # Download | |
| print(f"Downloading {tool['name']}: {download_url}") | |
| # Get filename | |
| filename = download_url.split('/')[-1] | |
| download_path = DOWNLOAD_DIR / filename | |
| # Temporary directory | |
| temp_dir = TEMP_DIR_BASE / f"{tool['name']}_install" | |
| try: | |
| # Download | |
| req = urllib.request.Request(download_url) | |
| req.add_header('User-Agent', f"{tool['name']}-installer/1.0") | |
| with urllib.request.urlopen(req) as response: | |
| # Get file size | |
| file_size = int(response.headers.get('Content-Length', 0)) | |
| # Show download progress | |
| with open(download_path, 'wb') as f: | |
| downloaded = 0 | |
| while True: | |
| chunk = response.read(8192) | |
| if not chunk: | |
| break | |
| f.write(chunk) | |
| downloaded += len(chunk) | |
| # Show progress | |
| if file_size > 0: | |
| percent = (downloaded / file_size) * 100 | |
| print(f"\rDownloading {tool['name']}: {percent:.1f}%", end='') | |
| else: | |
| print(f"\rDownloading {tool['name']}: {downloaded} bytes", end='') | |
| print(f"\n{tool['name']} download completed") | |
| # Extract (with security measures) | |
| print(f"Extracting {tool['name']}...") | |
| with tarfile.open(download_path, 'r:gz') as tar: | |
| safe_extract(tar, temp_dir) | |
| # Show extracted directory structure | |
| print(f"{tool['name']} extracted directory structure:") | |
| for root, dirs, files in os.walk(temp_dir): | |
| level = root.replace(str(temp_dir), '').count(os.sep) | |
| indent = ' ' * 2 * level | |
| print(f"{indent}{os.path.basename(root)}/") | |
| subindent = ' ' * 2 * (level + 1) | |
| for file in files: | |
| print(f"{subindent}{file}") | |
| # Handle different installation types | |
| install_type = tool.get('install_type', 'standard') | |
| if install_type == 'extract_to_local': | |
| # Special handling for shellcheck: extract to ~/.local | |
| install_shellcheck(tool, version, temp_dir, arch) | |
| elif install_type == 'extract_to_custom_dir': | |
| # Special handling for lua-language-server: extract to custom directory | |
| install_lua_language_server(tool, version, temp_dir, arch) | |
| else: | |
| # Standard installation | |
| install_standard(tool, version, temp_dir, arch) | |
| # Cleanup | |
| shutil.rmtree(temp_dir) | |
| download_path.unlink() | |
| print(f"Successfully installed {tool['name']}") | |
| except urllib.error.HTTPError as e: | |
| print(f"\nHTTP error occurred while downloading {tool['name']}: {e.code} {e.reason}") | |
| cleanup(download_path, temp_dir) | |
| sys.exit(1) | |
| except urllib.error.URLError as e: | |
| print(f"\nURL error occurred while downloading {tool['name']}: {e.reason}") | |
| cleanup(download_path, temp_dir) | |
| sys.exit(1) | |
| except Exception as e: | |
| print(f"\nError occurred while downloading/installing {tool['name']}: {e}") | |
| cleanup(download_path, temp_dir) | |
| sys.exit(1) | |
| def install_standard(tool, version, temp_dir, arch): | |
| """Standard installation method for most tools""" | |
| # Identify binary file path | |
| if tool['has_subdir']: | |
| # If there's a subdirectory | |
| if tool['name'] == 'ripgrep': | |
| extracted_dir = temp_dir / f"ripgrep-{version}-{arch}-unknown-linux-musl" | |
| elif tool['name'] == 'bat': | |
| extracted_dir = temp_dir / f"bat-v{version}-{arch}-unknown-linux-musl" | |
| elif tool['name'] == 'fd': | |
| extracted_dir = temp_dir / f"fd-v{version}-{arch}-unknown-linux-musl" | |
| elif tool['name'] == 'glow': | |
| extracted_dir = temp_dir / f"glow_{version}_Linux_{arch}" | |
| elif tool['name'] == 'eza': | |
| extracted_dir = temp_dir / f"eza_v{version}_Linux_{arch}" | |
| binary_path = extracted_dir / tool['binary_name'] | |
| else: | |
| # If no subdirectory (like duf) | |
| binary_path = temp_dir / tool['binary_name'] | |
| if not binary_path.exists(): | |
| print(f"Error: {tool['binary_name']} binary not found") | |
| # Show all extracted files | |
| print("Extracted files:") | |
| for root, dirs, files in os.walk(temp_dir): | |
| for file in files: | |
| print(f" {Path(root) / file}") | |
| sys.exit(1) | |
| # Copy to installation directory | |
| dest_file = INSTALL_DIR / tool['binary_name'] | |
| # Backup existing file | |
| if dest_file.exists(): | |
| backup_file = INSTALL_DIR / f"{tool['binary_name']}.backup.{version}" | |
| shutil.move(str(dest_file), str(backup_file)) | |
| print(f"Backed up existing {tool['name']} file to {backup_file}") | |
| # Copy executable | |
| shutil.copy2(str(binary_path), str(dest_file)) | |
| # Set execute permissions | |
| os.chmod(str(dest_file), 0o755) | |
| # Install fish shell completion | |
| install_fish_completion(tool, version, temp_dir, arch) | |
| print(f"Successfully installed {tool['name']} to {INSTALL_DIR}") | |
| def install_shellcheck(tool, version, temp_dir, arch): | |
| """Special installation for shellcheck: extract to ~/.local""" | |
| # shellcheck is extracted directly to ~/.local | |
| local_dir = Path.home() / ".local" | |
| # Find the extracted directory (should be shellcheck-v{version}) | |
| extracted_dir = None | |
| for item in temp_dir.iterdir(): | |
| if item.is_dir() and item.name.startswith("shellcheck-v"): | |
| extracted_dir = item | |
| break | |
| if not extracted_dir: | |
| print(f"Error: Could not find shellcheck directory in {temp_dir}") | |
| sys.exit(1) | |
| # Remove existing installation if it exists | |
| existing_install = local_dir / extracted_dir.name | |
| if existing_install.exists(): | |
| print(f"Removing existing {tool['name']} installation: {existing_install}") | |
| shutil.rmtree(existing_install) | |
| # Move extracted directory to ~/.local | |
| target_dir = local_dir / extracted_dir.name | |
| shutil.move(str(extracted_dir), str(target_dir)) | |
| print(f"Moved {tool['name']} to {target_dir}") | |
| # Find the shellcheck binary and set execute permissions | |
| shellcheck_binary = target_dir / tool['binary_name'] | |
| if shellcheck_binary.exists(): | |
| os.chmod(str(shellcheck_binary), 0o755) | |
| print(f"Set execute permissions on {shellcheck_binary}") | |
| # Create symlink in ~/.local/bin | |
| dest_file = INSTALL_DIR / tool['binary_name'] | |
| # Remove existing symlink if it exists | |
| if dest_file.exists() or dest_file.is_symlink(): | |
| dest_file.unlink() | |
| # Create new symlink | |
| os.symlink(str(shellcheck_binary), str(dest_file)) | |
| print(f"Created symlink: {dest_file} -> {shellcheck_binary}") | |
| def install_lua_language_server(tool, version, temp_dir, arch): | |
| """Special installation for lua-language-server: extract to custom directory""" | |
| # Create custom directory if it doesn't exist | |
| custom_dir = Path.home() / ".local" / tool['custom_dir'] | |
| custom_dir.mkdir(parents=True, exist_ok=True) | |
| # Extract all contents to the custom directory | |
| for item in temp_dir.iterdir(): | |
| if item.name != '.': # Skip hidden files if any | |
| target = custom_dir / item.name | |
| if target.exists(): | |
| if target.is_dir(): | |
| shutil.rmtree(target) | |
| else: | |
| target.unlink() | |
| shutil.move(str(item), str(target)) | |
| print(f"Moved {tool['name']} to {custom_dir}") | |
| # Find the lua-language-server binary and set execute permissions | |
| binary_in_custom = custom_dir / tool['binary_path'] | |
| if binary_in_custom.exists(): | |
| os.chmod(str(binary_in_custom), 0o755) | |
| print(f"Set execute permissions on {binary_in_custom}") | |
| # Create symlink for the binary | |
| dest_file = INSTALL_DIR / tool['binary_name'] | |
| # Remove existing symlink if it exists | |
| if dest_file.exists() or dest_file.is_symlink(): | |
| dest_file.unlink() | |
| # Create new symlink | |
| os.symlink(str(binary_in_custom), str(dest_file)) | |
| print(f"Created symlink: {dest_file} -> {binary_in_custom}") | |
| def cleanup(download_path, temp_dir): | |
| """Clean up temporary files""" | |
| if download_path and download_path.exists(): | |
| download_path.unlink() | |
| if temp_dir.exists(): | |
| shutil.rmtree(temp_dir) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment