Beispiel #1
0
def vim_plugin_update():
    if allowinteractive():
        execute(['vim', '+PlugClean', '+PlugUpdate'], stdout="TTY")
        if HOMELY.wantnvim():
            execute(['nvim', '+PlugClean', '+PlugUpdate'], stdout="TTY")
        return

    # install the self-updating plugins now
    if True:
        template = '#!/usr/bin/env bash\nvim-update-then-run {} "$@"\n'
        for what in ('vim', 'nvim'):
            exec_ = HOME + '/bin/' + what
            with writefile(exec_) as f:
                f.write(template.format(what))
            os.chmod(exec_, 0o755)
Beispiel #2
0
def init_composer():
    global INIT_DONE

    if not haveexecutable('composer'):
        raise Exception("TODO: composer is not installed")  # noqa

    composer_config = {
        'minimum-stability': 'dev',
        'prefer-stable': True,
    }
    mkdir('~/.config')
    mkdir('~/.config/composer')
    with writefile('~/.config/composer/composer.json') as f:
        json.dump(composer_config, f)

    INIT_DONE = True
Beispiel #3
0
def ubuntu_install_devilspie2():
    """
    Install devilspie2 under Ubuntu.

    devilspie2 can "pin" apps like Rhythmbox or Spotify, causing them to move across all
    desktops/workspaces. This means I don't accidentally flip to another desktop/workspace when I
    go to play some music or respond to a chat message.
    """
    question = 'Install devilspie2 to manage window sticky bits?'
    if not yesno('want_devilspie2', question, default=True):
        return

    installpkg('devilspie2', apt='devilspie2', brew=None)

    symlink('devilspie2', '~/.config/devilspie2')

    with writefile('~/.config/autostart/devilspie2.desktop') as f:
        f.write("[Desktop Entry]\n")
        f.write("Type=Application\n")
        f.write("Name=devilspie2\n")
        f.write("Exec=/usr/bin/devilspie2\n")
        f.write("Comment=devilspie2 - react to gnome window events\n")
        f.write("X-GNOME-Autostart-enabled=true\n")
Beispiel #4
0
def install_alacritty():
    if not yesno('want_alacritty', 'Install Alacritty?', default=None):
        return

    # write an alacritty.yml config that imports the ones from this repo
    lines = ['import:']
    lines.append('  - {}/alacritty-base.yml'.format(HERE))

    # FIXME: add some proper MacOS detection to homely
    if IS_OSX:
        lines.append('  - {}/alacritty-macos.yml'.format(HERE))

        keybindings = []
        keybindings.append(
            dict(
                key='T',
                mods='Control|Command',
                command=dict(
                    program=
                    '/Applications/Alacritty.app/Contents/MacOS/alacritty'),
            ))
        keybindings.append(
            dict(
                key='T',
                mods='Control|Command|Shift',
                command=dict(program=HERE + '/bin/macos-launch-todos'),
            ))
        keybindings.append(
            dict(
                key='S',
                mods='Control|Command',
                command=dict(program=HERE +
                             '/bin/macos-launch-terminal-selector'),
            ))

        with writefile('~/.config/alacritty-keybindings.yml') as f:
            json.dump({'key_bindings': keybindings}, f, indent='  ')

        lines.append('  - {}/.config/alacritty-keybindings.yml'.format(HOME))

    blockinfile('~/.config/alacritty.yml', lines, WHERE_TOP)

    if yesno('install_alacritty_homebrew', 'Install Alacritty via Homebrew?'):
        execute(['brew', 'install', 'alacritty'])

        # XXX:
        # There is an issue where upstream libraries used by alacritty handle
        # CMD+H and hide the alacritty window, preventing our own CMD+H key
        # binding from working. The only workaround right now is to override
        # the shortcut at OS level
        #
        # See https://github.com/alacritty/alacritty/issues/5923
        execute([
            'defaults',
            'write',
            'io.alacritty',
            'NSUserKeyEquivalents',
            '-dict-add',
            'Hide alacritty',
            '@^~$h',
        ])
