Merge pull request #923 from ZyX-I/merge-config

Implement configuration merging
This commit is contained in:
Nikolai Aleksandrovich Pavlov 2014-08-05 14:05:23 +04:00
commit c95b288527
10 changed files with 1036 additions and 548 deletions

View File

@ -30,6 +30,24 @@ corresponds to :file:`~/.config/powerline` on both platforms.
If you need per-instance configuration please refer to :ref:`Local configuration If you need per-instance configuration please refer to :ref:`Local configuration
overrides <local-configuration-overrides>`. overrides <local-configuration-overrides>`.
.. note:: If you have multiple configuration files with the same name in
different directories then these files will be merged. Merging happens in
the following order:
* :file:`{powerline_root}/powerline/config_files` is checked for
configuration first. Configuration from this source has least priority.
* :file:`$XDG_CONFIG_DIRS/powerline` directories are the next ones to check.
Checking happens in the reversed order: directories mentioned last are
checked before directories mentioned first. Each new found file is merged
with the result of previous merge.
* :file:`$XDG_CONFIG_HOME/powerline` directory is the last to check.
Configuration from there has top priority.
When merging configuration only dictionaries are merged and they are merged
recursively: keys from next file overrule those from the previous unless
corresponding values are both dictionaries in which case these dictionaries
are merged and key is assigned the result of the merge.
.. _quick-guide: .. _quick-guide:
Quick setup guide Quick setup guide

View File

@ -14,13 +14,25 @@ from powerline.lib import mergedicts
from threading import Lock, Event from threading import Lock, Event
def _find_config_file(search_paths, config_file): def _config_loader_condition(path):
return path and os.path.isfile(path)
def _find_config_files(search_paths, config_file, config_loader=None, loader_callback=None):
config_file += '.json' config_file += '.json'
found = False
for path in search_paths: for path in search_paths:
config_file_path = os.path.join(path, config_file) config_file_path = os.path.join(path, config_file)
if os.path.isfile(config_file_path): if os.path.isfile(config_file_path):
return config_file_path yield config_file_path
raise IOError('Config file not found in search path: {0}'.format(config_file)) found = True
elif config_loader:
config_loader.register_missing(_config_loader_condition, loader_callback, config_file_path)
if not found:
raise IOError('Config file not found in search paths ({0}): {1}'.format(
', '.join(search_paths),
config_file
))
class PowerlineLogger(object): class PowerlineLogger(object):
@ -103,14 +115,14 @@ def get_config_paths():
config_paths = [config_path] config_paths = [config_path]
config_dirs = os.environ.get('XDG_CONFIG_DIRS', DEFAULT_SYSTEM_CONFIG_DIR) config_dirs = os.environ.get('XDG_CONFIG_DIRS', DEFAULT_SYSTEM_CONFIG_DIR)
if config_dirs is not None: if config_dirs is not None:
config_paths.extend([os.path.join(d, 'powerline') for d in config_dirs.split(':')]) config_paths[:0] = reversed([os.path.join(d, 'powerline') for d in config_dirs.split(':')])
plugin_path = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'config_files') plugin_path = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'config_files')
config_paths.append(plugin_path) config_paths.insert(0, plugin_path)
return config_paths return config_paths
def generate_config_finder(get_config_paths=get_config_paths): def generate_config_finder(get_config_paths=get_config_paths):
'''Generate find_config_file function '''Generate find_config_files function
This function will find .json file given its path. This function will find .json file given its path.
@ -123,17 +135,17 @@ def generate_config_finder(get_config_paths=get_config_paths):
to it or raise IOError if it failed to find the file. to it or raise IOError if it failed to find the file.
''' '''
config_paths = get_config_paths() config_paths = get_config_paths()
return lambda cfg_path: _find_config_file(config_paths, cfg_path) return lambda *args: _find_config_files(config_paths, *args)
def load_config(cfg_path, find_config_file, config_loader, loader_callback=None): def load_config(cfg_path, find_config_files, config_loader, loader_callback=None):
'''Load configuration file and setup watches '''Load configuration file and setup watches
Watches are only set up if loader_callback is not None. Watches are only set up if loader_callback is not None.
:param str cfg_path: :param str cfg_path:
Path for configuration file that should be loaded. Path for configuration file that should be loaded.
:param function find_config_file: :param function find_config_files:
Function that finds configuration file. Check out the description of Function that finds configuration file. Check out the description of
the return value of ``generate_config_finder`` function. the return value of ``generate_config_finder`` function.
:param ConfigLoader config_loader: :param ConfigLoader config_loader:
@ -144,16 +156,16 @@ def load_config(cfg_path, find_config_file, config_loader, loader_callback=None)
:return: Configuration file contents. :return: Configuration file contents.
''' '''
try: found_files = find_config_files(cfg_path, config_loader, loader_callback)
path = find_config_file(cfg_path) ret = None
except IOError: for path in found_files:
if loader_callback:
config_loader.register_missing(find_config_file, loader_callback, cfg_path)
raise
else:
if loader_callback: if loader_callback:
config_loader.register(loader_callback, path) config_loader.register(loader_callback, path)
return config_loader.load(path) if ret is None:
ret = config_loader.load(path)
else:
mergedicts(ret, config_loader.load(path))
return ret
def _get_log_handler(common_config): def _get_log_handler(common_config):
@ -286,7 +298,7 @@ class Powerline(object):
elif self.renderer_module[-1] == '.': elif self.renderer_module[-1] == '.':
self.renderer_module = self.renderer_module[:-1] self.renderer_module = self.renderer_module[:-1]
self.find_config_file = generate_config_finder(self.get_config_paths) self.find_config_files = generate_config_finder(self.get_config_paths)
self.cr_kwargs_lock = Lock() self.cr_kwargs_lock = Lock()
self.cr_kwargs = {} self.cr_kwargs = {}
@ -437,7 +449,7 @@ class Powerline(object):
'''Load configuration and setup watches.''' '''Load configuration and setup watches.'''
return load_config( return load_config(
cfg_path, cfg_path,
self.find_config_file, self.find_config_files,
self.config_loader, self.config_loader,
self.cr_callbacks[type] self.cr_callbacks[type]
) )
@ -445,7 +457,7 @@ class Powerline(object):
def _purge_configs(self, type): def _purge_configs(self, type):
function = self.cr_callbacks[type] function = self.cr_callbacks[type]
self.config_loader.unregister_functions(set((function,))) self.config_loader.unregister_functions(set((function,)))
self.config_loader.unregister_missing(set(((self.find_config_file, function),))) self.config_loader.unregister_missing(set(((self.find_config_files, function),)))
def load_theme_config(self, name): def load_theme_config(self, name):
'''Get theme configuration. '''Get theme configuration.
@ -601,7 +613,7 @@ class Powerline(object):
pass pass
functions = tuple(self.cr_callbacks.values()) functions = tuple(self.cr_callbacks.values())
self.config_loader.unregister_functions(set(functions)) self.config_loader.unregister_functions(set(functions))
self.config_loader.unregister_missing(set(((self.find_config_file, function) for function in functions))) self.config_loader.unregister_missing(set(((self.find_config_files, function) for function in functions)))
def __enter__(self): def __enter__(self):
return self return self

View File

@ -119,9 +119,9 @@ def source_tmux_files(pl, args):
def create_powerline_logger(args): def create_powerline_logger(args):
find_config_file = generate_config_finder() find_config_files = generate_config_finder()
config_loader = ConfigLoader(run_once=True) config_loader = ConfigLoader(run_once=True)
config = load_config('config', find_config_file, config_loader) config = load_config('config', find_config_files, config_loader)
common_config = finish_common_config(config['common']) common_config = finish_common_config(config['common'])
logger = create_logger(common_config) logger = create_logger(common_config)
return PowerlineLogger(use_daemon_threads=True, logger=logger, ext='config') return PowerlineLogger(use_daemon_threads=True, logger=logger, ext='config')

View File

