Split Powerline.__init__ into __init__ and create_renderer

Target: with long-living Powerline objects periodically reload configuration 
recreating renderer. Use file watchers to watch for configuration. Configuration 
should be able to be safely reloaded in non-blocking mode in a separate thread 
up to the time when it comes to recreating renderer.

This commit does not add anything that actually reloads the configuration, 
multiple runs of .create_renderer were not tested.
This commit is contained in:
ZyX 2013-03-25 00:11:58 +04:00
parent bc7c5b784d
commit 80ddbfbf9a
8 changed files with 146 additions and 71 deletions

View File

@ -8,6 +8,8 @@ import logging
from powerline.colorscheme import Colorscheme
from threading import Lock
DEFAULT_SYSTEM_CONFIG_DIR = None
@ -97,64 +99,124 @@ class Powerline(object):
environ=os.environ,
getcwd=getattr(os, 'getcwdu', os.getcwd),
home=None):
self.ext = ext
self.renderer_module = renderer_module or ext
self.run_once = run_once
self.logger = logger
self.environ = environ
self.getcwd = getcwd
self.home = home
self.config_paths = self.get_config_paths()
# Load main config file
config = self.load_main_config()
common_config = config['common']
ext_config = config['ext'][ext]
self.ext = ext
self.renderer_lock = Lock()
# Load and initialize colorscheme
colorscheme_config = self.load_colorscheme_config(ext_config['colorscheme'])
colors_config = self.load_colors_config()
colorscheme = Colorscheme(colorscheme_config, colors_config)
self.prev_common_config = None
self.prev_ext_config = None
# Load and initialize extension theme
theme_config = self.load_theme_config(ext_config.get('theme', 'default'))
common_config['paths'] = [os.path.expanduser(path) for path in common_config.get('paths', [])]
self.import_paths = common_config['paths']
theme_kwargs = {
'ext': ext,
'common_config': common_config,
'run_once': run_once,
}
local_themes = self.get_local_themes(ext_config.get('local_themes'))
self.create_renderer(load_main_config=True, load_colors=True, load_colorscheme=True, load_theme=True)
# Load and initialize extension renderer
renderer_module_name = renderer_module or ext
renderer_module_import = 'powerline.renderers.{0}'.format(renderer_module_name)
try:
Renderer = __import__(renderer_module_import, fromlist=['renderer']).renderer
except ImportError as e:
sys.stderr.write('Error while importing renderer module: {0}\n'.format(e))
sys.exit(1)
options = {
'term_truecolor': common_config.get('term_truecolor', False),
'ambiwidth': common_config.get('ambiwidth', 1),
'tmux_escape': common_config.get('additional_escapes') == 'tmux',
'screen_escape': common_config.get('additional_escapes') == 'screen',
}
def create_renderer(self, load_main_config=False, load_colors=False, load_colorscheme=False, load_theme=False):
'''(Re)create renderer object. Can be used after Powerline object was
successfully initialized. If any of the below parameters except
``load_main_config`` is True renderer object will be recreated.
# Create logger
if not logger:
log_format = common_config.get('log_format', '%(asctime)s:%(levelname)s:%(message)s')
formatter = logging.Formatter(log_format)
:param bool load_main_config:
Determines whether main configuration file (:file:`config.json`)
should be loaded. If appropriate configuration changes implies
``load_colorscheme`` and ``load_theme`` and recreation of renderer
object. Wont trigger recreation if only unrelated configuration
changed.
:param bool load_colors:
Determines whether colors configuration from :file:`colors.json`
should be (re)loaded.
:param bool load_colorscheme:
Determines whether colorscheme configuration should be (re)loaded.
:param bool load_theme:
Determines whether theme configuration should be reloaded.
level = getattr(logging, common_config.get('log_level', 'WARNING'))
handler = self.get_log_handler(common_config)
handler.setLevel(level)
handler.setFormatter(formatter)
Note: reloading of local themes should be taken care of in renderer.
'''
common_config_differs = False
ext_config_differs = False
if load_main_config:
config = self.load_main_config()
self.common_config = config['common']
if self.common_config != self.prev_common_config:
common_config_differs = True
self.prev_common_config = self.common_config
self.common_config['paths'] = [os.path.expanduser(path) for path in self.common_config.get('paths', [])]
self.import_paths = self.common_config['paths']
logger = logging.getLogger('powerline')
logger.setLevel(level)
logger.addHandler(handler)
if not self.logger:
log_format = self.common_config.get('log_format', '%(asctime)s:%(levelname)s:%(message)s')
formatter = logging.Formatter(log_format)
pl = PowerlineState(logger=logger, environ=environ, getcwd=getcwd, home=home)
level = getattr(logging, self.common_config.get('log_level', 'WARNING'))
handler = self.get_log_handler()
handler.setLevel(level)
handler.setFormatter(formatter)
self.renderer = Renderer(theme_config, local_themes, theme_kwargs, colorscheme, pl, **options)
self.logger = logging.getLogger('powerline')
self.logger.setLevel(level)
self.logger.addHandler(handler)
def get_log_handler(self, common_config):
self.pl = PowerlineState(logger=self.logger, environ=self.environ, getcwd=self.getcwd, home=self.home)
self.renderer_options = {
'term_truecolor': self.common_config.get('term_truecolor', False),
'ambiwidth': self.common_config.get('ambiwidth', 1),
'tmux_escape': self.common_config.get('additional_escapes') == 'tmux',
'screen_escape': self.common_config.get('additional_escapes') == 'screen',
}
self.theme_kwargs = {
'ext': self.ext,
'common_config': self.common_config,
'run_once': self.run_once,
}
self.ext_config = config['ext'][self.ext]
if self.ext_config != self.prev_ext_config:
ext_config_differs = True
if not self.prev_ext_config or self.ext_config.get('local_themes') != self.prev_ext_config.get('local_themes'):
self.local_themes = self.get_local_themes(self.ext_config.get('local_themes'))
load_colorscheme = (load_colorscheme
or not self.prev_ext_config
or self.prev_ext_config['colorscheme'] != self.ext_config['colorscheme'])
load_theme = (load_theme
or not self.prev_ext_config
or self.prev_ext_config['theme'] != self.ext_config['theme'])
create_renderer = load_colors or load_colorscheme or load_theme or common_config_differs or ext_config_differs
if load_colors:
colors_config = self.load_colors_config()
if load_colorscheme or load_colors:
colorscheme_config = self.load_colorscheme_config(self.ext_config['colorscheme'])
self.colorscheme = Colorscheme(colorscheme_config, colors_config)
if load_theme:
self.theme_config = self.load_theme_config(self.ext_config.get('theme', 'default'))
if create_renderer:
renderer_module_import = 'powerline.renderers.{0}'.format(self.renderer_module)
try:
Renderer = __import__(renderer_module_import, fromlist=['renderer']).renderer
except Exception as e:
self.pl.exception('Failed to import renderer module: {0}', str(e))
sys.exit(1)
with self.renderer_lock:
self.renderer = Renderer(self.theme_config,
self.local_themes,
self.theme_kwargs,
self.colorscheme,
self.pl,
**self.renderer_options)
def get_log_handler(self):
'''Get log handler.
:param dict common_config:
@ -162,7 +224,7 @@ class Powerline(object):
:return: logging.Handler subclass.
'''
log_file = common_config.get('log_file', None)
log_file = self.common_config.get('log_file', None)
if log_file:
log_file = os.path.expanduser(log_file)
log_dir = os.path.dirname(log_file)
@ -237,3 +299,16 @@ class Powerline(object):
``__init__`` arguments, refer to its documentation.
'''
return None
def render(self, *args, **kwargs):
'''Lock renderer from modifications and pass all arguments further to
``self.renderer.render()``.
'''
with self.renderer_lock:
return self.renderer.render(*args, **kwargs)
def shutdown(self):
'''Lock renderer from modifications and run its ``.shutdown()`` method.
'''
with self.renderer_lock:
self.renderer.shutdown()

View File

@ -24,7 +24,7 @@ class PowerlinePromptManager(PromptManager):
def render(self, name, color=True, *args, **kwargs):
width = None if name == 'in' else self.width
res, res_nocolor = self.powerline.renderer.render(output_raw=True, width=width, matcher_info=name, segment_info=self.powerline_segment_info)
res, res_nocolor = self.powerline.render(output_raw=True, width=width, matcher_info=name, segment_info=self.powerline_segment_info)
self.txtwidth = len(res_nocolor)
self.width = self.txtwidth
return res if color else res_nocolor
@ -51,7 +51,7 @@ def load_ipython_extension(ip):
ip.prompt_manager = PowerlinePromptManager(powerline=powerline, shell=ip.prompt_manager.shell)
def shutdown_hook():
powerline.renderer.shutdown()
powerline.shutdown()
raise TryNext()
ip.hooks.shutdown_hook.add(shutdown_hook)

View File

@ -56,7 +56,7 @@ class PowerlinePrompt(BasePrompt):
def set_p_str(self, width=None):
self.p_str, self.p_str_nocolor = (
self.powerline.renderer.render(output_raw=True,
self.powerline.render(output_raw=True,
segment_info=self.powerline_segment_info,
matcher_info=self.powerline_prompt_type,
width=width)
@ -85,7 +85,7 @@ class PowerlinePrompt1(PowerlinePrompt):
self.powerline_last_in['prompt_text_len'] = self.prompt_text_len
def auto_rewrite(self):
return RewriteResult(self.powerline.renderer.render(matcher_info='rewrite', width=self.prompt_text_len, segment_info=self.powerline_segment_info)
return RewriteResult(self.powerline.render(matcher_info='rewrite', width=self.prompt_text_len, segment_info=self.powerline_segment_info)
+ (' ' * self.nrspaces))
@ -128,7 +128,7 @@ def setup(**kwargs):
raise TryNext()
def shutdown_hook():
powerline.renderer.shutdown()
powerline.shutdown()
raise TryNext()
ip.IP.hooks.late_startup_hook.add(late_startup_hook)

View File

@ -15,7 +15,7 @@ class Powerline(base._TextBox):
def update(self):
if not self.configured:
return True
self.text = self.powerline.renderer.render(side='right')
self.text = self.powerline.render(side='right')
self.bar.draw()
return True

View File

@ -67,7 +67,7 @@ endfunction
function! Powerline(window_id)
let winidx = index(map(range(1, winnr('$')), 's:GetWinID(v:val)'), a:window_id)
let current = w:window_id is# a:window_id
return s:pyeval('powerline.renderer.render('. a:window_id .', '. winidx .', '. current .')')
return s:pyeval('powerline.render('. a:window_id .', '. winidx .', '. current .')')
endfunction
function! PowerlineNew()
@ -84,7 +84,7 @@ endfunction
augroup Powerline
autocmd! ColorScheme * :exec s:powerline_pycmd 'powerline.renderer.reset_highlight()'
autocmd! VimEnter * :redrawstatus!
autocmd! VimLeave * :exec s:powerline_pycmd 'powerline.renderer.shutdown()'
autocmd! VimLeave * :exec s:powerline_pycmd 'powerline.shutdown()'
augroup END
exec s:powerline_pycmd 'powerline = VimPowerline()'

View File

@ -10,7 +10,7 @@ used_powerlines = []
def shutdown():
for powerline in used_powerlines:
powerline.renderer.shutdown()
powerline.shutdown()
def get_var_config(var):
@ -88,7 +88,7 @@ class Prompt(object):
self.args = powerline.args
def __str__(self):
r = self.powerline.renderer.render(width=zsh.columns(), side=self.side, segment_info=self.args)
r = self.powerline.render(width=zsh.columns(), side=self.side, segment_info=self.args)
if type(r) is not str:
if type(r) is bytes:
return r.decode('utf-8')
@ -101,7 +101,7 @@ class Prompt(object):
zsh.setvalue(self.savedpsvar, self.savedps)
used_powerlines.remove(self.powerline)
if self.powerline not in used_powerlines:
self.powerline.renderer.shutdown()
self.powerline.shutdown()
def set_prompt(powerline, psvar, side):

View File

@ -17,7 +17,7 @@ if __name__ == '__main__':
if 'PWD' in os.environ:
kwargs['getcwd'] = lambda: os.environ['PWD']
powerline = ShellPowerline(args, run_once=True, **kwargs)
rendered = powerline.renderer.render(width=args.width, side=args.side, segment_info=args)
rendered = powerline.render(width=args.width, side=args.side, segment_info=args)
try:
sys.stdout.write(rendered)
except UnicodeEncodeError:

View File

@ -18,7 +18,7 @@ SBLOCK = chr(ord('S') - 0x40)
def shutdown(powerline):
from powerline.segments import common, vim
try:
powerline.renderer.shutdown()
powerline.shutdown()
finally:
# After shutdown threads are useless, it is needed to recreate them.
from imp import reload
@ -39,7 +39,7 @@ class TestConfig(TestCase):
powerline = VimPowerline()
def check_output(*args):
out = powerline.renderer.render(*args + (0 if mode == 'nc' else 1,))
out = powerline.render(*args + (0 if mode == 'nc' else 1,))
if out in outputs:
self.fail('Duplicate in set #{0} for mode {1!r} (previously defined in set #{2} for mode {3!r})'.format(i, mode, *outputs[out]))
outputs[out] = (i, mode)
@ -69,27 +69,27 @@ class TestConfig(TestCase):
from powerline.shell import ShellPowerline
with replace_attr(common, 'urllib_read', urllib_read):
powerline = ShellPowerline(Args(ext=['tmux']), run_once=False)
powerline.renderer.render()
powerline.render()
powerline = ShellPowerline(Args(ext=['tmux']), run_once=False)
powerline.renderer.render()
powerline.render()
shutdown(powerline)
def test_zsh(self):
from powerline.shell import ShellPowerline
args = Args(last_pipe_status=[1, 0], ext=['shell'], renderer_module='zsh_prompt')
powerline = ShellPowerline(args, run_once=False)
powerline.renderer.render(segment_info=args)
powerline.render(segment_info=args)
powerline = ShellPowerline(args, run_once=False)
powerline.renderer.render(segment_info=args)
powerline.render(segment_info=args)
shutdown(powerline)
def test_bash(self):
from powerline.shell import ShellPowerline
args = Args(last_exit_code=1, ext=['shell'], renderer_module='bash_prompt', config=[('ext', {'shell': {'theme': 'default_leftonly'}})])
powerline = ShellPowerline(args, run_once=False)
powerline.renderer.render(segment_info=args)
powerline.render(segment_info=args)
powerline = ShellPowerline(args, run_once=False)
powerline.renderer.render(segment_info=args)
powerline.render(segment_info=args)
shutdown(powerline)
def test_ipython(self):
@ -103,8 +103,8 @@ class TestConfig(TestCase):
powerline = IpyPowerline()
segment_info = Args(prompt_count=1)
for prompt_type in ['in', 'in2', 'out', 'rewrite']:
powerline.renderer.render(matcher_info=prompt_type, segment_info=segment_info)
powerline.renderer.render(matcher_info=prompt_type, segment_info=segment_info)
powerline.render(matcher_info=prompt_type, segment_info=segment_info)
powerline.render(matcher_info=prompt_type, segment_info=segment_info)
shutdown(powerline)
def test_wm(self):
@ -113,7 +113,7 @@ class TestConfig(TestCase):
reload(common)
from powerline import Powerline
with replace_attr(common, 'urllib_read', urllib_read):
Powerline(ext='wm', renderer_module='pango_markup', run_once=True).renderer.render()
Powerline(ext='wm', renderer_module='pango_markup', run_once=True).render()
reload(common)