From 0bece00c9a936bef77ee4808587a5e789ec80fab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Watteng=C3=A5rd?= Date: Sat, 7 Nov 2015 19:13:52 +0100 Subject: [PATCH 01/93] First commit of battery percentage segment --- segments/battery.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 segments/battery.py diff --git a/segments/battery.py b/segments/battery.py new file mode 100644 index 0000000..17d9fad --- /dev/null +++ b/segments/battery.py @@ -0,0 +1,22 @@ +def add_battery_segment(): + f = open('/sys/class/power_supply/BAT0/capacity') + cap = f.read().strip() + f.close() + + f = open('/sys/class/power_supply/BAT0/status') + status = f.read().strip() + f.close() + + if status == 'Charging': + pwr = u' \u21ea ' + else: + pwr = ' ' + + if int(cap) > 20: + bg = Color.HOME_BG + else: + bg = Color.READONLY_BG + + powerline.append(' ' + cap + '%' + pwr, Color.HOME_FG, bg) + +add_battery_segment() From a57ee0fa719781e7f18ed71b3a5d4cee0df8cd7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Watteng=C3=A5rd?= Date: Sat, 7 Nov 2015 19:31:33 +0100 Subject: [PATCH 02/93] Made separate color constants for battery --- segments/battery.py | 18 ++++++++++++------ themes/default.py | 5 +++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/segments/battery.py b/segments/battery.py index 17d9fad..ab12e0a 100644 --- a/segments/battery.py +++ b/segments/battery.py @@ -1,9 +1,13 @@ def add_battery_segment(): - f = open('/sys/class/power_supply/BAT0/capacity') + CAP_FILE = '/sys/class/power_supply/BAT0/capacity' + STATUS_FILE = '/sys/class/power_supply/BAT0/status' + LOW_BATTERY_THRESHOLD = 20 + + f = open(CAP_FILE) cap = f.read().strip() f.close() - f = open('/sys/class/power_supply/BAT0/status') + f = open(STATUS_FILE) status = f.read().strip() f.close() @@ -12,11 +16,13 @@ def add_battery_segment(): else: pwr = ' ' - if int(cap) > 20: - bg = Color.HOME_BG + if int(cap) < LOW_BATTERY_THRESHOLD: + bg = Color.BATTERY_LOW_BG + fg = Color.BATTERY_LOW_FG else: - bg = Color.READONLY_BG + bg = Color.BATTERY_NORMAL_BG + fg = Color.BATTERY_NORMAL_FG - powerline.append(' ' + cap + '%' + pwr, Color.HOME_FG, bg) + powerline.append(' ' + cap + '%' + pwr, fg, bg) add_battery_segment() diff --git a/themes/default.py b/themes/default.py index ba4e551..4d93a58 100644 --- a/themes/default.py +++ b/themes/default.py @@ -42,6 +42,11 @@ class DefaultColor: VIRTUAL_ENV_BG = 35 # a mid-tone green VIRTUAL_ENV_FG = 00 + + BATTERY_NORMAL_BG = 22 + BATTERY_NORMAL_FG = 7 + BATTERY_LOW_BG = 196 + BATTERY_LOW_FG = 7 class Color(DefaultColor): """ From 3098ce9f033d11f1bc0534e7e2dc9780ee116f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Watteng=C3=A5rd?= Date: Sat, 7 Nov 2015 20:08:36 +0100 Subject: [PATCH 03/93] =?UTF-8?q?Replaced=20charging=20symbol=20with=20lig?= =?UTF-8?q?htning=20bolt=20(=E2=9A=A1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- segments/battery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/segments/battery.py b/segments/battery.py index ab12e0a..10efe1a 100644 --- a/segments/battery.py +++ b/segments/battery.py @@ -12,7 +12,7 @@ def add_battery_segment(): f.close() if status == 'Charging': - pwr = u' \u21ea ' + pwr = u' \u26A1 ' else: pwr = ' ' From e5a1c749632788548fdac221159378497ab0a3d3 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sun, 27 Dec 2015 13:22:39 -0500 Subject: [PATCH 04/93] Moved all repo stats code from git.py to the base When all of the new code to provide more interesting stats was added to git.py, it was not very re-usable. A few pull requests could use it though. In particular, #105 adds more info to the svn segment and #210 does the same for mercurial. I would rather not create inconsistencies among these segments or have them duplicate code. Moving it into the base file creates a place where each segment can access it and have a consistent behavior. --- powerline_shell_base.py | 59 ++++++++++++++++++++++++++++++++++ segments/git.py | 48 +++++++-------------------- test/repo_stats_test.py | 22 +++++++++++++ test/segments_test/git_test.py | 5 ++- 4 files changed, 96 insertions(+), 38 deletions(-) create mode 100644 test/repo_stats_test.py diff --git a/powerline_shell_base.py b/powerline_shell_base.py index 8b95fb3..6a51c0d 100755 --- a/powerline_shell_base.py +++ b/powerline_shell_base.py @@ -90,6 +90,65 @@ class Powerline: 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'\u2753', + '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 (True if sum(qualifiers) > 0 else False) + + 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 diff --git a/segments/git.py b/segments/git.py index d33a9d3..0da777f 100644 --- a/segments/git.py +++ b/segments/git.py @@ -2,16 +2,6 @@ import re import subprocess import os -GIT_SYMBOLS = { - 'detached': u'\u2693', - 'ahead': u'\u2B06', - 'behind': u'\u2B07', - 'staged': u'\u2714', - 'notstaged': u'\u270E', - 'untracked': u'\u2753', - 'conflicted': u'\u273C' -} - def get_PATH(): """Normally gets the PATH from the OS. This function exists to enable easily mocking the PATH in tests. @@ -43,33 +33,29 @@ def _get_git_detached_branch(): env=git_subprocess_env()) detached_ref = p.communicate()[0].decode("utf-8").rstrip('\n') if p.returncode == 0: - branch = u'{} {}'.format(GIT_SYMBOLS['detached'], detached_ref) + branch = u'{} {}'.format(RepoStats.symbols['detached'], detached_ref) else: branch = 'Big Bang' return branch def parse_git_stats(status): - stats = {'untracked': 0, 'notstaged': 0, 'staged': 0, 'conflicted': 0} + stats = RepoStats() for statusline in status[1:]: code = statusline[:2] if code == '??': - stats['untracked'] += 1 + stats.untracked += 1 elif code in ('DD', 'AU', 'UD', 'UA', 'DU', 'AA', 'UU'): - stats['conflicted'] += 1 + stats.conflicted += 1 else: if code[1] != ' ': - stats['notstaged'] += 1 + stats.not_staged += 1 if code[0] != ' ': - stats['staged'] += 1 + stats.staged += 1 return stats -def _n_or_empty(_dict, _key): - return _dict[_key] if int(_dict[_key]) > 1 else u'' - - def add_git_segment(powerline): try: p = subprocess.Popen(['git', 'status', '--porcelain', '-b'], @@ -84,33 +70,21 @@ def add_git_segment(powerline): return status = pdata[0].decode("utf-8").splitlines() - - branch_info = parse_git_branch_info(status) stats = parse_git_stats(status) - dirty = (True if sum(stats.values()) > 0 else False) + 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 dirty: + if stats.dirty: bg = Color.REPO_DIRTY_BG fg = Color.REPO_DIRTY_FG powerline.append(' %s ' % branch, fg, bg) - - def _add(_dict, _key, fg, bg): - if _dict[_key]: - _str = u' {}{} '.format(_n_or_empty(_dict, _key), GIT_SYMBOLS[_key]) - powerline.append(_str, fg, bg) - - if branch_info: - _add(branch_info, 'ahead', Color.GIT_AHEAD_FG, Color.GIT_AHEAD_BG) - _add(branch_info, 'behind', Color.GIT_BEHIND_FG, Color.GIT_BEHIND_BG) - _add(stats, 'staged', Color.GIT_STAGED_FG, Color.GIT_STAGED_BG) - _add(stats, 'notstaged', Color.GIT_NOTSTAGED_FG, Color.GIT_NOTSTAGED_BG) - _add(stats, 'untracked', Color.GIT_UNTRACKED_FG, Color.GIT_UNTRACKED_BG) - _add(stats, 'conflicted', Color.GIT_CONFLICTED_FG, Color.GIT_CONFLICTED_BG) + stats.add_to_powerline(powerline, Color) diff --git a/test/repo_stats_test.py b/test/repo_stats_test.py new file mode 100644 index 0000000..bcd206a --- /dev/null +++ b/test/repo_stats_test.py @@ -0,0 +1,22 @@ +import unittest +import powerline_shell_base as p + + +class RepoStatsTest(unittest.TestCase): + + def setUp(self): + self.repo_stats = p.RepoStats() + self.repo_stats.not_staged = 1 + self.repo_stats.conflicted = 4 + + def test_simple(self): + self.assertEqual(self.repo_stats.untracked, 0) + + def test_n_or_empty__empty(self): + self.assertEqual(self.repo_stats.n_or_empty("not_staged"), u"") + + def test_n_or_empty__n(self): + self.assertEqual(self.repo_stats.n_or_empty("conflicted"), u"4") + + def test_index(self): + self.assertEqual(self.repo_stats["not_staged"], 1) diff --git a/test/segments_test/git_test.py b/test/segments_test/git_test.py index 982289e..7ca344a 100644 --- a/test/segments_test/git_test.py +++ b/test/segments_test/git_test.py @@ -3,14 +3,17 @@ import mock import tempfile import shutil import sh +import powerline_shell_base as p import segments.git as git +git.Color = mock.MagicMock() +git.RepoStats = p.RepoStats + class GitTest(unittest.TestCase): def setUp(self): self.powerline = mock.MagicMock() - git.Color = mock.MagicMock() self.dirname = tempfile.mkdtemp() sh.cd(self.dirname) From 7681a384bba55736954138a9bda5bb978158fe79 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Fri, 1 Apr 2016 14:25:05 -0400 Subject: [PATCH 05/93] no need for a ternary --- powerline_shell_base.py | 2 +- test/repo_stats_test.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/powerline_shell_base.py b/powerline_shell_base.py index 6a51c0d..896693a 100755 --- a/powerline_shell_base.py +++ b/powerline_shell_base.py @@ -118,7 +118,7 @@ class RepoStats: self.staged, self.conflicted, ] - return (True if sum(qualifiers) > 0 else False) + return sum(qualifiers) > 0 def __getitem__(self, _key): return getattr(self, _key) diff --git a/test/repo_stats_test.py b/test/repo_stats_test.py index bcd206a..c97a089 100644 --- a/test/repo_stats_test.py +++ b/test/repo_stats_test.py @@ -9,6 +9,9 @@ class RepoStatsTest(unittest.TestCase): self.repo_stats.not_staged = 1 self.repo_stats.conflicted = 4 + def test_dirty(self): + self.assertTrue(self.repo_stats.dirty) + def test_simple(self): self.assertEqual(self.repo_stats.untracked, 0) From 75b95a3a10a19018f9d75ee40242b0992ab3b9f5 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Fri, 1 Apr 2016 14:31:13 -0400 Subject: [PATCH 06/93] move changelog to own file, add changelog for #221 --- CHANGELOG.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 78 ------------------------------------------------- 2 files changed, 82 insertions(+), 78 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..857583e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,82 @@ +# Changes + +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)) diff --git a/README.md b/README.md index 082c1a6..1bcec17 100644 --- a/README.md +++ b/README.md @@ -168,81 +168,3 @@ 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. - -# Changes - -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)) From cc05f820df5e63f510cfa21a5ac5b016577eed2b Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 16 Apr 2016 09:55:33 -0400 Subject: [PATCH 07/93] fix unicode issue for py3 --- powerline_shell_base.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/powerline_shell_base.py b/powerline_shell_base.py index 896693a..38d018c 100755 --- a/powerline_shell_base.py +++ b/powerline_shell_base.py @@ -13,6 +13,11 @@ def warn(msg): print('[powerline-bash] ', msg) +if py3: + def unicode(x): + return x + + class Powerline: symbols = { 'compatible': { From d48f383112f5520c4c33d69dca0806e127f495d7 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 16 Apr 2016 09:56:12 -0400 Subject: [PATCH 08/93] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 857583e..2a1cef3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # 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. From ce1fb672840268c564652c3d95de972fe73318f4 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Sun, 1 May 2016 06:30:37 +0900 Subject: [PATCH 09/93] Use CWD_FG for last path component --- segments/cwd.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/segments/cwd.py b/segments/cwd.py index 3c80b70..9dd38ba 100644 --- a/segments/cwd.py +++ b/segments/cwd.py @@ -37,12 +37,16 @@ def maybe_shorten_name(powerline, name): return name -def get_fg_bg(name): +def get_fg_bg(name, is_last_dir): """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,) + + if is_last_dir: + return (Color.CWD_FG, Color.PATH_BG,) + else: + return (Color.PATH_FG, Color.PATH_BG,) def add_cwd_segment(powerline): @@ -77,11 +81,11 @@ def add_cwd_segment(powerline): names = names[-1:] for i, name in enumerate(names): - fg, bg = get_fg_bg(name) + is_last_dir = (i == len(names) - 1) + fg, bg = get_fg_bg(name, is_last_dir) 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 From 168b4a49a36719a2e8abac7e1cd523aee2bcdb4a Mon Sep 17 00:00:00 2001 From: Michael Wild Date: Mon, 24 Oct 2016 17:10:47 +0200 Subject: [PATCH 10/93] Fixes segments/set_term_title.py for ZSH The %{ and %} quoting was missing that instructs ZSH to consider this part to have zero width. This broke history searching and long command lines. See e.g. http://stackoverflow.com/a/11916552/159834 --- segments/set_term_title.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/segments/set_term_title.py b/segments/set_term_title.py index e9954ae..7c3e826 100644 --- a/segments/set_term_title.py +++ b/segments/set_term_title.py @@ -6,7 +6,7 @@ def add_set_term_title_segment(powerline): 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' + 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')) From e157d1e57996457f0d76862866c8871aef1efab9 Mon Sep 17 00:00:00 2001 From: Michael Wild Date: Mon, 24 Oct 2016 17:02:34 +0200 Subject: [PATCH 11/93] Adds Cygwin-specific handling in segments/jobs.py --- segments/jobs.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/segments/jobs.py b/segments/jobs.py index a6ff180..2e0f9dc 100644 --- a/segments/jobs.py +++ b/segments/jobs.py @@ -1,17 +1,30 @@ import os import re import subprocess +import platform 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() + num_jobs = 0 - output_proc = subprocess.Popen(['ps', '-a', '-o', 'ppid'], - stdout=subprocess.PIPE) - output = output_proc.communicate()[0].decode("utf-8") + if platform.system().startswith('CYGWIN'): + # cygwin ps is a special snowflake... + output_proc = subprocess.Popen(['ps', '-af'], stdout=subprocess.PIPE) + output = map(lambda l: int(l.split()[2].strip()), + output_proc.communicate()[0].decode("utf-8").splitlines()[1:]) - num_jobs = len(re.findall(str(pppid), output)) - 1 + num_jobs = output.count(os.getppid()) - 1 + + else: + + 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) From 4a2015916604471a93ff0c426644087f3aee3ad0 Mon Sep 17 00:00:00 2001 From: Dror Atariah Date: Tue, 6 Dec 2016 13:30:51 +0100 Subject: [PATCH 12/93] Fixed problem with conda environments See https://github.com/powerline/powerline/issues/1692 --- segments/virtual_env.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/segments/virtual_env.py b/segments/virtual_env.py index 710efc6..35a368f 100644 --- a/segments/virtual_env.py +++ b/segments/virtual_env.py @@ -1,7 +1,7 @@ import os def add_virtual_env_segment(powerline): - env = os.getenv('VIRTUAL_ENV') or os.getenv('CONDA_ENV_PATH') + env = os.getenv('VIRTUAL_ENV') or os.getenv('CONDA_ENV_PATH') or os.getenv('CONDA_DEFAULT_ENV') if env is None: return From a253a72658bc7df220f0cde17a60020efedc4438 Mon Sep 17 00:00:00 2001 From: Diogo Autilio Date: Wed, 11 Jan 2017 01:56:29 -0200 Subject: [PATCH 13/93] Add rbenv segment --- config.py.dist | 3 +++ segments/rbenv.py | 13 +++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 segments/rbenv.py diff --git a/config.py.dist b/config.py.dist index a1e7627..55e894c 100644 --- a/config.py.dist +++ b/config.py.dist @@ -13,6 +13,9 @@ SEGMENTS = [ # Show current virtual environment (see http://www.virtualenv.org/) 'virtual_env', +# Show current ruby environment (see http://rbenv.org/) + 'rbenv', + # Show the current user's username as in ordinary prompts 'username', diff --git a/segments/rbenv.py b/segments/rbenv.py new file mode 100644 index 0000000..70e34a8 --- /dev/null +++ b/segments/rbenv.py @@ -0,0 +1,13 @@ +import subprocess + + +def add_rbenv_segment(powerline): + try: + p1 = subprocess.Popen(["rbenv", "local"], stdout=subprocess.PIPE) + version = p1.communicate()[0].decode("utf-8").rstrip() + if len(version) <= 0: + return + + powerline.append(' %s ' % version, Color.VIRTUAL_ENV_FG, Color.VIRTUAL_ENV_BG) + except OSError: + return From 724e8303a80f17c83128b5876dbb3d95c106805c Mon Sep 17 00:00:00 2001 From: Lehman Black Date: Tue, 28 Mar 2017 08:41:54 -0500 Subject: [PATCH 14/93] Add segment for npm version --- segments/npm_version.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 segments/npm_version.py diff --git a/segments/npm_version.py b/segments/npm_version.py new file mode 100644 index 0000000..0deb5f8 --- /dev/null +++ b/segments/npm_version.py @@ -0,0 +1,11 @@ +import subprocess + + +def add_npm_version_segment(powerline): + try: + p1 = subprocess.Popen(["npm", "--version"], stdout=subprocess.PIPE) + version = p1.communicate()[0].decode("utf-8").rstrip() + version = "npm " + version + powerline.append(version, 15, 18) + except OSError: + return From 400c29a3a8a8396bc1ce650e522ff090fc007c79 Mon Sep 17 00:00:00 2001 From: Florian Friedrich Date: Tue, 4 Apr 2017 14:34:17 +0200 Subject: [PATCH 15/93] Add newline segment --- powerline_shell_base.py | 2 ++ segments/newline.py | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 segments/newline.py diff --git a/powerline_shell_base.py b/powerline_shell_base.py index 38d018c..8ed2826 100755 --- a/powerline_shell_base.py +++ b/powerline_shell_base.py @@ -61,6 +61,8 @@ class Powerline: def color(self, prefix, code): if code is None: return '' + elif code is -1: + return self.reset else: return self.color_template % ('[%s;5;%sm' % (prefix, code)) diff --git a/segments/newline.py b/segments/newline.py new file mode 100644 index 0000000..eef4dc7 --- /dev/null +++ b/segments/newline.py @@ -0,0 +1,2 @@ +def add_newline_segment(powerline): + powerline.append("\n", -1, -1, '') From 30f2124db18e2d186ee7b79fbf60aa7d7ab0230a Mon Sep 17 00:00:00 2001 From: Florian Friedrich Date: Tue, 4 Apr 2017 14:35:33 +0200 Subject: [PATCH 16/93] Add example of newline to config.py.dist --- config.py.dist | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config.py.dist b/config.py.dist index a1e7627..c005dea 100644 --- a/config.py.dist +++ b/config.py.dist @@ -48,6 +48,9 @@ SEGMENTS = [ # Show the last command's exit code if it was non-zero # 'exit_code', +# Adds a line break +# 'newline', + # Shows a '#' if the current user is root, '$' otherwise # Also, changes color if the last command exited with a non-zero error code 'root', From 079cbc6cbf58f06e0ab7a87e2d80452f91cdd0ca Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Mon, 15 May 2017 11:49:21 -0400 Subject: [PATCH 17/93] changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a1cef3..63f3a19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changes +2017-05-15 + +* Fix the `set_term_title` segment for ZSH + ([@themiwi](https://github.com/banga/powerline-shell/pull/255)) + 2016-04-16 * Fix issue around unicode function for python 3 From cbdb5e9547d3db8b22d1d48718bc64605c92b04f Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Tue, 20 Jun 2017 11:07:18 -0400 Subject: [PATCH 18/93] readability improvements for #266 --- powerline_shell_base.py | 2 +- segments/newline.py | 2 +- themes/default.py | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/powerline_shell_base.py b/powerline_shell_base.py index 8ed2826..5d367a5 100755 --- a/powerline_shell_base.py +++ b/powerline_shell_base.py @@ -61,7 +61,7 @@ class Powerline: def color(self, prefix, code): if code is None: return '' - elif code is -1: + elif code == Color.RESET: return self.reset else: return self.color_template % ('[%s;5;%sm' % (prefix, code)) diff --git a/segments/newline.py b/segments/newline.py index eef4dc7..e9b4345 100644 --- a/segments/newline.py +++ b/segments/newline.py @@ -1,2 +1,2 @@ def add_newline_segment(powerline): - powerline.append("\n", -1, -1, '') + powerline.append("\nabc\n", Color.RESET, Color.RESET, separator='') diff --git a/themes/default.py b/themes/default.py index 08b1ff4..6526158 100644 --- a/themes/default.py +++ b/themes/default.py @@ -3,6 +3,11 @@ class DefaultColor: This class should have the default colors for every segment. Please test every new segment with this theme first. """ + # RESET is not a real color code. It is used as in indicator + # within the code that any foreground / background color should + # be cleared + RESET = -1 + USERNAME_FG = 250 USERNAME_BG = 240 USERNAME_ROOT_BG = 124 From 53fd55760bff3be87b8cc3cf06ef2524d91829f1 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Tue, 20 Jun 2017 11:12:54 -0400 Subject: [PATCH 19/93] changelog for #266 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63f3a19..cc7b03e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changes +2017-06-20 + +* Add `newline` segment + ([@ffried](https://github.com/banga/powerline-shell/pull/266)) + 2017-05-15 * Fix the `set_term_title` segment for ZSH From 0a367b24673f5c16b936f85a8dd3aaad097a7afe Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Tue, 20 Jun 2017 14:25:33 -0400 Subject: [PATCH 20/93] changelog for #265 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc7b03e..4498530 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ * Add `newline` segment ([@ffried](https://github.com/banga/powerline-shell/pull/266)) +* Add `npm_version` segment + ([@WileESpaghetti](https://github.com/banga/powerline-shell/pull/265)) 2017-05-15 From 0bf761c5c42f5a516f27e975470105af251d389f Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Tue, 20 Jun 2017 14:32:20 -0400 Subject: [PATCH 21/93] changelog for #257 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4498530..bfb4de0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ([@ffried](https://github.com/banga/powerline-shell/pull/266)) * Add `npm_version` segment ([@WileESpaghetti](https://github.com/banga/powerline-shell/pull/265)) +* Fix issue with conda environments + ([@drorata](https://github.com/banga/powerline-shell/pull/257)) 2017-05-15 From f75286c0df16d053bec64decf9c1661038e91b2b Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Tue, 20 Jun 2017 15:02:32 -0400 Subject: [PATCH 22/93] lint the readme and add TOC --- README.md | 108 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 72 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 1bcec17..36a9658 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,105 @@ -A Powerline style prompt for your shell -======================================= +# A Powerline style prompt for your shell -A [Powerline](https://github.com/Lokaltog/vim-powerline) like prompt for Bash, ZSH and Fish: +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. +- 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 + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Version Control](#version-control) +- [Setup](#setup) + - [All Shells](#all-shells) + - [Bash](#bash) + - [ZSH](#zsh) + - [Fish](#fish) +- [Customization](#customization) + - [Adding, Removing and Re-arranging segments](#adding-removing-and-re-arranging-segments) + - [Contributing new types of segments](#contributing-new-types-of-segments) + - [Themes](#themes) + + + +## 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 +- 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 +- 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 +- `✎` -- 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) + +- 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 +## 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 +- Patch the font you use for your terminal: see + [powerline-fonts](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 - * 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: -* Clone this repository somewhere: +``` +git clone https://github.com/milkbikis/powerline-shell +``` - 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 -* Copy `config.py.dist` to `config.py` and edit it to configure the segments you want. Then run +``` +./install.py +``` - ./install.py +This will generate `powerline-shell.py` - * This will generate `powerline-shell.py` +- (optional) Create a symlink to this python script in your home: -* (optional) Create a symlink to this python script in your home: +``` +ln -s ~/powerline-shell.py +``` - ln -s ~/powerline-shell.py +If you don't want the symlink, just modify the path in the commands below - * If you don't want the symlink, just modify the path in the commands below +- For python2.6 you have to install argparse -* For python2.6 you have to install argparse +``` +pip install argparse +``` - pip install argparse +### All Shells -### All Shells: -There are a few optional arguments which can be seen by running `powerline-shell.py --help`. +There are a few optional arguments which can be seen by running +`powerline-shell.py --help`. ``` --cwd-mode {fancy,plain,dironly} @@ -82,7 +115,8 @@ There are a few optional arguments which can be seen by running `powerline-shell segments ``` -### Bash: +### Bash + Add the following to your `.bashrc` (or `.profile` on Mac): ``` @@ -95,7 +129,8 @@ if [ "$TERM" != "linux" ]; then fi ``` -### ZSH: +### ZSH + Add the following to your `.zshrc`: ``` @@ -117,7 +152,8 @@ if [ "$TERM" != "linux" ]; then fi ``` -### Fish: +### Fish + Redefine `fish_prompt` in ~/.config/fish/config.fish: ``` @@ -126,7 +162,7 @@ function fish_prompt end ``` -# Customization +## Customization ### Adding, Removing and Re-arranging segments From d0db198e82c38199312ae9583191ca262f2cc484 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Tue, 20 Jun 2017 15:06:37 -0400 Subject: [PATCH 23/93] add screenshot back --- README.md | 2 +- bash-powerline-screenshot.png | Bin 0 -> 35843 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 bash-powerline-screenshot.png diff --git a/README.md b/README.md index 36a9658..1f49b43 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ 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) +![MacVim+Solarized+Powerline+CtrlP](https://raw.github.com/banga/powerline-shell/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 diff --git a/bash-powerline-screenshot.png b/bash-powerline-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..66a3b244627ee83a4f58bd6e7c5afcef82c24e5a GIT binary patch literal 35843 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 E05 Date: Tue, 20 Jun 2017 15:08:34 -0400 Subject: [PATCH 24/93] link to the FAQ --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 1f49b43..2154a26 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ ZSH and Fish: - [Adding, Removing and Re-arranging segments](#adding-removing-and-re-arranging-segments) - [Contributing new types of segments](#contributing-new-types-of-segments) - [Themes](#themes) +- [Troubleshooting](#troubleshooting) @@ -204,3 +205,9 @@ 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. + +## Troubleshooting + +See the [FAQ](https://github.com/banga/powerline-shell/wiki/FAQ). If you +continue to have issues, please open an +[issue](https://github.com/banga/powerline-shell/issues/new). From 0db94318c5bffd15964a99e919ab64ba21402a9c Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Tue, 20 Jun 2017 15:55:14 -0400 Subject: [PATCH 25/93] changelog for #256 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfb4de0..b1af8e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ([@WileESpaghetti](https://github.com/banga/powerline-shell/pull/265)) * Fix issue with conda environments ([@drorata](https://github.com/banga/powerline-shell/pull/257)) +* Fix jobs segment for Cygwin + ([@themiwi](https://github.com/banga/powerline-shell/pull/256)) 2017-05-15 From 2492c8d10b84500040d0760fbb48617e2f0fcfe0 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Wed, 21 Jun 2017 14:12:58 -0400 Subject: [PATCH 26/93] changelog and disable rbenv by default for #260 --- CHANGELOG.md | 5 +++++ config.py.dist | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1af8e7..02598c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changes +2017-06-21 + +* Add `rbenv` segment + ([@dogo](https://github.com/banga/powerline-shell/pull/260)) + 2017-06-20 * Add `newline` segment diff --git a/config.py.dist b/config.py.dist index 196e0c2..2affa3e 100644 --- a/config.py.dist +++ b/config.py.dist @@ -14,7 +14,7 @@ SEGMENTS = [ 'virtual_env', # Show current ruby environment (see http://rbenv.org/) - 'rbenv', +# 'rbenv', # Show the current user's username as in ordinary prompts 'username', From f63ab2fd5c20a606046cbe4628637a00c085e74b Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Wed, 21 Jun 2017 17:24:09 -0400 Subject: [PATCH 27/93] changelog for #235 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02598c8..69fdcf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ * Add `rbenv` segment ([@dogo](https://github.com/banga/powerline-shell/pull/260)) +* Fix path segment so that current directory is emphasized + ([@inamiy](https://github.com/banga/powerline-shell/pull/235)) 2017-06-20 From 800b9cdac77ff0dccd88b5dfc8c81a478ed1c544 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 24 Jun 2017 08:29:54 -0400 Subject: [PATCH 28/93] fix stupid abc mistake in newline segment --- segments/newline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/segments/newline.py b/segments/newline.py index e9b4345..a3f7261 100644 --- a/segments/newline.py +++ b/segments/newline.py @@ -1,2 +1,2 @@ def add_newline_segment(powerline): - powerline.append("\nabc\n", Color.RESET, Color.RESET, separator='') + powerline.append("\n", Color.RESET, Color.RESET, separator='') From f4908f3ed672b906b04cfa771d322821037cba69 Mon Sep 17 00:00:00 2001 From: Jonathan Dowland Date: Mon, 10 Jul 2017 16:08:22 +0100 Subject: [PATCH 29/93] remove duplicate hexstr2num hexstr2num was defined (identically) twice. --- lib/colortrans.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/colortrans.py b/lib/colortrans.py index 0e2922c..1c38552 100755 --- a/lib/colortrans.py +++ b/lib/colortrans.py @@ -281,10 +281,6 @@ RGB2SHORT_DICT = { (255, 255, 215): 230, (255, 255, 255): 231} - -def hexstr2num(hexstr): - return int(hexstr, 16) - def rgb2short(r, g, b): """ Find the closest xterm-256 approximation to the given RGB value. @param r,g,b: each is a number between 0-255 for the Red, Green, and Blue values From 2989b1e406aab612d6228b869a644947ac6372b7 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 26 Aug 2017 15:49:02 -0400 Subject: [PATCH 30/93] convert all the code into a proper package --- .gitignore | 1 + install.py | 49 ---------- .../__init__.py | 89 +++++-------------- {lib => powerline_shell}/color_compliment.py | 2 +- {lib => powerline_shell}/colortrans.py | 0 powerline_shell/repos.py | 56 ++++++++++++ {lib => powerline_shell/segments}/__init__.py | 0 {segments => powerline_shell/segments}/cwd.py | 24 ++--- .../segments}/exit_code.py | 4 +- .../segments}/fossil.py | 8 +- {segments => powerline_shell/segments}/git.py | 13 +-- {segments => powerline_shell/segments}/hg.py | 8 +- .../segments}/hostname.py | 2 +- .../segments}/jobs.py | 2 +- powerline_shell/segments/newline.py | 2 + .../segments}/node_version.py | 0 .../segments}/npm_version.py | 0 .../segments}/php_version.py | 0 .../segments}/rbenv.py | 2 +- .../segments}/read_only.py | 2 +- .../segments}/root.py | 8 +- .../segments}/ruby_version.py | 0 .../segments}/set_term_title.py | 0 powerline_shell/segments/ssh.py | 6 ++ {segments => powerline_shell/segments}/svn.py | 2 +- .../segments}/time.py | 2 +- .../segments}/uptime.py | 2 +- .../segments}/username.py | 6 +- .../segments}/virtual_env.py | 4 +- .../themes}/__init__.py | 0 {themes => powerline_shell/themes}/basic.py | 0 .../themes}/colortest.py | 0 {themes => powerline_shell/themes}/default.py | 0 .../themes}/solarized-dark.py | 0 {themes => powerline_shell/themes}/washed.py | 0 segments/newline.py | 2 - segments/ssh.py | 6 -- setup.py | 19 ++++ test/repo_stats_test.py | 1 + 39 files changed, 154 insertions(+), 168 deletions(-) delete mode 100755 install.py rename powerline_shell_base.py => powerline_shell/__init__.py (70%) mode change 100755 => 100644 rename {lib => powerline_shell}/color_compliment.py (97%) mode change 100755 => 100644 rename {lib => powerline_shell}/colortrans.py (100%) create mode 100644 powerline_shell/repos.py rename {lib => powerline_shell/segments}/__init__.py (100%) rename {segments => powerline_shell/segments}/cwd.py (77%) rename {segments => powerline_shell/segments}/exit_code.py (67%) rename {segments => powerline_shell/segments}/fossil.py (91%) rename {segments => powerline_shell/segments}/git.py (91%) rename {segments => powerline_shell/segments}/hg.py (87%) rename {segments => powerline_shell/segments}/hostname.py (88%) rename {segments => powerline_shell/segments}/jobs.py (91%) create mode 100644 powerline_shell/segments/newline.py rename {segments => powerline_shell/segments}/node_version.py (100%) rename {segments => powerline_shell/segments}/npm_version.py (100%) rename {segments => powerline_shell/segments}/php_version.py (100%) rename {segments => powerline_shell/segments}/rbenv.py (72%) rename {segments => powerline_shell/segments}/read_only.py (54%) rename {segments => powerline_shell/segments}/root.py (59%) rename {segments => powerline_shell/segments}/ruby_version.py (100%) rename {segments => powerline_shell/segments}/set_term_title.py (100%) create mode 100644 powerline_shell/segments/ssh.py rename {segments => powerline_shell/segments}/svn.py (92%) rename {segments => powerline_shell/segments}/time.py (73%) rename {segments => powerline_shell/segments}/uptime.py (89%) rename {segments => powerline_shell/segments}/username.py (63%) rename {segments => powerline_shell/segments}/virtual_env.py (77%) rename {segments => powerline_shell/themes}/__init__.py (100%) rename {themes => powerline_shell/themes}/basic.py (100%) rename {themes => powerline_shell/themes}/colortest.py (100%) rename {themes => powerline_shell/themes}/default.py (100%) rename {themes => powerline_shell/themes}/solarized-dark.py (100%) rename {themes => powerline_shell/themes}/washed.py (100%) delete mode 100644 segments/newline.py delete mode 100644 segments/ssh.py create mode 100755 setup.py diff --git a/.gitignore b/.gitignore index 5929123..d84c36e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ powerline-shell.py *.py[co] config.py +powerline_shell.egg-info/ diff --git a/install.py b/install.py deleted file mode 100755 index bf36f41..0000000 --- a/install.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/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) diff --git a/powerline_shell_base.py b/powerline_shell/__init__.py old mode 100755 new mode 100644 similarity index 70% rename from powerline_shell_base.py rename to powerline_shell/__init__.py index 5d367a5..9aea770 --- a/powerline_shell_base.py +++ b/powerline_shell/__init__.py @@ -1,24 +1,29 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import print_function - import argparse import os import sys +import config +import importlib +from .themes.default import DefaultColor py3 = sys.version_info.major == 3 - -def warn(msg): - print('[powerline-bash] ', msg) - - if py3: def unicode(x): return x -class Powerline: +def warn(msg): + print('[powerline-bash] ', msg) + + +def get_default_theme(): + return DefaultColor + + +class Powerline(object): symbols = { 'compatible': { 'lock': 'RO', @@ -46,9 +51,10 @@ class Powerline: 'bare': '%s', } - def __init__(self, args, cwd): + def __init__(self, args, cwd, theme=None): self.args = args self.cwd = cwd + self.theme = theme or get_default_theme() mode, shell = args.mode, args.shell self.color_template = self.color_templates[shell] self.reset = self.color_template % '[0m' @@ -61,7 +67,7 @@ class Powerline: def color(self, prefix, code): if code is None: return '' - elif code == Color.RESET: + elif code == self.theme.RESET: return self.reset else: return self.color_template % ('[%s;5;%sm' % (prefix, code)) @@ -98,64 +104,6 @@ class Powerline: segment[3])) -class RepoStats: - symbols = { - 'detached': u'\u2693', - 'ahead': u'\u2B06', - 'behind': u'\u2B07', - 'staged': u'\u2714', - 'not_staged': u'\u270E', - 'untracked': u'\u2753', - '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 @@ -186,7 +134,7 @@ def get_valid_cwd(): return cwd -if __name__ == "__main__": +def main(): arg_parser = argparse.ArgumentParser() arg_parser.add_argument('--cwd-mode', action='store', help='How to display the current directory', default='fancy', @@ -209,3 +157,8 @@ if __name__ == "__main__": args = arg_parser.parse_args() powerline = Powerline(args, get_valid_cwd()) + for segment in config.SEGMENTS: + mod = importlib.import_module("powerline_shell.segments." + segment) + fn = getattr(mod, "add_" + segment + "_segment") + fn(powerline) + sys.stdout.write(powerline.draw()) diff --git a/lib/color_compliment.py b/powerline_shell/color_compliment.py old mode 100755 new mode 100644 similarity index 97% rename from lib/color_compliment.py rename to powerline_shell/color_compliment.py index 9e022dd..0a040d2 --- a/lib/color_compliment.py +++ b/powerline_shell/color_compliment.py @@ -10,7 +10,7 @@ import sys # Original, non-relative import errors on Python3 from .colortrans import * -py3 = sys.version_info[0] == 3 +py3 = sys.version_info.major == 3 def getOppositeColor(r,g,b): diff --git a/lib/colortrans.py b/powerline_shell/colortrans.py similarity index 100% rename from lib/colortrans.py rename to powerline_shell/colortrans.py diff --git a/powerline_shell/repos.py b/powerline_shell/repos.py new file mode 100644 index 0000000..fe4e46e --- /dev/null +++ b/powerline_shell/repos.py @@ -0,0 +1,56 @@ +class RepoStats(object): + symbols = { + 'detached': u'\u2693', + 'ahead': u'\u2B06', + 'behind': u'\u2B07', + 'staged': u'\u2714', + 'not_staged': u'\u270E', + 'untracked': u'\u2753', + '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) diff --git a/lib/__init__.py b/powerline_shell/segments/__init__.py similarity index 100% rename from lib/__init__.py rename to powerline_shell/segments/__init__.py diff --git a/segments/cwd.py b/powerline_shell/segments/cwd.py similarity index 77% rename from segments/cwd.py rename to powerline_shell/segments/cwd.py index 9dd38ba..14702cc 100644 --- a/segments/cwd.py +++ b/powerline_shell/segments/cwd.py @@ -1,6 +1,8 @@ import os +import sys ELLIPSIS = u'\u2026' +py3 = sys.version_info.major == 3 def replace_home_dir(cwd): @@ -22,10 +24,10 @@ def split_path_into_names(cwd): return names -def requires_special_home_display(name): +def requires_special_home_display(powerline, 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) + return (name == '~' and powerline.theme.HOME_SPECIAL_DISPLAY) def maybe_shorten_name(powerline, name): @@ -37,16 +39,16 @@ def maybe_shorten_name(powerline, name): return name -def get_fg_bg(name, is_last_dir): +def get_fg_bg(powerline, name, is_last_dir): """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,) + if requires_special_home_display(powerline, name): + return (powerline.theme.HOME_FG, powerline.theme.HOME_BG,) if is_last_dir: - return (Color.CWD_FG, Color.PATH_BG,) + return (powerline.theme.CWD_FG, powerline.theme.PATH_BG,) else: - return (Color.PATH_FG, Color.PATH_BG,) + return (powerline.theme.PATH_FG, powerline.theme.PATH_BG,) def add_cwd_segment(powerline): @@ -56,7 +58,7 @@ def add_cwd_segment(powerline): cwd = replace_home_dir(cwd) if powerline.args.cwd_mode == 'plain': - powerline.append(' %s ' % (cwd,), Color.CWD_FG, Color.PATH_BG) + powerline.append(' %s ' % (cwd,), powerline.theme.CWD_FG, powerline.theme.PATH_BG) return names = split_path_into_names(cwd) @@ -82,11 +84,11 @@ def add_cwd_segment(powerline): for i, name in enumerate(names): is_last_dir = (i == len(names) - 1) - fg, bg = get_fg_bg(name, is_last_dir) + fg, bg = get_fg_bg(powerline, name, is_last_dir) separator = powerline.separator_thin - separator_fg = Color.SEPARATOR_FG - if requires_special_home_display(name) or is_last_dir: + separator_fg = powerline.theme.SEPARATOR_FG + if requires_special_home_display(powerline, name) or is_last_dir: separator = None separator_fg = None diff --git a/segments/exit_code.py b/powerline_shell/segments/exit_code.py similarity index 67% rename from segments/exit_code.py rename to powerline_shell/segments/exit_code.py index e3b320e..00be9b8 100644 --- a/segments/exit_code.py +++ b/powerline_shell/segments/exit_code.py @@ -1,6 +1,6 @@ def add_exit_code_segment(powerline): if powerline.args.prev_error == 0: return - fg = Color.CMD_FAILED_FG - bg = Color.CMD_FAILED_BG + fg = powerline.theme.CMD_FAILED_FG + bg = powerline.theme.CMD_FAILED_BG powerline.append(' %s ' % str(powerline.args.prev_error), fg, bg) diff --git a/segments/fossil.py b/powerline_shell/segments/fossil.py similarity index 91% rename from segments/fossil.py rename to powerline_shell/segments/fossil.py index 6c7818e..3183bb6 100644 --- a/segments/fossil.py +++ b/powerline_shell/segments/fossil.py @@ -18,12 +18,12 @@ def _add_fossil_segment(powerline): if len(branch) == 0: return - bg = Color.REPO_CLEAN_BG - fg = Color.REPO_CLEAN_FG + bg = powerline.theme.REPO_CLEAN_BG + fg = powerline.theme.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 + bg = powerline.theme.REPO_DIRTY_BG + fg = powerline.theme.REPO_DIRTY_FG extra = '' if has_untracked_files: extra += '+' diff --git a/segments/git.py b/powerline_shell/segments/git.py similarity index 91% rename from segments/git.py rename to powerline_shell/segments/git.py index 0da777f..1774d94 100644 --- a/segments/git.py +++ b/powerline_shell/segments/git.py @@ -1,6 +1,8 @@ import re import subprocess import os +from ..repos import RepoStats + def get_PATH(): """Normally gets the PATH from the OS. This function exists to enable @@ -8,6 +10,7 @@ def get_PATH(): """ return os.getenv("PATH") + def git_subprocess_env(): return { # LANG is specified to ensure git always uses a language we are expecting. @@ -80,11 +83,11 @@ def add_git_segment(powerline): else: branch = _get_git_detached_branch() - bg = Color.REPO_CLEAN_BG - fg = Color.REPO_CLEAN_FG + bg = powerline.theme.REPO_CLEAN_BG + fg = powerline.theme.REPO_CLEAN_FG if stats.dirty: - bg = Color.REPO_DIRTY_BG - fg = Color.REPO_DIRTY_FG + bg = powerline.theme.REPO_DIRTY_BG + fg = powerline.theme.REPO_DIRTY_FG powerline.append(' %s ' % branch, fg, bg) - stats.add_to_powerline(powerline, Color) + stats.add_to_powerline(powerline, powerline.theme) diff --git a/segments/hg.py b/powerline_shell/segments/hg.py similarity index 87% rename from segments/hg.py rename to powerline_shell/segments/hg.py index d5f1cf4..7d7dc6a 100644 --- a/segments/hg.py +++ b/powerline_shell/segments/hg.py @@ -24,12 +24,12 @@ 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 + bg = powerline.theme.REPO_CLEAN_BG + fg = powerline.theme.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 + bg = powerline.theme.REPO_DIRTY_BG + fg = powerline.theme.REPO_DIRTY_FG extra = '' if has_untracked_files: extra += '+' diff --git a/segments/hostname.py b/powerline_shell/segments/hostname.py similarity index 88% rename from segments/hostname.py rename to powerline_shell/segments/hostname.py index 609711e..d4c310a 100644 --- a/segments/hostname.py +++ b/powerline_shell/segments/hostname.py @@ -18,4 +18,4 @@ def add_hostname_segment(powerline): import socket host_prompt = ' %s ' % socket.gethostname().split('.')[0] - powerline.append(host_prompt, Color.HOSTNAME_FG, Color.HOSTNAME_BG) + powerline.append(host_prompt, powerline.theme.HOSTNAME_FG, powerline.theme.HOSTNAME_BG) diff --git a/segments/jobs.py b/powerline_shell/segments/jobs.py similarity index 91% rename from segments/jobs.py rename to powerline_shell/segments/jobs.py index 2e0f9dc..6866249 100644 --- a/segments/jobs.py +++ b/powerline_shell/segments/jobs.py @@ -27,4 +27,4 @@ def add_jobs_segment(powerline): num_jobs = len(re.findall(str(pppid), output)) - 1 if num_jobs > 0: - powerline.append(' %d ' % num_jobs, Color.JOBS_FG, Color.JOBS_BG) + powerline.append(' %d ' % num_jobs, powerline.theme.JOBS_FG, powerline.theme.JOBS_BG) diff --git a/powerline_shell/segments/newline.py b/powerline_shell/segments/newline.py new file mode 100644 index 0000000..07b13ff --- /dev/null +++ b/powerline_shell/segments/newline.py @@ -0,0 +1,2 @@ +def add_newline_segment(powerline): + powerline.append("\n", powerline.theme.RESET, powerline.theme.RESET, separator='') diff --git a/segments/node_version.py b/powerline_shell/segments/node_version.py similarity index 100% rename from segments/node_version.py rename to powerline_shell/segments/node_version.py diff --git a/segments/npm_version.py b/powerline_shell/segments/npm_version.py similarity index 100% rename from segments/npm_version.py rename to powerline_shell/segments/npm_version.py diff --git a/segments/php_version.py b/powerline_shell/segments/php_version.py similarity index 100% rename from segments/php_version.py rename to powerline_shell/segments/php_version.py diff --git a/segments/rbenv.py b/powerline_shell/segments/rbenv.py similarity index 72% rename from segments/rbenv.py rename to powerline_shell/segments/rbenv.py index 70e34a8..1208f6c 100644 --- a/segments/rbenv.py +++ b/powerline_shell/segments/rbenv.py @@ -8,6 +8,6 @@ def add_rbenv_segment(powerline): if len(version) <= 0: return - powerline.append(' %s ' % version, Color.VIRTUAL_ENV_FG, Color.VIRTUAL_ENV_BG) + powerline.append(' %s ' % version, powerline.theme.VIRTUAL_ENV_FG, powerline.theme.VIRTUAL_ENV_BG) except OSError: return diff --git a/segments/read_only.py b/powerline_shell/segments/read_only.py similarity index 54% rename from segments/read_only.py rename to powerline_shell/segments/read_only.py index efbe11a..c2bf79e 100644 --- a/segments/read_only.py +++ b/powerline_shell/segments/read_only.py @@ -4,4 +4,4 @@ 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) + powerline.append(' %s ' % powerline.lock, powerline.theme.READONLY_FG, powerline.theme.READONLY_BG) diff --git a/segments/root.py b/powerline_shell/segments/root.py similarity index 59% rename from segments/root.py rename to powerline_shell/segments/root.py index 49404ec..4900d7f 100644 --- a/segments/root.py +++ b/powerline_shell/segments/root.py @@ -4,9 +4,9 @@ def add_root_segment(powerline): 'zsh': ' %# ', 'bare': ' $ ', } - bg = Color.CMD_PASSED_BG - fg = Color.CMD_PASSED_FG + bg = powerline.theme.CMD_PASSED_BG + fg = powerline.theme.CMD_PASSED_FG if powerline.args.prev_error != 0: - fg = Color.CMD_FAILED_FG - bg = Color.CMD_FAILED_BG + fg = powerline.theme.CMD_FAILED_FG + bg = powerline.theme.CMD_FAILED_BG powerline.append(root_indicators[powerline.args.shell], fg, bg) diff --git a/segments/ruby_version.py b/powerline_shell/segments/ruby_version.py similarity index 100% rename from segments/ruby_version.py rename to powerline_shell/segments/ruby_version.py diff --git a/segments/set_term_title.py b/powerline_shell/segments/set_term_title.py similarity index 100% rename from segments/set_term_title.py rename to powerline_shell/segments/set_term_title.py diff --git a/powerline_shell/segments/ssh.py b/powerline_shell/segments/ssh.py new file mode 100644 index 0000000..c429162 --- /dev/null +++ b/powerline_shell/segments/ssh.py @@ -0,0 +1,6 @@ +import os + +def add_ssh_segment(powerline): + + if os.getenv('SSH_CLIENT'): + powerline.append(' %s ' % powerline.network, powerline.theme.SSH_FG, powerline.theme.SSH_BG) diff --git a/segments/svn.py b/powerline_shell/segments/svn.py similarity index 92% rename from segments/svn.py rename to powerline_shell/segments/svn.py index 89168c0..454b96f 100644 --- a/segments/svn.py +++ b/powerline_shell/segments/svn.py @@ -16,7 +16,7 @@ def _add_svn_segment(powerline): 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) + powerline.append(' %s ' % changes, powerline.theme.SVN_CHANGES_FG, powerline.theme.SVN_CHANGES_BG) def add_svn_segment(powerline): diff --git a/segments/time.py b/powerline_shell/segments/time.py similarity index 73% rename from segments/time.py rename to powerline_shell/segments/time.py index 8c0313f..dedad80 100644 --- a/segments/time.py +++ b/powerline_shell/segments/time.py @@ -7,4 +7,4 @@ def add_time_segment(powerline): import time time = ' %s ' % time.strftime('%H:%M:%S') - powerline.append(time, Color.HOSTNAME_FG, Color.HOSTNAME_BG) + powerline.append(time, powerline.theme.HOSTNAME_FG, powerline.theme.HOSTNAME_BG) diff --git a/segments/uptime.py b/powerline_shell/segments/uptime.py similarity index 89% rename from segments/uptime.py rename to powerline_shell/segments/uptime.py index 4c7711c..e53e258 100644 --- a/segments/uptime.py +++ b/powerline_shell/segments/uptime.py @@ -11,6 +11,6 @@ def add_uptime_segment(powerline): hours = '' if not hour_search else '%sh ' % hour_search.group(0) minutes = re.search('(?<=\:)\d{1,2}|\d{1,2}(?=\s+min)', raw_uptime).group(0) uptime = u' %s%s%sm \u2191 ' % (days, hours, minutes) - powerline.append(uptime, Color.CWD_FG, Color.PATH_BG) + powerline.append(uptime, powerline.theme.CWD_FG, powerline.theme.PATH_BG) except OSError: return diff --git a/segments/username.py b/powerline_shell/segments/username.py similarity index 63% rename from segments/username.py rename to powerline_shell/segments/username.py index 3d73a12..d8cc177 100644 --- a/segments/username.py +++ b/powerline_shell/segments/username.py @@ -9,8 +9,8 @@ def add_username_segment(powerline): user_prompt = ' %s ' % os.getenv('USER') if os.getenv('USER') == 'root': - bgcolor = Color.USERNAME_ROOT_BG + bgcolor = powerline.theme.USERNAME_ROOT_BG else: - bgcolor = Color.USERNAME_BG + bgcolor = powerline.theme.USERNAME_BG - powerline.append(user_prompt, Color.USERNAME_FG, bgcolor) + powerline.append(user_prompt, powerline.theme.USERNAME_FG, bgcolor) diff --git a/segments/virtual_env.py b/powerline_shell/segments/virtual_env.py similarity index 77% rename from segments/virtual_env.py rename to powerline_shell/segments/virtual_env.py index 35a368f..2b4de48 100644 --- a/segments/virtual_env.py +++ b/powerline_shell/segments/virtual_env.py @@ -6,6 +6,6 @@ def add_virtual_env_segment(powerline): return env_name = os.path.basename(env) - bg = Color.VIRTUAL_ENV_BG - fg = Color.VIRTUAL_ENV_FG + bg = powerline.theme.VIRTUAL_ENV_BG + fg = powerline.theme.VIRTUAL_ENV_FG powerline.append(' %s ' % env_name, fg, bg) diff --git a/segments/__init__.py b/powerline_shell/themes/__init__.py similarity index 100% rename from segments/__init__.py rename to powerline_shell/themes/__init__.py diff --git a/themes/basic.py b/powerline_shell/themes/basic.py similarity index 100% rename from themes/basic.py rename to powerline_shell/themes/basic.py diff --git a/themes/colortest.py b/powerline_shell/themes/colortest.py similarity index 100% rename from themes/colortest.py rename to powerline_shell/themes/colortest.py diff --git a/themes/default.py b/powerline_shell/themes/default.py similarity index 100% rename from themes/default.py rename to powerline_shell/themes/default.py diff --git a/themes/solarized-dark.py b/powerline_shell/themes/solarized-dark.py similarity index 100% rename from themes/solarized-dark.py rename to powerline_shell/themes/solarized-dark.py diff --git a/themes/washed.py b/powerline_shell/themes/washed.py similarity index 100% rename from themes/washed.py rename to powerline_shell/themes/washed.py diff --git a/segments/newline.py b/segments/newline.py deleted file mode 100644 index a3f7261..0000000 --- a/segments/newline.py +++ /dev/null @@ -1,2 +0,0 @@ -def add_newline_segment(powerline): - powerline.append("\n", Color.RESET, Color.RESET, separator='') diff --git a/segments/ssh.py b/segments/ssh.py deleted file mode 100644 index 5361bc5..0000000 --- a/segments/ssh.py +++ /dev/null @@ -1,6 +0,0 @@ -import os - -def add_ssh_segment(powerline): - - if os.getenv('SSH_CLIENT'): - powerline.append(' %s ' % powerline.network, Color.SSH_FG, Color.SSH_BG) diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..69843b0 --- /dev/null +++ b/setup.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +from setuptools import setup, find_packages + +setup(name="powerline-shell", + version="0.1.0-alpha", + description="A pretty prompt for your shell", + author="Buck Ryan", + url="httpss://github.com/banga/powerline-shell", + classifiers=[], + py_modules=["powerline_shell"], + install_requires=[ + "argparse", + ], + entry_points=""" + [console_scripts] + powerline-shell=powerline_shell:main + """, + packages=["powerline_shell"], +) diff --git a/test/repo_stats_test.py b/test/repo_stats_test.py index c97a089..a3e2149 100644 --- a/test/repo_stats_test.py +++ b/test/repo_stats_test.py @@ -1,5 +1,6 @@ import unittest import powerline_shell_base as p +from powerline_shell.repos import RepoStats class RepoStatsTest(unittest.TestCase): From b4a279b0e79c1a1c297710709777ee136e19b517 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 26 Aug 2017 16:00:30 -0400 Subject: [PATCH 31/93] fix missing imports --- powerline_shell/segments/ruby_version.py | 1 + powerline_shell/segments/set_term_title.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/powerline_shell/segments/ruby_version.py b/powerline_shell/segments/ruby_version.py index 040f533..513d10f 100644 --- a/powerline_shell/segments/ruby_version.py +++ b/powerline_shell/segments/ruby_version.py @@ -1,3 +1,4 @@ +import os import subprocess diff --git a/powerline_shell/segments/set_term_title.py b/powerline_shell/segments/set_term_title.py index 7c3e826..adaf707 100644 --- a/powerline_shell/segments/set_term_title.py +++ b/powerline_shell/segments/set_term_title.py @@ -1,3 +1,6 @@ +import os + + def add_set_term_title_segment(powerline): term = os.getenv('TERM') if not (('xterm' in term) or ('rxvt' in term)): @@ -12,4 +15,3 @@ def add_set_term_title_segment(powerline): 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, '') - From fafd37babb0f8825b4121545db668c4a8931d8ea Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 26 Aug 2017 17:46:46 -0400 Subject: [PATCH 32/93] Convert many segments to new class-based code --- powerline_shell/__init__.py | 27 ++++--------- powerline_shell/color_compliment.py | 12 +----- powerline_shell/segments.py | 0 powerline_shell/segments/cwd.py | 7 +++- powerline_shell/segments/exit_code.py | 16 +++++--- powerline_shell/segments/git.py | 32 ++++++++++----- powerline_shell/segments/hostname.py | 43 +++++++++++--------- powerline_shell/segments/jobs.py | 47 +++++++++++----------- powerline_shell/segments/newline.py | 11 ++++- powerline_shell/segments/root.py | 29 +++++++------ powerline_shell/segments/set_term_title.py | 2 +- powerline_shell/segments/virtual_env.py | 20 +++++---- powerline_shell/{repos.py => utils.py} | 29 ++++++++++++- test/repo_stats_test.py | 2 +- 14 files changed, 163 insertions(+), 114 deletions(-) create mode 100644 powerline_shell/segments.py rename powerline_shell/{repos.py => utils.py} (78%) diff --git a/powerline_shell/__init__.py b/powerline_shell/__init__.py index 9aea770..4b23322 100644 --- a/powerline_shell/__init__.py +++ b/powerline_shell/__init__.py @@ -7,20 +7,7 @@ import sys import config import importlib from .themes.default import DefaultColor - -py3 = sys.version_info.major == 3 - -if py3: - def unicode(x): - return x - - -def warn(msg): - print('[powerline-bash] ', msg) - - -def get_default_theme(): - return DefaultColor +from .utils import warn, py3 class Powerline(object): @@ -54,7 +41,7 @@ class Powerline(object): def __init__(self, args, cwd, theme=None): self.args = args self.cwd = cwd - self.theme = theme or get_default_theme() + self.theme = theme or DefaultColor mode, shell = args.mode, args.shell self.color_template = self.color_templates[shell] self.reset = self.color_template % '[0m' @@ -157,8 +144,10 @@ def main(): args = arg_parser.parse_args() powerline = Powerline(args, get_valid_cwd()) - for segment in config.SEGMENTS: - mod = importlib.import_module("powerline_shell.segments." + segment) - fn = getattr(mod, "add_" + segment + "_segment") - fn(powerline) + segments = [] + for seg_name in config.SEGMENTS: + mod = importlib.import_module("powerline_shell.segments." + seg_name) + segments.append(getattr(mod, "Segment")(powerline)) + for segment in segments: + segment.add_to_powerline() sys.stdout.write(powerline.draw()) diff --git a/powerline_shell/color_compliment.py b/powerline_shell/color_compliment.py index 0a040d2..08f8bea 100644 --- a/powerline_shell/color_compliment.py +++ b/powerline_shell/color_compliment.py @@ -1,4 +1,3 @@ -#! /usr/bin/env python from colorsys import hls_to_rgb, rgb_to_hls # md5 deprecated since Python 2.5 try: @@ -6,19 +5,13 @@ try: except ImportError: from hashlib import md5 import sys - -# Original, non-relative import errors on Python3 from .colortrans import * - -py3 = sys.version_info.major == 3 +from ..utils import py3 def getOppositeColor(r,g,b): hls = rgb_to_hls(r,g,b) - #print "hls is" - #print hls opp = list(hls[:]) - #opp[0] = (opp[0]+0.5)%1 # reverse hue (a.k.a. color), reversing tends to be jarring opp[0] = (opp[0]+0.2)%1 # shift hue (a.k.a. color) if opp[1] > 255/2: # for level you want to make sure they opp[1] -= 255/2 # are quite different so easily readable @@ -26,7 +19,6 @@ def getOppositeColor(r,g,b): opp[1] += 255/2 if opp[2] > -0.5: # if saturation is low on first color increase second's opp[2] -= 0.5 - #print opp opp = hls_to_rgb(*opp) m = max(opp) if m > 255: #colorsys module doesn't give caps to their conversions @@ -34,8 +26,6 @@ def getOppositeColor(r,g,b): return tuple([ int(x) for x in opp]) def stringToHashToColorAndOpposite(string): - # Python3: Unicode string must be encoded before digest - # Python2.7: works either way, but check in case breaks earlier py2 if py3: string = string.encode('utf-8') string = md5(string).hexdigest()[:6] # get a random color diff --git a/powerline_shell/segments.py b/powerline_shell/segments.py new file mode 100644 index 0000000..e69de29 diff --git a/powerline_shell/segments/cwd.py b/powerline_shell/segments/cwd.py index 14702cc..a1d7d29 100644 --- a/powerline_shell/segments/cwd.py +++ b/powerline_shell/segments/cwd.py @@ -1,8 +1,8 @@ import os import sys +from ..utils import warn, py3, BasicSegment ELLIPSIS = u'\u2026' -py3 = sys.version_info.major == 3 def replace_home_dir(cwd): @@ -94,3 +94,8 @@ def add_cwd_segment(powerline): powerline.append(' %s ' % maybe_shorten_name(powerline, name), fg, bg, separator, separator_fg) + + +class Segment(BasicSegment): + def add_to_powerline(self): + add_cwd_segment(self.powerline) diff --git a/powerline_shell/segments/exit_code.py b/powerline_shell/segments/exit_code.py index 00be9b8..85153ec 100644 --- a/powerline_shell/segments/exit_code.py +++ b/powerline_shell/segments/exit_code.py @@ -1,6 +1,10 @@ -def add_exit_code_segment(powerline): - if powerline.args.prev_error == 0: - return - fg = powerline.theme.CMD_FAILED_FG - bg = powerline.theme.CMD_FAILED_BG - powerline.append(' %s ' % str(powerline.args.prev_error), fg, bg) +from ..utils import BasicSegment + + +class Segment(BasicSegment): + def add_to_powerline(self): + if self.powerline.args.prev_error == 0: + return + fg = self.powerline.theme.CMD_FAILED_FG + bg = self.powerline.theme.CMD_FAILED_BG + self.powerline.append(' %s ' % str(self.powerline.args.prev_error), fg, bg) diff --git a/powerline_shell/segments/git.py b/powerline_shell/segments/git.py index 1774d94..03dd09f 100644 --- a/powerline_shell/segments/git.py +++ b/powerline_shell/segments/git.py @@ -1,7 +1,7 @@ import re import subprocess import os -from ..repos import RepoStats +from ..utils import RepoStats, ThreadedSegment def get_PATH(): @@ -59,18 +59,18 @@ def parse_git_stats(status): return stats -def add_git_segment(powerline): +def build_stats(): 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 + return None pdata = p.communicate() if p.returncode != 0: - return + return None status = pdata[0].decode("utf-8").splitlines() stats = parse_git_stats(status) @@ -82,12 +82,22 @@ def add_git_segment(powerline): branch = branch_info['local'] else: branch = _get_git_detached_branch() + return stats, branch - bg = powerline.theme.REPO_CLEAN_BG - fg = powerline.theme.REPO_CLEAN_FG - if stats.dirty: - bg = powerline.theme.REPO_DIRTY_BG - fg = powerline.theme.REPO_DIRTY_FG - powerline.append(' %s ' % branch, fg, bg) - stats.add_to_powerline(powerline, powerline.theme) +class Segment(ThreadedSegment): + def run(self): + self.stats, self.branch = build_stats() + + def add_to_powerline(self): + self.join() + if not self.stats: + return + bg = self.powerline.theme.REPO_CLEAN_BG + fg = self.powerline.theme.REPO_CLEAN_FG + if self.stats.dirty: + bg = self.powerline.theme.REPO_DIRTY_BG + fg = self.powerline.theme.REPO_DIRTY_FG + + self.powerline.append(" " + self.branch + " ", fg, bg) + self.stats.add_to_powerline(self.powerline) diff --git a/powerline_shell/segments/hostname.py b/powerline_shell/segments/hostname.py index d4c310a..ddbaff6 100644 --- a/powerline_shell/segments/hostname.py +++ b/powerline_shell/segments/hostname.py @@ -1,21 +1,28 @@ -def add_hostname_segment(powerline): - if powerline.args.colorize_hostname: - from lib.color_compliment import stringToHashToColorAndOpposite - from lib.colortrans import rgb2short - from socket import gethostname - hostname = gethostname() - FG, BG = stringToHashToColorAndOpposite(hostname) - FG, BG = (rgb2short(*color) for color in [FG, BG]) - host_prompt = ' %s ' % hostname.split('.')[0] +from ..utils import BasicSegment - powerline.append(host_prompt, FG, BG) - else: - if powerline.args.shell == 'bash': - host_prompt = ' \\h ' - elif powerline.args.shell == 'zsh': - host_prompt = ' %m ' + +class Segment(BasicSegment): + def add_to_powerline(self): + powerline = self.powerline + if powerline.args.colorize_hostname: + from lib.color_compliment import stringToHashToColorAndOpposite + from lib.colortrans import rgb2short + from socket import gethostname + hostname = gethostname() + FG, BG = stringToHashToColorAndOpposite(hostname) + FG, BG = (rgb2short(*color) for color in [FG, BG]) + host_prompt = ' %s ' % hostname.split('.')[0] + + powerline.append(host_prompt, FG, BG) else: - import socket - host_prompt = ' %s ' % socket.gethostname().split('.')[0] + if powerline.args.shell == 'bash': + host_prompt = ' \\h ' + elif powerline.args.shell == 'zsh': + host_prompt = ' %m ' + else: + import socket + host_prompt = ' %s ' % socket.gethostname().split('.')[0] - powerline.append(host_prompt, powerline.theme.HOSTNAME_FG, powerline.theme.HOSTNAME_BG) + powerline.append(host_prompt, + powerline.theme.HOSTNAME_FG, + powerline.theme.HOSTNAME_BG) diff --git a/powerline_shell/segments/jobs.py b/powerline_shell/segments/jobs.py index 6866249..e5d0551 100644 --- a/powerline_shell/segments/jobs.py +++ b/powerline_shell/segments/jobs.py @@ -2,29 +2,30 @@ import os import re import subprocess import platform +from ..utils import ThreadedSegment -def add_jobs_segment(powerline): - num_jobs = 0 - if platform.system().startswith('CYGWIN'): - # cygwin ps is a special snowflake... - output_proc = subprocess.Popen(['ps', '-af'], stdout=subprocess.PIPE) - output = map(lambda l: int(l.split()[2].strip()), - output_proc.communicate()[0].decode("utf-8").splitlines()[1:]) +class Segment(ThreadedSegment): + def run(self): + self.num_jobs = 0 + if platform.system().startswith('CYGWIN'): + # cygwin ps is a special snowflake... + output_proc = subprocess.Popen(['ps', '-af'], stdout=subprocess.PIPE) + output = map(lambda l: int(l.split()[2].strip()), + output_proc.communicate()[0].decode("utf-8").splitlines()[1:]) + self.num_jobs = output.count(os.getppid()) - 1 + else: + 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") + self.num_jobs = len(re.findall(str(pppid), output)) - 1 - num_jobs = output.count(os.getppid()) - 1 - - else: - - 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, powerline.theme.JOBS_FG, powerline.theme.JOBS_BG) + def add_to_powerline(self): + self.join() + if self.num_jobs > 0: + self.powerline.append(' %d ' % self.num_jobs, + self.powerline.theme.JOBS_FG, + self.powerline.theme.JOBS_BG) diff --git a/powerline_shell/segments/newline.py b/powerline_shell/segments/newline.py index 07b13ff..f25cb46 100644 --- a/powerline_shell/segments/newline.py +++ b/powerline_shell/segments/newline.py @@ -1,2 +1,9 @@ -def add_newline_segment(powerline): - powerline.append("\n", powerline.theme.RESET, powerline.theme.RESET, separator='') +from ..utils import BasicSegment + + +class Segment(BasicSegment): + def add_to_powerline(self): + self.powerline.append("\n", + self.powerline.theme.RESET, + self.powerline.theme.RESET, + separator="") diff --git a/powerline_shell/segments/root.py b/powerline_shell/segments/root.py index 4900d7f..5b31c60 100644 --- a/powerline_shell/segments/root.py +++ b/powerline_shell/segments/root.py @@ -1,12 +1,17 @@ -def add_root_segment(powerline): - root_indicators = { - 'bash': ' \\$ ', - 'zsh': ' %# ', - 'bare': ' $ ', - } - bg = powerline.theme.CMD_PASSED_BG - fg = powerline.theme.CMD_PASSED_FG - if powerline.args.prev_error != 0: - fg = powerline.theme.CMD_FAILED_FG - bg = powerline.theme.CMD_FAILED_BG - powerline.append(root_indicators[powerline.args.shell], fg, bg) +from ..utils import BasicSegment + + +class Segment(BasicSegment): + def add_to_powerline(self): + powerline = self.powerline + root_indicators = { + 'bash': ' \\$ ', + 'zsh': ' %# ', + 'bare': ' $ ', + } + bg = powerline.theme.CMD_PASSED_BG + fg = powerline.theme.CMD_PASSED_FG + if powerline.args.prev_error != 0: + fg = powerline.theme.CMD_FAILED_FG + bg = powerline.theme.CMD_FAILED_BG + powerline.append(root_indicators[powerline.args.shell], fg, bg) diff --git a/powerline_shell/segments/set_term_title.py b/powerline_shell/segments/set_term_title.py index adaf707..9846d6c 100644 --- a/powerline_shell/segments/set_term_title.py +++ b/powerline_shell/segments/set_term_title.py @@ -1,4 +1,5 @@ import os +import socket def add_set_term_title_segment(powerline): @@ -11,7 +12,6 @@ def add_set_term_title_segment(powerline): 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, '') diff --git a/powerline_shell/segments/virtual_env.py b/powerline_shell/segments/virtual_env.py index 2b4de48..ffbefec 100644 --- a/powerline_shell/segments/virtual_env.py +++ b/powerline_shell/segments/virtual_env.py @@ -1,11 +1,15 @@ import os +from ..utils import BasicSegment -def add_virtual_env_segment(powerline): - env = os.getenv('VIRTUAL_ENV') or os.getenv('CONDA_ENV_PATH') or os.getenv('CONDA_DEFAULT_ENV') - if env is None: - return - env_name = os.path.basename(env) - bg = powerline.theme.VIRTUAL_ENV_BG - fg = powerline.theme.VIRTUAL_ENV_FG - powerline.append(' %s ' % env_name, fg, bg) +class Segment(BasicSegment): + def add_to_powerline(self): + env = os.getenv('VIRTUAL_ENV') \ + or os.getenv('CONDA_ENV_PATH') \ + or os.getenv('CONDA_DEFAULT_ENV') + if not env: + return + env_name = os.path.basename(env) + bg = self.powerline.theme.VIRTUAL_ENV_BG + fg = self.powerline.theme.VIRTUAL_ENV_FG + self.powerline.append(" " + env_name + " ", fg, bg) diff --git a/powerline_shell/repos.py b/powerline_shell/utils.py similarity index 78% rename from powerline_shell/repos.py rename to powerline_shell/utils.py index fe4e46e..c817190 100644 --- a/powerline_shell/repos.py +++ b/powerline_shell/utils.py @@ -1,3 +1,13 @@ +import sys +import threading + +py3 = sys.version_info.major == 3 + +if py3: + def unicode(x): + return x + + class RepoStats(object): symbols = { 'detached': u'\u2693', @@ -43,14 +53,31 @@ class RepoStats(object): """ return unicode(self[_key]) if int(self[_key]) > 1 else u'' - def add_to_powerline(self, powerline, color): + def add_to_powerline(self, powerline): def add(_key, fg, bg): if self[_key]: s = u" {}{} ".format(self.n_or_empty(_key), self.symbols[_key]) powerline.append(s, fg, bg) + color = powerline.theme 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 warn(msg): + print('[powerline-bash] ', msg) + + +class BasicSegment(object): + def __init__(self, powerline): + self.powerline = powerline + + +class ThreadedSegment(threading.Thread): + def __init__(self, powerline): + super(ThreadedSegment, self).__init__() + self.powerline = powerline + self.start() diff --git a/test/repo_stats_test.py b/test/repo_stats_test.py index a3e2149..393eac3 100644 --- a/test/repo_stats_test.py +++ b/test/repo_stats_test.py @@ -1,6 +1,6 @@ import unittest import powerline_shell_base as p -from powerline_shell.repos import RepoStats +from powerline_shell.utils import RepoStats class RepoStatsTest(unittest.TestCase): From a374618ff472bbffb6836905943efd537061f4d5 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 26 Aug 2017 17:58:19 -0400 Subject: [PATCH 33/93] Convert the rest of the segments --- powerline_shell/segments/fossil.py | 36 +++++++++++--------- powerline_shell/segments/hg.py | 39 ++++++++++++---------- powerline_shell/segments/node_version.py | 19 ++++++----- powerline_shell/segments/npm_version.py | 19 ++++++----- powerline_shell/segments/php_version.py | 25 ++++++++------ powerline_shell/segments/rbenv.py | 24 +++++++------ powerline_shell/segments/read_only.py | 13 +++++--- powerline_shell/segments/ruby_version.py | 27 ++++++++------- powerline_shell/segments/set_term_title.py | 31 +++++++++-------- powerline_shell/segments/ssh.py | 11 ++++-- powerline_shell/segments/svn.py | 31 +++++++++-------- powerline_shell/segments/time.py | 24 ++++++++----- powerline_shell/segments/uptime.py | 30 +++++++++-------- powerline_shell/segments/username.py | 30 +++++++++-------- 14 files changed, 208 insertions(+), 151 deletions(-) diff --git a/powerline_shell/segments/fossil.py b/powerline_shell/segments/fossil.py index 3183bb6..1fa525f 100644 --- a/powerline_shell/segments/fossil.py +++ b/powerline_shell/segments/fossil.py @@ -1,5 +1,7 @@ import os import subprocess +from ..utils import BasicSegment + def get_fossil_status(): has_modified_files = False @@ -9,15 +11,14 @@ def get_fossil_status(): 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 = powerline.theme.REPO_CLEAN_BG fg = powerline.theme.REPO_CLEAN_FG has_modified_files, has_untracked_files, has_missing_files = get_fossil_status() @@ -32,19 +33,22 @@ def _add_fossil_segment(powerline): 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. +class Segment(BasicSegment): + def add_to_powerline(self): + """Wraps _add_fossil_segment in exception handling.""" + powerline = self.powerline - try: - _add_fossil_segment(powerline) - except OSError: - pass - except subprocess.CalledProcessError: - pass + # 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 diff --git a/powerline_shell/segments/hg.py b/powerline_shell/segments/hg.py index 7d7dc6a..9237146 100644 --- a/powerline_shell/segments/hg.py +++ b/powerline_shell/segments/hg.py @@ -1,5 +1,7 @@ import os import subprocess +from ..utils import BasicSegment + def get_hg_status(): has_modified_files = False @@ -20,20 +22,23 @@ def get_hg_status(): 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 = powerline.theme.REPO_CLEAN_BG - fg = powerline.theme.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 = powerline.theme.REPO_DIRTY_BG - fg = powerline.theme.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) + +class Segment(BasicSegment): + def add_to_powerline(self): + powerline = self.powerline + branch = os.popen('hg branch 2> /dev/null').read().rstrip() + if len(branch) == 0: + return False + bg = powerline.theme.REPO_CLEAN_BG + fg = powerline.theme.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 = powerline.theme.REPO_DIRTY_BG + fg = powerline.theme.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) diff --git a/powerline_shell/segments/node_version.py b/powerline_shell/segments/node_version.py index 466a086..e3a9f29 100644 --- a/powerline_shell/segments/node_version.py +++ b/powerline_shell/segments/node_version.py @@ -1,11 +1,14 @@ import subprocess +from ..utils import BasicSegment -def add_node_version_segment(powerline): - try: - p1 = subprocess.Popen(["node", "--version"], stdout=subprocess.PIPE) - version = p1.communicate()[0].decode("utf-8").rstrip() - version = "node " + version - powerline.append(version, 15, 18) - except OSError: - return +class Segment(BasicSegment): + def add_to_powerline(self): + powerline = self.powerline + try: + p1 = subprocess.Popen(["node", "--version"], stdout=subprocess.PIPE) + version = p1.communicate()[0].decode("utf-8").rstrip() + version = "node " + version + powerline.append(version, 15, 18) + except OSError: + return diff --git a/powerline_shell/segments/npm_version.py b/powerline_shell/segments/npm_version.py index 0deb5f8..164c1a0 100644 --- a/powerline_shell/segments/npm_version.py +++ b/powerline_shell/segments/npm_version.py @@ -1,11 +1,14 @@ import subprocess +from ..utils import BasicSegment -def add_npm_version_segment(powerline): - try: - p1 = subprocess.Popen(["npm", "--version"], stdout=subprocess.PIPE) - version = p1.communicate()[0].decode("utf-8").rstrip() - version = "npm " + version - powerline.append(version, 15, 18) - except OSError: - return +class Segment(BasicSegment): + def add_to_powerline(self): + powerline = self.powerline + try: + p1 = subprocess.Popen(["npm", "--version"], stdout=subprocess.PIPE) + version = p1.communicate()[0].decode("utf-8").rstrip() + version = "npm " + version + powerline.append(version, 15, 18) + except OSError: + return diff --git a/powerline_shell/segments/php_version.py b/powerline_shell/segments/php_version.py index 7b9b8aa..f905a34 100644 --- a/powerline_shell/segments/php_version.py +++ b/powerline_shell/segments/php_version.py @@ -1,14 +1,17 @@ import subprocess +from ..utils import BasicSegment -def add_php_version_segment(powerline): - try: - output = subprocess.check_output(['php', '-r', 'echo PHP_VERSION;'], stderr=subprocess.STDOUT) - if '-' in output: - version = ' %s ' % output.split('-')[0] - else: - version = ' %s ' % output - - powerline.append(version, 15, 4) - except OSError: - return +class Segment(BasicSegment): + def add_to_powerline(self): + powerline = self.powerline + try: + output = subprocess.check_output(['php', '-r', 'echo PHP_VERSION;'], + stderr=subprocess.STDOUT) + if '-' in output: + version = ' %s ' % output.split('-')[0] + else: + version = ' %s ' % output + powerline.append(version, 15, 4) + except OSError: + return diff --git a/powerline_shell/segments/rbenv.py b/powerline_shell/segments/rbenv.py index 1208f6c..4b8c5d3 100644 --- a/powerline_shell/segments/rbenv.py +++ b/powerline_shell/segments/rbenv.py @@ -1,13 +1,17 @@ import subprocess +from ..utils import BasicSegment -def add_rbenv_segment(powerline): - try: - p1 = subprocess.Popen(["rbenv", "local"], stdout=subprocess.PIPE) - version = p1.communicate()[0].decode("utf-8").rstrip() - if len(version) <= 0: - return - - powerline.append(' %s ' % version, powerline.theme.VIRTUAL_ENV_FG, powerline.theme.VIRTUAL_ENV_BG) - except OSError: - return +class Segment(BasicSegment): + def add_to_powerline(self): + powerline = self.powerline + try: + p1 = subprocess.Popen(["rbenv", "local"], stdout=subprocess.PIPE) + version = p1.communicate()[0].decode("utf-8").rstrip() + if len(version) <= 0: + return + powerline.append(' %s ' % version, + powerline.theme.VIRTUAL_ENV_FG, + powerline.theme.VIRTUAL_ENV_BG) + except OSError: + return diff --git a/powerline_shell/segments/read_only.py b/powerline_shell/segments/read_only.py index c2bf79e..149b113 100644 --- a/powerline_shell/segments/read_only.py +++ b/powerline_shell/segments/read_only.py @@ -1,7 +1,12 @@ import os +from ..utils import BasicSegment -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, powerline.theme.READONLY_FG, powerline.theme.READONLY_BG) +class Segment(BasicSegment): + def add_to_powerline(self): + powerline = self.powerline + cwd = powerline.cwd or os.getenv('PWD') + if not os.access(cwd, os.W_OK): + powerline.append(' %s ' % powerline.lock, + powerline.theme.READONLY_FG, + powerline.theme.READONLY_BG) diff --git a/powerline_shell/segments/ruby_version.py b/powerline_shell/segments/ruby_version.py index 513d10f..0e7bee1 100644 --- a/powerline_shell/segments/ruby_version.py +++ b/powerline_shell/segments/ruby_version.py @@ -1,16 +1,19 @@ import os import subprocess +from ..utils import BasicSegment -def add_ruby_version_segment(powerline): - try: - p1 = subprocess.Popen(["ruby", "-v"], stdout=subprocess.PIPE) - p2 = subprocess.Popen(["sed", "s/ (.*//"], stdin=p1.stdout, stdout=subprocess.PIPE) - version = p2.communicate()[0].decode("utf-8").rstrip() - if os.environ.has_key("GEM_HOME"): - gem = os.environ["GEM_HOME"].split("@") - if len(gem) > 1: - version += " " + gem[1] - powerline.append(version, 15, 1) - except OSError: - return +class Segment(BasicSegment): + def add_to_powerline(self): + powerline = self.powerline + try: + p1 = subprocess.Popen(["ruby", "-v"], stdout=subprocess.PIPE) + p2 = subprocess.Popen(["sed", "s/ (.*//"], stdin=p1.stdout, stdout=subprocess.PIPE) + version = p2.communicate()[0].decode("utf-8").rstrip() + if os.environ.has_key("GEM_HOME"): + gem = os.environ["GEM_HOME"].split("@") + if len(gem) > 1: + version += " " + gem[1] + powerline.append(version, 15, 1) + except OSError: + return diff --git a/powerline_shell/segments/set_term_title.py b/powerline_shell/segments/set_term_title.py index 9846d6c..9743959 100644 --- a/powerline_shell/segments/set_term_title.py +++ b/powerline_shell/segments/set_term_title.py @@ -1,17 +1,22 @@ import os import socket +from ..utils import BasicSegment -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: - 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, '') +class Segment(BasicSegment): + def add_to_powerline(self): + powerline = self.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: + 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, '') diff --git a/powerline_shell/segments/ssh.py b/powerline_shell/segments/ssh.py index c429162..5b8af8b 100644 --- a/powerline_shell/segments/ssh.py +++ b/powerline_shell/segments/ssh.py @@ -1,6 +1,11 @@ import os +from ..utils import BasicSegment -def add_ssh_segment(powerline): - if os.getenv('SSH_CLIENT'): - powerline.append(' %s ' % powerline.network, powerline.theme.SSH_FG, powerline.theme.SSH_BG) +class Segment(BasicSegment): + def add_to_powerline(self): + powerline = self.powerline + if os.getenv('SSH_CLIENT'): + powerline.append(' %s ' % powerline.network, + powerline.theme.SSH_FG, + powerline.theme.SSH_BG) diff --git a/powerline_shell/segments/svn.py b/powerline_shell/segments/svn.py index 454b96f..f2d9897 100644 --- a/powerline_shell/segments/svn.py +++ b/powerline_shell/segments/svn.py @@ -1,4 +1,5 @@ import subprocess +from ..utils import BasicSegment def _add_svn_segment(powerline): @@ -19,19 +20,21 @@ def _add_svn_segment(powerline): powerline.append(' %s ' % changes, powerline.theme.SVN_CHANGES_FG, powerline.theme.SVN_CHANGES_BG) -def add_svn_segment(powerline): - """Wraps _add_svn_segment in exception handling.""" +class Segment(BasicSegment): + def add_to_powerline(self): + """Wraps _add_svn_segment in exception handling.""" + powerline = self.powerline - # 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. + # 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 + try: + _add_svn_segment(powerline) + except OSError: + pass + except subprocess.CalledProcessError: + pass diff --git a/powerline_shell/segments/time.py b/powerline_shell/segments/time.py index dedad80..413abb6 100644 --- a/powerline_shell/segments/time.py +++ b/powerline_shell/segments/time.py @@ -1,10 +1,16 @@ -def add_time_segment(powerline): - if powerline.args.shell == 'bash': - time = ' \\t ' - elif powerline.args.shell == 'zsh': - time = ' %* ' - else: - import time - time = ' %s ' % time.strftime('%H:%M:%S') +from ..utils import BasicSegment +import time - powerline.append(time, powerline.theme.HOSTNAME_FG, powerline.theme.HOSTNAME_BG) + +class Segment(BasicSegment): + def add_to_powerline(self): + powerline = self.powerline + if powerline.args.shell == 'bash': + time = ' \\t ' + elif powerline.args.shell == 'zsh': + time = ' %* ' + else: + time = ' %s ' % time.strftime('%H:%M:%S') + powerline.append(time, + powerline.theme.HOSTNAME_FG, + powerline.theme.HOSTNAME_BG) diff --git a/powerline_shell/segments/uptime.py b/powerline_shell/segments/uptime.py index e53e258..b282100 100644 --- a/powerline_shell/segments/uptime.py +++ b/powerline_shell/segments/uptime.py @@ -1,16 +1,20 @@ import subprocess import re +from ..utils import BasicSegment -def add_uptime_segment(powerline): - try: - output = subprocess.check_output(['uptime'], stderr=subprocess.STDOUT) - raw_uptime = re.search('(?<=up).+(?=,\s+\d+\s+user)', output).group(0) - day_search = re.search('\d+(?=\s+day)', output) - days = '' if not day_search else '%sd ' % day_search.group(0) - hour_search = re.search('\d{1,2}(?=\:)', raw_uptime) - hours = '' if not hour_search else '%sh ' % hour_search.group(0) - minutes = re.search('(?<=\:)\d{1,2}|\d{1,2}(?=\s+min)', raw_uptime).group(0) - uptime = u' %s%s%sm \u2191 ' % (days, hours, minutes) - powerline.append(uptime, powerline.theme.CWD_FG, powerline.theme.PATH_BG) - except OSError: - return + +class Segment(BasicSegment): + def add_to_powerline(self): + powerline = self.powerline + try: + output = subprocess.check_output(['uptime'], stderr=subprocess.STDOUT) + raw_uptime = re.search('(?<=up).+(?=,\s+\d+\s+user)', output).group(0) + day_search = re.search('\d+(?=\s+day)', output) + days = '' if not day_search else '%sd ' % day_search.group(0) + hour_search = re.search('\d{1,2}(?=\:)', raw_uptime) + hours = '' if not hour_search else '%sh ' % hour_search.group(0) + minutes = re.search('(?<=\:)\d{1,2}|\d{1,2}(?=\s+min)', raw_uptime).group(0) + uptime = u' %s%s%sm \u2191 ' % (days, hours, minutes) + powerline.append(uptime, powerline.theme.CWD_FG, powerline.theme.PATH_BG) + except OSError: + return diff --git a/powerline_shell/segments/username.py b/powerline_shell/segments/username.py index d8cc177..be97c52 100644 --- a/powerline_shell/segments/username.py +++ b/powerline_shell/segments/username.py @@ -1,16 +1,20 @@ +from ..utils import BasicSegment -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 = powerline.theme.USERNAME_ROOT_BG - else: - bgcolor = powerline.theme.USERNAME_BG +class Segment(BasicSegment): + def add_to_powerline(self): + powerline = self.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') - powerline.append(user_prompt, powerline.theme.USERNAME_FG, bgcolor) + if os.getenv('USER') == 'root': + bgcolor = powerline.theme.USERNAME_ROOT_BG + else: + bgcolor = powerline.theme.USERNAME_BG + + powerline.append(user_prompt, powerline.theme.USERNAME_FG, bgcolor) From 0fcb9f82059ed7534966ff08cc73d77a1c93c76d Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 26 Aug 2017 18:36:36 -0400 Subject: [PATCH 34/93] convert fossil and npm segments to be threaded --- powerline_shell/segments/fossil.py | 73 ++++++++++++------------- powerline_shell/segments/npm_version.py | 19 ++++--- 2 files changed, 47 insertions(+), 45 deletions(-) diff --git a/powerline_shell/segments/fossil.py b/powerline_shell/segments/fossil.py index 1fa525f..cb1a26b 100644 --- a/powerline_shell/segments/fossil.py +++ b/powerline_shell/segments/fossil.py @@ -1,6 +1,18 @@ import os import subprocess -from ..utils import BasicSegment +from ..utils import ThreadedSegment + + +def get_fossil_branch(): + try: + subprocess.Popen(['fossil'], stdout=subprocess.PIPE).communicate() + except OSError: + return None + return ''.join([ + i.replace('*','').strip() + for i in os.popen("fossil branch 2> /dev/null").read().strip().split("\n") + if i.startswith('*') + ]) def get_fossil_status(): @@ -8,47 +20,34 @@ def get_fossil_status(): 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_untracked_files = bool( + os.popen("fossil extras 2>/dev/null").read().strip() + ) 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 = powerline.theme.REPO_CLEAN_BG - fg = powerline.theme.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 = powerline.theme.REPO_DIRTY_BG - fg = powerline.theme.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) +class Segment(ThreadedSegment): + def run(self): + self.branch = get_fossil_branch() + self.status = get_fossil_status() if self.branch else None - -class Segment(BasicSegment): def add_to_powerline(self): - """Wraps _add_fossil_segment in exception handling.""" + self.join() powerline = self.powerline - - # 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 + if not self.branch or not self.status: + return + has_modified, has_untracked, has_missing = self.status + bg = powerline.theme.REPO_CLEAN_BG + fg = powerline.theme.REPO_CLEAN_FG + if has_modified or has_untracked or has_missing: + bg = powerline.theme.REPO_DIRTY_BG + fg = powerline.theme.REPO_DIRTY_FG + extra = '' + if has_untracked: + extra += '+' + if has_missing: + extra += '!' + self.branch += (' ' + extra if extra != '' else '') + powerline.append(' %s ' % self.branch, fg, bg) diff --git a/powerline_shell/segments/npm_version.py b/powerline_shell/segments/npm_version.py index 164c1a0..4ef23bc 100644 --- a/powerline_shell/segments/npm_version.py +++ b/powerline_shell/segments/npm_version.py @@ -1,14 +1,17 @@ import subprocess -from ..utils import BasicSegment +from ..utils import ThreadedSegment -class Segment(BasicSegment): - def add_to_powerline(self): - powerline = self.powerline +class Segment(ThreadedSegment): + def run(self): try: p1 = subprocess.Popen(["npm", "--version"], stdout=subprocess.PIPE) - version = p1.communicate()[0].decode("utf-8").rstrip() - version = "npm " + version - powerline.append(version, 15, 18) + self.version = p1.communicate()[0].decode("utf-8").rstrip() except OSError: - return + self.version = None + + def add_to_powerline(self): + self.join() + if self.version: + # FIXME no hard-coded colors + self.powerline.append("npm " + self.version, 15, 18) From d5854473d319b04763d6976facbf5926c4560269 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 26 Aug 2017 18:50:37 -0400 Subject: [PATCH 35/93] fix setup script, move configuration into json --- .gitignore | 4 ++++ powerline_shell/__init__.py | 7 ++++--- setup.py | 7 +++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index d84c36e..42a5cc7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ powerline-shell.py *.py[co] config.py powerline_shell.egg-info/ +/build/ +/dist/ +tags +config.json diff --git a/powerline_shell/__init__.py b/powerline_shell/__init__.py index 4b23322..3990654 100644 --- a/powerline_shell/__init__.py +++ b/powerline_shell/__init__.py @@ -4,8 +4,8 @@ from __future__ import print_function import argparse import os import sys -import config import importlib +import json from .themes.default import DefaultColor from .utils import warn, py3 @@ -142,10 +142,11 @@ def main(): arg_parser.add_argument('prev_error', nargs='?', type=int, default=0, help='Error code returned by the last command') args = arg_parser.parse_args() - + with open("config.json") as f: + config = json.loads(f.read()) powerline = Powerline(args, get_valid_cwd()) segments = [] - for seg_name in config.SEGMENTS: + for seg_name in config["segments"]: mod = importlib.import_module("powerline_shell.segments." + seg_name) segments.append(getattr(mod, "Segment")(powerline)) for segment in segments: diff --git a/setup.py b/setup.py index 69843b0..6159f34 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,11 @@ setup(name="powerline-shell", author="Buck Ryan", url="httpss://github.com/banga/powerline-shell", classifiers=[], - py_modules=["powerline_shell"], + packages=[ + "powerline_shell", + "powerline_shell.segments", + "powerline_shell.themes", + ], install_requires=[ "argparse", ], @@ -15,5 +19,4 @@ setup(name="powerline-shell", [console_scripts] powerline-shell=powerline_shell:main """, - packages=["powerline_shell"], ) From 99ddecec21f95a8d936906eb6b3abe54f40fa99c Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 26 Aug 2017 20:16:57 -0400 Subject: [PATCH 36/93] more threaded segments --- powerline_shell/segments/hg.py | 31 +++++++++++++----------- powerline_shell/segments/node_version.py | 18 ++++++++------ powerline_shell/segments/php_version.py | 20 +++++++-------- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/powerline_shell/segments/hg.py b/powerline_shell/segments/hg.py index 9237146..2acf3a1 100644 --- a/powerline_shell/segments/hg.py +++ b/powerline_shell/segments/hg.py @@ -1,6 +1,6 @@ import os import subprocess -from ..utils import BasicSegment +from ..utils import ThreadedSegment def get_hg_status(): @@ -23,22 +23,25 @@ def get_hg_status(): return has_modified_files, has_untracked_files, has_missing_files -class Segment(BasicSegment): +class Segment(ThreadedSegment): + def run(self): + self.branch = os.popen('hg branch 2> /dev/null').read().rstrip() + self.status = get_hg_status() if self.branch else None + def add_to_powerline(self): - powerline = self.powerline - branch = os.popen('hg branch 2> /dev/null').read().rstrip() - if len(branch) == 0: - return False - bg = powerline.theme.REPO_CLEAN_BG - fg = powerline.theme.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 = powerline.theme.REPO_DIRTY_BG - fg = powerline.theme.REPO_DIRTY_FG + self.join() + if not self.branch or not self.status: + return + bg = self.powerline.theme.REPO_CLEAN_BG + fg = self.powerline.theme.REPO_CLEAN_FG + has_modified, has_untracked, has_missing = self.status + if has_modified or has_untracked or has_missing: + bg = self.powerline.theme.REPO_DIRTY_BG + fg = self.powerline.theme.REPO_DIRTY_FG extra = '' - if has_untracked_files: + if has_untracked: extra += '+' - if has_missing_files: + if has_missing: extra += '!' branch += (' ' + extra if extra != '' else '') return powerline.append(' %s ' % branch, fg, bg) diff --git a/powerline_shell/segments/node_version.py b/powerline_shell/segments/node_version.py index e3a9f29..aa19592 100644 --- a/powerline_shell/segments/node_version.py +++ b/powerline_shell/segments/node_version.py @@ -1,14 +1,18 @@ import subprocess -from ..utils import BasicSegment +from ..utils import ThreadedSegment -class Segment(BasicSegment): - def add_to_powerline(self): - powerline = self.powerline +class Segment(ThreadedSegment): + def run(self): try: p1 = subprocess.Popen(["node", "--version"], stdout=subprocess.PIPE) - version = p1.communicate()[0].decode("utf-8").rstrip() - version = "node " + version - powerline.append(version, 15, 18) + self.version = p1.communicate()[0].decode("utf-8").rstrip() except OSError: + self.version = None + + def add_to_powerline(self): + self.join() + if not self.version: return + # FIXME no hard-coded colors + self.powerline.append("node " + self.version, 15, 18) diff --git a/powerline_shell/segments/php_version.py b/powerline_shell/segments/php_version.py index f905a34..dc9a955 100644 --- a/powerline_shell/segments/php_version.py +++ b/powerline_shell/segments/php_version.py @@ -1,17 +1,17 @@ import subprocess -from ..utils import BasicSegment +from ..utils import ThreadedSegment -class Segment(BasicSegment): - def add_to_powerline(self): - powerline = self.powerline +class Segment(ThreadedSegment): + def run(self): try: output = subprocess.check_output(['php', '-r', 'echo PHP_VERSION;'], stderr=subprocess.STDOUT) - if '-' in output: - version = ' %s ' % output.split('-')[0] - else: - version = ' %s ' % output - powerline.append(version, 15, 4) + self.version = output.split('-')[0] if '-' in output else output except OSError: - return + self.version = None + + def add_to_powerline(self): + self.join() + # FIXME no hard-coded colors + self.powerline.append(" " + version + " ", 15, 4) From 0bee43f2b3d0c470210450139eb8b338214ed187 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 26 Aug 2017 20:20:52 -0400 Subject: [PATCH 37/93] fix issues with php version --- powerline_shell/segments/php_version.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/powerline_shell/segments/php_version.py b/powerline_shell/segments/php_version.py index dc9a955..52f1899 100644 --- a/powerline_shell/segments/php_version.py +++ b/powerline_shell/segments/php_version.py @@ -13,5 +13,7 @@ class Segment(ThreadedSegment): def add_to_powerline(self): self.join() + if not self.version: + return # FIXME no hard-coded colors - self.powerline.append(" " + version + " ", 15, 4) + self.powerline.append(" " + self.version + " ", 15, 4) From 46f992d9f72df7f8a06ac6b68956e152978fafcb Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 26 Aug 2017 20:30:59 -0400 Subject: [PATCH 38/93] Fix tests --- powerline_shell/__init__.py | 4 +++- powerline_shell/segments/git.py | 4 ++-- powerline_shell/utils.py | 4 +++- test/cwd_test.py | 10 +++++----- test/repo_stats_test.py | 3 +-- test/segments_test/git_test.py | 28 ++++++++++++++++------------ test/segments_test/uptime_test.py | 7 ++++--- 7 files changed, 34 insertions(+), 26 deletions(-) diff --git a/powerline_shell/__init__.py b/powerline_shell/__init__.py index 3990654..9627271 100644 --- a/powerline_shell/__init__.py +++ b/powerline_shell/__init__.py @@ -148,7 +148,9 @@ def main(): segments = [] for seg_name in config["segments"]: mod = importlib.import_module("powerline_shell.segments." + seg_name) - segments.append(getattr(mod, "Segment")(powerline)) + segment = getattr(mod, "Segment")(powerline) + segment.start() + segments.append(segment) for segment in segments: segment.add_to_powerline() sys.stdout.write(powerline.draw()) diff --git a/powerline_shell/segments/git.py b/powerline_shell/segments/git.py index 03dd09f..95e2c2a 100644 --- a/powerline_shell/segments/git.py +++ b/powerline_shell/segments/git.py @@ -66,11 +66,11 @@ def build_stats(): env=git_subprocess_env()) except OSError: # Popen will throw an OSError if git is not found - return None + return (None, None) pdata = p.communicate() if p.returncode != 0: - return None + return (None, None) status = pdata[0].decode("utf-8").splitlines() stats = parse_git_stats(status) diff --git a/powerline_shell/utils.py b/powerline_shell/utils.py index c817190..27d7986 100644 --- a/powerline_shell/utils.py +++ b/powerline_shell/utils.py @@ -75,9 +75,11 @@ class BasicSegment(object): def __init__(self, powerline): self.powerline = powerline + def start(self): + pass + class ThreadedSegment(threading.Thread): def __init__(self, powerline): super(ThreadedSegment, self).__init__() self.powerline = powerline - self.start() diff --git a/test/cwd_test.py b/test/cwd_test.py index 111863e..ed52dc3 100644 --- a/test/cwd_test.py +++ b/test/cwd_test.py @@ -3,7 +3,7 @@ import mock import os import tempfile import shutil -import powerline_shell_base as p +import powerline_shell as p class CwdTest(unittest.TestCase): @@ -15,14 +15,14 @@ class CwdTest(unittest.TestCase): shutil.rmtree(self.dirname) @mock.patch('os.getenv') - @mock.patch('powerline_shell_base.warn') + @mock.patch('powerline_shell.warn') def test_normal(self, warn, getenv): getenv.return_value = self.dirname self.assertEqual(p.get_valid_cwd(), self.dirname) self.assertEqual(warn.call_count, 0) @mock.patch('os.getenv') - @mock.patch('powerline_shell_base.warn') + @mock.patch('powerline_shell.warn') def test_nonexistent_warns(self, warn, getenv): subdir = os.path.join(self.dirname, 'subdir') getenv.return_value = subdir @@ -30,7 +30,7 @@ class CwdTest(unittest.TestCase): self.assertEqual(warn.call_count, 1) @mock.patch('os.getenv') - @mock.patch('powerline_shell_base.warn') + @mock.patch('powerline_shell.warn') def test_falls_back_to_getcwd(self, warn, getenv): getenv.return_value = None os.chdir(self.dirname) @@ -38,7 +38,7 @@ class CwdTest(unittest.TestCase): self.assertEqual(warn.call_count, 0) @mock.patch('os.getenv') - @mock.patch('powerline_shell_base.warn') + @mock.patch('powerline_shell.warn') def test_nonexistent_getcwd_warns(self, warn, getenv): subdir = os.path.join(self.dirname, 'subdir') getenv.return_value = None diff --git a/test/repo_stats_test.py b/test/repo_stats_test.py index 393eac3..fe4f10d 100644 --- a/test/repo_stats_test.py +++ b/test/repo_stats_test.py @@ -1,12 +1,11 @@ import unittest -import powerline_shell_base as p from powerline_shell.utils import RepoStats class RepoStatsTest(unittest.TestCase): def setUp(self): - self.repo_stats = p.RepoStats() + self.repo_stats = RepoStats() self.repo_stats.not_staged = 1 self.repo_stats.conflicted = 4 diff --git a/test/segments_test/git_test.py b/test/segments_test/git_test.py index 7ca344a..c2bcde5 100644 --- a/test/segments_test/git_test.py +++ b/test/segments_test/git_test.py @@ -3,11 +3,7 @@ import mock import tempfile import shutil import sh -import powerline_shell_base as p -import segments.git as git - -git.Color = mock.MagicMock() -git.RepoStats = p.RepoStats +import powerline_shell.segments.git as git class GitTest(unittest.TestCase): @@ -19,6 +15,8 @@ class GitTest(unittest.TestCase): sh.cd(self.dirname) sh.git("init", ".") + self.segment = git.Segment(self.powerline) + def tearDown(self): shutil.rmtree(self.dirname) @@ -33,30 +31,35 @@ class GitTest(unittest.TestCase): def _get_commit_hash(self): return sh.git("rev-parse", "HEAD") - @mock.patch('segments.git.get_PATH') + @mock.patch('powerline_shell.segments.git.get_PATH') def test_git_not_installed(self, get_PATH): get_PATH.return_value = "" # so git can't be found - git.add_git_segment(self.powerline) + self.segment.start() + self.segment.add_to_powerline() self.assertEqual(self.powerline.append.call_count, 0) def test_non_git_directory(self): shutil.rmtree(".git") - git.add_git_segment(self.powerline) + self.segment.start() + self.segment.add_to_powerline() self.assertEqual(self.powerline.append.call_count, 0) def test_big_bang(self): - git.add_git_segment(self.powerline) + self.segment.start() + self.segment.add_to_powerline() self.assertEqual(self.powerline.append.call_args[0][0], ' Big Bang ') def test_master_branch(self): self._add_and_commit("foo") - git.add_git_segment(self.powerline) + self.segment.start() + self.segment.add_to_powerline() self.assertEqual(self.powerline.append.call_args[0][0], ' master ') def test_different_branch(self): self._add_and_commit("foo") self._new_branch("bar") - git.add_git_segment(self.powerline) + self.segment.start() + self.segment.add_to_powerline() self.assertEqual(self.powerline.append.call_args[0][0], ' bar ') def test_detached(self): @@ -64,7 +67,8 @@ class GitTest(unittest.TestCase): commit_hash = self._get_commit_hash() self._add_and_commit("bar") sh.git("checkout", "HEAD^") - git.add_git_segment(self.powerline) + self.segment.start() + self.segment.add_to_powerline() # In detached mode, we output a unicode symbol and then the shortened # commit hash. diff --git a/test/segments_test/uptime_test.py b/test/segments_test/uptime_test.py index d2dacd2..3285a00 100644 --- a/test/segments_test/uptime_test.py +++ b/test/segments_test/uptime_test.py @@ -1,6 +1,6 @@ import unittest import mock -import segments.uptime as uptime +import powerline_shell.segments.uptime as uptime test_cases = { # linux test cases @@ -19,11 +19,12 @@ class UptimeTest(unittest.TestCase): def setUp(self): self.powerline = mock.MagicMock() - uptime.Color = mock.MagicMock() + self.segment = uptime.Segment(self.powerline) @mock.patch('subprocess.check_output') def test_all(self, check_output): for stdout, result in test_cases.items(): check_output.return_value = stdout - uptime.add_uptime_segment(self.powerline) + self.segment.start() + self.segment.add_to_powerline() self.assertEqual(self.powerline.append.call_args[0][0].split()[0], result) From 5d23890c81bc7fa60e585b8f20adaf99c848ab1a Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 26 Aug 2017 21:29:42 -0400 Subject: [PATCH 39/93] convert many arguments to config options --- .gitignore | 1 + powerline_shell/__init__.py | 106 ++++++++++++++------------- powerline_shell/color_compliment.py | 2 +- powerline_shell/segments/cwd.py | 17 +++-- powerline_shell/segments/hostname.py | 8 +- 5 files changed, 74 insertions(+), 60 deletions(-) diff --git a/.gitignore b/.gitignore index 42a5cc7..a18fc9f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ powerline_shell.egg-info/ /dist/ tags config.json +powerline-shell.json diff --git a/powerline_shell/__init__.py b/powerline_shell/__init__.py index 9627271..7d70ebe 100644 --- a/powerline_shell/__init__.py +++ b/powerline_shell/__init__.py @@ -10,6 +10,36 @@ from .themes.default import DefaultColor from .utils import warn, py3 +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 + + class Powerline(object): symbols = { 'compatible': { @@ -38,11 +68,13 @@ class Powerline(object): 'bare': '%s', } - def __init__(self, args, cwd, theme=None): + def __init__(self, args, config, theme=None): self.args = args - self.cwd = cwd + self.config = config self.theme = theme or DefaultColor - mode, shell = args.mode, args.shell + self.cwd = get_valid_cwd() + mode = config.get("mode", "patched") + shell = config.get("shell", "bash") self.color_template = self.color_templates[shell] self.reset = self.color_template % '[0m' self.lock = Powerline.symbols[mode]['lock'] @@ -51,6 +83,9 @@ class Powerline(object): 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 '' @@ -91,60 +126,33 @@ class Powerline(object): segment[3])) -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 +def find_config(): + for location in [ + "powerline-shell.json", + "~/.powerline-shell.json", + ]: + full = os.path.expanduser(location) + if os.path.exists(full): + return full def 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']) + 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') + help='Error code returned by the last command') args = arg_parser.parse_args() - with open("config.json") as f: + + config_path = find_config() + if not config_path: + warn("No config found") + return 1 + with open(config_path) as f: config = json.loads(f.read()) - powerline = Powerline(args, get_valid_cwd()) + + powerline = Powerline(args, config) segments = [] for seg_name in config["segments"]: mod = importlib.import_module("powerline_shell.segments." + seg_name) diff --git a/powerline_shell/color_compliment.py b/powerline_shell/color_compliment.py index 08f8bea..6a64dc4 100644 --- a/powerline_shell/color_compliment.py +++ b/powerline_shell/color_compliment.py @@ -6,7 +6,7 @@ except ImportError: from hashlib import md5 import sys from .colortrans import * -from ..utils import py3 +from .utils import py3 def getOppositeColor(r,g,b): diff --git a/powerline_shell/segments/cwd.py b/powerline_shell/segments/cwd.py index a1d7d29..22c439d 100644 --- a/powerline_shell/segments/cwd.py +++ b/powerline_shell/segments/cwd.py @@ -5,6 +5,10 @@ from ..utils import warn, py3, BasicSegment ELLIPSIS = u'\u2026' +def _mode(powerline): + return powerline.segment_conf("cwd", "mode", "fancy") + + def replace_home_dir(cwd): home = os.getenv('HOME') if cwd.startswith(home): @@ -34,8 +38,9 @@ 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] + max_size = powerline.segment_conf("cwd", "max_dir_size") + if max_size: + return name[:max_size] return name @@ -57,15 +62,15 @@ def add_cwd_segment(powerline): cwd = cwd.decode("utf-8") cwd = replace_home_dir(cwd) - if powerline.args.cwd_mode == 'plain': + if _mode(powerline) == 'plain': powerline.append(' %s ' % (cwd,), powerline.theme.CWD_FG, powerline.theme.PATH_BG) return names = split_path_into_names(cwd) - max_depth = powerline.args.cwd_max_depth + max_depth = powerline.segment_conf("cwd", "max_depth", 5) if max_depth <= 0: - warn("Ignoring --cwd-max-depth argument since it's not greater than 0") + warn("Ignoring cwd.max_depth option 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 @@ -77,7 +82,7 @@ def add_cwd_segment(powerline): 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): + if _mode(powerline) == "dironly": # The user has indicated they only want the current directory to be # displayed, so chop everything else off names = names[-1:] diff --git a/powerline_shell/segments/hostname.py b/powerline_shell/segments/hostname.py index ddbaff6..d3e6281 100644 --- a/powerline_shell/segments/hostname.py +++ b/powerline_shell/segments/hostname.py @@ -1,13 +1,13 @@ from ..utils import BasicSegment +from ..color_compliment import stringToHashToColorAndOpposite +from ..colortrans import rgb2short +from socket import gethostname class Segment(BasicSegment): def add_to_powerline(self): powerline = self.powerline - if powerline.args.colorize_hostname: - from lib.color_compliment import stringToHashToColorAndOpposite - from lib.colortrans import rgb2short - from socket import gethostname + if powerline.segment_conf("hostname", "colorize"): hostname = gethostname() FG, BG = stringToHashToColorAndOpposite(hostname) FG, BG = (rgb2short(*color) for color in [FG, BG]) From fc2f059ed478a16c70ca683fe475f1f06ff8eb50 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 26 Aug 2017 21:37:09 -0400 Subject: [PATCH 40/93] Fix theme support --- powerline_shell/themes/colortest.py => colortest.py | 0 powerline_shell/__init__.py | 11 +++++++---- powerline_shell/themes/basic.py | 4 +++- powerline_shell/themes/default.py | 2 +- .../themes/{solarized-dark.py => solarized_dark.py} | 3 +++ powerline_shell/themes/washed.py | 3 +++ 6 files changed, 17 insertions(+), 6 deletions(-) rename powerline_shell/themes/colortest.py => colortest.py (100%) rename powerline_shell/themes/{solarized-dark.py => solarized_dark.py} (94%) diff --git a/powerline_shell/themes/colortest.py b/colortest.py similarity index 100% rename from powerline_shell/themes/colortest.py rename to colortest.py diff --git a/powerline_shell/__init__.py b/powerline_shell/__init__.py index 7d70ebe..46511a0 100644 --- a/powerline_shell/__init__.py +++ b/powerline_shell/__init__.py @@ -6,7 +6,6 @@ import os import sys import importlib import json -from .themes.default import DefaultColor from .utils import warn, py3 @@ -68,10 +67,10 @@ class Powerline(object): 'bare': '%s', } - def __init__(self, args, config, theme=None): + def __init__(self, args, config, theme): self.args = args self.config = config - self.theme = theme or DefaultColor + self.theme = theme self.cwd = get_valid_cwd() mode = config.get("mode", "patched") shell = config.get("shell", "bash") @@ -152,7 +151,11 @@ def main(): with open(config_path) as f: config = json.loads(f.read()) - powerline = Powerline(args, config) + theme_name = config.get("theme", "default") + mod = importlib.import_module("powerline_shell.themes." + theme_name) + theme = getattr(mod, "Color") + + powerline = Powerline(args, config, theme) segments = [] for seg_name in config["segments"]: mod = importlib.import_module("powerline_shell.segments." + seg_name) diff --git a/powerline_shell/themes/basic.py b/powerline_shell/themes/basic.py index 5d07691..efb87cd 100644 --- a/powerline_shell/themes/basic.py +++ b/powerline_shell/themes/basic.py @@ -1,6 +1,8 @@ -# Basic theme which only uses colors in 0-15 range +from .default import DefaultColor + class Color(DefaultColor): + """Basic theme which only uses colors in 0-15 range""" USERNAME_FG = 8 USERNAME_BG = 15 USERNAME_ROOT_BG = 1 diff --git a/powerline_shell/themes/default.py b/powerline_shell/themes/default.py index 6526158..faa0473 100644 --- a/powerline_shell/themes/default.py +++ b/powerline_shell/themes/default.py @@ -1,4 +1,4 @@ -class DefaultColor: +class DefaultColor(object): """ This class should have the default colors for every segment. Please test every new segment with this theme first. diff --git a/powerline_shell/themes/solarized-dark.py b/powerline_shell/themes/solarized_dark.py similarity index 94% rename from powerline_shell/themes/solarized-dark.py rename to powerline_shell/themes/solarized_dark.py index 4e158c8..7e0f2d8 100644 --- a/powerline_shell/themes/solarized-dark.py +++ b/powerline_shell/themes/solarized_dark.py @@ -1,3 +1,6 @@ +from .default import DefaultColor + + class Color(DefaultColor): USERNAME_FG = 15 USERNAME_BG = 4 diff --git a/powerline_shell/themes/washed.py b/powerline_shell/themes/washed.py index 3c455e3..d1f2642 100644 --- a/powerline_shell/themes/washed.py +++ b/powerline_shell/themes/washed.py @@ -1,3 +1,6 @@ +from .default import DefaultColor + + class Color(DefaultColor): USERNAME_FG = 8 USERNAME_BG = 251 From 658d440ac792a5d7c3a42bb8a7439ff022e83d73 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 26 Aug 2017 21:41:47 -0400 Subject: [PATCH 41/93] default config --- powerline_shell/__init__.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/powerline_shell/__init__.py b/powerline_shell/__init__.py index 46511a0..9f14bde 100644 --- a/powerline_shell/__init__.py +++ b/powerline_shell/__init__.py @@ -134,6 +134,20 @@ def find_config(): if os.path.exists(full): return full +DEFAULT_CONFIG = { + "segments": [ + 'virtual_env', + 'username', + 'hostname', + 'ssh', + 'cwd', + 'git', + 'hg', + 'jobs', + 'root', + ] +} + def main(): arg_parser = argparse.ArgumentParser() @@ -145,11 +159,11 @@ def main(): args = arg_parser.parse_args() config_path = find_config() - if not config_path: - warn("No config found") - return 1 - with open(config_path) as f: - config = json.loads(f.read()) + if config_path: + with open(config_path) as f: + config = json.loads(f.read()) + else: + config = DEFAULT_CONFIG theme_name = config.get("theme", "default") mod = importlib.import_module("powerline_shell.themes." + theme_name) From af0ecb1ce394607d21dd84f66ede4930c0f06ed5 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 26 Aug 2017 21:45:12 -0400 Subject: [PATCH 42/93] try to get circle working --- circle.yml | 4 +++- dev_requirements.txt => requirements-dev.txt | 0 2 files changed, 3 insertions(+), 1 deletion(-) rename dev_requirements.txt => requirements-dev.txt (100%) diff --git a/circle.yml b/circle.yml index 7638a89..3d60d33 100644 --- a/circle.yml +++ b/circle.yml @@ -1,5 +1,7 @@ dependencies: pre: - - sudo pip install -r dev_requirements.txt + - sudo pip install -r requirements-dev.txt - git config --global user.email "tester@example.com" - git config --global user.name "Tester McGee" +test: + nosetests diff --git a/dev_requirements.txt b/requirements-dev.txt similarity index 100% rename from dev_requirements.txt rename to requirements-dev.txt From dfeb3bc8c30c333bf4887bc06fb0110264dfd5f2 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 26 Aug 2017 22:11:23 -0400 Subject: [PATCH 43/93] travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d1ad0ae --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: python From a92a79047b57c1c4b755c1172bb636ce6a72d65a Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 26 Aug 2017 22:16:08 -0400 Subject: [PATCH 44/93] more travis --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index d1ad0ae..a587362 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,5 @@ language: python +script: | + ./setup.py install + pip install -r requirements-dev.txt + nosetests From 73994c6a7b0fe2394ef7c42fb50d129ddcc309ba Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 26 Aug 2017 22:18:52 -0400 Subject: [PATCH 45/93] remove circle --- circle.yml | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 circle.yml diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 3d60d33..0000000 --- a/circle.yml +++ /dev/null @@ -1,7 +0,0 @@ -dependencies: - pre: - - sudo pip install -r requirements-dev.txt - - git config --global user.email "tester@example.com" - - git config --global user.name "Tester McGee" -test: - nosetests From 807dc6dfc54f2319af7c5908ff2f8c20ea5a832e Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 26 Aug 2017 22:45:31 -0400 Subject: [PATCH 46/93] fix setup.py link --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6159f34..87e8bc7 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup(name="powerline-shell", version="0.1.0-alpha", description="A pretty prompt for your shell", author="Buck Ryan", - url="httpss://github.com/banga/powerline-shell", + url="https://github.com/banga/powerline-shell", classifiers=[], packages=[ "powerline_shell", From 401aef8fc24a78a5d0c4137cb9123d25683845d4 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sun, 27 Aug 2017 00:00:30 -0400 Subject: [PATCH 47/93] README updates --- README.md | 182 +++++++++++++++++++------------- powerline_shell/__init__.py | 11 +- powerline_shell/segments/cwd.py | 8 +- 3 files changed, 120 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 2154a26..f0a64cb 100644 --- a/README.md +++ b/README.md @@ -17,14 +17,16 @@ ZSH and Fish: - [Version Control](#version-control) - [Setup](#setup) - - [All Shells](#all-shells) - [Bash](#bash) - [ZSH](#zsh) - [Fish](#fish) - [Customization](#customization) + - [Config File](#config-file) - [Adding, Removing and Re-arranging segments](#adding-removing-and-re-arranging-segments) + - [Changing the Look](#changing-the-look) + - [Themes](#themes) + - [Segment Configuration](#segment-configuration) - [Contributing new types of segments](#contributing-new-types-of-segments) - - [Themes](#themes) - [Troubleshooting](#troubleshooting) @@ -68,53 +70,13 @@ setting your $TERM to `xterm-256color`, because that works for me. commit](https://github.com/milkbikis/powerline-shell/commit/2a84ecc) in your copy -- Clone this repository somewhere: +- Install using pip: ``` -git clone https://github.com/milkbikis/powerline-shell +pip install --index-url https://test.pypi.org/simple/ 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 ~/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 -``` +- Setup your shell prompt using the instructions for your shell below. ### Bash @@ -122,7 +84,7 @@ Add the following to your `.bashrc` (or `.profile` on Mac): ``` function _update_ps1() { - PS1="$(~/powerline-shell.py $? 2> /dev/null)" + PS1="$(powerline-shell $?)" } if [ "$TERM" != "linux" ]; then @@ -136,7 +98,7 @@ Add the following to your `.zshrc`: ``` function powerline_precmd() { - PS1="$(~/powerline-shell.py $? --shell zsh 2> /dev/null)" + PS1="$(powerline-shell --shell zsh $?)" } function install_powerline_precmd() { @@ -159,27 +121,115 @@ Redefine `fish_prompt` in ~/.config/fish/config.fish: ``` function fish_prompt - ~/powerline-shell.py $status --shell bare ^/dev/null + powerline-shell --shell bare $status end ``` ## Customization +### Config File + +Powerline-shell is customizable through the use of a config file. This file is +expected to be located at `~/.powerline-shell.json`. You can generate the +default config at this location using: + +``` +powerline-shell --generate-config > ~/.powerline-shell.json +``` + ### 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. +Once you have generated your config file, you can now start adding or removing +"segments" - the building blocks of your shell. The list of segments available +are: + +- `cwd` - Shows your current working directory. See [Segment + Configuration](#segment-configuration) for some options. +- `exit_code` - When the previous command ends in a non-zero status, shows the + value of the exist status in red. +- `fossil` - Details about the current Fossil repo. +- `git` - Details about the current Git repo. +- `hg` - Details about the current Mercurial repo. +- `hostname` - Current machine's hostname. +- `jobs` - Number of background jobs currently running. +- `newline` - Inserts a newline into the prompt. +- `node_version` - `node --version` +- `npm_version` - `npm --version` +- `php_version` - Version of php on the machine +- `rbenv` - `rbenv local` +- `read_only` - Shows a lock icon if the current directory is read-only. +- `root` - Shows a `#` if logged in as root, `$` otherwise. +- `ruby_version` - `ruby --version` +- `set_term_title` - If able, sets the title of your terminal to include some + useful info. +- `ssh` - If logged into over SSH, shows a network icon. +- `svn` - Details about the current SVN repo. +- `time` - Shows the current time +- `uptime` - Uptime of the current machine +- `username` - Name of the logged-in user +- `virtual_env` - Shows the name of the current virtual env or conda env. + +### Changing the Look + +There are a few optional arguments which can be seen by running +`powerline-shell.py --help`. + +``` + --mode {patched,compatible,flat} + The characters used to make separators between + segments +``` + +#### Themes + +The `powerline_shell/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 `~/.powerline-shell.json` to the name of your theme. + +A script for testing color combinations is provided at `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. + +### Segment Configuration + +Some segments support additional configuration. The options for the segment are +nested under the name of the segment itself. For example, all of the options +for the `cwd` segment are set in `~/.powerline-shell.py` like: + +``` +{ + "segments": [...], + "cwd": { + options go here + } +} +``` + +The options for the `cwd` segment are: + +- `mode`: If "plain" then simple text will be used to show the cwd. If + "dironly," only the current directory will be shown. Otherwise expands the + cwd into individual directories. +- `max_depth`: Maximum number of directories to show in path +- `max_dir_size`: Maximum number of characters displayed for each directory in + the path + +The `hostname` segment provides one option: + +- `colorize`: If true, the hostname will be colorized based on a hash of + itself. ### 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. +The `powerline_shell/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. 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 @@ -190,21 +240,7 @@ 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. +after install the requirements in `requirements-dev.txt`. ## Troubleshooting diff --git a/powerline_shell/__init__.py b/powerline_shell/__init__.py index 9f14bde..ac6fdc2 100644 --- a/powerline_shell/__init__.py +++ b/powerline_shell/__init__.py @@ -54,8 +54,8 @@ class Powerline(object): 'separator_thin': u'\uE0B1' }, 'flat': { - 'lock': '', - 'network': '', + 'lock': u'\uE0A2', + 'network': u'\uE0A2', 'separator': '', 'separator_thin': '' }, @@ -151,6 +151,8 @@ DEFAULT_CONFIG = { 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', 'zsh', 'bare']) @@ -158,6 +160,10 @@ def main(): 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: @@ -179,3 +185,4 @@ def main(): for segment in segments: segment.add_to_powerline() sys.stdout.write(powerline.draw()) + return 0 diff --git a/powerline_shell/segments/cwd.py b/powerline_shell/segments/cwd.py index 22c439d..f99eb13 100644 --- a/powerline_shell/segments/cwd.py +++ b/powerline_shell/segments/cwd.py @@ -5,10 +5,6 @@ from ..utils import warn, py3, BasicSegment ELLIPSIS = u'\u2026' -def _mode(powerline): - return powerline.segment_conf("cwd", "mode", "fancy") - - def replace_home_dir(cwd): home = os.getenv('HOME') if cwd.startswith(home): @@ -62,7 +58,7 @@ def add_cwd_segment(powerline): cwd = cwd.decode("utf-8") cwd = replace_home_dir(cwd) - if _mode(powerline) == 'plain': + if powerline.segment_conf("cwd", "mode") == 'plain': powerline.append(' %s ' % (cwd,), powerline.theme.CWD_FG, powerline.theme.PATH_BG) return @@ -82,7 +78,7 @@ def add_cwd_segment(powerline): n_before = 2 if max_depth > 2 else max_depth - 1 names = names[:n_before] + [ELLIPSIS] + names[n_before - max_depth:] - if _mode(powerline) == "dironly": + if powerline.segment_conf("cwd", "mode") == "dironly": # The user has indicated they only want the current directory to be # displayed, so chop everything else off names = names[-1:] From c42bbf5133c101c53fa576de09897b2b03d9ae1f Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sun, 27 Aug 2017 00:09:41 -0400 Subject: [PATCH 48/93] fix notes about mode --- README.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index f0a64cb..c82846d 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,8 @@ ZSH and Fish: - [Customization](#customization) - [Config File](#config-file) - [Adding, Removing and Re-arranging segments](#adding-removing-and-re-arranging-segments) - - [Changing the Look](#changing-the-look) - - [Themes](#themes) + - [Segment Separator](#segment-separator) + - [Themes](#themes) - [Segment Configuration](#segment-configuration) - [Contributing new types of segments](#contributing-new-types-of-segments) - [Troubleshooting](#troubleshooting) @@ -169,18 +169,19 @@ are: - `username` - Name of the logged-in user - `virtual_env` - Shows the name of the current virtual env or conda env. -### Changing the Look +### Segment Separator -There are a few optional arguments which can be seen by running -`powerline-shell.py --help`. +By default, a unicode character (resembling the > symbol) is used to separate +each segment. This can be changed by changing the "mode" option in the config +file. The available modes are: -``` - --mode {patched,compatible,flat} - The characters used to make separators between - segments -``` +- `patched` - The default +- `compatible` - Attempts to use characters that may already be available using + your chosen font. +- `flat` - No separator is used between segments, giving each segment a + rectangular appearance (and also saves space). -#### Themes +### Themes The `powerline_shell/themes` directory stores themes for your prompt, which are basically color values used by segments. The `default.py` defines a default From 3edec346ef00f30eb2229067006dc3bd0823aa5b Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sun, 3 Sep 2017 16:47:32 -0400 Subject: [PATCH 49/93] create a setup.cfg --- setup.cfg | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..1eee7db --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[bdist_wheel] +universal = 1 + +[metadata] +description-file = README.md From c3506d075e21171f2cf3abefb933f6bae44e2afa Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Tue, 5 Sep 2017 10:31:10 -0400 Subject: [PATCH 50/93] tests for hostname segment --- powerline_shell/segments/hostname.py | 15 ++++++--------- test/segments_test/hostname_test.py | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 test/segments_test/hostname_test.py diff --git a/powerline_shell/segments/hostname.py b/powerline_shell/segments/hostname.py index d3e6281..894e139 100644 --- a/powerline_shell/segments/hostname.py +++ b/powerline_shell/segments/hostname.py @@ -11,18 +11,15 @@ class Segment(BasicSegment): hostname = gethostname() FG, BG = stringToHashToColorAndOpposite(hostname) FG, BG = (rgb2short(*color) for color in [FG, BG]) - host_prompt = ' %s ' % hostname.split('.')[0] - + host_prompt = " %s " % hostname.split(".")[0] powerline.append(host_prompt, FG, BG) else: - if powerline.args.shell == 'bash': - host_prompt = ' \\h ' - elif powerline.args.shell == 'zsh': - host_prompt = ' %m ' + if powerline.args.shell == "bash": + host_prompt = r" \h " + elif powerline.args.shell == "zsh": + host_prompt = " %m " else: - import socket - host_prompt = ' %s ' % socket.gethostname().split('.')[0] - + host_prompt = " %s " % gethostname().split(".")[0] powerline.append(host_prompt, powerline.theme.HOSTNAME_FG, powerline.theme.HOSTNAME_BG) diff --git a/test/segments_test/hostname_test.py b/test/segments_test/hostname_test.py new file mode 100644 index 0000000..68e941c --- /dev/null +++ b/test/segments_test/hostname_test.py @@ -0,0 +1,21 @@ +import unittest +import mock +import powerline_shell.segments.hostname as hostname +from powerline_shell.themes.default import Color +from argparse import Namespace + + +class HostnameTest(unittest.TestCase): + def setUp(self): + self.powerline = mock.MagicMock() + self.powerline.theme = Color + self.segment = hostname.Segment(self.powerline) + + def test_colorize(self): + self.powerline.segment_conf.return_value = True + self.segment.start() + self.segment.add_to_powerline() + args = self.powerline.append.call_args[0] + self.assertNotEqual(args[0], r" \h ") + self.assertNotEqual(args[1], Color.HOSTNAME_FG) + self.assertNotEqual(args[2], Color.HOSTNAME_BG) From b5f2d8a525c1d1c1b6dd76e6a22d81f5cde0e30d Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Tue, 5 Sep 2017 10:39:35 -0400 Subject: [PATCH 51/93] link to my config file for an example --- README.md | 3 +++ powerline_shell/segments/ssh.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c82846d..a20cea2 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,9 @@ default config at this location using: powerline-shell --generate-config > ~/.powerline-shell.json ``` +(You can see an example config file +[here](https://github.com/b-ryan/dotfiles/blob/master/home/powerline-shell.json)) + ### Adding, Removing and Re-arranging segments Once you have generated your config file, you can now start adding or removing diff --git a/powerline_shell/segments/ssh.py b/powerline_shell/segments/ssh.py index 5b8af8b..f64c967 100644 --- a/powerline_shell/segments/ssh.py +++ b/powerline_shell/segments/ssh.py @@ -4,8 +4,8 @@ from ..utils import BasicSegment class Segment(BasicSegment): def add_to_powerline(self): - powerline = self.powerline if os.getenv('SSH_CLIENT'): + powerline = self.powerline powerline.append(' %s ' % powerline.network, powerline.theme.SSH_FG, powerline.theme.SSH_BG) From b053eb4e51dc79ccc95d96ede7c8a4a6c723a146 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Tue, 5 Sep 2017 10:49:23 -0400 Subject: [PATCH 52/93] fix issues with hg segment --- powerline_shell/segments/hg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/powerline_shell/segments/hg.py b/powerline_shell/segments/hg.py index 2acf3a1..ca0e339 100644 --- a/powerline_shell/segments/hg.py +++ b/powerline_shell/segments/hg.py @@ -43,5 +43,5 @@ class Segment(ThreadedSegment): extra += '+' if has_missing: extra += '!' - branch += (' ' + extra if extra != '' else '') - return powerline.append(' %s ' % branch, fg, bg) + self.branch += (' ' + extra if extra != '' else '') + return self.powerline.append(' %s ' % self.branch, fg, bg) From 270af5bae534c0c4ca4a2084f41c90697c12cf79 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Tue, 5 Sep 2017 21:06:36 -0400 Subject: [PATCH 53/93] remove config.py.dist --- config.py.dist | 63 -------------------------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 config.py.dist diff --git a/config.py.dist b/config.py.dist deleted file mode 100644 index 2affa3e..0000000 --- a/config.py.dist +++ /dev/null @@ -1,63 +0,0 @@ -# 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 current ruby environment (see http://rbenv.org/) -# 'rbenv', - -# 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', - -# Adds a line break -# 'newline', - -# 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' From 0b02a4baad85f38c0e572096789a44ab59a3767e Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Tue, 5 Sep 2017 21:42:58 -0400 Subject: [PATCH 54/93] tests and fixes for hg segment --- powerline_shell/segments/hg.py | 56 ++++++++++++++++++++++++++-------- test/segments_test/git_test.py | 4 +-- test/segments_test/hg_test.py | 49 +++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 14 deletions(-) create mode 100644 test/segments_test/hg_test.py diff --git a/powerline_shell/segments/hg.py b/powerline_shell/segments/hg.py index ca0e339..05cb2f7 100644 --- a/powerline_shell/segments/hg.py +++ b/powerline_shell/segments/hg.py @@ -1,6 +1,18 @@ import os import subprocess from ..utils import ThreadedSegment +import subprocess + + +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 _subprocess_env(): + return {"PATH": get_PATH()} def get_hg_status(): @@ -8,25 +20,45 @@ def get_hg_status(): has_untracked_files = False has_missing_files = False - p = subprocess.Popen(['hg', 'status'], stdout=subprocess.PIPE) + p = subprocess.Popen(["hg", "status"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=_subprocess_env()) output = p.communicate()[0].decode("utf-8") - for line in output.split('\n'): - if line == '': + for line in output.split("\n"): + if line == "": continue - elif line[0] == '?': + elif line[0] == "?": has_untracked_files = True - elif line[0] == '!': + elif line[0] == "!": has_missing_files = True else: has_modified_files = True return has_modified_files, has_untracked_files, has_missing_files +def build_stats(): + try: + p = subprocess.Popen(["hg", "branch"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=_subprocess_env()) + except OSError: + # Will be thrown if hg cannot be found + return None, None + + pdata = p.communicate() + if p.returncode != 0: + return None, None + + branch = pdata[0].decode("utf-8").strip() + return branch, get_hg_status() + + class Segment(ThreadedSegment): def run(self): - self.branch = os.popen('hg branch 2> /dev/null').read().rstrip() - self.status = get_hg_status() if self.branch else None + self.branch, self.status = build_stats() def add_to_powerline(self): self.join() @@ -38,10 +70,10 @@ class Segment(ThreadedSegment): if has_modified or has_untracked or has_missing: bg = self.powerline.theme.REPO_DIRTY_BG fg = self.powerline.theme.REPO_DIRTY_FG - extra = '' + extra = "" if has_untracked: - extra += '+' + extra += "+" if has_missing: - extra += '!' - self.branch += (' ' + extra if extra != '' else '') - return self.powerline.append(' %s ' % self.branch, fg, bg) + extra += "!" + self.branch += " " + extra + return self.powerline.append(" %s " % self.branch, fg, bg) diff --git a/test/segments_test/git_test.py b/test/segments_test/git_test.py index c2bcde5..ab8c9dd 100644 --- a/test/segments_test/git_test.py +++ b/test/segments_test/git_test.py @@ -25,7 +25,7 @@ class GitTest(unittest.TestCase): sh.git("add", filename) sh.git("commit", "-m", "add file " + filename) - def _new_branch(self, branch): + def _checkout_new_branch(self, branch): sh.git("checkout", "-b", branch) def _get_commit_hash(self): @@ -57,7 +57,7 @@ class GitTest(unittest.TestCase): def test_different_branch(self): self._add_and_commit("foo") - self._new_branch("bar") + self._checkout_new_branch("bar") self.segment.start() self.segment.add_to_powerline() self.assertEqual(self.powerline.append.call_args[0][0], ' bar ') diff --git a/test/segments_test/hg_test.py b/test/segments_test/hg_test.py new file mode 100644 index 0000000..7efb865 --- /dev/null +++ b/test/segments_test/hg_test.py @@ -0,0 +1,49 @@ +import unittest +import mock +import tempfile +import shutil +import sh +import powerline_shell.segments.hg as hg + + +class HgTest(unittest.TestCase): + + def setUp(self): + self.powerline = mock.MagicMock() + + self.dirname = tempfile.mkdtemp() + sh.cd(self.dirname) + sh.hg("init", ".") + + self.segment = hg.Segment(self.powerline) + + def tearDown(self): + shutil.rmtree(self.dirname) + + def _add_and_commit(self, filename): + sh.touch(filename) + sh.hg("add", filename) + sh.hg("commit", "-m", "add file " + filename) + + def _checkout_new_branch(self, branch): + sh.hg("branch", branch) + + @mock.patch("powerline_shell.segments.hg.get_PATH") + def test_hg_not_installed(self, get_PATH): + get_PATH.return_value = "" # so hg can"t be found + self.segment.start() + self.segment.add_to_powerline() + self.assertEqual(self.powerline.append.call_count, 0) + + def test_non_hg_directory(self): + shutil.rmtree(".hg") + self.segment.start() + self.segment.add_to_powerline() + self.assertEqual(self.powerline.append.call_count, 0) + + def test_standard(self): + self._add_and_commit("foo") + self.segment.start() + self.segment.add_to_powerline() + self.assertEqual(self.powerline.append.call_args[0][0], " default ") + From d23f3943502044e8088b68b30ef4a0602efc1d38 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Tue, 5 Sep 2017 21:47:03 -0400 Subject: [PATCH 55/93] file formatting for username segment --- powerline_shell/segments/username.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/powerline_shell/segments/username.py b/powerline_shell/segments/username.py index be97c52..b03dc09 100644 --- a/powerline_shell/segments/username.py +++ b/powerline_shell/segments/username.py @@ -1,18 +1,18 @@ from ..utils import BasicSegment +import os class Segment(BasicSegment): def add_to_powerline(self): powerline = self.powerline - import os - if powerline.args.shell == 'bash': - user_prompt = ' \\u ' - elif powerline.args.shell == 'zsh': - user_prompt = ' %n ' + if powerline.args.shell == "bash": + user_prompt = r" \u " + elif powerline.args.shell == "zsh": + user_prompt = " %n " else: - user_prompt = ' %s ' % os.getenv('USER') + user_prompt = " %s " % os.getenv("USER") - if os.getenv('USER') == 'root': + if os.getenv("USER") == "root": bgcolor = powerline.theme.USERNAME_ROOT_BG else: bgcolor = powerline.theme.USERNAME_BG From 3fb3764209d67d57b1d3fdaf911fd423646b9cdb Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Tue, 5 Sep 2017 21:47:51 -0400 Subject: [PATCH 56/93] change README to use normal pypi for installation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a20cea2..628ffb0 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ setting your $TERM to `xterm-256color`, because that works for me. - Install using pip: ``` -pip install --index-url https://test.pypi.org/simple/ powerline-shell +pip install powerline-shell ``` - Setup your shell prompt using the instructions for your shell below. From 44a7f2c3e0d85e556a95c9444958e33d4701e9b0 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sun, 10 Sep 2017 19:46:07 -0400 Subject: [PATCH 57/93] svn file formatting --- powerline_shell/segments/svn.py | 48 +++++++++++---------------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/powerline_shell/segments/svn.py b/powerline_shell/segments/svn.py index f2d9897..660ffe5 100644 --- a/powerline_shell/segments/svn.py +++ b/powerline_shell/segments/svn.py @@ -2,39 +2,21 @@ import subprocess from ..utils import BasicSegment -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, powerline.theme.SVN_CHANGES_FG, powerline.theme.SVN_CHANGES_BG) - - class Segment(BasicSegment): def add_to_powerline(self): - """Wraps _add_svn_segment in exception handling.""" powerline = self.powerline - - # 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 + 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 + p1 = subprocess.Popen(["svn", "status"], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + p2 = subprocess.Popen(["grep", "-c", r"^[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, + powerline.theme.SVN_CHANGES_FG, + powerline.theme.SVN_CHANGES_BG) From 5ab153b9da13dd22ea23c9d2cb404f464569f090 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sun, 10 Sep 2017 19:50:27 -0400 Subject: [PATCH 58/93] Release version 0.1.0 --- CHANGELOG.md | 8 ++++++++ setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69fdcf6..1f3b347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changes +2017-09-10 + +* Complete overhaul of the project + ([@b-ryan](https://github.com/banga/powerline-shell/pull/280)) + * There is now a PyPi package + * It's significantly faster now + * Configuration and installation is brand new. See README.md + 2017-06-21 * Add `rbenv` segment diff --git a/setup.py b/setup.py index 87e8bc7..23f0faa 100755 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup(name="powerline-shell", - version="0.1.0-alpha", + version="0.1.0", description="A pretty prompt for your shell", author="Buck Ryan", url="https://github.com/banga/powerline-shell", From 73c08b07cf08608297cd000bbe96b3466a15d05f Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sun, 10 Sep 2017 20:31:59 -0400 Subject: [PATCH 59/93] Use sys.version_info[0] instead of .major Fixes https://github.com/banga/powerline-shell/issues/247 --- powerline_shell/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerline_shell/utils.py b/powerline_shell/utils.py index 27d7986..7f97f6d 100644 --- a/powerline_shell/utils.py +++ b/powerline_shell/utils.py @@ -1,7 +1,7 @@ import sys import threading -py3 = sys.version_info.major == 3 +py3 = sys.version_info[0] == 3 if py3: def unicode(x): From 7ad2bc28a6b1099ea164f68574cb097b357c274a Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sun, 10 Sep 2017 21:01:38 -0400 Subject: [PATCH 60/93] Redo svn segment to use RepoStats Closes https://github.com/banga/powerline-shell/pull/105 --- powerline_shell/segments/svn.py | 31 +++++++++++++++++++------------ test/segments_test/hg_test.py | 1 - 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/powerline_shell/segments/svn.py b/powerline_shell/segments/svn.py index 660ffe5..27e21d6 100644 --- a/powerline_shell/segments/svn.py +++ b/powerline_shell/segments/svn.py @@ -1,22 +1,29 @@ import subprocess -from ..utils import BasicSegment +from ..utils import BasicSegment, RepoStats class Segment(BasicSegment): def add_to_powerline(self): - powerline = self.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 - p1 = subprocess.Popen(["svn", "status"], stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - p2 = subprocess.Popen(["grep", "-c", r"^[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, - powerline.theme.SVN_CHANGES_FG, - powerline.theme.SVN_CHANGES_BG) + + try: + p1 = subprocess.Popen(["svn", "status"], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + except OSError: + return + + stdout = p1.communicate()[0] + stats = RepoStats() + for line in stdout.splitlines(): + if line[0] == "?": + stats.untracked += 1 + elif line[0] == "C": + stats.conflicted += 1 + elif line[0] in ["A", "D", "I", "M", "R", "!", "~"]: + stats.not_staged += 1 + + stats.add_to_powerline(self.powerline) diff --git a/test/segments_test/hg_test.py b/test/segments_test/hg_test.py index 7efb865..be8d147 100644 --- a/test/segments_test/hg_test.py +++ b/test/segments_test/hg_test.py @@ -46,4 +46,3 @@ class HgTest(unittest.TestCase): self.segment.start() self.segment.add_to_powerline() self.assertEqual(self.powerline.append.call_args[0][0], " default ") - From 4b328acf02de963f1f7ebf9f50a1149eae5ae7f6 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sun, 10 Sep 2017 21:04:26 -0400 Subject: [PATCH 61/93] unreleased changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f3b347..f9bcc7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changes +Unreleased + +* Rewrite SVN segment to be consistent with git +* Remove duplicate function in colortrans.py + ([@jmtd](https://github.com/banga/powerline-shell/pull/273)) +* Make python 3 check compatible with older Python versions + 2017-09-10 * Complete overhaul of the project From 9e49b7db2408374d550e399c95689786fdba9d4b Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sun, 10 Sep 2017 21:17:36 -0400 Subject: [PATCH 62/93] Add solarized_light theme Closes #95 Closes #142 Closes #143 --- CHANGELOG.md | 2 ++ powerline_shell/themes/solarized_light.py | 38 +++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 powerline_shell/themes/solarized_light.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f9bcc7f..e977b6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ Unreleased * Remove duplicate function in colortrans.py ([@jmtd](https://github.com/banga/powerline-shell/pull/273)) * Make python 3 check compatible with older Python versions +* New theme! `solarized_light` + ([@ruturajv](https://github.com/banga/powerline-shell/pull/143) 2017-09-10 diff --git a/powerline_shell/themes/solarized_light.py b/powerline_shell/themes/solarized_light.py new file mode 100644 index 0000000..da14cdf --- /dev/null +++ b/powerline_shell/themes/solarized_light.py @@ -0,0 +1,38 @@ +from .default import DefaultColor + + +class Color(DefaultColor): + USERNAME_FG = 15 + USERNAME_BG = 4 + USERNAME_ROOT_BG = 1 + + HOSTNAME_FG = 15 + HOSTNAME_BG = 10 + + HOME_SPECIAL_DISPLAY = False + PATH_FG = 10 + PATH_BG = 7 + CWD_FG = 0 + SEPARATOR_FG = 14 + + READONLY_BG = 1 + READONLY_FG = 7 + + REPO_CLEAN_FG = 0 + REPO_CLEAN_BG = 15 + REPO_DIRTY_FG = 1 + REPO_DIRTY_BG = 15 + + JOBS_FG = 4 + JOBS_BG = 7 + + CMD_PASSED_FG = 15 + CMD_PASSED_BG = 2 + CMD_FAILED_FG = 15 + CMD_FAILED_BG = 1 + + SVN_CHANGES_FG = REPO_DIRTY_FG + SVN_CHANGES_BG = REPO_DIRTY_BG + + VIRTUAL_ENV_BG = 15 + VIRTUAL_ENV_FG = 2 From c334d1231d3cb2485aa7a73d06b6fe58ff8671f8 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sun, 10 Sep 2017 21:45:17 -0400 Subject: [PATCH 63/93] Escape subshell commands --- powerline_shell/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/powerline_shell/__init__.py b/powerline_shell/__init__.py index ac6fdc2..2316a0f 100644 --- a/powerline_shell/__init__.py +++ b/powerline_shell/__init__.py @@ -7,6 +7,7 @@ import sys import importlib import json from .utils import warn, py3 +import re def get_valid_cwd(): @@ -100,7 +101,8 @@ class Powerline(object): return self.color('48', code) def append(self, content, fg, bg, separator=None, separator_fg=None): - self.segments.append((content, fg, bg, + sanitized = re.sub(r"([`$])", r"\\\1", content) + self.segments.append((sanitized, fg, bg, separator if separator is not None else self.separator, separator_fg if separator_fg is not None else bg)) From f12f94c6185b3a83158fa67799d0f2fc59f556c5 Mon Sep 17 00:00:00 2001 From: Emanuel Angelo Date: Tue, 12 Sep 2017 02:35:53 +0100 Subject: [PATCH 64/93] Add Bazaar segment --- powerline_shell/segments/bzr.py | 74 +++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 powerline_shell/segments/bzr.py diff --git a/powerline_shell/segments/bzr.py b/powerline_shell/segments/bzr.py new file mode 100644 index 0000000..0a2c47f --- /dev/null +++ b/powerline_shell/segments/bzr.py @@ -0,0 +1,74 @@ +import os +import subprocess +from ..utils import ThreadedSegment + + +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 _subprocess_env(): + return {"PATH": get_PATH()} + + +def get_bzr_status(): + has_modified_files = False + has_untracked_files = False + has_missing_files = False + p = subprocess.Popen(['bzr', 'status'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=_subprocess_env()) + output = p.communicate()[0].decode("utf-8") + if 'unknown:\n' in output: + has_untracked_files = True + elif 'removed:\n' in output: + has_missing_files = True + elif 'modified:\n' in output: + has_modified_files = True + return has_modified_files, has_untracked_files, has_missing_files + + +def build_stats(): + try: + p = subprocess.Popen(["bzr", "nick"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=_subprocess_env()) + except OSError: + # Will be thrown if bzr cannot be found + return None, None + + pdata = p.communicate() + if p.returncode != 0: + return None, None + + branch = pdata[0].decode("utf-8").strip() + return branch, get_bzr_status() + + +class Segment(ThreadedSegment): + def run(self): + self.branch, self.status = build_stats() + + def add_to_powerline(self): + self.join() + if not self.branch or not self.status: + return + bg = self.powerline.theme.REPO_CLEAN_BG + fg = self.powerline.theme.REPO_CLEAN_FG + has_modified, has_untracked, has_missing = self.status + if has_modified or has_untracked or has_missing: + bg = self.powerline.theme.REPO_DIRTY_BG + fg = self.powerline.theme.REPO_DIRTY_FG + extra = "" + if has_untracked: + extra += "+" + if has_missing: + extra += "!" + self.branch += " " + extra + return self.powerline.append(" %s " % self.branch, fg, bg) + From 98e03022af568d68405d3c5f24a7751785c0c537 Mon Sep 17 00:00:00 2001 From: Emanuel Angelo Date: Tue, 12 Sep 2017 22:32:31 +0100 Subject: [PATCH 65/93] Reimplements bzr segment with RepoStats class --- powerline_shell/segments/bzr.py | 80 ++++++++++++++++----------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/powerline_shell/segments/bzr.py b/powerline_shell/segments/bzr.py index 0a2c47f..456b6e3 100644 --- a/powerline_shell/segments/bzr.py +++ b/powerline_shell/segments/bzr.py @@ -1,6 +1,6 @@ import os import subprocess -from ..utils import ThreadedSegment +from ..utils import RepoStats, ThreadedSegment def get_PATH(): @@ -10,65 +10,65 @@ def get_PATH(): return os.getenv("PATH") -def _subprocess_env(): +def bzr_subprocess_env(): return {"PATH": get_PATH()} -def get_bzr_status(): - has_modified_files = False - has_untracked_files = False - has_missing_files = False - p = subprocess.Popen(['bzr', 'status'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=_subprocess_env()) - output = p.communicate()[0].decode("utf-8") - if 'unknown:\n' in output: - has_untracked_files = True - elif 'removed:\n' in output: - has_missing_files = True - elif 'modified:\n' in output: - has_modified_files = True - return has_modified_files, has_untracked_files, has_missing_files +def _get_bzr_branch(): + p = subprocess.Popen(['bzr', 'nick'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env=bzr_subprocess_env()) + branch = p.communicate()[0].decode("utf-8").rstrip('\n') + return branch + + +def parse_bzr_stats(status): + stats = RepoStats() + statustype = "not_staged" + for statusline in status: + if statusline[:2] == " ": + setattr(stats, statustype, getattr(stats, statustype) + 1) + elif statusline == "added:": + statustype = "staged" + elif statusline in ("removed:", "missing:"): + statustype = "conflicted" + elif statusline == "unknown:": + statustype = "untracked" + else: # renamed, modified or kind changed + statustype = "not_staged" + return stats def build_stats(): try: - p = subprocess.Popen(["bzr", "nick"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=_subprocess_env()) + p = subprocess.Popen(['bzr', 'status'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env=bzr_subprocess_env()) except OSError: - # Will be thrown if bzr cannot be found - return None, None - + # Popen will throw an OSError if bzr is not found + return (None, None) pdata = p.communicate() if p.returncode != 0: - return None, None - - branch = pdata[0].decode("utf-8").strip() - return branch, get_bzr_status() + return (None, None) + status = pdata[0].decode("utf-8").splitlines() + stats = parse_bzr_stats(status) + branch = _get_bzr_branch() + return stats, branch class Segment(ThreadedSegment): def run(self): - self.branch, self.status = build_stats() + self.stats, self.branch = build_stats() def add_to_powerline(self): self.join() - if not self.branch or not self.status: + if not self.stats: return bg = self.powerline.theme.REPO_CLEAN_BG fg = self.powerline.theme.REPO_CLEAN_FG - has_modified, has_untracked, has_missing = self.status - if has_modified or has_untracked or has_missing: + if self.stats.dirty: bg = self.powerline.theme.REPO_DIRTY_BG fg = self.powerline.theme.REPO_DIRTY_FG - extra = "" - if has_untracked: - extra += "+" - if has_missing: - extra += "!" - self.branch += " " + extra - return self.powerline.append(" %s " % self.branch, fg, bg) + self.powerline.append(" " + self.branch + " ", fg, bg) + self.stats.add_to_powerline(self.powerline) From 2168f5eb890b33c2fb660eb03c4aa594aaa69144 Mon Sep 17 00:00:00 2001 From: Emanuel Angelo Date: Wed, 13 Sep 2017 13:16:48 +0100 Subject: [PATCH 66/93] Better association between bzr status and RepoStats qualifiers --- powerline_shell/segments/bzr.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/powerline_shell/segments/bzr.py b/powerline_shell/segments/bzr.py index 456b6e3..e5c14df 100644 --- a/powerline_shell/segments/bzr.py +++ b/powerline_shell/segments/bzr.py @@ -30,11 +30,9 @@ def parse_bzr_stats(status): setattr(stats, statustype, getattr(stats, statustype) + 1) elif statusline == "added:": statustype = "staged" - elif statusline in ("removed:", "missing:"): - statustype = "conflicted" elif statusline == "unknown:": statustype = "untracked" - else: # renamed, modified or kind changed + else: # removed, missing, renamed, modified or kind changed statustype = "not_staged" return stats From bec4e1f649f96209454b64f1315c8a759b497396 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Wed, 13 Sep 2017 10:32:46 -0400 Subject: [PATCH 67/93] changelog for #283 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e977b6a..a63c53d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ Unreleased +* Add Bazaar segment + ([@emansije](https://github.com/banga/powerline-shell/pull/283)) * Rewrite SVN segment to be consistent with git * Remove duplicate function in colortrans.py ([@jmtd](https://github.com/banga/powerline-shell/pull/273)) From 2c0a5909ce8497813e02523b1cda2d3b9c836706 Mon Sep 17 00:00:00 2001 From: Emanuel Angelo Date: Thu, 14 Sep 2017 00:25:24 +0100 Subject: [PATCH 68/93] Renames some RepoStats class qualifiers to more generic terms untracked -> new not_staged -> changed --- powerline_shell/segments/bzr.py | 6 +++--- powerline_shell/segments/git.py | 4 ++-- powerline_shell/segments/svn.py | 4 ++-- powerline_shell/utils.py | 24 ++++++++++++------------ 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/powerline_shell/segments/bzr.py b/powerline_shell/segments/bzr.py index e5c14df..c90dfa8 100644 --- a/powerline_shell/segments/bzr.py +++ b/powerline_shell/segments/bzr.py @@ -24,16 +24,16 @@ def _get_bzr_branch(): def parse_bzr_stats(status): stats = RepoStats() - statustype = "not_staged" + statustype = "changed" for statusline in status: if statusline[:2] == " ": setattr(stats, statustype, getattr(stats, statustype) + 1) elif statusline == "added:": statustype = "staged" elif statusline == "unknown:": - statustype = "untracked" + statustype = "new" else: # removed, missing, renamed, modified or kind changed - statustype = "not_staged" + statustype = "changed" return stats diff --git a/powerline_shell/segments/git.py b/powerline_shell/segments/git.py index 95e2c2a..72ef7a1 100644 --- a/powerline_shell/segments/git.py +++ b/powerline_shell/segments/git.py @@ -47,12 +47,12 @@ def parse_git_stats(status): for statusline in status[1:]: code = statusline[:2] if code == '??': - stats.untracked += 1 + stats.new += 1 elif code in ('DD', 'AU', 'UD', 'UA', 'DU', 'AA', 'UU'): stats.conflicted += 1 else: if code[1] != ' ': - stats.not_staged += 1 + stats.changed += 1 if code[0] != ' ': stats.staged += 1 diff --git a/powerline_shell/segments/svn.py b/powerline_shell/segments/svn.py index 27e21d6..995d04d 100644 --- a/powerline_shell/segments/svn.py +++ b/powerline_shell/segments/svn.py @@ -20,10 +20,10 @@ class Segment(BasicSegment): stats = RepoStats() for line in stdout.splitlines(): if line[0] == "?": - stats.untracked += 1 + stats.new += 1 elif line[0] == "C": stats.conflicted += 1 elif line[0] in ["A", "D", "I", "M", "R", "!", "~"]: - stats.not_staged += 1 + stats.changed += 1 stats.add_to_powerline(self.powerline) diff --git a/powerline_shell/utils.py b/powerline_shell/utils.py index 7f97f6d..135c819 100644 --- a/powerline_shell/utils.py +++ b/powerline_shell/utils.py @@ -14,24 +14,24 @@ class RepoStats(object): 'ahead': u'\u2B06', 'behind': u'\u2B07', 'staged': u'\u2714', - 'not_staged': u'\u270E', - 'untracked': u'\u2753', + 'changed': u'\u270E', + 'new': u'\u2753', 'conflicted': u'\u273C' } def __init__(self): self.ahead = 0 self.behind = 0 - self.untracked = 0 - self.not_staged = 0 + self.new = 0 + self.changed = 0 self.staged = 0 self.conflicted = 0 @property def dirty(self): qualifiers = [ - self.untracked, - self.not_staged, + self.new, + self.changed, self.staged, self.conflicted, ] @@ -45,11 +45,11 @@ class RepoStats(object): 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: + As an example, if you want to show an icon for new files, but you only + want a number to appear next to the icon when there are more than one + new file, you can do: - segment = repo_stats.n_or_empty("untracked") + icon_string + segment = repo_stats.n_or_empty("new") + icon_string """ return unicode(self[_key]) if int(self[_key]) > 1 else u'' @@ -62,8 +62,8 @@ class RepoStats(object): 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('changed', color.GIT_NOTSTAGED_FG, color.GIT_NOTSTAGED_BG) + add('new', color.GIT_UNTRACKED_FG, color.GIT_UNTRACKED_BG) add('conflicted', color.GIT_CONFLICTED_FG, color.GIT_CONFLICTED_BG) From 30c5bdad4695a8fb02167355a9d4588b9208b81e Mon Sep 17 00:00:00 2001 From: Emanuel Angelo Date: Thu, 14 Sep 2017 01:13:27 +0100 Subject: [PATCH 69/93] Changes tests accordingly --- test/repo_stats_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/repo_stats_test.py b/test/repo_stats_test.py index fe4f10d..123259b 100644 --- a/test/repo_stats_test.py +++ b/test/repo_stats_test.py @@ -6,20 +6,20 @@ class RepoStatsTest(unittest.TestCase): def setUp(self): self.repo_stats = RepoStats() - self.repo_stats.not_staged = 1 + self.repo_stats.changed = 1 self.repo_stats.conflicted = 4 def test_dirty(self): self.assertTrue(self.repo_stats.dirty) def test_simple(self): - self.assertEqual(self.repo_stats.untracked, 0) + self.assertEqual(self.repo_stats.new, 0) def test_n_or_empty__empty(self): - self.assertEqual(self.repo_stats.n_or_empty("not_staged"), u"") + self.assertEqual(self.repo_stats.n_or_empty("changed"), u"") def test_n_or_empty__n(self): self.assertEqual(self.repo_stats.n_or_empty("conflicted"), u"4") def test_index(self): - self.assertEqual(self.repo_stats["not_staged"], 1) + self.assertEqual(self.repo_stats["changed"], 1) From 66e64ba3083a6e1fb2b39e196d403bc6e55578b1 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Wed, 13 Sep 2017 20:19:12 -0400 Subject: [PATCH 70/93] changelog for #284 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a63c53d..a7f1051 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Unreleased * Add Bazaar segment ([@emansije](https://github.com/banga/powerline-shell/pull/283)) + * And rename properties of RepoStats for clarity + ([@emansije](https://github.com/banga/powerline-shell/pull/284)) * Rewrite SVN segment to be consistent with git * Remove duplicate function in colortrans.py ([@jmtd](https://github.com/banga/powerline-shell/pull/273)) From 2f5350e1eb395d8d80b9ba8d278a5d662c68c61a Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Wed, 13 Sep 2017 20:20:16 -0400 Subject: [PATCH 71/93] upgrade version to 0.2.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 23f0faa..5da09d3 100755 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup(name="powerline-shell", - version="0.1.0", + version="0.2.0", description="A pretty prompt for your shell", author="Buck Ryan", url="https://github.com/banga/powerline-shell", From f9d3f1295217d65a6bb5a160c3fc302beb09d7a8 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Wed, 13 Sep 2017 20:21:07 -0400 Subject: [PATCH 72/93] get ready for 0.2.0 --- CHANGELOG.md | 2 +- README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7f1051..0fa637f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changes -Unreleased +2017-09-13 (version 0.2.0) * Add Bazaar segment ([@emansije](https://github.com/banga/powerline-shell/pull/283)) diff --git a/README.md b/README.md index 628ffb0..c2c5b96 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,7 @@ Once you have generated your config file, you can now start adding or removing "segments" - the building blocks of your shell. The list of segments available are: +- `bzr` - Details about the current Bazaar repo. - `cwd` - Shows your current working directory. See [Segment Configuration](#segment-configuration) for some options. - `exit_code` - When the previous command ends in a non-zero status, shows the From 166ae54ee2dc2e9fa84896a1fbbcb75468166f04 Mon Sep 17 00:00:00 2001 From: Emanuel Angelo Date: Thu, 14 Sep 2017 23:51:30 +0100 Subject: [PATCH 73/93] Redo fossil segment to use RepoStats This implementation does not work well in older versions of fossil, like 1.37, apparently due to a bug in that version of fossil that throws an error with `fossil changes --differ`. --- powerline_shell/segments/fossil.py | 90 ++++++++++++++++++------------ 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/powerline_shell/segments/fossil.py b/powerline_shell/segments/fossil.py index cb1a26b..3bdb724 100644 --- a/powerline_shell/segments/fossil.py +++ b/powerline_shell/segments/fossil.py @@ -1,53 +1,73 @@ import os import subprocess -from ..utils import ThreadedSegment +from ..utils import RepoStats, ThreadedSegment -def get_fossil_branch(): - try: - subprocess.Popen(['fossil'], stdout=subprocess.PIPE).communicate() - except OSError: - return None +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 fossil_subprocess_env(): + return {"PATH": get_PATH()} + + +def _get_fossil_branch(): + branches = os.popen("fossil branch 2>/dev/null").read().strip().split("\n") return ''.join([ i.replace('*','').strip() - for i in os.popen("fossil branch 2> /dev/null").read().strip().split("\n") + for i in branches if i.startswith('*') ]) -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 = bool( - os.popen("fossil extras 2>/dev/null").read().strip() - ) - has_missing_files = 'MISSING' in output - has_modified_files = 'EDITED' in output - return has_modified_files, has_untracked_files, has_missing_files +def parse_fossil_stats(status): + stats = RepoStats() + for filestatus in [line.split()[0] for line in status.strip().split("\n")]: + if filestatus == "ADDED": + stats.staged += 1 + elif filestatus == "EXTRA": + stats.new += 1 + elif filestatus == "CONFLICT": + stats.conflicted += 1 + else: + stats.changed += 1 + return stats + + +def build_stats(): + try: + subprocess.Popen(['fossil'], stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=fossil_subprocess_env()).communicate() + except OSError: + # Popen will throw an OSError if fossil is not found + return (None, None) + branch = _get_fossil_branch() + if branch == "": + return (None, None) + status = os.popen("fossil changes --differ 2>/dev/null").read().strip() + if status == "": + return (RepoStats(), branch) + stats = parse_fossil_stats(status) + return stats, branch class Segment(ThreadedSegment): def run(self): - self.branch = get_fossil_branch() - self.status = get_fossil_status() if self.branch else None + self.stats, self.branch = build_stats() def add_to_powerline(self): self.join() - powerline = self.powerline - if not self.branch or not self.status: + if not self.stats: return - has_modified, has_untracked, has_missing = self.status - bg = powerline.theme.REPO_CLEAN_BG - fg = powerline.theme.REPO_CLEAN_FG - if has_modified or has_untracked or has_missing: - bg = powerline.theme.REPO_DIRTY_BG - fg = powerline.theme.REPO_DIRTY_FG - extra = '' - if has_untracked: - extra += '+' - if has_missing: - extra += '!' - self.branch += (' ' + extra if extra != '' else '') - powerline.append(' %s ' % self.branch, fg, bg) + bg = self.powerline.theme.REPO_CLEAN_BG + fg = self.powerline.theme.REPO_CLEAN_FG + if self.stats.dirty: + bg = self.powerline.theme.REPO_DIRTY_BG + fg = self.powerline.theme.REPO_DIRTY_FG + + self.powerline.append(" " + self.branch + " ", fg, bg) + self.stats.add_to_powerline(self.powerline) From e09c53da58285761423e03321e5ddb82b2c2576c Mon Sep 17 00:00:00 2001 From: Emanuel Angelo Date: Fri, 15 Sep 2017 00:31:30 +0100 Subject: [PATCH 74/93] Hack around fossil bug For older versions of fossil that throw an error on `fossil changes --differ`, like version 1.37. --- powerline_shell/segments/fossil.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/powerline_shell/segments/fossil.py b/powerline_shell/segments/fossil.py index 3bdb724..b2814e6 100644 --- a/powerline_shell/segments/fossil.py +++ b/powerline_shell/segments/fossil.py @@ -25,7 +25,7 @@ def _get_fossil_branch(): def parse_fossil_stats(status): stats = RepoStats() - for filestatus in [line.split()[0] for line in status.strip().split("\n")]: + for filestatus in [line.split()[0] for line in status]: if filestatus == "ADDED": stats.staged += 1 elif filestatus == "EXTRA": @@ -48,8 +48,10 @@ def build_stats(): branch = _get_fossil_branch() if branch == "": return (None, None) - status = os.popen("fossil changes --differ 2>/dev/null").read().strip() - if status == "": + status = os.popen("fossil changes 2>/dev/null").read().strip().split("\n") + extra = os.popen("fossil extras 2>/dev/null").read().strip().split("\n") + status += ["EXTRA " + filename for filename in extra if filename != ""] + if status == ['']: return (RepoStats(), branch) stats = parse_fossil_stats(status) return stats, branch From 063d3923df73d297c81f6351178ded140fa7f0ac Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 16 Sep 2017 10:02:11 -0400 Subject: [PATCH 75/93] fix two issues with fish shell --- powerline_shell/__init__.py | 3 +-- powerline_shell/segments/time.py | 9 +++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/powerline_shell/__init__.py b/powerline_shell/__init__.py index ac6fdc2..e5386cf 100644 --- a/powerline_shell/__init__.py +++ b/powerline_shell/__init__.py @@ -73,8 +73,7 @@ class Powerline(object): self.theme = theme self.cwd = get_valid_cwd() mode = config.get("mode", "patched") - shell = config.get("shell", "bash") - self.color_template = self.color_templates[shell] + self.color_template = self.color_templates[args.shell] self.reset = self.color_template % '[0m' self.lock = Powerline.symbols[mode]['lock'] self.network = Powerline.symbols[mode]['network'] diff --git a/powerline_shell/segments/time.py b/powerline_shell/segments/time.py index 413abb6..ab4c911 100644 --- a/powerline_shell/segments/time.py +++ b/powerline_shell/segments/time.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from ..utils import BasicSegment import time @@ -6,11 +7,11 @@ class Segment(BasicSegment): def add_to_powerline(self): powerline = self.powerline if powerline.args.shell == 'bash': - time = ' \\t ' + time_ = ' \\t ' elif powerline.args.shell == 'zsh': - time = ' %* ' + time_ = ' %* ' else: - time = ' %s ' % time.strftime('%H:%M:%S') - powerline.append(time, + time_ = ' %s ' % time.strftime('%H:%M:%S') + powerline.append(time_, powerline.theme.HOSTNAME_FG, powerline.theme.HOSTNAME_BG) From d2fd79690eec323d8a77f79fc69b728f4d331466 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 16 Sep 2017 10:03:11 -0400 Subject: [PATCH 76/93] prepare for 0.2.1 --- CHANGELOG.md | 4 ++++ setup.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fa637f..87574bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes +2017-09-16 (version 0.2.1) + +* Fix issues preventing fish shell from rendering. + 2017-09-13 (version 0.2.0) * Add Bazaar segment diff --git a/setup.py b/setup.py index 5da09d3..bbf527a 100755 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup(name="powerline-shell", - version="0.2.0", + version="0.2.1", description="A pretty prompt for your shell", author="Buck Ryan", url="https://github.com/banga/powerline-shell", From 655a12a278bb5590b64d531ad3964e58666c903a Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Mon, 18 Sep 2017 09:13:10 -0400 Subject: [PATCH 77/93] Fix py3 issues in uptime and in unicode function --- powerline_shell/segments/uptime.py | 2 +- powerline_shell/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/powerline_shell/segments/uptime.py b/powerline_shell/segments/uptime.py index b282100..96e4c26 100644 --- a/powerline_shell/segments/uptime.py +++ b/powerline_shell/segments/uptime.py @@ -7,7 +7,7 @@ class Segment(BasicSegment): def add_to_powerline(self): powerline = self.powerline try: - output = subprocess.check_output(['uptime'], stderr=subprocess.STDOUT) + output = subprocess.check_output(['uptime'], stderr=subprocess.STDOUT).decode("utf-8") raw_uptime = re.search('(?<=up).+(?=,\s+\d+\s+user)', output).group(0) day_search = re.search('\d+(?=\s+day)', output) days = '' if not day_search else '%sd ' % day_search.group(0) diff --git a/powerline_shell/utils.py b/powerline_shell/utils.py index 135c819..e3a9a2f 100644 --- a/powerline_shell/utils.py +++ b/powerline_shell/utils.py @@ -5,7 +5,7 @@ py3 = sys.version_info[0] == 3 if py3: def unicode(x): - return x + return str(x) class RepoStats(object): From 3e025e29470cec572cf0c9d2da4a7cda3280f564 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Mon, 18 Sep 2017 09:14:54 -0400 Subject: [PATCH 78/93] prepare 0.2.2 --- CHANGELOG.md | 5 +++++ setup.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87574bd..b8f9816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changes +2017-09-18 (version 0.2.2) + +* Fix python3 issue in uptime segment. Fixes + [#291](https://github.com/banga/powerline-shell/issues/291). + 2017-09-16 (version 0.2.1) * Fix issues preventing fish shell from rendering. diff --git a/setup.py b/setup.py index bbf527a..ffa46e3 100755 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup(name="powerline-shell", - version="0.2.1", + version="0.2.2", description="A pretty prompt for your shell", author="Buck Ryan", url="https://github.com/banga/powerline-shell", From 7cd14c3267a20a380da861b5bc94464dd4d953c6 Mon Sep 17 00:00:00 2001 From: Emanuel Angelo Date: Tue, 19 Sep 2017 11:57:00 +0100 Subject: [PATCH 79/93] Fix bug when there are extra files and no tracked changes If the fossil repository had untracked files and no modifications in the tracked files, the parsing would fail because the status list would begin with an unexpected blank element. --- powerline_shell/segments/fossil.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/powerline_shell/segments/fossil.py b/powerline_shell/segments/fossil.py index b2814e6..52e5257 100644 --- a/powerline_shell/segments/fossil.py +++ b/powerline_shell/segments/fossil.py @@ -25,12 +25,12 @@ def _get_fossil_branch(): def parse_fossil_stats(status): stats = RepoStats() - for filestatus in [line.split()[0] for line in status]: - if filestatus == "ADDED": + for line in status: + if line.startswith("ADDED"): stats.staged += 1 - elif filestatus == "EXTRA": + elif line.startswith("EXTRA"): stats.new += 1 - elif filestatus == "CONFLICT": + elif line.startswith("CONFLICT"): stats.conflicted += 1 else: stats.changed += 1 @@ -48,11 +48,12 @@ def build_stats(): branch = _get_fossil_branch() if branch == "": return (None, None) - status = os.popen("fossil changes 2>/dev/null").read().strip().split("\n") + changes = os.popen("fossil changes 2>/dev/null").read().strip().split("\n") extra = os.popen("fossil extras 2>/dev/null").read().strip().split("\n") - status += ["EXTRA " + filename for filename in extra if filename != ""] - if status == ['']: + extra = ["EXTRA " + filename for filename in extra] + if changes == extra == ['']: return (RepoStats(), branch) + status = [line for line in changes + extra if line != ''] stats = parse_fossil_stats(status) return stats, branch From 40882fbacf56d1fa7a3dbd6c61ff5ffc51c140e6 Mon Sep 17 00:00:00 2001 From: Emanuel Angelo Date: Tue, 19 Sep 2017 12:15:27 +0100 Subject: [PATCH 80/93] Fixes bug that calculated an extra untracked file in the repository --- powerline_shell/segments/fossil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerline_shell/segments/fossil.py b/powerline_shell/segments/fossil.py index 52e5257..8fbbb9d 100644 --- a/powerline_shell/segments/fossil.py +++ b/powerline_shell/segments/fossil.py @@ -50,7 +50,7 @@ def build_stats(): return (None, None) changes = os.popen("fossil changes 2>/dev/null").read().strip().split("\n") extra = os.popen("fossil extras 2>/dev/null").read().strip().split("\n") - extra = ["EXTRA " + filename for filename in extra] + extra = ["EXTRA " + filename for filename in extra if filename != ""] if changes == extra == ['']: return (RepoStats(), branch) status = [line for line in changes + extra if line != ''] From 0601b47b41b8b88513b1dc5a47b96aeebebe1466 Mon Sep 17 00:00:00 2001 From: Emanuel Angelo Date: Tue, 26 Sep 2017 02:33:02 +0100 Subject: [PATCH 81/93] Fixes bug in `test/repostats_test.py` --- test/repo_stats_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/repo_stats_test.py b/test/repo_stats_test.py index 123259b..300d00b 100644 --- a/test/repo_stats_test.py +++ b/test/repo_stats_test.py @@ -19,7 +19,7 @@ class RepoStatsTest(unittest.TestCase): self.assertEqual(self.repo_stats.n_or_empty("changed"), u"") def test_n_or_empty__n(self): - self.assertEqual(self.repo_stats.n_or_empty("conflicted"), u"4") + self.assertEqual(self.repo_stats.n_or_empty("conflicted"), 4) def test_index(self): self.assertEqual(self.repo_stats["changed"], 1) From b6c714253d3fc8767969503343f7d3c8b032eb2b Mon Sep 17 00:00:00 2001 From: Emanuel Angelo Date: Tue, 26 Sep 2017 02:42:00 +0100 Subject: [PATCH 82/93] Adds test for fossil segment It implies going from a threaded segment to a basic one, otherwise it throws a RuntimeError: threads can only be started once --- powerline_shell/segments/fossil.py | 23 +++++----- test/segments_test/fossil_test.py | 68 ++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 test/segments_test/fossil_test.py diff --git a/powerline_shell/segments/fossil.py b/powerline_shell/segments/fossil.py index 8fbbb9d..bfe38cd 100644 --- a/powerline_shell/segments/fossil.py +++ b/powerline_shell/segments/fossil.py @@ -1,6 +1,6 @@ import os import subprocess -from ..utils import RepoStats, ThreadedSegment +from ..utils import RepoStats, BasicSegment def get_PATH(): @@ -37,6 +37,14 @@ def parse_fossil_stats(status): return stats +def _get_fossil_status(): + changes = os.popen("fossil changes 2>/dev/null").read().strip().split("\n") + extra = os.popen("fossil extras 2>/dev/null").read().strip().split("\n") + extra = ["EXTRA " + filename for filename in extra if filename != ""] + status = [line for line in changes + extra if line != ''] + return status + + def build_stats(): try: subprocess.Popen(['fossil'], stdout=subprocess.PIPE, @@ -48,22 +56,17 @@ def build_stats(): branch = _get_fossil_branch() if branch == "": return (None, None) - changes = os.popen("fossil changes 2>/dev/null").read().strip().split("\n") - extra = os.popen("fossil extras 2>/dev/null").read().strip().split("\n") - extra = ["EXTRA " + filename for filename in extra if filename != ""] - if changes == extra == ['']: + status = _get_fossil_status() + if status == []: return (RepoStats(), branch) - status = [line for line in changes + extra if line != ''] stats = parse_fossil_stats(status) return stats, branch -class Segment(ThreadedSegment): - def run(self): - self.stats, self.branch = build_stats() +class Segment(BasicSegment): def add_to_powerline(self): - self.join() + self.stats, self.branch = build_stats() if not self.stats: return bg = self.powerline.theme.REPO_CLEAN_BG diff --git a/test/segments_test/fossil_test.py b/test/segments_test/fossil_test.py new file mode 100644 index 0000000..3415873 --- /dev/null +++ b/test/segments_test/fossil_test.py @@ -0,0 +1,68 @@ +import unittest +import mock +import tempfile +import shutil +import sh +import powerline_shell.segments.fossil as fossil +from powerline_shell.utils import RepoStats + + +rs = RepoStats() +test_cases = { + "EXTRA new-file": rs.symbols["new"], + "EDITED modified-file": rs.symbols["changed"], + "CONFLICT conflicted-file": rs.symbols["conflicted"], + "ADDED added-file": rs.symbols["staged"], +} + + +class FossilTest(unittest.TestCase): + + def setUp(self): + self.powerline = mock.MagicMock() + + self.dirname = tempfile.mkdtemp() + sh.cd(self.dirname) + sh.fossil("init", "test.fossil") + sh.fossil("open", "test.fossil") + + self.segment = fossil.Segment(self.powerline) + + def tearDown(self): + shutil.rmtree(self.dirname) + + def _add_and_commit(self, filename): + sh.touch(filename) + sh.fossil("add", filename) + sh.fossil("commit", "-m", "add file " + filename) + + def _checkout_new_branch(self, branch): + sh.fossil("branch", "new", branch, "trunk") + + @mock.patch("powerline_shell.segments.fossil.get_PATH") + def test_fossil_not_installed(self, get_PATH): + get_PATH.return_value = "" # so fossil can't be found + self.segment.start() + self.segment.add_to_powerline() + self.assertEqual(self.powerline.append.call_count, 0) + + def test_non_fossil_directory(self): + sh.fossil("close", "--force") + self.segment.start() + self.segment.add_to_powerline() + self.assertEqual(self.powerline.append.call_count, 0) + + def test_standard(self): + self._add_and_commit("foo") + self.segment.start() + self.segment.add_to_powerline() + self.assertEqual(self.powerline.append.call_args[0][0], " trunk ") + + @mock.patch('powerline_shell.segments.fossil._get_fossil_status') + def test_all(self, check_output): + for stdout, result in test_cases.items(): + check_output.return_value = [stdout] + self.segment.start() + self.segment.add_to_powerline() + self.assertEqual(self.powerline.append.call_args[0][0].split()[0], + result) From ebb7187f04f31f522d611830217a340dbfa9453b Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Tue, 26 Sep 2017 11:06:44 -0400 Subject: [PATCH 83/93] Update parsing tests --- powerline_shell/segments/fossil.py | 4 ++-- powerline_shell/utils.py | 24 +++++++++++++++++------- test/repo_stats_test.py | 2 +- test/segments_test/fossil_test.py | 17 ++++++----------- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/powerline_shell/segments/fossil.py b/powerline_shell/segments/fossil.py index bfe38cd..3c464f9 100644 --- a/powerline_shell/segments/fossil.py +++ b/powerline_shell/segments/fossil.py @@ -1,6 +1,6 @@ import os import subprocess -from ..utils import RepoStats, BasicSegment +from ..utils import RepoStats, ThreadedSegment def get_PATH(): @@ -63,7 +63,7 @@ def build_stats(): return stats, branch -class Segment(BasicSegment): +class Segment(ThreadedSegment): def add_to_powerline(self): self.stats, self.branch = build_stats() diff --git a/powerline_shell/utils.py b/powerline_shell/utils.py index e3a9a2f..c99b08c 100644 --- a/powerline_shell/utils.py +++ b/powerline_shell/utils.py @@ -19,13 +19,23 @@ class RepoStats(object): 'conflicted': u'\u273C' } - def __init__(self): - self.ahead = 0 - self.behind = 0 - self.new = 0 - self.changed = 0 - self.staged = 0 - self.conflicted = 0 + def __init__(self, ahead=0, behind=0, new=0, changed=0, staged=0, conflicted=0): + self.ahead = ahead + self.behind = behind + self.new = new + self.changed = changed + self.staged = staged + self.conflicted = conflicted + + def __eq__(self, other): + return ( + self.ahead == other.ahead and + self.behind == other.behind and + self.new == other.new and + self.changed == other.changed and + self.staged == other.staged and + self.conflicted == other.conflicted + ) @property def dirty(self): diff --git a/test/repo_stats_test.py b/test/repo_stats_test.py index 300d00b..123259b 100644 --- a/test/repo_stats_test.py +++ b/test/repo_stats_test.py @@ -19,7 +19,7 @@ class RepoStatsTest(unittest.TestCase): self.assertEqual(self.repo_stats.n_or_empty("changed"), u"") def test_n_or_empty__n(self): - self.assertEqual(self.repo_stats.n_or_empty("conflicted"), 4) + self.assertEqual(self.repo_stats.n_or_empty("conflicted"), u"4") def test_index(self): self.assertEqual(self.repo_stats["changed"], 1) diff --git a/test/segments_test/fossil_test.py b/test/segments_test/fossil_test.py index 3415873..9f9d01d 100644 --- a/test/segments_test/fossil_test.py +++ b/test/segments_test/fossil_test.py @@ -6,13 +6,11 @@ import sh import powerline_shell.segments.fossil as fossil from powerline_shell.utils import RepoStats - -rs = RepoStats() test_cases = { - "EXTRA new-file": rs.symbols["new"], - "EDITED modified-file": rs.symbols["changed"], - "CONFLICT conflicted-file": rs.symbols["conflicted"], - "ADDED added-file": rs.symbols["staged"], + "EXTRA new-file": RepoStats(new=1), + "EDITED modified-file": RepoStats(changed=1), + "CONFLICT conflicted-file": RepoStats(conflicted=1), + "ADDED added-file": RepoStats(staged=1), } @@ -61,8 +59,5 @@ class FossilTest(unittest.TestCase): @mock.patch('powerline_shell.segments.fossil._get_fossil_status') def test_all(self, check_output): for stdout, result in test_cases.items(): - check_output.return_value = [stdout] - self.segment.start() - self.segment.add_to_powerline() - self.assertEqual(self.powerline.append.call_args[0][0].split()[0], - result) + stats = fossil.parse_fossil_stats([stdout]) + self.assertEquals(result, stats) From f722ec75497f587514514de55677d7d3c9d995eb Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Fri, 29 Sep 2017 16:41:28 -0400 Subject: [PATCH 84/93] changelog for #286 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8f9816..ec09e6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changes +Unreleased + +* Redo Fossil segment to be consistent with git, svn, etc. + ([@emansije](https://github.com/banga/powerline-shell/pull/286)) + 2017-09-18 (version 0.2.2) * Fix python3 issue in uptime segment. Fixes From 219974601d0d4e2fb244450c48a803847a78aa24 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 30 Sep 2017 11:01:15 -0400 Subject: [PATCH 85/93] Make subshell escaping only happen for bash --- powerline_shell/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/powerline_shell/__init__.py b/powerline_shell/__init__.py index 7894587..2c87c64 100644 --- a/powerline_shell/__init__.py +++ b/powerline_shell/__init__.py @@ -100,8 +100,7 @@ class Powerline(object): return self.color('48', code) def append(self, content, fg, bg, separator=None, separator_fg=None): - sanitized = re.sub(r"([`$])", r"\\\1", content) - self.segments.append((sanitized, fg, bg, + 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)) @@ -115,12 +114,16 @@ class Powerline(object): def draw_segment(self, idx): segment = self.segments[idx] + if self.args.shell == "bash": + sanitized = re.sub(r"([`$])", r"\\\1", segment[0]) + else: + sanitized = segment[0] 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], + sanitized, self.bgcolor(next_segment[2]) if next_segment else self.reset, self.fgcolor(segment[4]), segment[3])) From 01dd52f7db95f8646382a85356d666739bb04d34 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 30 Sep 2017 11:08:02 -0400 Subject: [PATCH 86/93] changelog for #282 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec09e6a..28458bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Unreleased * Redo Fossil segment to be consistent with git, svn, etc. ([@emansije](https://github.com/banga/powerline-shell/pull/286)) +* Fix subshell execution in bash described by + [pw3nage](https://github.com/njhartwell/pw3nage) + ([@b-ryan](https://github.com/banga/powerline-shell/pull/282)) 2017-09-18 (version 0.2.2) From e336ae4478ff1c49c1d020296faa01920cb8462c Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 30 Sep 2017 11:09:16 -0400 Subject: [PATCH 87/93] Change symbol for SSH segment to be text "SSH" closes #287 --- powerline_shell/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/powerline_shell/__init__.py b/powerline_shell/__init__.py index 2c87c64..ec25c0d 100644 --- a/powerline_shell/__init__.py +++ b/powerline_shell/__init__.py @@ -50,13 +50,13 @@ class Powerline(object): }, 'patched': { 'lock': u'\uE0A2', - 'network': u'\uE0A2', + 'network': 'SSH', 'separator': u'\uE0B0', 'separator_thin': u'\uE0B1' }, 'flat': { 'lock': u'\uE0A2', - 'network': u'\uE0A2', + 'network': 'SSH', 'separator': '', 'separator_thin': '' }, From da56da3bb37e02775abbf021849686bc65b14bb7 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 30 Sep 2017 11:11:08 -0400 Subject: [PATCH 88/93] changelog for #287 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28458bd..32f17ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ Unreleased * Fix subshell execution in bash described by [pw3nage](https://github.com/njhartwell/pw3nage) ([@b-ryan](https://github.com/banga/powerline-shell/pull/282)) +* Change SSH segment to just use the text `SSH` instead of showing a lock + symbol. Closes [#287](https://github.com/banga/powerline-shell/issues/287). 2017-09-18 (version 0.2.2) From a95090550cbef8a1f59fc36ae313fa50724c7b95 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 30 Sep 2017 11:11:51 -0400 Subject: [PATCH 89/93] version 0.3.0 --- CHANGELOG.md | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32f17ef..3f4d511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changes -Unreleased +2017-09-30 (version 0.3.0) * Redo Fossil segment to be consistent with git, svn, etc. ([@emansije](https://github.com/banga/powerline-shell/pull/286)) diff --git a/setup.py b/setup.py index ffa46e3..08fc42d 100755 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup(name="powerline-shell", - version="0.2.2", + version="0.3.0", description="A pretty prompt for your shell", author="Buck Ryan", url="https://github.com/banga/powerline-shell", From 2dd4c6dbbf10909e0ab22b9821e3803dfbb17899 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 30 Sep 2017 17:08:46 -0400 Subject: [PATCH 90/93] re-implement #175 closes #175 --- powerline_shell/segments/username.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/powerline_shell/segments/username.py b/powerline_shell/segments/username.py index b03dc09..9e4d9d8 100644 --- a/powerline_shell/segments/username.py +++ b/powerline_shell/segments/username.py @@ -1,5 +1,6 @@ from ..utils import BasicSegment import os +import pwd class Segment(BasicSegment): @@ -12,7 +13,7 @@ class Segment(BasicSegment): else: user_prompt = " %s " % os.getenv("USER") - if os.getenv("USER") == "root": + if pwd.getpwuid(os.getuid())[0] == "root": bgcolor = powerline.theme.USERNAME_ROOT_BG else: bgcolor = powerline.theme.USERNAME_BG From 251277297fc89925f1c18ef861ad40a38882d567 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 30 Sep 2017 17:10:23 -0400 Subject: [PATCH 91/93] changelog for #175 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f4d511..d1cb7b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changes +Unreleased + +* Fix username segment's background color after "su" command + ([@Fak3](https://github.com/banga/powerline-shell/pull/175)) + 2017-09-30 (version 0.3.0) * Redo Fossil segment to be consistent with git, svn, etc. From 3c8172577d7e8352014621675de2c95c3378cc3c Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 30 Sep 2017 17:39:28 -0400 Subject: [PATCH 92/93] update battery segment to work with latest powerline shell plus refactor a bit and try to find the battery directory properly --- powerline_shell/segments/battery.py | 31 +++++++++++++++++++++++++++++ powerline_shell/themes/default.py | 2 +- segments/battery.py | 28 -------------------------- 3 files changed, 32 insertions(+), 29 deletions(-) create mode 100644 powerline_shell/segments/battery.py delete mode 100644 segments/battery.py diff --git a/powerline_shell/segments/battery.py b/powerline_shell/segments/battery.py new file mode 100644 index 0000000..d9acd41 --- /dev/null +++ b/powerline_shell/segments/battery.py @@ -0,0 +1,31 @@ +from ..utils import BasicSegment, warn +import os + +LOW_BATTERY_THRESHOLD = 20 +# See discussion in https://github.com/banga/powerline-shell/pull/204 regarding +# the directory where battery info is saved +DIR_OPTIONS = ["/sys/class/power_supply/BAT0", + "/sys/class/power_supply/BAT1"] + + +class Segment(BasicSegment): + def add_to_powerline(self): + if os.path.exists("/sys/class/power_supply/BAT0"): + dir_ = "/sys/class/power_supply/BAT0" + elif os.path.exists("/sys/class/power_supply/BAT1"): + dir_ = "/sys/class/power_supply/BAT1" + else: + warn("battery directory could not be found") + return + with open(os.path.join(dir_, "capacity")) as f: + cap = f.read().strip() + with open(os.path.join(dir_, "status")) as f: + status = f.read().strip() + pwr = u" \u26A1 " if status == "Charging" else u" " + if int(cap) < LOW_BATTERY_THRESHOLD: + bg = self.powerline.theme.BATTERY_LOW_BG + fg = self.powerline.theme.BATTERY_LOW_FG + else: + bg = self.powerline.theme.BATTERY_NORMAL_BG + fg = self.powerline.theme.BATTERY_NORMAL_FG + self.powerline.append(" " + cap + "%" + pwr, fg, bg) diff --git a/powerline_shell/themes/default.py b/powerline_shell/themes/default.py index 3185128..3b8a2b7 100644 --- a/powerline_shell/themes/default.py +++ b/powerline_shell/themes/default.py @@ -60,7 +60,7 @@ class DefaultColor(object): VIRTUAL_ENV_BG = 35 # a mid-tone green VIRTUAL_ENV_FG = 00 - + BATTERY_NORMAL_BG = 22 BATTERY_NORMAL_FG = 7 BATTERY_LOW_BG = 196 diff --git a/segments/battery.py b/segments/battery.py deleted file mode 100644 index 10efe1a..0000000 --- a/segments/battery.py +++ /dev/null @@ -1,28 +0,0 @@ -def add_battery_segment(): - CAP_FILE = '/sys/class/power_supply/BAT0/capacity' - STATUS_FILE = '/sys/class/power_supply/BAT0/status' - LOW_BATTERY_THRESHOLD = 20 - - f = open(CAP_FILE) - cap = f.read().strip() - f.close() - - f = open(STATUS_FILE) - status = f.read().strip() - f.close() - - if status == 'Charging': - pwr = u' \u26A1 ' - else: - pwr = ' ' - - if int(cap) < LOW_BATTERY_THRESHOLD: - bg = Color.BATTERY_LOW_BG - fg = Color.BATTERY_LOW_FG - else: - bg = Color.BATTERY_NORMAL_BG - fg = Color.BATTERY_NORMAL_FG - - powerline.append(' ' + cap + '%' + pwr, fg, bg) - -add_battery_segment() From a103870c425f509cbbbd919508c3895273cedb74 Mon Sep 17 00:00:00 2001 From: Buck Ryan Date: Sat, 30 Sep 2017 17:40:47 -0400 Subject: [PATCH 93/93] changelog for #204 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1cb7b5..ea61c5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Unreleased * Fix username segment's background color after "su" command ([@Fak3](https://github.com/banga/powerline-shell/pull/175)) +* New `battery` segment which shows the percentage your battery is charged and + an icon when your battery is charging. + ([@wattengard](https://github.com/banga/powerline-shell/pull/204)) 2017-09-30 (version 0.3.0)