Merge branch 'top-theme-extend' into develop

This commit is contained in:
ZyX 2014-08-29 20:25:51 +04:00
commit 9fe0b66125
8 changed files with 152 additions and 19 deletions

View File

@ -265,6 +265,8 @@ extension-specific key <config-ext-top_theme>` or from :ref:`default_top_theme
common configuration key <config-common-default_top_theme>`. Powerline ships common configuration key <config-common-default_top_theme>`. Powerline ships
with the following top themes: with the following top themes:
.. _config-top_themes-list:
========================== ==================================================== ========================== ====================================================
Theme Description Theme Description
========================== ==================================================== ========================== ====================================================

View File

@ -29,6 +29,52 @@ object it should receive the following arguments:
And also any other argument(s) specified by user in :ref:`args key And also any other argument(s) specified by user in :ref:`args key
<config-themes-seg-args>` (no additional arguments by default). <config-themes-seg-args>` (no additional arguments by default).
Object representing segment may have the following attributes used by
powerline:
``powerline_requires_segment_info``
This attribute controls whether segment will receive ``segment_info``
argument: if it is present argument will be received.
``powerline_requires_filesystem_watcher``
This attribute controls whether segment will receive ``create_watcher``
argument: if it is present argument will be received.
``powerline_segment_datas``
This attribute must be a dictionary containing ``top_theme: segment_data``
mapping where ``top_theme`` is any theme name (it is expected that all of
the names from :ref:`top-level themes list <config-top_themes-list>` are
present) and ``segment_data`` is a dictionary like the one that is contained
inside :ref:`segment_data dictionary in configuration
<config-themes-segment_data>`. This attribute should be used to specify
default theme-specific values for *third-party* segments: powerline
theme-specific values go directly to :ref:`top-level themes
<config-themes>`.
``startup``
This attribute must be a callable which accepts the following keyword
arguments:
* ``pl``: :py:class:`powerline.PowerlineLogger` instance which is to be used
for logging.
* ``shutdown_event``: :py:class:`Event` object which will be set when
powerline will be shut down.
* Any arguments found in user configuration for the given segment (i.e.
:ref:`args key <config-themes-seg-args>`).
This function is called at powerline startup when using long-running
processes (e.g. powerline in vim, in zsh with libzpython, in ipython or in
powerline daemon) and not called when ``powerline-render`` executable is
used (more specific: when :py:class:`powerline.Powerline` constructor
received true ``run_once`` argument).
``shutdown``
This attribute must be a callable that accepts no arguments and shuts down
threads and frees any other resources allocated in ``startup`` method of the
segment in question.
This function is not called when ``startup`` method is not called.
This callable object should may return either a string (``unicode`` in Python2 This callable object should may return either a string (``unicode`` in Python2
or ``str`` in Python3, *not* ``str`` in Python2 or ``bytes`` in Python3) object or ``str`` in Python3, *not* ``str`` in Python2 or ``bytes`` in Python3) object
or a list of dictionaries. String object is a short form of the following return or a list of dictionaries. String object is a short form of the following return

View File

@ -485,13 +485,15 @@ class Powerline(object):
self.ext_config = config['ext'][self.ext] self.ext_config = config['ext'][self.ext]
self.theme_levels = ( top_theme = (
os.path.join('themes', (
self.ext_config.get('top_theme') self.ext_config.get('top_theme')
or self.common_config['default_top_theme'] or self.common_config['default_top_theme']
)), )
self.theme_levels = (
os.path.join('themes', top_theme),
os.path.join('themes', self.ext, '__main__'), os.path.join('themes', self.ext, '__main__'),
) )
self.renderer_options['theme_kwargs']['top_theme'] = top_theme
if self.ext_config != self.prev_ext_config: if self.ext_config != self.prev_ext_config:
ext_config_differs = True ext_config_differs = True

View File

@ -15,7 +15,9 @@ def wraps_saveargs(wrapped):
def mergedicts(d1, d2): def mergedicts(d1, d2):
'''Recursively merge two dictionaries. First dictionary is modified in-place. '''Recursively merge two dictionaries
First dictionary is modified in-place.
''' '''
for k in d2: for k in d2:
if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], dict): if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], dict):
@ -26,11 +28,23 @@ def mergedicts(d1, d2):
d1[k] = d2[k] d1[k] = d2[k]
def mergedefaults(d1, d2):
'''Recursively merge two dictionaries, keeping existing values
First dictionary is modified in-place.
'''
for k in d2:
if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], dict):
mergedefaults(d1[k], d2[k])
else:
d1.setdefault(k, d2[k])
def mergedicts_copy(d1, d2): def mergedicts_copy(d1, d2):
'''Recursively merge two dictionaries. '''Recursively merge two dictionaries.
Dictionaries are not modified. Copying happens only if necessary. Assumes Dictionaries are not modified. Copying happens only if necessary. Assumes
that first dictionary support .copy() method. that first dictionary supports .copy() method.
''' '''
ret = d1.copy() ret = d1.copy()
for k in d2: for k in d2:

View File

@ -2,9 +2,7 @@
from __future__ import absolute_import from __future__ import absolute_import
from inspect import ArgSpec, getargspec from inspect import ArgSpec, getargspec
from itertools import count
from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment
from powerline.segments import Segment from powerline.segments import Segment
@ -31,8 +29,8 @@ def getconfigargspec(obj):
if len(arg) > 1: if len(arg) > 1:
defaults.append(arg[1]) defaults.append(arg[1])
requires_segment_info = getattr(obj, 'powerline_requires_segment_info', False) requires_segment_info = hasattr(obj, 'powerline_requires_segment_info')
requires_filesystem_watcher = getattr(obj, 'powerline_requires_filesystem_watcher', False) requires_filesystem_watcher = hasattr(obj, 'powerline_requires_filesystem_watcher')
for name, method in argspecobjs: for name, method in argspecobjs:
argspec = getargspec(method) argspec = getargspec(method)

