From 9fcf8fa0c1aa65fb62c56ddf60a1d989a659aecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Fern=C3=A1ndez?= Date: Wed, 4 Nov 2015 12:19:28 +0100 Subject: [PATCH 01/46] Including `dist` folder to MANIFEST.in --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index e3e42f79..14acc932 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ recursive-include powerline *.json *.vim recursive-include powerline/bindings *.* recursive-exclude powerline/bindings *.pyc *.pyo +recursive-include powerline/dist *.* recursive-include client *.* recursive-include docs/source *.rst *.py include docs/Makefile From 2063411d34ab42767649b07e9267d56acac880d4 Mon Sep 17 00:00:00 2001 From: Michele Sorcinelli Date: Sun, 8 Nov 2015 12:40:43 +0100 Subject: [PATCH 02/46] Fix #1483 -- [Battery - Linux] Fix AC status fetch Fix a bug where the `online` property is checked inside battery instead of AC. As specified in issue #1483, `online` is found only inside `/sys/class/power_supply/AC/` directories, and equals `1` when cable is plugged, and `0` when unplugged. Edit: updated with --amend according to ZyX-I hints in order to work with powerline daemon. --- powerline/segments/common/bat.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/powerline/segments/common/bat.py b/powerline/segments/common/bat.py index 26e8b0dc..0411fcd3 100644 --- a/powerline/segments/common/bat.py +++ b/powerline/segments/common/bat.py @@ -62,19 +62,25 @@ def _fetch_battery_info(pl): pl.debug('Not using DBUS+UPower as no batteries were found') if os.path.isdir('/sys/class/power_supply'): - linux_bat_fmt = '/sys/class/power_supply/{0}/capacity' + online_path_verified = None + linux_supplier_fmt = '/sys/class/power_supply/{0}/capacity' linux_ac_fmt = '/sys/class/power_supply/{0}/online' - for linux_bat in os.listdir('/sys/class/power_supply'): - cap_path = linux_bat_fmt.format(linux_bat) - online_path = linux_ac_fmt.format(linux_bat) - if linux_bat.startswith('BAT') and os.path.exists(cap_path): - pl.debug('Using /sys/class/power_supply with battery {0}', linux_bat) + for linux_supplier in os.listdir('/sys/class/power_supply'): + cap_path = linux_supplier_fmt.format(linux_supplier) + online_path = linux_ac_fmt.format(linux_supplier) + if linux_supplier.startswith('AC') and os.path.exists(online_path): + pl.debug('Using /sys/class/power_supply with AC {0}', online_path) + online_path_verified = online_path + elif linux_supplier.startswith('BAT') and os.path.exists(cap_path): + pl.debug('Using /sys/class/power_supply with battery {0}', linux_supplier) def _get_battery_status(pl): + _ac_powered = None with open(cap_path, 'r') as f: _capacity = int(float(f.readline().split()[0])) - with open(online_path, 'r') as f: - _ac_powered = f.readline() == 1 + if online_path_verified: + with open(online_path_verified, 'r') as f: + _ac_powered = int(f.readline()) == 1 return _capacity, _ac_powered return _get_battery_status pl.debug('Not using /sys/class/power_supply as no batteries were found') From 6efabc170df106d43041e3ab8d264809397b2dfb Mon Sep 17 00:00:00 2001 From: John Drouhard Date: Sat, 14 Nov 2015 12:38:07 -0600 Subject: [PATCH 03/46] Fix performance issue with tabline and showtabline=2 vim_getbufoption(segment, 'buflisted') was causing vim to update the tabline for every keystroke. using vim.eval('buflisted(nr)') allows vim to optimize when it needs to update Fixes #1281 --- powerline/listers/vim.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/powerline/listers/vim.py b/powerline/listers/vim.py index c1995957..9a8b4a8e 100644 --- a/powerline/listers/vim.py +++ b/powerline/listers/vim.py @@ -89,22 +89,13 @@ def bufferlister(pl, segment_info, show_unlisted=False, **kwargs): return dct return ( - ( - buf_segment_info, - add_multiplier(buf_segment_info['buffer'], {'highlight_group_prefix': prefix}) - ) - for buf_segment_info, prefix in ( - ( - buffer_updated_segment_info( - segment_info, - buffer - ), - ('buf' if buffer is cur_buffer else 'buf_nc') - ) - for buffer in vim.buffers - ) if ( - buf_segment_info['buffer'] is cur_buffer - or show_unlisted - or int(vim_getbufoption(buf_segment_info, 'buflisted')) + (lambda buffer, prefix: ( + buffer_updated_segment_info(segment_info, buffer), + add_multiplier(buffer, {'highlight_group_prefix': prefix} + ))(buffer, 'buf' if buffer is cur_buffer else 'buf_nc') + for buffer in vim.buffers if ( + buffer is cur_buffer + or show_unlisted + or int(vim.eval('buflisted(%s)' % buffer.number)) > 0 ) ) From 63214c8a798bb6f17510335dc8cb59acedc59085 Mon Sep 17 00:00:00 2001 From: John Drouhard Date: Sat, 14 Nov 2015 12:45:13 -0600 Subject: [PATCH 04/46] Update highlight groups for tabline This changes the buffer highlight groups from buf and buf_nc to buf, buf_nc, buf_mod, and buf_nc_mod. Doing this allows a higher level of configurability for the highlight groups used in the buffer-only tabline. --- .../colorschemes/vim/__main__.json | 13 +++++++++-- .../config_files/themes/vim/tabline.json | 2 +- powerline/listers/vim.py | 23 ++++++++++++++----- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/powerline/config_files/colorschemes/vim/__main__.json b/powerline/config_files/colorschemes/vim/__main__.json index 0d460b59..1ce2e7b3 100644 --- a/powerline/config_files/colorschemes/vim/__main__.json +++ b/powerline/config_files/colorschemes/vim/__main__.json @@ -23,16 +23,25 @@ "csv:column_number": "line_current", "csv:column_name": "line_current_symbol", + "tab:background": "background", + "tab:divider": "background:divider", + "tab_nc:modified_indicator": "modified_indicator", "tab_nc:file_directory": "information:unimportant", "tab_nc:file_name": "tab_nc:file_directory", "tab_nc:tabnr": "tab_nc:file_directory", "buf_nc:file_directory": "tab_nc:file_directory", - "buf_nc:file_name": "tab_nc:file_name", - "buf_nc:bufnr": "tab_nc:tabnr", + "buf_nc:file_name": "buf_nc:file_directory", + "buf_nc:bufnr": "buf_nc:file_directory", "buf_nc:modified_indicator": "tab_nc:modified_indicator", + "buf_nc_mod:file_directory": "tab_nc:file_directory", + "buf_nc_mod:file_name": "buf_nc_mod:file_directory", + "buf_nc_mod:bufnr": "buf_nc_mod:file_directory", + "buf_nc_mod:modified_indicator": "tab_nc:modified_indicator", + + "commandt:label": "file_name", "commandt:background": "background", "commandt:finder": "file_name", diff --git a/powerline/config_files/themes/vim/tabline.json b/powerline/config_files/themes/vim/tabline.json index 48bae6b8..1e3130ec 100644 --- a/powerline/config_files/themes/vim/tabline.json +++ b/powerline/config_files/themes/vim/tabline.json @@ -67,7 +67,7 @@ }, { "type": "string", - "highlight_groups": ["background"], + "highlight_groups": ["tab:background"], "draw_soft_divider": false, "draw_hard_divider": false, "width": "auto" diff --git a/powerline/listers/vim.py b/powerline/listers/vim.py index 9a8b4a8e..50aea25b 100644 --- a/powerline/listers/vim.py +++ b/powerline/listers/vim.py @@ -2,7 +2,7 @@ from __future__ import (unicode_literals, division, absolute_import, print_function) from powerline.theme import requires_segment_info -from powerline.bindings.vim import (current_tabpage, list_tabpages, vim_getbufoption) +from powerline.bindings.vim import (current_tabpage, list_tabpages) try: import vim @@ -49,7 +49,10 @@ def tablister(pl, segment_info, **kwargs): return ( (lambda tabpage, prefix: ( tabpage_updated_segment_info(segment_info, tabpage), - add_multiplier(tabpage, {'highlight_group_prefix': prefix}) + add_multiplier(tabpage, { + 'highlight_group_prefix': prefix, + 'divider_highlight_group': 'tab:divider' + }) ))(tabpage, 'tab' if tabpage == cur_tabpage else 'tab_nc') for tabpage in list_tabpages() ) @@ -75,7 +78,8 @@ def bufferlister(pl, segment_info, show_unlisted=False, **kwargs): and ``bufnr`` keys set to buffer-specific ones, ``window``, ``winnr`` and ``window_id`` keys set to None. - Adds either ``buf:`` or ``buf_nc:`` prefix to all segment highlight groups. + Adds one of ``buf:``, ``buf_nc:``, ``buf_mod:``, or ``buf_nc_mod`` + prefix to all segment highlight groups. :param bool show_unlisted: True if unlisted buffers should be shown as well. Current buffer is @@ -89,10 +93,17 @@ def bufferlister(pl, segment_info, show_unlisted=False, **kwargs): return dct return ( - (lambda buffer, prefix: ( + (lambda buffer, current, modified: ( buffer_updated_segment_info(segment_info, buffer), - add_multiplier(buffer, {'highlight_group_prefix': prefix} - ))(buffer, 'buf' if buffer is cur_buffer else 'buf_nc') + add_multiplier(buffer, { + 'highlight_group_prefix': '%s%s' % (current, modified), + 'divider_highlight_group': 'tab:divider' + }) + ))( + buffer, + 'buf' if buffer is cur_buffer else 'buf_nc', + '_mod' if int(vim.eval('getbufvar(%s, \'&mod\')' % buffer.number)) > 0 else '' + ) for buffer in vim.buffers if ( buffer is cur_buffer or show_unlisted From a85b54d9236211e288bc739d570696a64cfa2080 Mon Sep 17 00:00:00 2001 From: John Drouhard Date: Sat, 14 Nov 2015 14:40:51 -0600 Subject: [PATCH 05/46] Update vim test module to implement 'getbufvar(...)' This allows the tests for the buffer tablist to pass, since it now uses getbufvar(nr, '&modified') to detect whether the buffer needs to be modified --- powerline/listers/vim.py | 2 +- tests/vim.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/powerline/listers/vim.py b/powerline/listers/vim.py index 50aea25b..abd7928d 100644 --- a/powerline/listers/vim.py +++ b/powerline/listers/vim.py @@ -102,7 +102,7 @@ def bufferlister(pl, segment_info, show_unlisted=False, **kwargs): ))( buffer, 'buf' if buffer is cur_buffer else 'buf_nc', - '_mod' if int(vim.eval('getbufvar(%s, \'&mod\')' % buffer.number)) > 0 else '' + '_mod' if int(vim.eval('getbufvar(%s, \'&modified\')' % buffer.number)) > 0 else '' ) for buffer in vim.buffers if ( buffer is cur_buffer diff --git a/tests/vim.py b/tests/vim.py index 142d6929..3f6882c8 100644 --- a/tests/vim.py +++ b/tests/vim.py @@ -265,6 +265,14 @@ def eval(expr): import os assert os.path.basename(current.buffer.name).startswith('NERD_tree_') return '/usr/include' + elif expr.startswith('getbufvar('): + import re + match = re.match(r'^getbufvar\((\d+), ["\'](.+)["\']\)$', expr) + if not match: + raise NotImplementedError(expr) + bufnr = int(match.group(1)) + varname = match.group(2) + return _emul_getbufvar(bufnr, varname) elif expr == 'tabpagenr()': return current.tabpage.number elif expr == 'tabpagenr("$")': From 3f400f7d1a2d1bd955e1ad0779f85bb1a87a0161 Mon Sep 17 00:00:00 2001 From: John Drouhard Date: Sat, 14 Nov 2015 19:11:20 -0600 Subject: [PATCH 06/46] update test_tabline.vim with new tab:divider highlight group --- tests/test_tabline.vim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_tabline.vim b/tests/test_tabline.vim index ded985ba..a2e1374f 100755 --- a/tests/test_tabline.vim +++ b/tests/test_tabline.vim @@ -16,7 +16,7 @@ catch cquit endtry -if result isnot# '%1T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc  %2T2 ./def %#Pl_236_3158064_240_5789784_NONE# %3T%#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %T%#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Tabs ' +if result isnot# '%1T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc %#Pl_244_8421504_236_3158064_NONE# %2T%#Pl_247_10395294_236_3158064_NONE#2 ./def %#Pl_236_3158064_240_5789784_NONE# %3T%#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %T%#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Tabs ' call writefile(['Unexpected tabline', result], 'message.fail') cquit endif @@ -30,7 +30,7 @@ catch cquit endtry -if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc  2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs ' +if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc %#Pl_244_8421504_236_3158064_NONE# %#Pl_247_10395294_236_3158064_NONE#2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs ' call writefile(['Unexpected tabline (2)', result], 'message.fail') cquit endif @@ -42,7 +42,7 @@ catch call writefile(['Exception while evaluating &tabline (3)', v:exception], 'message.fail') endtry -if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc  2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs ' +if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc %#Pl_244_8421504_236_3158064_NONE# %#Pl_247_10395294_236_3158064_NONE#2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs ' call writefile(['Unexpected tabline (3)', result], 'message.fail') cquit endif From 31210242ef2369b8f1ec35fe36f36af7ebb51e37 Mon Sep 17 00:00:00 2001 From: John Drouhard Date: Sat, 14 Nov 2015 20:16:22 -0600 Subject: [PATCH 07/46] use str.format() instead of % operator; add comment to explain the usage of vim.eval('buflisted..') --- powerline/listers/vim.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/powerline/listers/vim.py b/powerline/listers/vim.py index abd7928d..4e112142 100644 --- a/powerline/listers/vim.py +++ b/powerline/listers/vim.py @@ -96,17 +96,28 @@ def bufferlister(pl, segment_info, show_unlisted=False, **kwargs): (lambda buffer, current, modified: ( buffer_updated_segment_info(segment_info, buffer), add_multiplier(buffer, { - 'highlight_group_prefix': '%s%s' % (current, modified), + 'highlight_group_prefix': '{0}{1}'.format(current, modified), 'divider_highlight_group': 'tab:divider' }) ))( buffer, 'buf' if buffer is cur_buffer else 'buf_nc', - '_mod' if int(vim.eval('getbufvar(%s, \'&modified\')' % buffer.number)) > 0 else '' + '_mod' if int(vim.eval('getbufvar({0}, \'&modified\')'.format(buffer.number))) > 0 else '' ) for buffer in vim.buffers if ( buffer is cur_buffer or show_unlisted + # We can't use vim_getbufoption(segment_info, 'buflisted') + # here for performance reasons. Querying the buffer options + # through the vim python module's option attribute caused + # vim to think it needed to update the tabline for every + # keystroke after any event that changed the buffer's + # options. + # + # Using the vim module's eval method to directly use the + # buflisted(nr) vim method instead does not cause vim to + # update the tabline after every keystroke, but rather after + # events that would change that status. Fixes #1281 or int(vim.eval('buflisted(%s)' % buffer.number)) > 0 ) ) From 48469c02ef1303e54d29d577b187a1bc3cdf3dd0 Mon Sep 17 00:00:00 2001 From: Foo Date: Sun, 22 Nov 2015 05:26:34 +0300 Subject: [PATCH 08/46] Enable python-3.5 build --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 01429651..9f4747c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,7 @@ matrix: - python: "3.2" - python: "3.3" - python: "3.4" + - python: "3.5" - python: "pypy" - python: "pypy3" - python: "2.6" From 3f59edc060dd82c03b2e4493c2b7b79d6b44f51c Mon Sep 17 00:00:00 2001 From: Foo Date: Sun, 22 Nov 2015 04:31:12 +0300 Subject: [PATCH 09/46] Add more logging options --- docs/source/configuration/local.rst | 13 + docs/source/configuration/reference.rst | 28 +- powerline/__init__.py | 174 +++++++-- powerline/bindings/config.py | 6 +- powerline/lint/__init__.py | 44 ++- powerline/lint/checks.py | 64 ++++ powerline/lint/spec.py | 3 +- powerline/listers/vim.py | 2 +- powerline/segments/vim/__init__.py | 2 +- powerline/vim.py | 124 ++++--- tests/test_logging.py | 467 ++++++++++++++++++++++++ 11 files changed, 808 insertions(+), 119 deletions(-) create mode 100644 tests/test_logging.py diff --git a/docs/source/configuration/local.rst b/docs/source/configuration/local.rst index 5ea233f4..0f3d1104 100644 --- a/docs/source/configuration/local.rst +++ b/docs/source/configuration/local.rst @@ -12,6 +12,8 @@ Vim overrides Vim configuration can be overridden using the following options: +.. _local-configuration-overrides-vim-config: + ``g:powerline_config_overrides`` Dictionary, recursively merged with contents of :file:`powerline/config.json`. @@ -37,6 +39,17 @@ Vim configuration can be overridden using the following options: was configured in :ref:`log_* options `. Level is always :ref:`log_level `, same for format. + .. warning:: + This variable is deprecated. Use :ref:`log_file option + ` in conjunction with + :py:class:`powerline.vim.VimVarHandler` class and :ref:`Vim config + overrides variable `. Using + this is also the only variant to make saving into the environment + variable the *only* place where log is saved or save into different + variable. + + .. autoclass:: powerline.vim.VimVarHandler + .. _local-configuration-overrides-script: Powerline script overrides diff --git a/docs/source/configuration/reference.rst b/docs/source/configuration/reference.rst index 55841415..e32ee06b 100644 --- a/docs/source/configuration/reference.rst +++ b/docs/source/configuration/reference.rst @@ -94,14 +94,38 @@ Common configuration is a subdictionary that is a value of ``common`` key in .. _config-common-log: ``log_file`` - Defines path which will hold powerline logs. If not present, logging will be - done to stderr. + Defines how logs will be handled. There are three variants here: + + #. Absent. In this case logging will be done to stderr: equivalent to + ``[["logging.StreamHandler", []]]`` or ``[null]``. + #. Plain string. In this case logging will be done to the given file: + ``"/file/name"`` is equivalent to ``[["logging.FileHandler", + [["/file/name"]]]]`` or ``["/file/name"]``. Leading ``~/`` is expanded in + the file name, so using ``"~/.log/foo"`` is permitted. If directory + pointed by the option is absent, it will be created, but not its parent. + #. List of handler definitions. Handler definition may either be ``null``, + a string or a list with two or three elements: + + #. Logging class name and module. If module name is absent, it is + equivalent to ``logging.handlers``. + #. Class constructor arguments in a form ``[[args[, kwargs]]]``: accepted + variants are ``[]`` (no arguments), ``[args]`` (e.g. + ``[["/file/name"]]``: only positional arguments) or ``[args, kwargs]`` + (e.g. ``[[], {"host": "localhost", "port": 6666}]``: positional and + keyword arguments, but no positional arguments in the example). + #. Optional logging level. Overrides :ref:`log_level key + ` and has the same format. + #. Optional format string. Partially overrides :ref:`log_format key + ` and has the same format. “Partially” here + means that it may only specify more critical level. .. _config-common-log_level: ``log_level`` String, determines logging level. Defaults to ``WARNING``. +.. _config-common-log_format: + ``log_format`` String, determines format of the log messages. Defaults to ``'%(asctime)s:%(level)s:%(message)s'``. diff --git a/powerline/__init__.py b/powerline/__init__.py index 9d2bb689..cd9e47d9 100644 --- a/powerline/__init__.py +++ b/powerline/__init__.py @@ -9,7 +9,7 @@ from threading import Lock, Event from powerline.colorscheme import Colorscheme from powerline.lib.config import ConfigLoader -from powerline.lib.unicode import safe_unicode, FailedUnicode +from powerline.lib.unicode import unicode, safe_unicode, FailedUnicode from powerline.config import DEFAULT_SYSTEM_CONFIG_DIR from powerline.lib.dict import mergedicts from powerline.lib.encoding import get_preferred_output_encoding @@ -121,7 +121,7 @@ def get_fallback_logger(stream=None): handler.setLevel(level) handler.setFormatter(formatter) - logger = logging.getLogger('powerline') + logger = logging.Logger('powerline') logger.setLevel(level) logger.addHandler(handler) _fallback_logger = PowerlineLogger(None, logger, '_fallback_') @@ -200,40 +200,102 @@ def load_config(cfg_path, find_config_files, config_loader, loader_callback=None return ret -def _get_log_handler(common_config, stream=None): - '''Get log handler. +def _set_log_handlers(common_config, logger, get_module_attr, stream=None): + '''Set log handlers :param dict common_config: Configuration dictionary used to create handler. - - :return: logging.Handler subclass. + :param logging.Logger logger: + Logger to which handlers will be attached. + :param func get_module_attr: + :py:func:`gen_module_attr_getter` output. + :param file stream: + Stream to use by default for :py:class:`logging.StreamHandler` in place + of :py:attr:`sys.stderr`. May be ``None``. ''' - log_file = common_config['log_file'] - if log_file: - log_file = os.path.expanduser(log_file) - log_dir = os.path.dirname(log_file) - if not os.path.isdir(log_dir): - os.mkdir(log_dir) - return logging.FileHandler(log_file) - else: - return logging.StreamHandler(stream) + log_targets = common_config['log_file'] + num_handlers = 0 + for log_target in log_targets: + if log_target is None: + log_target = ['logging.StreamHandler', []] + elif isinstance(log_target, unicode): + log_target = os.path.expanduser(log_target) + log_dir = os.path.dirname(log_target) + if log_dir and not os.path.isdir(log_dir): + os.mkdir(log_dir) + log_target = ['logging.FileHandler', [[log_target]]] + module, handler_class_name = log_target[0].rpartition('.')[::2] + module = module or 'logging.handlers' + try: + handler_class_args = log_target[1][0] + except IndexError: + if module == 'logging' and handler_class_name == 'StreamHandler': + handler_class_args = [stream] + else: + handler_class_args = () + try: + handler_class_kwargs = log_target[1][1] + except IndexError: + handler_class_kwargs = {} + module = str(module) + handler_class_name = str(handler_class_name) + handler_class = get_module_attr(module, handler_class_name) + if not handler_class: + continue + handler = handler_class(*handler_class_args, **handler_class_kwargs) + try: + handler_level_name = log_target[2] + except IndexError: + handler_level_name = common_config['log_level'] + try: + handler_format = log_target[3] + except IndexError: + handler_format = common_config['log_format'] + handler.setLevel(getattr(logging, handler_level_name)) + handler.setFormatter(logging.Formatter(handler_format)) + logger.addHandler(handler) + num_handlers += 1 + if num_handlers == 0 and log_targets: + raise ValueError('Failed to set up any handlers') -def create_logger(common_config, stream=None): +def create_logger(common_config, use_daemon_threads=True, ext='__unknown__', + import_paths=None, imported_modules=None, stream=None): '''Create logger according to provided configuration + + :param dict common_config: + Common configuration, from :py:func:`finish_common_config`. + :param bool use_daemon_threads: + Whether daemon threads should be used. Argument to + :py:class:`PowerlineLogger` constructor. + :param str ext: + Used extension. Argument to :py:class:`PowerlineLogger` constructor. + :param set imported_modules: + Set where imported modules are saved. Argument to + :py:func:`gen_module_attr_getter`. May be ``None``, in this case new + empty set is used. + :param file stream: + Stream to use by default for :py:class:`logging.StreamHandler` in place + of :py:attr:`sys.stderr`. May be ``None``. + + :return: Three objects: + + #. :py:class:`logging.Logger` instance. + #. :py:class:`PowerlineLogger` instance. + #. Function, output of :py:func:`gen_module_attr_getter`. ''' - log_format = common_config['log_format'] - formatter = logging.Formatter(log_format) - + logger = logging.Logger('powerline') level = getattr(logging, common_config['log_level']) - handler = _get_log_handler(common_config, stream) - handler.setLevel(level) - handler.setFormatter(formatter) - - logger = logging.getLogger('powerline') logger.setLevel(level) - logger.addHandler(handler) - return logger + + pl = PowerlineLogger(use_daemon_threads, logger, ext) + get_module_attr = gen_module_attr_getter( + pl, common_config['paths'], + set() if imported_modules is None else imported_modules) + + _set_log_handlers(common_config, logger, get_module_attr, stream) + + return logger, pl, get_module_attr def finish_common_config(encoding, common_config): @@ -264,7 +326,10 @@ def finish_common_config(encoding, common_config): common_config.setdefault('additional_escapes', None) common_config.setdefault('reload_config', True) common_config.setdefault('interval', None) - common_config.setdefault('log_file', None) + common_config.setdefault('log_file', [None]) + + if not isinstance(common_config['log_file'], list): + common_config['log_file'] = [common_config['log_file']] common_config['paths'] = [ os.path.expanduser(path) for path in common_config['paths'] @@ -324,6 +389,26 @@ def gen_module_attr_getter(pl, import_paths, imported_modules): return get_module_attr +LOG_KEYS = set(('log_format', 'log_level', 'log_file', 'paths')) +'''List of keys related to logging +''' + + +def _get_log_keys(common_config): + '''Return a common configuration copy with only log-related config left + + :param dict common_config: + Common configuration. + + :return: + :py:class:`dict` instance which has only keys from + :py:attr:`powerline.LOG_KEYS` left. + ''' + return dict(( + (k, v) for k, v in common_config.items() if k in LOG_KEYS + )) + + class Powerline(object): '''Main powerline class, entrance point for all powerline uses. Sets powerline up and loads the configuration. @@ -380,6 +465,7 @@ class Powerline(object): self.ext = ext self.run_once = run_once self.logger = logger + self.had_logger = bool(self.logger) self.use_daemon_threads = use_daemon_threads if not renderer_module: @@ -430,8 +516,20 @@ class Powerline(object): This function is used to create logger unless it was already specified at initialization. + + :return: Three objects: + + #. :py:class:`logging.Logger` instance. + #. :py:class:`PowerlineLogger` instance. + #. Function, output of :py:func:`gen_module_attr_getter`. ''' - return create_logger(self.common_config, self.default_log_stream) + return create_logger( + common_config=self.common_config, + use_daemon_threads=self.use_daemon_threads, + ext=self.ext, + imported_modules=self.imported_modules, + stream=self.default_log_stream, + ) def create_renderer(self, load_main=False, load_colors=False, load_colorscheme=False, load_theme=False): '''(Re)create renderer object. Can be used after Powerline object was @@ -465,22 +563,24 @@ class Powerline(object): or not self.prev_common_config or self.prev_common_config['default_top_theme'] != self.common_config['default_top_theme']) + log_keys_differ = (not self.prev_common_config or ( + _get_log_keys(self.prev_common_config) != _get_log_keys(self.common_config) + )) + self.prev_common_config = self.common_config - self.import_paths = self.common_config['paths'] - - if not self.logger: - self.logger = self.create_logger() - - if not self.pl: - self.pl = PowerlineLogger(self.use_daemon_threads, self.logger, self.ext) + if log_keys_differ: + if self.had_logger: + self.pl = PowerlineLogger(self.use_daemon_threads, self.logger, self.ext) + self.get_module_attr = gen_module_attr_getter( + self.pl, self.common_config['paths'], self.imported_modules) + else: + self.logger, self.pl, self.get_module_attr = self.create_logger() self.config_loader.pl = self.pl if not self.run_once: self.config_loader.set_watcher(self.common_config['watcher']) - self.get_module_attr = gen_module_attr_getter(self.pl, self.import_paths, self.imported_modules) - mergedicts(self.renderer_options, dict( pl=self.pl, term_truecolor=self.common_config['term_truecolor'], diff --git a/powerline/bindings/config.py b/powerline/bindings/config.py index aedee89e..0db902be 100644 --- a/powerline/bindings/config.py +++ b/powerline/bindings/config.py @@ -9,7 +9,7 @@ import shlex from powerline.config import POWERLINE_ROOT, TMUX_CONFIG_DIRECTORY from powerline.lib.config import ConfigLoader -from powerline import generate_config_finder, load_config, create_logger, PowerlineLogger, finish_common_config +from powerline import generate_config_finder, load_config, create_logger, finish_common_config from powerline.shell import ShellPowerline from powerline.lib.shell import which from powerline.bindings.tmux import (TmuxVersionInfo, run_tmux_command, set_tmux_environment, get_tmux_version, @@ -221,8 +221,8 @@ def get_main_config(args): def create_powerline_logger(args): config = get_main_config(args) common_config = finish_common_config(get_preferred_output_encoding(), config['common']) - logger = create_logger(common_config) - return PowerlineLogger(use_daemon_threads=True, logger=logger, ext='config') + logger, pl, get_module_attr = create_logger(common_config) + return pl def check_command(cmd): diff --git a/powerline/lint/__init__.py b/powerline/lint/__init__.py index 72d9da5e..c464a52a 100644 --- a/powerline/lint/__init__.py +++ b/powerline/lint/__init__.py @@ -22,7 +22,7 @@ from powerline.lint.checks import (check_matcher_func, check_ext, check_config, 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, register_common_name, - highlight_group_spec) + highlight_group_spec, check_log_file_level, check_logging_handler) from powerline.lint.spec import Spec from powerline.lint.context import Context @@ -56,6 +56,11 @@ ext_spec = Spec( top_theme=top_theme_spec().optional(), ).copy gen_components_spec = (lambda *components: Spec().list(Spec().type(unicode).oneof(set(components)))) +log_level_spec = Spec().re('^[A-Z]+$').func( + (lambda value, *args: (True, True, not hasattr(logging, value))), + (lambda value: 'unknown debugging level {0}'.format(value)) +).copy +log_format_spec = Spec().type(unicode).copy main_spec = (Spec( common=Spec( default_top_theme=top_theme_spec().optional(), @@ -67,21 +72,32 @@ main_spec = (Spec( (lambda value, *args: (True, True, not os.path.exists(os.path.expanduser(value.value)))), (lambda value: 'path does not exist: {0}'.format(value)) ).optional(), - log_file=Spec().type(unicode).func( - ( - lambda value, *args: ( - True, - True, - not os.path.isdir(os.path.dirname(os.path.expanduser(value))) - ) + log_file=Spec().either( + Spec().type(unicode).func( + ( + lambda value, *args: ( + True, + True, + not os.path.isdir(os.path.dirname(os.path.expanduser(value))) + ) + ), + (lambda value: 'directory does not exist: {0}'.format(os.path.dirname(value))) ), - (lambda value: 'directory does not exist: {0}'.format(os.path.dirname(value))) + Spec().list(Spec().either( + Spec().type(unicode, type(None)), + Spec().tuple( + Spec().re(function_name_re).func(check_logging_handler), + Spec().tuple( + Spec().type(list).optional(), + Spec().type(dict).optional(), + ), + log_level_spec().func(check_log_file_level).optional(), + log_format_spec().optional(), + ), + )) ).optional(), - log_level=Spec().re('^[A-Z]+$').func( - (lambda value, *args: (True, True, not hasattr(logging, value))), - (lambda value: 'unknown debugging level {0}'.format(value)) - ).optional(), - log_format=Spec().type(unicode).optional(), + log_level=log_level_spec().optional(), + log_format=log_format_spec().optional(), interval=Spec().either(Spec().cmp('gt', 0.0), Spec().type(type(None))).optional(), reload_config=Spec().type(bool).optional(), watcher=Spec().type(unicode).oneof(set(('auto', 'inotify', 'stat'))).optional(), diff --git a/powerline/lint/checks.py b/powerline/lint/checks.py index 4c746bf6..c2fcfc45 100644 --- a/powerline/lint/checks.py +++ b/powerline/lint/checks.py @@ -800,3 +800,67 @@ def check_exinclude_function(name, data, context, echoerr): if not func: return True, False, True return True, False, False + + +def check_log_file_level(this_level, data, context, echoerr): + '''Check handler level specified in :ref:`log_file key ` + + This level must be greater or equal to the level in :ref:`log_level key + `. + ''' + havemarks(this_level) + hadproblem = False + top_level = context[0][1].get('common', {}).get('log_level', 'WARNING') + top_level_str = top_level + top_level_mark = getattr(top_level, 'mark', None) + if ( + not isinstance(top_level, unicode) or not hasattr(logging, top_level) + or not isinstance(this_level, unicode) or not hasattr(logging, this_level) + ): + return True, False, hadproblem + top_level = getattr(logging, top_level) + this_level_str = this_level + this_level_mark = this_level.mark + this_level = getattr(logging, this_level) + if this_level < top_level: + echoerr( + context='Error while checking log level index (key {key})'.format( + key=context.key), + context_mark=this_level_mark, + problem='found level that is less critical then top level ({0} < {0})'.format( + this_level_str, top_level_str), + problem_mark=top_level_mark, + ) + hadproblem = True + return True, False, hadproblem + + +def check_logging_handler(handler_name, data, context, echoerr): + havemarks(handler_name) + import_paths = [os.path.expanduser(path) for path in context[0][1].get('common', {}).get('paths', [])] + + handler_module, separator, handler_class = handler_name.rpartition('.') + if not separator: + handler_module = 'logging.handlers' + handler_class = handler_name + with WithPath(import_paths): + try: + handler = getattr(__import__(str(handler_module), fromlist=[str(handler_class)]), str(handler_class)) + except ImportError: + echoerr(context='Error while loading logger class (key {key})'.format(key=context.key), + problem='failed to load module {0}'.format(handler_module), + problem_mark=handler_name.mark) + return True, False, True + except AttributeError: + echoerr(context='Error while loading logger class (key {key})'.format(key=context.key), + problem='failed to load handler class {0}'.format(handler_class), + problem_mark=handler_name.mark) + return True, False, True + + if not issubclass(handler, logging.Handler): + echoerr(context='Error while loading logger class (key {key})'.format(key=context.key), + problem='loaded class {0} is not a logging.Handler subclass'.format(handler_class), + problem_mark=handler_name.mark) + return True, False, True + + return True, False, False diff --git a/powerline/lint/spec.py b/powerline/lint/spec.py index e3d072a4..f7b9155c 100644 --- a/powerline/lint/spec.py +++ b/powerline/lint/spec.py @@ -531,7 +531,8 @@ class Spec(object): if max_len == min_len: self.len('eq', len(specs)) else: - self.len('ge', min_len) + if min_len > 0: + self.len('ge', min_len) self.len('le', max_len) start = len(self.specs) diff --git a/powerline/listers/vim.py b/powerline/listers/vim.py index c1995957..2345b810 100644 --- a/powerline/listers/vim.py +++ b/powerline/listers/vim.py @@ -7,7 +7,7 @@ from powerline.bindings.vim import (current_tabpage, list_tabpages, vim_getbufop try: import vim except ImportError: - vim = {} + vim = object() def tabpage_updated_segment_info(segment_info, tabpage): diff --git a/powerline/segments/vim/__init__.py b/powerline/segments/vim/__init__.py index 16ad0194..8c740c95 100644 --- a/powerline/segments/vim/__init__.py +++ b/powerline/segments/vim/__init__.py @@ -11,7 +11,7 @@ from collections import defaultdict try: import vim except ImportError: - vim = {} + vim = object() from powerline.bindings.vim import (vim_get_func, getbufvar, vim_getbufoption, buffer_name, vim_getwinvar, diff --git a/powerline/vim.py b/powerline/vim.py index 53e23147..603a6a50 100644 --- a/powerline/vim.py +++ b/powerline/vim.py @@ -7,10 +7,13 @@ import logging from itertools import count -import vim +try: + import vim +except ImportError: + vim = object() from powerline.bindings.vim import vim_get_func, vim_getvar, get_vim_encoding, python_to_vim -from powerline import Powerline, FailedUnicode +from powerline import Powerline, FailedUnicode, finish_common_config from powerline.lib.dict import mergedicts from powerline.lib.unicode import u @@ -32,19 +35,21 @@ def _override_from(config, override_varname, key=None): class VimVarHandler(logging.Handler, object): '''Vim-specific handler which emits messages to Vim global variables - Used variable: ``g:powerline_log_messages``. + :param str varname: + Variable where ''' - def __init__(self, *args, **kwargs): - super(VimVarHandler, self).__init__(*args, **kwargs) - vim.command('unlet! g:powerline_log_messages') - vim.command('let g:powerline_log_messages = []') + def __init__(self, varname): + super(VimVarHandler, self).__init__() + utf_varname = u(varname) + self.vim_varname = utf_varname.encode('ascii') + vim.command('unlet! g:' + utf_varname) + vim.command('let g:' + utf_varname + ' = []') - @staticmethod - def emit(record): + def emit(self, record): message = u(record.message) if record.exc_text: message += '\n' + u(record.exc_text) - vim.eval(b'add(g:powerline_log_messages, ' + python_to_vim(message) + b')') + vim.eval(b'add(g:' + self.vim_varname + b', ' + python_to_vim(message) + b')') class VimPowerline(Powerline): @@ -53,6 +58,12 @@ class VimPowerline(Powerline): self.last_window_id = 1 self.pyeval = pyeval self.construct_window_statusline = self.create_window_statusline_constructor() + if all((hasattr(vim.current.window, attr) for attr in ('options', 'vars', 'number'))): + self.win_idx = self.new_win_idx + else: + self.win_idx = self.old_win_idx + self._vim_getwinvar = vim_get_func('getwinvar', 'bytes') + self._vim_setwinvar = vim_get_func('setwinvar') if sys.version_info < (3,): def create_window_statusline_constructor(self): @@ -80,18 +91,6 @@ class VimPowerline(Powerline): default_log_stream = sys.stdout - def create_logger(self): - logger = super(VimPowerline, self).create_logger() - try: - if int(vim_getvar('powerline_use_var_handler')): - formatter = logging.Formatter(self.common_config['log_format']) - handler = VimVarHandler(getattr(logging, self.common_config['log_level'])) - handler.setFormatter(formatter) - logger.addHandler(handler) - except KeyError: - pass - return logger - def add_local_theme(self, key, config): '''Add local themes at runtime (during vim session). @@ -137,7 +136,16 @@ class VimPowerline(Powerline): get_encoding = staticmethod(get_vim_encoding) def load_main_config(self): - return _override_from(super(VimPowerline, self).load_main_config(), 'powerline_config_overrides') + main_config = _override_from(super(VimPowerline, self).load_main_config(), 'powerline_config_overrides') + try: + use_var_handler = bool(int(vim_getvar('powerline_use_var_handler'))) + except KeyError: + use_var_handler = False + if use_var_handler: + main_config.setdefault('common', {}) + main_config['common'] = finish_common_config(self.get_encoding(), main_config['common']) + main_config['common']['log_file'].append(['powerline.vim.VimVarHandler', [['powerline_log_messages']]]) + return main_config def load_theme_config(self, name): return _override_from( @@ -252,44 +260,40 @@ class VimPowerline(Powerline): # do anything. pass - if all((hasattr(vim.current.window, attr) for attr in ('options', 'vars', 'number'))): - def win_idx(self, window_id): - r = None - for window in vim.windows: - try: - curwindow_id = window.vars['powerline_window_id'] - if r is not None and curwindow_id == window_id: - raise KeyError - except KeyError: - curwindow_id = self.last_window_id - self.last_window_id += 1 - window.vars['powerline_window_id'] = curwindow_id - statusline = self.construct_window_statusline(curwindow_id) - if window.options['statusline'] != statusline: - window.options['statusline'] = statusline - if curwindow_id == window_id if window_id else window is vim.current.window: - r = (window, curwindow_id, window.number) - return r - else: - _vim_getwinvar = staticmethod(vim_get_func('getwinvar', 'bytes')) - _vim_setwinvar = staticmethod(vim_get_func('setwinvar')) + def new_win_idx(self, window_id): + r = None + for window in vim.windows: + try: + curwindow_id = window.vars['powerline_window_id'] + if r is not None and curwindow_id == window_id: + raise KeyError + except KeyError: + curwindow_id = self.last_window_id + self.last_window_id += 1 + window.vars['powerline_window_id'] = curwindow_id + statusline = self.construct_window_statusline(curwindow_id) + if window.options['statusline'] != statusline: + window.options['statusline'] = statusline + if curwindow_id == window_id if window_id else window is vim.current.window: + r = (window, curwindow_id, window.number) + return r - def win_idx(self, window_id): - r = None - for winnr, window in zip(count(1), vim.windows): - curwindow_id = self._vim_getwinvar(winnr, 'powerline_window_id') - if curwindow_id and not (r is not None and curwindow_id == window_id): - curwindow_id = int(curwindow_id) - else: - curwindow_id = self.last_window_id - self.last_window_id += 1 - self._vim_setwinvar(winnr, 'powerline_window_id', curwindow_id) - statusline = self.construct_window_statusline(curwindow_id) - if self._vim_getwinvar(winnr, '&statusline') != statusline: - self._vim_setwinvar(winnr, '&statusline', statusline) - if curwindow_id == window_id if window_id else window is vim.current.window: - r = (window, curwindow_id, winnr) - return r + def old_win_idx(self, window_id): + r = None + for winnr, window in zip(count(1), vim.windows): + curwindow_id = self._vim_getwinvar(winnr, 'powerline_window_id') + if curwindow_id and not (r is not None and curwindow_id == window_id): + curwindow_id = int(curwindow_id) + else: + curwindow_id = self.last_window_id + self.last_window_id += 1 + self._vim_setwinvar(winnr, 'powerline_window_id', curwindow_id) + statusline = self.construct_window_statusline(curwindow_id) + if self._vim_getwinvar(winnr, '&statusline') != statusline: + self._vim_setwinvar(winnr, '&statusline', statusline) + if curwindow_id == window_id if window_id else window is vim.current.window: + r = (window, curwindow_id, winnr) + return r def statusline(self, window_id): window, window_id, winnr = self.win_idx(window_id) or (None, None, None) diff --git a/tests/test_logging.py b/tests/test_logging.py new file mode 100644 index 00000000..6de4a389 --- /dev/null +++ b/tests/test_logging.py @@ -0,0 +1,467 @@ +# vim:fileencoding=utf-8:noet + +'''Tests for various logging features''' + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import re +import codecs +import os + +from io import StringIO +from shutil import rmtree + +from powerline import finish_common_config, create_logger + +from tests import TestCase +from tests.lib import replace_attr + + +TIMESTAMP_RE = r'\d{4}-\d\d-\d\d \d\d:\d\d:\d\d,\d{3}' + + +class TestRE(TestCase): + def assertMatches(self, text, regexp): + self.assertTrue( + re.match(regexp, text), + '{0!r} did not match {1!r}'.format(text, regexp), + ) + + +def close_handlers(logger): + for handler in logger.handlers: + handler.close() + + +class TestHandlers(TestRE): + def test_stderr_handler_is_default(self): + out = StringIO() + err = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {}) + logger, pl, get_module_attr = create_logger(common_config) + pl.error('Foo') + close_handlers(logger) + self.assertMatches(err.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + self.assertEqual(out.getvalue(), '') + + def test_stream_override(self): + out = StringIO() + err = StringIO() + stream = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_explicit_none(self): + out = StringIO() + err = StringIO() + stream = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [None]}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_explicit_stream_handler(self): + out = StringIO() + err = StringIO() + stream = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [['logging.StreamHandler', [[]]]]}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + self.assertEqual(stream.getvalue(), '') + self.assertMatches(err.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + self.assertEqual(out.getvalue(), '') + + def test_explicit_stream_handler_implicit_stream(self): + out = StringIO() + err = StringIO() + stream = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [['logging.StreamHandler', []]]}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_file_handler(self): + out = StringIO() + err = StringIO() + stream = StringIO() + file_name = 'test_logging-test_file_handler' + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': file_name}) + try: + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + with codecs.open(file_name, encoding='utf-8') as fp: + self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + finally: + os.unlink(file_name) + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_file_handler_create_dir(self): + out = StringIO() + err = StringIO() + stream = StringIO() + file_name = 'test_logging-test_file_handler_create_dir/file' + + self.assertFalse(os.path.isdir(os.path.dirname(file_name))) + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': file_name}) + try: + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + self.assertTrue(os.path.isdir(os.path.dirname(file_name))) + with codecs.open(file_name, encoding='utf-8') as fp: + self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + finally: + rmtree(os.path.dirname(file_name)) + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_multiple_files(self): + out = StringIO() + err = StringIO() + stream = StringIO() + file_name_1 = 'test_logging-test_multiple_files-1' + file_name_2 = file_name_1[:-1] + '2' + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [file_name_1, file_name_2]}) + try: + try: + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + for file_name in (file_name_1, file_name_2): + with codecs.open(file_name, encoding='utf-8') as fp: + self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + finally: + os.unlink(file_name_1) + finally: + os.unlink(file_name_2) + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_multiple_files_and_stream(self): + out = StringIO() + err = StringIO() + stream = StringIO() + file_name_1 = 'test_logging-test_multiple_files_and_stream-1' + file_name_2 = file_name_1[:-1] + '2' + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [file_name_1, file_name_2, None]}) + try: + try: + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + for file_name in (file_name_1, file_name_2): + with codecs.open(file_name, encoding='utf-8') as fp: + self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + finally: + os.unlink(file_name_1) + finally: + os.unlink(file_name_2) + self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_handler_args(self): + out = StringIO() + err = StringIO() + stream = StringIO() + file_name = 'test_logging-test_handler_args' + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [ + ['RotatingFileHandler', [[file_name]]] + ]}) + try: + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + with codecs.open(file_name, encoding='utf-8') as fp: + self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + finally: + os.unlink(file_name) + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_handler_args_kwargs(self): + out = StringIO() + err = StringIO() + stream = StringIO() + file_name = 'test_logging-test_handler_args_kwargs' + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [ + ['RotatingFileHandler', [[file_name], {'maxBytes': 1, 'backupCount': 1}]] + ]}) + try: + try: + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + pl.error('Bar') + close_handlers(logger) + with codecs.open(file_name, encoding='utf-8') as fp: + self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$') + with codecs.open(file_name + '.1', encoding='utf-8') as fp: + self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + finally: + os.unlink(file_name + '.1') + finally: + os.unlink(file_name) + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_logger_level(self): + out = StringIO() + err = StringIO() + stream = StringIO() + stream1 = StringIO() + stream2 = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [ + ['logging.StreamHandler', [[stream1]], 'WARNING'], + ['logging.StreamHandler', [[stream2]], 'ERROR'], + ]}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.warn('Foo') + pl.error('Bar') + close_handlers(logger) + self.assertMatches(stream1.getvalue(), ( + '^' + TIMESTAMP_RE + ':WARNING:__unknown__:Foo\n' + + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$' + )) + self.assertMatches(stream2.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$') + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_logger_level_not_overriding_default(self): + out = StringIO() + err = StringIO() + stream = StringIO() + stream1 = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [ + ['logging.StreamHandler', [[stream1]], 'DEBUG'], + ]}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.debug('Foo') + pl.error('Bar') + close_handlers(logger) + self.assertMatches(stream1.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$') + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_top_log_level(self): + out = StringIO() + err = StringIO() + stream = StringIO() + stream1 = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [ + ['logging.StreamHandler', [[stream1]], 'DEBUG'], + ], 'log_level': 'DEBUG'}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.debug('Foo') + pl.error('Bar') + close_handlers(logger) + self.assertMatches(stream1.getvalue(), ( + '^' + TIMESTAMP_RE + ':DEBUG:__unknown__:Foo\n' + + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$' + )) + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_logger_format(self): + out = StringIO() + err = StringIO() + stream = StringIO() + stream1 = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [ + ['logging.StreamHandler', [[stream1]], 'WARNING', 'FOO'], + ]}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.warn('Foo') + pl.error('Bar') + close_handlers(logger) + self.assertEqual(stream1.getvalue(), 'FOO\nFOO\n') + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_top_log_format(self): + out = StringIO() + err = StringIO() + stream = StringIO() + stream1 = StringIO() + stream2 = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [ + ['logging.StreamHandler', [[stream1]], 'WARNING', 'FOO'], + ['logging.StreamHandler', [[stream2]], 'WARNING'], + ], 'log_format': 'BAR'}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.warn('Foo') + pl.error('Bar') + close_handlers(logger) + self.assertEqual(stream2.getvalue(), 'BAR\nBAR\n') + self.assertEqual(stream1.getvalue(), 'FOO\nFOO\n') + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + +class TestPowerlineLogger(TestRE): + def test_args_formatting(self): + stream = StringIO() + + common_config = finish_common_config('utf-8', {}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.warn('foo {0}', 'Test') + pl.warn('bar {0!r}', 'Test') + close_handlers(logger) + self.assertMatches(stream.getvalue(), ( + '^' + TIMESTAMP_RE + ':WARNING:__unknown__:foo Test\n' + + TIMESTAMP_RE + ':WARNING:__unknown__:bar u?\'Test\'\n$' + )) + + def test_prefix_formatting(self): + stream = StringIO() + + common_config = finish_common_config('utf-8', {}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.prefix = '1' + pl.warn('foo') + pl.prefix = '2' + pl.warn('bar') + close_handlers(logger) + self.assertMatches(stream.getvalue(), ( + '^' + TIMESTAMP_RE + ':WARNING:__unknown__:1:foo\n' + + TIMESTAMP_RE + ':WARNING:__unknown__:2:bar\n$' + )) + + def test_kwargs_formatting(self): + stream = StringIO() + + common_config = finish_common_config('utf-8', {}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.warn('foo {arg}', arg='Test') + pl.warn('bar {arg!r}', arg='Test') + close_handlers(logger) + self.assertMatches(stream.getvalue(), ( + '^' + TIMESTAMP_RE + ':WARNING:__unknown__:foo Test\n' + + TIMESTAMP_RE + ':WARNING:__unknown__:bar u?\'Test\'\n$' + )) + + def test_args_kwargs_formatting(self): + stream = StringIO() + + common_config = finish_common_config('utf-8', {}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.warn('foo {0!r} {arg}', 'Test0', arg='Test') + pl.warn('bar {0} {arg!r}', 'Test0', arg='Test') + close_handlers(logger) + self.assertMatches(stream.getvalue(), ( + '^' + TIMESTAMP_RE + ':WARNING:__unknown__:foo u?\'Test0\' Test\n' + + TIMESTAMP_RE + ':WARNING:__unknown__:bar Test0 u?\'Test\'\n$' + )) + + def test_exception_formatting(self): + stream = StringIO() + + common_config = finish_common_config('utf-8', {}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + try: + raise ValueError('foo') + except ValueError: + pl.exception('Message') + close_handlers(logger) + self.assertMatches(stream.getvalue(), ( + '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Message\n' + + 'Traceback \\(most recent call last\\):\n' + + '(?: File ".*?", line \\d+, in \\w+\n [^\n]*\n)+' + + 'ValueError: foo\n$' + )) + + def test_levels(self): + stream = StringIO() + + common_config = finish_common_config('utf-8', {'log_level': 'DEBUG'}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.debug('1') + pl.info('2') + pl.warn('3') + pl.error('4') + pl.critical('5') + close_handlers(logger) + self.assertMatches(stream.getvalue(), ( + '^' + TIMESTAMP_RE + ':DEBUG:__unknown__:1\n' + + TIMESTAMP_RE + ':INFO:__unknown__:2\n' + + TIMESTAMP_RE + ':WARNING:__unknown__:3\n' + + TIMESTAMP_RE + ':ERROR:__unknown__:4\n' + + TIMESTAMP_RE + ':CRITICAL:__unknown__:5\n$' + )) + + +old_cwd = None + + +def setUpModule(): + global old_cwd + global __file__ + old_cwd = os.getcwd() + __file__ = os.path.abspath(__file__) + os.chdir(os.path.dirname(__file__)) + + +def tearDownModule(): + global old_cwd + os.chdir(old_cwd) + + +if __name__ == '__main__': + from tests import main + main() From dbb968cdc34e13e9cd65f41666cf555db014a9bf Mon Sep 17 00:00:00 2001 From: chmelevskij Date: Tue, 24 Nov 2015 22:46:44 +0000 Subject: [PATCH 10/46] Add iTerm fix solution. Add a iTerm fix solution from https://github.com/powerline/fonts/issues/44 --- docs/source/troubleshooting/osx.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/troubleshooting/osx.rst b/docs/source/troubleshooting/osx.rst index cb7e56b8..c2251e66 100644 --- a/docs/source/troubleshooting/osx.rst +++ b/docs/source/troubleshooting/osx.rst @@ -7,7 +7,8 @@ I can’t see any fancy symbols, what’s wrong? * If you’re using iTerm2, please update to `this revision `_ - or newer. + or newer. Also make sure that Preferences>Profiles>Text>Non-ASCII Font is the same as + your main Font. * You need to set your ``LANG`` and ``LC_*`` environment variables to a UTF-8 locale (e.g. ``LANG=en_US.utf8``). Consult your Linux distro’s documentation for information about setting these variables correctly. From 4265da2e1db57e8a4c039935c9bb4a7441bea45e Mon Sep 17 00:00:00 2001 From: Mook Date: Sat, 28 Nov 2015 11:28:51 -0800 Subject: [PATCH 11/46] [Players - mpd] Use Unicode where available Otherwise the mpd function will fail when it encounters non-ASCII metadata as it tries to do a unicode.format() and attempts to decode the incoming data as ASCII, throwing a UnicodeDecodeError exception. Fixes #1500 --- powerline/segments/common/players.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/powerline/segments/common/players.py b/powerline/segments/common/players.py index 8ae6cb1a..e98115e7 100644 --- a/powerline/segments/common/players.py +++ b/powerline/segments/common/players.py @@ -189,7 +189,11 @@ class MpdPlayerSegment(PlayerSegment): 'total': now_playing[3], } else: - client = mpd.MPDClient() + try: + client = mpd.MPDClient(use_unicode=True) + except TypeError: + # python-mpd 1.x does not support use_unicode + client = mpd.MPDClient() client.connect(host, port) if password: client.password(password) From 1e0228a3c732e09b7e4bf6b402251c3dc7c76cb8 Mon Sep 17 00:00:00 2001 From: Mook Date: Sat, 28 Nov 2015 12:07:01 -0800 Subject: [PATCH 12/46] [Battery - Linux] Look at battery status instead of AC online-ness The names of AC adapters are straight from ACPI and are sometimes called "ADPx" instead of "ACx". To avoid confusion, look at the battery status instead. Check for a power supply with a capacity instead of names that start with "BAT" for the same reason. Fixes #1498 --- powerline/segments/common/bat.py | 35 +++++++++++++++----------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/powerline/segments/common/bat.py b/powerline/segments/common/bat.py index 0411fcd3..ac426a05 100644 --- a/powerline/segments/common/bat.py +++ b/powerline/segments/common/bat.py @@ -62,27 +62,24 @@ def _fetch_battery_info(pl): pl.debug('Not using DBUS+UPower as no batteries were found') if os.path.isdir('/sys/class/power_supply'): - online_path_verified = None - linux_supplier_fmt = '/sys/class/power_supply/{0}/capacity' - linux_ac_fmt = '/sys/class/power_supply/{0}/online' + linux_capacity_fmt = '/sys/class/power_supply/{0}/capacity' + linux_status_fmt = '/sys/class/power_supply/{0}/status' for linux_supplier in os.listdir('/sys/class/power_supply'): - cap_path = linux_supplier_fmt.format(linux_supplier) - online_path = linux_ac_fmt.format(linux_supplier) - if linux_supplier.startswith('AC') and os.path.exists(online_path): - pl.debug('Using /sys/class/power_supply with AC {0}', online_path) - online_path_verified = online_path - elif linux_supplier.startswith('BAT') and os.path.exists(cap_path): - pl.debug('Using /sys/class/power_supply with battery {0}', linux_supplier) - - def _get_battery_status(pl): + cap_path = linux_capacity_fmt.format(linux_supplier) + status_path = linux_status_fmt.format(linux_supplier) + if not os.path.exists(cap_path): + continue + pl.debug('Using /sys/class/power_supply with battery {0}', linux_supplier) + def _get_battery_status(pl): + with open(cap_path, 'r') as f: + _capacity = int(float(f.readline().split()[0])) + try: + with open(status_path, 'r') as f: + _ac_powered = (f.readline().strip() != 'Discharging') + except IOError: _ac_powered = None - with open(cap_path, 'r') as f: - _capacity = int(float(f.readline().split()[0])) - if online_path_verified: - with open(online_path_verified, 'r') as f: - _ac_powered = int(f.readline()) == 1 - return _capacity, _ac_powered - return _get_battery_status + return _capacity, _ac_powered + return _get_battery_status pl.debug('Not using /sys/class/power_supply as no batteries were found') else: pl.debug('Not using /sys/class/power_supply: no directory') From b40119a18704a3f346e4c5e3cb6bb33f2610f2d8 Mon Sep 17 00:00:00 2001 From: Foo Date: Thu, 17 Dec 2015 00:08:37 +0300 Subject: [PATCH 13/46] Remove status-utf8 option It is incorrect to set it at all because what powerline outputs depends on locale used, and if locale is utf8 then tmux should already set this option. --- powerline/bindings/tmux/powerline-base.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/powerline/bindings/tmux/powerline-base.conf b/powerline/bindings/tmux/powerline-base.conf index a17f0c41..77791581 100644 --- a/powerline/bindings/tmux/powerline-base.conf +++ b/powerline/bindings/tmux/powerline-base.conf @@ -1,5 +1,4 @@ set -g status on -set -g status-utf8 on set -g status-interval 2 set -g status-left-length 20 set -g status-right '#(env "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS tmux right -R pane_id=\"`tmux display -p "#D"`\")' From 7b60b941fd65e7565ba603ba1415d28d92f78b7a Mon Sep 17 00:00:00 2001 From: S0lll0s Date: Tue, 5 Jan 2016 00:46:43 +0100 Subject: [PATCH 14/46] Let segment_info overwrite i3wm workspace output --- powerline/segments/i3wm.py | 8 ++++++-- tests/test_segments.py | 25 +++++++++++++++++-------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/powerline/segments/i3wm.py b/powerline/segments/i3wm.py index a5a82774..ea0217d6 100644 --- a/powerline/segments/i3wm.py +++ b/powerline/segments/i3wm.py @@ -19,7 +19,8 @@ def calcgrp(w): return group -def workspaces(pl, only_show=None, output=None, strip=0): +@requires_segment_info +def workspaces(pl, segment_info, only_show=None, output=None, strip=0): '''Return list of used workspaces :param list only_show: @@ -28,7 +29,8 @@ def workspaces(pl, only_show=None, output=None, strip=0): are shown. :param str output: - If specified, only workspaces on this output are shown. + If specified, only workspaces on this output are shown. If unspecified, + may be set by the lemonbar renderer and bindings. :param int strip: Specifies how many characters from the front of each workspace name @@ -45,6 +47,8 @@ def workspaces(pl, only_show=None, output=None, strip=0): else: conn = i3ipc.Connection() + output = output or ('output' in segment_info and segment_info['output']) + return [{ 'contents': w['name'][min(len(w['name']), strip):], 'highlight_groups': calcgrp(w) diff --git a/tests/test_segments.py b/tests/test_segments.py index a1c70c98..edb69a59 100644 --- a/tests/test_segments.py +++ b/tests/test_segments.py @@ -891,39 +891,48 @@ class TestI3WM(TestCase): {'name': '3: w3', 'output': 'HDMI1', 'focused': False, 'urgent': True, 'visible': True}, {'name': '4: w4', 'output': 'DVI01', 'focused': True, 'urgent': True, 'visible': True}, ]))): - self.assertEqual(i3wm.workspaces(pl=pl), [ + segment_info = {} + + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info), [ {'contents': '1: w1', 'highlight_groups': ['workspace']}, {'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']}, {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, ]) - self.assertEqual(i3wm.workspaces(pl=pl, only_show=None), [ + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=None), [ {'contents': '1: w1', 'highlight_groups': ['workspace']}, {'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']}, {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, ]) - self.assertEqual(i3wm.workspaces(pl=pl, only_show=['focused', 'urgent']), [ + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['focused', 'urgent']), [ {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, ]) - self.assertEqual(i3wm.workspaces(pl=pl, only_show=['visible']), [ + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible']), [ {'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']}, {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, ]) - self.assertEqual(i3wm.workspaces(pl=pl, only_show=['visible'], strip=3), [ + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3), [ {'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']}, {'contents': 'w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, {'contents': 'w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, ]) - self.assertEqual(i3wm.workspaces(pl=pl, only_show=['focused', 'urgent'], output='DVI01'), [ + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['focused', 'urgent'], output='DVI01'), [ {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, ]) - self.assertEqual(i3wm.workspaces(pl=pl, only_show=['visible'], output='HDMI1'), [ + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], output='HDMI1'), [ {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, ]) - self.assertEqual(i3wm.workspaces(pl=pl, only_show=['visible'], strip=3, output='LVDS1'), [ + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3, output='LVDS1'), [ + {'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']}, + ]) + segment_info['output'] = 'LVDS1' + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], output='HDMI1'), [ + {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, + ]) + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3), [ {'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']}, ]) From ee5f471b491cec3f0b6f08095f4aa91d5320435a Mon Sep 17 00:00:00 2001 From: S0lll0s Date: Tue, 5 Jan 2016 01:15:09 +0100 Subject: [PATCH 15/46] Deprecate "bar" bindings in favor of lemonbar --- docs/source/configuration/reference.rst | 4 + docs/source/usage/wm-widgets.rst | 21 +++-- powerline/bindings/bar/powerline-bar.py | 18 ++-- .../bindings/lemonbar/powerline-lemonbar.py | 91 +++++++++++++++++++ powerline/lemonbar.py | 21 +++++ powerline/lint/__init__.py | 6 ++ powerline/renderers/bar.py | 45 --------- powerline/renderers/lemonbar.py | 61 +++++++++++++ powerline/segments/i3wm.py | 7 +- tests/test_configuration.py | 12 +-- 10 files changed, 212 insertions(+), 74 deletions(-) create mode 100755 powerline/bindings/lemonbar/powerline-lemonbar.py create mode 100644 powerline/lemonbar.py delete mode 100644 powerline/renderers/bar.py create mode 100644 powerline/renderers/lemonbar.py diff --git a/docs/source/configuration/reference.rst b/docs/source/configuration/reference.rst index e32ee06b..99b32be1 100644 --- a/docs/source/configuration/reference.rst +++ b/docs/source/configuration/reference.rst @@ -187,6 +187,10 @@ Common configuration is a subdictionary that is a value of ``ext`` key in ``out`` and ``rewrite`` prompts (refer to IPython documentation for more details) while ``in`` prompt is the default. + For wm (:ref:`lemonbar ` only) it is a dictionary + ``{output : theme_name}`` that maps the ``xrandr`` output names to the + local themes to use on that output. + ``components`` Determines which extension components should be enabled. This key is highly extension-specific, here is the table of extensions and corresponding diff --git a/docs/source/usage/wm-widgets.rst b/docs/source/usage/wm-widgets.rst index 6c0806e2..cd32c763 100644 --- a/docs/source/usage/wm-widgets.rst +++ b/docs/source/usage/wm-widgets.rst @@ -50,24 +50,27 @@ Add the following to :file:`~/.config/qtile/config.py`: ), ] -.. _bar-usage: +.. _lemonbar-usage: -bar-aint-recursive -================== +lemonbar (formerly bar-aint-recursive) +====================================== -To run the bar simply pipe the output of the binding script into ``bar`` and -specify appropriate options, for example like this:: +To run the bar simply start the binding script: - python /path/to/powerline/bindings/bar/powerline-bar.py | bar + python /path/to/powerline/bindings/lemonbar/powerline-lemonbar.py -to run with i3, simply ``exec`` this in i3 config file:: +You can specify options to be passed to ``lemonbar`` after ``--``, like so: - exec python /path/to/powerline/bindings/bar/powerline-bar.py --i3 | bar + python /path/to/powerline/bindings/lemonbar/powerline-lemonbar.py --height 16 -- -f "Source Code Pro for Powerline-9" + +to run with i3, simply ``exec`` this in the i3 config file and set the ``--i3`` switch: + + exec python /path/to/powerline/bindings/lemonbar/powerline-lemonbar.py --i3 Running the binding in i3-mode will require `i3ipc `_ (or the outdated `i3-py `_). -See the `bar documentation `_ for more +See the `lemonbar documentation `_ for more information and options. I3 bar diff --git a/powerline/bindings/bar/powerline-bar.py b/powerline/bindings/bar/powerline-bar.py index 35496f6f..05ef7685 100755 --- a/powerline/bindings/bar/powerline-bar.py +++ b/powerline/bindings/bar/powerline-bar.py @@ -2,31 +2,27 @@ # vim:fileencoding=utf-8:noet from __future__ import (unicode_literals, division, absolute_import, print_function) +import os import sys import time from threading import Lock, Timer from argparse import ArgumentParser -from powerline import Powerline +from powerline.lemonbar import LemonbarPowerline from powerline.lib.encoding import get_unicode_writer -class BarPowerline(Powerline): - get_encoding = staticmethod(lambda: 'utf-8') - - def init(self): - super(BarPowerline, self).init(ext='wm', renderer_module='bar') - - if __name__ == '__main__': - parser = ArgumentParser(description='Powerline BAR bindings.') + parser = ArgumentParser(description='Powerline lemonbar bindings.') parser.add_argument( '--i3', action='store_true', help='Subscribe for i3 events.' ) args = parser.parse_args() - powerline = BarPowerline() + powerline = LemonbarPowerline() + powerline.update_renderer() + powerline.pl.warn("The 'bar' bindings are deprecated, please switch to 'lemonbar'") lock = Lock() modes = ['default'] write = get_unicode_writer(encoding='utf-8') @@ -60,4 +56,4 @@ if __name__ == '__main__': conn.main() while True: - time.sleep(1e10) + time.sleep(1e8) diff --git a/powerline/bindings/lemonbar/powerline-lemonbar.py b/powerline/bindings/lemonbar/powerline-lemonbar.py new file mode 100755 index 00000000..ca658813 --- /dev/null +++ b/powerline/bindings/lemonbar/powerline-lemonbar.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import time +import re +import subprocess + +from threading import Lock, Timer +from argparse import ArgumentParser, REMAINDER + +from powerline.lemonbar import LemonbarPowerline +from powerline.lib.shell import run_cmd + + +if __name__ == '__main__': + parser = ArgumentParser( + description='Powerline BAR bindings.', + usage='%(prog)s [-h] [--i3] [--height HEIGHT] [--interval INTERVAL] [--bar-command CMD] -- args' + ) + parser.add_argument( + '--i3', action='store_true', + help='Subscribe for i3 events.' + ) + parser.add_argument( + '--height', default='', + help='Bar height.' + ) + parser.add_argument( + '--interval', '-i', + type=float, default=0.5, + help='Refresh interval.' + ) + parser.add_argument( + '--bar-command', '-c', + default='lemonbar', + help='Name of the lemonbar executable to use.' + ) + parser.add_argument( + 'args', nargs=REMAINDER, + help='Extra arguments for lemonbar.' + ) + args = parser.parse_args() + + powerline = LemonbarPowerline() + powerline.update_renderer() + bars = [] + active_screens = [match.groupdict() for match in re.finditer( + '^(?P[0-9A-Za-z-]+) connected (?P\d+)x(?P\d+)\+(?P\d+)\+(?P\d+)', + run_cmd(powerline.pl, ['xrandr', '-q']), + re.MULTILINE + )] + + for screen in active_screens: + command = [args.bar_command, '-g', '{0}x{1}+{2}'.format(screen['width'], args.height, screen['x'])] + args.args[1:] + process = subprocess.Popen(command, stdin=subprocess.PIPE) + bars.append((screen['name'], process, int(screen['width']) / 5)) + + lock = Lock() + modes = ['default'] + + def render(reschedule=False): + if reschedule: + Timer(args.interval, render, kwargs={'reschedule': True}).start() + + global lock + with lock: + for output, process, width in bars: + process.stdin.write(powerline.render(mode=modes[0], width=width, matcher_info=output).encode('utf-8') + b'\n') + process.stdin.flush() + + def update(evt): + modes[0] = evt.change + render() + + render(reschedule=True) + + if args.i3: + try: + import i3ipc + except ImportError: + import i3 + i3.Subscription(lambda evt, data, sub: render(), 'workspace') + else: + conn = i3ipc.Connection() + conn.on('workspace::focus', lambda conn, evt: render()) + conn.on('mode', lambda conn, evt: update(evt)) + conn.main() + + while True: + time.sleep(1e8) diff --git a/powerline/lemonbar.py b/powerline/lemonbar.py new file mode 100644 index 00000000..b49f86b5 --- /dev/null +++ b/powerline/lemonbar.py @@ -0,0 +1,21 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline import Powerline +from powerline.lib.dict import mergedicts + + +class LemonbarPowerline(Powerline): + def init(self): + super(LemonbarPowerline, self).init(ext='wm', renderer_module='lemonbar') + + get_encoding = staticmethod(lambda: 'utf-8') + + def get_local_themes(self, local_themes): + if not local_themes: + return {} + + return dict(( + (key, {'config': self.load_theme_config(val)}) + for key, val in local_themes.items() + )) diff --git a/powerline/lint/__init__.py b/powerline/lint/__init__.py index c464a52a..6f7fa4b2 100644 --- a/powerline/lint/__init__.py +++ b/powerline/lint/__init__.py @@ -126,6 +126,12 @@ main_spec = (Spec( select=ext_theme_spec(), ), ).optional(), + wm=ext_spec().update( + local_themes=Spec().unknown_spec( + Spec().re('^[0-9A-Za-z-]+$'), + ext_theme_spec() + ).optional() + ).optional(), ).unknown_spec( check_ext, ext_spec(), diff --git a/powerline/renderers/bar.py b/powerline/renderers/bar.py deleted file mode 100644 index 1a0684aa..00000000 --- a/powerline/renderers/bar.py +++ /dev/null @@ -1,45 +0,0 @@ -# vim:fileencoding=utf-8:noet -from __future__ import (unicode_literals, division, absolute_import, print_function) - -from powerline.renderer import Renderer -from powerline.colorscheme import ATTR_UNDERLINE - - -class BarRenderer(Renderer): - '''bar (bar ain't recursive) renderer - - - See documentation of `bar `_ and :ref:`the usage instructions ` - ''' - - character_translations = Renderer.character_translations.copy() - character_translations[ord('%')] = '%%' - - @staticmethod - def hlstyle(*args, **kwargs): - # We don’t need to explicitly reset attributes, so skip those calls - return '' - - def hl(self, contents, fg=None, bg=None, attrs=None): - text = '' - - if fg is not None: - if fg is not False and fg[1] is not False: - text += '%{{F#ff{0:06x}}}'.format(fg[1]) - if bg is not None: - if bg is not False and bg[1] is not False: - text += '%{{B#ff{0:06x}}}'.format(bg[1]) - - if attrs & ATTR_UNDERLINE: - text += '%{+u}' - - return text + contents + '%{F-B--u}' - - def render(self, *args, **kwargs): - return '%{{l}}{0}%{{r}}{1}'.format( - super(BarRenderer, self).render(side='left', *args, **kwargs), - super(BarRenderer, self).render(side='right', *args, **kwargs), - ) - - -renderer = BarRenderer diff --git a/powerline/renderers/lemonbar.py b/powerline/renderers/lemonbar.py new file mode 100644 index 00000000..f378f235 --- /dev/null +++ b/powerline/renderers/lemonbar.py @@ -0,0 +1,61 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.renderer import Renderer +from powerline.theme import Theme +from powerline.colorscheme import ATTR_UNDERLINE + + +class LemonbarRenderer(Renderer): + '''lemonbar (formerly bar/bar ain't recursive) renderer + + + See documentation of `lemonbar `_ and :ref:`the usage instructions ` + ''' + + character_translations = Renderer.character_translations.copy() + character_translations[ord('%')] = '%%{}' + + @staticmethod + def hlstyle(*args, **kwargs): + # We don’t need to explicitly reset attributes, so skip those calls + return '' + + def hl(self, contents, fg=None, bg=None, attrs=None): + text = '' + + if fg is not None: + if fg is not False and fg[1] is not False: + text += '%{{F#ff{0:06x}}}'.format(fg[1]) + if bg is not None: + if bg is not False and bg[1] is not False: + text += '%{{B#ff{0:06x}}}'.format(bg[1]) + + if attrs & ATTR_UNDERLINE: + text += '%{+u}' + + return text + contents + '%{F-B--u}' + + def render(self, *args, **kwargs): + return '%{{l}}{0}%{{r}}{1}'.format( + super(LemonbarRenderer, self).render(side='left', segment_info={'output': kwargs.get('matcher_info')}, *args, **kwargs), + super(LemonbarRenderer, self).render(side='right', segment_info={'output': kwargs.get('matcher_info')}, *args, **kwargs), + ) + + def get_theme(self, matcher_info): + if not matcher_info or matcher_info not in self.local_themes: + return self.theme + match = self.local_themes[matcher_info] + + try: + return match['theme'] + except KeyError: + match['theme'] = Theme( + theme_config=match['config'], + main_theme_config=self.theme_config, + **self.theme_kwargs + ) + return match['theme'] + + +renderer = LemonbarRenderer diff --git a/powerline/segments/i3wm.py b/powerline/segments/i3wm.py index ea0217d6..9d214cfe 100644 --- a/powerline/segments/i3wm.py +++ b/powerline/segments/i3wm.py @@ -29,8 +29,9 @@ def workspaces(pl, segment_info, only_show=None, output=None, strip=0): are shown. :param str output: - If specified, only workspaces on this output are shown. If unspecified, - may be set by the lemonbar renderer and bindings. + May be set to the name of an X output. If specified, only workspaces + on that output are shown. Overrides automatic output detection by + the lemonbar renderer and bindings. :param int strip: Specifies how many characters from the front of each workspace name @@ -47,7 +48,7 @@ def workspaces(pl, segment_info, only_show=None, output=None, strip=0): else: conn = i3ipc.Connection() - output = output or ('output' in segment_info and segment_info['output']) + output = output or segment_info.get('output') return [{ 'contents': w['name'][min(len(w['name']), strip):], diff --git a/tests/test_configuration.py b/tests/test_configuration.py index c0708c3d..3cd7d387 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -825,28 +825,28 @@ class TestVim(TestCase): sys.path.pop(0) -class TestBar(TestRender): - def test_bar(self): +class TestLemonbar(TestRender): + def test_lemonbar(self): import powerline as powerline_module with swap_attributes(config, powerline_module): - with get_powerline_raw(config, powerline_module.Powerline, replace_gcp=True, ext='wm', renderer_module='bar') as powerline: + with get_powerline_raw(config, powerline_module.Powerline, replace_gcp=True, ext='wm', renderer_module='lemonbar') as powerline: self.assertRenderEqual( powerline, '%{l}%{F#ffc00000}%{B#ff008000}%{+u} A%{F-B--u}%{F#ff008000}%{B#ffc00000}>>%{F-B--u}%{F#ff008000}%{B#ffc00000}B%{F-B--u}%{F#ffc00000}>>%{F-B--u}%{r}%{F#ffc00000}<<%{F-B--u}%{F#ff804000}%{B#ffc00000}%{+u}C%{F-B--u}%{F#ff0000c0}%{B#ffc00000}<<%{F-B--u}%{F#ff008000}%{B#ff0000c0}D %{F-B--u}' ) @with_new_config - def test_bar_escape(self, config): + def test_lemonbar_escape(self, config): import powerline as powerline_module config['themes/wm/default']['segments']['left'] = ( highlighted_string('%{asd}', 'hl1'), highlighted_string('10% %', 'hl2'), ) with swap_attributes(config, powerline_module): - with get_powerline_raw(config, powerline_module.Powerline, replace_gcp=True, ext='wm', renderer_module='bar') as powerline: + with get_powerline_raw(config, powerline_module.Powerline, replace_gcp=True, ext='wm', renderer_module='lemonbar') as powerline: self.assertRenderEqual( powerline, - '%{l}%{F#ffc00000}%{B#ff008000}%{+u} %%{asd}%{F-B--u}%{F#ff008000}%{B#ffc00000}>>%{F-B--u}%{F#ff008000}%{B#ffc00000}10%% %%%{F-B--u}%{F#ffc00000}>>%{F-B--u}%{r}%{F#ffc00000}<<%{F-B--u}%{F#ff804000}%{B#ffc00000}%{+u}C%{F-B--u}%{F#ff0000c0}%{B#ffc00000}<<%{F-B--u}%{F#ff008000}%{B#ff0000c0}D %{F-B--u}' + '%{l}%{F#ffc00000}%{B#ff008000}%{+u} %%{}{asd}%{F-B--u}%{F#ff008000}%{B#ffc00000}>>%{F-B--u}%{F#ff008000}%{B#ffc00000}10%%{} %%{}%{F-B--u}%{F#ffc00000}>>%{F-B--u}%{r}%{F#ffc00000}<<%{F-B--u}%{F#ff804000}%{B#ffc00000}%{+u}C%{F-B--u}%{F#ff0000c0}%{B#ffc00000}<<%{F-B--u}%{F#ff008000}%{B#ff0000c0}D %{F-B--u}' ) From b29988b7ec45cc895f4e9b2047a61c17acdc4d5b Mon Sep 17 00:00:00 2001 From: Foo Date: Thu, 7 Jan 2016 04:18:05 +0300 Subject: [PATCH 16/46] Add functional tests for lemonbar bindings --- tests/common.sh | 4 +- tests/run_bar_tests.sh | 194 ++++++++++++++++++ tests/test_bar/path/lemonbar | 11 + tests/test_bar/path/xrandr | 31 +++ tests/test_bar/powerline/config.json | 9 + .../powerline/themes/wm/__main__.json | 10 + .../test_bar/powerline/themes/wm/default.json | 18 ++ tests/test_bar/powerline/themes/wm/dvi.json | 18 ++ 8 files changed, 294 insertions(+), 1 deletion(-) create mode 100755 tests/run_bar_tests.sh create mode 100755 tests/test_bar/path/lemonbar create mode 100755 tests/test_bar/path/xrandr create mode 100644 tests/test_bar/powerline/config.json create mode 100644 tests/test_bar/powerline/themes/wm/__main__.json create mode 100644 tests/test_bar/powerline/themes/wm/default.json create mode 100644 tests/test_bar/powerline/themes/wm/dvi.json diff --git a/tests/common.sh b/tests/common.sh index 0b555162..14da0e19 100644 --- a/tests/common.sh +++ b/tests/common.sh @@ -18,7 +18,9 @@ exit_suite() { echo "${FAIL_SUMMARY}" fi export POWERLINE_CURRENT_SUITE="${POWERLINE_CURRENT_SUITE%/*}" - exit $FAILED + if test "x$1" != "x--continue" ; then + exit $FAILED + fi } fail() { diff --git a/tests/run_bar_tests.sh b/tests/run_bar_tests.sh new file mode 100755 index 00000000..2204b2bc --- /dev/null +++ b/tests/run_bar_tests.sh @@ -0,0 +1,194 @@ +#!/bin/sh +. tests/common.sh + +enter_suite bar + +TEST_ROOT="$ROOT/tests/bar" +TEST_PATH="$TEST_ROOT/path" +TEST_STATIC_ROOT="$ROOT/tests/test_bar" + +test -d "$TEST_ROOT" && rm -r "$TEST_ROOT" +mkdir "$TEST_ROOT" +cp -r "$TEST_STATIC_ROOT/path" "$TEST_ROOT" +cp -r "$TEST_STATIC_ROOT/powerline" "$TEST_ROOT" + +export PYTHONPATH="$ROOT${PYTHONPATH:+:}$PYTHONPATH" + +ln -s "$(which "${PYTHON}")" "$TEST_PATH"/python +ln -s "$(which sed)" "$TEST_PATH" +ln -s "$(which cat)" "$TEST_PATH" +ln -s "$(which mkdir)" "$TEST_PATH" +ln -s "$(which basename)" "$TEST_PATH" +ln -s "$TEST_PATH/lemonbar" "$TEST_PATH/bar-aint-recursive" + +DEPRECATED_SCRIPT="$ROOT/powerline/bindings/bar/powerline-bar.py" + +run() { + env -i \ + LANG=C \ + PATH="$TEST_PATH" \ + XDG_CONFIG_HOME="$TEST_ROOT" \ + XDG_CONFIG_DIRS="$TEST_ROOT/dummy" \ + PYTHONPATH="$PYTHONPATH" \ + TEST_ROOT="$TEST_ROOT" \ + LD_LIBRARY_PATH="$LD_LIBRARY_PATH" \ + "$@" || true +} + +display_log() { + local log_file="$1" + echo "$log_file:" + echo '============================================================' + cat -v "$log_file" + echo + echo '____________________________________________________________' +} + +check_log() { + local log_file="$1" + local text="$2" + local warns="$3" + if test "$warns" = "warns" ; then + local warning="$(head -n1 "$log_file" | sed 's/.*://')" + local expwarning="The 'bar' bindings are deprecated, please switch to 'lemonbar'" + if test "x$warning" != "x$expwarning" ; then + echo "Got: $warning" + echo "Exp: $expwarning" + fail "warn" F "Expected warning" + fi + sed -r -i -e '1d' "$log_file" + fi + local line="$(head -n1 "$log_file")" + local linenum="$(cat "$log_file" | wc -l)" + if test $linenum -lt 5 ; then + fail "log:lt" F "Script was run not enough times" + return 1 + elif test $linenum -gt 15 ; then + fail "log:gt" E "Script was run too many times" + return 1 + fi + local expline="%{l}%{F#ffd0d0d0}%{B#ff303030} $text-left %{F-B--u}%{F#ff303030} %{F-B--u}%{r}%{F#ff303030} %{F-B--u}%{F#ffd0d0d0}%{B#ff303030} $text-right %{F-B--u}" + if test "x$expline" != "x$line" ; then + echo "Line: '$line'" + echo "Expected: '$expline'" + fail "log:line" F "Unexpected line" + return 1 + fi + local ret=0 + while test $linenum -gt 0 ; do + echo "$line" >> "$TEST_ROOT/ok" + linenum=$(( linenum - 1 )) + done + if ! diff "$TEST_ROOT/ok" "$log_file" ; then + fail "log:diff" F "Unexpected output" + ret=1 + fi + rm "$TEST_ROOT/ok" + return $ret +} + +killscript() { + kill -KILL $1 || true +} + +if ! test -e "$DEPRECATED_SCRIPT" ; then + # TODO: uncomment when skip is available + # skip "deprecated" "Missing deprecated bar bindings script" + : +else + enter_suite "deprecated" + run python "$DEPRECATED_SCRIPT" $args > "$TEST_ROOT/deprecated.log" 2>&1 & + SPID=$! + sleep 5 + killscript $SPID + if ! check_log "$TEST_ROOT/deprecated.log" "default" warns ; then + display_log "$TEST_ROOT/deprecated.log" + fail "log" F "Checking log failed" + fi + rm "$TEST_ROOT/deprecated.log" + exit_suite --continue +fi + +LEMONBAR_SCRIPT="$ROOT/powerline/bindings/lemonbar/powerline-lemonbar.py" + +if ! test -e "$LEMONBAR_SCRIPT" ; then + # TODO: uncomment when skip is available + # skip "lemonbar" "Missing lemonbar bindings script" + : +else + enter_suite "lemonbar" + for args in "" "-i0.5" "--interval=0.5" "-- test args" "-c bar-aint-recursive" "--height=10"; do + rm -rf "$TEST_ROOT/results" + run python "$LEMONBAR_SCRIPT" $args > "$TEST_ROOT/lemonbar.log" 2>&1 & + SPID=$! + sleep 5 + killscript $SPID + sleep 0.5 + enter_suite "args($args)" + fnum=0 + for file in "$TEST_ROOT/results"/*.log ; do + if ! test -e "$file" ; then + fail "log" E "Log file is missing" + break + fi + fnum=$(( fnum + 1 )) + args_file="${file%.log}.args" + if ! test -e "$args_file" ; then + fail "args" E "$args_file is missing" + else + cat "$args_file" >> "$TEST_ROOT/args.log" + fi + text="dvi" + if cat "$args_file" | grep -q +1 ; then + text="default" + fi + if ! check_log "$file" "$text" ; then + display_log "$file" + fail "log" F "Checking log failed" + fi + rm "$file" + done + if test "$fnum" -ne 2 ; then + fail "fnum" F "Expected two output files" + fi + if test "x${args#--height}" != "x$args" ; then + height="${args#--height}" + height="${height# }" + height="${height#=}" + height="${height%% *}" + fi + command="lemonbar" + if test "x${args#-c}" != "x$args" ; then + command="${args#-c}" + command="${command# }" + command="${command#=}" + command="${command%% *}" + fi + received_args="$(cat "$TEST_ROOT/args.log" | sort)" + rm "$TEST_ROOT/args.log" + script_args="${args#*-- }" + script_args="${script_args# }" + if test "x${script_args}" '=' "x$args" ; then + script_args= + fi + expected_args="$command -g 1920x$height+0${script_args:+ }$script_args${NL}$command -g 1920x$height+1${script_args:+ }$script_args" + if test "x$expected_args" != "x$received_args" ; then + echo "args:${NL}<$received_args>" + echo "expected:${NL}<$expected_args>" + fail "args" F "Expected different args" + fi + if ! test -z "$(cat "$TEST_ROOT/lemonbar.log")" ; then + display_log "$TEST_ROOT/lemonbar.log" + fail "stderr" E "Unexpected script output" + fi + rm "$TEST_ROOT/lemonbar.log" + exit_suite --continue + done + exit_suite --continue +fi + +if test $FAILED -eq 0 ; then + rm -r "$TEST_ROOT" +fi + +exit_suite diff --git a/tests/test_bar/path/lemonbar b/tests/test_bar/path/lemonbar new file mode 100755 index 00000000..13c6030e --- /dev/null +++ b/tests/test_bar/path/lemonbar @@ -0,0 +1,11 @@ +#!/bin/sh + +RES_DIR="$TEST_ROOT/results" +mkdir -p "$RES_DIR" +RES_FILE="$RES_DIR/$$" +while test -e "$RES_FILE.log" ; do + RES_FILE="$RES_FILE.${RANDOM:-`date +%N | sed s/^0*//`}" +done + +echo $(basename $0) "$@" > "$RES_FILE.args" +cat > "$RES_FILE.log" diff --git a/tests/test_bar/path/xrandr b/tests/test_bar/path/xrandr new file mode 100755 index 00000000..d02e3005 --- /dev/null +++ b/tests/test_bar/path/xrandr @@ -0,0 +1,31 @@ +#!/bin/sh + +cat << EOF +Screen 0: minimum 8 x 8, current 1920 x 1200, maximum 16384 x 16384 +DVI-I-0 disconnected (normal left inverted right x axis y axis) +VGA-0 connected 1920x1200+1+0 (normal left inverted right x axis y axis) 520mm x 330mm + 1920x1200 59.95*+ + 1920x1080 60.00 + 1680x1050 59.95 + 1600x1200 60.00 + 1440x900 59.89 + 1280x1024 75.02 60.02 + 1280x800 59.81 + 1152x864 75.00 + 1024x768 75.03 60.00 + 800x600 75.00 60.32 + 640x480 75.00 59.94 +DVI-I-1 connected 1920x1200+0+0 (normal left inverted right x axis y axis) 520mm x 330mm + 1920x1200 59.95*+ + 1920x1080 60.00 + 1680x1050 59.95 + 1600x1200 60.00 + 1440x900 59.89 + 1280x1024 75.02 60.02 + 1280x800 59.81 + 1152x864 75.00 + 1024x768 75.03 60.00 + 800x600 75.00 60.32 + 640x480 75.00 59.94 +HDMI-0 disconnected (normal left inverted right x axis y axis) +EOF diff --git a/tests/test_bar/powerline/config.json b/tests/test_bar/powerline/config.json new file mode 100644 index 00000000..0b35d567 --- /dev/null +++ b/tests/test_bar/powerline/config.json @@ -0,0 +1,9 @@ +{ + "ext": { + "wm": { + "local_themes": { + "DVI-I-1": "dvi" + } + } + } +} diff --git a/tests/test_bar/powerline/themes/wm/__main__.json b/tests/test_bar/powerline/themes/wm/__main__.json new file mode 100644 index 00000000..5654ce8c --- /dev/null +++ b/tests/test_bar/powerline/themes/wm/__main__.json @@ -0,0 +1,10 @@ +{ + "segment_data": { + "time": { + "display": false + }, + "powerline.segments.common.time.date": { + "display": false + } + } +} diff --git a/tests/test_bar/powerline/themes/wm/default.json b/tests/test_bar/powerline/themes/wm/default.json new file mode 100644 index 00000000..d9eaf5f8 --- /dev/null +++ b/tests/test_bar/powerline/themes/wm/default.json @@ -0,0 +1,18 @@ +{ + "segments": { + "left": [ + { + "type": "string", + "highlight_groups": ["time"], + "contents": "default-left" + } + ], + "right": [ + { + "type": "string", + "highlight_groups": ["time"], + "contents": "default-right" + } + ] + } +} diff --git a/tests/test_bar/powerline/themes/wm/dvi.json b/tests/test_bar/powerline/themes/wm/dvi.json new file mode 100644 index 00000000..4cf3731b --- /dev/null +++ b/tests/test_bar/powerline/themes/wm/dvi.json @@ -0,0 +1,18 @@ +{ + "segments": { + "left": [ + { + "type": "string", + "highlight_groups": ["time"], + "contents": "dvi-left" + } + ], + "right": [ + { + "type": "string", + "highlight_groups": ["time"], + "contents": "dvi-right" + } + ] + } +} From 619ae9050eab5eead3ddbfa22baf4f5a5b471120 Mon Sep 17 00:00:00 2001 From: Foo Date: Thu, 7 Jan 2016 05:15:39 +0300 Subject: [PATCH 17/46] Lint test configuration --- tests/run_bar_tests.sh | 7 +++++++ tests/test_bar/powerline/themes/wm/__main__.json | 10 ---------- 2 files changed, 7 insertions(+), 10 deletions(-) delete mode 100644 tests/test_bar/powerline/themes/wm/__main__.json diff --git a/tests/run_bar_tests.sh b/tests/run_bar_tests.sh index 2204b2bc..473ee33c 100755 --- a/tests/run_bar_tests.sh +++ b/tests/run_bar_tests.sh @@ -187,6 +187,13 @@ else exit_suite --continue fi +if ! powerline-lint \ + -p "$ROOT/powerline/config_files" \ + -p "$TEST_STATIC_ROOT/powerline" +then + fail "lint" F "Checking test config failed" +fi + if test $FAILED -eq 0 ; then rm -r "$TEST_ROOT" fi diff --git a/tests/test_bar/powerline/themes/wm/__main__.json b/tests/test_bar/powerline/themes/wm/__main__.json deleted file mode 100644 index 5654ce8c..00000000 --- a/tests/test_bar/powerline/themes/wm/__main__.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "segment_data": { - "time": { - "display": false - }, - "powerline.segments.common.time.date": { - "display": false - } - } -} From 8168ad142865628ef71e4e7679625168551ca5ef Mon Sep 17 00:00:00 2001 From: Foo Date: Fri, 8 Jan 2016 04:22:07 +0300 Subject: [PATCH 18/46] Improve powerline_automan: fix some bugs and add minimal argument MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bugs: - Default action is store_true while it should be just store. - nargs default depends on metavar while it should only depend on action. - REMAINDER nargs is not supported. Minimal argument means minimal mode which removes all section and creates a container with “synopsis” and “description” sections’ contents. --- docs/source/powerline_automan.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/docs/source/powerline_automan.py b/docs/source/powerline_automan.py index 1f79001a..85d241cb 100644 --- a/docs/source/powerline_automan.py +++ b/docs/source/powerline_automan.py @@ -6,6 +6,7 @@ import re import codecs from collections import namedtuple +from argparse import REMAINDER from functools import reduce @@ -61,9 +62,9 @@ def parse_argument(*args, **kwargs): is_option = args[0].startswith('-') is_long_option = args[0].startswith('--') is_short_option = is_option and not is_long_option - action = kwargs.get('action', 'store_true') - multi = kwargs.get('action') in ('append',) - nargs = kwargs.get('nargs') or (1 if kwargs.get('metavar') or action in ('append',) else 0) + action = kwargs.get('action', 'store') + multi = kwargs.get('action') in ('append',) or kwargs.get('nargs') is REMAINDER + nargs = kwargs.get('nargs', (1 if action in ('append', 'store') else 0)) return Argument( names=args, help=u(kwargs.get('help', '')), @@ -165,9 +166,20 @@ def format_usage_arguments(arguments, base_length=None): # `--` is automatically transformed into – (EN DASH) # when parsing into HTML. We do not need this. line[-1] += [nodes.Text(char) for char in name] + elif argument.nargs is REMAINDER: + line.append(nodes.Text('[')) + line.append(nodes.strong()) + line[-1] += [nodes.Text(char) for char in '--'] + line.append(nodes.Text('] ')) if argument.nargs: - assert(argument.nargs in (1, '?')) - with SurroundWith(line, argument.nargs == '?' and argument.is_option): + assert(argument.nargs in (1, '?', REMAINDER)) + with SurroundWith( + line, ( + True + if argument.nargs is REMAINDER + else (argument.nargs == '?' and argument.is_option) + ) + ): if argument.is_long_option: line.append(nodes.Text('=')) line.append(nodes.emphasis(text=argument.metavar)) @@ -337,15 +349,21 @@ class AutoManParser(object): class AutoMan(Directive): required_arguments = 1 optional_arguments = 0 - option_spec = dict(prog=unchanged_required) + option_spec = dict(prog=unchanged_required, minimal=bool) has_content = False def run(self): + minimal = self.options.get('minimal') module = self.arguments[0] template_args = {} template_args.update(get_authors()) get_argparser = __import__(str(module), fromlist=[str('get_argparser')]).get_argparser parser = get_argparser(AutoManParser) + if minimal: + container = nodes.container() + container += parser.automan_usage(self.options['prog']) + container += parser.automan_description() + return [container] synopsis_section = nodes.section( '', nodes.title(text='Synopsis'), From 646b0ea5ea7f3ab75cc3934931d6561404e6497b Mon Sep 17 00:00:00 2001 From: Foo Date: Fri, 8 Jan 2016 04:24:51 +0300 Subject: [PATCH 19/46] Improve powerline-lemonbar documentation Changes: - Usage determination transferred back to argparse (it wraps). - Added complete description of lemonbar script to wm-widgets.rst. - --interval and --height got their arguments clarified by metavar: it was not clear what units they use. - --bar-command got its metavar because it is better then default BAR_COMMAND. - --bar-command short variant is now -C. --- docs/source/usage/wm-widgets.rst | 6 ++++ .../bindings/lemonbar/powerline-lemonbar.py | 29 ++------------- powerline/commands/lemonbar.py | 35 +++++++++++++++++++ tests/run_bar_tests.sh | 6 ++-- 4 files changed, 46 insertions(+), 30 deletions(-) create mode 100644 powerline/commands/lemonbar.py diff --git a/docs/source/usage/wm-widgets.rst b/docs/source/usage/wm-widgets.rst index cd32c763..1aa1a2de 100644 --- a/docs/source/usage/wm-widgets.rst +++ b/docs/source/usage/wm-widgets.rst @@ -73,6 +73,12 @@ Running the binding in i3-mode will require `i3ipc `_ for more information and options. +All ``powerline-lemonbar.py`` arguments: + +.. automan:: powerline.commands.lemonbar + :prog: powerline-lemonbar.py + :minimal: true + I3 bar ====== diff --git a/powerline/bindings/lemonbar/powerline-lemonbar.py b/powerline/bindings/lemonbar/powerline-lemonbar.py index ca658813..ae4ffb34 100755 --- a/powerline/bindings/lemonbar/powerline-lemonbar.py +++ b/powerline/bindings/lemonbar/powerline-lemonbar.py @@ -7,39 +7,14 @@ import re import subprocess from threading import Lock, Timer -from argparse import ArgumentParser, REMAINDER from powerline.lemonbar import LemonbarPowerline from powerline.lib.shell import run_cmd +from powerline.commands.lemonbar import get_argparser if __name__ == '__main__': - parser = ArgumentParser( - description='Powerline BAR bindings.', - usage='%(prog)s [-h] [--i3] [--height HEIGHT] [--interval INTERVAL] [--bar-command CMD] -- args' - ) - parser.add_argument( - '--i3', action='store_true', - help='Subscribe for i3 events.' - ) - parser.add_argument( - '--height', default='', - help='Bar height.' - ) - parser.add_argument( - '--interval', '-i', - type=float, default=0.5, - help='Refresh interval.' - ) - parser.add_argument( - '--bar-command', '-c', - default='lemonbar', - help='Name of the lemonbar executable to use.' - ) - parser.add_argument( - 'args', nargs=REMAINDER, - help='Extra arguments for lemonbar.' - ) + parser = get_argparser() args = parser.parse_args() powerline = LemonbarPowerline() diff --git a/powerline/commands/lemonbar.py b/powerline/commands/lemonbar.py new file mode 100644 index 00000000..547c52cc --- /dev/null +++ b/powerline/commands/lemonbar.py @@ -0,0 +1,35 @@ +# vim:fileencoding=utf-8:noet +# WARNING: using unicode_literals causes errors in argparse +from __future__ import (division, absolute_import, print_function) + +import argparse + + +def get_argparser(ArgumentParser=argparse.ArgumentParser): + parser = ArgumentParser( + description='Powerline BAR bindings.' + ) + parser.add_argument( + '--i3', action='store_true', + help='Subscribe for i3 events.' + ) + parser.add_argument( + '--height', default='', + metavar='PIXELS', help='Bar height.' + ) + parser.add_argument( + '--interval', '-i', + type=float, default=0.5, + metavar='SECONDS', help='Refresh interval.' + ) + parser.add_argument( + '--bar-command', '-C', + default='lemonbar', + metavar='CMD', help='Name of the lemonbar executable to use.' + ) + parser.add_argument( + 'args', nargs=argparse.REMAINDER, + help='Extra arguments for lemonbar. Should be preceded with ``--`` ' + 'argument in order not to be confused with script own arguments.' + ) + return parser diff --git a/tests/run_bar_tests.sh b/tests/run_bar_tests.sh index 473ee33c..7d658ff1 100755 --- a/tests/run_bar_tests.sh +++ b/tests/run_bar_tests.sh @@ -117,7 +117,7 @@ if ! test -e "$LEMONBAR_SCRIPT" ; then : else enter_suite "lemonbar" - for args in "" "-i0.5" "--interval=0.5" "-- test args" "-c bar-aint-recursive" "--height=10"; do + for args in "" "-i0.5" "--interval=0.5" "-- test args" "--bar-command bar-aint-recursive" "--height=10"; do rm -rf "$TEST_ROOT/results" run python "$LEMONBAR_SCRIPT" $args > "$TEST_ROOT/lemonbar.log" 2>&1 & SPID=$! @@ -158,8 +158,8 @@ else height="${height%% *}" fi command="lemonbar" - if test "x${args#-c}" != "x$args" ; then - command="${args#-c}" + if test "x${args#--bar-command}" != "x$args" ; then + command="${args#--bar-command}" command="${command# }" command="${command#=}" command="${command%% *}" From 53aca6b1905e06f1899b70950fcfc0493b82af4c Mon Sep 17 00:00:00 2001 From: S0lll0s Date: Tue, 12 Jan 2016 18:05:40 +0100 Subject: [PATCH 20/46] Add i3wm segment_info keys to developer reference --- docs/source/develop/segments.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/source/develop/segments.rst b/docs/source/develop/segments.rst index d96bb1cb..a47d0a52 100644 --- a/docs/source/develop/segments.rst +++ b/docs/source/develop/segments.rst @@ -517,6 +517,16 @@ Pdb Equal to the length of :py:attr:`pdb.Pdb.stack` at the first invocation of the prompt decremented by one. +i3wm +---- + +``mode`` + Currently active i3 mode (as a string). + +``output`` + ``xrandr`` output name currently drawing to. Currently only available + in lemonbar bindings. + Segment class ============= From bc7d571d0053b91ecc04b45c77c05074481978d6 Mon Sep 17 00:00:00 2001 From: S0lll0s Date: Tue, 12 Jan 2016 23:42:17 +0100 Subject: [PATCH 21/46] Add i3wm segment for singular workspace --- powerline/segments/i3wm.py | 41 +++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/powerline/segments/i3wm.py b/powerline/segments/i3wm.py index 9d214cfe..29093072 100644 --- a/powerline/segments/i3wm.py +++ b/powerline/segments/i3wm.py @@ -51,7 +51,7 @@ def workspaces(pl, segment_info, only_show=None, output=None, strip=0): output = output or segment_info.get('output') return [{ - 'contents': w['name'][min(len(w['name']), strip):], + 'contents': w['name'][strip:], 'highlight_groups': calcgrp(w) } for w in conn.get_workspaces() if (not only_show or any(w[typ] for typ in only_show)) @@ -59,6 +59,45 @@ def workspaces(pl, segment_info, only_show=None, output=None, strip=0): ] +@requires_segment_info +def workspace(pl, segment_info, workspace=None, strip=0): + '''Return the specified workspace name + + :param str workspace: + Specifies which workspace to show. If unspecified, may be set by the + ``list_workspaces`` lister. + + :param int strip: + Specifies how many characters from the front of each workspace name + should be stripped (e.g. to remove workspace numbers). Defaults to zero. + + Highlight groups used: ``workspace`` or ``w_visible``, ``workspace`` or ``w_focused``, ``workspace`` or ``w_urgent``. + ''' + workspace = workspace or segment_info.get('workspace')['name'] + if segment_info.get('workspace') and segment_info.get('workspace')['name'] == workspace: + w = segment_info.get('workspace') + else: + global conn + if not conn: + try: + import i3ipc + except ImportError: + import i3 as conn + else: + conn = i3ipc.Connection() + try: + w = next(( + w for w in get_i3_connection().get_workspaces() + if w['name'] == workspace + )) + except StopIteration: + return None + return [{ + 'contents': w['name'][min(len(w['name']), strip):], + 'highlight_groups': calcgrp(w) + }] + + @requires_segment_info def mode(pl, segment_info, names={'default': None}): '''Returns current i3 mode From 24fa03567e5034728ddb7cdff697dfed058b03ab Mon Sep 17 00:00:00 2001 From: S0lll0s Date: Tue, 12 Jan 2016 22:38:03 +0100 Subject: [PATCH 22/46] Add listers for i3wm workspaces and outputs --- docs/source/configuration/listers.rst | 8 +- docs/source/develop/segments.rst | 6 ++ .../bindings/lemonbar/powerline-lemonbar.py | 9 +-- powerline/lib/dict.py | 8 ++ powerline/lint/imp.py | 6 ++ powerline/listers/i3wm.py | 78 +++++++++++++++++++ powerline/segments/i3wm.py | 4 +- 7 files changed, 108 insertions(+), 11 deletions(-) create mode 100644 powerline/listers/i3wm.py diff --git a/docs/source/configuration/listers.rst b/docs/source/configuration/listers.rst index 04e5371e..e6ba06e7 100644 --- a/docs/source/configuration/listers.rst +++ b/docs/source/configuration/listers.rst @@ -16,8 +16,6 @@ their type and ``segments`` key with a list of segments (a bit more details in More information in :ref:`Writing listers ` section. -Currently only Vim listers are available. - Vim listers ----------- @@ -29,3 +27,9 @@ Pdb listers .. automodule:: powerline.listers.pdb :members: + +i3wm listers +---------- + +.. automodule:: powerline.listers.i3wm + :members: diff --git a/docs/source/develop/segments.rst b/docs/source/develop/segments.rst index a47d0a52..30f4a664 100644 --- a/docs/source/develop/segments.rst +++ b/docs/source/develop/segments.rst @@ -527,6 +527,12 @@ i3wm ``xrandr`` output name currently drawing to. Currently only available in lemonbar bindings. +``workspace`` + dictionary containing the workspace name under the key ``"name"`` and + boolean values for the ``"visible"``, ``"urgent"`` and ``"focused"`` + keys, indicating the state of the workspace. Currently only provided by + the :py:func:`powerline.listers.i3wm.workspace_lister` lister. + Segment class ============= diff --git a/powerline/bindings/lemonbar/powerline-lemonbar.py b/powerline/bindings/lemonbar/powerline-lemonbar.py index ae4ffb34..5a272ab5 100755 --- a/powerline/bindings/lemonbar/powerline-lemonbar.py +++ b/powerline/bindings/lemonbar/powerline-lemonbar.py @@ -9,8 +9,8 @@ import subprocess from threading import Lock, Timer from powerline.lemonbar import LemonbarPowerline -from powerline.lib.shell import run_cmd from powerline.commands.lemonbar import get_argparser +from powerline.listers.i3wm import get_connected_xrandr_outputs if __name__ == '__main__': @@ -20,13 +20,8 @@ if __name__ == '__main__': powerline = LemonbarPowerline() powerline.update_renderer() bars = [] - active_screens = [match.groupdict() for match in re.finditer( - '^(?P[0-9A-Za-z-]+) connected (?P\d+)x(?P\d+)\+(?P\d+)\+(?P\d+)', - run_cmd(powerline.pl, ['xrandr', '-q']), - re.MULTILINE - )] - for screen in active_screens: + for screen in get_connected_xrandr_outputs(powerline.pl): command = [args.bar_command, '-g', '{0}x{1}+{2}'.format(screen['width'], args.height, screen['x'])] + args.args[1:] process = subprocess.Popen(command, stdin=subprocess.PIPE) bars.append((screen['name'], process, int(screen['width']) / 5)) diff --git a/powerline/lib/dict.py b/powerline/lib/dict.py index d8d2088b..c06ab30c 100644 --- a/powerline/lib/dict.py +++ b/powerline/lib/dict.py @@ -78,3 +78,11 @@ def mergedicts_copy(d1, d2): else: ret[k] = d2[k] return ret + + +def updated(d, *args, **kwargs): + '''Copy dictionary and update it with provided arguments + ''' + d = d.copy() + d.update(*args, **kwargs) + return d diff --git a/powerline/lint/imp.py b/powerline/lint/imp.py index 6e402132..399654e1 100644 --- a/powerline/lint/imp.py +++ b/powerline/lint/imp.py @@ -21,6 +21,12 @@ class WithPath(object): def import_function(function_type, name, data, context, echoerr, module): havemarks(name, module) + if module == 'powerline.segments.i3wm' and name == 'workspaces': + echoerr(context='Warning while checking segments (key {key})'.format(key=context.key), + context_mark=name.mark, + problem='segment {0} from {1} is deprecated'.format(name, module), + problem_mark=module.mark) + with WithPath(data['import_paths']): try: func = getattr(__import__(str(module), fromlist=[str(name)]), str(name)) diff --git a/powerline/listers/i3wm.py b/powerline/listers/i3wm.py new file mode 100644 index 00000000..fedb7c81 --- /dev/null +++ b/powerline/listers/i3wm.py @@ -0,0 +1,78 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import re + +from powerline.theme import requires_segment_info +from powerline.lib.shell import run_cmd +from powerline.lib.dict import updated + + +conn = None +XRANDR_OUTPUT_RE = re.compile(r'^(?P[0-9A-Za-z-]+) connected (?P\d+)x(?P\d+)\+(?P\d+)\+(?P\d+)', re.MULTILINE) +def get_connected_xrandr_outputs(pl): + return (match.groupdict() for match in XRANDR_OUTPUT_RE.finditer( + run_cmd(pl, ['xrandr', '-q']) + )) + + +@requires_segment_info +def output_lister(pl, segment_info): + '''List all outputs in segment_info format + ''' + + return ( + ( + updated(segment_info, output=output['name']), + { + 'draw_inner_divider': None + } + ) + for output in get_connected_xrandr_outputs(pl) + ) + + +@requires_segment_info +def workspace_lister(pl, segment_info, only_show=None, output=None): + '''List all workspaces in segment_info format + + Sets the segment info values of ``workspace`` and ``output`` to the name of + the i3 workspace and the ``xrandr`` output respectively and the keys + ``"visible"``, ``"urgent"`` and ``"focused`` to a boolean indicating these + states. + + :param list only_show: + Specifies which workspaces to list. Valid entries are ``"visible"``, + ``"urgent"`` and ``"focused"``. If omitted or ``null`` all workspaces + are listed. + + :param str output: + May be set to the name of an X output. If specified, only workspaces + on that output are listed. Overrides automatic output detection by + the lemonbar renderer and bindings. Set to ``false`` to force + all workspaces to be shown. + ''' + + if output == None: + output = output or segment_info.get('output') + + return ( + ( + updated( + segment_info, + output=w['output'], + workspace={ + 'name': w['name'], + 'visible': w['visible'], + 'urgent': w['urgent'], + 'focused': w['focused'], + }, + ), + { + 'draw_inner_divider': None + } + ) + for w in conn.get_workspaces() + if (((not only_show or any(w[typ] for typ in only_show)) + and (not output or w['output'] == output))) + ) diff --git a/powerline/segments/i3wm.py b/powerline/segments/i3wm.py index 29093072..8be8ada0 100644 --- a/powerline/segments/i3wm.py +++ b/powerline/segments/i3wm.py @@ -54,8 +54,8 @@ def workspaces(pl, segment_info, only_show=None, output=None, strip=0): 'contents': w['name'][strip:], 'highlight_groups': calcgrp(w) } for w in conn.get_workspaces() - if (not only_show or any(w[typ] for typ in only_show)) - and (not output or w['output'] == output) + if ((not only_show or any(w[typ] for typ in only_show)) + and (not output or w['output'] == output)) ] From db99ad1d90e818b1ce9df782edcdc6612277d0eb Mon Sep 17 00:00:00 2001 From: S0lll0s Date: Fri, 15 Jan 2016 15:37:37 +0100 Subject: [PATCH 23/46] Refactor i3wm segment and lemonbar binding Move get_i3_connection and get_xrandr_outputs into bindings.wm --- .../bindings/lemonbar/powerline-lemonbar.py | 2 +- powerline/bindings/wm/__init__.py | 38 ++++++++++++++ powerline/listers/i3wm.py | 16 ++---- powerline/segments/i3wm.py | 52 ++++++++----------- tests/test_segments.py | 2 +- 5 files changed, 65 insertions(+), 45 deletions(-) create mode 100644 powerline/bindings/wm/__init__.py diff --git a/powerline/bindings/lemonbar/powerline-lemonbar.py b/powerline/bindings/lemonbar/powerline-lemonbar.py index 5a272ab5..76372541 100755 --- a/powerline/bindings/lemonbar/powerline-lemonbar.py +++ b/powerline/bindings/lemonbar/powerline-lemonbar.py @@ -10,7 +10,7 @@ from threading import Lock, Timer from powerline.lemonbar import LemonbarPowerline from powerline.commands.lemonbar import get_argparser -from powerline.listers.i3wm import get_connected_xrandr_outputs +from powerline.bindings.wm import get_connected_xrandr_outputs if __name__ == '__main__': diff --git a/powerline/bindings/wm/__init__.py b/powerline/bindings/wm/__init__.py new file mode 100644 index 00000000..6f8be621 --- /dev/null +++ b/powerline/bindings/wm/__init__.py @@ -0,0 +1,38 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import re + +from powerline.theme import requires_segment_info +from powerline.lib.shell import run_cmd + + +conn = None + + +def get_i3_connection(): + '''Return a valid, cached i3 Connection instance + ''' + global conn + if not conn: + try: + import i3ipc + except ImportError: + import i3 as conn + else: + conn = i3ipc.Connection() + return conn + + +XRANDR_OUTPUT_RE = re.compile(r'^(?P[0-9A-Za-z-]+) connected (?P\d+)x(?P\d+)\+(?P\d+)\+(?P\d+)', re.MULTILINE) + + +def get_connected_xrandr_outputs(pl): + '''Iterate over xrandr outputs + + Outputs are represented by a dictionary with ``name``, ``width``, + ``height``, ``x`` and ``y`` keys. + ''' + return (match.groupdict() for match in XRANDR_OUTPUT_RE.finditer( + run_cmd(pl, ['xrandr', '-q']) + )) diff --git a/powerline/listers/i3wm.py b/powerline/listers/i3wm.py index fedb7c81..188e888b 100644 --- a/powerline/listers/i3wm.py +++ b/powerline/listers/i3wm.py @@ -1,19 +1,9 @@ # vim:fileencoding=utf-8:noet from __future__ import (unicode_literals, division, absolute_import, print_function) -import re - from powerline.theme import requires_segment_info -from powerline.lib.shell import run_cmd from powerline.lib.dict import updated - - -conn = None -XRANDR_OUTPUT_RE = re.compile(r'^(?P[0-9A-Za-z-]+) connected (?P\d+)x(?P\d+)\+(?P\d+)\+(?P\d+)', re.MULTILINE) -def get_connected_xrandr_outputs(pl): - return (match.groupdict() for match in XRANDR_OUTPUT_RE.finditer( - run_cmd(pl, ['xrandr', '-q']) - )) +from powerline.bindings.wm import get_i3_connection, get_connected_xrandr_outputs @requires_segment_info @@ -72,7 +62,7 @@ def workspace_lister(pl, segment_info, only_show=None, output=None): 'draw_inner_divider': None } ) - for w in conn.get_workspaces() + for w in get_i3_connection().get_workspaces() if (((not only_show or any(w[typ] for typ in only_show)) - and (not output or w['output'] == output))) + and (not output or w['output'] == output))) ) diff --git a/powerline/segments/i3wm.py b/powerline/segments/i3wm.py index 8be8ada0..b0b405d4 100644 --- a/powerline/segments/i3wm.py +++ b/powerline/segments/i3wm.py @@ -2,9 +2,7 @@ from __future__ import (unicode_literals, division, absolute_import, print_function) from powerline.theme import requires_segment_info - - -conn = None +from powerline.bindings.wm import get_i3_connection def calcgrp(w): @@ -39,23 +37,16 @@ def workspaces(pl, segment_info, only_show=None, output=None, strip=0): Highlight groups used: ``workspace`` or ``w_visible``, ``workspace`` or ``w_focused``, ``workspace`` or ``w_urgent``. ''' - global conn - if not conn: - try: - import i3ipc - except ImportError: - import i3 as conn - else: - conn = i3ipc.Connection() - output = output or segment_info.get('output') - return [{ - 'contents': w['name'][strip:], - 'highlight_groups': calcgrp(w) - } for w in conn.get_workspaces() + return [ + { + 'contents': w['name'][strip:], + 'highlight_groups': calcgrp(w) + } + for w in get_i3_connection().get_workspaces() if ((not only_show or any(w[typ] for typ in only_show)) - and (not output or w['output'] == output)) + and (not output or w['output'] == output)) ] @@ -65,7 +56,8 @@ def workspace(pl, segment_info, workspace=None, strip=0): :param str workspace: Specifies which workspace to show. If unspecified, may be set by the - ``list_workspaces`` lister. + ``list_workspaces`` lister if used, otherwise falls back to + currently focused workspace. :param int strip: Specifies how many characters from the front of each workspace name @@ -73,18 +65,7 @@ def workspace(pl, segment_info, workspace=None, strip=0): Highlight groups used: ``workspace`` or ``w_visible``, ``workspace`` or ``w_focused``, ``workspace`` or ``w_urgent``. ''' - workspace = workspace or segment_info.get('workspace')['name'] - if segment_info.get('workspace') and segment_info.get('workspace')['name'] == workspace: - w = segment_info.get('workspace') - else: - global conn - if not conn: - try: - import i3ipc - except ImportError: - import i3 as conn - else: - conn = i3ipc.Connection() + if workspace: try: w = next(( w for w in get_i3_connection().get_workspaces() @@ -92,6 +73,17 @@ def workspace(pl, segment_info, workspace=None, strip=0): )) except StopIteration: return None + elif segment_info.get('workspace'): + w = segment_info['workspace'] + else: + try: + w = next(( + w for w in get_i3_connection().get_workspaces() + if w['focused'] + )) + except StopIteration: + return None + return [{ 'contents': w['name'][min(len(w['name']), strip):], 'highlight_groups': calcgrp(w) diff --git a/tests/test_segments.py b/tests/test_segments.py index edb69a59..3cb1aa0e 100644 --- a/tests/test_segments.py +++ b/tests/test_segments.py @@ -885,7 +885,7 @@ class TestWthr(TestCommon): class TestI3WM(TestCase): def test_workspaces(self): pl = Pl() - with replace_attr(i3wm, 'conn', Args(get_workspaces=lambda: iter([ + with replace_attr(i3wm, 'conn', Args(get_i3_workspaces=lambda: iter([ {'name': '1: w1', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': False}, {'name': '2: w2', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': True}, {'name': '3: w3', 'output': 'HDMI1', 'focused': False, 'urgent': True, 'visible': True}, From 10243ad89f1a04068379568067ddf3f2f2e4f538 Mon Sep 17 00:00:00 2001 From: S0lll0s Date: Thu, 4 Feb 2016 04:41:58 +0300 Subject: [PATCH 24/46] Make workspace(strip) strip `{number}:` prefix if true --- powerline/segments/i3wm.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/powerline/segments/i3wm.py b/powerline/segments/i3wm.py index b0b405d4..eb0d3c44 100644 --- a/powerline/segments/i3wm.py +++ b/powerline/segments/i3wm.py @@ -1,10 +1,15 @@ # vim:fileencoding=utf-8:noet from __future__ import (unicode_literals, division, absolute_import, print_function) +import re + from powerline.theme import requires_segment_info from powerline.bindings.wm import get_i3_connection +WORKSPACE_REGEX = re.compile(r'^[0-9]+: ?') + + def calcgrp(w): group = [] if w['focused']: @@ -17,6 +22,12 @@ def calcgrp(w): return group +def format_name(name, strip=False): + if strip: + return WORKSPACE_REGEX.sub('', name, count=1) + return name + + @requires_segment_info def workspaces(pl, segment_info, only_show=None, output=None, strip=0): '''Return list of used workspaces @@ -51,7 +62,7 @@ def workspaces(pl, segment_info, only_show=None, output=None, strip=0): @requires_segment_info -def workspace(pl, segment_info, workspace=None, strip=0): +def workspace(pl, segment_info, workspace=None, strip=False): '''Return the specified workspace name :param str workspace: @@ -59,9 +70,9 @@ def workspace(pl, segment_info, workspace=None, strip=0): ``list_workspaces`` lister if used, otherwise falls back to currently focused workspace. - :param int strip: - Specifies how many characters from the front of each workspace name - should be stripped (e.g. to remove workspace numbers). Defaults to zero. + :param bool strip: + Specifies whether workspace numbers (in the ``1: name`` format) should + be stripped from workspace names before being displayed. Defaults to false. Highlight groups used: ``workspace`` or ``w_visible``, ``workspace`` or ``w_focused``, ``workspace`` or ``w_urgent``. ''' @@ -85,7 +96,7 @@ def workspace(pl, segment_info, workspace=None, strip=0): return None return [{ - 'contents': w['name'][min(len(w['name']), strip):], + 'contents': format_name(w['name'], strip=strip), 'highlight_groups': calcgrp(w) }] From 623c1f85bc5411552088a264902037e23ffb45ac Mon Sep 17 00:00:00 2001 From: S0lll0s Date: Fri, 15 Jan 2016 18:18:04 +0100 Subject: [PATCH 25/46] Add tests for i3wm listers and workspace segment --- tests/test_listers.py | 227 +++++++++++++++++++++++++++++++++++++++++ tests/test_segments.py | 43 +++++++- 2 files changed, 266 insertions(+), 4 deletions(-) create mode 100644 tests/test_listers.py diff --git a/tests/test_listers.py b/tests/test_listers.py new file mode 100644 index 00000000..3d3ed094 --- /dev/null +++ b/tests/test_listers.py @@ -0,0 +1,227 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import powerline.listers.i3wm as i3wm + +from tests.lib import Args, replace_attr, Pl +from tests import TestCase + + +class TestI3WM(TestCase): + @staticmethod + def get_workspaces(): + return iter([ + {'name': '1: w1', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': False}, + {'name': '2: w2', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': True}, + {'name': '3: w3', 'output': 'HDMI1', 'focused': False, 'urgent': True, 'visible': True}, + {'name': '4: w4', 'output': 'DVI01', 'focused': True, 'urgent': True, 'visible': True}, + ]) + + @staticmethod + def get_outputs(pl): + return iter([ + {'name': 'LVDS1'}, + {'name': 'HDMI1'}, + {'name': 'DVI01'}, + ]) + + def test_output_lister(self): + pl = Pl() + with replace_attr(i3wm, 'get_connected_xrandr_outputs', self.get_outputs): + self.assertEqual( + list(i3wm.output_lister(pl=pl, segment_info={'a': 1})), + [ + ({'a': 1, 'output': 'LVDS1'}, {'draw_inner_divider': None}), + ({'a': 1, 'output': 'HDMI1'}, {'draw_inner_divider': None}), + ({'a': 1, 'output': 'DVI01'}, {'draw_inner_divider': None}), + ] + ) + + def test_workspace_lister(self): + pl = Pl() + with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)): + self.assertEqual( + list(i3wm.workspace_lister(pl=pl, segment_info={'a': 1})), + [ + ({ + 'a': 1, + 'output': 'LVDS1', + 'workspace': { + 'name': '1: w1', + 'focused': False, + 'urgent': False, + 'visible': False + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'LVDS1', + 'workspace': { + 'name': '2: w2', + 'focused': False, + 'urgent': False, + 'visible': True + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'HDMI1', + 'workspace': { + 'name': '3: w3', + 'focused': False, + 'urgent': True, + 'visible': True + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'DVI01', + 'workspace': { + 'name': '4: w4', + 'focused': True, + 'urgent': True, + 'visible': True + } + }, {'draw_inner_divider': None}), + ] + ) + + self.assertEqual( + list(i3wm.workspace_lister(pl=pl, segment_info={'a': 1}, output='LVDS1')), + [ + ({ + 'a': 1, + 'output': 'LVDS1', + 'workspace': { + 'name': '1: w1', + 'focused': False, + 'urgent': False, + 'visible': False + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'LVDS1', + 'workspace': { + 'name': '2: w2', + 'focused': False, + 'urgent': False, + 'visible': True + } + }, {'draw_inner_divider': None}), + ] + ) + + self.assertEqual( + list(i3wm.workspace_lister( + pl=pl, + segment_info={'a': 1, 'output': 'LVDS1'} + )), + [ + ({ + 'a': 1, + 'output': 'LVDS1', + 'workspace': { + 'name': '1: w1', + 'focused': False, + 'urgent': False, + 'visible': False + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'LVDS1', + 'workspace': { + 'name': '2: w2', + 'focused': False, + 'urgent': False, + 'visible': True + } + }, {'draw_inner_divider': None}), + ] + ) + + self.assertEqual( + list(i3wm.workspace_lister( + pl=pl, + segment_info={'a': 1, 'output': 'LVDS1'}, + output=False + )), + [ + ({ + 'a': 1, + 'output': 'LVDS1', + 'workspace': { + 'name': '1: w1', + 'focused': False, + 'urgent': False, + 'visible': False + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'LVDS1', + 'workspace': { + 'name': '2: w2', + 'focused': False, + 'urgent': False, + 'visible': True + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'HDMI1', + 'workspace': { + 'name': '3: w3', + 'focused': False, + 'urgent': True, + 'visible': True + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'DVI01', + 'workspace': { + 'name': '4: w4', + 'focused': True, + 'urgent': True, + 'visible': True + } + }, {'draw_inner_divider': None}), + ] + ) + + self.assertEqual( + list(i3wm.workspace_lister( + pl=pl, + segment_info={'a': 1}, + only_show=['focused', 'urgent'] + )), + [ + ({ + 'a': 1, + 'output': 'HDMI1', + 'workspace': { + 'name': '3: w3', + 'focused': False, + 'urgent': True, + 'visible': True + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'DVI01', + 'workspace': { + 'name': '4: w4', + 'focused': True, + 'urgent': True, + 'visible': True + } + }, {'draw_inner_divider': None}), + ] + ) + + +if __name__ == '__main__': + from tests import main + main() diff --git a/tests/test_segments.py b/tests/test_segments.py index 3cb1aa0e..55fa8137 100644 --- a/tests/test_segments.py +++ b/tests/test_segments.py @@ -883,14 +883,26 @@ class TestWthr(TestCommon): class TestI3WM(TestCase): - def test_workspaces(self): - pl = Pl() - with replace_attr(i3wm, 'conn', Args(get_i3_workspaces=lambda: iter([ + @staticmethod + def get_workspaces(): + return iter([ {'name': '1: w1', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': False}, {'name': '2: w2', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': True}, {'name': '3: w3', 'output': 'HDMI1', 'focused': False, 'urgent': True, 'visible': True}, {'name': '4: w4', 'output': 'DVI01', 'focused': True, 'urgent': True, 'visible': True}, - ]))): + ]) + + @staticmethod + def get_outputs(pl): + return iter([ + {'name': 'LVDS1'}, + {'name': 'HDMI1'}, + {'name': 'DVI01'}, + ]) + + def test_workspaces(self): + pl = Pl() + with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)): segment_info = {} self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info), [ @@ -936,6 +948,29 @@ class TestI3WM(TestCase): {'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']}, ]) + def test_workspace(self): + pl = Pl() + with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)): + segment_info = {} + + self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='1: w1'), [ + {'contents': '1: w1', 'highlight_groups': ['workspace']}, + ]) + self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='3: w3', strip=True), [ + {'contents': 'w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, + ]) + self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='9: w9'), None) + self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info), [ + {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + ]) + segment_info['workspace'] = next(self.get_workspaces()) + self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='4: w4'), [ + {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + ]) + self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, strip=True), [ + {'contents': 'w1', 'highlight_groups': ['workspace']}, + ]) + def test_mode(self): pl = Pl() self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'default'}), None) From f3fe6d99f6e23ed8b00339dcdda6f8d166cdb15d Mon Sep 17 00:00:00 2001 From: "mr.Shu" Date: Sun, 7 Feb 2016 15:32:12 +0100 Subject: [PATCH 26/46] docs: Fix a small typo in develop/segments.rst * The display_condition key in the docs was rendered as 'display_condition`' due to a small typo. This commit fixes that. Signed-off-by: mr.Shu --- docs/source/develop/segments.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/develop/segments.rst b/docs/source/develop/segments.rst index 30f4a664..543ddd57 100644 --- a/docs/source/develop/segments.rst +++ b/docs/source/develop/segments.rst @@ -305,7 +305,7 @@ Segment dictionary contains the following keys: ``side`` Segment side: ``right`` or ``left``. - ``display_condition``` + ``display_condition`` Contains function that takes three position parameters: :py:class:`powerline.PowerlineLogger` instance, :ref:`segment_info ` dictionary and current mode and returns either ``True`` From 3dd71c61daea989e5938f38f38a9790bea86eac3 Mon Sep 17 00:00:00 2001 From: "mr.Shu" Date: Sun, 7 Feb 2016 15:48:00 +0100 Subject: [PATCH 27/46] fix: Make dbus battery status consistent * In #1499 a change was introduced that made use of the "Discharging" status of a battery in order to detect whether the AC power is available. The DBus part of the function that gets this data, however, stayed unchanged. In order to keep it consistent this commit changes it in such a way that it preforms the same function as the code introduced in #1499 by checking for the Discharging state of the battery as described here: http://upower.freedesktop.org/docs/Device.html#Device:State Signed-off-by: mr.Shu --- powerline/segments/common/bat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerline/segments/common/bat.py b/powerline/segments/common/bat.py index ac426a05..0579057e 100644 --- a/powerline/segments/common/bat.py +++ b/powerline/segments/common/bat.py @@ -57,7 +57,7 @@ def _fetch_battery_info(pl): dbus.Interface(dev, dbus_interface=devinterface).Get( devtype_name, 'State' - ) == 1 + ) != 2 ) pl.debug('Not using DBUS+UPower as no batteries were found') From 953a55f789e138bf6f49cdcd92aa72b32f942122 Mon Sep 17 00:00:00 2001 From: S0lll0s Date: Mon, 8 Feb 2016 22:09:30 +0100 Subject: [PATCH 28/46] Fix output detection when setting primary outputs in powerline.bindings.wm.get_connected_xrandr_outputs --- powerline/bindings/wm/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/powerline/bindings/wm/__init__.py b/powerline/bindings/wm/__init__.py index 6f8be621..0f2cc9d6 100644 --- a/powerline/bindings/wm/__init__.py +++ b/powerline/bindings/wm/__init__.py @@ -24,14 +24,14 @@ def get_i3_connection(): return conn -XRANDR_OUTPUT_RE = re.compile(r'^(?P[0-9A-Za-z-]+) connected (?P\d+)x(?P\d+)\+(?P\d+)\+(?P\d+)', re.MULTILINE) +XRANDR_OUTPUT_RE = re.compile(r'^(?P[0-9A-Za-z-]+) connected(?P primary)? (?P\d+)x(?P\d+)\+(?P\d+)\+(?P\d+)', re.MULTILINE) def get_connected_xrandr_outputs(pl): '''Iterate over xrandr outputs Outputs are represented by a dictionary with ``name``, ``width``, - ``height``, ``x`` and ``y`` keys. + ``height``, ``primary``, ``x`` and ``y`` keys. ''' return (match.groupdict() for match in XRANDR_OUTPUT_RE.finditer( run_cmd(pl, ['xrandr', '-q']) From 73dcc78136be9fe42cd8d6ed471ef038cd78c76d Mon Sep 17 00:00:00 2001 From: Nikolai Aleksandrovich Pavlov Date: Wed, 10 Feb 2016 23:10:02 +0300 Subject: [PATCH 29/46] Update command used to install macvim with brew Flag `--override-system-vim` is deprecated. Fixes #1526. --- docs/source/installation/osx.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/installation/osx.rst b/docs/source/installation/osx.rst index 5a85e692..55b8ebf8 100644 --- a/docs/source/installation/osx.rst +++ b/docs/source/installation/osx.rst @@ -56,7 +56,7 @@ Vim installation Any terminal vim version with Python 3.2+ or Python 2.6+ support should work, but MacVim users need to install it using the following command:: - brew install macvim --env-std --override-system-vim + brew install macvim --env-std --with-override-system-vim Fonts installation ================== From 71f56ff1025d433dd7f3a47836f32b15283561f5 Mon Sep 17 00:00:00 2001 From: Micha Gorelick Date: Sat, 20 Feb 2016 16:25:34 -0500 Subject: [PATCH 30/46] Multiple Battery Support --- powerline/segments/common/bat.py | 70 +++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/powerline/segments/common/bat.py b/powerline/segments/common/bat.py index 0579057e..2d7c9982 100644 --- a/powerline/segments/common/bat.py +++ b/powerline/segments/common/bat.py @@ -30,6 +30,7 @@ def _fetch_battery_info(pl): else: devinterface = 'org.freedesktop.DBus.Properties' devtype_name = interface + '.Device' + devices = [] for devpath in up.EnumerateDevices(dbus_interface=interface): dev = bus.get_object(interface, devpath) devget = lambda what: dev.Get( @@ -46,39 +47,62 @@ def _fetch_battery_info(pl): if not bool(devget('PowerSupply')): pl.debug('Not using DBUS+UPower with {0}: not a power supply', devpath) continue + devices.append(devpath) pl.debug('Using DBUS+UPower with {0}', devpath) - return lambda pl: ( - float( - dbus.Interface(dev, dbus_interface=devinterface).Get( + if devices: + def _flatten_battery(pl): + energy = 0.0 + energy_full = 0.0 + state = True + for devpath in devices: + dev = bus.get_object(interface, devpath) + energy_full += float( + dbus.Interface(dev, dbus_interface=devinterface).Get( + devtype_name, + 'EnergyFull' + ), + ) + energy += float( + dbus.Interface(dev, dbus_interface=devinterface).Get( + devtype_name, + 'Energy' + ), + ) + state &= dbus.Interface(dev, dbus_interface=devinterface).Get( devtype_name, - 'Percentage' - ), - ), - dbus.Interface(dev, dbus_interface=devinterface).Get( - devtype_name, - 'State' - ) != 2 - ) + 'State' + ) != 2 + return (energy * 100.0/ energy_full), state + return _flatten_battery pl.debug('Not using DBUS+UPower as no batteries were found') if os.path.isdir('/sys/class/power_supply'): - linux_capacity_fmt = '/sys/class/power_supply/{0}/capacity' + linux_energy_full_fmt = '/sys/class/power_supply/{0}/enery_full' + linux_energy_fmt = '/sys/class/power_supply/{0}/enery' linux_status_fmt = '/sys/class/power_supply/{0}/status' + devices = [] for linux_supplier in os.listdir('/sys/class/power_supply'): - cap_path = linux_capacity_fmt.format(linux_supplier) - status_path = linux_status_fmt.format(linux_supplier) - if not os.path.exists(cap_path): + energy_path = linux_energy_fmt.format(linux_supplier) + if not os.path.exists(energy_path): continue pl.debug('Using /sys/class/power_supply with battery {0}', linux_supplier) + devices.append(linux_supplier) + if devices: def _get_battery_status(pl): - with open(cap_path, 'r') as f: - _capacity = int(float(f.readline().split()[0])) - try: - with open(status_path, 'r') as f: - _ac_powered = (f.readline().strip() != 'Discharging') - except IOError: - _ac_powered = None - return _capacity, _ac_powered + energy = 0.0 + energy_full = 0.0 + state = True + for device in devices: + with open(linux_energy_full_fmt.format(device), 'r') as f: + energy_full += int(float(f.readline().split()[0])) + with open(linux_energy_fmt.format(device), 'r') as f: + energy += int(float(f.readline().split()[0])) + try: + with open(linux_status_fmt.format(device), 'r') as f: + state &= (f.readline().strip() != 'Discharging') + except IOError: + state = None + return (energy * 100.0 / energy_full), state return _get_battery_status pl.debug('Not using /sys/class/power_supply as no batteries were found') else: From ff2b37a63321b06fceb166bb939d03806e825658 Mon Sep 17 00:00:00 2001 From: Micha Gorelick Date: Sat, 20 Feb 2016 16:30:57 -0500 Subject: [PATCH 31/46] flake8 --- powerline/segments/common/bat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerline/segments/common/bat.py b/powerline/segments/common/bat.py index 2d7c9982..160d9430 100644 --- a/powerline/segments/common/bat.py +++ b/powerline/segments/common/bat.py @@ -72,7 +72,7 @@ def _fetch_battery_info(pl): devtype_name, 'State' ) != 2 - return (energy * 100.0/ energy_full), state + return (energy * 100.0 / energy_full), state return _flatten_battery pl.debug('Not using DBUS+UPower as no batteries were found') From 7e319f2edecc1623e753c1d929d848a96359d7b4 Mon Sep 17 00:00:00 2001 From: Micha Gorelick Date: Sun, 21 Feb 2016 00:19:17 -0500 Subject: [PATCH 32/46] filename bugfix Filename reference in `/sys/` method for the battery segment referenced a non-existant file. --- powerline/segments/common/bat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/powerline/segments/common/bat.py b/powerline/segments/common/bat.py index 160d9430..a0b89245 100644 --- a/powerline/segments/common/bat.py +++ b/powerline/segments/common/bat.py @@ -77,8 +77,8 @@ def _fetch_battery_info(pl): pl.debug('Not using DBUS+UPower as no batteries were found') if os.path.isdir('/sys/class/power_supply'): - linux_energy_full_fmt = '/sys/class/power_supply/{0}/enery_full' - linux_energy_fmt = '/sys/class/power_supply/{0}/enery' + linux_energy_full_fmt = '/sys/class/power_supply/{0}/energy_full' + linux_energy_fmt = '/sys/class/power_supply/{0}/energy_now' linux_status_fmt = '/sys/class/power_supply/{0}/status' devices = [] for linux_supplier in os.listdir('/sys/class/power_supply'): From 289e59e789c8725486d7953910a978d4b50a8ccd Mon Sep 17 00:00:00 2001 From: Kamus Hadenes Date: Wed, 9 Dec 2015 17:17:16 -0200 Subject: [PATCH 33/46] Add support for OS X en0 on internal_ip --- powerline/segments/common/net.py | 1 + 1 file changed, 1 insertion(+) diff --git a/powerline/segments/common/net.py b/powerline/segments/common/net.py index eb5b4bcf..47c2ce71 100644 --- a/powerline/segments/common/net.py +++ b/powerline/segments/common/net.py @@ -74,6 +74,7 @@ else: _interface_starts = { 'eth': 10, # Regular ethernet adapters : eth1 'enp': 10, # Regular ethernet adapters, Gentoo : enp2s0 + 'en': 10, # OS X : en0 'ath': 9, # Atheros WiFi adapters : ath0 'wlan': 9, # Other WiFi adapters : wlan1 'wlp': 9, # Other WiFi adapters, Gentoo : wlp5s0 From 00212fa20866824ec1e2591f231bc8d743b8a8c6 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 6 Mar 2016 19:50:57 +0100 Subject: [PATCH 34/46] docs/source/usage/other.rst: fix typo in note --- docs/source/usage/other.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/usage/other.rst b/docs/source/usage/other.rst index e08c0632..1646a89a 100644 --- a/docs/source/usage/other.rst +++ b/docs/source/usage/other.rst @@ -89,8 +89,8 @@ root `):: .. note:: The availability of the ``powerline-config`` command is required for - powerline support. DLlocation of this script may be specified via - ``$POWERLINE_CONFIG_COMMAND`` environment variable. + powerline support. The location of this script may be specified via + the ``$POWERLINE_CONFIG_COMMAND`` environment variable. .. note:: It is advised to run ``powerline-daemon`` before adding the above line to From f710a87ba68bdb9bbcaa161f78f38e9ba649f022 Mon Sep 17 00:00:00 2001 From: s-ol Date: Wed, 9 Mar 2016 22:17:31 +0100 Subject: [PATCH 35/46] Add segments.i3wm.scratchpad --- powerline/config_files/themes/ascii.json | 9 ++++ powerline/config_files/themes/powerline.json | 9 ++++ .../themes/powerline_unicode7.json | 9 ++++ powerline/config_files/themes/unicode.json | 9 ++++ .../config_files/themes/unicode_terminus.json | 9 ++++ .../themes/unicode_terminus_condensed.json | 9 ++++ powerline/segments/i3wm.py | 44 +++++++++++++++++-- 7 files changed, 95 insertions(+), 3 deletions(-) diff --git a/powerline/config_files/themes/ascii.json b/powerline/config_files/themes/ascii.json index 9e876734..7b12d446 100644 --- a/powerline/config_files/themes/ascii.json +++ b/powerline/config_files/themes/ascii.json @@ -131,6 +131,15 @@ "args": { "text": "+" } + }, + + "powerline.segments.i3wm.scratchpad": { + "args": { + "icons": { + "fresh": "O", + "changed": "X" + } + } } } } diff --git a/powerline/config_files/themes/powerline.json b/powerline/config_files/themes/powerline.json index c33b5c1f..4ca9f0ee 100644 --- a/powerline/config_files/themes/powerline.json +++ b/powerline/config_files/themes/powerline.json @@ -129,6 +129,15 @@ "args": { "text": "+" } + }, + + "powerline.segments.i3wm.scratchpad": { + "args": { + "icons": { + "fresh": "●", + "changed": "○" + } + } } } } diff --git a/powerline/config_files/themes/powerline_unicode7.json b/powerline/config_files/themes/powerline_unicode7.json index bfa86fe0..d470d3a9 100644 --- a/powerline/config_files/themes/powerline_unicode7.json +++ b/powerline/config_files/themes/powerline_unicode7.json @@ -143,6 +143,15 @@ "args": { "text": "🖫⃥" } + }, + + "powerline.segments.i3wm.scratchpad": { + "args": { + "icons": { + "fresh": "●", + "changed": "○" + } + } } } } diff --git a/powerline/config_files/themes/unicode.json b/powerline/config_files/themes/unicode.json index eadfc870..4b52fd3b 100644 --- a/powerline/config_files/themes/unicode.json +++ b/powerline/config_files/themes/unicode.json @@ -129,6 +129,15 @@ "args": { "text": "+" } + }, + + "powerline.segments.i3wm.scratchpad": { + "args": { + "icons": { + "fresh": "●", + "changed": "○" + } + } } } } diff --git a/powerline/config_files/themes/unicode_terminus.json b/powerline/config_files/themes/unicode_terminus.json index 8c1e045a..b7b005e4 100644 --- a/powerline/config_files/themes/unicode_terminus.json +++ b/powerline/config_files/themes/unicode_terminus.json @@ -129,6 +129,15 @@ "args": { "text": "+" } + }, + + "powerline.segments.i3wm.scratchpad": { + "args": { + "icons": { + "fresh": "●", + "changed": "○" + } + } } } } diff --git a/powerline/config_files/themes/unicode_terminus_condensed.json b/powerline/config_files/themes/unicode_terminus_condensed.json index 1c567dc7..fc9e90aa 100644 --- a/powerline/config_files/themes/unicode_terminus_condensed.json +++ b/powerline/config_files/themes/unicode_terminus_condensed.json @@ -130,6 +130,15 @@ "args": { "text": "+" } + }, + + "powerline.segments.i3wm.scratchpad": { + "args": { + "icons": { + "fresh": "●", + "changed": "○" + } + } } } } diff --git a/powerline/segments/i3wm.py b/powerline/segments/i3wm.py index eb0d3c44..3f508ee2 100644 --- a/powerline/segments/i3wm.py +++ b/powerline/segments/i3wm.py @@ -10,7 +10,7 @@ from powerline.bindings.wm import get_i3_connection WORKSPACE_REGEX = re.compile(r'^[0-9]+: ?') -def calcgrp(w): +def workspace_groups(w): group = [] if w['focused']: group.append('w_focused') @@ -53,7 +53,7 @@ def workspaces(pl, segment_info, only_show=None, output=None, strip=0): return [ { 'contents': w['name'][strip:], - 'highlight_groups': calcgrp(w) + 'highlight_groups': workspace_groups(w) } for w in get_i3_connection().get_workspaces() if ((not only_show or any(w[typ] for typ in only_show)) @@ -97,7 +97,7 @@ def workspace(pl, segment_info, workspace=None, strip=False): return [{ 'contents': format_name(w['name'], strip=strip), - 'highlight_groups': calcgrp(w) + 'highlight_groups': workspace_groups(w) }] @@ -115,3 +115,41 @@ def mode(pl, segment_info, names={'default': None}): if mode in names: return names[mode] return mode + + +def scratchpad_groups(w): + group = [] + if w.urgent: + group.append('scratchpad:urgent') + if w.nodes[0].focused: + group.append('scratchpad:focused') + if w.workspace().name != '__i3_scratch': + group.append('scratchpad:visible') + group.append('scratchpad') + return group + + +SCRATCHPAD_ICONS = { + 'fresh': 'O', + 'changed': 'X', +} + + +def scratchpad(pl, icons=SCRATCHPAD_ICONS): + '''Returns the windows currently on the scratchpad + + :param dict icons: + Specifies the strings to show for the different scratchpad window states. Must + contain the keys ``fresh`` and ``changed``. + + Highlight groups used: ``scratchpad`` or ``scratchpad:visible``, ``scratchpad`` or ``scratchpad:focused``, ``scratchpad`` or ``scratchpad:urgent``. + ''' + + return [ + { + 'contents': icons.get(w.scratchpad_state, icons['changed']), + 'highlight_groups': scratchpad_groups(w) + } + for w in get_i3_connection().get_tree().descendents() + if w.scratchpad_state != 'none' + ] From e9b70b9edf35e81774e04fb29b469f5231a2f3f0 Mon Sep 17 00:00:00 2001 From: Foo Date: Sat, 19 Mar 2016 23:07:01 +0300 Subject: [PATCH 36/46] Make Popen.communicate receive only bytes Fixes #1547 Closes #1548 --- powerline/lib/shell.py | 5 +++-- tests/lib/__init__.py | 7 ++++++- tests/test_lib.py | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/powerline/lib/shell.py b/powerline/lib/shell.py index fcdc35ce..2082e82e 100644 --- a/powerline/lib/shell.py +++ b/powerline/lib/shell.py @@ -7,7 +7,7 @@ import os from subprocess import Popen, PIPE from functools import partial -from powerline.lib.encoding import get_preferred_input_encoding +from powerline.lib.encoding import get_preferred_input_encoding, get_preferred_output_encoding if sys.platform.startswith('win32'): @@ -36,7 +36,8 @@ def run_cmd(pl, cmd, stdin=None, strip=True): pl.exception('Could not execute command ({0}): {1}', e, cmd) return None else: - stdout, err = p.communicate(stdin) + stdout, err = p.communicate( + stdin if stdin is None else stdin.encode(get_preferred_output_encoding())) stdout = stdout.decode(get_preferred_input_encoding()) return stdout.strip() if strip else stdout diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py index b9c0d3ad..38287c82 100644 --- a/tests/lib/__init__.py +++ b/tests/lib/__init__.py @@ -16,11 +16,16 @@ class Pl(object): self.use_daemon_threads = True for meth in ('error', 'warn', 'debug', 'exception', 'info'): - exec (( + exec(( 'def {0}(self, msg, *args, **kwargs):\n' ' self.{0}s.append((kwargs.get("prefix") or self.prefix, msg, args, kwargs))\n' ).format(meth)) + def __nonzero__(self): + return bool(self.exceptions or self.errors or self.warns) + + __bool__ = __nonzero__ + class Args(object): theme_override = {} diff --git a/tests/test_lib.py b/tests/test_lib.py index e6b28624..6c7aac25 100644 --- a/tests/test_lib.py +++ b/tests/test_lib.py @@ -17,6 +17,7 @@ from powerline.lib.vcs import guess, get_fallback_create_watcher from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment from powerline.lib.monotonic import monotonic from powerline.lib.vcs.git import git_directory +from powerline.lib.shell import run_cmd import powerline.lib.unicode as plu @@ -48,6 +49,24 @@ def thread_number(): return len(threading.enumerate()) +class TestShell(TestCase): + def test_run_cmd(self): + pl = Pl() + self.assertEqual(run_cmd(pl, ['xxx_nonexistent_command_xxx']), None) + self.assertEqual(len(pl.exceptions), 1) + pl = Pl() + self.assertEqual(run_cmd(pl, ['echo', ' test ']), 'test') + self.assertFalse(pl) + self.assertEqual(run_cmd(pl, ['echo', ' test '], strip=True), 'test') + self.assertFalse(pl) + self.assertEqual(run_cmd(pl, ['echo', ' test '], strip=False), ' test \n') + self.assertFalse(pl) + self.assertEqual(run_cmd(pl, ['cat'], stdin='test'), 'test') + self.assertFalse(pl) + self.assertEqual(run_cmd(pl, ['sh', '-c', 'cat >&2'], stdin='test'), '') + self.assertFalse(pl) + + class TestThreaded(TestCase): def test_threaded_segment(self): log = [] From eec80dfb6cc97225ed3c8864034c2fb404dcbf16 Mon Sep 17 00:00:00 2001 From: s-ol Date: Sat, 19 Mar 2016 23:42:44 +0100 Subject: [PATCH 37/46] Add test for segments.i3wm.scratchpad --- tests/test_segments.py | 47 +++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/tests/test_segments.py b/tests/test_segments.py index 55fa8137..027d6227 100644 --- a/tests/test_segments.py +++ b/tests/test_segments.py @@ -892,14 +892,6 @@ class TestI3WM(TestCase): {'name': '4: w4', 'output': 'DVI01', 'focused': True, 'urgent': True, 'visible': True}, ]) - @staticmethod - def get_outputs(pl): - return iter([ - {'name': 'LVDS1'}, - {'name': 'HDMI1'}, - {'name': 'DVI01'}, - ]) - def test_workspaces(self): pl = Pl() with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)): @@ -978,6 +970,45 @@ class TestI3WM(TestCase): self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'default'}, names={'default': 'test'}), 'test') self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'test'}, names={'default': 'test', 'test': 't'}), 't') + def test_scratchpad(self): + class Conn(object): + def get_tree(self): + return self + + def descendents(self): + nodes_unfocused = [Args(focused = False)] + nodes_focused = [Args(focused = True)] + + workspace_scratch = lambda: Args(name='__i3_scratch') + workspace_noscratch = lambda: Args(name='2: www') + return [ + Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused), + Args(scratchpad_state='changed', urgent=True, workspace=workspace_noscratch, nodes=nodes_focused), + Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused), + Args(scratchpad_state=None, urgent=False, workspace=workspace_noscratch, nodes=nodes_unfocused), + Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_focused), + Args(scratchpad_state=None, urgent=True, workspace=workspace_noscratch, nodes=nodes_unfocused), + ] + + pl = Pl() + with replace_attr(i3wm, 'get_i3_connection', lambda: Conn()): + self.assertEqual(i3wm.scratchpad(pl=pl), [ + {'contents': 'O', 'highlight_groups': ['scratchpad']}, + {'contents': 'X', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:focused', 'scratchpad:visible', 'scratchpad']}, + {'contents': 'O', 'highlight_groups': ['scratchpad']}, + {'contents': 'X', 'highlight_groups': ['scratchpad:visible', 'scratchpad']}, + {'contents': 'O', 'highlight_groups': ['scratchpad:focused', 'scratchpad']}, + {'contents': 'X', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:visible', 'scratchpad']}, + ]) + self.assertEqual(i3wm.scratchpad(pl=pl, icons={'changed': '-', 'fresh': 'o'}), [ + {'contents': 'o', 'highlight_groups': ['scratchpad']}, + {'contents': '-', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:focused', 'scratchpad:visible', 'scratchpad']}, + {'contents': 'o', 'highlight_groups': ['scratchpad']}, + {'contents': '-', 'highlight_groups': ['scratchpad:visible', 'scratchpad']}, + {'contents': 'o', 'highlight_groups': ['scratchpad:focused', 'scratchpad']}, + {'contents': '-', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:visible', 'scratchpad']}, + ]) + class TestMail(TestCommon): module_name = 'mail' From ecf088539b43ab3207b6b597f8b96b23a3a5b191 Mon Sep 17 00:00:00 2001 From: Jiri Tyr Date: Tue, 22 Mar 2016 11:11:12 +0000 Subject: [PATCH 38/46] More specific instructions for iPython --- docs/source/usage/other.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/source/usage/other.rst b/docs/source/usage/other.rst index 1646a89a..72bff6d1 100644 --- a/docs/source/usage/other.rst +++ b/docs/source/usage/other.rst @@ -114,11 +114,12 @@ For IPython<0.11 add the following lines to :file:`.ipython/ipy_user_conf.py`: # create skeleton ipy_user_conf.py file): powerline_setup() -For IPython>=0.11 add the following line to :file:`ipython_config.py` file in -the used profile: +For IPython>=0.11 add the following line to +:file:`~/.ipython/profile_default/ipython_config.py` file in the used profile: .. code-block:: Python + c = get_config() c.InteractiveShellApp.extensions = [ 'powerline.bindings.ipython.post_0_11' ] From 2f1893a96862f108d76aaeb8ab59429c5b60b997 Mon Sep 17 00:00:00 2001 From: FocusedWolf Date: Thu, 24 Mar 2016 04:52:45 -0400 Subject: [PATCH 39/46] Fix LC_MESSAGES AttributeError Adds code to check if locale has the 'LC_MESSAGES' attribute before executing the following line of code: locale.getlocale(locale.LC_MESSAGES)[1] Fixes #1555 --- powerline/lib/encoding.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/powerline/lib/encoding.py b/powerline/lib/encoding.py index 798fc396..76a51d81 100644 --- a/powerline/lib/encoding.py +++ b/powerline/lib/encoding.py @@ -43,9 +43,15 @@ def get_preferred_output_encoding(): Falls back to ASCII, so that output is most likely to be displayed correctly. ''' + if hasattr(locale, 'LC_MESSAGES'): + return ( + locale.getlocale(locale.LC_MESSAGES)[1] + or locale.getdefaultlocale()[1] + or 'ascii' + ) + return ( - locale.getlocale(locale.LC_MESSAGES)[1] - or locale.getdefaultlocale()[1] + locale.getdefaultlocale()[1] or 'ascii' ) @@ -57,9 +63,15 @@ def get_preferred_input_encoding(): Falls back to latin1 so that function is less likely to throw as decoded output is primary searched for ASCII values. ''' + if hasattr(locale, 'LC_MESSAGES'): + return ( + locale.getlocale(locale.LC_MESSAGES)[1] + or locale.getdefaultlocale()[1] + or 'latin1' + ) + return ( - locale.getlocale(locale.LC_MESSAGES)[1] - or locale.getdefaultlocale()[1] + locale.getdefaultlocale()[1] or 'latin1' ) From e0a70986ed245b2e360b4bf85049031efd8dc858 Mon Sep 17 00:00:00 2001 From: Nikolai Aleksandrovich Pavlov Date: Thu, 24 Mar 2016 23:18:47 +0300 Subject: [PATCH 40/46] Fix a few typos/strange wording in installation/osx.rst --- docs/source/installation/osx.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/source/installation/osx.rst b/docs/source/installation/osx.rst index 55b8ebf8..e520348d 100644 --- a/docs/source/installation/osx.rst +++ b/docs/source/installation/osx.rst @@ -11,12 +11,10 @@ Python package sudo port select python python27-apple - . Homebrew may be used here:: + Homebrew may be used here:: brew install python - . - .. note:: In case :file:`powerline.sh` as a client ``socat`` and ``coreutils`` need to be installed. ``coreutils`` may be installed using ``brew install @@ -45,7 +43,7 @@ Python package ``powerline-status`` in PyPI. .. note:: - Powerline developers should be aware that``pip install --editable`` does + Powerline developers should be aware that ``pip install --editable`` does not currently fully work. Installation performed this way are missing ``powerline`` executable that needs to be symlinked. It will be located in ``scripts/powerline``. @@ -61,8 +59,8 @@ but MacVim users need to install it using the following command:: Fonts installation ================== -Install downloaded patched font by double-clicking the font file in Finder, then -clicking :guilabel:`Install this font` in the preview window. +To install patched font double-click the font file in Finder, then click +:guilabel:`Install this font` in the preview window. After installing the patched font MacVim or terminal emulator (whatever application powerline should work with) need to be configured to use the patched From dc14421fdb7bfce70a0f39ae9fa8e78e2242cd24 Mon Sep 17 00:00:00 2001 From: Foo Date: Sun, 27 Mar 2016 03:22:05 +0300 Subject: [PATCH 41/46] =?UTF-8?q?Use=20proper=20dictionary=20for=20getting?= =?UTF-8?q?=20=E2=80=9Celapsed=E2=80=9D=20value=20in=20mpd=20bindings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #1550 --- powerline/segments/common/players.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerline/segments/common/players.py b/powerline/segments/common/players.py index e98115e7..333612cb 100644 --- a/powerline/segments/common/players.py +++ b/powerline/segments/common/players.py @@ -208,7 +208,7 @@ class MpdPlayerSegment(PlayerSegment): 'album': now_playing.get('album'), 'artist': now_playing.get('artist'), 'title': now_playing.get('title'), - 'elapsed': _convert_seconds(now_playing.get('elapsed', 0)), + 'elapsed': _convert_seconds(status.get('elapsed', 0)), 'total': _convert_seconds(now_playing.get('time', 0)), } From e8955532c9b13ac7bd2afaa692d291c6bbfbf3de Mon Sep 17 00:00:00 2001 From: s-ol Date: Wed, 6 Apr 2016 12:56:12 +0200 Subject: [PATCH 42/46] Fix and update documentation concerning i3wm update dependencies for xrandr and i3ipc, fix a typo --- docs/source/installation.rst | 5 +++-- powerline/listers/i3wm.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index be786480..507b432b 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -30,8 +30,9 @@ Generic requirements with bazaar repositories. * ``pyuv`` python package. Required for :ref:`libuv-based watcher ` to work. -* ``i3-py``, `available on github `_. Required - for i3wm bindings and segments. +* ``i3-ipc`` python package. Required for i3wm bindings and segments. +* ``xrandr`` program. Required for the multi-monitor lemonbar binding and the + :py:func:`powerline.listers.i3wm.output_lister`. .. note:: Until mercurial and bazaar support Python-3 or PyPy powerline will not diff --git a/powerline/listers/i3wm.py b/powerline/listers/i3wm.py index 188e888b..0bbcfdc3 100644 --- a/powerline/listers/i3wm.py +++ b/powerline/listers/i3wm.py @@ -28,7 +28,7 @@ def workspace_lister(pl, segment_info, only_show=None, output=None): Sets the segment info values of ``workspace`` and ``output`` to the name of the i3 workspace and the ``xrandr`` output respectively and the keys - ``"visible"``, ``"urgent"`` and ``"focused`` to a boolean indicating these + ``"visible"``, ``"urgent"`` and ``"focused"`` to a boolean indicating these states. :param list only_show: From 5c8b52bc346d0521815e2a57ba8b6f663a35f86e Mon Sep 17 00:00:00 2001 From: Foo Date: Tue, 22 Mar 2016 15:23:25 +0300 Subject: [PATCH 43/46] Fix return value of vim_getbufoption, use new command-t matcher Now it uses _vim_to_python to transform its return value. Should fix tests. --- powerline/bindings/vim/__init__.py | 2 +- powerline/matchers/vim/__init__.py | 4 ++-- powerline/matchers/vim/plugin/commandt.py | 7 +++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/powerline/bindings/vim/__init__.py b/powerline/bindings/vim/__init__.py index e617943a..b754c1f3 100644 --- a/powerline/bindings/vim/__init__.py +++ b/powerline/bindings/vim/__init__.py @@ -273,7 +273,7 @@ def _vim_to_python(value): if hasattr(vim, 'options'): def vim_getbufoption(info, option): - return info['buffer'].options[str(option)] + return _vim_to_python(info['buffer'].options[str(option)]) def vim_getoption(option): return vim.options[str(option)] diff --git a/powerline/matchers/vim/__init__.py b/powerline/matchers/vim/__init__.py index b2bad4cc..f6de45ed 100644 --- a/powerline/matchers/vim/__init__.py +++ b/powerline/matchers/vim/__init__.py @@ -7,7 +7,7 @@ from powerline.bindings.vim import vim_getbufoption, buffer_name def help(matcher_info): - return str(vim_getbufoption(matcher_info, 'buftype')) == 'help' + return vim_getbufoption(matcher_info, 'buftype') == 'help' def cmdwin(matcher_info): @@ -16,4 +16,4 @@ def cmdwin(matcher_info): def quickfix(matcher_info): - return str(vim_getbufoption(matcher_info, 'buftype')) == 'quickfix' + return vim_getbufoption(matcher_info, 'buftype') == 'quickfix' diff --git a/powerline/matchers/vim/plugin/commandt.py b/powerline/matchers/vim/plugin/commandt.py index e4236f3e..7eefe9b7 100644 --- a/powerline/matchers/vim/plugin/commandt.py +++ b/powerline/matchers/vim/plugin/commandt.py @@ -3,9 +3,12 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct import os -from powerline.bindings.vim import buffer_name +from powerline.bindings.vim import vim_getbufoption, buffer_name def commandt(matcher_info): name = buffer_name(matcher_info) - return name and os.path.basename(name) == b'GoToFile' + return ( + vim_getbufoption(matcher_info, 'filetype') == 'command-t' + or (name and os.path.basename(name) == b'GoToFile') + ) From 43cc472b60434fd9baf7a3fa3d092a388017c32d Mon Sep 17 00:00:00 2001 From: Foo Date: Fri, 8 Apr 2016 20:58:15 +0300 Subject: [PATCH 44/46] Remove UCS-2 Python-2.6 tests Some packages are no longer compatible with Python-2.6. It is easier to leave only python-2.7 UCS-2 build then to fix Python-2.6. --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9f4747c0..7546af90 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,10 +27,6 @@ matrix: - python: "3.5" - python: "pypy" - python: "pypy3" - - python: "2.6" - env: >- - USE_UCS2_PYTHON=1 - UCS2_PYTHON_VARIANT="2.6" - python: "2.7" env: >- USE_UCS2_PYTHON=1 From e3369f200f804008d6d7de2feebc939552338c75 Mon Sep 17 00:00:00 2001 From: Iblis Lin Date: Fri, 8 Apr 2016 16:37:20 +0800 Subject: [PATCH 45/46] Add `short` param for system_load segment Testing included --- powerline/segments/common/sys.py | 9 ++++++++- tests/test_segments.py | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/powerline/segments/common/sys.py b/powerline/segments/common/sys.py index f53e2c7d..a83025bf 100644 --- a/powerline/segments/common/sys.py +++ b/powerline/segments/common/sys.py @@ -13,7 +13,8 @@ from powerline.segments import with_docstring cpu_count = None -def system_load(pl, format='{avg:.1f}', threshold_good=1, threshold_bad=2, track_cpu_count=False): +def system_load(pl, format='{avg:.1f}', threshold_good=1, threshold_bad=2, + track_cpu_count=False, short=False): '''Return system load average. Highlights using ``system_load_good``, ``system_load_bad`` and @@ -35,6 +36,8 @@ def system_load(pl, format='{avg:.1f}', threshold_good=1, threshold_bad=2, track :param bool track_cpu_count: if True powerline will continuously poll the system to detect changes in the number of CPUs. + :param bool short: + if True only the sys load over last 1 minute will be displayed. Divider highlight group used: ``background:divider``. @@ -61,6 +64,10 @@ def system_load(pl, format='{avg:.1f}', threshold_good=1, threshold_bad=2, track 'divider_highlight_group': 'background:divider', 'gradient_level': gradient_level, }) + + if short: + return ret + ret[0]['contents'] += ' ' ret[1]['contents'] += ' ' return ret diff --git a/tests/test_segments.py b/tests/test_segments.py index 027d6227..0a7adcbf 100644 --- a/tests/test_segments.py +++ b/tests/test_segments.py @@ -811,6 +811,12 @@ class TestSys(TestCommon): {'contents': '4 ', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100}, {'contents': '2', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 75.0} ]) + self.assertEqual(self.module.system_load(pl=pl, short=True), [ + {'contents': '7.5', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100}, + ]) + self.assertEqual(self.module.system_load(pl=pl, format='{avg:.0f}', threshold_good=0, threshold_bad=1, short=True), [ + {'contents': '8', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100}, + ]) def test_cpu_load_percent(self): try: From 22b24cd93114de5bcf95d5c2319aebdfc16cbee4 Mon Sep 17 00:00:00 2001 From: Foo Date: Tue, 19 Apr 2016 02:02:45 +0300 Subject: [PATCH 46/46] Update base version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ab55238c..2c9d758d 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ else: def get_version(): - base_version = '2.3' + base_version = '2.4' base_version += '.dev9999' try: return base_version + '+git.' + str(subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip())