From 97266b7ffc93cb0e9ce843dacb14b865b8493bf1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 4 Feb 2014 21:37:42 +0400 Subject: [PATCH] Initial support for #770 What was done: - Implemented loading using configuration hierarhy as proposed in the issue - Implemented group aliasing What was not: - Some tests (config_reload) are failing - Other (test_configuration) are spamming console with unexpected messages - No support for powerline-lint - No tests for new functionality - Specifically I have not checked whether group aliasing actually works - Colorschemes were not ported Some other things: I have named this branch `config-ng` because I have other ideas about configuration and it would be good to include them making only one possibly backwards-incompatible merge commit instead of many. Specifically I am going to rebase `merge-config` branch here. --- powerline/__init__.py | 56 ++++++++++++++++++++++++++++++++++++- powerline/colorscheme.py | 43 ++++++++++++++++++---------- tests/test_config_reload.py | 31 +++++++++++--------- 3 files changed, 101 insertions(+), 29 deletions(-) diff --git a/powerline/__init__.py b/powerline/__init__.py index 2d4fba9c..e982375d 100644 --- a/powerline/__init__.py +++ b/powerline/__init__.py @@ -9,6 +9,7 @@ from powerline.colorscheme import Colorscheme from powerline.lib.config import ConfigLoader from powerline.lib.unicode import safe_unicode, FailedUnicode from powerline.config import DEFAULT_SYSTEM_CONFIG_DIR +from powerline.lib import mergedicts from threading import Lock, Event @@ -220,6 +221,22 @@ def finish_common_config(common_config): return common_config +if sys.version_info < (3,): + # `raise exception[0], None, exception[1]` is a SyntaxError in python-3* + # Not using ('''…''') because this syntax does not work in python-2.6 + exec(('def reraise(exception):\n' + ' if type(exception) is tuple:\n' + ' raise exception[0], None, exception[1]\n' + ' else:\n' + ' raise exception\n')) +else: + def reraise(exception): + if type(exception) is tuple: + raise exception[0].with_traceback(exception[1]) + else: + raise exception + + class Powerline(object): '''Main powerline class, entrance point for all powerline uses. Sets powerline up and loads the configuration. @@ -455,7 +472,38 @@ class Powerline(object): :return: dictionary with :ref:`colorscheme configuration `. ''' - return self._load_config(os.path.join('colorschemes', self.ext, name), 'colorscheme') + # TODO Make sure no colorscheme name ends with __ (do it in + # powerline-lint) + levels = ( + os.path.join('colorschemes', name), + os.path.join('colorschemes', self.ext, '__main__'), + os.path.join('colorschemes', self.ext, name), + ) + config = {} + loaded = 0 + exceptions = [] + for cfg_path in levels: + try: + lvl_config = self._load_config(cfg_path, 'colorscheme') + except IOError as e: + if sys.version_info < (3,): + tb = sys.exc_info()[2] + exceptions.append((e, tb)) + else: + exceptions.append(e) + else: + if not cfg_path.endswith('__'): + loaded += 1 + mergedicts(config, lvl_config) + if not loaded: + for exception in exceptions: + if type(exception) is tuple: + e = exception[0] + else: + e = exception + self.exception('Failed to load colorscheme: {0}', e, exception=exception) + raise e + return config def load_colors_config(self): '''Get colorscheme. @@ -562,5 +610,11 @@ class Powerline(object): def exception(self, msg, *args, **kwargs): if 'prefix' not in kwargs: kwargs['prefix'] = 'powerline' + exception = kwargs.pop('exception', None) pl = getattr(self, 'pl', None) or get_fallback_logger() + if exception: + try: + reraise(exception) + except Exception: + return pl.exception(msg, *args, **kwargs) return pl.exception(msg, *args, **kwargs) diff --git a/powerline/colorscheme.py b/powerline/colorscheme.py index 3b30118f..a49c5c09 100644 --- a/powerline/colorscheme.py +++ b/powerline/colorscheme.py @@ -1,6 +1,10 @@ # vim:fileencoding=utf-8:noet from copy import copy +try: + from __builtin__ import unicode +except ImportError: + unicode = str DEFAULT_MODE_KEY = None @@ -70,32 +74,41 @@ class Colorscheme(object): else: return self.colors[gradient] - def get_highlighting(self, groups, mode, gradient_level=None): - trans = self.translations.get(mode, {}) - for group in hl_iter(groups): - if 'groups' in trans and group in trans['groups']: + def get_group_props(self, mode, trans, group, translate_colors=True): + if isinstance(group, (str, unicode)): + try: + group_props = trans['groups'][group] + except KeyError: try: - group_props = trans['groups'][group] + group_props = self.groups[group] except KeyError: - continue - break - + return None + else: + return self.get_group_props(mode, trans, group_props, True) else: - try: - group_props = copy(self.groups[group]) - except KeyError: - continue - + return self.get_group_props(mode, trans, group_props, False) + else: + if translate_colors: + group_props = copy(group) try: ctrans = trans['colors'] + except KeyError: + pass + else: for key in ('fg', 'bg'): try: group_props[key] = ctrans[group_props[key]] except KeyError: pass - except KeyError: - pass + return group_props + else: + return group + def get_highlighting(self, groups, mode, gradient_level=None): + trans = self.translations.get(mode, {}) + for group in hl_iter(groups): + group_props = self.get_group_props(mode, trans, group) + if group_props: break else: raise KeyError('Highlighting groups not found in colorscheme: ' + ', '.join(hl_iter(groups))) diff --git a/tests/test_config_reload.py b/tests/test_config_reload.py index 4d33128e..7f3c184f 100644 --- a/tests/test_config_reload.py +++ b/tests/test_config_reload.py @@ -101,7 +101,7 @@ class TestConfigReload(TestCase): with get_powerline(run_once=True) as p: with replace_item(globals(), 'config', deepcopy(config)): self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') + self.assertAccessEvents('config', 'colors', 'colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') config['config']['common']['spaces'] = 1 add_watcher_events(p, 'config', wait=False, interval=0.05) # When running once thread should not start @@ -117,7 +117,7 @@ class TestConfigReload(TestCase): with get_powerline(run_once=False) as p: with replace_item(globals(), 'config', deepcopy(config)): self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') + self.assertAccessEvents('config', 'colors', 'colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') config['config']['common']['spaces'] = 1 add_watcher_events(p, 'config') @@ -141,14 +141,19 @@ class TestConfigReload(TestCase): config['config']['ext']['test']['colorscheme'] = 'nonexistent' add_watcher_events(p, 'config') self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>>') - self.assertAccessEvents('config', 'colorschemes/test/nonexistent') + self.assertAccessEvents('config', 'colorschemes/nonexistent', 'colorschemes/test/__main__', 'colorschemes/test/nonexistent') # It should normally handle file missing error - self.assertEqual(p.logger._pop_msgs(), ['exception:test:powerline:Failed to create renderer: colorschemes/test/nonexistent']) + self.assertEqual(p.logger._pop_msgs(), [ + '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/nonexistent', + 'exception:test:powerline:Failed to create renderer: colorschemes/test/nonexistent' + ]) config['config']['ext']['test']['colorscheme'] = '2' add_watcher_events(p, 'config') self.assertEqual(p.render(), '<2 3 1> s <3 4 False>>><1 4 4>g <4 False False>>>') - self.assertAccessEvents('config', 'colorschemes/test/2') + self.assertAccessEvents('config', 'colorschemes/2', 'colorschemes/test/__main__', 'colorschemes/test/2') self.assertEqual(p.logger._pop_msgs(), []) config['config']['ext']['test']['theme'] = '2' @@ -170,7 +175,7 @@ class TestConfigReload(TestCase): with get_powerline(run_once=False) as p: with replace_item(globals(), 'config', deepcopy(config)): self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') + self.assertAccessEvents('config', 'colors', 'colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') config['config']['ext']['test']['colorscheme'] = 'nonexistentraise' add_watcher_events(p, 'config') @@ -192,7 +197,7 @@ class TestConfigReload(TestCase): }, } while not p._will_create_renderer(): - sleep(0.000001) + sleep(0.1) self.assertEqual(p.render(), '<1 3 1> s<3 4 False>>><2 4 4>g<4 False False>>>') self.assertAccessEvents('colorschemes/test/nonexistentraise') self.assertEqual(p.logger._pop_msgs(), []) @@ -202,7 +207,7 @@ class TestConfigReload(TestCase): with get_powerline(run_once=False) as p: with replace_item(globals(), 'config', deepcopy(config)): self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') + self.assertAccessEvents('config', 'colors', 'colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') config['colors']['colors']['col1'] = 5 add_watcher_events(p, 'colors') @@ -215,12 +220,12 @@ class TestConfigReload(TestCase): with get_powerline(run_once=False) as p: with replace_item(globals(), 'config', deepcopy(config)): self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') + self.assertAccessEvents('config', 'colors', 'colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') config['colorschemes/test/default']['groups']['str1']['bg'] = 'col3' 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>>>') - self.assertAccessEvents('colorschemes/test/default') + self.assertAccessEvents('colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default') self.assertEqual(p.logger._pop_msgs(), []) pop_events() @@ -228,7 +233,7 @@ class TestConfigReload(TestCase): with get_powerline(run_once=False) as p: with replace_item(globals(), 'config', deepcopy(config)): self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') + self.assertAccessEvents('config', 'colors', 'colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') config['themes/test/default']['segments']['left'][0]['contents'] = 'col3' add_watcher_events(p, 'themes/test/default') @@ -242,7 +247,7 @@ class TestConfigReload(TestCase): config['config']['common']['interval'] = None with get_powerline(run_once=False) as p: self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') + self.assertAccessEvents('config', 'colors', 'colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') config['themes/test/default']['segments']['left'][0]['contents'] = 'col3' add_watcher_events(p, 'themes/test/default', wait=False) @@ -257,7 +262,7 @@ class TestConfigReload(TestCase): config['config']['common']['interval'] = None with get_powerline(run_once=True) as p: self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') + self.assertAccessEvents('config', 'colors', 'colorschemes/default', 'colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') config['themes/test/default']['segments']['left'][0]['contents'] = 'col3' add_watcher_events(p, 'themes/test/default', wait=False)