Beispiel #5
0
def mypips(venv_pip=None, write_dev_reqs=False):
    # if we're on macos then we need to tell homely.pipinstall to use 'pip3' instead of 'pip'
    if IS_OSX:
        pips = ['pip3']
    else:
        pips = None

    # of course we probably want virtualenv!
    if venv_pip is None:
        pipinstall('virtualenv', pips=pips)

    theworks = want_full or venv_pip

    # these packages will be installed using the virtualenv's pip, or pip2+pip3 depending on what's
    # present. They're needed for development.
    packages = [
        'jedi',
        'yapf',
        'isort',
        # needed for `git rebase -i` commit comparisons
        'GitPython',
    ]

    if wantnvim() and not install_nvim_via_apt():
        # if we want nvim then we probably need the pynvim package
        packages.append('pynvim')

    # a nice python repl
    if theworks or yesno('install_ptpython', 'PIP Install ptpython?', True):
        packages.append('ptpython')

    # another nice python repl
    if theworks or yesno('install_ipython', 'PIP Install iPython?', True):
        packages.append('ipython')

    # a few of my macros use `q` for logging
    if theworks or yesno('install_python_q', 'PIP Install `q`?', True):
        packages.append('q')

    if write_dev_reqs:
        assert venv_pip is None
        mkdir('~/.config')

        with writefile('~/.config/dev_requirements.txt') as f:
            f.write('flake8\n')
            for p in packages:
                f.write(p + '\n')

    if want_python2_anything:
        trypips = ['pip2', 'pip3']
    else:
        trypips = ['pip3']

    for package in packages:
        if venv_pip:
            venv_exec(venv_pip, ['pip', 'install', package])
        else:
            mypipinstall(package, trypips=trypips)

    # if it's a virtualenv, always just install flake8. Otherwise, we need to ask the user if
    # they want to install both
    if venv_pip:
        venv_exec(venv_pip, ['pip', 'install', 'flake8'])
    else:
        # always install simplejson globally as we need it for other parts of our homely install
        mypipinstall('simplejson', trypips=trypips)

        have_pip3 = haveexecutable('pip3')
        if have_pip3 and yesno('install_flake8_python3',
                               'Install flake8 for python3?'):
            mypipinstall('flake8', ['pip3'])
        if want_python2_anything and yesno('install_flake8_python2',
                                           'Install flake8 for python2?'):
            mypipinstall('flake8', ['pip2'])
Beispiel #6
0
def powerline_theme():
    right = [
        {
            "function": "todonext.powerline.firstitem"
        },
    ]

    if haveexecutable('jerjerrod') and wantjerjerrod():
        wsnames = "jerjerrod.powerline.wsnames"
        right += [
            {
                "function": "jerjerrod.powerline.wsscancount"
            },
            {
                "function": wsnames,
                "args": {
                    "category": "JERJERROD:CHANGED"
                }
            },
            {
                "function": wsnames,
                "args": {
                    "category": "JERJERROD:UNTRACKED"
                }
            },
            {
                "function": wsnames,
                "args": {
                    "category": "JERJERROD:UNPUSHED"
                }
            },
            {
                "function": wsnames,
                "args": {
                    "category": "JERJERROD:UNKNOWN"
                }
            },
        ]

    right.append({
        "function": "homely.powerline.shortstatus",
        "args": {
            "autoupdate":
            True,
            "reattach_to_user_namespace":
            haveexecutable('reattach-to-user-namespace'),
            "colors": {
                "paused": "HOMELY:PAUSED",
                "running": "HOMELY:RUNNING",
                "failed": "HOMELY:FAILED",
                "noconn": "HOMELY:NOCONN",
                "dirty": "HOMELY:DIRTY",
                "never": "HOMELY:NEVER",
                "ok": "HOMELY:OK",
            }
        },
    })
    right.append({
        "function": "powerline.segments.common.time.date",
        "name": "time",
        "args": {
            "format": "%H:%M",
            "istime": True,
        },
    })
    right.append({"function": "powerline.segments.common.net.hostname"})

    config = {"segments": {"right": right}}
    if want_unicode_fix():
        config["segment_data"] = {"time": {"before": ""}}

    import simplejson
    dumped = simplejson.dumps(config)
    mkdir('~/.config')
    mkdir('~/.config/powerline')
    mkdir('~/.config/powerline/themes')
    mkdir('~/.config/powerline/themes/tmux')
    with writefile('~/.config/powerline/themes/tmux/default.json') as f:
        f.write(dumped)
