Skip to content

Commit

Permalink
Add required pl argument to segments
Browse files Browse the repository at this point in the history
Fixes #340
Ref #330
  • Loading branch information
ZyX-I committed Mar 24, 2013
1 parent cc1c982 commit ed435f8
Show file tree
Hide file tree
Showing 17 changed files with 512 additions and 300 deletions.
11 changes: 11 additions & 0 deletions docs/source/configuration.rst
Expand Up @@ -155,6 +155,17 @@ Common configuration is a subdictionary that is a value of ``common`` key in
:ref:`module segment option <config-themes-seg-module>`. Paths defined here
have priority when searching for modules.

``log_file``
Defines path which will hold powerline logs. If not present, logging will be
done to stderr.

``log_level``
String, determines logging level. Defaults to ``WARNING``.

``log_format``
String, determines format of the log messages. Defaults to
``'%(asctime)s:%(level)s:%(message)s'``.

Extension-specific configuration
--------------------------------

Expand Down
94 changes: 92 additions & 2 deletions powerline/__init__.py
Expand Up @@ -4,6 +4,7 @@
import json
import os
import sys
import logging

from powerline.colorscheme import Colorscheme

Expand All @@ -25,6 +26,42 @@ def load_json_config(search_paths, config_file, load=json.load, open_file=open_f
raise IOError('Config file not found in search path: {0}'.format(config_file))


class PowerlineState(object):
def __init__(self, logger, environ, getcwd, home):
self.environ = environ
self.getcwd = getcwd
self.home = home or environ.get('HOME', None)
self.logger = logger
self.prefix = None
self.last_msgs = {}

def _log(self, attr, msg, *args, **kwargs):
prefix = kwargs.get('prefix') or self.prefix
msg = ((prefix + ':') if prefix else '') + msg.format(*args, **kwargs)
key = attr+':'+prefix
if msg != self.last_msgs.get(key):
getattr(self.logger, attr)(msg)
self.last_msgs[key] = msg

def critical(self, msg, *args, **kwargs):
self._log('critical', msg, *args, **kwargs)

def exception(self, msg, *args, **kwargs):
self._log('exception', msg, *args, **kwargs)

def info(self, msg, *args, **kwargs):
self._log('info', msg, *args, **kwargs)

def error(self, msg, *args, **kwargs):
self._log('error', msg, *args, **kwargs)

def warn(self, msg, *args, **kwargs):
self._log('warning', msg, *args, **kwargs)

def debug(self, msg, *args, **kwargs):
self._log('debug', msg, *args, **kwargs)


class Powerline(object):
'''Main powerline class, entrance point for all powerline uses. Sets
powerline up and loads the configuration.
Expand All @@ -37,9 +74,29 @@ class Powerline(object):
:param str renderer_module:
Overrides renderer module (defaults to ``ext``). Should be the name of
the package imported like this: ``powerline.renders.{render_module}``.
:param bool run_once:
Determines whether .renderer.render() method will be run only once
during python session.
:param Logger logger:
If present, no new logger will be created and this logger will be used.
:param dict environ:
Object with ``.__getitem__`` and ``.get`` methods used to obtain
environment variables. Defaults to ``os.environ``.
:param func getcwd:
Function used to get current working directory. Defaults to
``os.getcwdu`` or ``os.getcwd``.
:param str home:
Home directory. Defaults to ``environ.get('HOME')``.
'''

def __init__(self, ext, renderer_module=None, run_once=False):
def __init__(self,
ext,
renderer_module=None,
run_once=False,
logger=None,
environ=os.environ,
getcwd=getattr(os, 'getcwdu', os.getcwd),
home=None):
self.config_paths = self.get_config_paths()

# Load main config file
Expand Down Expand Up @@ -78,7 +135,40 @@ def __init__(self, ext, renderer_module=None, run_once=False):
'tmux_escape': common_config.get('additional_escapes') == 'tmux',
'screen_escape': common_config.get('additional_escapes') == 'screen',
}
self.renderer = Renderer(theme_config, local_themes, theme_kwargs, colorscheme, **options)

# Create logger
if not logger:
log_format = common_config.get('format', '%(asctime)s:%(level)s:%(message)s')
formatter = logging.Formatter(log_format)

level = getattr(logging, common_config.get('log_level', 'WARNING'))
handler = self.get_log_handler(common_config)
handler.setLevel(level)

logger = logging.getLogger('powerline')
logger.setLevel(level)
logger.addHandler(handler)

pl = PowerlineState(logger=logger, environ=environ, getcwd=getcwd, home=home)

self.renderer = Renderer(theme_config, local_themes, theme_kwargs, colorscheme, pl, **options)

