#!/usr/bin/env python3 import argparse import os import sys import time import urllib.request import urllib.parse import subprocess import functools from typing import Dict class Color: @staticmethod def red(s): return '\033[31m' + s + '\033[0m' @staticmethod def green(s): return '\033[32m' + s + '\033[0m' @staticmethod def yellow(s): return '\033[33m' + s + '\033[0m' @staticmethod def blue(s): return '\033[34m' + s + '\033[0m' @staticmethod def magenta(s): return '\033[35m' + s + '\033[0m' @staticmethod def cyan(s): return '\033[36m' + s + '\033[0m' @staticmethod def gray(s): return '\033[37m' + s + '\033[0m' def make_full_path(path: str) -> str: return os.path.expanduser(path) def make_dir_as_needed(path: str) -> None: folder = os.path.dirname(make_full_path(path)) os.makedirs(folder, exist_ok=True) def shell_run(cmd: str) -> int: print(Color.gray(f'> {cmd}')) return subprocess.check_call(cmd, shell=True) def make_executable(rc_path: str) -> None: shell_run(f'chmod +x {rc_path}') def no_post_download(rc_path: str) -> None: pass def add_to_bashrc(rc_path: str) -> None: shell_run('echo >> ~/.bashrc') shell_run(f'echo "# added by agile-conf/install ({time.asctime()})" >> ~/.bashrc') shell_run(f'echo ". {rc_path}" >> ~/.bashrc') def backup(file: str) -> str: full_path = make_full_path(file) existing_file = None if os.path.exists(full_path): existing_file = f'{full_path}-{time.time()}' shell_run(f'/bin/mv {full_path} {existing_file}') return existing_file def get_comment_str(filename: str) -> str: if filename.endswith('.vimrc'): return '\\"' else: return '#' def prepend_to_rc(rc_path: str, dest_rc_path: str) -> None: existing_dest_rc = backup(dest_rc_path) comment_str = get_comment_str(dest_rc_path) shell_run(f': > {dest_rc_path}') append_to_rc(rc_path, dest_rc_path) if existing_dest_rc: shell_run(f'echo >> {dest_rc_path}') shell_run(f'echo "{comment_str} {existing_dest_rc} pasted below (added by agile-conf/install)" >> {dest_rc_path}') shell_run(f'cat {existing_dest_rc} >> {dest_rc_path}') shell_run(f'echo "{comment_str} {existing_dest_rc} pasted above (added by agile-conf/install)" >> {dest_rc_path}') shell_run(f'echo >> {dest_rc_path}') def append_to_rc(rc_path: str, dest_rc_path: str, is_optional: bool = False) -> None: comment_str = get_comment_str(dest_rc_path) shell_run(f'echo "{comment_str} added by agile-conf/install ({time.asctime()})" >> {dest_rc_path}') comment_str_for_optional_conf = f'{comment_str} ' if is_optional else '' shell_run(f'echo "{comment_str_for_optional_conf}source {rc_path}" >> {dest_rc_path}') def add_to_vimrc(rc_path: str) -> None: make_dir_as_needed('~/.vim') make_dir_as_needed('~/.vim/backups') make_dir_as_needed('~/.vim/swapfiles') append_to_rc(rc_path, '~/.vimrc') def add_to_tmux_conf(rc_path: str, is_optional: bool = False) -> None: append_to_rc(rc_path, '~/.tmux.conf', is_optional) def setup_vundle(rc_path: str) -> None: prepend_to_rc(rc_path, '~/.vimrc') install_vundle_plugins() def install_vundle_plugins(): vundle_path = '~/.vim/bundle/Vundle.vim' backup(os.path.dirname(vundle_path)) shell_run(f'git clone https://github.com/VundleVim/Vundle.vim.git {vundle_path}') shell_run('vim +PluginInstall +qall') def setup_readline_inputrc(rc_path: str) -> None: readline_config_path = '~/.inputrc' backup(readline_config_path) shell_run(f'cp {rc_path} {readline_config_path}') def setup_root_gitconfig(rc_path: str) -> None: gitconfig_path = '~/.gitconfig' backup(gitconfig_path) shell_run(f'cp {rc_path} {gitconfig_path}') def setup_gitconfig(rc_path: str) -> None: gitconfig_path = '~/.gitconfig' shell_run(f'echo "[include]" >> {gitconfig_path}') shell_run(f'echo " path = {rc_path}" >> {gitconfig_path}') def setup_optional_gitconfig(rc_path: str) -> None: gitconfig_path = '~/.gitconfig' shell_run(f'echo "# [include]" >> {gitconfig_path}') shell_run(f'echo "# path = {rc_path}" >> {gitconfig_path}') def setup_gitignore_global(rc_path: str) -> None: gitconfig_path = '~/.gitconfig' shell_run(f'echo "[core]" >> {gitconfig_path}') shell_run(f'echo " excludesfile = {rc_path}" >> {gitconfig_path}') def setup_st_commit_msg(rc_path: str) -> None: gitconfig_path = '~/.gitconfig' shell_run(f'echo "[commit]" >> {gitconfig_path}') shell_run(f'echo " template = {rc_path}" >> {gitconfig_path}') class Rc: def __init__(self, url: str, local_path: str, post_download_func=lambda rc_path: print('no post-download procedure')): self.local_path = local_path self.local_full_path = make_full_path(local_path) self.url = url self.post_download = post_download_func def download(self) -> None: make_dir_as_needed(self.local_full_path) with open(self.local_full_path, 'wt') as f: response = urllib.request.urlopen(self.url) f.write(response.read().decode()) def install(self) -> None: print('%s <== %s' % (self.local_full_path, self.url)) self.download() self.post_download(self.local_path) def make_typed_local_path(url: str) -> str: """ fragment at the end of url will be used as the type, which is one of the keys in post_download_func_map such as '.bashrc', '.gitconfig', etc. examples: file:///tmp/file#.bashrc file:///$HOME/amazon.gitconfig#.optional_gitconfig """ defraged_url, file_type = urllib.parse.urldefrag(url) return os.path.basename(defraged_url) + file_type def make_url_map() -> Dict[str, str]: """ parse the command line arguments and build a url->filepath map. url will be downloaded and saved as filepath :return: the url->filepath map """ install_folder, urls = parse_args() if not install_folder: install_folder = '~/.agile-conf' if not urls: default_url_prefix = 'https://raw.githubusercontent.com/yyu/agile-conf/master' # aka https://git.io/v7LAT files = [ # for .bashrc 'bash.aliases.bashrc', 'bash.files.vimrc.bashrc', 'bash.termcolors.bashrc', 'bash.terminal_prompt.bashrc', 'bash.256colors.bashrc', # for readline 'readline.inputrc', # for git 'git.root_gitconfig', # root gitconfig 'git.basic.gitconfig', 'git.yy.gitconfig', 'git.aliases.gitconfig', 'git.github.gitconfig', 'git.diff.merge.optional_gitconfig', # optional 'git.lfs.optional_gitconfig', # optional 'git.linux.optional_gitconfig', # optional 'git.macos.optional_gitconfig', # optional 'git.sourcetree.optional_gitconfig', # optional 'git.gitignore_global', # global gitignore 'git.stCommitMsg', # for vim 'vi.vim_plugins.vundle', 'vi.cursor.vimrc', 'vi.files.vimrc', 'vi.syntax.vimrc', 'vi.tab.space.vimrc', 'vi.more.vimrc', # for tmux 'tmux.keys.tmux_conf', 'tmux.messages.tmux_conf', 'tmux.panes.tmux_conf', 'tmux.status.tmux_conf', 'tmux.windows.tmux_conf', 'tmux.macosvim.optional_tmux_conf', # scripts 'git.diff.sh' ] urls = [os.path.join(default_url_prefix, f) for f in files] return {url: os.path.join(install_folder, make_typed_local_path(url)) for url in urls} post_download_func_map = { '.sh': make_executable, '.bash': make_executable, '.resource': no_post_download, '.bashrc': add_to_bashrc, '.vundle': setup_vundle, '.vimrc': add_to_vimrc, '.tmux_conf': add_to_tmux_conf, '.optional_tmux_conf': functools.partial(add_to_tmux_conf, is_optional=True), '.root_gitconfig': setup_root_gitconfig, '.inputrc': setup_readline_inputrc, '.gitconfig': setup_gitconfig, '.optional_gitconfig': setup_optional_gitconfig, '.gitignore_global': setup_gitignore_global, '.stCommitMsg': setup_st_commit_msg, } def standard_post_download(rc_path: str) -> None: _, ext = os.path.splitext(rc_path) post_download_func_map[ext](rc_path) def parse_args() -> (str, str): description_sep = '\n ' supported_types = description_sep.join(post_download_func_map.keys()) description = f'supports:{description_sep}{supported_types}' description += '\n\n' description += '''Check the following to clean up: ~/.bashrc ~/.vimrc ~/.tmux.conf ~/.gitconfig ~/.agile-conf/ ~/.vim/ ~/.vim/vundle/ ''' description += '\n\n' description += '''To remove all lines added by agile-conf, run gnu-sed -i.bak -e "/added by agile-conf/,+2d" ~/.bashrc # bsd sed used by macos may not work ''' arg_parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawTextHelpFormatter) arg_parser.add_argument(dest='urls', metavar='url', nargs='*') arg_parser.add_argument('-o', dest='folder', action='store', help='folder for the downloaded content') arguments = arg_parser.parse_args() return arguments.folder, arguments.urls if __name__ == '__main__': url_map = make_url_map() rc_list = [Rc(url, local_path, standard_post_download) for (url, local_path) in url_map.items()] for rc in rc_list: rc.install() print(Color.green('\ncheck the results: vim -o ~/.bashrc ~/.vimrc ~/.tmux.conf ~/.gitconfig'), file=sys.stderr) print(Color.green('\na new bash session is good for your health'), file=sys.stderr) print(Color.cyan('\n\nalso consider:\n git clone https://github.com/rkitover/vimpager.git'), file=sys.stderr)