Release 1.2

Changes:
- When guessing used network interface for internal_ip segment the priority of
  various VM bridge interfaces was lowered.
- Added `ignore_statuses` option that makes one able to prevent certain
  repository statutes (e.g. `"U"`: only modification is unknown files) from
  marking repository as dirty.
- Refactored `powerline.segments.common.players` module: deprecated
  `now_playing` segment, replacing it with segments with names identical to
  player names (e.g. `powerline.segments.common.players.mpd`). `now_playing`
  segment will continue to work until powerline-2.0 though.
- Fixed `powerline.segments.tmux.attached_clients` segment: it was not working
  at all due to missing argument in one function call.
- Fixed regular expression used to detect NERDTree buffers.
This commit is contained in:
ZyX 2014-10-12 17:15:15 +04:00
commit 39dda39bdc
22 changed files with 710 additions and 144 deletions

View File

@ -29,6 +29,24 @@ object it should receive the following arguments:
And also any other argument(s) specified by user in :ref:`args key
<config-themes-seg-args>` (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 <config-themes-seg-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:

View File

@ -59,3 +59,12 @@ I receive an ``ImportError`` when trying to use Powerline on OS X!
* See `issue #39 <https://github.com/Lokaltog/powerline/issues/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 <https://github.com/joyent/node/issues/5463>`_ 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``).

View File

@ -28,7 +28,7 @@
"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": [] },
"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": [] },

View File

@ -20,6 +20,16 @@
"ellipsis": "..."
}
},
"player": {
"args": {
"state_symbols": {
"fallback": "",
"play": ">",
"pause": "~",
"stop": "X"
}
}
},
"line_current_symbol": {
"contents": "LN "

View File

@ -23,6 +23,16 @@
"line_current_symbol": {
"contents": " "
},
"player": {
"args": {
"state_symbols": {
"fallback": "♫",
"play": "▶",
"pause": "▮▮",
"stop": "■"
}
}
},
"time": {
"before": "⌚ "

View File

@ -19,6 +19,16 @@
"ellipsis": "⋯"
}
},
"player": {
"args": {
"state_symbols": {
"fallback": "♫",
"play": "▶",
"pause": "▮▮",
"stop": "■"
}
}
},
"line_current_symbol": {
"contents": "␤ "

View File

@ -23,6 +23,16 @@
"line_current_symbol": {
"contents": "␤ "
},
"player": {
"args": {
"state_symbols": {
"fallback": "♫",
"play": "▶",
"pause": "▮▮",
"stop": "■"
}
}
},
"time": {
"before": ""

View File

@ -24,6 +24,16 @@
"line_current_symbol": {
"contents": "␤"
},
"player": {
"args": {
"state_symbols": {
"fallback": "♫",
"play": "▶",
"pause": "▮▮",
"stop": "■"
}
}
},
"time": {
"before": ""

View File

@ -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):

View File

@ -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)

View File

@ -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('<common name definition>', 1, 1, s, 1)
cname_mark = Mark('<common name definition>', 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:

View File

@ -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))

View File

@ -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):

View File

@ -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|$)')

View File

@ -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,83 @@ 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)
return [{
'contents': format.format(**stats),
'highlight_group': ['now_playing', 'player_' + (stats['state'] or 'fallback'), 'player'],
}]
@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 get_player_status(self, pl):
pass
@staticmethod
def _convert_seconds(seconds):
return '{0:.0f}:{1:02.0f}'.format(*divmod(float(seconds), 60))
def argspecobjs(self):
for ret in super(PlayerSegment, self).argspecobjs():
yield ret
yield 'get_player_status', self.get_player_status
def player_cmus(self, pl):
def omitted_args(self, name, method):
return (0,)
_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).
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:
+------------+-------------------------------------------------------------+
|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.
======== ========================================================
'''
_player = with_docstring(PlayerSegment(), _common_args.format('_player'))
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,21 +146,37 @@ 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:
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')
@ -113,16 +200,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 +240,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 +248,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 +287,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 +329,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 +337,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'
], strip=False)
if not now_playing:
return
now_playing = now_playing.split('\n')
@ -230,7 +400,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 +436,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],
@ -265,6 +446,34 @@ class NowPlayingSegment(Segment):
'elapsed': elapsed,
'total': total,
'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)
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()

View File

@ -2,32 +2,57 @@
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, ignore_statuses=()):
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 = '?'
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,
'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.
: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``.
''')

View File

@ -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)

View File

@ -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,34 @@ 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.
: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``.
Divider highlight group used: ``branch:divider``.
''')
@requires_filesystem_watcher

169
scripts/powerline-release.py Executable file
View File

@ -0,0 +1,169 @@
#!/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
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):
if s == ('+' * len(s)):
try:
last_version = next(iter(sorted([
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')
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('.')))
def merge(version_string, rev, **kwargs):
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', version_string])
def push(version_string, **kwargs):
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'])
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)
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'),
('app-vim', 'powerline-vim'),
):
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)
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)
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, 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=version_string, user=user, password=password, **kwargs)
p = argparse.ArgumentParser(description='Powerline release script')
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(
version=args.version,
rev=args.rev,
user=args.user,
password=args.password,
overlay=args.overlay,
run_stages=args.stages,
)

View File

@ -54,7 +54,7 @@ else:
setup(
name='powerline-status',
version='1.1',
version='1.2',
description='The ultimate statusline/prompt utility.',
long_description=README,
classifiers=[

View File

@ -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':
@ -600,25 +600,57 @@ 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)
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):
@ -1057,22 +1089,36 @@ 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'}
])
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()
@ -1155,6 +1201,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):

View File

@ -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}" \