def get_log_handler(self, common_config):
'''Get log handler.
:param dict common_config:
Common configuration.
:return: logging.Handler subclass.
'''
log_file = common_config.get('file', None)
if log_file:
log_dir = os.path.dirname(log_file)
if not os.path.isdir(log_dir):
os.mkdir(log_dir)
return logging.FileHandler(log_file)
else:
return logging.StreamHandler()

@staticmethod
def get_config_paths():
Expand Down
21 changes: 19 additions & 2 deletions powerline/bindings/zsh/__init__.py
Expand Up @@ -45,6 +45,22 @@ def config_path(self):
return None


class Environment(object):
@staticmethod
def __getitem__(key):
try:
return zsh.getvalue(key)
except IndexError as e:
raise KeyError(*e.args)

@staticmethod
def get(key, default=None):
try:
return zsh.getvalue(key)
except IndexError:
return default


class Prompt(object):
__slots__ = ('render', 'side', 'savedpsvar', 'savedps')

Expand All @@ -53,9 +69,10 @@ def __init__(self, powerline, side, savedpsvar=None, savedps=None):
self.side = side
self.savedpsvar = savedpsvar
self.savedps = savedps
self.args = powerline.args

def __str__(self):
return self.render(width=zsh.columns(), side=self.side).encode('utf-8')
return self.render(width=zsh.columns(), side=self.side, segment_info=args).encode('utf-8')

def __del__(self):
if self.savedps:
Expand All @@ -71,6 +88,6 @@ def set_prompt(powerline, psvar, side):


def setup():
powerline = ShellPowerline(Args())
powerline = ShellPowerline(Args(), environ=Environment(), getcwd=lambda: zsh.getvalue('PWD'))
set_prompt(powerline, 'PS1', 'left')
set_prompt(powerline, 'RPS1', 'right')
64 changes: 37 additions & 27 deletions powerline/lib/threaded.py
Expand Up @@ -23,15 +23,24 @@ def __init__(self):
self.did_set_interval = False
self.thread = None

def __call__(self, **kwargs):
def __call__(self, update_first=True, **kwargs):
if self.run_once:
self.pl = kwargs['pl']
self.set_state(**kwargs)
self.update()
elif not self.is_alive():
self.startup(**kwargs)
# Without this we will not have to wait long until receiving bug “I
# opened vim, but branch information is only shown after I move
# cursor”.
#
# If running once .update() is called in __call__.
if update_first and self.update_first:
self.update_first = False
self.update()
self.start()

with self.write_lock:
return self.render(**kwargs)
return self.render(update_first=update_first, **kwargs)

def is_alive(self):
return self.thread and self.thread.is_alive()
Expand All @@ -49,7 +58,10 @@ def run(self):
start_time = monotonic()

with self.update_lock:
self.update()
try:
self.update()
except Exception as e:
self.error('Exception while updating: {0}', str(e))

self.sleep(monotonic() - start_time)

Expand All @@ -67,28 +79,27 @@ def set_interval(self, interval=None):
self.interval = interval
self.has_set_interval = True

def set_state(self, update_first=True, interval=None, **kwargs):
def set_state(self, interval=None, **kwargs):
if not self.did_set_interval or interval:
self.set_interval(interval)
# Without this we will not have to wait long until receiving bug “I
# opened vim, but branch information is only shown after I move cursor”.
#
# If running once .update() is called in __call__.
if update_first and self.update_first and not self.run_once:
self.update_first = False
self.update()

def startup(self, **kwargs):
# Normally .update() succeeds to run before value is requested, meaning
# that user is getting values he needs directly at vim startup. Without
# .startup() we will not have to wait long until receiving bug “I opened
# vim, but branch information is only shown after I move cursor”.
def startup(self, pl, **kwargs):
self.run_once = False
self.pl = pl

if not self.is_alive():
self.set_state(**kwargs)
self.start()

def error(self, *args, **kwargs):
self.pl.error(prefix=self.__class__.__name__, *args, **kwargs)

def warn(self, *args, **kwargs):
self.pl.warn(prefix=self.__class__.__name__, *args, **kwargs)

def debug(self, *args, **kwargs):
self.pl.debug(prefix=self.__class__.__name__, *args, **kwargs)


def printed(func):
def f(*args, **kwargs):
Expand All @@ -109,12 +120,14 @@ def __init__(self):
def key(**kwargs):
return frozenset(kwargs.items())

