Start re-organising my dotfiles

This commit is contained in:
Jonathan Hodgson 2018-04-16 23:03:29 +01:00
parent 4141702986
commit 8954b21d7f
101 changed files with 40 additions and 3228 deletions

View file

@ -0,0 +1,86 @@
# Changes
2016-04-16
* Fix issue around unicode function for python 3
2016-04-01
* Refactor of the way the git segment manages data about git's state.
([@b-ryan](https://github.com/milkbikis/powerline-shell/pull/221))
2015-12-26
* Beginnings of unit testing for segments. Included in this change was a
refactor of the way segments are added to powerline. Now, instead of looking
for a global `powerline` object, `powerline` is passed into the function to
add the segment. Segments will also no longer add the segments by calling the
`add` function themselves.
([@b-ryan](https://github.com/milkbikis/powerline-shell/pull/212))
* Python3 fixes for `lib/color_compliment.py`.
([@ceholden](https://github.com/milkbikis/powerline-shell/pull/220))
2015-11-25
* `virtual_env` segment now supports environments made with `conda`
([@ceholden](https://github.com/milkbikis/powerline-shell/pull/198))
2015-11-21
* Fixes for Python 3 compatibility
([@b-ryan](https://github.com/milkbikis/powerline-shell/pull/211))
2015-11-18
* The git segment has gotten a makeover
([@MartinWetterwald](https://github.com/milkbikis/powerline-shell/pull/136))
* Fix git segment when git is not on the standard PATH
([@andrejgl](https://github.com/milkbikis/powerline-shell/pull/153))
* Fix `--cwd-max-depth` showing duplicates when it's <= 2
([@b-ryan](https://github.com/milkbikis/powerline-shell/pull/209))
* Add padding around `exit_code` segment
([@phatblat](https://github.com/milkbikis/powerline-shell/pull/205))
2015-10-02
* New option (`--cwd-max-dir-size`) which allows you to limit each directory
that is displayed to a number of characters. This currently does not apply
if you are using `--cwd-mode plain`.
([@mart-e](https://github.com/milkbikis/powerline-shell/pull/127))
2015-08-26
* New `plain` mode of displaying the current working directory which can be
used by adding `--cwd-only plain` to `powerline-shell.py`.
This deprecates the `--cwd-only` option. `--cwd-mode dironly` can be used
instead. ([@paol](https://github.com/milkbikis/powerline-shell/pull/156))
2015-08-18
* New `time` segment
([@filipebarros](https://github.com/milkbikis/powerline-shell/pull/107))
2015-08-01
* Use `print` function for some python3 compatibility
([@strycore](https://github.com/milkbikis/powerline-shell/pull/195))
2015-07-31
* The current working directory no longer follows symbolic links
* New `exit_code` segment
([@disruptek](https://github.com/milkbikis/powerline-shell/pull/129))
2015-07-30
* Fix ZSH root indicator
([@nkcfan](https://github.com/milkbikis/powerline-shell/pull/150))
* Add uptime segment
([@marcioAlmada](https://github.com/milkbikis/powerline-shell/pull/139))
2015-07-27
* Use `python2` instead of `python` in hashbangs
([@Undeterminant](https://github.com/milkbikis/powerline-shell/pull/100))
* Add `node_version` segment
([@mmilleruva](https://github.com/milkbikis/powerline-shell/pull/189))

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 Shrey Banga and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,170 @@
A Powerline style prompt for your shell
=======================================
A [Powerline](https://github.com/Lokaltog/vim-powerline) like prompt for Bash, ZSH and Fish:
![MacVim+Solarized+Powerline+CtrlP](https://raw.github.com/milkbikis/dotfiles-mac/master/bash-powerline-screenshot.png)
* Shows some important details about the git/svn/hg/fossil branch (see below)
* Changes color if the last command exited with a failure code
* If you're too deep into a directory tree, shortens the displayed path with an ellipsis
* Shows the current Python [virtualenv](http://www.virtualenv.org/) environment
* It's easy to customize and extend. See below for details.
### Version Control
All of the version control systems supported by powerline shell give you a
quick look into the state of your repo:
* The current branch is displayed and changes background color when the
branch is dirty.
* When the local branch differs from the remote, the difference in number
of commits is shown along with `⇡` or `⇣` indicating whether a git push
or pull is pending
In addition, git has a few extra symbols:
* `✎` -- a file has been modified, but not staged for commit
* `✔` -- a file is staged for commit
* `✼` -- a file has conflicts
FIXME
* A `+` appears when untracked files are present (except for git, which
uses `?` instead)
Each of these will have a number next to it if more than one file matches.
# Setup
This script uses ANSI color codes to display colors in a terminal. These are
notoriously non-portable, so may not work for you out of the box, but try
setting your $TERM to `xterm-256color`, because that works for me.
* Patch the font you use for your terminal: see https://github.com/Lokaltog/powerline-fonts
* If you struggle too much to get working fonts in your terminal, you can use "compatible" mode.
* If you're using old patched fonts, you have to use the older symbols. Basically reverse [this commit](https://github.com/milkbikis/powerline-shell/commit/2a84ecc) in your copy
* Clone this repository somewhere:
git clone https://github.com/milkbikis/powerline-shell
* Copy `config.py.dist` to `config.py` and edit it to configure the segments you want. Then run
./install.py
* This will generate `powerline-shell.py`
* (optional) Create a symlink to this python script in your home:
ln -s <path/to/powerline-shell.py> ~/powerline-shell.py
* If you don't want the symlink, just modify the path in the commands below
* For python2.6 you have to install argparse
pip install argparse
### All Shells:
There are a few optional arguments which can be seen by running `powerline-shell.py --help`.
```
--cwd-mode {fancy,plain,dironly}
How to display the current directory
--cwd-max-depth CWD_MAX_DEPTH
Maximum number of directories to show in path
--cwd-max-dir-size CWD_MAX_DIR_SIZE
Maximum number of letters displayed for each directory
in the path
--colorize-hostname Colorize the hostname based on a hash of itself.
--mode {patched,compatible,flat}
The characters used to make separators between
segments
```
### Bash:
Add the following to your `.bashrc` (or `.profile` on Mac):
```
function _update_ps1() {
PS1="$(~/powerline-shell.py $? 2> /dev/null)"
}
if [ "$TERM" != "linux" ]; then
PROMPT_COMMAND="_update_ps1; $PROMPT_COMMAND"
fi
```
### ZSH:
Add the following to your `.zshrc`:
```
function powerline_precmd() {
PS1="$(~/powerline-shell.py $? --shell zsh 2> /dev/null)"
}
function install_powerline_precmd() {
for s in "${precmd_functions[@]}"; do
if [ "$s" = "powerline_precmd" ]; then
return
fi
done
precmd_functions+=(powerline_precmd)
}
if [ "$TERM" != "linux" ]; then
install_powerline_precmd
fi
```
### Fish:
Redefine `fish_prompt` in ~/.config/fish/config.fish:
```
function fish_prompt
~/powerline-shell.py $status --shell bare ^/dev/null
end
```
# Customization
### Adding, Removing and Re-arranging segments
The `config.py` file defines which segments are drawn and in which order. Simply
comment out and rearrange segment names to get your desired arrangement. Every
time you change `config.py`, run `install.py`, which will generate a new
`powerline-shell.py` customized to your configuration. You should see the new
prompt immediately.
### Contributing new types of segments
The `segments` directory contains python scripts which are injected as is into
a single file `powerline_shell_base.py`. Each segment script defines a function
that inserts one or more segments into the prompt. If you want to add a new
segment, simply create a new file in the segments directory and add its name to
the `config.py` file at the appropriate location.
Make sure that your script does not introduce new globals which might conflict
with other scripts. Your script should fail silently and run quickly in any
scenario.
Make sure you introduce new default colors in `themes/default.py` for every new
segment you create. Test your segment with this theme first.
You should add tests for your segment as best you are able. Unit and
integration tests are both welcome. Run your tests with the `nosetests` command
after install the requirements in `dev_requirements.txt`.
### Themes
The `themes` directory stores themes for your prompt, which are basically color
values used by segments. The `default.py` defines a default theme which can be
used standalone, and every other theme falls back to it if they miss colors for
any segments. Create new themes by copying any other existing theme and
changing the values. To use a theme, set the `THEME` variable in `config.py` to
the name of your theme.
A script for testing color combinations is provided at `themes/colortest.py`.
Note that the colors you see may vary depending on your terminal. When designing
a theme, please test your theme on multiple terminals, especially with default
settings.

View file

View file

@ -0,0 +1,5 @@
dependencies:
pre:
- sudo pip install -r dev_requirements.txt
- git config --global user.email "tester@example.com"
- git config --global user.name "Tester McGee"

View file

@ -0,0 +1,57 @@
# This is the configuration file for your powerline-shell prompt
# Every time you make a change to this file, run install.py to apply changes
#
# For instructions on how to use the powerline-shell.py script, see the README
# Add, remove or rearrange these segments to customize what you see on the shell
# prompt. Any segment you add must be present in the segments/ directory
SEGMENTS = [
# Set the terminal window title to user@host:dir
'set_term_title',
# Show current virtual environment (see http://www.virtualenv.org/)
'virtual_env',
# Show the current user's username as in ordinary prompts
'username',
# Show the machine's hostname. Mostly used when ssh-ing into other machines
# 'hostname',
# Show a padlock when ssh-ing from another machine
'ssh',
# Show the current directory. If the path is too long, the middle part is
# replaced with ellipsis ('...')
'cwd',
# Show a padlock if the current user has no write access to the current
# directory
'read_only',
# Show the current git branch and status
'git',
# Show the current mercurial branch and status
'hg',
# Show the current svn branch and status
'svn',
# Show the current fossil branch and status
'fossil',
# Show number of running jobs
'jobs',
# Show the last command's exit code if it was non-zero
# 'exit_code',
# Shows a '#' if the current user is root, '$' otherwise
# Also, changes color if the last command exited with a non-zero error code
'root',
]
# Change the colors used to draw individual segments in your prompt
THEME = 'default'

View file

@ -0,0 +1,57 @@
# This is the configuration file for your powerline-shell prompt
# Every time you make a change to this file, run install.py to apply changes
#
# For instructions on how to use the powerline-shell.py script, see the README
# Add, remove or rearrange these segments to customize what you see on the shell
# prompt. Any segment you add must be present in the segments/ directory
SEGMENTS = [
# Set the terminal window title to user@host:dir
# 'set_term_title',
# Show current virtual environment (see http://www.virtualenv.org/)
'virtual_env',
# Show the current user's username as in ordinary prompts
'username',
# Show the machine's hostname. Mostly used when ssh-ing into other machines
'hostname',
# Show a padlock when ssh-ing from another machine
'ssh',
# Show the current directory. If the path is too long, the middle part is
# replaced with ellipsis ('...')
'cwd',
# Show a padlock if the current user has no write access to the current
# directory
'read_only',
# Show the current git branch and status
'git',
# Show the current mercurial branch and status
'hg',
# Show the current svn branch and status
'svn',
# Show the current fossil branch and status
'fossil',
# Show number of running jobs
'jobs',
# Show the last command's exit code if it was non-zero
# 'exit_code',
# Shows a '#' if the current user is root, '$' otherwise
# Also, changes color if the last command exited with a non-zero error code
'root',
]
# Change the colors used to draw individual segments in your prompt
THEME = 'default'

View file

@ -0,0 +1,3 @@
nose>=1.3.7
mock>=1.3.0
sh>=1.11

View file

@ -0,0 +1,49 @@
#!/usr/bin/env python
from __future__ import print_function
import os
import stat
try:
import config
except ImportError:
print('Created personal config.py for your customizations')
import shutil
shutil.copyfile('config.py.dist', 'config.py')
import config
TEMPLATE_FILE = 'powerline_shell_base.py'
OUTPUT_FILE = 'powerline-shell.py'
SEGMENTS_DIR = 'segments'
THEMES_DIR = 'themes'
def load_source(srcfile):
try:
return ''.join(open(srcfile).readlines()) + '\n\n'
except IOError:
print('Could not open', srcfile)
return ''
if __name__ == "__main__":
source = load_source(TEMPLATE_FILE)
source += load_source(os.path.join(THEMES_DIR, 'default.py'))
if config.THEME != 'default':
source += load_source(os.path.join(THEMES_DIR, config.THEME + '.py'))
for segment in config.SEGMENTS:
source += load_source(os.path.join(SEGMENTS_DIR, segment + '.py'))
# assumes each segment file will have a function called
# add_segment__[segment] that accepts the powerline object
source += 'add_{}_segment(powerline)\n'.format(segment)
source += 'sys.stdout.write(powerline.draw())\n'
try:
open(OUTPUT_FILE, 'w').write(source)
st = os.stat(OUTPUT_FILE)
os.chmod(OUTPUT_FILE, st.st_mode | stat.S_IEXEC)
print(OUTPUT_FILE, 'saved successfully')
except IOError:
print('ERROR: Could not write to powerline-shell.py. Make sure it is writable')
exit(1)

View file

@ -0,0 +1,704 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import argparse
import os
import sys
py3 = sys.version_info.major == 3
def warn(msg):
print('[powerline-bash] ', msg)
if py3:
def unicode(x):
return x
class Powerline:
symbols = {
'compatible': {
'lock': 'RO',
'network': 'SSH',
'separator': u'\u25B6',
'separator_thin': u'\u276F'
},
'patched': {
'lock': u'\uE0A2',
'network': u'\uE0A2',
'separator': u'\uE0B0',
'separator_thin': u'\uE0B1'
},
'flat': {
'lock': '',
'network': '',
'separator': '',
'separator_thin': ''
},
}
color_templates = {
'bash': '\\[\\e%s\\]',
'zsh': '%%{%s%%}',
'bare': '%s',
}
def __init__(self, args, cwd):
self.args = args
self.cwd = cwd
mode, shell = args.mode, args.shell
self.color_template = self.color_templates[shell]
self.reset = self.color_template % '[0m'
self.lock = Powerline.symbols[mode]['lock']
self.network = Powerline.symbols[mode]['network']
self.separator = Powerline.symbols[mode]['separator']
self.separator_thin = Powerline.symbols[mode]['separator_thin']
self.segments = []
def color(self, prefix, code):
if code is None:
return ''
else:
return self.color_template % ('[%s;5;%sm' % (prefix, code))
def fgcolor(self, code):
return self.color('38', code)
def bgcolor(self, code):
return self.color('48', code)
def append(self, content, fg, bg, separator=None, separator_fg=None):
self.segments.append((content, fg, bg,
separator if separator is not None else self.separator,
separator_fg if separator_fg is not None else bg))
def draw(self):
text = (''.join(self.draw_segment(i) for i in range(len(self.segments)))
+ self.reset) + ' '
if py3:
return text
else:
return text.encode('utf-8')
def draw_segment(self, idx):
segment = self.segments[idx]
next_segment = self.segments[idx + 1] if idx < len(self.segments)-1 else None
return ''.join((
self.fgcolor(segment[1]),
self.bgcolor(segment[2]),
segment[0],
self.bgcolor(next_segment[2]) if next_segment else self.reset,
self.fgcolor(segment[4]),
segment[3]))
class RepoStats:
symbols = {
'detached': u'\u2693',
'ahead': u'\u2B06',
'behind': u'\u2B07',
'staged': u'\u2714',
'not_staged': u'\u270E',
'untracked': u'\u003F',
'conflicted': u'\u273C'
}
def __init__(self):
self.ahead = 0
self.behind = 0
self.untracked = 0
self.not_staged = 0
self.staged = 0
self.conflicted = 0
@property
def dirty(self):
qualifiers = [
self.untracked,
self.not_staged,
self.staged,
self.conflicted,
]
return sum(qualifiers) > 0
def __getitem__(self, _key):
return getattr(self, _key)
def n_or_empty(self, _key):
"""Given a string name of one of the properties of this class, returns
the value of the property as a string when the value is greater than
1. When it is not greater than one, returns an empty string.
As an example, if you want to show an icon for untracked files, but you
only want a number to appear next to the icon when there are more than
one untracked files, you can do:
segment = repo_stats.n_or_empty("untracked") + icon_string
"""
return unicode(self[_key]) if int(self[_key]) > 1 else u''
def add_to_powerline(self, powerline, color):
def add(_key, fg, bg):
if self[_key]:
s = u" {}{} ".format(self.n_or_empty(_key), self.symbols[_key])
powerline.append(s, fg, bg)
add('ahead', color.GIT_AHEAD_FG, color.GIT_AHEAD_BG)
add('behind', color.GIT_BEHIND_FG, color.GIT_BEHIND_BG)
add('staged', color.GIT_STAGED_FG, color.GIT_STAGED_BG)
add('not_staged', color.GIT_NOTSTAGED_FG, color.GIT_NOTSTAGED_BG)
add('untracked', color.GIT_UNTRACKED_FG, color.GIT_UNTRACKED_BG)
add('conflicted', color.GIT_CONFLICTED_FG, color.GIT_CONFLICTED_BG)
def get_valid_cwd():
""" We check if the current working directory is valid or not. Typically
happens when you checkout a different branch on git that doesn't have
this directory.
We return the original cwd because the shell still considers that to be
the working directory, so returning our guess will confuse people
"""
# Prefer the PWD environment variable. Python's os.getcwd function follows
# symbolic links, which is undesirable. But if PWD is not set then fall
# back to this func
try:
cwd = os.getenv('PWD') or os.getcwd()
except:
warn("Your current directory is invalid. If you open a ticket at " +
"https://github.com/milkbikis/powerline-shell/issues/new " +
"we would love to help fix the issue.")
sys.stdout.write("> ")
sys.exit(1)
parts = cwd.split(os.sep)
up = cwd
while parts and not os.path.exists(up):
parts.pop()
up = os.sep.join(parts)
if cwd != up:
warn("Your current directory is invalid. Lowest valid directory: "
+ up)
return cwd
if __name__ == "__main__":
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('--cwd-mode', action='store',
help='How to display the current directory', default='fancy',
choices=['fancy', 'plain', 'dironly'])
arg_parser.add_argument('--cwd-only', action='store_true',
help='Deprecated. Use --cwd-mode=dironly')
arg_parser.add_argument('--cwd-max-depth', action='store', type=int,
default=5, help='Maximum number of directories to show in path')
arg_parser.add_argument('--cwd-max-dir-size', action='store', type=int,
help='Maximum number of letters displayed for each directory in the path')
arg_parser.add_argument('--colorize-hostname', action='store_true',
help='Colorize the hostname based on a hash of itself.')
arg_parser.add_argument('--mode', action='store', default='patched',
help='The characters used to make separators between segments',
choices=['patched', 'compatible', 'flat'])
arg_parser.add_argument('--shell', action='store', default='bash',
help='Set this to your shell type', choices=['bash', 'zsh', 'bare'])
arg_parser.add_argument('prev_error', nargs='?', type=int, default=0,
help='Error code returned by the last command')
args = arg_parser.parse_args()
powerline = Powerline(args, get_valid_cwd())
class DefaultColor:
"""
This class should have the default colors for every segment.
Please test every new segment with this theme first.
"""
USERNAME_FG = 250
USERNAME_BG = 240
USERNAME_ROOT_BG = 124
HOSTNAME_FG = 250
HOSTNAME_BG = 238
HOME_SPECIAL_DISPLAY = True
HOME_BG = 31 # blueish
HOME_FG = 15 # white
PATH_BG = 237 # dark grey
PATH_FG = 250 # light grey
CWD_FG = 254 # nearly-white grey
SEPARATOR_FG = 244
READONLY_BG = 124
READONLY_FG = 254
SSH_BG = 166 # medium orange
SSH_FG = 254
REPO_CLEAN_BG = 148 # a light green color
REPO_CLEAN_FG = 0 # black
REPO_DIRTY_BG = 161 # pink/red
REPO_DIRTY_FG = 15 # white
JOBS_FG = 39
JOBS_BG = 238
CMD_PASSED_BG = 236
CMD_PASSED_FG = 15
CMD_FAILED_BG = 161
CMD_FAILED_FG = 15
SVN_CHANGES_BG = 148
SVN_CHANGES_FG = 22 # dark green
GIT_AHEAD_BG = 240
GIT_AHEAD_FG = 250
GIT_BEHIND_BG = 240
GIT_BEHIND_FG = 250
GIT_STAGED_BG = 22
GIT_STAGED_FG = 15
GIT_NOTSTAGED_BG = 130
GIT_NOTSTAGED_FG = 15
GIT_UNTRACKED_BG = 52
GIT_UNTRACKED_FG = 15
GIT_CONFLICTED_BG = 9
GIT_CONFLICTED_FG = 15
VIRTUAL_ENV_BG = 35 # a mid-tone green
VIRTUAL_ENV_FG = 00
class Color(DefaultColor):
"""
This subclass is required when the user chooses to use 'default' theme.
Because the segments require a 'Color' class for every theme.
"""
pass
def add_set_term_title_segment(powerline):
term = os.getenv('TERM')
if not (('xterm' in term) or ('rxvt' in term)):
return
if powerline.args.shell == 'bash':
set_title = '\\[\\e]0;\\u@\\h: \\w\\a\\]'
elif powerline.args.shell == 'zsh':
set_title = '\033]0;%n@%m: %~\007'
else:
import socket
set_title = '\033]0;%s@%s: %s\007' % (os.getenv('USER'), socket.gethostname().split('.')[0], powerline.cwd or os.getenv('PWD'))
powerline.append(set_title, None, None, '')
add_set_term_title_segment(powerline)
import os
def add_virtual_env_segment(powerline):
env = os.getenv('VIRTUAL_ENV') or os.getenv('CONDA_ENV_PATH')
if env is None:
return
env_name = os.path.basename(env)
bg = Color.VIRTUAL_ENV_BG
fg = Color.VIRTUAL_ENV_FG
powerline.append(' %s ' % env_name, fg, bg)
add_virtual_env_segment(powerline)
def add_username_segment(powerline):
import os
if powerline.args.shell == 'bash':
user_prompt = ' \\u '
elif powerline.args.shell == 'zsh':
user_prompt = ' %n '
else:
user_prompt = ' %s ' % os.getenv('USER')
if os.getenv('USER') == 'root':
bgcolor = Color.USERNAME_ROOT_BG
else:
bgcolor = Color.USERNAME_BG
powerline.append(user_prompt, Color.USERNAME_FG, bgcolor)
add_username_segment(powerline)
import os
def add_ssh_segment(powerline):
if os.getenv('SSH_CLIENT'):
powerline.append(' %s ' % powerline.network, Color.SSH_FG, Color.SSH_BG)
add_ssh_segment(powerline)
import os
ELLIPSIS = u'\u2026'
def replace_home_dir(cwd):
home = os.getenv('HOME')
if cwd.startswith(home):
return '~' + cwd[len(home):]
return cwd
def split_path_into_names(cwd):
names = cwd.split(os.sep)
if names[0] == '':
names = names[1:]
if not names[0]:
return ['/']
return names
def requires_special_home_display(name):
"""Returns true if the given directory name matches the home indicator and
the chosen theme should use a special home indicator display."""
return (name == '~' and Color.HOME_SPECIAL_DISPLAY)
def maybe_shorten_name(powerline, name):
"""If the user has asked for each directory name to be shortened, will
return the name up to their specified length. Otherwise returns the full
name."""
if powerline.args.cwd_max_dir_size:
return name[:powerline.args.cwd_max_dir_size]
return name
def get_fg_bg(name):
"""Returns the foreground and background color to use for the given name.
"""
if requires_special_home_display(name):
return (Color.HOME_FG, Color.HOME_BG,)
return (Color.PATH_FG, Color.PATH_BG,)
def add_cwd_segment(powerline):
cwd = powerline.cwd or os.getenv('PWD')
if not py3:
cwd = cwd.decode("utf-8")
cwd = replace_home_dir(cwd)
if powerline.args.cwd_mode == 'plain':
powerline.append(' %s ' % (cwd,), Color.CWD_FG, Color.PATH_BG)
return
names = split_path_into_names(cwd)
max_depth = powerline.args.cwd_max_depth
if max_depth <= 0:
warn("Ignoring --cwd-max-depth argument since it's not greater than 0")
elif len(names) > max_depth:
# https://github.com/milkbikis/powerline-shell/issues/148
# n_before is the number is the number of directories to put before the
# ellipsis. So if you are at ~/a/b/c/d/e and max depth is 4, it will
# show `~ a ... d e`.
#
# max_depth must be greater than n_before or else you end up repeating
# parts of the path with the way the splicing is written below.
n_before = 2 if max_depth > 2 else max_depth - 1
names = names[:n_before] + [ELLIPSIS] + names[n_before - max_depth:]
if (powerline.args.cwd_mode == 'dironly' or powerline.args.cwd_only):
# The user has indicated they only want the current directory to be
# displayed, so chop everything else off
names = names[-1:]
for i, name in enumerate(names):
fg, bg = get_fg_bg(name)
separator = powerline.separator_thin
separator_fg = Color.SEPARATOR_FG
is_last_dir = (i == len(names) - 1)
if requires_special_home_display(name) or is_last_dir:
separator = None
separator_fg = None
powerline.append(' %s ' % maybe_shorten_name(powerline, name), fg, bg,
separator, separator_fg)
add_cwd_segment(powerline)
import os
def add_read_only_segment(powerline):
cwd = powerline.cwd or os.getenv('PWD')
if not os.access(cwd, os.W_OK):
powerline.append(' %s ' % powerline.lock, Color.READONLY_FG, Color.READONLY_BG)
add_read_only_segment(powerline)
import re
import subprocess
import os
def get_PATH():
"""Normally gets the PATH from the OS. This function exists to enable
easily mocking the PATH in tests.
"""
return os.getenv("PATH")
def git_subprocess_env():
return {
# LANG is specified to ensure git always uses a language we are expecting.
# Otherwise we may be unable to parse the output.
"LANG": "C",
# https://github.com/milkbikis/powerline-shell/pull/126
"HOME": os.getenv("HOME"),
# https://github.com/milkbikis/powerline-shell/pull/153
"PATH": get_PATH(),
}
def parse_git_branch_info(status):
info = re.search('^## (?P<local>\S+?)''(\.{3}(?P<remote>\S+?)( \[(ahead (?P<ahead>\d+)(, )?)?(behind (?P<behind>\d+))?\])?)?$', status[0])
return info.groupdict() if info else None
def _get_git_detached_branch():
p = subprocess.Popen(['git', 'describe', '--tags', '--always'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
env=git_subprocess_env())
detached_ref = p.communicate()[0].decode("utf-8").rstrip('\n')
if p.returncode == 0:
branch = u'{} {}'.format(RepoStats.symbols['detached'], detached_ref)
else:
branch = 'Big Bang'
return branch
def parse_git_stats(status):
stats = RepoStats()
for statusline in status[1:]:
code = statusline[:2]
if code == '??':
stats.untracked += 1
elif code in ('DD', 'AU', 'UD', 'UA', 'DU', 'AA', 'UU'):
stats.conflicted += 1
else:
if code[1] != ' ':
stats.not_staged += 1
if code[0] != ' ':
stats.staged += 1
return stats
def add_git_segment(powerline):
try:
p = subprocess.Popen(['git', 'status', '--porcelain', '-b'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
env=git_subprocess_env())
except OSError:
# Popen will throw an OSError if git is not found
return
pdata = p.communicate()
if p.returncode != 0:
return
status = pdata[0].decode("utf-8").splitlines()
stats = parse_git_stats(status)
branch_info = parse_git_branch_info(status)
if branch_info:
stats.ahead = branch_info["ahead"]
stats.behind = branch_info["behind"]
branch = branch_info['local']
else:
branch = _get_git_detached_branch()
bg = Color.REPO_CLEAN_BG
fg = Color.REPO_CLEAN_FG
if stats.dirty:
bg = Color.REPO_DIRTY_BG
fg = Color.REPO_DIRTY_FG
powerline.append(' %s ' % branch, fg, bg)
stats.add_to_powerline(powerline, Color)
add_git_segment(powerline)
import os
import subprocess
def get_hg_status():
has_modified_files = False
has_untracked_files = False
has_missing_files = False
p = subprocess.Popen(['hg', 'status'], stdout=subprocess.PIPE)
output = p.communicate()[0].decode("utf-8")
for line in output.split('\n'):
if line == '':
continue
elif line[0] == '?':
has_untracked_files = True
elif line[0] == '!':
has_missing_files = True
else:
has_modified_files = True
return has_modified_files, has_untracked_files, has_missing_files
def add_hg_segment(powerline):
branch = os.popen('hg branch 2> /dev/null').read().rstrip()
if len(branch) == 0:
return False
bg = Color.REPO_CLEAN_BG
fg = Color.REPO_CLEAN_FG
has_modified_files, has_untracked_files, has_missing_files = get_hg_status()
if has_modified_files or has_untracked_files or has_missing_files:
bg = Color.REPO_DIRTY_BG
fg = Color.REPO_DIRTY_FG
extra = ''
if has_untracked_files:
extra += '+'
if has_missing_files:
extra += '!'
branch += (' ' + extra if extra != '' else '')
return powerline.append(' %s ' % branch, fg, bg)
add_hg_segment(powerline)
import subprocess
def _add_svn_segment(powerline):
is_svn = subprocess.Popen(['svn', 'status'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
is_svn_output = is_svn.communicate()[1].decode("utf-8").strip()
if len(is_svn_output) != 0:
return
#"svn status | grep -c "^[ACDIMRX\\!\\~]"
p1 = subprocess.Popen(['svn', 'status'], stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
p2 = subprocess.Popen(['grep', '-c', '^[ACDIMR\\!\\~]'],
stdin=p1.stdout, stdout=subprocess.PIPE)
output = p2.communicate()[0].decode("utf-8").strip()
if len(output) > 0 and int(output) > 0:
changes = output.strip()
powerline.append(' %s ' % changes, Color.SVN_CHANGES_FG, Color.SVN_CHANGES_BG)
def add_svn_segment(powerline):
"""Wraps _add_svn_segment in exception handling."""
# FIXME This function was added when introducing a testing framework,
# during which the 'powerline' object was passed into the
# `add_[segment]_segment` functions instead of being a global variable. At
# that time it was unclear whether the below exceptions could actually be
# thrown. It would be preferable to find out whether they ever will. If so,
# write a comment explaining when. Otherwise remove.
try:
_add_svn_segment(powerline)
except OSError:
pass
except subprocess.CalledProcessError:
pass
add_svn_segment(powerline)
import os
import subprocess
def get_fossil_status():
has_modified_files = False
has_untracked_files = False
has_missing_files = False
output = os.popen('fossil changes 2>/dev/null').read().strip()
has_untracked_files = True if os.popen("fossil extras 2>/dev/null").read().strip() else False
has_missing_files = 'MISSING' in output
has_modified_files = 'EDITED' in output
return has_modified_files, has_untracked_files, has_missing_files
def _add_fossil_segment(powerline):
subprocess.Popen(['fossil'], stdout=subprocess.PIPE).communicate()[0]
branch = ''.join([i.replace('*','').strip() for i in os.popen("fossil branch 2> /dev/null").read().strip().split("\n") if i.startswith('*')])
if len(branch) == 0:
return
bg = Color.REPO_CLEAN_BG
fg = Color.REPO_CLEAN_FG
has_modified_files, has_untracked_files, has_missing_files = get_fossil_status()
if has_modified_files or has_untracked_files or has_missing_files:
bg = Color.REPO_DIRTY_BG
fg = Color.REPO_DIRTY_FG
extra = ''
if has_untracked_files:
extra += '+'
if has_missing_files:
extra += '!'
branch += (' ' + extra if extra != '' else '')
powerline.append(' %s ' % branch, fg, bg)
def add_fossil_segment(powerline):
"""Wraps _add_fossil_segment in exception handling."""
# FIXME This function was added when introducing a testing framework,
# during which the 'powerline' object was passed into the
# `add_[segment]_segment` functions instead of being a global variable. At
# that time it was unclear whether the below exceptions could actually be
# thrown. It would be preferable to find out whether they ever will. If so,
# write a comment explaining when. Otherwise remove.
try:
_add_fossil_segment(powerline)
except OSError:
pass
except subprocess.CalledProcessError:
pass
add_fossil_segment(powerline)
import os
import re
import subprocess
def add_jobs_segment(powerline):
pppid_proc = subprocess.Popen(['ps', '-p', str(os.getppid()), '-oppid='],
stdout=subprocess.PIPE)
pppid = pppid_proc.communicate()[0].decode("utf-8").strip()
output_proc = subprocess.Popen(['ps', '-a', '-o', 'ppid'],
stdout=subprocess.PIPE)
output = output_proc.communicate()[0].decode("utf-8")
num_jobs = len(re.findall(str(pppid), output)) - 1
if num_jobs > 0:
powerline.append(' %d ' % num_jobs, Color.JOBS_FG, Color.JOBS_BG)
add_jobs_segment(powerline)
def add_root_segment(powerline):
root_indicators = {
'bash': ' \\$ ',
'zsh': ' %# ',
'bare': ' $ ',
}
bg = Color.CMD_PASSED_BG
fg = Color.CMD_PASSED_FG
if powerline.args.prev_error != 0:
fg = Color.CMD_FAILED_FG
bg = Color.CMD_FAILED_BG
powerline.append(root_indicators[powerline.args.shell], fg, bg)
add_root_segment(powerline)
sys.stdout.write(powerline.draw())

View file

@ -0,0 +1,209 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import argparse
import os
import sys
py3 = sys.version_info.major == 3
def warn(msg):
print('[powerline-bash] ', msg)
if py3:
def unicode(x):
return x
class Powerline:
symbols = {
'compatible': {
'lock': 'RO',
'network': 'SSH',
'separator': u'\u25B6',
'separator_thin': u'\u276F'
},
'patched': {
'lock': u'\uE0A2',
'network': u'\uE0A2',
'separator': u'\uE0B0',
'separator_thin': u'\uE0B1'
},
'flat': {
'lock': '',
'network': '',
'separator': '',
'separator_thin': ''
},
}
color_templates = {
'bash': '\\[\\e%s\\]',
'zsh': '%%{%s%%}',
'bare': '%s',
}
def __init__(self, args, cwd):
self.args = args
self.cwd = cwd
mode, shell = args.mode, args.shell
self.color_template = self.color_templates[shell]
self.reset = self.color_template % '[0m'
self.lock = Powerline.symbols[mode]['lock']
self.network = Powerline.symbols[mode]['network']
self.separator = Powerline.symbols[mode]['separator']
self.separator_thin = Powerline.symbols[mode]['separator_thin']
self.segments = []
def color(self, prefix, code):
if code is None:
return ''
else:
return self.color_template % ('[%s;5;%sm' % (prefix, code))
def fgcolor(self, code):
return self.color('38', code)
def bgcolor(self, code):
return self.color('48', code)
def append(self, content, fg, bg, separator=None, separator_fg=None):
self.segments.append((content, fg, bg,
separator if separator is not None else self.separator,
separator_fg if separator_fg is not None else bg))
def draw(self):
text = (''.join(self.draw_segment(i) for i in range(len(self.segments)))
+ self.reset) + ' '
if py3:
return text
else:
return text.encode('utf-8')
def draw_segment(self, idx):
segment = self.segments[idx]
next_segment = self.segments[idx + 1] if idx < len(self.segments)-1 else None
return ''.join((
self.fgcolor(segment[1]),
self.bgcolor(segment[2]),
segment[0],
self.bgcolor(next_segment[2]) if next_segment else self.reset,
self.fgcolor(segment[4]),
segment[3]))
class RepoStats:
symbols = {
'detached': u'\u2693',
'ahead': u'\u2B06',
'behind': u'\u2B07',
'staged': u'\u2714',
'not_staged': u'\u270E',
'untracked': u'\u003F',
'conflicted': u'\u273C'
}
def __init__(self):
self.ahead = 0
self.behind = 0
self.untracked = 0
self.not_staged = 0
self.staged = 0
self.conflicted = 0
@property
def dirty(self):
qualifiers = [
self.untracked,
self.not_staged,
self.staged,
self.conflicted,
]
return sum(qualifiers) > 0
def __getitem__(self, _key):
return getattr(self, _key)
def n_or_empty(self, _key):
"""Given a string name of one of the properties of this class, returns
the value of the property as a string when the value is greater than
1. When it is not greater than one, returns an empty string.
As an example, if you want to show an icon for untracked files, but you
only want a number to appear next to the icon when there are more than
one untracked files, you can do:
segment = repo_stats.n_or_empty("untracked") + icon_string
"""
return unicode(self[_key]) if int(self[_key]) > 1 else u''
def add_to_powerline(self, powerline, color):
def add(_key, fg, bg):
if self[_key]:
s = u" {}{} ".format(self.n_or_empty(_key), self.symbols[_key])
powerline.append(s, fg, bg)
add('ahead', color.GIT_AHEAD_FG, color.GIT_AHEAD_BG)
add('behind', color.GIT_BEHIND_FG, color.GIT_BEHIND_BG)
add('staged', color.GIT_STAGED_FG, color.GIT_STAGED_BG)
add('not_staged', color.GIT_NOTSTAGED_FG, color.GIT_NOTSTAGED_BG)
add('untracked', color.GIT_UNTRACKED_FG, color.GIT_UNTRACKED_BG)
add('conflicted', color.GIT_CONFLICTED_FG, color.GIT_CONFLICTED_BG)
def get_valid_cwd():
""" We check if the current working directory is valid or not. Typically
happens when you checkout a different branch on git that doesn't have
this directory.
We return the original cwd because the shell still considers that to be
the working directory, so returning our guess will confuse people
"""
# Prefer the PWD environment variable. Python's os.getcwd function follows
# symbolic links, which is undesirable. But if PWD is not set then fall
# back to this func
try:
cwd = os.getenv('PWD') or os.getcwd()
except:
warn("Your current directory is invalid. If you open a ticket at " +
"https://github.com/milkbikis/powerline-shell/issues/new " +
"we would love to help fix the issue.")
sys.stdout.write("> ")
sys.exit(1)
parts = cwd.split(os.sep)
up = cwd
while parts and not os.path.exists(up):
parts.pop()
up = os.sep.join(parts)
if cwd != up:
warn("Your current directory is invalid. Lowest valid directory: "
+ up)
return cwd
if __name__ == "__main__":
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('--cwd-mode', action='store',
help='How to display the current directory', default='fancy',
choices=['fancy', 'plain', 'dironly'])
arg_parser.add_argument('--cwd-only', action='store_true',
help='Deprecated. Use --cwd-mode=dironly')
arg_parser.add_argument('--cwd-max-depth', action='store', type=int,
default=5, help='Maximum number of directories to show in path')
arg_parser.add_argument('--cwd-max-dir-size', action='store', type=int,
help='Maximum number of letters displayed for each directory in the path')
arg_parser.add_argument('--colorize-hostname', action='store_true',
help='Colorize the hostname based on a hash of itself.')
arg_parser.add_argument('--mode', action='store', default='patched',
help='The characters used to make separators between segments',
choices=['patched', 'compatible', 'flat'])
arg_parser.add_argument('--shell', action='store', default='bash',
help='Set this to your shell type', choices=['bash', 'zsh', 'bare'])
arg_parser.add_argument('prev_error', nargs='?', type=int, default=0,
help='Error code returned by the last command')
args = arg_parser.parse_args()
powerline = Powerline(args, get_valid_cwd())