#!/usr/bin/python3 """Main installer file. Shamelessly copied from https://github.com/dmerejkowsky/dotfiles. """ import argparse import subprocess from typing import List, Optional from urllib.request import urlretrieve import cli_ui as ui # type: ignore from path import Path # type: ignore import ruamel.yaml class Installer: """Regroups all the installation methods listed in the yaml conf file.""" def __init__(self, force: bool = False): self.conf = ruamel.yaml.safe_load( # type: ignore Path("configs.yml").text() ) self.base_dir = Path.getcwd() self.home = Path("~").expanduser() self.force = force def pretty_path(self, path: Path) -> str: """Put the ~/ back in the path.""" relpath: str = path.relpath(self.home) return "~/" + relpath def do_clone(self, url: str, dest: str, branch: str = "master") -> None: """Do the git clone.""" p_dest = Path(dest).expanduser() pretty_dest = self.pretty_path(p_dest) p_dest.parent.makedirs_p() if p_dest.exists(): if self.force: ui.info_2("Removing", pretty_dest) p_dest.rmtree() else: ui.info_2("Skipping", pretty_dest) return ui.info_2("Cloning", url, "->", pretty_dest) git_command = ["git", "clone", url, p_dest, "--branch", branch] subprocess.check_call(git_command) def do_copy(self, src: str, dest: str) -> None: """Copy two files.""" p_dest = Path(dest).expanduser() pretty_dest = self.pretty_path(p_dest) p_dest.parent.makedirs_p() if p_dest.exists() and not self.force: ui.info_2("Skipping", pretty_dest) return p_src: Path = self.base_dir / "dotfiles" / src ui.info_2("Copy", p_src, "->", self.pretty_path(p_src)) p_src.copy(p_dest) def do_download( self, *, url: str, dest: str, executable: bool = False ) -> None: """Retrieve a file from a url.""" p_dest = Path(dest).expanduser() pretty_dest = self.pretty_path(p_dest) p_dest.parent.makedirs_p() if p_dest.exists() and not self.force: ui.info_2("Skipping", pretty_dest) else: ui.info_2("Fetching", url, "->", pretty_dest) urlretrieve(url, p_dest) if executable: p_dest.chmod(0o755) def do_run(self, args: List[str]) -> None: """Run a command.""" command = [x.format(home=self.home) for x in args] ui.info_2("Running", "`{}`".format(" ".join(command))) subprocess.check_call(command) def do_symlink(self, src: str, dest: str) -> None: """Make a symlink to a file.""" self._do_symlink(src, dest, is_dir=False) def do_symlink_dir(self, src: str, dest: str) -> None: """Make a symlink to a dir.""" self._do_symlink(src, dest, is_dir=True) def _do_symlink(self, src: str, dest: str, *, is_dir: bool) -> None: p_dest = Path(dest).expanduser() pretty_dest = self.pretty_path(p_dest) if is_dir: p_dest.parent.parent.makedirs_p() else: p_dest.parent.makedirs_p() if p_dest.exists() and not self.force: ui.info_2("Skipping", pretty_dest) return if p_dest.islink(): # TODO: check fucntion for bash -ef equivalent p_dest.remove() ui.info_2("Symlink", pretty_dest, "->", src) src_full: Path = self.base_dir / "dotfiles" / src src_full.symlink(p_dest) def do_write(self, dest: str, content: str) -> None: """Write into a file.""" p_dest = Path(dest).expanduser() pretty_dest = self.pretty_path(p_dest) if p_dest.exists() and not self.force: ui.info_2("Skipping", pretty_dest) return ui.info_2("Creating", pretty_dest) p_dest.parent.makedirs_p() content = content.format(base_dir=self.base_dir, home=self.home) if not content.endswith("\n"): content += "\n" p_dest.write_text(content) def install(self, programs: Optional[List[str]] = None) -> None: """Install the programs provided. If no program is provided, use the conf file to find the programs. """ if not programs: programs = sorted(self.conf.keys()) for program in programs: self.install_program(program) def install_program(self, program: str) -> None: """Install one program (called by self.install()). Call indivdual methods from the conf file. """ ui.info(ui.green, program) ui.info("-" * len(program)) todo = self.conf[program] for action in todo: name = list(action.keys())[0] params = action[name] func = getattr(self, "do_{}".format(name)) if isinstance(params, dict): func(**params) else: func(*params) ui.info() def main() -> None: """Parse args and instantiate the Installer.""" parser = argparse.ArgumentParser() parser.add_argument( "programs", nargs="*" ) parser.add_argument( "--force", action="store_true", help="Overwrite existing files" ) args = parser.parse_args() force = args.force programs = args.programs installer = Installer(force=force) installer.install(programs=programs) if __name__ == "__main__": main()