#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import print_function import argparse import os import sys import importlib import json from .utils import warn, py3, import_file import re def _current_dir(): """Returns the full current working directory as the user would have used in their shell (ie. without following symbolic links). With the introduction of Bash for Windows, we can't use the PWD environment variable very easily. `os.sep` for windows is `\` but the PWD variable will use `/`. So just always use the `os` functions for dealing with paths. This also is fine because the use of PWD below is done to avoid following symlinks, which Windows doesn't have. For non-Windows systems, prefer the PWD environment variable. Python's `os.getcwd` function follows symbolic links, which is undesirable.""" if os.name == "nt": return os.getcwd() return os.getenv("PWD") or os.getcwd() def get_valid_cwd(): """Determine and check the current working directory for validity. Typically, an directory arises when you checkout a different branch on git that doesn't have this directory. When an invalid directory is found, a warning is printed to the screen, but the directory is still returned as-is, since this is what the shell considers to be the cwd.""" try: cwd = _current_dir() 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 class Powerline(object): symbols = { 'compatible': { 'lock': 'RO', 'network': 'SSH', 'separator': u'\u25B6', 'separator_thin': u'\u276F' }, 'patched': { 'lock': u'\uE0A2', 'network': 'SSH', 'separator': u'\uE0B0', 'separator_thin': u'\uE0B1' }, 'flat': { 'lock': u'\uE0A2', 'network': 'SSH', 'separator': '', 'separator_thin': '' }, } color_templates = { 'bash': r'\[\e%s\]', 'tcsh': r'%%{\e%s%%}', 'zsh': '%%{%s%%}', 'bare': '%s', } def __init__(self, args, config, theme): self.args = args self.config = config self.theme = theme self.cwd = get_valid_cwd() mode = config.get("mode", "patched") self.color_template = self.color_templates[args.shell] self.reset = self.color_template % '[0m\]\[\e[0K' 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 segment_conf(self, seg_name, key, default=None): return self.config.get(seg_name, {}).get(key, default) def color(self, prefix, code): if code is None: return '' elif code == self.theme.RESET: return self.reset 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, sanitize=True): if self.args.shell == "bash" and sanitize: content = re.sub(r"([`$])", r"\\\1", content) 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])) def find_config(): for location in [ "powerline-shell.json", "~/.powerline-shell.json", os.path.join(os.environ.get("XDG_CONFIG_HOME", "~/.config"), "powerline-shell", "config.json"), ]: full = os.path.expanduser(location) if os.path.exists(full): return full DEFAULT_CONFIG = { "segments": [ 'virtual_env', 'username', 'hostname', 'ssh', 'cwd', 'git', 'hg', 'jobs', 'root', ] } class ThemeNotFoundException(Exception): pass def read_theme(config): theme_name = config.get("theme", "default") try: mod = importlib.import_module("powerline_shell.themes." + theme_name) except ImportError: try: mod = import_file("custom_theme", os.path.expanduser(theme_name)) except ImportError: raise ThemeNotFoundException( "Theme " + theme_name + " cannot be found") return getattr(mod, "Color") def main(): arg_parser = argparse.ArgumentParser() arg_parser.add_argument('--generate-config', action='store_true', help='Generate the default config and print it to stdout') arg_parser.add_argument('--shell', action='store', default='bash', help='Set this to your shell type', choices=['bash', 'tcsh', '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() if args.generate_config: print(json.dumps(DEFAULT_CONFIG, indent=2)) return 0 config_path = find_config() if config_path: with open(config_path) as f: config = json.loads(f.read()) else: config = DEFAULT_CONFIG theme = read_theme(config) powerline = Powerline(args, config, theme) segments = [] for seg_name in config["segments"]: mod = importlib.import_module("powerline_shell.segments." + seg_name) segment = getattr(mod, "Segment")(powerline) segment.start() segments.append(segment) for segment in segments: segment.add_to_powerline() sys.stdout.write(powerline.draw()) return 0