#!/usr/bin/env python3 import argparse import shlex import subprocess import sys from pathlib import Path from shutil import which def is_tool(name): """Check whether `name` is on PATH and marked as executable.""" if not which(name): print(f"ERROR: {name} is not found in your PATH") sys.exit(1) def run(cmd, capture=True, returncode=False): result = subprocess.run( shlex.split(cmd), stdout=subprocess.PIPE if capture else None, text=True, shell=False, ) if returncode: return result.returncode elif capture: return result.stdout.strip() class Gum: def __init__(self): is_tool("gum") def confirm(self, question): return not run( "gum confirm " "--selected.bold " "--selected.background 11 " "--selected.foreground 8 " f"'{question}'", returncode=True, ) def color(self, text, color): return run(f'gum style --foreground {color} "{text}"') def header(self, text, color=11): run( "gum style " "--foreground 212 --border-foreground 212 --border rounded " '--align center --width 30 --margin "1 4" ' f"'{self.color(text,color)}'", capture=False, ) def choose(self, options): if isinstance(options, str): options = options.split("") if isinstance(options, list): options = "".join([f"'{i}'" for i in options]) return run(f"gum choose {options}") def input(self, placeholder, value): run( "gum input" + f"--placeholder {placeholder}" if placeholder else "" + f"--value {value}" if value else "" ) def spinner(self, placeholder, cmd): run(f"gum spin -s points --title '{placeholder}'" + cmd) gum = Gum() def pprint(text, color): print(gum.color(text, color)) def parse_exts(mdfile): with Path(mdfile).open("r") as f: md = f.read() md = md.split("##")[1:] exts = {} all = [] for section in md: head = section.splitlines()[0].strip() exts[head] = [] for line in section.splitlines()[1:]: if line == "": continue exts[head].append(line.strip()) all.append(line.strip()) return exts, sorted(all) class SubcommandHelpFormatter(argparse.RawDescriptionHelpFormatter): def _format_action(self, action): parts = super(argparse.RawDescriptionHelpFormatter, self)._format_action(action) if action.nargs == argparse.PARSER: parts = "\n".join(parts.split("\n")[1:]) return parts def get_args(): p = argparse.ArgumentParser(formatter_class=SubcommandHelpFormatter) sp = p.add_subparsers(title="commands", metavar="", dest="command",required=True) check = sp.add_parser( "check", help="check the extensions which have already been installed", aliases = ['c'] ) install = sp.add_parser( "install", help="install the extensions from the spec", aliases =['i'] ) install.add_argument( "-f", "--force", help="force the installation of exts", action="store_true" ) remove = sp.add_parser( "remove", help="remove all extra packages", aliases = ['r'] ) p.add_argument( "-s", "--spec", help="mardown spec list (default ~/.dotfiles/vscode/spec.md)", default=Path.home() / ".dotfiles" / "vscode" / "spec.md", ) if len(sys.argv) < 2: # p.print_usage() p.print_help() sys.exit(1) return p.parse_args() def get_extra_exts(spec, installed): # sanitize lists spec = [ext.lower() for ext in spec] installed = [ext.lower() for ext in installed] return [ext for ext in spec if ext not in installed], [ ext for ext in installed if ext not in spec ] def code(flag, exts, force=False): cmd = "code " + " ".join([f"{flag} {ext}" for ext in exts]) if force: cmd +=" --force" run(cmd, capture=False) def main(): args = get_args() gum.header("VS Code Extensions Installer") spec_sections, spec_list = parse_exts(args.spec) not_installed, extra_installed = get_extra_exts( spec_list, run("code --list-extensions").splitlines() ) if args.command == "check" or args.command == "c": for head, exts in spec_sections.items(): pprint(f"## {head}", 11) for ext in exts: if ext.lower() in not_installed: pprint(ext, 9) else: print(ext) if extra_installed: pprint("\nExtra installed extensions:", 11) print("\n".join(extra_installed)) elif args.command == "install" or args.command =='i': pprint("Installing extensions from spec list", 11) code("--install-extension", spec_list, args.force) elif args.command == "remove" or args.command == "r": pprint("Removing extensions I don't know about", 11) print("\n".join(extra_installed) + "\n") if gum.confirm(f"Remove the above {len(extra_installed)} packages"): print("bye bye packages") code("--uninstall-extension", extra_installed) if __name__ == "__main__": main()