View File

@ -4,7 +4,7 @@ from __future__ import absolute_import, unicode_literals, division, print_functi
from powerline.lib.watcher import create_file_watcher from powerline.lib.watcher import create_file_watcher
def list_segment_key_values(segment, theme_configs, key, module=None, default=None): def list_segment_key_values(segment, theme_configs, segment_data, key, module=None, default=None):
try: try:
yield segment[key] yield segment[key]
except KeyError: except KeyError:
@ -32,6 +32,11 @@ def list_segment_key_values(segment, theme_configs, key, module=None, default=No
yield segment_data[name][key] yield segment_data[name][key]
except KeyError: except KeyError:
pass pass
if segment_data is not None:
try:
yield segment_data[key]
except KeyError:
pass
yield default yield default
@ -173,14 +178,15 @@ def process_segment(pl, side, segment_info, parsed_segments, segment, mode):
parsed_segments.append(segment) parsed_segments.append(segment)
def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, get_module_attr): def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, get_module_attr, top_theme):
data = { data = {
'default_module': default_module or 'powerline.segments.' + ext, 'default_module': default_module or 'powerline.segments.' + ext,
'get_module_attr': get_module_attr, 'get_module_attr': get_module_attr,
'segment_data': None,
} }
def get_key(merge, segment, module, key, default=None): def get_key(merge, segment, module, key, default=None):
return get_segment_key(merge, segment, theme_configs, key, module, default) return get_segment_key(merge, segment, theme_configs, data['segment_data'], key, module, default)
data['get_key'] = get_key data['get_key'] = get_key
def get(segment, side): def get(segment, side):
@ -199,6 +205,13 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, ge
if not get_key(False, segment, module, 'display', True): if not get_key(False, segment, module, 'display', True):
return None return None
segment_datas = getattr(_contents_func, 'powerline_segment_datas', None)
if segment_datas:
try:
data['segment_data'] = segment_datas[top_theme]
except KeyError:
pass
if segment_type == 'function': if segment_type == 'function':
highlight_group = [module + '.' + segment['name'], segment['name']] highlight_group = [module + '.' + segment['name'], segment['name']]
else: else:

View File

@ -30,6 +30,7 @@ class Theme(object):
common_config, common_config,
pl, pl,
get_module_attr, get_module_attr,
top_theme,
main_theme_config=None, main_theme_config=None,
run_once=False, run_once=False,
shutdown_event=None): shutdown_event=None):
@ -54,7 +55,15 @@ class Theme(object):
theme_configs = [theme_config] theme_configs = [theme_config]
if main_theme_config: if main_theme_config:
theme_configs.append(main_theme_config) theme_configs.append(main_theme_config)
get_segment = gen_segment_getter(pl, ext, common_config, theme_configs, theme_config.get('default_module'), get_module_attr) get_segment = gen_segment_getter(
pl,
ext,
common_config,
theme_configs,
theme_config.get('default_module'),
get_module_attr,
top_theme
)
for segdict in itertools.chain((theme_config['segments'],), for segdict in itertools.chain((theme_config['segments'],),
theme_config['segments'].get('above', ())): theme_config['segments'].get('above', ())):
self.segments.append(new_empty_segment_line()) self.segments.append(new_empty_segment_line())

View File

@ -1,13 +1,18 @@
# vim:fileencoding=utf-8:noet # vim:fileencoding=utf-8:noet
from __future__ import unicode_literals, absolute_import, division from __future__ import unicode_literals, absolute_import, division
import tests.vim as vim_module
from tests import TestCase
from tests.lib.config_mock import get_powerline, get_powerline_raw, swap_attributes
from functools import wraps
from copy import deepcopy
import sys import sys
import os import os
from functools import wraps
from copy import deepcopy
import tests.vim as vim_module
from tests import TestCase
from tests.lib.config_mock import get_powerline, get_powerline_raw, swap_attributes
from tests.lib import Args
def highlighted_string(s, group, **kwargs): def highlighted_string(s, group, **kwargs):
ret = { ret = {
@ -428,6 +433,50 @@ class TestModes(TestRender):
self.assertRenderEqual(p, '{56} s2{6-}>>{--}', mode='m3') self.assertRenderEqual(p, '{56} s2{6-}>>{--}', mode='m3')
class TestSegmentAttributes(TestRender):
@add_args
def test_no_attributes(self, p, config):
def m1(divider=',', **kwargs):
return divider.join(kwargs.keys()) + divider
sys.modules['bar'] = Args(m1=m1)
config['themes/test/default']['segments'] = {
'left': [
{
'name': 'm1',
'module': 'bar'
}
]
}
self.assertRenderEqual(p, '{56} pl,{6-}>>{--}')
@add_args
def test_segment_datas(self, p, config):
def m1(divider=',', **kwargs):
return divider.join(kwargs.keys()) + divider
m1.powerline_segment_datas = {
'powerline': {
'args': {
'divider': ';'
}
},
'ascii': {
'args': {
'divider': '--'
}
}
}
sys.modules['bar'] = Args(m1=m1)
config['themes/test/default']['segments'] = {
'left': [
{
'name': 'm1',
'module': 'bar'
}
]
}
self.assertRenderEqual(p, '{56} pl;{6-}>>{--}')
class TestVim(TestCase): class TestVim(TestCase):
def test_environ_update(self): def test_environ_update(self):
# Regression test: test that segment obtains environment from vim, not # Regression test: test that segment obtains environment from vim, not