view install.py @ 414:72365ec18f54

Merge changes
author Ludovic Chabant <ludovic@chabant.com>
date Thu, 18 Jan 2018 16:27:32 -0800
parents c6da0c9f40ae
children 222b477ad678
line wrap: on
line source

import os
import os.path
import sys
import stat
import argparse
import functools
import subprocess
import configparser


dotfiles_dir = os.path.abspath(os.path.dirname(__file__))

is_nix = True
is_windows = False
if sys.platform == "win32":
    is_nix = False
    is_windows = True


def _p(*paths):
    return os.path.join(dotfiles_dir, *paths).replace('/', os.sep)


def nixslash(path):
    return path.replace('\\', '/')


def ensure_dir(path):
    full_path = os.path.abspath(os.path.expanduser(path))
    if not os.path.isdir(full_path):
        os.makedirs(full_path, mode=0o700)


def mklink(orig_rel_path, link_path, mode=None):
    orig_full_path = os.path.join(dotfiles_dir, orig_rel_path)
    link_full_path = os.path.abspath(os.path.expanduser(link_path))
    if os.path.islink(link_full_path):
        print("Unlinking %s" % link_full_path)
        os.unlink(link_full_path)
    elif os.path.exists(link_full_path):
        print("Removing %s" % link_full_path)
        os.remove(link_full_path)

    print("%s -> %s" % (link_full_path, orig_full_path))
    os.symlink(orig_full_path, link_full_path)
    if mode is not None:
        os.chmod(link_full_path, mode)


def writelines(path, lines):
    full_path = os.path.abspath(os.path.expanduser(path))
    print("%d lines to %s" % (len(lines), full_path))
    with open(full_path, 'w') as fp:
        for l in lines:
            fp.write(l)
            fp.write('\n')


def only_on_nix(f):
    @functools.wraps(f)
    def decorator(*args, **kwargs):
        if is_nix:
            return f(*args, **kwargs)
    return decorator


def only_on_win(f):
    @functools.wraps(f)
    def decorator(*args, **kwargs):
        if is_windows:
            return f(*args, **kwargs)
    return decorator


def needs_config(f):
    f.__dotfiles_needs_config__ = True
    return f


def run_priority(prio):
    def wrapper(f):
        f.__dotfiles_priority__ = prio
        return f
    return wrapper


@only_on_nix
def install_bash():
    mklink('bashrc/bashrc', '.bashrc')
    mklink('bashrc/bash_profile', '.bash_profile')


@only_on_nix
def install_fish():
    ensure_dir('~/.config')
    mklink('fish', '~/.config/fish')


def install_vim():
    vimrc_path = '~/.vimrc'
    if is_windows:
        vimrc_path = '~/_vimrc'
    writelines(vimrc_path, [
        'set runtimepath+=%s' % nixslash(_p('vim')),
        'source %s' % nixslash(_p('vim', 'vimrc'))
    ])


@run_priority(2)   # Needs to run before `fish`.
def install_mercurial():
    hgrc_path = '~/.hgrc'
    if is_windows:
        hgrc_path = '~/mercurial.ini'
    writelines(hgrc_path, [
        '%%include %s' % _p('hgrc/hgrc'),
        '[ui]',
        'ignore = %s' % _p('hgrc/hgignore'),
        '[subrepos]',
        'git:allowed = true',
        '[extensions]',
        'hggit = %s' % _p('lib/hg/hg-git/hggit/'),
        'onsub = %s' % _p('lib/hg/onsub/onsub.py'),
        'allpaths = %s' % _p('lib/hg/allpaths/mercurial_all_paths.py'),
        'prompt = %s' % _p('lib/hg/hg-prompt/prompt.py'),
        'evolve = %s' % _p('lib/hg/mutable-history/hgext3rd/evolve'),
        'terse-status = %s' % _p('lib/hg/terse-status/terse-status.py')
    ])
    if is_nix:
        print("Building fast-hg-prompt...")
        compile_ok = True
        try:
            subprocess.check_call(['make'], cwd=_p('lib/hg/fast-hg-prompt'))
        except subprocess.CalledProcessError:
            compile_ok = False

        for n in ['bookmark', 'remote', 'status']:
            link_path = os.path.expanduser('~/.local/bin/fast-hg-%s' % n)
            if compile_ok:
                mklink('lib/hg/fast-hg-prompt/fast-hg-%s' % n, link_path,
                       mode=(stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR))
            elif os.path.islink(link_path):
                os.unlink(link_path)
            elif os.path.exists(link_path):
                os.remove(link_path)


def install_git():
    writelines('~/.gitconfig', [
        '[include]',
        'path = %s' % _p('git/gitconfig')
    ])
    if is_windows:
        subprocess.check_call(
            ['setx', 'GIT_SSH',
             '%USERPROFILE%\\Dropbox\\Utilities\\plink.exe'],
            shell=True)


@only_on_nix
def install_tmux():
    mklink('tmux/tmux.conf', '~/.tmux.conf')


@only_on_nix
def install_weechat():
    mklink('weechat', '~/.weechat')


@only_on_nix
def install_mutt():
    writelines('~/.muttrc', [
        'source "gpg2 -dq %s |"' % _p('mutt/variables.gpg'),
        'source "%s"' % _p('mutt/muttrc'),
        'source "%s"' % _p('lib/mutt/mutt-colors-solarized/'
                           'mutt-colors-solarized-dark-256.muttrc')
    ])


def clone_git(url, path):
    if os.path.isdir(path):
        print("Skipping git clone of %s -- directory exists" % path)
    print("git clone %s %s" % (url, path))
    ensure_dir(os.path.dirname(path))
    subprocess.check_call(['git', 'clone', url, path])


def clone_hg(url, path):
    if os.path.isdir(path):
        print("Skipping hg clone of %s -- directory exists" % path)
    print("hg clone %s %s" % (url, path))
    ensure_dir(os.path.dirname(path))
    env = dict(os.environ)
    env.update({'HGPLAIN': '1'})
    subprocess.check_call(['hg', 'clone', url, path], env=env)


@needs_config
@run_priority(100)
def install_subrepos(cfg):
    if not cfg.has_section('subrepos'):
        return

    for path, url in cfg.items('subrepos'):
        full_path = _p(path)
        if url.startswith('[git]'):
            clone_git(url[len('[git]'):], full_path)
        else:
            clone_hg(url, full_path)


def main():
    print("dotfiles installer")
    print("python %s" % sys.version)
    print("on %s" % sys.platform)
    print('')

    cfg = configparser.ConfigParser()
    cfg.read(_p('install.cfg'))

    mod_names = ['all']
    this_mod = sys.modules[__name__]
    for an in dir(this_mod):
        if not an.startswith('install_'):
            continue

        name = an[len('install_'):]
        mod_names.append(name)

    parser = argparse.ArgumentParser()
    parser.add_argument(
        'module', nargs='*',
        choices=mod_names,
        help="Which module(s) to install. Defaults to all modules.")
    args = parser.parse_args()

    funcs = []
    selected_mods = set(args.module)
    if 'all' in selected_mods:
        selected_mods = set(mod_names)
        selected_mods.remove('all')
    for mn in selected_mods:
        func = getattr(this_mod, 'install_%s' % mn)
        funcs.append((mn, func))

    funcs = sorted(funcs, key=_get_install_func_priority, reverse=True)
    for name, func in funcs:
        print("Installing %s" % name)
        if hasattr(func, '__dotfiles_needs_config__'):
            func(cfg)
        else:
            func()


def _get_install_func_priority(func_info):
    func = func_info[1]
    return getattr(func, '__dotfiles_priority__', 0)


if __name__ == '__main__':
    main()