Beispiel #7
0
def powerline():
    mypipinstall('powerline-status', ['pip3'])

    mkdir('~/.config')
    mkdir('~/.config/powerline')
    paths = [
        "%s/config_files" % powerline_path(),
        "%s/powerline" % HERE,
        "%s/.config/powerline" % HOME,
    ]

    if allow_installing_stuff and IS_UBUNTU:
        msg = 'Install fonts-powerline package for this OS? (Works on Ubuntu)'
        if yesno('powerline_fonts', msg, False, noprompt=False):
            execute(['sudo', 'apt-get', 'install', 'fonts-powerline'],
                    stdout="TTY")

    lines = [
        'export POWERLINE_CONFIG_PATHS=%s' % ":".join(paths),
    ]
    if want_unicode_fix():
        lines.append('export HOMELY_POWERLINE_HOUSE=H')

    blockinfile('~/.shellrc', lines, WHERE_END)

    # ask the user what colour prefs they would like and put it in
    # ~/.config/powerline/colors.sh
    colourfile = os.path.join(HOME, '.config', 'powerline', 'colours.sh')
    load = False
    defaults = dict(
        bg="gray1",
        fg1="white",
        fg2="gray6",
    )
    if not os.path.exists(colourfile):
        if yesno(None, 'Select base colours now?', True, noprompt=False):
            # load available colours from colors.json
            with open("%s/config_files/colors.json" % powerline_path()) as f:
                import simplejson
                colors = simplejson.load(f)
            with open(colourfile, 'w') as f:
                f.write(
                    "# Set the 3 variables using colour names from below.\n")
                f.write(
                    "# WARNING! If you misspell a colour your powerline may not work!\n"
                )
                f.write("#\n")
                f.write("# primary background colour\n")
                f.write("bg=%(bg)s\n" % defaults)
                f.write("# foreground colour for highlighted tab\n")
                f.write("fg1=%(fg1)s\n" % defaults)
                f.write("# foreground colour for other tabs\n")
                f.write("fg2=%(fg2)s\n" % defaults)
                f.write("# possible colours:\n")
                for name in sorted(colors.get("colors", {})):
                    f.write("#   %s\n" % name)
            execute(['vim', colourfile], stdout="TTY")
            load = True
    else:
        load = True
        if yesno(None, 'Select base colours now?', False, noprompt=False):
            execute(['vim', colourfile], stdout="TTY")

    colourset = defaults
    if load:
        with open(colourfile, 'r') as f:
            for line in [l.rstrip() for l in f]:  # noqa: E741
                if len(line) and not line.startswith('#'):
                    import pprint
                    print('line = ' + pprint.pformat(line))  # noqa TODO
                    name, val = line.split('=')
                    colourset[name] = val
    data = {}
    data["groups"] = {
        "window:current": {
            "bg": colourset["bg"],
            "fg": colourset["fg1"],
            "attrs": []
        },
        "window_name": {
            "bg": colourset["bg"],
            "fg": colourset["fg1"],
            "attrs": ["bold"]
        },  # noqa
        "session:prefix": {
            "bg": colourset["bg"],
            "fg": "gray90",
            "attrs": ["bold"]
        },
        "active_window_status": {
            "fg": colourset["fg2"],
            "bg": "gray0",
            "attrs": []
        },
        "hostname": {
            "bg": colourset["bg"],
            "fg": "gray90",
            "attrs": []
        },
    }
    # write out a colorscheme override for tmux using our powerline colours
    mkdir('~/.config')
    mkdir('~/.config/powerline')
    mkdir('~/.config/powerline/colorschemes')
    mkdir('~/.config/powerline/colorschemes/tmux')
    import simplejson
    dumped = simplejson.dumps(data)
    with writefile('~/.config/powerline/colorschemes/tmux/default.json') as f:
        f.write(dumped)