@ -1,8 +1,8 @@
# vim:fileencoding=utf-8:noet # vim:fileencoding=utf-8:noet
from powerline.lint.markedjson import load from powerline.lint.markedjson import load
from powerline import generate_config_finder, get_config_paths from powerline import generate_config_finder, get_config_paths, load_config
from powerline.lib.config import load_json_config from powerline.lib.config import ConfigLoader
from powerline.lint.markedjson.error import echoerr, MarkedError from powerline.lint.markedjson.error import echoerr, MarkedError
from powerline.segments.vim import vim_modes from powerline.segments.vim import vim_modes
from powerline.lint.inspect import getconfigargspec from powerline.lint.inspect import getconfigargspec
@ -159,7 +159,13 @@ class Spec(object):
for item in value: for item in value:
if isinstance(item_func, int): if isinstance(item_func, int):
spec = self.specs[item_func] spec = self.specs[item_func]
proceed, fhadproblem = spec.match(item, value.mark, data, context + (('list item ' + unicode(i), item),), echoerr) proceed, fhadproblem = spec.match(
item,
value.mark,
data,
context + (('list item ' + unicode(i), item),),
echoerr
)
else: else:
proceed, echo, fhadproblem = item_func(item, data, context, echoerr) proceed, echo, fhadproblem = item_func(item, data, context, echoerr)
if echo and fhadproblem: if echo and fhadproblem:
@ -192,7 +198,13 @@ class Spec(object):
def check_tuple(self, value, context_mark, data, context, echoerr, start, end): def check_tuple(self, value, context_mark, data, context, echoerr, start, end):
hadproblem = False hadproblem = False
for (i, item, spec) in zip(itertools.count(), value, self.specs[start:end]): for (i, item, spec) in zip(itertools.count(), value, self.specs[start:end]):
proceed, ihadproblem = spec.match(item, value.mark, data, context + (('tuple item ' + unicode(i), item),), echoerr) proceed, ihadproblem = spec.match(
item,
value.mark,
data,
context + (('tuple item ' + unicode(i), item),),
echoerr
)
if ihadproblem: if ihadproblem:
hadproblem = True hadproblem = True
if not proceed: if not proceed:
@ -221,10 +233,16 @@ class Spec(object):
def len(self, comparison, cint, msg_func=None): def len(self, comparison, cint, msg_func=None):
cmp_func = self.cmp_funcs[comparison] cmp_func = self.cmp_funcs[comparison]
msg_func = msg_func or (lambda value: 'length of {0!r} is not {1} {2}'.format(value, self.cmp_msgs[comparison], cint)) msg_func = (
self.checks.append(('check_func', msg_func
(lambda value, *args: (True, True, not cmp_func(len(value), cint))), or (lambda value: 'length of {0!r} is not {1} {2}'.format(
msg_func)) value, self.cmp_msgs[comparison], cint))
)
self.checks.append((
'check_func',
(lambda value, *args: (True, True, not cmp_func(len(value), cint))),
msg_func
))
return self return self
def cmp(self, comparison, cint, msg_func=None): def cmp(self, comparison, cint, msg_func=None):
@ -236,16 +254,20 @@ class Spec(object):
self.type(type(cint)) self.type(type(cint))
cmp_func = self.cmp_funcs[comparison] cmp_func = self.cmp_funcs[comparison]
msg_func = msg_func or (lambda value: '{0} is not {1} {2}'.format(value, self.cmp_msgs[comparison], cint)) msg_func = msg_func or (lambda value: '{0} is not {1} {2}'.format(value, self.cmp_msgs[comparison], cint))
self.checks.append(('check_func', self.checks.append((
(lambda value, *args: (True, True, not cmp_func(value.value, cint))), 'check_func',
msg_func)) (lambda value, *args: (True, True, not cmp_func(value.value, cint))),
msg_func
))
return self return self
def unsigned(self, msg_func=None): def unsigned(self, msg_func=None):
self.type(int) self.type(int)
self.checks.append(('check_func', self.checks.append((
(lambda value, *args: (True, True, value < 0)), 'check_func',
lambda value: '{0} must be greater then zero'.format(value))) (lambda value, *args: (True, True, value < 0)),
(lambda value: '{0} must be greater then zero'.format(value))
))
return self return self
def list(self, item_func, msg_func=None): def list(self, item_func, msg_func=None):
@ -286,25 +308,35 @@ class Spec(object):
self.type(unicode) self.type(unicode)
compiled = re.compile(regex) compiled = re.compile(regex)
msg_func = msg_func or (lambda value: 'String "{0}" does not match "{1}"'.format(value, regex)) msg_func = msg_func or (lambda value: 'String "{0}" does not match "{1}"'.format(value, regex))
self.checks.append(('check_func', self.checks.append((
(lambda value, *args: (True, True, not compiled.match(value.value))), 'check_func',
msg_func)) (lambda value, *args: (True, True, not compiled.match(value.value))),
msg_func
))
return self return self
def ident(self, msg_func=None): def ident(self, msg_func=None):
msg_func = msg_func or (lambda value: 'String "{0}" is not an alphanumeric/underscore identifier'.format(value)) msg_func = (
msg_func
or (lambda value: 'String "{0}" is not an alphanumeric/underscore identifier'.format(value))
)
return self.re('^\w+$', msg_func) return self.re('^\w+$', msg_func)
def oneof(self, collection, msg_func=None): def oneof(self, collection, msg_func=None):
msg_func = msg_func or (lambda value: '"{0}" must be one of {1!r}'.format(value, list(collection))) msg_func = msg_func or (lambda value: '"{0}" must be one of {1!r}'.format(value, list(collection)))
self.checks.append(('check_func', self.checks.append((
lambda value, *args: (True, True, value not in collection), 'check_func',
msg_func)) (lambda value, *args: (True, True, value not in collection)),
msg_func
))
return self return self
def error(self, msg): def error(self, msg):
self.checks.append(('check_func', lambda *args: (True, True, True), self.checks.append((
lambda value: msg.format(value))) 'check_func',
(lambda *args: (True, True, True)),
(lambda value: msg.format(value))
))
return self return self
def either(self, *specs): def either(self, *specs):
@ -334,7 +366,13 @@ class Spec(object):
for key, vali in self.keys.items(): for key, vali in self.keys.items():
valspec = self.specs[vali] valspec = self.specs[vali]
if key in value: if key in value:
proceed, mhadproblem = valspec.match(value[key], value.mark, data, context + ((key, value[key]),), echoerr) proceed, mhadproblem = valspec.match(
value[key],
value.mark,
data,
context + ((key, value[key]),),
echoerr
)
if mhadproblem: if mhadproblem:
hadproblem = True hadproblem = True
if not proceed: if not proceed:
@ -358,7 +396,13 @@ class Spec(object):
if khadproblem: if khadproblem:
hadproblem = True hadproblem = True
if proceed: if proceed:
proceed, vhadproblem = valspec.match(value[key], value.mark, data, context + ((key, value[key]),), echoerr) proceed, vhadproblem = valspec.match(
value[key],
value.mark,
data,
context + ((key, value[key]),),
echoerr
)
if vhadproblem: if vhadproblem:
hadproblem = True hadproblem = True
break break
@ -416,9 +460,14 @@ def check_matcher_func(ext, match_name, data, context, echoerr):
if hasattr(func, 'func_code') and hasattr(func.func_code, 'co_argcount'): if hasattr(func, 'func_code') and hasattr(func.func_code, 'co_argcount'):
if func.func_code.co_argcount != 1: if func.func_code.co_argcount != 1:
echoerr(context='Error while loading matcher functions', echoerr(
problem='function {0} accepts {1} arguments instead of 1. Are you sure it is the proper function?'.format(match_function, func.func_code.co_argcount), context='Error while loading matcher functions',
problem_mark=match_name.mark) problem=(
'function {0} accepts {1} arguments instead of 1. '
'Are you sure it is the proper function?'
).format(match_function, func.func_code.co_argcount),
problem_mark=match_name.mark
)
return True, False return True, False
@ -477,17 +526,30 @@ main_spec = (Spec(
left=divside_spec(), left=divside_spec(),
right=divside_spec(), right=divside_spec(),
), ),
spaces=Spec().unsigned().cmp('le', 2, spaces=Spec().unsigned().cmp(
lambda value: 'Are you sure you need such a big ({0}) number of spaces?'.format(value)), 'le', 2, lambda value: 'Are you sure you need such a big ({0}) number of spaces?'.format(value)
),
term_truecolor=Spec().type(bool).optional(), term_truecolor=Spec().type(bool).optional(),
# Python is capable of loading from zip archives. Thus checking path # Python is capable of loading from zip archives. Thus checking path
# only for existence of the path, not for it being a directory # only for existence of the path, not for it being a directory
paths=Spec().list((lambda value, *args: (True, True, not os.path.exists(os.path.expanduser(value.value)))), paths=Spec().list(
lambda value: 'path does not exist: {0}'.format(value)).optional(), (lambda value, *args: (True, True, not os.path.exists(os.path.expanduser(value.value)))),
log_file=Spec().type(unicode).func(lambda value, *args: (True, True, not os.path.isdir(os.path.dirname(os.path.expanduser(value)))), (lambda value: 'path does not exist: {0}'.format(value))
lambda value: 'directory does not exist: {0}'.format(os.path.dirname(value))).optional(), ).optional(),
log_level=Spec().re('^[A-Z]+$').func(lambda value, *args: (True, True, not hasattr(logging, value)), log_file=Spec().type(unicode).func(
lambda value: 'unknown debugging level {0}'.format(value)).optional(), (
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)))
).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_format=Spec().type(unicode).optional(),
interval=Spec().either(Spec().cmp('gt', 0.0), Spec().type(type(None))).optional(), interval=Spec().either(Spec().cmp('gt', 0.0), Spec().type(type(None))).optional(),
reload_config=Spec().type(bool).optional(), reload_config=Spec().type(bool).optional(),
@ -497,8 +559,9 @@ main_spec = (Spec(
vim=Spec( vim=Spec(
colorscheme=colorscheme_spec(), colorscheme=colorscheme_spec(),
theme=theme_spec(), theme=theme_spec(),
local_themes=Spec() local_themes=Spec().unknown_spec(
.unknown_spec(lambda *args: check_matcher_func('vim', *args), theme_spec()) lambda *args: check_matcher_func('vim', *args), theme_spec()
),
).optional(), ).optional(),
ipython=Spec( ipython=Spec(
colorscheme=colorscheme_spec(), colorscheme=colorscheme_spec(),
@ -517,24 +580,28 @@ main_spec = (Spec(
select=theme_spec(), select=theme_spec(),
), ),
).optional(), ).optional(),
).unknown_spec(check_ext, ).unknown_spec(
Spec( check_ext,
colorscheme=colorscheme_spec(), Spec(
theme=theme_spec(), colorscheme=colorscheme_spec(),
)) theme=theme_spec(),
.context_message('Error while loading extensions configuration (key {key})'), )
).context_message('Error while loading extensions configuration (key {key})'),
).context_message('Error while loading main configuration')) ).context_message('Error while loading main configuration'))
term_color_spec = Spec().unsigned().cmp('le', 255).copy term_color_spec = Spec().unsigned().cmp('le', 255).copy
true_color_spec = Spec().re('^[0-9a-fA-F]{6}$', true_color_spec = Spec().re(
lambda value: '"{0}" is not a six-digit hexadecimal unsigned integer written as a string'.format(value)).copy '^[0-9a-fA-F]{6}$',
(lambda value: '"{0}" is not a six-digit hexadecimal unsigned integer written as a string'.format(value))
).copy
colors_spec = (Spec( colors_spec = (Spec(
colors=Spec().unknown_spec( colors=Spec().unknown_spec(
Spec().ident(), Spec().ident(),
Spec().either( Spec().either(
Spec().tuple(term_color_spec(), true_color_spec()), Spec().tuple(term_color_spec(), true_color_spec()),
term_color_spec())) term_color_spec()
.context_message('Error while checking colors (key {key})'), )
).context_message('Error while checking colors (key {key})'),
gradients=Spec().unknown_spec( gradients=Spec().unknown_spec(
Spec().ident(), Spec().ident(),
Spec().tuple( Spec().tuple(
@ -546,10 +613,14 @@ colors_spec = (Spec(
def check_color(color, data, context, echoerr): def check_color(color, data, context, echoerr):
if color not in data['colors_config'].get('colors', {}) and color not in data['colors_config'].get('gradients', {}): if (color not in data['colors_config'].get('colors', {})
echoerr(context='Error while checking highlight group in colorscheme (key {key})'.format(key=context_key(context)), and color not in data['colors_config'].get('gradients', {})):
problem='found unexistent color or gradient {0}'.format(color), echoerr(
problem_mark=color.mark) context='Error while checking highlight group in colorscheme (key {key})'.format(
key=context_key(context)),
problem='found unexistent color or gradient {0}'.format(color),
problem_mark=color.mark
)
return True, False, True return True, False, True
return True, False, False return True, False, False
@ -618,9 +689,13 @@ def check_group(group, data, context, echoerr):
if not proceed: if not proceed:
break break
if not_found: if not_found:
new_echoerr(context='Error while checking group definition in colorscheme (key {key})'.format(key=context_key(context)), new_echoerr(
problem='name {0} is not present in {1} {2} colorschemes: {3}'.format(group, tofind, ext, ', '.join(not_found)), context='Error while checking group definition in colorscheme (key {key})'.format(
problem_mark=group.mark) key=context_key(context)),
problem='name {0} is not present in {1} {2} colorschemes: {3}'.format(
group, tofind, ext, ', '.join(not_found)),
problem_mark=group.mark
)
new_echoerr.echo_all() new_echoerr.echo_all()
return True, False, hadproblem return True, False, hadproblem
@ -717,25 +792,34 @@ def check_key_compatibility(segment, data, context, echoerr):
keys = set(segment) keys = set(segment)
if not ((keys - generic_keys) < type_keys[segment_type]): if not ((keys - generic_keys) < type_keys[segment_type]):
unknown_keys = keys - generic_keys - type_keys[segment_type] unknown_keys = keys - generic_keys - type_keys[segment_type]
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)), echoerr(
context_mark=context[-1][1].mark, context='Error while checking segments (key {key})'.format(key=context_key(context)),
problem='found keys not used with the current segment type: {0}'.format( context_mark=context[-1][1].mark,
list_sep.join(unknown_keys)), problem='found keys not used with the current segment type: {0}'.format(
problem_mark=list(unknown_keys)[0].mark) list_sep.join(unknown_keys)),
problem_mark=list(unknown_keys)[0].mark
)
hadproblem = True hadproblem = True
if not (keys >= required_keys[segment_type]): if not (keys >= required_keys[segment_type]):
missing_keys = required_keys[segment_type] - keys missing_keys = required_keys[segment_type] - keys
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)), echoerr(
context_mark=context[-1][1].mark, context='Error while checking segments (key {key})'.format(key=context_key(context)),
problem='found missing required keys: {0}'.format( context_mark=context[-1][1].mark,
list_sep.join(missing_keys))) problem='found missing required keys: {0}'.format(
list_sep.join(missing_keys))
)
hadproblem = True hadproblem = True
if not (segment_type == 'function' or (keys & highlight_keys)): if not (segment_type == 'function' or (keys & highlight_keys)):
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)), echoerr(
context_mark=context[-1][1].mark, context='Error while checking segments (key {key})'.format(key=context_key(context)),
problem='found missing keys required to determine highlight group. Either highlight_group or name key must be present') context_mark=context[-1][1].mark,
problem=(
'found missing keys required to determine highlight group. '
'Either highlight_group or name key must be present'
)
)
hadproblem = True hadproblem = True
return True, False, hadproblem return True, False, hadproblem
@ -842,35 +926,52 @@ def check_segment_name(name, data, context, echoerr):
if divider_hl_group: if divider_hl_group:
r = hl_exists(divider_hl_group, data, context, echoerr, allow_gradients=True) r = hl_exists(divider_hl_group, data, context, echoerr, allow_gradients=True)
if r: if r:
echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)), echoerr(
problem='found highlight group {0} not defined in the following colorschemes: {1}\n(Group name was obtained from function documentation.)'.format( context='Error while checking theme (key {key})'.format(key=context_key(context)),
divider_hl_group, list_sep.join(r)), problem=(
problem_mark=name.mark) 'found highlight group {0} not defined in the following colorschemes: {1}\n'
'(Group name was obtained from function documentation.)'
).format(divider_hl_group, list_sep.join(r)),
problem_mark=name.mark
)
hadproblem = True hadproblem = True
if hl_groups: if hl_groups:
greg = re.compile(r'``([^`]+)``( \(gradient\))?') greg = re.compile(r'``([^`]+)``( \(gradient\))?')
hl_groups = [[greg.match(subs).groups() for subs in s.split(' or ')] for s in (list_sep.join(hl_groups)).split(', ')] hl_groups = [[greg.match(subs).groups() for subs in s.split(' or ')]
for s in (list_sep.join(hl_groups)).split(', ')]
for required_pack in hl_groups: for required_pack in hl_groups:
rs = [hl_exists(hl_group, data, context, echoerr, allow_gradients=('force' if gradient else False)) rs = [hl_exists(hl_group, data, context, echoerr, allow_gradients=('force' if gradient else False))
for hl_group, gradient in required_pack] for hl_group, gradient in required_pack]
if all(rs): if all(rs):
echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)), echoerr(
problem='found highlight groups list ({0}) with all groups not defined in some colorschemes\n(Group names were taken from function documentation.)'.format( context='Error while checking theme (key {key})'.format(key=context_key(context)),
list_sep.join((h[0] for h in required_pack))), problem=(
problem_mark=name.mark) 'found highlight groups list ({0}) with all groups not defined in some colorschemes\n'
'(Group names were taken from function documentation.)'
).format(list_sep.join((h[0] for h in required_pack))),
problem_mark=name.mark
)
for r, h in zip(rs, required_pack): for r, h in zip(rs, required_pack):
echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)), echoerr(
problem='found highlight group {0} not defined in the following colorschemes: {1}'.format( context='Error while checking theme (key {key})'.format(key=context_key(context)),
h[0], list_sep.join(r))) problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
h[0], list_sep.join(r))
)
hadproblem = True hadproblem = True
else: else:
r = hl_exists(name, data, context, echoerr, allow_gradients=True) r = hl_exists(name, data, context, echoerr, allow_gradients=True)
if r: if r:
echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)), echoerr(
problem='found highlight group {0} not defined in the following colorschemes: {1}\n(If not specified otherwise in documentation, highlight group for function segments\nis the same as the function name.)'.format( context='Error while checking theme (key {key})'.format(key=context_key(context)),
name, list_sep.join(r)), problem=(
problem_mark=name.mark) 'found highlight group {0} not defined in the following colorschemes: {1}\n'
'(If not specified otherwise in documentation, '
'highlight group for function segments\n'
'is the same as the function name.)'
).format(name, list_sep.join(r)),
problem_mark=name.mark
)
hadproblem = True hadproblem = True
return True, False, hadproblem return True, False, hadproblem
@ -915,17 +1016,23 @@ def hl_exists(hl_group, data, context, echoerr, allow_gradients=False):
if hasgradient: if hasgradient:
hadgradient = True hadgradient = True
if allow_gradients is False and not hascolor and hasgradient: if allow_gradients is False and not hascolor and hasgradient:
echoerr(context='Error while checking highlight group in theme (key {key})'.format(key=context_key(context)), echoerr(
context_mark=getattr(hl_group, 'mark', None), context='Error while checking highlight group in theme (key {key})'.format(
problem='group {0} is using gradient {1} instead of a color'.format(hl_group, color), key=context_key(context)),
problem_mark=color.mark) context_mark=getattr(hl_group, 'mark', None),
problem='group {0} is using gradient {1} instead of a color'.format(hl_group, color),
problem_mark=color.mark
)
r.append(colorscheme) r.append(colorscheme)
continue continue
if allow_gradients == 'force' and not hadgradient: if allow_gradients == 'force' and not hadgradient:
echoerr(context='Error while checking highlight group in theme (key {key})'.format(key=context_key(context)), echoerr(
context_mark=getattr(hl_group, 'mark', None), context='Error while checking highlight group in theme (key {key})'.format(
problem='group {0} should have at least one gradient color, but it has no'.format(hl_group), key=context_key(context)),
problem_mark=group_config.mark) context_mark=getattr(hl_group, 'mark', None),
problem='group {0} should have at least one gradient color, but it has no'.format(hl_group),
problem_mark=group_config.mark
)
r.append(colorscheme) r.append(colorscheme)
return r return r
@ -933,10 +1040,12 @@ def hl_exists(hl_group, data, context, echoerr, allow_gradients=False):
def check_highlight_group(hl_group, data, context, echoerr): def check_highlight_group(hl_group, data, context, echoerr):
r = hl_exists(hl_group, data, context, echoerr) r = hl_exists(hl_group, data, context, echoerr)
if r: if r:
echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)), echoerr(
problem='found highlight group {0} not defined in the following colorschemes: {1}'.format( context='Error while checking theme (key {key})'.format(key=context_key(context)),
hl_group, list_sep.join(r)), problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
problem_mark=hl_group.mark) hl_group, list_sep.join(r)),
problem_mark=hl_group.mark
)
return True, False, True return True, False, True
return True, False, False return True, False, False
@ -944,15 +1053,19 @@ def check_highlight_group(hl_group, data, context, echoerr):
def check_highlight_groups(hl_groups, data, context, echoerr): def check_highlight_groups(hl_groups, data, context, echoerr):
rs = [hl_exists(hl_group, data, context, echoerr) for hl_group in hl_groups] rs = [hl_exists(hl_group, data, context, echoerr) for hl_group in hl_groups]
if all(rs): if all(rs):
echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)), echoerr(
problem='found highlight groups list ({0}) with all groups not defined in some colorschemes'.format( context='Error while checking theme (key {key})'.format(key=context_key(context)),
list_sep.join((unicode(h) for h in hl_groups))), problem='found highlight groups list ({0}) with all groups not defined in some colorschemes'.format(
problem_mark=hl_groups.mark) list_sep.join((unicode(h) for h in hl_groups))),
problem_mark=hl_groups.mark
)
for r, hl_group in zip(rs, hl_groups): for r, hl_group in zip(rs, hl_groups):
echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)), echoerr(
problem='found highlight group {0} not defined in the following colorschemes: {1}'.format( context='Error while checking theme (key {key})'.format(key=context_key(context)),
problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
hl_group, list_sep.join(r)), hl_group, list_sep.join(r)),
problem_mark=hl_group.mark) problem_mark=hl_group.mark
)
return True, False, True return True, False, True
return True, False, False return True, False, False
@ -1005,9 +1118,11 @@ def check_args_variant(segment, args, data, context, echoerr):
hadproblem = False hadproblem = False
if required_args - present_args: if required_args - present_args:
echoerr(context='Error while checking segment arguments (key {key})'.format(key=context_key(context)), echoerr(
context_mark=args.mark, context='Error while checking segment arguments (key {key})'.format(key=context_key(context)),
problem='some of the required keys are missing: {0}'.format(list_sep.join(required_args - present_args))) context_mark=args.mark,
problem='some of the required keys are missing: {0}'.format(list_sep.join(required_args - present_args))
)
hadproblem = True hadproblem = True
if not all_args >= present_args: if not all_args >= present_args:
@ -1019,7 +1134,13 @@ def check_args_variant(segment, args, data, context, echoerr):
if isinstance(segment, ThreadedSegment): if isinstance(segment, ThreadedSegment):
for key in set(threaded_args_specs) & present_args: for key in set(threaded_args_specs) & present_args:
proceed, khadproblem = threaded_args_specs[key].match(args[key], args.mark, data, context + ((key, args[key]),), echoerr) proceed, khadproblem = threaded_args_specs[key].match(
args[key],
args.mark,
data,
context + ((key, args[key]),),
echoerr
)
if khadproblem: if khadproblem:
hadproblem = True hadproblem = True
if not proceed: if not proceed:
@ -1070,7 +1191,10 @@ def get_all_possible_segments(data, context, echoerr):
for segments in theme_config.get('segments', {}).values(): for segments in theme_config.get('segments', {}).values():
for segment in segments: for segment in segments:
if segment.get('type', 'function') == 'function': if segment.get('type', 'function') == 'function':
module = segment.get('module', context[0][1].get('default_module', 'powerline.segments.' + data['ext'])) module = segment.get(
'module',
context[0][1].get('default_module', 'powerline.segments.' + data['ext'])
)
func = import_segment(name, data, context, echoerr, module=module) func = import_segment(name, data, context, echoerr, module=module)
if func: if func:
yield func yield func
@ -1085,7 +1209,7 @@ segment_module_spec = Spec().type(unicode).func(check_segment_module).optional()
sub_segments_spec = Spec() sub_segments_spec = Spec()
segment_spec = Spec( segment_spec = Spec(
type=Spec().oneof(type_keys).optional(), type=Spec().oneof(type_keys).optional(),
name=Spec().re('^[a-zA-Z_]\w+$').func(check_segment_name).optional(), name=Spec().re('^[a-zA-Z_]\w*$').func(check_segment_name).optional(),
exclude_modes=Spec().list(vim_mode_spec()).optional(), exclude_modes=Spec().list(vim_mode_spec()).optional(),
include_modes=Spec().list(vim_mode_spec()).optional(), include_modes=Spec().list(vim_mode_spec()).optional(),
draw_hard_divider=Spec().type(bool).optional(), draw_hard_divider=Spec().type(bool).optional(),
@ -1101,11 +1225,15 @@ segment_spec = Spec(
args=args_spec().func(lambda *args, **kwargs: check_args(get_one_segment_variant, *args, **kwargs)), args=args_spec().func(lambda *args, **kwargs: check_args(get_one_segment_variant, *args, **kwargs)),
contents=Spec().type(unicode).optional(), contents=Spec().type(unicode).optional(),
highlight_group=Spec().list( highlight_group=Spec().list(
highlight_group_spec().re('^(?:(?!:divider$).)+$', highlight_group_spec().re(
lambda value: 'it is recommended that only divider highlight group names end with ":divider"') '^(?:(?!:divider$).)+$',
(lambda value: 'it is recommended that only divider highlight group names end with ":divider"')
)
).func(check_highlight_groups).optional(), ).func(check_highlight_groups).optional(),
divider_highlight_group=highlight_group_spec().func(check_highlight_group).re(':divider$', divider_highlight_group=highlight_group_spec().func(check_highlight_group).re(
lambda value: 'it is recommended that divider highlight group names end with ":divider"').optional(), ':divider$',
(lambda value: 'it is recommended that divider highlight group names end with ":divider"')
).optional(),
segments=sub_segments_spec, segments=sub_segments_spec,
).func(check_full_segment_data) ).func(check_full_segment_data)
sub_segments_spec.optional().list(segment_spec) sub_segments_spec.optional().list(segment_spec)
@ -1114,8 +1242,8 @@ segdict_spec=Spec(
left=segments_spec().context_message('Error while loading segments from left side (key {key})'), left=segments_spec().context_message('Error while loading segments from left side (key {key})'),
right=segments_spec().context_message('Error while loading segments from right side (key {key})'), right=segments_spec().context_message('Error while loading segments from right side (key {key})'),
).func( ).func(
lambda value, *args: (True, True, not (('left' in value) or ('right' in value))), (lambda value, *args: (True, True, not (('left' in value) or ('right' in value)))),
lambda value: 'segments dictionary must contain either left, right or both keys' (lambda value: 'segments dictionary must contain either left, right or both keys')
).context_message('Error while loading segments (key {key})').copy ).context_message('Error while loading segments (key {key})').copy
theme_spec = (Spec( theme_spec = (Spec(
default_module=segment_module_spec(), default_module=segment_module_spec(),
@ -1133,9 +1261,19 @@ theme_spec = (Spec(
).context_message('Error while loading theme')) ).context_message('Error while loading theme'))
def generate_json_config_loader(lhadproblem):
def load_json_config(config_file_path, load=load, open_file=open_file):
with open_file(config_file_path) as config_file_fp:
r, hadproblem = load(config_file_fp)
if hadproblem:
lhadproblem[0] = True
return r
return load_json_config
def check(paths=None, debug=False): def check(paths=None, debug=False):
search_paths = paths or get_config_paths() search_paths = paths or get_config_paths()
find_config_file = generate_config_finder(lambda: search_paths) find_config_files = generate_config_finder(lambda: search_paths)
logger = logging.getLogger('powerline-lint') logger = logging.getLogger('powerline-lint')
logger.setLevel(logging.DEBUG if debug else logging.ERROR) logger.setLevel(logging.DEBUG if debug else logging.ERROR)
@ -1143,6 +1281,11 @@ def check(paths=None, debug=False):
ee = EchoErr(echoerr, logger) ee = EchoErr(echoerr, logger)
lhadproblem = [False]
load_json_config = generate_json_config_loader(lhadproblem)
config_loader = ConfigLoader(run_once=True, load=load_json_config)
paths = { paths = {
'themes': defaultdict(lambda: []), 'themes': defaultdict(lambda: []),
'colorschemes': defaultdict(lambda: []), 'colorschemes': defaultdict(lambda: []),
@ -1198,17 +1341,9 @@ def check(paths=None, debug=False):
typ, typ,
)) ))
lhadproblem = [False]
def load_config(stream):
r, hadproblem = load(stream)
if hadproblem:
lhadproblem[0] = True
return r
hadproblem = False hadproblem = False
try: try:
main_config = load_json_config(find_config_file('config'), load=load_config, open_file=open_file) main_config = load_config('config', find_config_files, config_loader)
except IOError: except IOError:
main_config = {} main_config = {}
sys.stderr.write('\nConfiguration file not found: config.json\n') sys.stderr.write('\nConfiguration file not found: config.json\n')
@ -1218,13 +1353,18 @@ def check(paths=None, debug=False):
sys.stderr.write(str(e) + '\n') sys.stderr.write(str(e) + '\n')
hadproblem = True hadproblem = True
else: else:
if main_spec.match(main_config, data={'configs': configs, 'lists': lists}, context=(('', main_config),), echoerr=ee)[1]: if main_spec.match(
main_config,
data={'configs': configs, 'lists': lists},
context=(('', main_config),),
echoerr=ee
)[1]:
hadproblem = True hadproblem = True
import_paths = [os.path.expanduser(path) for path in main_config.get('common', {}).get('paths', [])] import_paths = [os.path.expanduser(path) for path in main_config.get('common', {}).get('paths', [])]
try: try:
colors_config = load_json_config(find_config_file('colors'), load=load_config, open_file=open_file) colors_config = load_config('colors', find_config_files, config_loader)
except IOError: except IOError:
colors_config = {} colors_config = {}
sys.stderr.write('\nConfiguration file not found: colors.json\n') sys.stderr.write('\nConfiguration file not found: colors.json\n')
@ -1330,8 +1470,14 @@ def check(paths=None, debug=False):
theme_configs[ext][theme] = config theme_configs[ext][theme] = config
for ext, configs in theme_configs.items(): for ext, configs in theme_configs.items():
data = {'ext': ext, 'colorscheme_configs': colorscheme_configs, 'import_paths': import_paths, data = {
'main_config': main_config, 'ext_theme_configs': configs, 'colors_config': colors_config} 'ext': ext,
'colorscheme_configs': colorscheme_configs,
'import_paths': import_paths,
'main_config': main_config,
'ext_theme_configs': configs,
'colors_config': colors_config
}
for theme, config in configs.items(): for theme, config in configs.items():
data['theme'] = theme data['theme'] = theme
if theme_spec.match(config, context=(('', config),), data=data, echoerr=ee)[1]: if theme_spec.match(config, context=(('', config),), data=data, echoerr=ee)[1]:

