import platform from itertools import chain from os import environ, pathsep, system from pathlib import Path from sys import argv from tempfile import NamedTemporaryFile from urllib.request import urlopen DOTFILES_DIR = Path.cwd() DOTFILES_GIT = DOTFILES_DIR / ".git" HOME_DIR = Path.home() CONFIG_FILE_NAMES = ( ".fdignore", ".gitconfig", ".gitignore_global", ".hushlogin", ".ripgreprc", ) GHOSTTY_CONF = HOME_DIR / ".config" / "ghostty" DELTA_CONF = HOME_DIR / ".config" / "delta" BAT_THEMES = HOME_DIR / ".config" / "bat" / "themes" DIRVENV_URL = "https://raw.githubusercontent.com/cuducos/dirvenv.fish/refs/heads/main/install.fish" IS_MAC = platform.system().lower() == "darwin" FONT_NAMES = ( ( "FiraCode Nerd Font Mono Retina", "FiraCode Nerd Font Mono Bold", "FiraCode Nerd Font Mono Light", "FiraCode Nerd Font Mono Medium", ) if IS_MAC else ( "Fira Code", "Fira Code Bold", "Fira Code Light", "Fira Code Medium", ) ) FONT_KEYS = ( "font-family", "font-family-bold", "font-family-italic", "font-family-bold-italic", ) def which(bin): for directory in environ["PATH"].split(pathsep): path = Path(directory) / bin if not path.exists() or not path.is_file(): continue return path def download_as(url, path): if not path.exists(): path.touch() with urlopen(url) as resp: path.write_bytes(resp.read()) def create_all_dirs(): for path in DOTFILES_DIR.glob("**/*"): if not path.is_dir() or DOTFILES_GIT in path.parents: continue target = HOME_DIR / path.relative_to(DOTFILES_DIR) target.mkdir(exist_ok=True) def create_all_symlinks(): config_files = (DOTFILES_DIR / f for f in CONFIG_FILE_NAMES) for path in chain(config_files, (DOTFILES_DIR / ".config").glob("**/*")): if not path.is_file(): continue target = HOME_DIR / path.relative_to(DOTFILES_DIR) if target.exists() or target.is_symlink(): continue target.symlink_to(path) def remove_orphan_symlinks(): config_dir = HOME_DIR / ".config" config_targets = config_dir.rglob("*") if config_dir.is_dir() else () for target in chain(HOME_DIR.iterdir(), config_targets): if not target.is_symlink(): continue path = (target.parent / target.readlink()).resolve() if not path.is_relative_to(DOTFILES_DIR): continue if not path.exists(): response = input(f"Remove orphan symlink {target}? [y/N] ") if response.strip().lower() in ("y", "yes"): target.unlink() def configure_ghostty(): lines = (f"{k} = {v}" for k, v in zip(FONT_KEYS, FONT_NAMES)) GHOSTTY_CONF.mkdir(parents=True, exist_ok=True) (GHOSTTY_CONF / "fonts").write_text("\n".join(lines)) def configure_bat(): bat = which("bat") or which("batcat") if not bat: return BAT_THEMES.mkdir(parents=True, exist_ok=True) path = BAT_THEMES / "Catppuccin Latte.tmTheme" if path.exists(): return download_as( "https://raw.githubusercontent.com/catppuccin/bat/main/themes/Catppuccin%20Latte.tmTheme", path, ) system(f"{bat} cache --build") def configure_delta(): if not which("delta"): return DELTA_CONF.mkdir(parents=True, exist_ok=True) path = DELTA_CONF / "catppuccin.gitconfig" if path.exists(): return download_as( "https://raw.githubusercontent.com/catppuccin/delta/main/catppuccin.gitconfig", path, ) def install_dirvenv(): with NamedTemporaryFile(suffix=".fish") as tmp: download_as(DIRVENV_URL, Path(tmp.name)) system(f"fish {tmp.name}") def configure_nvim(): system("nvim --headless -c 'lua vim.pack.update(nil, { force = true })' -c 'quitall'") system("nvim --headless -c 'silent UpdateRemotePlugins' -c 'quitall'") system( "nvim --headless -c 'autocmd User MasonUpgradeComplete sleep 100m | qall' -c 'MasonUpgrade'" ) if __name__ == "__main__": create_all_dirs() if "--remove-orphans" in argv: remove_orphan_symlinks() create_all_symlinks() configure_ghostty() configure_bat() configure_delta() install_dirvenv() configure_nvim()