Improve arguments checks
This commit is contained in:
parent
7e57010c19
commit
fee328666f
|
@ -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})'),
|
||||||
|
|
Loading…
Reference in New Issue