View File

@ -16,8 +16,21 @@ def gen_new(cls):
return __new__ return __new__
def gen_init(cls):
def __init__(self, value, mark):
return cls.__init__(self, value)
return __init__
def gen_getnewargs(cls):
def __getnewargs__(self):
return (self.value, self.mark)
return __getnewargs__
class MarkedUnicode(unicode): class MarkedUnicode(unicode):
__new__ = gen_new(unicode) __new__ = gen_new(unicode)
__getnewargs__ = gen_getnewargs(unicode)
def _proc_partition(self, part_result): def _proc_partition(self, part_result):
pointdiff = 1 pointdiff = 1
@ -41,33 +54,43 @@ class MarkedUnicode(unicode):
class MarkedInt(int): class MarkedInt(int):
__new__ = gen_new(int) __new__ = gen_new(int)
__getnewargs__ = gen_getnewargs(int)
class MarkedFloat(float): class MarkedFloat(float):
__new__ = gen_new(float) __new__ = gen_new(float)
__getnewargs__ = gen_getnewargs(float)
class MarkedDict(dict): class MarkedDict(dict):
__new__ = gen_new(dict) __new__ = gen_new(dict)
__init__ = gen_init(dict)
def __init__(self, value, mark): __getnewargs__ = gen_getnewargs(dict)
super(MarkedDict, self).__init__(value)
def copy(self): def copy(self):
return MarkedDict(super(MarkedDict, self).copy(), self.mark) return MarkedDict(super(MarkedDict, self).copy(), self.mark)
class MarkedList(list):
__new__ = gen_new(list)
__init__ = gen_init(list)
__getnewargs__ = gen_getnewargs(list)
class MarkedValue: class MarkedValue:
def __init__(self, value, mark): def __init__(self, value, mark):
self.mark = mark self.mark = mark
self.value = value self.value = value
__getinitargs__ = gen_getnewargs(None)
specialclasses = { specialclasses = {
unicode: MarkedUnicode, unicode: MarkedUnicode,
int: MarkedInt, int: MarkedInt,
float: MarkedFloat, float: MarkedFloat,
dict: MarkedDict, dict: MarkedDict,
list: MarkedList,
} }
classcache = {} classcache = {}