Beispiel #8
0
def test_writefile_usage(tmpdir):
    from homely._engine2 import Engine
    from homely.files import LineInFile
    from homely.general import WriteFile, writefile

    cfgpath = gettmpfilepath(tmpdir, '.json')

    # the file we'll be playing with
    f1 = os.path.join(tmpdir, 'f1.txt')
    f2 = os.path.join(tmpdir, 'f2.txt')
    f3 = os.path.join(tmpdir, 'f3.json')

    # use LineInFile to put stuff in the file
    e = Engine(cfgpath)
    e.run(LineInFile(f1, 'AAA'))
    e.run(LineInFile(f1, 'BBB'))
    del e
    assert contents(f1) == "AAA\nBBB\n"

    # now use WriteFile() to give it new contents
    e = Engine(cfgpath)
    e.run(WriteFile(f1, "BBB\nCCC\n"))
    assert contents(f1) == "BBB\nCCC\n"
    e.cleanup(e.RAISE)
    del e
    # make sure the cleanup didn't blow anything away
    assert contents(f1) == "BBB\nCCC\n"

    # make sure the file is removed on cleanup
    e = Engine(cfgpath)
    e.cleanup(e.RAISE)
    del e
    assert not os.path.exists(f1)

    contents(f2, "Already here!\n")
    assert os.path.exists(f2)
    e = Engine(cfgpath)
    e.run(WriteFile(f2, "AAA\nBBB\n", canoverwrite=True))
    e.cleanup(e.RAISE)
    del e
    assert contents(f2) == "AAA\nBBB\n"

    # running a LineInFile() won't clean up what's already there
    e = Engine(cfgpath)
    e.run(LineInFile(f2, "CCC"))
    e.cleanup(e.RAISE)
    del e
    assert contents(f2) == "AAA\nBBB\nCCC\n"

    # note that removing the LineInFile() doesn't clean up the file because we
    # no longer know whether the user put their config in there
    e = Engine(cfgpath)
    e.cleanup(e.RAISE)
    del e
    assert not os.path.exists(f1)
    assert os.path.exists(f2)

    # now test the context manager
    import homely._engine2
    e = Engine(cfgpath)
    homely._engine2._ENGINE = e
    data = {"z": [3, 4, 5, True], "y": "Hello world", "x": None}
    with writefile(f3) as f:
        if sys.version_info[0] < 3:
            f.write(simplejson.dumps(data, ensure_ascii=False))
        else:
            f.write(simplejson.dumps(data))
    e.cleanup(e.RAISE)
    del e
    assert os.path.exists(f3)
    with open(f3, 'r') as f:
        assert simplejson.loads(f.read()) == data

    # prove that the WriteFile() disappearing results in the file being removed
    e = Engine(cfgpath)
    e.cleanup(e.RAISE)
    del e
    assert not os.path.exists(f3)
Beispiel #9
0
def tmux_keys():
    import re

    lines = []

    # needs to be installed for the current version of python
    mypipinstall('pyyaml', ['pip%d' % sys.version_info.major])
    import yaml

    with open(HERE + '/keybindings/keys.yaml') as f:
        document = yaml.safe_load(f)
    regex = re.compile(r"^(C-)?(O-)?(M-)?(S-)?([ -~]|CR|BS|SPACE|ESC)$")
    if 'tmux' in document:
        static = {}
        dynamic = {}
        if haveexecutable("reattach-to-user-namespace"):
            static[
                'tmux_copy_cmd'] = 'copy-pipe-and-cancel "reattach-to-user-namespace pbcopy"'
        elif haveexecutable("pbcopy"):
            static['tmux_copy_cmd'] = 'copy-pipe-and-cancel "pbcopy"'
        elif True:
            # XXX: I've been having issues with Ubuntu terminal lately where it
            # can't decipher the terminal codes coming from tmux's
            # copy-selection command
            static['tmux_copy_cmd'] = 'copy-pipe-and-cancel "xsel -b -i"'
        else:
            static['tmux_copy_cmd'] = 'copy-selection'

        sections = [
            ('direct', 'bind-key -n {key} {binding}'),
            ('prefixed', 'bind-key {key} {binding}'),
            ('copy-mode-vi',
             'bind-key -T copy-mode-vi {key} send-keys -X {binding}'),
        ]

        for sectionname, template in sections:
            for keycombo, binding in document["tmux"].get(sectionname,
                                                          {}).items():
                if hasattr(binding, 'keys'):  # is it a dict?
                    if 'static' in binding:
                        binding = static[binding['static']]
                    elif 'dynamic' in binding:
                        binding = dynamic[binding['dynamic']]()
                    else:
                        raise Exception("Invalid binding %r" % binding)
                m = regex.match(keycombo)
                if not m:
                    warn("Invalid keycombo for tmux: direct: %r" % keycombo)
                    continue
                ctrl, opt, meta, shift, key = m.groups()
                if key == "CR":
                    key = "Enter"
                elif key == "ESC":
                    key = "Escape"
                elif key == "SPACE":
                    key = "Space"
                else:
                    assert not key.islower(
                    ), "Lowercase keycombo %r is not allowed" % keycombo
                    key = key.lower()
                modifiers = ''
                if ctrl:
                    modifiers += 'C-'
                assert not opt, "O- prefix not allowed for tmux keybinding %r" % keycombo
                if meta:
                    modifiers += 'M-'
                if shift:
                    assert key.islower(), "Invalid keycombo %r" % keycombo
                    key = key.upper()
                lines.append(
                    template.format(key=modifiers + key, binding=binding))

    # we also want to make our special PANE mode
    pm = TmuxCustomMode(
        'panemode',
        "PANEMODE: Move between panes using h, j, k, l. Resize panes using H, J, K, L"
    )
    pm.prefixkeybind('p')
    pm.prefixkeybind('C-p')
    pm.bindkey('h', 'select-pane -L')
    pm.bindkey('j', 'select-pane -D')
    pm.bindkey('k', 'select-pane -U')
    pm.bindkey('l', 'select-pane -R')
    pm.bindkey('H', 'resize-pane -L 2')
    pm.bindkey('J', 'resize-pane -D 2')
    pm.bindkey('K', 'resize-pane -U 2')
    pm.bindkey('L', 'resize-pane -R 2')
    lines.extend(pm.getlines())

    with writefile('~/.tmux/keybindings.conf') as f:
        for line in lines:
            f.write(line)
            f.write("\n")
