209 lines
6.9 KiB
Python
Executable file
209 lines
6.9 KiB
Python
Executable file
#!/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())
|