View File

@ -3,37 +3,53 @@ from threading import Lock
from powerline.renderer import Renderer from powerline.renderer import Renderer
from powerline.lib.config import ConfigLoader from powerline.lib.config import ConfigLoader
from powerline import Powerline from powerline import Powerline
from tests.lib import Args, replace_attr
from copy import deepcopy from copy import deepcopy
from time import sleep from time import sleep
from functools import wraps from functools import wraps
import os
access_log = [] class TestHelpers(object):
access_lock = Lock() def __init__(self, config):
self.config = config
self.access_log = []
self.access_lock = Lock()
def loader_condition(self, path):
return (path in self.config) and path
def load_json_config(config_file_path, *args, **kwargs): def find_config_files(self, cfg_path, config_loader, loader_callback):
global access_log if cfg_path.endswith('.json'):
with access_lock: cfg_path = cfg_path[:-5]
access_log.append(config_file_path) if cfg_path.startswith('/'):
try: cfg_path = cfg_path.lstrip('/')
return deepcopy(config_container['config'][config_file_path]) with self.access_lock:
except KeyError: self.access_log.append('check:' + cfg_path)
raise IOError(config_file_path) if cfg_path in self.config:
yield cfg_path
else:
if config_loader:
config_loader.register_missing(self.loader_condition, loader_callback, cfg_path)
raise IOError(('fcf:' if cfg_path.endswith('raise') else '') + cfg_path)
def load_json_config(self, config_file_path, *args, **kwargs):
if config_file_path.endswith('.json'):
config_file_path = config_file_path[:-5]
if config_file_path.startswith('/'):
config_file_path = config_file_path.lstrip('/')
with self.access_lock:
self.access_log.append('load:' + config_file_path)
try:
return deepcopy(self.config[config_file_path])
except KeyError:
raise IOError(config_file_path)
def _find_config_file(config, search_paths, config_file): def pop_events(self):
if config_file.endswith('raise') and config_file not in config: with self.access_lock:
raise IOError('fcf:' + config_file) r = self.access_log[:]
return config_file self.access_log = []
return r
def pop_events():
global access_log
with access_lock:
r = access_log[:]
access_log = []
return r
def log_call(func): def log_call(func):
@ -44,7 +60,7 @@ def log_call(func):
return ret return ret
class Watcher(object): class TestWatcher(object):
events = set() events = set()
lock = Lock() lock = Lock()
@ -109,20 +125,46 @@ class EvenSimplerRenderer(Renderer):
class TestPowerline(Powerline): class TestPowerline(Powerline):
_created = False _created = False
def __init__(self, _helpers, **kwargs):
super(TestPowerline, self).__init__(**kwargs)
self._helpers = _helpers
self.find_config_files = _helpers.find_config_files
@staticmethod @staticmethod
def get_local_themes(local_themes): def get_local_themes(local_themes):
return local_themes return local_themes
@staticmethod
def get_config_paths():
return ['']
def _will_create_renderer(self): def _will_create_renderer(self):
return self.cr_kwargs return self.cr_kwargs
def _pop_events(self):
renderer = SimpleRenderer return self._helpers.pop_events()
def get_powerline(**kwargs): renderer = EvenSimplerRenderer
class TestConfigLoader(ConfigLoader):
def __init__(self, _helpers, **kwargs):
watcher = TestWatcher()
super(TestConfigLoader, self).__init__(
load=_helpers.load_json_config,
watcher=watcher,
watcher_type='test',
**kwargs
)
def get_powerline(config, **kwargs):
helpers = TestHelpers(config)
return get_powerline_raw( return get_powerline_raw(
helpers,
TestPowerline, TestPowerline,
_helpers=helpers,
ext='test', ext='test',
renderer_module='tests.lib.config_mock', renderer_module='tests.lib.config_mock',
logger=Logger(), logger=Logger(),
@ -130,45 +172,42 @@ def get_powerline(**kwargs):
) )
def get_powerline_raw(PowerlineClass, **kwargs): def select_renderer(simpler_renderer=False):
global renderer global renderer
watcher = Watcher() renderer = EvenSimplerRenderer if simpler_renderer else SimpleRenderer
if kwargs.pop('simpler_renderer', False):
renderer = EvenSimplerRenderer
else: def get_powerline_raw(helpers, PowerlineClass, **kwargs):
renderer = SimpleRenderer if not isinstance(helpers, TestHelpers):
helpers = TestHelpers(helpers)
select_renderer(kwargs.pop('simpler_renderer', False))
pl = PowerlineClass( pl = PowerlineClass(
config_loader=ConfigLoader( config_loader=TestConfigLoader(
load=load_json_config, _helpers=helpers,
watcher=watcher,
watcher_type='test',
run_once=kwargs.get('run_once') run_once=kwargs.get('run_once')
), ),
**kwargs **kwargs
) )
pl._watcher = watcher pl._watcher = pl.config_loader.watcher
return pl return pl
config_container = None def swap_attributes(config, powerline_module):
return replace_attr(powerline_module, 'os', Args(
path=Args(
def swap_attributes(cfg_container, powerline_module, replaces): isfile=lambda path: path.lstrip('/').replace('.json', '') in config,
global config_container join=os.path.join,
config_container = cfg_container expanduser=lambda path: path,
if not replaces: realpath=lambda path: path,
replaces = { dirname=os.path.dirname,
'_find_config_file': lambda *args: _find_config_file(config_container['config'], *args), ),
} environ={},
for attr, val in replaces.items(): ))
old_val = getattr(powerline_module, attr)
setattr(powerline_module, attr, val)
replaces[attr] = old_val
return replaces
def add_watcher_events(p, *args, **kwargs): def add_watcher_events(p, *args, **kwargs):
p._watcher._reset(args) if isinstance(p._watcher, TestWatcher):
p._watcher._reset(args)
while not p._will_create_renderer(): while not p._will_create_renderer():
sleep(kwargs.get('interval', 0.1)) sleep(kwargs.get('interval', 0.1))
if not kwargs.get('wait', True): if not kwargs.get('wait', True):