Beispiel #10
0
def test_writefile_usage(tmpdir):
    from homely._engine2 import Engine
    from homely.files import LineInFile
    from homely.general import WriteFile, writefile

    cfgpath = gettmpfilepath(tmpdir, '.json')

    # the file we'll be playing with
    f1 = os.path.join(tmpdir, 'f1.txt')
    f2 = os.path.join(tmpdir, 'f2.txt')
    f3 = os.path.join(tmpdir, 'f3.json')

    # use LineInFile to put stuff in the file
    e = Engine(cfgpath)
    e.run(LineInFile(f1, 'AAA'))
    e.run(LineInFile(f1, 'BBB'))
    del e
    assert contents(f1) == "AAA\nBBB\n"

    # now use WriteFile() to give it new contents
    e = Engine(cfgpath)
    e.run(WriteFile(f1, "BBB\nCCC\n"))
    assert contents(f1) == "BBB\nCCC\n"
    e.cleanup(e.RAISE)
    del e
    # make sure the cleanup didn't blow anything away
    assert contents(f1) == "BBB\nCCC\n"

    # make sure the file is removed on cleanup
    e = Engine(cfgpath)
    e.cleanup(e.RAISE)
    del e
    assert not os.path.exists(f1)

    contents(f2, "Already here!\n")
    assert os.path.exists(f2)
    e = Engine(cfgpath)
    e.run(WriteFile(f2, "AAA\nBBB\n", canoverwrite=True))
    e.cleanup(e.RAISE)
    del e
    assert contents(f2) == "AAA\nBBB\n"

    # running a LineInFile() won't clean up what's already there
    e = Engine(cfgpath)
    e.run(LineInFile(f2, "CCC"))
    e.cleanup(e.RAISE)
    del e
    assert contents(f2) == "AAA\nBBB\nCCC\n"

    # note that removing the LineInFile() doesn't clean up the file because we
    # no longer know whether the user put their config in there
    e = Engine(cfgpath)
    e.cleanup(e.RAISE)
    del e
    assert not os.path.exists(f1)
    assert os.path.exists(f2)

    # now test the context manager
    import homely._engine2
    e = Engine(cfgpath)
    homely._engine2._ENGINE = e
    data = {"z": [3, 4, 5, True], "y": "Hello world", "x": None}
    with writefile(f3) as f:
        if sys.version_info[0] < 3:
            f.write(simplejson.dumps(data, ensure_ascii=False))
        else:
            f.write(simplejson.dumps(data))
    e.cleanup(e.RAISE)
    del e
    assert os.path.exists(f3)
    with open(f3, 'r') as f:
        assert simplejson.loads(f.read()) == data

    # prove that the WriteFile() disappearing results in the file being removed
    e = Engine(cfgpath)
    e.cleanup(e.RAISE)
    del e
    assert not os.path.exists(f3)