def soft_config_copy( src: pathlib.Path, dest: pathlib.Path, dry_run: bool = True, keep_backup: bool = True, ) -> bool: """ returns: True if it worked, False otherwise """ if not src.is_file(): utils.echo(f"{src} NOT FOUND!", color=utils.bcolors.DEBUG) return False if dest.is_file() and utils.is_symlink(dest, src): utils.echo(f"Already soft-linked: {dest}\n", color=utils.bcolors.DEBUG) return False if not dest.parent.is_dir(): utils.mkdirs(dest.parent, dry_run=dry_run) if dest.is_file() and keep_backup: backup_path = dest.parent / f"{dest.name}_{utils.get_timestamp()}" utils.cp(src=dest, dest=backup_path, dry_run=dry_run) utils.soft_ln(src=src, dest=dest, dry_run=dry_run) return True
def hard_config_copy( src: pathlib.Path, dest: pathlib.Path, dry_run: bool = True, keep_backup: bool = True, ) -> bool: """ returns: True if it worked, False otherwise """ if not src.is_file(): utils.echo(f"{src} NOT FOUND!", color=utils.bcolors.DEBUG) return False if dest.is_file() and os.path.samefile(src, dest): utils.echo(f"Already hard-linked: {dest}\n", color=utils.bcolors.DEBUG) # resolved dest hard-link points back to the source! return False if not dest.parent.is_dir(): utils.mkdirs(dest.parent, dry_run=dry_run) if dest.is_file() and keep_backup: backup_path = dest.parent / f"{dest.name}_{utils.get_timestamp()}" utils.cp(src=dest, dest=backup_path, dry_run=dry_run) utils.hard_ln(src=src, dest=dest, dry_run=dry_run) return True
def apply_dotfiles( input_dotfiles: T.Set, dry_run: bool = True, homedir: T.Optional[str] = None, keep_backup: bool = True, ) -> None: if not input_dotfiles: utils.echo("Empty dotfile input, skipping!", color=utils.bcolors.WARNING) utils.echo(f"Possible keys:\n{report_dotmap(DOTFILE_MAP)}", color=utils.bcolors.INFO) return None if not isinstance(input_dotfiles, set): input_dotfiles = set(input_dotfiles) # Case-insensitive matching l_map = {k.lower(): k for k in DOTFILE_MAP} unkown_keys = {k for k in input_dotfiles if k.lower() not in l_map} known_keys = { l_map[k.lower()] for k in input_dotfiles if k.lower() in l_map } # give unkown keys the chance to expand expanded_keys = set() invalid_keys = set() for unmatched_key in unkown_keys: matched_keys = [ known_k for l_k, known_k in l_map.items() if l_k.startswith(unmatched_key.lower()) ] if not matched_keys: invalid_keys.add(unmatched_key) else: expanded_keys.update(matched_keys) # match_keys if invalid_keys: utils.echo( ["Removing invalid install keys:"] + sorted(invalid_keys), color=utils.bcolors.WARNING, sep="\n\t> ", ) input_dotfiles = known_keys.union(expanded_keys) # Filter the dotfile map dotfile_map = {k: DOTFILE_MAP[k] for k in input_dotfiles} dotabsfilepath_map = map_dotfiles_to_paths(dotfile_map, homedir=homedir) for src_config_file, dest_config_file in dotabsfilepath_map.items(): if soft_config_copy( src_config_file, dest_config_file, dry_run=dry_run, keep_backup=keep_backup, ): utils.echo(f"{src_config_file} -> {dest_config_file}", color=utils.bcolors.INFO)
def build_aptget_pkg_maps( pkg_keys: Optional[Sequence[str]] = None, ) -> Tuple[Dict[str, str], Dict[str, str]]: """ Gets the commands for the packages with keys *pkg_keys* and returns the (*install_map*, *remove map*) """ pkg_subset = (set(TRIVIAL_PGKS).union(PKG_PPAs).union(RENAMED_PKGs).union( MULTIPLE_PKGs)) if pkg_keys: pkg_subset = pkg_subset.intersection(pkg_keys) lost_inputs = set(pkg_keys).difference(pkg_subset) if lost_inputs: echo( ([f"[{len(lost_inputs)}] UNKNOWN APT-GET pkg(s)"] + [ f"[{idx:> 3}] {p}" for idx, p in enumerate(sorted(lost_inputs), start=1) ]), color=bcolors.WARNING, sep="\n\t> ", ) if not pkg_subset: echo("No APT-GET pkgs will be installed!", color=bcolors.WARNING, sep="\n\t> ") return dict(), dict() if pkg_keys: echo( ([f"[{len(pkg_subset)}] Filtered APT-GET pkgs:"] + [ f"[{idx:> 3}] {p}" for idx, p in enumerate(sorted(pkg_subset), start=1) ]), color=bcolors.DEBUG, sep="\n\t> ", ) install_map, remove_map = dict(), dict() for pkg in pkg_subset: # Explode each name into all of the subpackages it contains subpkgs_to_install = MULTIPLE_PKGs.get(pkg, [pkg]) # Get their "real-names" as opposed to our keys pkgs = [RENAMED_PKGs.get(p, p) for p in subpkgs_to_install] ppas = [PKG_PPAs.get(p, p) for p in subpkgs_to_install] install_map[pkg] = aptget_install(pkgs=pkgs, ppas=ppas) remove_map[pkg] = aptget_remove(pkgs=pkgs, ppas=ppas) return install_map, remove_map
def git_pkg_vers( pkg_keys: Union[None, str, Sequence[str]] = None, ) -> Dict[str, str]: pgk_subset = set(NAME_TO_REPO_MAP) if pkg_keys: pgk_subset = pgk_subset.intersection(pkg_keys) lost_inputs = set(pkg_keys).difference(pgk_subset) if lost_inputs: echo( ([f"[{len(lost_inputs)}] UNKNOWN GIT pkg(s)"] + [ f"[{idx:> 3}] {p}" for idx, p in enumerate(sorted(lost_inputs), start=1) ]), color=bcolors.WARNING, sep="\n\t> ", ) if not pgk_subset: echo( "No GIT pkgs versions will be searched for!", color=bcolors.WARNING, sep="\n\t> ", ) return dict() echo( ([f"[{len(pgk_subset)}] Filtered APT-GET pkgs:"] + [ f"[{idx:> 3}] {p}" for idx, p in enumerate(sorted(pgk_subset), start=1) ]), color=bcolors.DEBUG, sep="\n\t> ", ) vers = get_release_tags([NAME_TO_REPO_MAP[k] for k in pgk_subset]) return {k: v for k, v in zip(pgk_subset, vers)}
parser.add_argument("-y", "--yes", action="store_true", help="Don't ask for confirmation") args = parser.parse_args() input_dotfiles = args.dotfiles dry_run = args.dry # dry_run = True # TODO DEBUGGING parser = argparse.ArgumentParser() input_paths = [f for f in input_dotfiles if os.path.isfile(f)] input_dotfiles = [f for f in input_dotfiles if f not in input_paths] for f in input_paths: # TODO DOES THIS WORK? with open(f, "r") as fp: input_dotfiles.extend(fp.readlines()) # No inputs? if not input_dotfiles: utils.echo(f"No inputs given!", color=utils.bcolors.INFO) utils.echo(f"Possible keys:\n{report_dotmap(DOTFILE_MAP)}", color=utils.bcolors.INFO) sys.exit(0) # Valid inputs? apply_dotfiles(input_dotfiles, dry_run=dry_run) utils.echo("\n\nDone!\n\n", color=utils.bcolors.BOLD + utils.bcolors.DEBUG)
action="store_true", help="More output") parser.add_argument("-y", "--yes", action="store_true", help="Don't ask for confirmation") args = parser.parse_args() pkgs = args.pkgs args.dry = True # TODO DEBUG MODE if not pkgs: echo( ["No inputs given, you can choose from these possible keys:"] + sorted(possible_pkg_keys), color=bcolors.INFO, sep="\n\t> ", ) sys.exit(0) if len(pkgs) == 1 and pkgs[0].lower() == "all": echo("Warning, no pkgs specified... Install EVERYTHING?", color=bcolors.WARNING) confirmation = input("[Y]es/[N]o") if confirmation.upper()[:1] == "Y": pkgs = possible_pkg_keys elif confirmation.upper()[:1] == "N": echo("OK, stopping...", color=bcolors.WARNING) sys.exit(0) else: echo(f"Unknown selection: {confirmation}", color=bcolors.WARNING)
def map_dotfiles_to_paths( dotfile_map: Dict[str, str] = None, dotfile_keys: Sequence[str] = None, homedir: Optional[str] = None, dotfile_dir: Union[str, pathlib.Path] = DOTFILE_DIR, ) -> Dict[str, pathlib.Path]: """ Returns a mapping between each dotfile in *dotfile_map* and its destination, based on the *homedir* """ if dotfile_map is None: dotfile_map = DOTFILE_MAP if dotfile_keys: echo( ["Filtering selected dotfile keys:"] + sorted(dotfile_keys), color=bcolors.INFO, sep="\n\t> ", ) dotfile_map = { k: v for k, v in dotfile_map.items() if k in dotfile_keys } if not dotfile_map: echo("No dotfiles to map!", color=bcolors.WARNING, sep="\n\t> ") return dict() if not isinstance(dotfile_dir, pathlib.Path): dotfile_dir = pathlib.Path(dotfile_dir) # -------------------------------------------------------------------------- # # Clean up the dotfile map # -------------------------------------------------------------------------- # missing_fs = {k for k in dotfile_map if not (dotfile_dir / k).is_file()} if missing_fs: echo( ["Ignoring dotfile mappings with missing src files:"] + sorted(missing_fs), color=bcolors.WARNING, sep="\n\t> ", ) dotfile_map = { k: v for k, v in dotfile_map.items() if k not in missing_fs } # Better logic if not homedir: homedir = os.path.expanduser("~") echo(f"Expanding '~' to 'hotexamples_com'\n", color=bcolors.INFO) dotfile_map = { k: pathlib.Path(v).expanduser().absolute() for k, v in dotfile_map.items() } elif os.path.isdir(homedir): echo(f"Using input home directory: hotexamples_com\n") dotfile_map = { k: pathlib.Path(v.replace("~", str(homedir))).absolute() for k, v in dotfile_map.items() } elif (pathlib.Path("~") / homedir).is_dir(): homedir = pathlib.Path("~") / homedir echo(f"Using input home directory: hotexamples_com\n") dotfile_map = { k: pathlib.Path(v.replace("~", str(homedir))).absolute() for k, v in dotfile_map.items() } else: raise OSError(f"Invalid home directory: hotexamples_com") # -------------------------------------------------------------------------- # # Match them # -------------------------------------------------------------------------- # repo_files = { str(f.relative_to(dotfile_dir)): f.absolute() for f in dotfile_dir.rglob("*") } matched_ks = set(repo_files).intersection(dotfile_map) missed_ks = set(repo_files).difference(repo_files) if missed_ks: echo( ["files without mappings:"] + sorted(missed_ks), sep="\n\t> ", color=bcolors.WARNING, ) bad_map_ks = {k for k in matched_ks if REPO_DIR in dotfile_map[k].parents} if bad_map_ks: echo( ["invalid dest mappings:"] + sorted(bad_map_ks), sep="\n\t> ", color=bcolors.WARNING, ) matched_ks = matched_ks.difference(bad_map_ks) if not matched_ks: echo(f"No known dotfiles were found!", color=bcolors.FAIL) raise ValueError(f"No matched_ks found!") return { dotfile_dir / k: dotfile_map[k] for k in repo_files if k in matched_ks }