View File

@ -71,6 +71,7 @@ class TestParser(TestCase):
'-c', 'common.spaces=4', '-c', 'common.spaces=4',
'-t', 'default.segment_data.hostname.before=H:', '-t', 'default.segment_data.hostname.before=H:',
'-p', '.', '-p', '.',
'-p', '..',
'-R', 'smth={"abc":"def"}', '-R', 'smth={"abc":"def"}',
], { ], {
'ext': ['shell'], 'ext': ['shell'],
@ -90,7 +91,7 @@ class TestParser(TestCase):
} }
} }
}, },
'config_path': ['.'], 'config_path': ['.', '..'],
'renderer_arg': {'smth': {'abc': 'def'}}, 'renderer_arg': {'smth': {'abc': 'def'}},
}), }),
(['shell', '-R', 'arg=true'], {'ext': ['shell'], 'renderer_arg': {'arg': True}}), (['shell', '-R', 'arg=true'], {'ext': ['shell'], 'renderer_arg': {'arg': True}}),

View File

@ -0,0 +1,263 @@
# vim:fileencoding=utf-8:noet
from __future__ import unicode_literals
from powerline import Powerline
from tests import TestCase
from tests.lib.config_mock import select_renderer
from shutil import rmtree
import os
import json
from powerline.lib import mergedicts_copy as mdc
from subprocess import check_call
from operator import add
CONFIG_DIR = 'tests/config'
root_config = lambda: {
'common': {
'dividers': {
'left': {
'hard': '#>',
'soft': '|>',
},
'right': {
'hard': '<#',
'soft': '<|',
},
},
'spaces': 0,
'interval': None,
'watcher': 'auto',
},
'ext': {
'test': {
'theme': 'default',
'colorscheme': 'default',
},
},
}
colors_config = lambda: {
'colors': {
'c1': 1,
'c2': 2,
},
'gradients': {
},
}
colorscheme_config = lambda: {
'groups': {
'g': {'fg': 'c1', 'bg': 'c2', 'attr': []},
}
}
theme_config = lambda: {
'segment_data': {
's': {
'before': 'b',
},
},
'segments': {
'left': [
{
'type': 'string',
'name': 's',
'contents': 't',
'highlight_group': ['g'],
},
],
'right': [],
}
}
main_tree = lambda: {
'1/config': root_config(),
'1/colors': colors_config(),
'1/colorschemes/default': colorscheme_config(),
'1/themes/test/default': theme_config(),
}
def mkdir_recursive(directory):
if os.path.isdir(directory):
return
mkdir_recursive(os.path.dirname(directory))
os.mkdir(directory)
class TestPowerline(Powerline):
def get_config_paths(self):
return tuple(sorted([
os.path.join(CONFIG_DIR, d)
for d in os.listdir(CONFIG_DIR)
]))
class WithConfigTree(object):
__slots__ = ('tree', 'p', 'p_kwargs')
def __init__(self, tree, p_kwargs={'run_once': True}):
self.tree = tree
self.p = None
self.p_kwargs = p_kwargs
def __enter__(self, *args):
os.mkdir(CONFIG_DIR)
for k, v in self.tree.items():
fname = os.path.join(CONFIG_DIR, k) + '.json'
mkdir_recursive(os.path.dirname(fname))
with open(fname, 'w') as F:
json.dump(v, F)
select_renderer(simpler_renderer=True)
self.p = TestPowerline(
ext='test',
renderer_module='tests.lib.config_mock',
**self.p_kwargs
)
if os.environ.get('POWERLINE_RUN_LINT_DURING_TESTS'):
try:
check_call(['scripts/powerline-lint'] + reduce(add, (
['-p', d] for d in self.p.get_config_paths()
)))
except:
self.__exit__()
raise
return self.p.__enter__(*args)
def __exit__(self, *args):
try:
rmtree(CONFIG_DIR)
finally:
if self.p:
self.p.__exit__(*args)
class TestMerging(TestCase):
def assertRenderEqual(self, p, output, **kwargs):
self.assertEqual(p.render(**kwargs).replace(' ', ' '), output)
def test_not_merged_config(self):
with WithConfigTree(main_tree()) as p:
self.assertRenderEqual(p, '{12} bt{2-}#>{--}')
def test_root_config_merging(self):
with WithConfigTree(mdc(main_tree(), {
'2/config': {
'common': {
'dividers': {
'left': {
'hard': '!>',
}
}
}
},
})) as p:
self.assertRenderEqual(p, '{12} bt{2-}!>{--}')
with WithConfigTree(mdc(main_tree(), {
'2/config': {
'common': {
'dividers': {
'left': {
'hard': '!>',
}
}
}
},
'3/config': {
'common': {
'dividers': {
'left': {
'hard': '>>',
}
}
}
},
})) as p:
self.assertRenderEqual(p, '{12} bt{2-}>>{--}')
with WithConfigTree(mdc(main_tree(), {
'2/config': {
'common': {
'spaces': 1,
}
},
'3/config': {
'common': {
'dividers': {
'left': {
'hard': '>>',
}
}
}
},
})) as p:
self.assertRenderEqual(p, '{12} bt {2-}>>{--}')
def test_colors_config_merging(self):
with WithConfigTree(mdc(main_tree(), {
'2/colors': {
'colors': {
'c1': 3,
}
},
})) as p:
self.assertRenderEqual(p, '{32} bt{2-}#>{--}')
with WithConfigTree(mdc(main_tree(), {
'2/colors': {
'colors': {
'c1': 3,
}
},
'3/colors': {
'colors': {
'c1': 4,
}
},
})) as p:
self.assertRenderEqual(p, '{42} bt{2-}#>{--}')
with WithConfigTree(mdc(main_tree(), {
'2/colors': {
'colors': {
'c1': 3,
}
},
'3/colors': {
'colors': {
'c2': 4,
}
},
})) as p:
self.assertRenderEqual(p, '{34} bt{4-}#>{--}')
def test_colorschemes_merging(self):
with WithConfigTree(mdc(main_tree(), {
'2/colorschemes/default': {
'groups': {
'g': {'fg': 'c2', 'bg': 'c1', 'attr': []},
}
},
})) as p:
self.assertRenderEqual(p, '{21} bt{1-}#>{--}')
def test_theme_merging(self):
with WithConfigTree(mdc(main_tree(), {
'2/themes/test/default': {
'segment_data': {
's': {
'after': 'a',
}
}
},
})) as p:
self.assertRenderEqual(p, '{12} bta{2-}#>{--}')
if __name__ == '__main__':
from tests import main
main()

