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)
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
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")
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', ])
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'])
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)
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)
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)
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")