def render(self, **kwargs):
def render(self, update_first, **kwargs):
key = self.key(**kwargs)
try:
update_state = self.queries[key][1]
except KeyError:
update_state = self.compute_state(key) if self.update_first or self.run_once else None
# Allow only to forbid to compute missing values: in either user
# configuration or in subclasses.
update_state = self.compute_state(key) if update_first and self.update_first or self.run_once else None
# No locks: render method is already running with write_lock acquired.
self.queries[key] = (monotonic(), update_state)
return self.render_one(update_state, **kwargs)
Expand All @@ -124,27 +137,24 @@ def update(self):
removes = []
for key, (last_query_time, state) in list(self.queries.items()):
if last_query_time < monotonic() < last_query_time + self.drop_interval:
updates[key] = (last_query_time, self.compute_state(key))
try:
updates[key] = (last_query_time, self.compute_state(key))
except Exception as e:
self.error('Exception while computing state for {0}: {1}', repr(key), str(e))
else:
removes.append(key)
with self.write_lock:
self.queries.update(updates)
for key in removes:
self.queries.pop(key)

def set_state(self, update_first=True, interval=None, **kwargs):
def set_state(self, interval=None, **kwargs):
if not self.did_set_interval or (interval < self.interval):
self.set_interval(interval)

# Allow only to forbid to compute missing values: in either user
# configuration or in subclasses.
if self.update_first:
self.update_first = update_first

key = self.key(**kwargs)
if not self.run_once and key not in self.queries:
self.queries[key] = (monotonic(), self.compute_state(key) if self.update_first else None)

@staticmethod
def render_one(update_state, **kwargs):
return update_state
Expand Down
17 changes: 13 additions & 4 deletions powerline/lint/__init__.py
Expand Up @@ -8,6 +8,7 @@
import re
from collections import defaultdict
from copy import copy
import logging


try:
Expand Down Expand Up @@ -408,6 +409,11 @@ def check_config(d, theme, data, context, echoerr):
# only for existence of the path, not for it being a directory
paths=Spec().list((lambda value, *args: (True, True, not os.path.exists(value.value))),
lambda value: 'path does not exist: {0}'.format(value)).optional(),
log_file=Spec().type(str).func(lambda value, *args: (True, True, not os.path.isdir(os.path.dirname(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(str).optional(),
).context_message('Error while loading common configuration (key {key})'),
ext=Spec(
vim=Spec(
Expand Down Expand Up @@ -786,6 +792,11 @@ def check_segment_data_key(key, data, context, echoerr):
return True, False, False


# FIXME More checks, limit existing to ThreadedSegment instances only
args_spec = Spec(
interval=Spec().either(Spec().type(float), Spec().type(int)).optional(),
update_first=Spec().type(bool).optional(),
).unknown_spec(Spec(), Spec()).optional().copy
highlight_group_spec = Spec().type(unicode).copy
segment_module_spec = Spec().type(unicode).func(check_segment_module).optional().copy
segments_spec = Spec().optional().list(
Expand All @@ -801,8 +812,7 @@ def check_segment_data_key(key, data, context, echoerr):
before=Spec().type(unicode).optional(),
width=Spec().either(Spec().unsigned(), Spec().cmp('eq', 'auto')).optional(),
align=Spec().oneof(set('lr')).optional(),
# FIXME Check args
args=Spec().type(dict).optional(),
args=args_spec(),
contents=Spec().type(unicode).optional(),
highlight_group=Spec().list(
highlight_group_spec().re('^(?:(?!:divider$).)+$',
Expand All @@ -819,8 +829,7 @@ def check_segment_data_key(key, data, context, echoerr):
Spec(
after=Spec().type(unicode).optional(),
before=Spec().type(unicode).optional(),
# FIXME Check args
args=Spec().type(dict).optional(),
args=args_spec(),
contents=Spec().type(unicode).optional(),
),
).optional().context_message('Error while loading segment data (key {key})'),
Expand Down
4 changes: 3 additions & 1 deletion powerline/renderer.py
Expand Up @@ -18,9 +18,11 @@ def construct_returned_value(rendered_highlighted, segments, output_raw):


class Renderer(object):
def __init__(self, theme_config, local_themes, theme_kwargs, colorscheme, **options):
def __init__(self, theme_config, local_themes, theme_kwargs, colorscheme, pl, **options):
self.__dict__.update(options)
self.theme_config = theme_config
theme_kwargs['pl'] = pl
self.pl = pl
self.theme = Theme(theme_config=theme_config, **theme_kwargs)
self.local_themes = local_themes
self.theme_kwargs = theme_kwargs
Expand Down
7 changes: 1 addition & 6 deletions powerline/renderers/vim.py
Expand Up @@ -66,12 +66,7 @@ def strwidth(string):
return vim.strwidth(string)

def render(self, window_id, winidx, current):
'''Render all segments.
This method handles replacing of the percent placeholder for vim
statuslines, and it caches segment contents which are retrieved and
used in non-current windows.
'''
'''Render all segments.'''
if current:
mode = vim_mode(1)
mode = mode_translations.get(mode, mode)
Expand Down

0 comments on commit ed435f8

Please sign in to comment.