View File

@ -1,11 +1,12 @@
# vim:fileencoding=utf-8:noet # vim:fileencoding=utf-8:noet
from __future__ import unicode_literals from __future__ import unicode_literals
import powerline as powerline_module
from time import sleep from time import sleep
from tests import TestCase
from tests.lib import replace_item
from tests.lib.config_mock import swap_attributes, get_powerline, pop_events, add_watcher_events
from copy import deepcopy from copy import deepcopy
from functools import wraps
from tests import TestCase
from tests.lib.config_mock import get_powerline, add_watcher_events
config = { config = {
@ -93,201 +94,192 @@ config = {
} }
def with_new_config(func):
@wraps(func)
def f(self):
return func(self, deepcopy(config))
return f
class TestConfigReload(TestCase): class TestConfigReload(TestCase):
def assertAccessEvents(self, *args): def assertAccessEvents(self, p, *args):
self.assertEqual(set(pop_events()), set(args)) events = set()
for event in args:
if ':' not in event:
events.add('check:' + event)
events.add('load:' + event)
else:
events.add(event)
self.assertEqual(set(p._pop_events()), events)
def test_noreload(self): @with_new_config
with get_powerline(run_once=True) as p: def test_noreload(self, config):
with replace_item(globals(), 'config', deepcopy(config)): with get_powerline(config, run_once=True) as p:
self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
self.assertAccessEvents('config', 'colors', 'colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default')
config['config']['common']['spaces'] = 1 config['config']['common']['spaces'] = 1
add_watcher_events(p, 'config', wait=False, interval=0.05) add_watcher_events(p, 'config', wait=False, interval=0.05)
# When running once thread should not start # When running once thread should not start
self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
self.assertAccessEvents() self.assertAccessEvents(p)
self.assertEqual(p.logger._pop_msgs(), []) self.assertEqual(p.logger._pop_msgs(), [])
# Without the following assertion test_reload_colors may fail for
# unknown reason (with AssertionError telling about “config” accessed
# one more time then needed)
pop_events()
def test_reload_main(self): @with_new_config
with get_powerline(run_once=False) as p: def test_reload_main(self, config):
with replace_item(globals(), 'config', deepcopy(config)): with get_powerline(config, run_once=False) as p:
self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
self.assertAccessEvents('config', 'colors', 'colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default')
config['config']['common']['spaces'] = 1 config['config']['common']['spaces'] = 1
add_watcher_events(p, 'config') add_watcher_events(p, 'config')
self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>><None None None>') self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>><None None None>')
self.assertAccessEvents('config') self.assertAccessEvents(p, 'config')
self.assertEqual(p.logger._pop_msgs(), []) self.assertEqual(p.logger._pop_msgs(), [])
config['config']['ext']['test']['theme'] = 'nonexistent' config['config']['ext']['test']['theme'] = 'nonexistent'
add_watcher_events(p, 'config') add_watcher_events(p, 'config')
self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>><None None None>') self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>><None None None>')
self.assertAccessEvents('config', 'themes/test/nonexistent') self.assertAccessEvents(p, 'config', 'check:themes/test/nonexistent')
# It should normally handle file missing error # It should normally handle file missing error
self.assertEqual(p.logger._pop_msgs(), ['exception:test:powerline:Failed to create renderer: themes/test/nonexistent']) self.assertEqual(p.logger._pop_msgs(), ['exception:test:powerline:Failed to create renderer: themes/test/nonexistent'])
config['config']['ext']['test']['theme'] = 'default' config['config']['ext']['test']['theme'] = 'default'
add_watcher_events(p, 'config') add_watcher_events(p, 'config')
self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>><None None None>') self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>><None None None>')
self.assertAccessEvents('config', 'themes/test/default') self.assertAccessEvents(p, 'config', 'themes/test/default')
self.assertEqual(p.logger._pop_msgs(), []) self.assertEqual(p.logger._pop_msgs(), [])
config['config']['ext']['test']['colorscheme'] = 'nonexistent' config['config']['ext']['test']['colorscheme'] = 'nonexistent'
add_watcher_events(p, 'config') add_watcher_events(p, 'config')
self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>><None None None>') self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>><None None None>')
self.assertAccessEvents('config', 'colorschemes/nonexistent', 'colorschemes/test/__main__', 'colorschemes/test/nonexistent') self.assertAccessEvents(p, 'config', 'check:colorschemes/nonexistent', 'check:colorschemes/test/__main__', 'check:colorschemes/test/nonexistent')
# It should normally handle file missing error # It should normally handle file missing error
self.assertEqual(p.logger._pop_msgs(), [ self.assertEqual(p.logger._pop_msgs(), [
'exception:test:powerline:Failed to load colorscheme: colorschemes/nonexistent', 'exception:test:powerline:Failed to load colorscheme: colorschemes/nonexistent',
'exception:test:powerline:Failed to load colorscheme: colorschemes/test/__main__', 'exception:test:powerline:Failed to load colorscheme: colorschemes/test/__main__',
'exception:test:powerline:Failed to load colorscheme: colorschemes/test/nonexistent', 'exception:test:powerline:Failed to load colorscheme: colorschemes/test/nonexistent',
'exception:test:powerline:Failed to create renderer: colorschemes/test/nonexistent' 'exception:test:powerline:Failed to create renderer: colorschemes/test/nonexistent'
]) ])
config['config']['ext']['test']['colorscheme'] = '2' config['config']['ext']['test']['colorscheme'] = '2'
add_watcher_events(p, 'config') add_watcher_events(p, 'config')
self.assertEqual(p.render(), '<2 3 1> s <3 4 False>>><1 4 4>g <4 False False>>><None None None>') self.assertEqual(p.render(), '<2 3 1> s <3 4 False>>><1 4 4>g <4 False False>>><None None None>')
self.assertAccessEvents('config', 'colorschemes/2', 'colorschemes/test/__main__', 'colorschemes/test/2') self.assertAccessEvents(p, 'config', 'check:colorschemes/2', 'check:colorschemes/test/__main__', 'colorschemes/test/2')
self.assertEqual(p.logger._pop_msgs(), []) self.assertEqual(p.logger._pop_msgs(), [])
config['config']['ext']['test']['theme'] = '2' config['config']['ext']['test']['theme'] = '2'
add_watcher_events(p, 'config') add_watcher_events(p, 'config')
self.assertEqual(p.render(), '<2 3 1> t <3 4 False>>><1 4 4>b <4 False False>>><None None None>') self.assertEqual(p.render(), '<2 3 1> t <3 4 False>>><1 4 4>b <4 False False>>><None None None>')
self.assertAccessEvents('config', 'themes/test/2') self.assertAccessEvents(p, 'config', 'themes/test/2')
self.assertEqual(p.logger._pop_msgs(), []) self.assertEqual(p.logger._pop_msgs(), [])
self.assertEqual(p.renderer.local_themes, None) self.assertEqual(p.renderer.local_themes, None)
config['config']['ext']['test']['local_themes'] = 'something' config['config']['ext']['test']['local_themes'] = 'something'
add_watcher_events(p, 'config') add_watcher_events(p, 'config')
self.assertEqual(p.render(), '<2 3 1> t <3 4 False>>><1 4 4>b <4 False False>>><None None None>') self.assertEqual(p.render(), '<2 3 1> t <3 4 False>>><1 4 4>b <4 False False>>><None None None>')
self.assertAccessEvents('config') self.assertAccessEvents(p, 'config')
self.assertEqual(p.logger._pop_msgs(), []) self.assertEqual(p.logger._pop_msgs(), [])
self.assertEqual(p.renderer.local_themes, 'something') self.assertEqual(p.renderer.local_themes, 'something')
pop_events()
def test_reload_unexistent(self): @with_new_config
with get_powerline(run_once=False) as p: def test_reload_unexistent(self, config):
with replace_item(globals(), 'config', deepcopy(config)): with get_powerline(config, run_once=False) as p:
self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
self.assertAccessEvents('config', 'colors', 'colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default')
config['config']['ext']['test']['colorscheme'] = 'nonexistentraise' config['config']['ext']['test']['colorscheme'] = 'nonexistentraise'
add_watcher_events(p, 'config') add_watcher_events(p, 'config')
# It may appear that p.logger._pop_msgs() is called after given # It may appear that p.logger._pop_msgs() is called after given
# exception is added to the mesagges, but before config_loader # exception is added to the mesagges, but before config_loader
# exception was added (this one: # exception was added (this one:
# “exception:test:config_loader:Error while running condition # “exception:test:config_loader:Error while running condition
# function for key colorschemes/test/nonexistentraise: # function for key colorschemes/test/nonexistentraise:
# fcf:colorschemes/test/nonexistentraise”). # fcf:colorschemes/test/nonexistentraise”).
# sleep(0.1) # sleep(0.1)
self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
# For colorschemes/{test/,}*raise find_config_file raises # For colorschemes/{test/,}*raise find_config_file raises
# IOError, but it does not do so for colorschemes/test/__main__, # IOError, but it does not do so for check:colorschemes/test/__main__,
# so powerline is trying to load this, but not other # so powerline is trying to load this, but not other
# colorschemes/* # colorschemes/*
self.assertAccessEvents('config', 'colorschemes/test/__main__') self.assertAccessEvents(p, 'config', 'check:colorschemes/test/__main__', 'check:colorschemes/nonexistentraise', 'check:colorschemes/test/nonexistentraise')
self.assertIn('exception:test:powerline:Failed to create renderer: fcf:colorschemes/test/nonexistentraise', p.logger._pop_msgs()) self.assertIn('exception:test:powerline:Failed to create renderer: fcf:colorschemes/test/nonexistentraise', p.logger._pop_msgs())
config['colorschemes/nonexistentraise'] = {} config['colorschemes/nonexistentraise'] = {}
config['colorschemes/test/nonexistentraise'] = { config['colorschemes/test/nonexistentraise'] = {
'groups': { 'groups': {
"str1": {"fg": "col1", "bg": "col3", "attr": ["bold"]}, "str1": {"fg": "col1", "bg": "col3", "attr": ["bold"]},
"str2": {"fg": "col2", "bg": "col4", "attr": ["underline"]}, "str2": {"fg": "col2", "bg": "col4", "attr": ["underline"]},
}, },
} }
while not p._will_create_renderer(): while not p._will_create_renderer():
sleep(0.1) sleep(0.1)
self.assertEqual(p.render(), '<1 3 1> s<3 4 False>>><2 4 4>g<4 False False>>><None None None>') self.assertEqual(p.render(), '<1 3 1> s<3 4 False>>><2 4 4>g<4 False False>>><None None None>')
# Same as above # Same as above
self.assertAccessEvents('colorschemes/nonexistentraise', 'colorschemes/test/nonexistentraise', 'colorschemes/test/__main__') self.assertAccessEvents(p, 'colorschemes/nonexistentraise', 'colorschemes/test/nonexistentraise', 'check:colorschemes/test/__main__')
self.assertEqual(p.logger._pop_msgs(), []) self.assertEqual(p.logger._pop_msgs(), [])
pop_events()
def test_reload_colors(self): @with_new_config
with get_powerline(run_once=False) as p: def test_reload_colors(self, config):
with replace_item(globals(), 'config', deepcopy(config)): with get_powerline(config, run_once=False) as p:
self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
self.assertAccessEvents('config', 'colors', 'colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default')
config['colors']['colors']['col1'] = 5 config['colors']['colors']['col1'] = 5
add_watcher_events(p, 'colors') add_watcher_events(p, 'colors')
self.assertEqual(p.render(), '<5 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') self.assertEqual(p.render(), '<5 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
self.assertAccessEvents('colors') self.assertAccessEvents(p, 'colors')
self.assertEqual(p.logger._pop_msgs(), []) self.assertEqual(p.logger._pop_msgs(), [])
pop_events()
def test_reload_colorscheme(self): @with_new_config
with get_powerline(run_once=False) as p: def test_reload_colorscheme(self, config):
with replace_item(globals(), 'config', deepcopy(config)): with get_powerline(config, run_once=False) as p:
self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
self.assertAccessEvents('config', 'colors', 'colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default')
config['colorschemes/test/default']['groups']['str1']['bg'] = 'col3' config['colorschemes/test/default']['groups']['str1']['bg'] = 'col3'
add_watcher_events(p, 'colorschemes/test/default') add_watcher_events(p, 'colorschemes/test/default')
self.assertEqual(p.render(), '<1 3 1> s<3 4 False>>><3 4 4>g<4 False False>>><None None None>') self.assertEqual(p.render(), '<1 3 1> s<3 4 False>>><3 4 4>g<4 False False>>><None None None>')
self.assertAccessEvents('colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default') self.assertAccessEvents(p, 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default')
self.assertEqual(p.logger._pop_msgs(), []) self.assertEqual(p.logger._pop_msgs(), [])
pop_events()
def test_reload_theme(self): @with_new_config
with get_powerline(run_once=False) as p: def test_reload_theme(self, config):
with replace_item(globals(), 'config', deepcopy(config)): with get_powerline(config, run_once=False) as p:
self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
self.assertAccessEvents('config', 'colors', 'colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default')
config['themes/test/default']['segments']['left'][0]['contents'] = 'col3' config['themes/test/default']['segments']['left'][0]['contents'] = 'col3'
add_watcher_events(p, 'themes/test/default') add_watcher_events(p, 'themes/test/default')
self.assertEqual(p.render(), '<1 2 1> col3<2 4 False>>><3 4 4>g<4 False False>>><None None None>') self.assertEqual(p.render(), '<1 2 1> col3<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
self.assertAccessEvents('themes/test/default') self.assertAccessEvents(p, 'themes/test/default')
self.assertEqual(p.logger._pop_msgs(), []) self.assertEqual(p.logger._pop_msgs(), [])
pop_events()
def test_reload_theme_main(self): @with_new_config
with replace_item(globals(), 'config', deepcopy(config)): def test_reload_theme_main(self, config):
config['config']['common']['interval'] = None config['config']['common']['interval'] = None
with get_powerline(run_once=False) as p: with get_powerline(config, run_once=False) as p:
self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
self.assertAccessEvents('config', 'colors', 'colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default')
config['themes/test/default']['segments']['left'][0]['contents'] = 'col3' config['themes/test/default']['segments']['left'][0]['contents'] = 'col3'
add_watcher_events(p, 'themes/test/default', wait=False) add_watcher_events(p, 'themes/test/default', wait=False)
self.assertEqual(p.render(), '<1 2 1> col3<2 4 False>>><3 4 4>g<4 False False>>><None None None>') self.assertEqual(p.render(), '<1 2 1> col3<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
self.assertAccessEvents('themes/test/default') self.assertAccessEvents(p, 'themes/test/default')
self.assertEqual(p.logger._pop_msgs(), []) self.assertEqual(p.logger._pop_msgs(), [])
self.assertTrue(p._watcher._calls) self.assertTrue(p._watcher._calls)
pop_events()
def test_run_once_no_theme_reload(self): @with_new_config
with replace_item(globals(), 'config', deepcopy(config)): def test_run_once_no_theme_reload(self, config):
config['config']['common']['interval'] = None config['config']['common']['interval'] = None
with get_powerline(run_once=True) as p: with get_powerline(config, run_once=True) as p:
self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
self.assertAccessEvents('config', 'colors', 'colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default')
config['themes/test/default']['segments']['left'][0]['contents'] = 'col3' config['themes/test/default']['segments']['left'][0]['contents'] = 'col3'
add_watcher_events(p, 'themes/test/default', wait=False) add_watcher_events(p, 'themes/test/default', wait=False)
self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
self.assertAccessEvents() self.assertAccessEvents(p)
self.assertEqual(p.logger._pop_msgs(), []) self.assertEqual(p.logger._pop_msgs(), [])
self.assertEqual(p._watcher._calls, [])
pop_events()
replaces = {}
def setUpModule():
global replaces
replaces = swap_attributes(globals(), powerline_module, replaces)
tearDownModule = setUpModule
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -1,11 +1,8 @@
# 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 import tests.vim as vim_module
import powerline as powerline_module
from tests import TestCase from tests import TestCase
from tests.lib import replace_item from tests.lib.config_mock import get_powerline, get_powerline_raw, swap_attributes
from tests.lib.config_mock import swap_attributes, get_powerline
from tests.lib.config_mock import get_powerline_raw
from functools import wraps from functools import wraps
from copy import deepcopy from copy import deepcopy
import sys import sys
@ -123,11 +120,19 @@ config = {
} }
def add_p_arg(func): def with_new_config(func):
@wraps(func) @wraps(func)
def f(self): def f(self):
with get_powerline(run_once=True, simpler_renderer=True) as p: return func(self, deepcopy(config))
func(self, p) return f
def add_args(func):
@wraps(func)
def f(self):
new_config = deepcopy(config)
with get_powerline(new_config, run_once=True, simpler_renderer=True) as p:
func(self, p, new_config)
return f return f
@ -140,166 +145,158 @@ class TestRender(TestCase):
class TestLines(TestRender): class TestLines(TestRender):
@add_p_arg @add_args
def test_without_above(self, p): def test_without_above(self, p, config):
self.assertRenderEqual(p, '{121} s{24}>>{344}g{34}>{34}<{344}f {--}') self.assertRenderEqual(p, '{121} s{24}>>{344}g{34}>{34}<{344}f {--}')
self.assertRenderEqual(p, '{121} s {24}>>{344}g{34}>{34}<{344}f {--}', width=10) self.assertRenderEqual(p, '{121} s {24}>>{344}g{34}>{34}<{344}f {--}', width=10)
# self.assertRenderEqual(p, '{121} s {24}>>{344}g{34}>{34}<{344} f {--}', width=11) # self.assertRenderEqual(p, '{121} s {24}>>{344}g{34}>{34}<{344} f {--}', width=11)
self.assertEqual(list(p.render_above_lines()), []) self.assertEqual(list(p.render_above_lines()), [])
def test_with_above(self): @with_new_config
with replace_item(globals(), 'config', deepcopy(config)): def test_with_above(self, config):
old_segments = deepcopy(config['themes/test/default']['segments']) old_segments = deepcopy(config['themes/test/default']['segments'])
config['themes/test/default']['segments']['above'] = [old_segments] config['themes/test/default']['segments']['above'] = [old_segments]
with get_powerline(run_once=True, simpler_renderer=True) as p: with get_powerline(config, run_once=True, simpler_renderer=True) as p:
self.assertRenderLinesEqual(p, [ self.assertRenderLinesEqual(p, [
'{121} s{24}>>{344}g{34}>{34}<{344}f {--}', '{121} s{24}>>{344}g{34}>{34}<{344}f {--}',
]) ])
self.assertRenderLinesEqual(p, [ self.assertRenderLinesEqual(p, [
'{121} s {24}>>{344}g{34}>{34}<{344}f {--}', '{121} s {24}>>{344}g{34}>{34}<{344}f {--}',
], width=10) ], width=10)
config['themes/test/default']['segments']['above'] = [old_segments] * 2 config['themes/test/default']['segments']['above'] = [old_segments] * 2
with get_powerline(run_once=True, simpler_renderer=True) as p: with get_powerline(config, run_once=True, simpler_renderer=True) as p:
self.assertRenderLinesEqual(p, [ self.assertRenderLinesEqual(p, [
'{121} s{24}>>{344}g{34}>{34}<{344}f {--}', '{121} s{24}>>{344}g{34}>{34}<{344}f {--}',
'{121} s{24}>>{344}g{34}>{34}<{344}f {--}', '{121} s{24}>>{344}g{34}>{34}<{344}f {--}',
]) ])
self.assertRenderLinesEqual(p, [ self.assertRenderLinesEqual(p, [
'{121} s {24}>>{344}g{34}>{34}<{344}f {--}', '{121} s {24}>>{344}g{34}>{34}<{344}f {--}',
'{121} s {24}>>{344}g{34}>{34}<{344}f {--}', '{121} s {24}>>{344}g{34}>{34}<{344}f {--}',
], width=10) ], width=10)
class TestSegments(TestRender): class TestSegments(TestRender):
@add_p_arg @add_args
def test_display(self, p): def test_display(self, p, config):
with replace_item(globals(), 'config', deepcopy(config)): config['themes/test/default']['segments']['left'][0]['display'] = False
config['themes/test/default']['segments']['left'][0]['display'] = False config['themes/test/default']['segments']['left'][1]['display'] = True
config['themes/test/default']['segments']['left'][1]['display'] = True config['themes/test/default']['segments']['right'][0]['display'] = False
config['themes/test/default']['segments']['right'][0]['display'] = False self.assertRenderEqual(p, '{344} g{4-}>>{--}')
self.assertRenderEqual(p, '{344} g{4-}>>{--}')
class TestColorschemesHierarchy(TestRender): class TestColorschemesHierarchy(TestRender):
@add_p_arg @add_args
def test_group_redirects(self, p): def test_group_redirects(self, p, config):
with replace_item(globals(), 'config', deepcopy(config)): config['themes/test/default']['segments'] = {
config['themes/test/default']['segments'] = { 'left': [
'left': [ highlighted_string('a', 'd1', draw_hard_divider=False),
highlighted_string('a', 'd1', draw_hard_divider=False), highlighted_string('b', 'd2', draw_hard_divider=False),
highlighted_string('b', 'd2', draw_hard_divider=False), highlighted_string('c', 'd3', draw_hard_divider=False),
highlighted_string('c', 'd3', draw_hard_divider=False), highlighted_string('A', 'm1', draw_hard_divider=False),
highlighted_string('A', 'm1', draw_hard_divider=False), highlighted_string('B', 'm2', draw_hard_divider=False),
highlighted_string('B', 'm2', draw_hard_divider=False), highlighted_string('C', 'm3', draw_hard_divider=False),
highlighted_string('C', 'm3', draw_hard_divider=False), highlighted_string('1', 'g1', draw_hard_divider=False),
highlighted_string('1', 'g1', draw_hard_divider=False), highlighted_string('2', 'g2', draw_hard_divider=False),
highlighted_string('2', 'g2', draw_hard_divider=False), highlighted_string('3', 'g3', draw_hard_divider=False),
highlighted_string('3', 'g3', draw_hard_divider=False), ],
], 'right': [],
'right': [], }
} self.assertRenderEqual(p, '{78} a{910}b{1112}c{56}A{910}B{1112}C{56}1{78}2{910}3{--}')
self.assertRenderEqual(p, '{78} a{910}b{1112}c{56}A{910}B{1112}C{56}1{78}2{910}3{--}') self.assertEqual(p.logger._pop_msgs(), [])
self.assertEqual(p.logger._pop_msgs(), [])
@add_p_arg @add_args
def test_group_redirects_no_main(self, p): def test_group_redirects_no_main(self, p, config):
with replace_item(globals(), 'config', deepcopy(config)): del config['colorschemes/test/__main__']
del config['colorschemes/test/__main__'] config['themes/test/default']['segments'] = {
config['themes/test/default']['segments'] = { 'left': [
'left': [ highlighted_string('a', 'd1', draw_hard_divider=False),
highlighted_string('a', 'd1', draw_hard_divider=False), highlighted_string('1', 'g1', draw_hard_divider=False),
highlighted_string('1', 'g1', draw_hard_divider=False), highlighted_string('2', 'g2', draw_hard_divider=False),
highlighted_string('2', 'g2', draw_hard_divider=False), highlighted_string('3', 'g3', draw_hard_divider=False),
highlighted_string('3', 'g3', draw_hard_divider=False), ],
], 'right': [],
'right': [], }
} self.assertRenderEqual(p, '{78} a{56}1{78}2{910}3{--}')
self.assertRenderEqual(p, '{78} a{56}1{78}2{910}3{--}') self.assertEqual(p.logger._pop_msgs(), [])
self.assertEqual(p.logger._pop_msgs(), [])
@add_p_arg @add_args
def test_group_redirects_no_top_default(self, p): def test_group_redirects_no_top_default(self, p, config):
with replace_item(globals(), 'config', deepcopy(config)): del config['colorschemes/default']
del config['colorschemes/default'] config['themes/test/default']['segments'] = {
config['themes/test/default']['segments'] = { 'left': [
'left': [ highlighted_string('c', 'd3', draw_soft_divider=False),
highlighted_string('c', 'd3', draw_soft_divider=False), highlighted_string('C', 'm3', draw_hard_divider=False),
highlighted_string('C', 'm3', draw_hard_divider=False), ],
], 'right': [],
'right': [], }
} self.assertRenderEqual(p, '{1112} c{1112}C{--}')
self.assertRenderEqual(p, '{1112} c{1112}C{--}') self.assertEqual(p.logger._pop_msgs(), [])
self.assertEqual(p.logger._pop_msgs(), [])
@add_p_arg @add_args
def test_group_redirects_no_test_default(self, p): def test_group_redirects_no_test_default(self, p, config):
with replace_item(globals(), 'config', deepcopy(config)): del config['colorschemes/test/default']
del config['colorschemes/test/default'] config['themes/test/default']['segments'] = {
config['themes/test/default']['segments'] = { 'left': [
'left': [ highlighted_string('A', 'm1', draw_hard_divider=False),
highlighted_string('A', 'm1', draw_hard_divider=False), highlighted_string('B', 'm2', draw_hard_divider=False),
highlighted_string('B', 'm2', draw_hard_divider=False), highlighted_string('C', 'm3', draw_hard_divider=False),
highlighted_string('C', 'm3', draw_hard_divider=False), highlighted_string('1', 'g1', draw_hard_divider=False),
highlighted_string('1', 'g1', draw_hard_divider=False), highlighted_string('2', 'g2', draw_hard_divider=False),
highlighted_string('2', 'g2', draw_hard_divider=False), highlighted_string('3', 'g3', draw_hard_divider=False),
highlighted_string('3', 'g3', draw_hard_divider=False), ],
], 'right': [],
'right': [], }
} self.assertRenderEqual(p, '{56} A{910}B{1112}C{56}1{78}2{910}3{--}')
self.assertRenderEqual(p, '{56} A{910}B{1112}C{56}1{78}2{910}3{--}') self.assertEqual(p.logger._pop_msgs(), [])
self.assertEqual(p.logger._pop_msgs(), [])
@add_p_arg @add_args
def test_group_redirects_only_main(self, p): def test_group_redirects_only_main(self, p, config):
with replace_item(globals(), 'config', deepcopy(config)): del config['colorschemes/default']
del config['colorschemes/default'] del config['colorschemes/test/default']
del config['colorschemes/test/default'] config['themes/test/default']['segments'] = {
config['themes/test/default']['segments'] = { 'left': [
'left': [ highlighted_string('C', 'm3', draw_hard_divider=False),
highlighted_string('C', 'm3', draw_hard_divider=False), ],
], 'right': [],
'right': [], }
} # Powerline is not able to work without default colorscheme
# Powerline is not able to work without default colorscheme # somewhere, thus it will output error string
# somewhere, thus it will output error string self.assertRenderEqual(p, 'colorschemes/test/default')
self.assertRenderEqual(p, 'colorschemes/test/default') self.assertEqual(p.logger._pop_msgs(), [
self.assertEqual(p.logger._pop_msgs(), [ 'exception:test:powerline:Failed to load colorscheme: colorschemes/default',
'exception:test:powerline:Failed to load colorscheme: colorschemes/default', 'exception:test:powerline:Failed to load colorscheme: colorschemes/test/default',
'exception:test:powerline:Failed to load colorscheme: colorschemes/test/default', 'exception:test:powerline:Failed to create renderer: colorschemes/test/default',
'exception:test:powerline:Failed to create renderer: colorschemes/test/default', 'exception:test:powerline:Failed to render: colorschemes/test/default',
'exception:test:powerline:Failed to render: colorschemes/test/default', ])
])
@add_p_arg @add_args
def test_group_redirects_only_top_default(self, p): def test_group_redirects_only_top_default(self, p, config):
with replace_item(globals(), 'config', deepcopy(config)): del config['colorschemes/test/__main__']
del config['colorschemes/test/__main__'] del config['colorschemes/test/default']
del config['colorschemes/test/default'] config['themes/test/default']['segments'] = {
config['themes/test/default']['segments'] = { 'left': [
'left': [ highlighted_string('1', 'g1', draw_hard_divider=False),
highlighted_string('1', 'g1', draw_hard_divider=False), highlighted_string('2', 'g2', draw_hard_divider=False),
highlighted_string('2', 'g2', draw_hard_divider=False), highlighted_string('3', 'g3', draw_hard_divider=False),
highlighted_string('3', 'g3', draw_hard_divider=False), ],
], 'right': [],
'right': [], }
} self.assertRenderEqual(p, '{56} 1{78}2{910}3{--}')
self.assertRenderEqual(p, '{56} 1{78}2{910}3{--}') self.assertEqual(p.logger._pop_msgs(), [])
self.assertEqual(p.logger._pop_msgs(), [])
@add_p_arg @add_args
def test_group_redirects_only_test_default(self, p): def test_group_redirects_only_test_default(self, p, config):
with replace_item(globals(), 'config', deepcopy(config)): del config['colorschemes/default']
del config['colorschemes/default'] del config['colorschemes/test/__main__']
del config['colorschemes/test/__main__'] config['themes/test/default']['segments'] = {
config['themes/test/default']['segments'] = { 'left': [
'left': [ highlighted_string('s', 'str1', draw_hard_divider=False),
highlighted_string('s', 'str1', draw_hard_divider=False), ],
], 'right': [],
'right': [], }
} self.assertRenderEqual(p, '{121} s{--}')
self.assertRenderEqual(p, '{121} s{--}') self.assertEqual(p.logger._pop_msgs(), [])
self.assertEqual(p.logger._pop_msgs(), [])
class TestVim(TestCase): class TestVim(TestCase):
@ -307,28 +304,25 @@ class TestVim(TestCase):
# Regression test: test that segment obtains environment from vim, not # Regression test: test that segment obtains environment from vim, not
# from os.environ. # from os.environ.
from powerline.vim import VimPowerline from powerline.vim import VimPowerline
with vim_module._with('environ', TEST='abc'): import powerline as powerline_module
with get_powerline_raw(VimPowerline) as powerline: import vim
window = vim_module.current.window vim.vars['powerline_config_path'] = '/'
window_id = 1 with swap_attributes(config, powerline_module):
winnr = window.number with vim_module._with('environ', TEST='abc'):
self.assertEqual(powerline.render(window, window_id, winnr), '%#Pl_3_8404992_4_192_underline#\xa0abc%#Pl_4_192_NONE_None_NONE#>>') with get_powerline_raw(config, VimPowerline) as powerline:
vim_module._environ['TEST'] = 'def' window = vim_module.current.window
self.assertEqual(powerline.render(window, window_id, winnr), '%#Pl_3_8404992_4_192_underline#\xa0def%#Pl_4_192_NONE_None_NONE#>>') window_id = 1
winnr = window.number
self.assertEqual(powerline.render(window, window_id, winnr), '%#Pl_3_8404992_4_192_underline#\xa0abc%#Pl_4_192_NONE_None_NONE#>>')
replaces = {} vim_module._environ['TEST'] = 'def'
self.assertEqual(powerline.render(window, window_id, winnr), '%#Pl_3_8404992_4_192_underline#\xa0def%#Pl_4_192_NONE_None_NONE#>>')
def setUpModule(): def setUpModule():
global replaces
replaces = swap_attributes(globals(), powerline_module, replaces)
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'path'))) sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'path')))
def tearDownModule(): def tearDownModule():
global replaces
replaces = swap_attributes(globals(), powerline_module, replaces)
sys.path.pop(0) sys.path.pop(0)