Implement configuration merging

Fixes #418
This commit is contained in:
ZyX 2014-07-13 23:02:05 +04:00
parent 3d77306c35
commit 27db44ac7a
5 changed files with 307 additions and 37 deletions

View File

@ -14,13 +14,25 @@ from powerline.lib import mergedicts
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'
found = False
for path in search_paths:
config_file_path = os.path.join(path, config_file)
if os.path.isfile(config_file_path):
return config_file_path
raise IOError('Config file not found in search path: {0}'.format(config_file))
yield config_file_path
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):
@ -103,14 +115,14 @@ def get_config_paths():
config_paths = [config_path]
config_dirs = os.environ.get('XDG_CONFIG_DIRS', DEFAULT_SYSTEM_CONFIG_DIR)
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')
config_paths.append(plugin_path)
config_paths.insert(0, plugin_path)
return 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.
@ -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.
'''
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
Watches are only set up if loader_callback is not None.
:param str cfg_path:
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
the return value of ``generate_config_finder`` function.
: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.
'''
try:
path = find_config_file(cfg_path)
except IOError:
if loader_callback:
config_loader.register_missing(find_config_file, loader_callback, cfg_path)
raise
else:
found_files = find_config_files(cfg_path, config_loader, loader_callback)
ret = None
for path in found_files:
if loader_callback:
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):
@ -286,7 +298,7 @@ class Powerline(object):
elif 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 = {}
@ -437,7 +449,7 @@ class Powerline(object):
'''Load configuration and setup watches.'''
return load_config(
cfg_path,
self.find_config_file,
self.find_config_files,
self.config_loader,
self.cr_callbacks[type]
)
@ -445,7 +457,7 @@ class Powerline(object):
def _purge_configs(self, type):
function = self.cr_callbacks[type]
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):
'''Get theme configuration.
@ -601,7 +613,7 @@ class Powerline(object):
pass
functions = tuple(self.cr_callbacks.values())
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):
return self

View File

@ -119,9 +119,9 @@ def source_tmux_files(pl, 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 = load_config('config', find_config_file, config_loader)
config = load_config('config', find_config_files, config_loader)
common_config = finish_common_config(config['common'])
logger = create_logger(common_config)
return PowerlineLogger(use_daemon_threads=True, logger=logger, ext='config')

View File

@ -1,8 +1,8 @@
# vim:fileencoding=utf-8:noet
from powerline.lint.markedjson import load
from powerline import generate_config_finder, get_config_paths
from powerline.lib.config import load_json_config
from powerline import generate_config_finder, get_config_paths, load_config
from powerline.lib.config import ConfigLoader
from powerline.lint.markedjson.error import echoerr, MarkedError
from powerline.segments.vim import vim_modes
from powerline.lint.inspect import getconfigargspec
@ -1261,9 +1261,19 @@ theme_spec = (Spec(
).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):
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.setLevel(logging.DEBUG if debug else logging.ERROR)
@ -1271,6 +1281,11 @@ def check(paths=None, debug=False):
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 = {
'themes': defaultdict(lambda: []),
'colorschemes': defaultdict(lambda: []),
@ -1326,17 +1341,9 @@ def check(paths=None, debug=False):
typ,
))
lhadproblem = [False]
def load_config(stream):
r, hadproblem = load(stream)
if hadproblem:
lhadproblem[0] = True
return r
hadproblem = False
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:
main_config = {}
sys.stderr.write('\nConfiguration file not found: config.json\n')
@ -1357,7 +1364,7 @@ def check(paths=None, debug=False):
import_paths = [os.path.expanduser(path) for path in main_config.get('common', {}).get('paths', [])]
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:
colors_config = {}
sys.stderr.write('\nConfiguration file not found: colors.json\n')

View File

@ -117,7 +117,7 @@ class TestPowerline(Powerline):
return self.cr_kwargs
renderer = SimpleRenderer
renderer = EvenSimplerRenderer
def get_powerline(**kwargs):

View File

@ -0,0 +1,251 @@
# vim:fileencoding=utf-8:noet
from __future__ import unicode_literals
from powerline import Powerline
from tests import TestCase
from shutil import rmtree
import os
import json
from powerline.lib import mergedicts_copy as mdc
CONFIG_DIR = 'tests/config'
root_config = lambda: {
'common': {
'dividers': {
'left': {
'hard': '#>',
'soft': '|>',
},
'right': {
'hard': '<#',
'soft': '<|',
},
},
'spaces': 0,
'interval': 0,
'watcher': 'test',
},
'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)
self.p = TestPowerline(
ext='test',
renderer_module='tests.lib.config_mock',
**self.p_kwargs
)
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': 3,
}
},
'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()