Improve arguments checks

This commit is contained in:
ZyX 2013-04-13 14:20:54 +04:00
parent 7e57010c19
commit fee328666f
1 changed files with 159 additions and 42 deletions

View File

@ -3,6 +3,9 @@ from powerline import find_config_file, Powerline
from powerline.lib.config import load_json_config from powerline.lib.config import load_json_config
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.markedjson.markedvalue import gen_marked_value
from powerline.lib.threaded import ThreadedSegment
import itertools import itertools
import sys import sys
import os import os
@ -25,8 +28,33 @@ def open_file(path):
EMPTYTUPLE = tuple() EMPTYTUPLE = tuple()
class JStr(unicode):
def join(self, iterable):
return super(JStr, self).join((unicode(item) for item in iterable))
key_sep = JStr('/')
list_sep = JStr(', ')
def context_key(context): def context_key(context):
return '/'.join((unicode(c[0]) for c in context)) return key_sep.join((c[0] for c in context))
class DelayedEchoErr(object):
def __init__(self, echoerr):
self.echoerr = echoerr
self.errs = []
def __call__(self, *args, **kwargs):
self.errs.append((args, kwargs))
def echo_all(self):
for args, kwargs in self.errs:
self.echoerr(*args, **kwargs)
def __nonzero__(self):
return not not self.errs
class Spec(object): class Spec(object):
@ -80,7 +108,7 @@ class Spec(object):
context_mark=context_mark, context_mark=context_mark,
problem='{0!r} must be a {1} instance, not {2}'.format( problem='{0!r} must be a {1} instance, not {2}'.format(
value, value,
', '.join((t.__name__ for t in types)), list_sep.join((t.__name__ for t in types)),
type(value.value).__name__ type(value.value).__name__
), ),
problem_mark=value.mark) problem_mark=value.mark)
@ -118,10 +146,7 @@ class Spec(object):
return True, hadproblem return True, hadproblem
def check_either(self, value, context_mark, data, context, echoerr, start, end): def check_either(self, value, context_mark, data, context, echoerr, start, end):
errs = [] new_echoerr = DelayedEchoErr(echoerr)
def new_echoerr(*args, **kwargs):
errs.append((args, kwargs))
hadproblem = False hadproblem = False
for spec in self.specs[start:end]: for spec in self.specs[start:end]:
@ -131,8 +156,7 @@ class Spec(object):
if not hadproblem: if not hadproblem:
return True, False return True, False
for args, kwargs in errs: new_echoerr.echo_all()
echoerr(*args, **kwargs)
return False, hadproblem return False, hadproblem
@ -392,6 +416,7 @@ def check_config(d, theme, data, context, echoerr):
return True, False, True return True, False, True
return True, False, False return True, False, False
divider_spec = Spec().type(unicode).len('le', 3, divider_spec = Spec().type(unicode).len('le', 3,
lambda value: 'Divider {0!r} is too large!'.format(value)).copy lambda value: 'Divider {0!r} is too large!'.format(value)).copy
divside_spec = Spec( divside_spec = Spec(
@ -551,7 +576,7 @@ def check_key_compatibility(segment, data, context, echoerr):
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)), echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)),
context_mark=context[-1][1].mark, context_mark=context[-1][1].mark,
problem='found keys not used with the current segment type: {0}'.format( problem='found keys not used with the current segment type: {0}'.format(
', '.join((unicode(key) for key in unknown_keys))), list_sep.join(unknown_keys)),
problem_mark=list(unknown_keys)[0].mark) problem_mark=list(unknown_keys)[0].mark)
hadproblem = True hadproblem = True
@ -560,7 +585,7 @@ def check_key_compatibility(segment, data, context, echoerr):
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)), echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)),
context_mark=context[-1][1].mark, context_mark=context[-1][1].mark,
problem='found missing required keys: {0}'.format( problem='found missing required keys: {0}'.format(
', '.join((unicode(key) for key in missing_keys)))) 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)):
@ -619,28 +644,39 @@ def check_full_segment_data(segment, data, context, echoerr):
return check_key_compatibility(segment_copy, data, context, echoerr) return check_key_compatibility(segment_copy, data, context, echoerr)
def import_segment(name, data, context, echoerr, module=None):
if not module:
module = context[-2][1].get('module', context[0][1].get('default_module', 'powerline.segments.' + data['ext']))
with WithPath(data['import_paths']):
try:
func = getattr(__import__(unicode(module), fromlist=[unicode(name)]), unicode(name))
except ImportError:
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)),
problem='failed to import module {0}'.format(module),
problem_mark=module.mark)
return None
except AttributeError:
echoerr(context='Error while loading segment function (key {key})'.format(key=context_key(context)),
problem='failed to load function {0} from module {1}'.format(name, module),
problem_mark=name.mark)
return None
if not callable(func):
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)),
problem='imported "function" {0} from module {1} is not callable'.format(name, module),
problem_mark=module.mark)
return None
return func
def check_segment_name(name, data, context, echoerr): def check_segment_name(name, data, context, echoerr):
ext = data['ext'] ext = data['ext']
if context[-2][1].get('type', 'function') == 'function': if context[-2][1].get('type', 'function') == 'function':
module = context[-2][1].get('module', context[0][1].get('default_module', 'powerline.segments.' + ext)) func = import_segment(name, data, context, echoerr)
with WithPath(data['import_paths']):
try:
func = getattr(__import__(unicode(module), fromlist=[unicode(name)]), unicode(name))
except ImportError:
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)),
problem='failed to import module {0}'.format(module),
problem_mark=module.mark)
return True, False, True
except AttributeError:
echoerr(context='Error while loading segment function (key {key})'.format(key=context_key(context)),
problem='failed to load function {0} from module {1}'.format(name, module),
problem_mark=name.mark)
return True, False, True
if not callable(func): if not func:
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)),
problem='imported "function" {0} from module {1} is not callable'.format(name, module),
problem_mark=module.mark)
return True, False, True return True, False, True
hl_groups = [] hl_groups = []
@ -662,32 +698,32 @@ def check_segment_name(name, data, context, echoerr):
if r: if r:
echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)), echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)),
problem='found highlight group {0} not defined in the following colorschemes: {1}\n(Group name was obtained from function documentation.)'.format( problem='found highlight group {0} not defined in the following colorschemes: {1}\n(Group name was obtained from function documentation.)'.format(
divider_hl_group, ', '.join(r)), divider_hl_group, list_sep.join(r)),
problem_mark=name.mark) 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 (', '.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(context='Error while checking theme (key {key})'.format(key=context_key(context)),
problem='found highlight groups list ({0}) with all groups not defined in some colorschemes\n(Group names were taken from function documentation.)'.format( problem='found highlight groups list ({0}) with all groups not defined in some colorschemes\n(Group names were taken from function documentation.)'.format(
', '.join((unicode(h[0]) for h in required_pack))), list_sep.join((h[0] for h in required_pack))),
problem_mark=name.mark) 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(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( problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
h[0], ', '.join(r))) 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(context='Error while checking theme (key {key})'.format(key=context_key(context)),
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( 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(
name, ', '.join(r)), name, list_sep.join(r)),
problem_mark=name.mark) problem_mark=name.mark)
hadproblem = True hadproblem = True
@ -753,7 +789,7 @@ def check_highlight_group(hl_group, data, context, echoerr):
if r: if r:
echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)), echoerr(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( problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
hl_group, ', '.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
@ -764,12 +800,12 @@ def check_highlight_groups(hl_groups, data, context, echoerr):
if all(rs): if all(rs):
echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)), echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)),
problem='found highlight groups list ({0}) with all groups not defined in some colorschemes'.format( problem='found highlight groups list ({0}) with all groups not defined in some colorschemes'.format(
', '.join((unicode(h) for h in hl_groups))), list_sep.join((unicode(h) for h in hl_groups))),
problem_mark=hl_groups.mark) 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(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( problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
hl_group, ', '.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
@ -807,11 +843,92 @@ def check_segment_data_key(key, data, context, echoerr):
return True, False, False return True, False, False
# FIXME More checks, limit existing to ThreadedSegment instances only threaded_args_specs = {
'interval': Spec().cmp('gt', 0.0),
'update_first': Spec().type(bool),
'shutdown_event': Spec().error('Shutdown event must be set by powerline'),
}
def check_args_variant(segment, args, data, context, echoerr):
argspec = getconfigargspec(segment)
present_args = set(args)
all_args = set(argspec.args)
required_args = set(argspec.args[:-len(argspec.defaults)])
hadproblem = False
if required_args - present_args:
echoerr(context='Error while checking segment arguments (key {key})'.format(key=context_key(context)),
context_mark=args.mark,
problem='some of the required keys are missing: {0}'.format(list_sep.join(required_args - present_args)))
hadproblem = True
if not all_args >= present_args:
echoerr(context='Error while checking segment arguments (key {key})'.format(key=context_key(context)),
context_mark=args.mark,
problem='found unknown keys: {0}'.format(list_sep.join(present_args - all_args)),
problem_mark=next(iter(present_args - all_args)).mark)
hadproblem = True
if isinstance(segment, ThreadedSegment):
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)
if khadproblem:
hadproblem = True
if not proceed:
return hadproblem
return hadproblem
def check_args(get_segment_variants, args, data, context, echoerr):
new_echoerr = DelayedEchoErr(echoerr)
count = 0
hadproblem = False
for segment in get_segment_variants(data, context, new_echoerr):
count += 1
shadproblem = check_args_variant(segment, args, data, context, echoerr)
if shadproblem:
hadproblem = True
if not count:
hadproblem = True
new_echoerr.echo_all()
echoerr(context='Error while checking segment arguments (key {key})'.format(key=context_key(context)),
context_mark=context[-2][1].mark,
problem='no suitable segments found')
return True, False, hadproblem
def get_one_segment_variant(data, context, echoerr):
name = context[-2][1].get('name')
if name:
func = import_segment(name, data, context, echoerr)
if func:
yield func
def get_all_possible_segments(data, context, echoerr):
name = context[-2][0]
module, name = (gen_marked_value(value, name.mark) for value in name.rpartition('.')[::2])
if module:
func = import_segment(name, data, context, echoerr, module=module)
if func:
yield func
else:
for theme_config in data['ext_theme_configs'].values():
for segments in theme_config.get('segments', {}).values():
for segment in segments:
if segment.get('type', 'function') == 'function':
module = segment.get('module', context[0][1].get('default_module', 'powerline.segments.' + data['ext']))
func = import_segment(name, data, context, echoerr, module=module)
if func:
yield func
args_spec = Spec( args_spec = Spec(
interval=Spec().cmp('gt', 0.0).optional(),
update_first=Spec().type(bool).optional(),
shutdown_event=Spec().error('Shutdown event must be set by powerline').optional(),
pl=Spec().error('pl object must be set by powerline').optional(), pl=Spec().error('pl object must be set by powerline').optional(),
segment_info=Spec().error('Segment info dictionary must be set by powerline').optional(), segment_info=Spec().error('Segment info dictionary must be set by powerline').optional(),
).unknown_spec(Spec(), Spec()).optional().copy ).unknown_spec(Spec(), Spec()).optional().copy
@ -832,7 +949,7 @@ segments_spec = Spec().optional().list(
before=Spec().type(unicode).optional(), before=Spec().type(unicode).optional(),
width=Spec().either(Spec().unsigned(), Spec().cmp('eq', 'auto')).optional(), width=Spec().either(Spec().unsigned(), Spec().cmp('eq', 'auto')).optional(),
align=Spec().oneof(set('lr')).optional(), align=Spec().oneof(set('lr')).optional(),
args=args_spec(), 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('^(?:(?!:divider$).)+$',
@ -849,7 +966,7 @@ theme_spec = (Spec(
Spec( Spec(
after=Spec().type(unicode).optional(), after=Spec().type(unicode).optional(),
before=Spec().type(unicode).optional(), before=Spec().type(unicode).optional(),
args=args_spec(), args=args_spec().func(lambda *args, **kwargs: check_args(get_all_possible_segments, *args, **kwargs)),
contents=Spec().type(unicode).optional(), contents=Spec().type(unicode).optional(),
), ),
).optional().context_message('Error while loading segment data (key {key})'), ).optional().context_message('Error while loading segment data (key {key})'),