From bfbb24268a947eda596e2b6a22dcc34dadbe2d58 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 5 Sep 2014 21:55:13 +0400 Subject: [PATCH 01/21] Add script that will be used for releasing powerline --- scripts/powerline-release.py | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100755 scripts/powerline-release.py diff --git a/scripts/powerline-release.py b/scripts/powerline-release.py new file mode 100755 index 00000000..0aa3ca30 --- /dev/null +++ b/scripts/powerline-release.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import argparse +import codecs +import os + +from subprocess import check_output, check_call + + +def parse_version(s): + if s == ('+' * len(s)): + try: + last_version = next(iter(sorted([ + tuple(map(int, tag[len('release-'):].split('.'))) + for tag in check_output(['git', 'tag', '-l', 'release-*']).split('\n')[:-1] + ], reverse=True))) + except StopIteration: + raise ValueError('No existing versions found') + + version = [] + for i in range(len(s) - 1): + try: + version.append(last_version[i]) + except IndexError: + version.append(0) + + try: + version.append(last_version[len(s) - 1] + 1) + except IndexError: + version.append(1) + + if len(version) == 1: + version.append(0) + + return tuple(version) + else: + return tuple(map(int, s.split('.'))) + + +p = argparse.ArgumentParser(description='Powerline release script') +p.add_argument('-v', '--version', type=parse_version, metavar='VERSION', help='Use given version for the release. If version contains only `+\' signs then it will increase latest version number: one `+\' increases major version number (e.g. 1.2.3 -> 2.0), `++\' increases minor version number (e.g. 1.2.3 -> 1.3), `+++\' increases patch level (e.g. 1.2.3 -> 1.2.4). Defaults to `+++\'', default='+++') +p.add_argument('-r', '--rev', metavar='COMMIT', help='Use given revision for the release. Defaults to `develop\'.', default='develop') + + +def create_release(version, rev): + version_string = '.'.join((str(v) for v in version)) + check_call(['git', 'checkout', 'master']) + check_call(['git', 'merge', '--no-ff', '--no-commit', '--log', rev]) + + with codecs.open('.setup.py.new', 'w', encoding='utf-8') as NS: + with codecs.open('setup.py', 'r', encoding='utf-8') as OS: + for line in OS: + if line.startswith('\tversion='): + line = '\tversion=\'' + version_string + '\',\n' + elif 'Development Status' in line: + line = '\t\t\'Development Status :: 5 - Production/Stable\',\n' + NS.write(line) + + os.unlink('setup.py') + os.rename('.setup.py.new', 'setup.py') + check_call(['git', 'add', 'setup.py']) + + check_call(['git', 'commit']) + check_call(['git', 'tag', '-m', 'Release ' + version_string, '-a', 'release-' + version_string]) + check_call(['python', 'setup.py', 'sdist', 'upload']) + check_call(['python', 'setup.py', 'upload_docs']) + + +if __name__ == '__main__': + args = p.parse_args() + create_release(args.version, args.rev) From 5f81c51914559abe17378fd4d141dbd1d7c87b00 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 5 Sep 2014 22:57:22 +0400 Subject: [PATCH 02/21] Push to upstream and do not use `release-` prefix --- scripts/powerline-release.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/powerline-release.py b/scripts/powerline-release.py index 0aa3ca30..50abc0fe 100755 --- a/scripts/powerline-release.py +++ b/scripts/powerline-release.py @@ -13,8 +13,8 @@ def parse_version(s): if s == ('+' * len(s)): try: last_version = next(iter(sorted([ - tuple(map(int, tag[len('release-'):].split('.'))) - for tag in check_output(['git', 'tag', '-l', 'release-*']).split('\n')[:-1] + tuple(map(int, tag.split('.'))) + for tag in check_output(['git', 'tag', '-l', '[0-9]*.*']).split('\n')[:-1] ], reverse=True))) except StopIteration: raise ValueError('No existing versions found') @@ -63,7 +63,9 @@ def create_release(version, rev): check_call(['git', 'add', 'setup.py']) check_call(['git', 'commit']) - check_call(['git', 'tag', '-m', 'Release ' + version_string, '-a', 'release-' + version_string]) + check_call(['git', 'tag', '-m', 'Release ' + version_string, '-a', version_string]) + check_call(['git', 'push', 'upstream', 'master']) + check_call(['git', 'push', 'upstream', version_string]) check_call(['python', 'setup.py', 'sdist', 'upload']) check_call(['python', 'setup.py', 'upload_docs']) From ab07e8f91036c86df7b6a0a8fe148c3850e3e1e3 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 7 Sep 2014 14:06:01 +0400 Subject: [PATCH 03/21] Add ability to run only the given stages --- scripts/powerline-release.py | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/scripts/powerline-release.py b/scripts/powerline-release.py index 50abc0fe..1e56f1a3 100755 --- a/scripts/powerline-release.py +++ b/scripts/powerline-release.py @@ -39,13 +39,7 @@ def parse_version(s): return tuple(map(int, s.split('.'))) -p = argparse.ArgumentParser(description='Powerline release script') -p.add_argument('-v', '--version', type=parse_version, metavar='VERSION', help='Use given version for the release. If version contains only `+\' signs then it will increase latest version number: one `+\' increases major version number (e.g. 1.2.3 -> 2.0), `++\' increases minor version number (e.g. 1.2.3 -> 1.3), `+++\' increases patch level (e.g. 1.2.3 -> 1.2.4). Defaults to `+++\'', default='+++') -p.add_argument('-r', '--rev', metavar='COMMIT', help='Use given revision for the release. Defaults to `develop\'.', default='develop') - - -def create_release(version, rev): - version_string = '.'.join((str(v) for v in version)) +def merge(version_string, rev): check_call(['git', 'checkout', 'master']) check_call(['git', 'merge', '--no-ff', '--no-commit', '--log', rev]) @@ -64,12 +58,38 @@ def create_release(version, rev): check_call(['git', 'commit']) check_call(['git', 'tag', '-m', 'Release ' + version_string, '-a', version_string]) + + +def push(version_string, rev): check_call(['git', 'push', 'upstream', 'master']) check_call(['git', 'push', 'upstream', version_string]) + + +def upload(*args): check_call(['python', 'setup.py', 'sdist', 'upload']) check_call(['python', 'setup.py', 'upload_docs']) +stages = ( + ('merge', merge), + ('push', push), + ('upload', upload), +) + + +def create_release(version, rev, run_stages=None): + version_string = '.'.join((str(v) for v in version)) + for stname, stfunc in stages: + if run_stages is None or stname in run_stages: + stfunc(version_string, rev) + + +p = argparse.ArgumentParser(description='Powerline release script') +p.add_argument('-v', '--version', type=parse_version, metavar='VERSION', help='Use given version for the release. If version contains only `+\' signs then it will increase latest version number: one `+\' increases major version number (e.g. 1.2.3 -> 2.0), `++\' increases minor version number (e.g. 1.2.3 -> 1.3), `+++\' increases patch level (e.g. 1.2.3 -> 1.2.4). Defaults to `+++\'', default='+++') +p.add_argument('-r', '--rev', metavar='COMMIT', help='Use given revision for the release. Defaults to `develop\'.', default='develop') +p.add_argument('-s', '--stages', action='append', metavar='STAGE', help='Only run one of the given stages (default to all).', choices=tuple((stname for stname, stfunc in stages))) + + if __name__ == '__main__': args = p.parse_args() - create_release(args.version, args.rev) + create_release(args.version, args.rev, args.stages) From 0520526037a490c6f1d0bda869b751d1ecb4dd03 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 7 Sep 2014 15:54:56 +0400 Subject: [PATCH 04/21] Add code to deal with raiagent overlay --- scripts/powerline-release.py | 83 +++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 7 deletions(-) diff --git a/scripts/powerline-release.py b/scripts/powerline-release.py index 1e56f1a3..c147c5d1 100755 --- a/scripts/powerline-release.py +++ b/scripts/powerline-release.py @@ -5,8 +5,17 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct import argparse import codecs import os +import shutil from subprocess import check_output, check_call +from getpass import getpass + +from github import Github + + +OVERLAY_NAME = 'raiagent' +OVERLAY = 'leycec/' + OVERLAY_NAME +OVERLAY_BRANCH_FORMAT = 'powerline-release-{0}' def parse_version(s): @@ -39,7 +48,7 @@ def parse_version(s): return tuple(map(int, s.split('.'))) -def merge(version_string, rev): +def merge(version_string, rev, **kwargs): check_call(['git', 'checkout', 'master']) check_call(['git', 'merge', '--no-ff', '--no-commit', '--log', rev]) @@ -60,36 +69,96 @@ def merge(version_string, rev): check_call(['git', 'tag', '-m', 'Release ' + version_string, '-a', version_string]) -def push(version_string, rev): +def push(version_string, **kwargs): check_call(['git', 'push', 'upstream', 'master']) check_call(['git', 'push', 'upstream', version_string]) -def upload(*args): +def upload(**args): check_call(['python', 'setup.py', 'sdist', 'upload']) check_call(['python', 'setup.py', 'upload_docs']) +gh = None + + +def get_gh(user, password): + global gh + if not gh: + gh = Github(user, password) + return gh + + +def create_ebuilds(version_string, overlay, user, **kwargs): + overlay_url = 'git://github.com/' + OVERLAY + if not os.path.isdir(overlay): + check_call(['git', 'clone', overlay_url, overlay]) + check_call(['git', 'checkout', 'master'], cwd=overlay) + check_call(['git', 'pull', '--ff-only', overlay_url, 'master'], cwd=overlay) + branch = OVERLAY_BRANCH_FORMAT.format(version_string) + check_call(['git', 'branch', branch], cwd=overlay) + check_call(['git', 'checkout', branch], cwd=overlay) + new_files = [] + for category, pn in ( + ('app-misc', 'powerline'), + ('app-vim', 'powerline-python'), + ): + pdir = os.path.join(overlay, category, pn) + v1_0 = os.path.join(pdir, '{0}-1.0.ebuild'.format(pn)) + vcur = os.path.join(pdir, '{0}-{1}.ebuild'.format(pn, version_string)) + shutil.copy2(v1_0, vcur) + check_call(['git', 'add', vcur], cwd=overlay) + new_files.append(vcur) + check_call(['git', 'commit'] + new_files + ['-m', 'powerline*: Release {0}'.format(version_string)], + cwd=overlay) + check_call(['git', 'push', 'git@github.com:{0}/{1}'.format(user, OVERLAY_NAME), branch], cwd=overlay) + + +def update_overlay(version_string, user, password, **kwargs): + gh = get_gh(user, password) + overlay = gh.get_repo(OVERLAY) + overlay.create_pull( + title='New powerline version: ' + version_string, + body='Add ebuilds for new powerline version\n\n---\n\nCreated automatically by release script', + base='master', + head=user + ':' + OVERLAY_BRANCH_FORMAT.format(version_string), + ) + + stages = ( ('merge', merge), ('push', push), ('upload', upload), + ('create_ebuilds', create_ebuilds), + ('update_overlay', update_overlay), ) -def create_release(version, rev, run_stages=None): +def create_release(version, user, password=None, run_stages=None, **kwargs): version_string = '.'.join((str(v) for v in version)) + if not password: + password = getpass('Password for {0}: '.format(user)) for stname, stfunc in stages: if run_stages is None or stname in run_stages: - stfunc(version_string, rev) + stfunc(version_string=version_string, user=user, password=password, **kwargs) p = argparse.ArgumentParser(description='Powerline release script') -p.add_argument('-v', '--version', type=parse_version, metavar='VERSION', help='Use given version for the release. If version contains only `+\' signs then it will increase latest version number: one `+\' increases major version number (e.g. 1.2.3 -> 2.0), `++\' increases minor version number (e.g. 1.2.3 -> 1.3), `+++\' increases patch level (e.g. 1.2.3 -> 1.2.4). Defaults to `+++\'', default='+++') +p.add_argument('-u', '--user', type=str, metavar='USER', help='Github username.', required=True) +p.add_argument('-v', '--version', type=parse_version, metavar='VERSION', help='Use given version for the release. If version contains only `+\' signs then it will increase latest version number: one `+\' increases major version number (e.g. 1.2.3 -> 2.0), `++\' increases minor version number (e.g. 1.2.3 -> 1.3), `+++\' increases patch level (e.g. 1.2.3 -> 1.2.4). Defaults to `+++\'.', default='+++') p.add_argument('-r', '--rev', metavar='COMMIT', help='Use given revision for the release. Defaults to `develop\'.', default='develop') p.add_argument('-s', '--stages', action='append', metavar='STAGE', help='Only run one of the given stages (default to all).', choices=tuple((stname for stname, stfunc in stages))) +p.add_argument('-p', '--password', type=str, metavar='PASS', help='Github password. You will be prompted if it is not supplied.') +p.add_argument('-o', '--overlay', type=str, metavar='PATH', help='Location of the local clone of the {0} overlay. If provided directory does not exist it will be created by “git clone”. Defaults to /tmp/powerline-{0}.'.format(OVERLAY_NAME), default='/tmp/powerline-' + OVERLAY_NAME) if __name__ == '__main__': args = p.parse_args() - create_release(args.version, args.rev, args.stages) + create_release( + version=args.version, + rev=args.rev, + user=args.user, + password=args.password, + overlay=args.overlay, + run_stages=args.stages, + ) From cd2703d7cc11f0b83a313c9f9a0a1cd842f55c16 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 21 Sep 2014 16:02:52 +0400 Subject: [PATCH 05/21] powerline-python got renamed to powerline-vim --- scripts/powerline-release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/powerline-release.py b/scripts/powerline-release.py index c147c5d1..d9cd7a49 100755 --- a/scripts/powerline-release.py +++ b/scripts/powerline-release.py @@ -101,7 +101,7 @@ def create_ebuilds(version_string, overlay, user, **kwargs): new_files = [] for category, pn in ( ('app-misc', 'powerline'), - ('app-vim', 'powerline-python'), + ('app-vim', 'powerline-vim'), ): pdir = os.path.join(overlay, category, pn) v1_0 = os.path.join(pdir, '{0}-1.0.ebuild'.format(pn)) From b60569278432c20b40d847139dcb3e81328170ef Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 21 Sep 2014 17:03:30 +0400 Subject: [PATCH 06/21] Update manifests --- scripts/powerline-release.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/powerline-release.py b/scripts/powerline-release.py index d9cd7a49..ecfc213f 100755 --- a/scripts/powerline-release.py +++ b/scripts/powerline-release.py @@ -98,6 +98,9 @@ def create_ebuilds(version_string, overlay, user, **kwargs): branch = OVERLAY_BRANCH_FORMAT.format(version_string) check_call(['git', 'branch', branch], cwd=overlay) check_call(['git', 'checkout', branch], cwd=overlay) + os.environ['DISTDIR'] = '/tmp/powerline-distfiles' + if not os.path.isdir(os.environ['DISTDIR']): + os.mkdir(os.environ['DISTDIR']) new_files = [] for category, pn in ( ('app-misc', 'powerline'), @@ -107,8 +110,10 @@ def create_ebuilds(version_string, overlay, user, **kwargs): v1_0 = os.path.join(pdir, '{0}-1.0.ebuild'.format(pn)) vcur = os.path.join(pdir, '{0}-{1}.ebuild'.format(pn, version_string)) shutil.copy2(v1_0, vcur) - check_call(['git', 'add', vcur], cwd=overlay) new_files.append(vcur) + check_call(['ebuild', vcur, 'manifest']) + new_files.append(os.path.join(pdir, 'Manifest')) + check_call(['git', 'add', '--'] + new_files, cwd=overlay) check_call(['git', 'commit'] + new_files + ['-m', 'powerline*: Release {0}'.format(version_string)], cwd=overlay) check_call(['git', 'push', 'git@github.com:{0}/{1}'.format(user, OVERLAY_NAME), branch], cwd=overlay) From 05cb1d2d3ce33135a60d3b927ad50149dd62e050 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 22 Sep 2014 07:42:42 +0400 Subject: [PATCH 07/21] Lower the priority of various VM bridge interfaces Closes #1094 --- powerline/segments/common/net.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/powerline/segments/common/net.py b/powerline/segments/common/net.py index 3e4ccb24..74ae60d9 100644 --- a/powerline/segments/common/net.py +++ b/powerline/segments/common/net.py @@ -72,13 +72,16 @@ except ImportError: return None else: _interface_starts = { - 'eth': 10, # Regular ethernet adapters : eth1 - 'enp': 10, # Regular ethernet adapters, Gentoo : enp2s0 - 'ath': 9, # Atheros WiFi adapters : ath0 - 'wlan': 9, # Other WiFi adapters : wlan1 - 'wlp': 9, # Other WiFi adapters, Gentoo : wlp5s0 - 'teredo': 1, # miredo interface : teredo - 'lo': -10, # Loopback interface : lo + 'eth': 10, # Regular ethernet adapters : eth1 + 'enp': 10, # Regular ethernet adapters, Gentoo : enp2s0 + 'ath': 9, # Atheros WiFi adapters : ath0 + 'wlan': 9, # Other WiFi adapters : wlan1 + 'wlp': 9, # Other WiFi adapters, Gentoo : wlp5s0 + 'teredo': 1, # miredo interface : teredo + 'lo': -10, # Loopback interface : lo + 'docker': -5, # Docker bridge interface : docker0 + 'vmnet': -5, # VMWare bridge interface : vmnet1 + 'vboxnet': -5, # VirtualBox bridge interface : vboxnet0 } _interface_start_re = re.compile(r'^([a-z]+?)(\d|$)') From 3f2aabb77bf9c7e79eace61c3da048271c97b17a Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 26 Sep 2014 00:20:58 +0400 Subject: [PATCH 08/21] Refactor Vim and common branch segments to share code --- powerline/segments/common/vcs.py | 57 ++++++++++++++++++------------ powerline/segments/vim/__init__.py | 43 +++++++++++----------- tests/test_segments.py | 50 ++++++++++++++++---------- 3 files changed, 86 insertions(+), 64 deletions(-) diff --git a/powerline/segments/common/vcs.py b/powerline/segments/common/vcs.py index a6311bc5..9daddf1e 100644 --- a/powerline/segments/common/vcs.py +++ b/powerline/segments/common/vcs.py @@ -2,32 +2,45 @@ from __future__ import (unicode_literals, division, absolute_import, print_function) from powerline.lib.vcs import guess, tree_status +from powerline.segments import Segment, with_docstring from powerline.theme import requires_segment_info, requires_filesystem_watcher @requires_filesystem_watcher @requires_segment_info -def branch(pl, segment_info, create_watcher, status_colors=False): - '''Return the current VCS branch. +class BranchSegment(Segment): + divider_highlight_group = None - :param bool status_colors: - determines whether repository status will be used to determine highlighting. Default: False. + @staticmethod + def get_directory(segment_info): + return segment_info['getcwd']() - Highlight groups used: ``branch_clean``, ``branch_dirty``, ``branch``. - ''' - name = segment_info['getcwd']() - repo = guess(path=name, create_watcher=create_watcher) - if repo is not None: - branch = repo.branch() - scol = ['branch'] - if status_colors: - try: - status = tree_status(repo, pl) - except Exception as e: - pl.exception('Failed to compute tree status: {0}', str(e)) - status = '?' - scol.insert(0, 'branch_dirty' if status and status.strip() else 'branch_clean') - return [{ - 'contents': branch, - 'highlight_group': scol, - }] + def __call__(self, pl, segment_info, create_watcher, status_colors=False): + name = self.get_directory(segment_info) + if name: + repo = guess(path=name, create_watcher=create_watcher) + if repo is not None: + branch = repo.branch() + scol = ['branch'] + if status_colors: + try: + status = tree_status(repo, pl) + except Exception as e: + pl.exception('Failed to compute tree status: {0}', str(e)) + status = '?' + scol.insert(0, 'branch_dirty' if status and status.strip() else 'branch_clean') + return [{ + 'contents': branch, + 'highlight_group': scol, + 'divider_highlight_group': self.divider_highlight_group, + }] + + +branch = with_docstring(BranchSegment(), +'''Return the current VCS branch. + +:param bool status_colors: + determines whether repository status will be used to determine highlighting. Default: False. + +Highlight groups used: ``branch_clean``, ``branch_dirty``, ``branch``. +''') diff --git a/powerline/segments/vim/__init__.py b/powerline/segments/vim/__init__.py index 6467890d..31f3557e 100644 --- a/powerline/segments/vim/__init__.py +++ b/powerline/segments/vim/__init__.py @@ -17,9 +17,11 @@ from powerline.bindings.vim import (vim_get_func, getbufvar, vim_getbufoption, list_tabpage_buffers_segment_info) from powerline.theme import requires_segment_info, requires_filesystem_watcher from powerline.lib import add_divider_highlight_group -from powerline.lib.vcs import guess, tree_status +from powerline.lib.vcs import guess from powerline.lib.humanize_bytes import humanize_bytes from powerline.lib import wraps_saveargs as wraps +from powerline.segments.common.vcs import BranchSegment +from powerline.segments import with_docstring try: from __builtin__ import xrange as range @@ -480,31 +482,26 @@ def modified_buffers(pl, text='+ ', join_str=','): @requires_filesystem_watcher @requires_segment_info -def branch(pl, segment_info, create_watcher, status_colors=False): - '''Return the current working branch. +class VimBranchSegment(BranchSegment): + divider_highlight_group = 'branch:divider' - :param bool status_colors: - determines whether repository status will be used to determine highlighting. Default: False. + @staticmethod + def get_directory(segment_info): + if vim_getbufoption(segment_info, 'buftype'): + return None + return buffer_name(segment_info) - Highlight groups used: ``branch_clean``, ``branch_dirty``, ``branch``. - Divider highlight group used: ``branch:divider``. - ''' - name = buffer_name(segment_info) - skip = not (name and (not vim_getbufoption(segment_info, 'buftype'))) - if not skip: - repo = guess(path=name, create_watcher=create_watcher) - if repo is not None: - branch = repo.branch() - scol = ['branch'] - if status_colors: - status = tree_status(repo, pl) - scol.insert(0, 'branch_dirty' if status and status.strip() else 'branch_clean') - return [{ - 'contents': branch, - 'highlight_group': scol, - 'divider_highlight_group': 'branch:divider', - }] +branch = with_docstring(VimBranchSegment(), +'''Return the current working branch. + +:param bool status_colors: + determines whether repository status will be used to determine highlighting. Default: False. + +Highlight groups used: ``branch_clean``, ``branch_dirty``, ``branch``. + +Divider highlight group used: ``branch:divider``. +''') @requires_filesystem_watcher diff --git a/tests/test_segments.py b/tests/test_segments.py index 57f23abb..62db1288 100644 --- a/tests/test_segments.py +++ b/tests/test_segments.py @@ -600,23 +600,33 @@ class TestVcs(TestCommon): branch = partial(common.branch, pl=pl, create_watcher=create_watcher) with replace_attr(self.module, 'guess', get_dummy_guess(status=lambda: None, directory='/tmp/tests')): with replace_attr(self.module, 'tree_status', lambda repo, pl: None): - self.assertEqual(branch(segment_info=segment_info, status_colors=False), [ - {'highlight_group': ['branch'], 'contents': 'tests'} - ]) - self.assertEqual(branch(segment_info=segment_info, status_colors=True), [ - {'contents': 'tests', 'highlight_group': ['branch_clean', 'branch']} - ]) + self.assertEqual(branch(segment_info=segment_info, status_colors=False), [{ + 'highlight_group': ['branch'], + 'contents': 'tests', + 'divider_highlight_group': None + }]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True), [{ + 'contents': 'tests', + 'highlight_group': ['branch_clean', 'branch'], + 'divider_highlight_group': None + }]) with replace_attr(self.module, 'guess', get_dummy_guess(status=lambda: 'D ', directory='/tmp/tests')): with replace_attr(self.module, 'tree_status', lambda repo, pl: 'D '): - self.assertEqual(branch(segment_info=segment_info, status_colors=False), [ - {'highlight_group': ['branch'], 'contents': 'tests'} - ]) - self.assertEqual(branch(segment_info=segment_info, status_colors=True), [ - {'contents': 'tests', 'highlight_group': ['branch_dirty', 'branch']} - ]) - self.assertEqual(branch(segment_info=segment_info, status_colors=False), [ - {'highlight_group': ['branch'], 'contents': 'tests'} - ]) + self.assertEqual(branch(segment_info=segment_info, status_colors=False), [{ + 'highlight_group': ['branch'], + 'contents': 'tests', + 'divider_highlight_group': None + }]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True), [{ + 'contents': 'tests', + 'highlight_group': ['branch_dirty', 'branch'], + 'divider_highlight_group': None + }]) + self.assertEqual(branch(segment_info=segment_info, status_colors=False), [{ + 'highlight_group': ['branch'], + 'contents': 'tests', + 'divider_highlight_group': None + }]) with replace_attr(self.module, 'guess', lambda path, create_watcher: None): self.assertEqual(branch(segment_info=segment_info, status_colors=False), None) @@ -1057,16 +1067,16 @@ class TestVim(TestCase): create_watcher = get_fallback_create_watcher() branch = partial(self.vim.branch, pl=pl, create_watcher=create_watcher) with vim_module._with('buffer', '/foo') as segment_info: - with replace_attr(self.vim, 'guess', get_dummy_guess(status=lambda: None)): - with replace_attr(self.vim, 'tree_status', lambda repo, pl: None): + with replace_attr(self.vcs, 'guess', get_dummy_guess(status=lambda: None)): + with replace_attr(self.vcs, 'tree_status', lambda repo, pl: None): self.assertEqual(branch(segment_info=segment_info, status_colors=False), [ {'divider_highlight_group': 'branch:divider', 'highlight_group': ['branch'], 'contents': 'foo'} ]) self.assertEqual(branch(segment_info=segment_info, status_colors=True), [ {'divider_highlight_group': 'branch:divider', 'highlight_group': ['branch_clean', 'branch'], 'contents': 'foo'} ]) - with replace_attr(self.vim, 'guess', get_dummy_guess(status=lambda: 'DU')): - with replace_attr(self.vim, 'tree_status', lambda repo, pl: 'DU'): + with replace_attr(self.vcs, 'guess', get_dummy_guess(status=lambda: 'DU')): + with replace_attr(self.vcs, 'tree_status', lambda repo, pl: 'DU'): self.assertEqual(branch(segment_info=segment_info, status_colors=False), [ {'divider_highlight_group': 'branch:divider', 'highlight_group': ['branch'], 'contents': 'foo'} ]) @@ -1155,6 +1165,8 @@ class TestVim(TestCase): sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'path'))) from powerline.segments import vim cls.vim = vim + from powerline.segments.common import vcs + cls.vcs = vcs @classmethod def tearDownClass(cls): From 8b1a502f0d71b52304543d07a24eb12b890a8d9d Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 26 Sep 2014 00:34:32 +0400 Subject: [PATCH 09/21] Add ignore_statuses option to branch segments Closes #1080 --- powerline/segments/common/vcs.py | 18 ++++++++++++--- powerline/segments/vim/__init__.py | 10 ++++++++- tests/test_segments.py | 36 ++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/powerline/segments/common/vcs.py b/powerline/segments/common/vcs.py index 9daddf1e..401741b2 100644 --- a/powerline/segments/common/vcs.py +++ b/powerline/segments/common/vcs.py @@ -15,7 +15,7 @@ class BranchSegment(Segment): def get_directory(segment_info): return segment_info['getcwd']() - def __call__(self, pl, segment_info, create_watcher, status_colors=False): + def __call__(self, pl, segment_info, create_watcher, status_colors=False, ignore_statuses=()): name = self.get_directory(segment_info) if name: repo = guess(path=name, create_watcher=create_watcher) @@ -28,7 +28,11 @@ class BranchSegment(Segment): except Exception as e: pl.exception('Failed to compute tree status: {0}', str(e)) status = '?' - scol.insert(0, 'branch_dirty' if status and status.strip() else 'branch_clean') + else: + status = status and status.strip() + if status in ignore_statuses: + status = None + scol.insert(0, 'branch_dirty' if status else 'branch_clean') return [{ 'contents': branch, 'highlight_group': scol, @@ -40,7 +44,15 @@ branch = with_docstring(BranchSegment(), '''Return the current VCS branch. :param bool status_colors: - determines whether repository status will be used to determine highlighting. Default: False. + Determines whether repository status will be used to determine highlighting. + Default: False. +:param bool ignore_statuses: + List of statuses which will not result in repo being marked as dirty. Most + useful is setting this option to ``["U"]``: this will ignore repository + which has just untracked files (i.e. repository with modified, deleted or + removed files will be marked as dirty, while just untracked files will make + segment show clean repository). Only applicable if ``status_colors`` option + is True. Highlight groups used: ``branch_clean``, ``branch_dirty``, ``branch``. ''') diff --git a/powerline/segments/vim/__init__.py b/powerline/segments/vim/__init__.py index 31f3557e..86260e6b 100644 --- a/powerline/segments/vim/__init__.py +++ b/powerline/segments/vim/__init__.py @@ -496,7 +496,15 @@ branch = with_docstring(VimBranchSegment(), '''Return the current working branch. :param bool status_colors: - determines whether repository status will be used to determine highlighting. Default: False. + Determines whether repository status will be used to determine highlighting. + Default: False. +:param bool ignore_statuses: + List of statuses which will not result in repo being marked as dirty. Most + useful is setting this option to ``["U"]``: this will ignore repository + which has just untracked files (i.e. repository with modified, deleted or + removed files will be marked as dirty, while just untracked files will make + segment show clean repository). Only applicable if ``status_colors`` option + is True. Highlight groups used: ``branch_clean``, ``branch_dirty``, ``branch``. diff --git a/tests/test_segments.py b/tests/test_segments.py index 62db1288..8070f44d 100644 --- a/tests/test_segments.py +++ b/tests/test_segments.py @@ -629,6 +629,28 @@ class TestVcs(TestCommon): }]) with replace_attr(self.module, 'guess', lambda path, create_watcher: None): self.assertEqual(branch(segment_info=segment_info, status_colors=False), None) + with replace_attr(self.module, 'guess', get_dummy_guess(status=lambda: 'U')): + with replace_attr(self.module, 'tree_status', lambda repo, pl: 'U'): + self.assertEqual(branch(segment_info=segment_info, status_colors=False, ignore_statuses=['U']), [{ + 'highlight_group': ['branch'], + 'contents': 'tests', + 'divider_highlight_group': None + }]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True, ignore_statuses=['DU']), [{ + 'highlight_group': ['branch_dirty', 'branch'], + 'contents': 'tests', + 'divider_highlight_group': None + }]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True), [{ + 'highlight_group': ['branch_dirty', 'branch'], + 'contents': 'tests', + 'divider_highlight_group': None + }]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True, ignore_statuses=['U']), [{ + 'highlight_group': ['branch_clean', 'branch'], + 'contents': 'tests', + 'divider_highlight_group': None + }]) class TestTime(TestCommon): @@ -1083,6 +1105,20 @@ class TestVim(TestCase): self.assertEqual(branch(segment_info=segment_info, status_colors=True), [ {'divider_highlight_group': 'branch:divider', 'highlight_group': ['branch_dirty', 'branch'], 'contents': 'foo'} ]) + with replace_attr(self.vcs, 'guess', get_dummy_guess(status=lambda: 'U')): + with replace_attr(self.vcs, 'tree_status', lambda repo, pl: 'U'): + self.assertEqual(branch(segment_info=segment_info, status_colors=False, ignore_statuses=['U']), [ + {'divider_highlight_group': 'branch:divider', 'highlight_group': ['branch'], 'contents': 'foo'} + ]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True, ignore_statuses=['DU']), [ + {'divider_highlight_group': 'branch:divider', 'highlight_group': ['branch_dirty', 'branch'], 'contents': 'foo'} + ]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True), [ + {'divider_highlight_group': 'branch:divider', 'highlight_group': ['branch_dirty', 'branch'], 'contents': 'foo'} + ]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True, ignore_statuses=['U']), [ + {'divider_highlight_group': 'branch:divider', 'highlight_group': ['branch_clean', 'branch'], 'contents': 'foo'} + ]) def test_file_vcs_status(self): pl = Pl() From 34e3aed094e1dd0f5b44c2120cf1f2286bb60ce3 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 27 Sep 2014 18:06:53 +0400 Subject: [PATCH 10/21] Add missing argument to get_tmux_output Fixes #1097 --- powerline/segments/tmux.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/powerline/segments/tmux.py b/powerline/segments/tmux.py index b26824dc..1f343893 100644 --- a/powerline/segments/tmux.py +++ b/powerline/segments/tmux.py @@ -11,12 +11,12 @@ def attached_clients(pl, minimum=1): The minimum number of attached clients that must be present for this segment to be visible. ''' - session_output = get_tmux_output('list-panes', '-F', '#{session_name}') + session_output = get_tmux_output(pl, 'list-panes', '-F', '#{session_name}') if not session_output: return None session_name = session_output.rstrip().split('\n')[0] - attached_clients_output = get_tmux_output('list-clients', '-t', session_name) + attached_clients_output = get_tmux_output(pl, 'list-clients', '-t', session_name) attached_count = len(attached_clients_output.rstrip().split('\n')) return None if attached_count < minimum else str(attached_count) From 2607fddbb063c12aaab0e15fb96fbda2a492f240 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Oct 2014 08:45:13 +0400 Subject: [PATCH 11/21] Fix case in nerd tree matcher regex Fixes #1105 --- powerline/matchers/vim/plugin/nerdtree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerline/matchers/vim/plugin/nerdtree.py b/powerline/matchers/vim/plugin/nerdtree.py index 3ec423df..d6e9f69d 100644 --- a/powerline/matchers/vim/plugin/nerdtree.py +++ b/powerline/matchers/vim/plugin/nerdtree.py @@ -7,7 +7,7 @@ import re from powerline.bindings.vim import buffer_name -NERD_TREE_RE = re.compile(b'NERD_TREE_\\d+') +NERD_TREE_RE = re.compile(b'NERD_tree_\\d+') def nerdtree(matcher_info): From d1ac624c7f1bce297f89c019276c71094b87c273 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Oct 2014 00:36:53 +0400 Subject: [PATCH 12/21] Fix broken tests --- tests/test_segments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_segments.py b/tests/test_segments.py index 8070f44d..1e3bb8eb 100644 --- a/tests/test_segments.py +++ b/tests/test_segments.py @@ -277,7 +277,7 @@ class TestShell(TestCase): class TestTmux(TestCase): def test_attached_clients(self): - def get_tmux_output(cmd, *args): + def get_tmux_output(pl, cmd, *args): if cmd == 'list-panes': return 'session_name\n' elif cmd == 'list-clients': From 0d3609fb9675b643ea3f1ccce5765e39c6a97ed3 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Oct 2014 00:41:06 +0400 Subject: [PATCH 13/21] Specify $TERM in place of assuming it is set to something working Ref #1104 --- tests/test_shells/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_shells/test.sh b/tests/test_shells/test.sh index fb4c4984..4f1ba473 100755 --- a/tests/test_shells/test.sh +++ b/tests/test_shells/test.sh @@ -62,7 +62,7 @@ run() { env -i \ LANG=en_US.UTF-8 \ PATH="$local_path" \ - TERM="${TERM}" \ + TERM="screen-256color" \ COLUMNS="${COLUMNS}" \ LINES="${LINES}" \ TEST_TYPE="${TEST_TYPE}" \ From a37f90921fc68311eb16405411373a8a050accf1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Oct 2014 14:56:11 +0400 Subject: [PATCH 14/21] Refactor powerline.segments.common.players to use multiple functions Also adds documentation. Still have to update top-level themes. Fixes #445 --- powerline/lint/imp.py | 6 + powerline/segments/common/players.py | 303 +++++++++++++++++++++------ 2 files changed, 249 insertions(+), 60 deletions(-) diff --git a/powerline/lint/imp.py b/powerline/lint/imp.py index 98b7591b..d3966512 100644 --- a/powerline/lint/imp.py +++ b/powerline/lint/imp.py @@ -27,6 +27,12 @@ def import_function(function_type, name, data, context, echoerr, module): problem='module {0} is deprecated'.format(module), problem_mark=module.mark) + if module == 'powerline.segments.common.players' and name == 'now_playing': + echoerr(context='Warning while checking segments (key {key})'.format(key=context.key), + context_mark=name.mark, + problem='function {0}.{1} is deprecated: use {0}.{{player_name}} instead'.format(module, name), + problem_mark=module.mark) + with WithPath(data['import_paths']): try: func = getattr(__import__(str(module), fromlist=[str(name)]), str(name)) diff --git a/powerline/segments/common/players.py b/powerline/segments/common/players.py index 589e419e..47dc8f0b 100644 --- a/powerline/segments/common/players.py +++ b/powerline/segments/common/players.py @@ -5,7 +5,7 @@ import sys from powerline.lib.shell import asrun, run_cmd from powerline.lib.unicode import out_u -from powerline.segments import Segment +from powerline.segments import Segment, with_docstring STATE_SYMBOLS = { @@ -16,9 +16,25 @@ STATE_SYMBOLS = { } -class NowPlayingSegment(Segment): - def __call__(self, player='mpd', format='{state_symbol} {artist} - {title} ({total})', state_symbols=STATE_SYMBOLS, **kwargs): - player_func = getattr(self, 'player_{0}'.format(player)) +def _convert_state(state): + '''Guess player state''' + state = state.lower() + if 'play' in state: + return 'play' + if 'pause' in state: + return 'pause' + if 'stop' in state: + return 'stop' + return 'fallback' + + +def _convert_seconds(seconds): + '''Convert seconds to minutes:seconds format''' + return '{0:.0f}:{1:02.0f}'.format(*divmod(float(seconds), 60)) + + +class PlayerSegment(Segment): + def __call__(self, format='{state_symbol} {artist} - {title} ({total})', state_symbols=STATE_SYMBOLS, **kwargs): stats = { 'state': 'fallback', 'album': None, @@ -27,28 +43,72 @@ class NowPlayingSegment(Segment): 'elapsed': None, 'total': None, } - func_stats = player_func(**kwargs) + func_stats = self.get_player_status(**kwargs) if not func_stats: return None stats.update(func_stats) stats['state_symbol'] = state_symbols.get(stats['state']) return format.format(**stats) - @staticmethod - def _convert_state(state): - state = state.lower() - if 'play' in state: - return 'play' - if 'pause' in state: - return 'pause' - if 'stop' in state: - return 'stop' + def argspecobjs(self): + for ret in super(PlayerSegment, self).argspecobjs(): + yield ret + yield 'get_player_status', self.get_player_status - @staticmethod - def _convert_seconds(seconds): - return '{0:.0f}:{1:02.0f}'.format(*divmod(float(seconds), 60)) + def omitted_args(self, name, method): + return (0,) - def player_cmus(self, pl): + +_common_args = ''' +This player segment should be added like this: + +.. code-block:: json + + {{ + "function": "powerline.segments.common.players.{0}", + "name": "player" + }} + +(with additional ``"args": {{…}}`` if needed). + +:param str format: + Format used for displaying data from player. Should be a str.format-like + string with the following keyword parameters: + + +------------+-------------------------------------------------------------+ + |Parameter |Description | + +============+=============================================================+ + |state_symbol|Symbol displayed for play/pause/stop states. There is also | + | |“fallback” state used in case function failed to get player | + | |state. For this state symbol is by default empty. All | + | |symbols are defined in ``state_symbols`` argument. | + +------------+-------------------------------------------------------------+ + |album |Album that is currently played. | + +------------+-------------------------------------------------------------+ + |artist |Artist whose song is currently played | + +------------+-------------------------------------------------------------+ + |title |Currently played composition. | + +------------+-------------------------------------------------------------+ + |elapsed |Composition duration in format M:SS (minutes:seconds). | + +------------+-------------------------------------------------------------+ + |total |Composition length in format M:SS. | + +------------+-------------------------------------------------------------+ +:param dict state_symbols: + Symbols used for displaying state. Must contain all of the following keys: + + ======== ======================================================== + Key Description + ======== ======================================================== + play Displayed when player is playing. + pause Displayed when player is paused. + stop Displayed when player is not playing anything. + fallback Displayed if state is not one of the above or not known. + ======== ======================================================== +''' + + +class CmusPlayerSegment(PlayerSegment): + def get_player_status(self, pl): '''Return cmus player information. cmus-remote -Q returns data with multi-level information i.e. @@ -75,17 +135,28 @@ class NowPlayingSegment(Segment): now_playing = dict(((token[0] if token[0] not in ignore_levels else token[1], (' '.join(token[1:]) if token[0] not in ignore_levels else ' '.join(token[2:]))) for token in [line.split(' ') for line in now_playing_str.split('\n')[:-1]])) - state = self._convert_state(now_playing.get('status')) + state = _convert_state(now_playing.get('status')) return { 'state': state, 'album': now_playing.get('album'), 'artist': now_playing.get('artist'), 'title': now_playing.get('title'), - 'elapsed': self._convert_seconds(now_playing.get('position', 0)), - 'total': self._convert_seconds(now_playing.get('duration', 0)), + 'elapsed': _convert_seconds(now_playing.get('position', 0)), + 'total': _convert_seconds(now_playing.get('duration', 0)), } - def player_mpd(self, pl, host='localhost', port=6600): + +cmus = with_docstring(CmusPlayerSegment(), +('''Return CMUS player information + +Requires cmus-remote command be acessible from $PATH. + +{0} +''').format(_common_args.format('cmus'))) + + +class MpdPlayerSegment(PlayerSegment): + def get_player_status(self, pl, host='localhost', port=6600): try: import mpd except ImportError: @@ -113,16 +184,33 @@ class NowPlayingSegment(Segment): 'album': now_playing.get('album'), 'artist': now_playing.get('artist'), 'title': now_playing.get('title'), - 'elapsed': self._convert_seconds(now_playing.get('elapsed', 0)), - 'total': self._convert_seconds(now_playing.get('time', 0)), + 'elapsed': _convert_seconds(now_playing.get('elapsed', 0)), + 'total': _convert_seconds(now_playing.get('time', 0)), } - def player_dbus(self, player_name, bus_name, player_path, iface_prop, iface_player): - try: - import dbus - except ImportError: - self.exception('Could not add {0} segment: requires dbus module', player_name) - return + +mpd = with_docstring(MpdPlayerSegment(), +('''Return Music Player Daemon information + +Requires mpc command to be acessible from $PATH or ``mpd`` Python module. + +{0} +:param str host: + Host on which mpd runs. +:param int port: + Port which should be connected to. +''').format(_common_args.format('mpd'))) + + +try: + import dbus +except ImportError: + def _get_dbus_player_status(pl, player_name, **kwargs): + pl.error('Could not add {0} segment: requires dbus module', player_name) + return +else: + def _get_dbus_player_status(pl, bus_name, player_path, iface_prop, + iface_player, player_name='player'): bus = dbus.SessionBus() try: player = bus.get_object(bus_name, player_path) @@ -136,7 +224,7 @@ class NowPlayingSegment(Segment): album = out_u(info.get('xesam:album')) title = out_u(info.get('xesam:title')) artist = info.get('xesam:artist') - state = self._convert_state(status) + state = _convert_state(status) if artist: artist = out_u(artist[0]) return { @@ -144,11 +232,38 @@ class NowPlayingSegment(Segment): 'album': album, 'artist': artist, 'title': title, - 'total': self._convert_seconds(info.get('mpris:length') / 1e6), + 'total': _convert_seconds(info.get('mpris:length') / 1e6), } - def player_spotify_dbus(self, pl): - return self.player_dbus( + +class DbusPlayerSegment(PlayerSegment): + get_player_status = staticmethod(_get_dbus_player_status) + + +dbus_player = with_docstring(DbusPlayerSegment(), +('''Return generic dbus player state + +Requires ``dbus`` python module. Only for players that support specific protocol + (e.g. like :py:func:`spotify` and :py:func:`clementine`). + +{0} +:param str player_name: + Player name. Used in error messages only. +:param str bus_name: + Dbus bus name. +:param str player_path: + Path to the player on the given bus. +:param str iface_prop: + Interface properties name for use with dbus.Interface. +:param str iface_player: + Player name. +''').format(_common_args.format('dbus_player'))) + + +class SpotifyDbusPlayerSegment(PlayerSegment): + def get_player_status(self, pl): + return _get_dbus_player_status( + pl=pl, player_name='Spotify', bus_name='com.spotify.qt', player_path='/', @@ -156,16 +271,18 @@ class NowPlayingSegment(Segment): iface_player='org.freedesktop.MediaPlayer2', ) - def player_clementine(self, pl): - return self.player_dbus( - player_name='Clementine', - bus_name='org.mpris.MediaPlayer2.clementine', - player_path='/org/mpris/MediaPlayer2', - iface_prop='org.freedesktop.DBus.Properties', - iface_player='org.mpris.MediaPlayer2.Player', - ) - def player_spotify_apple_script(self, pl): +spotify_dbus = with_docstring(SpotifyDbusPlayerSegment(), +('''Return spotify player information + +Requires ``dbus`` python module. + +{0} +''').format(_common_args.format('spotify_dbus'))) + + +class SpotifyAppleScriptPlayerSegment(PlayerSegment): + def get_player_status(self, pl): status_delimiter = '-~`/=' ascript = ''' tell application "System Events" @@ -196,7 +313,7 @@ class NowPlayingSegment(Segment): return None spotify_status = spotify.split(status_delimiter) - state = self._convert_state(spotify_status[0]) + state = _convert_state(spotify_status[0]) if state == 'stop': return None return { @@ -204,21 +321,58 @@ class NowPlayingSegment(Segment): 'album': spotify_status[1], 'artist': spotify_status[2], 'title': spotify_status[3], - 'total': self._convert_seconds(int(spotify_status[4])) + 'total': _convert_seconds(int(spotify_status[4])) } - try: - __import__('dbus') - except ImportError: - if sys.platform.startswith('darwin'): - player_spotify = player_spotify_apple_script - else: - player_spotify = player_spotify_dbus - else: - player_spotify = player_spotify_dbus - def player_rhythmbox(self, pl): - now_playing = run_cmd(pl, ['rhythmbox-client', '--no-start', '--no-present', '--print-playing-format', '%at\n%aa\n%tt\n%te\n%td']) +spotify_apple_script = with_docstring(SpotifyAppleScriptPlayerSegment(), +('''Return spotify player information + +Requires ``osascript`` available in $PATH. + +{0} +''').format(_common_args.format('spotify_apple_script'))) + + +if 'dbus' in globals() or not sys.platform.startswith('darwin'): + spotify = spotify_dbus + _old_name = 'spotify_dbus' +else: + spotify = spotify_apple_script + _old_name = 'spotify_apple_script' + + +spotify = with_docstring(spotify, spotify.__doc__.replace(_old_name, 'spotify')) + + +class ClementinePlayerSegment(PlayerSegment): + def get_player_status(self, pl): + return _get_dbus_player_status( + pl=pl, + player_name='Clementine', + bus_name='org.mpris.MediaPlayer2.clementine', + player_path='/org/mpris/MediaPlayer2', + iface_prop='org.freedesktop.DBus.Properties', + iface_player='org.mpris.MediaPlayer2.Player', + ) + + +clementine = with_docstring(ClementinePlayerSegment(), +('''Return clementine player information + +Requires ``dbus`` python module. + +{0} +''').format(_common_args.format('clementine'))) + + +class RhythmboxPlayerSegment(PlayerSegment): + def get_player_status(self, pl): + now_playing = run_cmd(pl, [ + 'rhythmbox-client', + '--no-start', '--no-present', + '--print-playing-format', '%at\n%aa\n%tt\n%te\n%td' + ]) if not now_playing: return now_playing = now_playing.split('\n') @@ -230,7 +384,18 @@ class NowPlayingSegment(Segment): 'total': now_playing[4], } - def player_rdio(self, pl): + +rhythmbox = with_docstring(RhythmboxPlayerSegment(), +('''Return rhythmbox player information + +Requires ``rhythmbox-client`` available in $PATH. + +{0} +''').format(_common_args.format('rhythmbox'))) + + +class RDIOPlayerSegment(PlayerSegment): + def get_player_status(self, pl): status_delimiter = '-~`/=' ascript = ''' tell application "System Events" @@ -255,9 +420,9 @@ class NowPlayingSegment(Segment): now_playing = now_playing.split('\n') if len(now_playing) != 6: return - state = self._convert_state(now_playing[5]) - total = self._convert_seconds(now_playing[4]) - elapsed = self._convert_seconds(float(now_playing[3]) * float(now_playing[4]) / 100) + state = _convert_state(now_playing[5]) + total = _convert_seconds(now_playing[4]) + elapsed = _convert_seconds(float(now_playing[3]) * float(now_playing[4]) / 100) return { 'title': now_playing[0], 'artist': now_playing[1], @@ -267,4 +432,22 @@ class NowPlayingSegment(Segment): 'state': state, 'state_symbol': self.STATE_SYMBOLS.get(state) } + + +rdio = with_docstring(RDIOPlayerSegment(), +('''Return rdio player information + +Requires ``osascript`` available in $PATH. + +{0} +''').format(_common_args.format('rdio'))) + + +class NowPlayingSegment(Segment): + def __call__(self, player='mpd', **kwargs): + player_segment = globals()[player] + assert(isinstance(player_segment, PlayerSegment)) + return player_segment(**kwargs) + + now_playing = NowPlayingSegment() From f233c1f77e801ce6f3eec8f3bff4b6998ef08493 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Oct 2014 14:58:48 +0400 Subject: [PATCH 15/21] Fix mpd support --- powerline/lib/shell.py | 6 ++++-- powerline/segments/common/players.py | 9 +++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/powerline/lib/shell.py b/powerline/lib/shell.py index df7cc65e..fcdc35ce 100644 --- a/powerline/lib/shell.py +++ b/powerline/lib/shell.py @@ -16,7 +16,7 @@ if sys.platform.startswith('win32'): Popen = partial(Popen, creationflags=0x08000000) -def run_cmd(pl, cmd, stdin=None): +def run_cmd(pl, cmd, stdin=None, strip=True): '''Run command and return its stdout, stripped If running command fails returns None and logs failure to ``pl`` argument. @@ -27,6 +27,8 @@ def run_cmd(pl, cmd, stdin=None): Command which will be run. :param str stdin: String passed to command. May be None. + :param bool strip: + True if the result should be stripped. ''' try: p = Popen(cmd, shell=False, stdout=PIPE, stdin=PIPE) @@ -36,7 +38,7 @@ def run_cmd(pl, cmd, stdin=None): else: stdout, err = p.communicate(stdin) stdout = stdout.decode(get_preferred_input_encoding()) - return stdout.strip() + return stdout.strip() if strip else stdout def asrun(pl, ascript): diff --git a/powerline/segments/common/players.py b/powerline/segments/common/players.py index 47dc8f0b..1dedebba 100644 --- a/powerline/segments/common/players.py +++ b/powerline/segments/common/players.py @@ -160,7 +160,12 @@ class MpdPlayerSegment(PlayerSegment): try: import mpd except ImportError: - now_playing = run_cmd(pl, ['mpc', 'current', '-f', '%album%\n%artist%\n%title%\n%time%', '-h', str(host), '-p', str(port)]) + now_playing = run_cmd(pl, [ + 'mpc', 'current', + '-f', '%album%\n%artist%\n%title%\n%time%', + '-h', str(host), + '-p', str(port) + ], strip=False) if not now_playing: return now_playing = now_playing.split('\n') @@ -372,7 +377,7 @@ class RhythmboxPlayerSegment(PlayerSegment): 'rhythmbox-client', '--no-start', '--no-present', '--print-playing-format', '%at\n%aa\n%tt\n%te\n%td' - ]) + ], strip=False) if not now_playing: return now_playing = now_playing.split('\n') From cdb030ec03a6a5d37eae01b17aa0f61729ce7305 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Oct 2014 15:05:28 +0400 Subject: [PATCH 16/21] Rename highlight groups --- powerline/config_files/colorschemes/default.json | 3 ++- powerline/segments/common/players.py | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/powerline/config_files/colorschemes/default.json b/powerline/config_files/colorschemes/default.json index 7f234506..7bf84501 100644 --- a/powerline/config_files/colorschemes/default.json +++ b/powerline/config_files/colorschemes/default.json @@ -28,7 +28,8 @@ "battery_gradient": { "fg": "white_red", "bg": "gray0", "attr": [] }, "battery_full": { "fg": "red", "bg": "gray0", "attr": [] }, "battery_empty": { "fg": "white", "bg": "gray0", "attr": [] }, - "now_playing": { "fg": "gray10", "bg": "black", "attr": [] }, + "now_playing": "player", + "player": { "fg": "gray10", "bg": "black", "attr": [] }, "user": { "fg": "white", "bg": "darkblue", "attr": ["bold"] }, "superuser": { "fg": "white", "bg": "brightred", "attr": ["bold"] }, "branch": { "fg": "gray9", "bg": "gray2", "attr": [] }, diff --git a/powerline/segments/common/players.py b/powerline/segments/common/players.py index 1dedebba..57e5e403 100644 --- a/powerline/segments/common/players.py +++ b/powerline/segments/common/players.py @@ -48,7 +48,10 @@ class PlayerSegment(Segment): return None stats.update(func_stats) stats['state_symbol'] = state_symbols.get(stats['state']) - return format.format(**stats) + return [{ + 'contents': format.format(**stats), + 'highlight_group': ['player_' + (stats['state'] or 'fallback'), 'player', 'now_playing'], + }] def argspecobjs(self): for ret in super(PlayerSegment, self).argspecobjs(): @@ -71,6 +74,8 @@ This player segment should be added like this: (with additional ``"args": {{…}}`` if needed). +Highlight groups used: ``player_fallback`` or ``player``, ``player_play`` or ``player``, ``player_pause`` or ``player``, ``player_stop`` or ``player``. + :param str format: Format used for displaying data from player. Should be a str.format-like string with the following keyword parameters: From dad11972841c50800d21292f5582e121faf294b5 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Oct 2014 15:09:57 +0400 Subject: [PATCH 17/21] Add `"player"` segment data --- powerline/config_files/themes/ascii.json | 10 ++++++++++ powerline/config_files/themes/powerline.json | 10 ++++++++++ powerline/config_files/themes/unicode.json | 10 ++++++++++ powerline/config_files/themes/unicode_terminus.json | 10 ++++++++++ .../themes/unicode_terminus_condensed.json | 10 ++++++++++ 5 files changed, 50 insertions(+) diff --git a/powerline/config_files/themes/ascii.json b/powerline/config_files/themes/ascii.json index 7b49da21..80b2fcb7 100644 --- a/powerline/config_files/themes/ascii.json +++ b/powerline/config_files/themes/ascii.json @@ -20,6 +20,16 @@ "ellipsis": "..." } }, + "player": { + "args": { + "state_symbols": { + "fallback": "", + "play": ">", + "pause": "~", + "stop": "X" + } + } + }, "line_current_symbol": { "contents": "LN " diff --git a/powerline/config_files/themes/powerline.json b/powerline/config_files/themes/powerline.json index bba3b541..ddc6b60c 100644 --- a/powerline/config_files/themes/powerline.json +++ b/powerline/config_files/themes/powerline.json @@ -23,6 +23,16 @@ "line_current_symbol": { "contents": " " }, + "player": { + "args": { + "state_symbols": { + "fallback": "♫", + "play": "▶", + "pause": "▮▮", + "stop": "■" + } + } + }, "time": { "before": "⌚ " diff --git a/powerline/config_files/themes/unicode.json b/powerline/config_files/themes/unicode.json index de74c23b..d1461ee0 100644 --- a/powerline/config_files/themes/unicode.json +++ b/powerline/config_files/themes/unicode.json @@ -19,6 +19,16 @@ "ellipsis": "⋯" } }, + "player": { + "args": { + "state_symbols": { + "fallback": "♫", + "play": "▶", + "pause": "▮▮", + "stop": "■" + } + } + }, "line_current_symbol": { "contents": "␤ " diff --git a/powerline/config_files/themes/unicode_terminus.json b/powerline/config_files/themes/unicode_terminus.json index 33a06395..d4df1fdd 100644 --- a/powerline/config_files/themes/unicode_terminus.json +++ b/powerline/config_files/themes/unicode_terminus.json @@ -23,6 +23,16 @@ "line_current_symbol": { "contents": "␤ " }, + "player": { + "args": { + "state_symbols": { + "fallback": "♫", + "play": "▶", + "pause": "▮▮", + "stop": "■" + } + } + }, "time": { "before": "" diff --git a/powerline/config_files/themes/unicode_terminus_condensed.json b/powerline/config_files/themes/unicode_terminus_condensed.json index a386c7cd..e153a93c 100644 --- a/powerline/config_files/themes/unicode_terminus_condensed.json +++ b/powerline/config_files/themes/unicode_terminus_condensed.json @@ -24,6 +24,16 @@ "line_current_symbol": { "contents": "␤" }, + "player": { + "args": { + "state_symbols": { + "fallback": "♫", + "play": "▶", + "pause": "▮▮", + "stop": "■" + } + } + }, "time": { "before": "" From 8551e51b6d2a9fd268590725807ad67ad2214f22 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Oct 2014 15:16:26 +0400 Subject: [PATCH 18/21] Prioritize now_playing highlight group, but remove it from colorscheme Reasoning for first: backwards compatibility. If user defined `now_playing` highlight group it is what should be used. Reasoning for second: it is useless there. If user has defined its own group its effect will not be affected, if he has not then there is nothing to talk about. --- powerline/config_files/colorschemes/default.json | 1 - powerline/segments/common/players.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/powerline/config_files/colorschemes/default.json b/powerline/config_files/colorschemes/default.json index 7bf84501..2c138bd3 100644 --- a/powerline/config_files/colorschemes/default.json +++ b/powerline/config_files/colorschemes/default.json @@ -28,7 +28,6 @@ "battery_gradient": { "fg": "white_red", "bg": "gray0", "attr": [] }, "battery_full": { "fg": "red", "bg": "gray0", "attr": [] }, "battery_empty": { "fg": "white", "bg": "gray0", "attr": [] }, - "now_playing": "player", "player": { "fg": "gray10", "bg": "black", "attr": [] }, "user": { "fg": "white", "bg": "darkblue", "attr": ["bold"] }, "superuser": { "fg": "white", "bg": "brightred", "attr": ["bold"] }, diff --git a/powerline/segments/common/players.py b/powerline/segments/common/players.py index 57e5e403..45568728 100644 --- a/powerline/segments/common/players.py +++ b/powerline/segments/common/players.py @@ -50,7 +50,7 @@ class PlayerSegment(Segment): stats['state_symbol'] = state_symbols.get(stats['state']) return [{ 'contents': format.format(**stats), - 'highlight_group': ['player_' + (stats['state'] or 'fallback'), 'player', 'now_playing'], + 'highlight_group': ['now_playing', 'player_' + (stats['state'] or 'fallback'), 'player'], }] def argspecobjs(self): From b8a4d9b0540d3efffe207e869a512ea1a73c500f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Oct 2014 16:33:34 +0400 Subject: [PATCH 19/21] Add some workarounds for powerline-lint --- docs/source/develop/segments.rst | 18 ++++++++++++++++++ powerline/lint/__init__.py | 7 ++++++- powerline/lint/checks.py | 17 +++++++++++++++++ powerline/segments/common/players.py | 17 +++++++++++++++++ 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/docs/source/develop/segments.rst b/docs/source/develop/segments.rst index f69a600c..66003a72 100644 --- a/docs/source/develop/segments.rst +++ b/docs/source/develop/segments.rst @@ -29,6 +29,24 @@ object it should receive the following arguments: And also any other argument(s) specified by user in :ref:`args key ` (no additional arguments by default). +.. note:: + For powerline-lint to work properly the following things may be needed: + + #. If your segment is a :py:class:`powerline.segments.Segment` and used + arguments are scattered over multiple methods + :py:meth:`powerline.segments.Segment.argspecobjs` should be overridden in + subclass to tell powerline-lint which objects should be inspected for + arguments. + #. If your segment takes some arguments that are never listed, but accessed + via ``kwargs.get()`` or you cannot use previous function for whatever + reason :py:meth:`powerline.segments.Segment.additional_args` should be + overridden in subclass. + #. If you are expecting user to use one :ref:`name ` + for multiple segments which cannot be linked to the segment function + automatically by powerline-lint (e.g. because there are no instances of + the segments in question in the default configuration) you should use + :py:func:`powerline.lint.checks.register_common_name`. + Object representing segment may have the following attributes used by powerline: diff --git a/powerline/lint/__init__.py b/powerline/lint/__init__.py index 49cb511a..51c25860 100644 --- a/powerline/lint/__init__.py +++ b/powerline/lint/__init__.py @@ -19,7 +19,7 @@ from powerline.lint.checks import (check_matcher_func, check_ext, check_config, check_segment_module, check_exinclude_function, type_keys, check_segment_function, check_args, get_one_segment_function, check_highlight_groups, check_highlight_group, check_full_segment_data, - get_all_possible_functions, check_segment_data_key) + get_all_possible_functions, check_segment_data_key, register_common_name) from powerline.lint.spec import Spec from powerline.lint.context import Context @@ -289,6 +289,10 @@ theme_spec = common_theme_spec().update( ) +def register_common_names(): + register_common_name('player', 'powerline.segments.common.players', '_player') + + def check(paths=None, debug=False, echoerr=echoerr, require_ext=None): '''Check configuration sanity @@ -308,6 +312,7 @@ def check(paths=None, debug=False, echoerr=echoerr, require_ext=None): ``False`` if user configuration seems to be completely sane and ``True`` if some problems were found. ''' + register_common_names() search_paths = paths or get_config_paths() find_config_files = generate_config_finder(lambda: search_paths) diff --git a/powerline/lint/checks.py b/powerline/lint/checks.py index a59d4440..52abdb78 100644 --- a/powerline/lint/checks.py +++ b/powerline/lint/checks.py @@ -5,6 +5,8 @@ import os import re import logging +from collections import defaultdict + from powerline.lib.threaded import ThreadedSegment from powerline.lib.unicode import unicode from powerline.lint.markedjson.markedvalue import MarkedUnicode @@ -673,6 +675,16 @@ def get_one_segment_function(data, context, echoerr): yield func +common_names = defaultdict(set) + + +def register_common_name(name, cmodule, cname): + s = cmodule + '.' + cname + cmodule_mark = Mark('', 1, 1, s, 1) + cname_mark = Mark('', 1, len(cmodule) + 1, s, len(cmodule) + 1) + common_names[name].add((MarkedUnicode(cmodule, cmodule_mark), MarkedUnicode(cname, cname_mark))) + + def get_all_possible_functions(data, context, echoerr): name = context[-2][0] module, name = name.rpartition('.')[::2] @@ -681,6 +693,11 @@ def get_all_possible_functions(data, context, echoerr): if func: yield func else: + if name in common_names: + for cmodule, cname in common_names[name]: + cfunc = import_segment(cname, data, context, echoerr, module=MarkedUnicode(cmodule, None)) + if cfunc: + yield cfunc for ext, theme_config in list_themes(data, context): for segments in theme_config.get('segments', {}).values(): for segment in segments: diff --git a/powerline/segments/common/players.py b/powerline/segments/common/players.py index 45568728..838ff1c7 100644 --- a/powerline/segments/common/players.py +++ b/powerline/segments/common/players.py @@ -53,6 +53,9 @@ class PlayerSegment(Segment): 'highlight_group': ['now_playing', 'player_' + (stats['state'] or 'fallback'), 'player'], }] + def get_player_status(self, pl): + pass + def argspecobjs(self): for ret in super(PlayerSegment, self).argspecobjs(): yield ret @@ -112,6 +115,9 @@ Highlight groups used: ``player_fallback`` or ``player``, ``player_play`` or ``p ''' +_player = with_docstring(PlayerSegment(), _common_args.format('_player')) + + class CmusPlayerSegment(PlayerSegment): def get_player_status(self, pl): '''Return cmus player information. @@ -459,5 +465,16 @@ class NowPlayingSegment(Segment): assert(isinstance(player_segment, PlayerSegment)) return player_segment(**kwargs) + def argspecobjs(self): + for ret in super(NowPlayingSegment, self).argspecobjs(): + yield ret + yield '__call__', PlayerSegment.__call__ + for k, v in globals().items(): + if isinstance(v, type) and issubclass(v, PlayerSegment) and v is not DbusPlayerSegment: + yield 'get_player_status', v.get_player_status + + def omitted_args(self, name, method): + return (0,) + now_playing = NowPlayingSegment() From 425e8f744de61451d40779324e640d500e0ebd40 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Oct 2014 16:53:53 +0400 Subject: [PATCH 20/21] Remove self.STATE_SYMBOLS reference: there is no such attribute --- powerline/segments/common/players.py | 1 - 1 file changed, 1 deletion(-) diff --git a/powerline/segments/common/players.py b/powerline/segments/common/players.py index 838ff1c7..a53d36d9 100644 --- a/powerline/segments/common/players.py +++ b/powerline/segments/common/players.py @@ -446,7 +446,6 @@ class RDIOPlayerSegment(PlayerSegment): 'elapsed': elapsed, 'total': total, 'state': state, - 'state_symbol': self.STATE_SYMBOLS.get(state) } From 266649143829f0a60982ebd2bc10cb8828a80e43 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Oct 2014 17:03:34 +0400 Subject: [PATCH 21/21] Document pyuv problem in troubleshooting section Closes #5463 --- docs/source/troubleshooting/osx.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/source/troubleshooting/osx.rst b/docs/source/troubleshooting/osx.rst index 4be81490..3d8363d8 100644 --- a/docs/source/troubleshooting/osx.rst +++ b/docs/source/troubleshooting/osx.rst @@ -59,3 +59,12 @@ I receive an ``ImportError`` when trying to use Powerline on OS X! * See `issue #39 `_ for a discussion and other possible solutions for this issue. + +I receive “FSEventStreamStart: register_with_server: ERROR” with status_colors +------------------------------------------------------------------------------ + +This is `a known `_ libuv issue that +happens if one is trying to watch too many files. It should be fixed in +libuv-0.12. Until then it is suggested to either disable ``status_colors`` (from +:py:func:`powerline.segments.common.vcs.branch`) or choose stat-based watcher +(will have effectively the same effect as disabling ``status_colors``).