Add most configuration checks
Changes: - Add main configuration, colors.json, almost full themes and colorschemes checks - Make powerline.lint.check return whether it had problems - Make powerline-lint fail if .check reported problems - Make tests run powerline-lint - Add the script to the list of the installed scripts Fixes #278
This commit is contained in:
parent
287a88f473
commit
d27f7a0411
|
@ -0,0 +1,899 @@
|
|||
from powerline.lint.markedjson import load
|
||||
from powerline import load_json_config, Powerline
|
||||
from powerline.lint.markedjson.error import echoerr
|
||||
from powerline.segments.vim import vim_modes
|
||||
import itertools
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from copy import copy
|
||||
|
||||
|
||||
try:
|
||||
from __builtin__ import unicode
|
||||
except ImportError:
|
||||
unicode = str
|
||||
|
||||
|
||||
def open_file(path):
|
||||
return open(path, 'rb')
|
||||
|
||||
|
||||
def find_config(search_paths, config_file):
|
||||
config_file += '.json'
|
||||
for path in search_paths:
|
||||
if os.path.isfile(os.path.join(path, config_file)):
|
||||
return path
|
||||
return None
|
||||
|
||||
|
||||
EMPTYTUPLE = tuple()
|
||||
|
||||
|
||||
def context_key(context):
|
||||
return '/'.join((unicode(c[0]) for c in context))
|
||||
|
||||
|
||||
class Spec(object):
|
||||
def __init__(self, **keys):
|
||||
new_keys = {}
|
||||
self.specs = list(keys.values())
|
||||
for k, v in keys.items():
|
||||
new_keys[k] = len(self.specs)
|
||||
self.specs.append(v)
|
||||
self.keys = new_keys
|
||||
self.checks = []
|
||||
self.cmsg = ''
|
||||
self.isoptional = False
|
||||
self.uspecs = []
|
||||
self.ufailmsg = lambda key: 'found unknown key: {0}'.format(key)
|
||||
if keys:
|
||||
self.type(dict)
|
||||
|
||||
def copy(self):
|
||||
return self.__class__().update(self.__dict__)
|
||||
|
||||
def update(self, d):
|
||||
self.__dict__.update(d)
|
||||
self.checks = copy(self.checks)
|
||||
self.uspecs = copy(self.uspecs)
|
||||
self.specs = [spec.copy() for spec in self.specs]
|
||||
return self
|
||||
|
||||
def unknown_spec(self, keyfunc, spec):
|
||||
if isinstance(keyfunc, Spec):
|
||||
self.specs.append(keyfunc)
|
||||
keyfunc = len(self.specs) - 1
|
||||
self.specs.append(spec)
|
||||
self.uspecs.append((keyfunc, len(self.specs) - 1))
|
||||
return self
|
||||
|
||||
def unknown_msg(self, msgfunc):
|
||||
self.ufailmsg = msgfunc
|
||||
return self
|
||||
|
||||
def context_message(self, msg):
|
||||
self.cmsg = msg
|
||||
for spec in self.specs:
|
||||
if not spec.cmsg:
|
||||
spec.context_message(msg)
|
||||
return self
|
||||
|
||||
def check_type(self, value, context_mark, data, context, echoerr, t):
|
||||
if type(value.value) is not t:
|
||||
echoerr(context=self.cmsg.format(key=context_key(context)),
|
||||
context_mark=context_mark,
|
||||
problem='must be a {0} instance'.format(t.__name__),
|
||||
problem_mark=value.mark)
|
||||
return False, True
|
||||
return True, False
|
||||
|
||||
def check_func(self, value, context_mark, data, context, echoerr, func, msg_func):
|
||||
proceed, echo, hadproblem = func(value, data, context, echoerr)
|
||||
if echo and hadproblem:
|
||||
echoerr(context=self.cmsg.format(key=context_key(context)),
|
||||
context_mark=context_mark,
|
||||
problem=msg_func(value),
|
||||
problem_mark=value.mark)
|
||||
return proceed, hadproblem
|
||||
|
||||
def check_list(self, value, context_mark, data, context, echoerr, item_func, msg_func):
|
||||
i = 0
|
||||
hadproblem = False
|
||||
for item in value:
|
||||
if isinstance(item_func, int):
|
||||
spec = self.specs[item_func]
|
||||
proceed, fhadproblem = spec.match(item, value.mark, data, context + (('list item ' + unicode(i), item),), echoerr)
|
||||
else:
|
||||
proceed, echo, fhadproblem = item_func(item, data, context, echoerr)
|
||||
if echo and fhadproblem:
|
||||
echoerr(context=self.cmsg.format(key=context_key(context)+'/list item '+unicode(i)),
|
||||
context_mark=value.mark,
|
||||
problem=msg_func(item),
|
||||
problem_mark=item.mark)
|
||||
if fhadproblem:
|
||||
hadproblem = True
|
||||
if not proceed:
|
||||
return proceed, hadproblem
|
||||
i += 1
|
||||
return True, hadproblem
|
||||
|
||||
def check_either(self, value, context_mark, data, context, echoerr, start, end):
|
||||
errs = []
|
||||
|
||||
def new_echoerr(*args, **kwargs):
|
||||
errs.append((args, kwargs))
|
||||
|
||||
hadproblem = False
|
||||
for spec in self.specs[start:end]:
|
||||
proceed, hadproblem = spec.match(value, value.mark, data, context, new_echoerr)
|
||||
if not proceed:
|
||||
break
|
||||
if not hadproblem:
|
||||
return True, False
|
||||
|
||||
for args, kwargs in errs:
|
||||
echoerr(*args, **kwargs)
|
||||
|
||||
return False, hadproblem
|
||||
|
||||
def check_tuple(self, value, context_mark, data, context, echoerr, start, end):
|
||||
hadproblem = False
|
||||
for (i, item, spec) in zip(itertools.count(), value, self.specs[start:end]):
|
||||
proceed, ihadproblem = spec.match(item, value.mark, data, context + (('tuple item '+unicode(i), item),), echoerr)
|
||||
if ihadproblem:
|
||||
hadproblem = True
|
||||
if not proceed:
|
||||
return False, hadproblem
|
||||
return True, hadproblem
|
||||
|
||||
def type(self, t):
|
||||
self.checks.append(('check_type', t))
|
||||
return self
|
||||
|
||||
cmp_funcs = {
|
||||
'le': lambda x, y: x <= y,
|
||||
'lt': lambda x, y: x < y,
|
||||
'ge': lambda x, y: x >= y,
|
||||
'gt': lambda x, y: x > y,
|
||||
'eq': lambda x, y: x == y,
|
||||
}
|
||||
|
||||
cmp_msgs = {
|
||||
'le': 'lesser or equal to',
|
||||
'lt': 'lesser then',
|
||||
'ge': 'greater or equal to',
|
||||
'gt': 'greater then',
|
||||
'eq': 'equal to',
|
||||
}
|
||||
|
||||
def len(self, comparison, cint, msg_func=None):
|
||||
cmp_func = self.cmp_funcs[comparison]
|
||||
msg_func = msg_func or (lambda value: 'length of {0!r} is not {1} {2}'.format(value, self.cmp_msgs[comparison], cint))
|
||||
self.checks.append(('check_func',
|
||||
(lambda value, *args: (True, True, not cmp_func(len(value), cint))),
|
||||
msg_func))
|
||||
return self
|
||||
|
||||
def cmp(self, comparison, cint, msg_func=None):
|
||||
if type(cint) is str:
|
||||
self.type(unicode)
|
||||
else:
|
||||
self.type(type(cint))
|
||||
cmp_func = self.cmp_funcs[comparison]
|
||||
msg_func = msg_func or (lambda value: '{0} is not {1} {2}'.format(value, self.cmp_msgs[comparison], cint))
|
||||
self.checks.append(('check_func',
|
||||
(lambda value, *args: (True, True, not cmp_func(value, cint))),
|
||||
msg_func))
|
||||
return self
|
||||
|
||||
def unsigned(self, msg_func=None):
|
||||
self.type(int)
|
||||
self.checks.append(('check_func',
|
||||
(lambda value, *args: (True, True, value < 0)),
|
||||
lambda value: '{0} must be greater then zero'.format(value)))
|
||||
return self
|
||||
|
||||
def list(self, item_func, msg_func=None):
|
||||
self.type(list)
|
||||
if isinstance(item_func, Spec):
|
||||
self.specs.append(item_func)
|
||||
item_func = len(self.specs) - 1
|
||||
self.checks.append(('check_list', item_func, msg_func or (lambda item: 'failed check')))
|
||||
return self
|
||||
|
||||
def tuple(self, *specs):
|
||||
self.type(list)
|
||||
|
||||
max_len = len(specs)
|
||||
min_len = max_len
|
||||
for spec in reversed(specs):
|
||||
if spec.isoptional:
|
||||
min_len -= 1
|
||||
else:
|
||||
break
|
||||
if max_len == min_len:
|
||||
self.len('eq', len(specs))
|
||||
else:
|
||||
self.len('ge', min_len)
|
||||
self.len('le', max_len)
|
||||
|
||||
start = len(self.specs)
|
||||
for i, spec in zip(itertools.count(), specs):
|
||||
self.specs.append(spec)
|
||||
self.checks.append(('check_tuple', start, len(self.specs)))
|
||||
return self
|
||||
|
||||
def func(self, func, msg_func=None):
|
||||
self.checks.append(('check_func', func, msg_func or (lambda value: 'failed check')))
|
||||
return self
|
||||
|
||||
def re(self, regex, msg_func=None):
|
||||
self.type(unicode)
|
||||
compiled = re.compile(regex)
|
||||
msg_func = msg_func or (lambda value: 'String "{0}" does not match "{1}"'.format(value, regex))
|
||||
self.checks.append(('check_func',
|
||||
(lambda value, *args: (True, True, not compiled.match(value.value))),
|
||||
msg_func))
|
||||
return self
|
||||
|
||||
def ident(self, msg_func=None):
|
||||
msg_func = msg_func or (lambda value: 'String "{0}" is not an alphanumeric/underscore identifier'.format(value))
|
||||
return self.re('^\w+$', msg_func)
|
||||
|
||||
def oneof(self, collection, msg_func=None):
|
||||
msg_func = msg_func or (lambda value: '"{0}" must be one of {1!r}'.format(value, list(collection)))
|
||||
self.checks.append(('check_func',
|
||||
lambda value, *args: (True, True, value not in collection),
|
||||
msg_func))
|
||||
return self
|
||||
|
||||
def either(self, *specs):
|
||||
start = len(self.specs)
|
||||
self.specs.extend(specs)
|
||||
self.checks.append(('check_either', start, len(self.specs)))
|
||||
return self
|
||||
|
||||
def optional(self):
|
||||
self.isoptional = True
|
||||
return self
|
||||
|
||||
def match_checks(self, *args):
|
||||
hadproblem = False
|
||||
for check in self.checks:
|
||||
proceed, chadproblem = getattr(self, check[0])(*(args + check[1:]))
|
||||
if chadproblem:
|
||||
hadproblem = True
|
||||
if not proceed:
|
||||
return False, hadproblem
|
||||
return True, hadproblem
|
||||
|
||||
def match(self, value, context_mark=None, data=None, context=EMPTYTUPLE, echoerr=echoerr):
|
||||
proceed, hadproblem = self.match_checks(value, context_mark, data, context, echoerr)
|
||||
if proceed:
|
||||
if self.keys or self.uspecs:
|
||||
for key, vali in self.keys.items():
|
||||
valspec = self.specs[vali]
|
||||
if key in value:
|
||||
proceed, mhadproblem = valspec.match(value[key], value.mark, data, context + ((key, value[key]),), echoerr)
|
||||
if mhadproblem:
|
||||
hadproblem = True
|
||||
if not proceed:
|
||||
return False, hadproblem
|
||||
else:
|
||||
if not valspec.isoptional:
|
||||
hadproblem = True
|
||||
echoerr(context=self.cmsg.format(key=context_key(context)),
|
||||
context_mark=None,
|
||||
problem='required key is missing: {0}'.format(key),
|
||||
problem_mark=value.mark)
|
||||
for key in value.keys():
|
||||
if key not in self.keys:
|
||||
for keyfunc, vali in self.uspecs:
|
||||
valspec = self.specs[vali]
|
||||
if isinstance(keyfunc, int):
|
||||
spec = self.specs[keyfunc]
|
||||
proceed, khadproblem = spec.match(key, context_mark, data, context, echoerr)
|
||||
else:
|
||||
proceed, khadproblem = keyfunc(key, data, context, echoerr)
|
||||
if khadproblem:
|
||||
hadproblem = True
|
||||
if proceed:
|
||||
valspec.match(value[key], value.mark, data, context + ((key, value[key]),), echoerr)
|
||||
break
|
||||
else:
|
||||
hadproblem = True
|
||||
if self.ufailmsg:
|
||||
echoerr(context=self.cmsg.format(key=context_key(context)),
|
||||
context_mark=None,
|
||||
problem=self.ufailmsg(key),
|
||||
problem_mark=key.mark)
|
||||
return True, hadproblem
|
||||
|
||||
|
||||
class WithPath(object):
|
||||
def __init__(self, import_paths):
|
||||
self.import_paths = import_paths
|
||||
|
||||
def __enter__(self):
|
||||
self.oldpath = sys.path
|
||||
sys.path = self.import_paths + sys.path
|
||||
|
||||
def __exit__(self, *args):
|
||||
sys.path = self.oldpath
|
||||
|
||||
|
||||
def check_matcher_func(ext, match_name, data, context, echoerr):
|
||||
import_paths = [os.path.expanduser(path) for path in context[0][1].get('common', {}).get('paths', [])]
|
||||
|
||||
match_module, separator, match_function = match_name.rpartition('.')
|
||||
if not separator:
|
||||
match_module = 'powerline.matchers.{0}'.format(ext)
|
||||
match_function = match_name
|
||||
with WithPath(import_paths):
|
||||
try:
|
||||
func = getattr(__import__(match_module, fromlist=[match_function]), unicode(match_function))
|
||||
except ImportError:
|
||||
echoerr(context='Error while loading matcher functions',
|
||||
problem='failed to load module {0}'.format(match_module),
|
||||
problem_mark=match_name.mark)
|
||||
return True, True
|
||||
except AttributeError:
|
||||
echoerr(context='Error while loading matcher functions',
|
||||
problem='failed to load matcher function {0}'.format(match_function),
|
||||
problem_mark=match_name.mark)
|
||||
return True, True
|
||||
|
||||
if not callable(func):
|
||||
echoerr(context='Error while loading matcher functions',
|
||||
problem='loaded "function" {0} is not callable'.format(match_function),
|
||||
problem_mark=match_name.mark)
|
||||
return True, True
|
||||
|
||||
if hasattr(func, 'func_code') and hasattr(func.func_code, 'co_argcount'):
|
||||
if func.func_code.co_argcount != 1:
|
||||
echoerr(context='Error while loading matcher functions',
|
||||
problem='function {0} accepts {1} arguments instead of 1. Are you sure it is the proper function?'.format(match_function, func.func_code.co_argcount),
|
||||
problem_mark=match_name.mark)
|
||||
|
||||
return True, False
|
||||
|
||||
|
||||
def check_ext(ext, data, context, echoerr):
|
||||
hadsomedirs = False
|
||||
hadproblem = False
|
||||
for subdir in ('themes', 'colorschemes'):
|
||||
if ext not in data['configs'][subdir]:
|
||||
hadproblem = True
|
||||
echoerr(context='Error while loading {0} extension configuration'.format(ext),
|
||||
context_mark=ext.mark,
|
||||
problem='{0} configuration does not exist'.format(subdir))
|
||||
else:
|
||||
hadsomedirs = True
|
||||
return hadsomedirs, hadproblem
|
||||
|
||||
|
||||
def check_config(d, theme, data, context, echoerr):
|
||||
if len(context) == 4:
|
||||
ext = context[-2][0]
|
||||
else:
|
||||
# local_themes
|
||||
ext = context[-3][0]
|
||||
if ext not in data['configs']['themes'] or theme not in data['configs']['themes'][ext]:
|
||||
echoerr(context='Error while loading {0} from {1} extension configuration'.format(d[:-1], ext),
|
||||
problem='failed to find configuration file themes/{0}/{1}.json'.format(ext, theme),
|
||||
problem_mark=theme.mark)
|
||||
return True, False, True
|
||||
return True, False, False
|
||||
|
||||
divider_spec = Spec().type(unicode).len('le', 3,
|
||||
lambda value: 'Divider {0!r} is too large!'.format(value)).copy
|
||||
divside_spec = Spec(
|
||||
hard=divider_spec(),
|
||||
soft=divider_spec(),
|
||||
).copy
|
||||
colorscheme_spec = Spec().type(unicode).func(lambda *args: check_config('colorschemes', *args)).copy
|
||||
theme_spec = Spec().type(unicode).func(lambda *args: check_config('themes', *args)).copy
|
||||
main_spec = (Spec(
|
||||
common=Spec(
|
||||
dividers=Spec(
|
||||
left=divside_spec(),
|
||||
right=divside_spec(),
|
||||
),
|
||||
spaces=Spec().unsigned().cmp('le', 2,
|
||||
lambda value: 'Are you sure you need such a big ({0}) number of spaces?'.format(value)),
|
||||
term_truecolor=Spec().type(bool).optional(),
|
||||
# Python is capable of loading from zip archives. Thus checking path
|
||||
# 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(),
|
||||
).context_message('Error while loading common configuration (key {key})'),
|
||||
ext=Spec(
|
||||
vim=Spec(
|
||||
colorscheme=colorscheme_spec(),
|
||||
theme=theme_spec(),
|
||||
local_themes=Spec()
|
||||
.unknown_spec(lambda *args: check_matcher_func('vim', *args), theme_spec())
|
||||
),
|
||||
).unknown_spec(check_ext,
|
||||
Spec(
|
||||
colorscheme=colorscheme_spec(),
|
||||
theme=theme_spec(),
|
||||
))
|
||||
.context_message('Error while loading extensions configuration (key {key})'),
|
||||
).context_message('Error while loading main configuration'))
|
||||
|
||||
term_color_spec=Spec().unsigned().cmp('le', 255).copy
|
||||
true_color_spec=Spec().re('^[0-9a-fA-F]{6}$',
|
||||
lambda value: '"{0}" is not a six-digit hexadecimal unsigned integer written as a string'.format(value)).copy
|
||||
colors_spec = (Spec(
|
||||
colors=Spec().unknown_spec(
|
||||
Spec().ident(),
|
||||
Spec().either(
|
||||
Spec().tuple(term_color_spec(), true_color_spec()),
|
||||
term_color_spec()))
|
||||
.context_message('Error while checking colors (key {key})'),
|
||||
gradients=Spec().unknown_spec(
|
||||
Spec().ident(),
|
||||
Spec().tuple(
|
||||
Spec().len('gt', 1).list(term_color_spec()),
|
||||
Spec().len('gt', 1).list(true_color_spec()).optional(),
|
||||
)
|
||||
).context_message('Error while checking gradients (key {key})'),
|
||||
).context_message('Error while loading colors configuration'))
|
||||
|
||||
|
||||
def check_color(color, data, context, echoerr):
|
||||
if color not in data['colors_config'].get('colors', {}) and color not in data['colors_config'].get('gradients', {}):
|
||||
echoerr(context='Error while checking highlight group in colorscheme (key {key})'.format(key=context_key(context)),
|
||||
problem='found unexistent color or gradient {0}'.format(color),
|
||||
problem_mark=color.mark)
|
||||
return True, False, True
|
||||
return True, False, False
|
||||
|
||||
|
||||
def check_translated_group_name(group, data, context, echoerr):
|
||||
if group not in context[0][1].get('groups', {}):
|
||||
echoerr(context='Error while checking translated group in colorscheme (key {key})'.format(key=context_key(context)),
|
||||
problem='translated group {0} is not in main groups dictionary'.format(group),
|
||||
problem_mark=group.mark)
|
||||
return True, False, True
|
||||
return True, False, False
|
||||
|
||||
|
||||
color_spec = Spec().type(unicode).func(check_color).copy
|
||||
name_spec = Spec().type(unicode).len('gt', 0).optional().copy
|
||||
group_spec = Spec(
|
||||
fg=color_spec(),
|
||||
bg=color_spec(),
|
||||
attr=Spec().list(Spec().type(unicode).oneof(set(('bold', 'italic', 'underline')))).optional(),
|
||||
).copy
|
||||
group_name_spec = Spec().re('^\w+(?::\w+)?$').copy
|
||||
groups_spec = Spec().unknown_spec(
|
||||
group_name_spec(),
|
||||
group_spec(),
|
||||
).copy
|
||||
colorscheme_spec = (Spec(
|
||||
name=name_spec(),
|
||||
groups=groups_spec().context_message('Error while loading groups (key {key})'),
|
||||
).context_message('Error while loading coloscheme'))
|
||||
vim_mode_spec = Spec().oneof(set(list(vim_modes) + ['nc'])).copy
|
||||
vim_colorscheme_spec = (Spec(
|
||||
name=name_spec(),
|
||||
groups=groups_spec().context_message('Error while loading groups (key {key})'),
|
||||
mode_translations=Spec().unknown_spec(
|
||||
vim_mode_spec(),
|
||||
Spec(
|
||||
colors=Spec().unknown_spec(
|
||||
color_spec(),
|
||||
color_spec(),
|
||||
).optional(),
|
||||
groups=Spec().unknown_spec(
|
||||
group_name_spec().func(check_translated_group_name),
|
||||
group_spec(),
|
||||
).optional(),
|
||||
),
|
||||
).context_message('Error while loading mode translations (key {key})'),
|
||||
).context_message('Error while loading vim colorscheme'))
|
||||
|
||||
|
||||
generic_keys = set(('exclude_modes', 'include_modes', 'width', 'align', 'name', 'draw_divider', 'priority', 'after', 'before'))
|
||||
type_keys = {
|
||||
'function': set(('args', 'module')),
|
||||
'string': set(('contents', 'type', 'highlight_group', 'divider_highlight_group')),
|
||||
'filler': set(('type', 'highlight_group', 'divider_highlight_group')),
|
||||
}
|
||||
required_keys = {
|
||||
'function': set(),
|
||||
'string': set(('contents', 'highlight_group')),
|
||||
'filler': set(('highlight_group',)),
|
||||
}
|
||||
function_keys = set(('args', 'module'))
|
||||
|
||||
|
||||
def check_key_compatibility(segment, data, context, echoerr):
|
||||
segment_type = segment.get('type', 'function')
|
||||
|
||||
if segment_type not in type_keys:
|
||||
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)),
|
||||
problem='found segment with unknown type {0}'.format(segment_type),
|
||||
problem_mark=segment_type.mark)
|
||||
return False, False, True
|
||||
|
||||
keys = set(segment)
|
||||
if not ((keys - generic_keys) < type_keys[segment_type]):
|
||||
unknown_keys = keys - generic_keys - type_keys[segment_type]
|
||||
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)),
|
||||
context_mark=context[-1][1].mark,
|
||||
problem='found keys not used with the current segment type: {0}'.format(
|
||||
', '.join((unicode(key) for key in unknown_keys))),
|
||||
problem_mark=list(unknown_keys)[0].mark)
|
||||
return True, False, True
|
||||
|
||||
if not (keys > required_keys[segment_type]):
|
||||
missing_keys = required_keys[segment_type] - keys
|
||||
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)),
|
||||
context_mark=context[-1][1].mark,
|
||||
problem='found missing required keys: {0}'.format(
|
||||
', '.join((unicode(key) for key in missing_keys))))
|
||||
return True, False, True
|
||||
|
||||
return True, False, False
|
||||
|
||||
|
||||
def check_segment_module(module, data, context, echoerr):
|
||||
with WithPath(data['import_paths']):
|
||||
try:
|
||||
__import__(unicode(module))
|
||||
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
|
||||
return True, False, False
|
||||
|
||||
|
||||
def check_full_segment_data(segment, data, context, echoerr):
|
||||
if 'name' not in segment:
|
||||
return True, False, False
|
||||
|
||||
ext = data['ext']
|
||||
theme_segment_data = context[0][1].get('segment_data', {})
|
||||
top_theme_name = data['main_config'].get('ext', {}).get(ext, {}).get('theme', {})
|
||||
if data['theme'] == top_theme_name:
|
||||
top_segment_data = {}
|
||||
else:
|
||||
top_segment_data = data['ext_theme_configs'].get(top_theme_name, {}).get('segment_data', {})
|
||||
|
||||
names = [segment['name']]
|
||||
if segment.get('type', 'function') == 'function':
|
||||
module = segment.get('module', context[0][1].get('default_module', 'powerline.segments.'+ext))
|
||||
names.insert(0, unicode(module) + '.' + unicode(names[0]))
|
||||
|
||||
segment_copy = segment.copy()
|
||||
|
||||
for key in ('before', 'after', 'args', 'contents'):
|
||||
if key not in segment_copy:
|
||||
for segment_data in [theme_segment_data, top_segment_data]:
|
||||
for name in names:
|
||||
try:
|
||||
val = segment_data[name][key]
|
||||
# HACK to keep marks
|
||||
l = list(segment_data[name])
|
||||
k = l[l.index(key)]
|
||||
segment_copy[k] = val
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return check_key_compatibility(segment_copy, data, context, echoerr)
|
||||
|
||||
|
||||
def check_segment_name(name, data, context, echoerr):
|
||||
ext = data['ext']
|
||||
if context[-2][1].get('type', 'function') == 'function':
|
||||
module = context[-2][1].get('module', context[0][1].get('default_module', 'powerline.segments.'+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 function {0} from module {1}'.format(name, module),
|
||||
problem_mark=module.mark)
|
||||
return True, False, True
|
||||
|
||||
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 True, False, True
|
||||
|
||||
hl_groups = []
|
||||
divider_hl_group = None
|
||||
|
||||
if func.__doc__:
|
||||
H_G_USED_STR = 'Highlight groups used: '
|
||||
D_H_G_USED_STR = 'Divider highlight group used: '
|
||||
for line in func.__doc__.split('\n'):
|
||||
if H_G_USED_STR in line:
|
||||
hl_groups.append(line[line.index(H_G_USED_STR)+len(H_G_USED_STR):])
|
||||
elif D_H_G_USED_STR in line:
|
||||
divider_hl_group = line[line.index(D_H_G_USED_STR)+len(D_H_G_USED_STR)+2:-3]
|
||||
|
||||
hadproblem = False
|
||||
|
||||
if divider_hl_group:
|
||||
r = hl_exists(divider_hl_group, data, context, echoerr, allow_gradients=True)
|
||||
if r:
|
||||
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(
|
||||
divider_hl_group, ', '.join(r)),
|
||||
problem_mark=name.mark)
|
||||
hadproblem = True
|
||||
|
||||
if hl_groups:
|
||||
greg = re.compile(r'``([^`]+)``( \(gradient\))?')
|
||||
hl_groups = [[greg.match(subs).groups() for subs in s.split(' or ')] for s in (', '.join(hl_groups)).split(', ')]
|
||||
for required_pack in hl_groups:
|
||||
rs = [hl_exists(hl_group, data, context, echoerr, allow_gradients=('force' if gradient else False))
|
||||
for hl_group, gradient in required_pack]
|
||||
if all(rs):
|
||||
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(
|
||||
', '.join((unicode(h[0]) for h in required_pack))),
|
||||
problem_mark=name.mark)
|
||||
for r, h in zip(rs, required_pack):
|
||||
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(
|
||||
h[0], ', '.join(r)))
|
||||
hadproblem = True
|
||||
else:
|
||||
r = hl_exists(name, data, context, echoerr, allow_gradients=True)
|
||||
if r:
|
||||
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(
|
||||
name, ', '.join(r)),
|
||||
problem_mark=name.mark)
|
||||
hadproblem = True
|
||||
|
||||
return True, False, hadproblem
|
||||
else:
|
||||
if name not in context[0][1].get('segment_data', {}):
|
||||
top_theme_name = data['main_config'].get('ext', {}).get(ext, {}).get('theme', {})
|
||||
if data['theme'] == top_theme_name:
|
||||
top_theme = {}
|
||||
else:
|
||||
top_theme = data['ext_theme_configs'].get(top_theme_name, {})
|
||||
if name not in top_theme.get('segment_data', {}):
|
||||
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)),
|
||||
problem='found useless use of name key (such name is not present in theme/segment_data)',
|
||||
problem_mark=name.mark)
|
||||
|
||||
return True, False, False
|
||||
|
||||
|
||||
def hl_exists(hl_group, data, context, echoerr, allow_gradients=False):
|
||||
ext = data['ext']
|
||||
if ext not in data['colorscheme_configs']:
|
||||
# No colorschemes. Error was already reported, no need to report it
|
||||
# twice
|
||||
return []
|
||||
r = []
|
||||
for colorscheme, cconfig in data['colorscheme_configs'][ext].items():
|
||||
if hl_group not in cconfig.get('groups', {}):
|
||||
r.append(colorscheme)
|
||||
elif not allow_gradients:
|
||||
group_config = cconfig['groups'][hl_group]
|
||||
hadgradient = False
|
||||
for ckey in ('fg', 'bg'):
|
||||
color = group_config.get(ckey)
|
||||
if not color:
|
||||
# No color. Error was already reported.
|
||||
continue
|
||||
# Gradients are only allowed for function segments. Note that
|
||||
# whether *either* color or gradient exists should have been
|
||||
# already checked
|
||||
hascolor = color in data['colors_config'].get('colors', {})
|
||||
hasgradient = color in data['colors_config'].get('gradients', {})
|
||||
if hasgradient:
|
||||
hadgradient = True
|
||||
if allow_gradients is False and not hascolor and hasgradient:
|
||||
echoerr(context='Error while checking highlight group in theme (key {key})'.format(key=context_key(context)),
|
||||
context_mark=hl_group.mark,
|
||||
problem='group {0} is using gradient {1} instead of a color'.format(hl_group, color),
|
||||
problem_mark=color.mark)
|
||||
r.append(colorscheme)
|
||||
continue
|
||||
if allow_gradients == 'force' and not hadgradient:
|
||||
echoerr(context='Error while checking highlight group in theme (key {key})'.format(key=context_key(context)),
|
||||
context_mark=hl_group.mark,
|
||||
problem='group {0} should have at least one gradient color, but it has no'.format(hl_group),
|
||||
problem_mark=group_config.mark)
|
||||
r.append(colorscheme)
|
||||
return r
|
||||
|
||||
|
||||
def check_highlight_group(hl_group, data, context, echoerr):
|
||||
r = hl_exists(hl_group, data, context, echoerr)
|
||||
if r:
|
||||
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(
|
||||
hl_group, ', '.join(r)),
|
||||
problem_mark=hl_group.mark)
|
||||
return True, False, True
|
||||
return True, False, False
|
||||
|
||||
|
||||
def check_highlight_groups(hl_groups, data, context, echoerr):
|
||||
rs = [hl_exists(hl_group, data, context, echoerr) for hl_group in hl_groups]
|
||||
if all(rs):
|
||||
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(
|
||||
', '.join((unicode(h) for h in hl_groups))),
|
||||
problem_mark=hl_groups.mark)
|
||||
for r, hl_group in zip(rs, hl_groups):
|
||||
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(
|
||||
hl_group, ', '.join(r)),
|
||||
problem_mark=hl_group.mark)
|
||||
return True, False, True
|
||||
return True, False, False
|
||||
|
||||
|
||||
def check_segment_data_key(key, data, context, echoerr):
|
||||
ext = data['ext']
|
||||
top_theme_name = data['main_config'].get('ext', {}).get(ext, {}).get('theme', {})
|
||||
is_top_theme = (data['theme'] == top_theme_name)
|
||||
if is_top_theme:
|
||||
themes = data['ext_theme_configs'].values()
|
||||
else:
|
||||
themes = [context[0][1]]
|
||||
|
||||
for theme in themes:
|
||||
for segments in theme.get('segments', {}).values():
|
||||
found = False
|
||||
for segment in segments:
|
||||
if 'name' in segment:
|
||||
if key == segment['name']:
|
||||
found = True
|
||||
module = segment.get('module', theme.get('default_module', 'powerline.segments.'+ext))
|
||||
if key == unicode(module) + '.' + unicode(segment['name']):
|
||||
found = True
|
||||
if found:
|
||||
break
|
||||
if found:
|
||||
break
|
||||
else:
|
||||
echoerr(context='Error while checking segment data',
|
||||
problem='found key {0} that cannot be associated with any segment'.format(key),
|
||||
problem_mark=key.mark)
|
||||
return True, False, True
|
||||
|
||||
return True, False, False
|
||||
|
||||
|
||||
highlight_group_spec = Spec().type(unicode).copy
|
||||
segment_module_spec = Spec().type(unicode).func(check_segment_module).optional().copy
|
||||
segments_spec = Spec().optional().list(
|
||||
Spec(
|
||||
type=Spec().oneof(type_keys).optional(),
|
||||
name=Spec().re('^[a-zA-Z_]\w+$').func(check_segment_name).optional(),
|
||||
exclude_modes=Spec().list(vim_mode_spec()).optional(),
|
||||
include_modes=Spec().list(vim_mode_spec()).optional(),
|
||||
draw_divider=Spec().type(bool).optional(),
|
||||
module=segment_module_spec(),
|
||||
priority=Spec().cmp('ge', -1).optional(),
|
||||
after=Spec().type(unicode).optional(),
|
||||
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(),
|
||||
contents=Spec().type(unicode).optional(),
|
||||
highlight_group=Spec().list(
|
||||
highlight_group_spec().re('^(?:(?!:divider$).)+$',
|
||||
lambda value: 'it is recommended that only divider highlight group names end with ":divider"')
|
||||
).func(check_highlight_groups).optional(),
|
||||
divider_highlight_group=highlight_group_spec().func(check_highlight_group).re(':divider$',
|
||||
lambda value: 'it is recommended that divider highlight group names end with ":divider"').optional(),
|
||||
).func(check_full_segment_data),
|
||||
).copy
|
||||
theme_spec = (Spec(
|
||||
default_module=segment_module_spec(),
|
||||
segment_data=Spec().unknown_spec(
|
||||
Spec().func(check_segment_data_key),
|
||||
Spec(
|
||||
after=Spec().type(unicode).optional(),
|
||||
before=Spec().type(unicode).optional(),
|
||||
# FIXME Check args
|
||||
args=Spec().type(dict).optional(),
|
||||
contents=Spec().type(unicode).optional(),
|
||||
),
|
||||
).optional().context_message('Error while loading segment data (key {key})'),
|
||||
segments=Spec(
|
||||
left=segments_spec().context_message('Error while loading segments from left side (key {key})'),
|
||||
right=segments_spec().context_message('Error while loading segments from right side (key {key})'),
|
||||
).func(
|
||||
lambda value, *args: (True, True, not (('left' in value) or ('right' in value))),
|
||||
lambda value: 'segments dictionary must contain either left, right or both keys'
|
||||
).context_message('Error while loading segments (key {key})'),
|
||||
).context_message('Error while loading theme'))
|
||||
|
||||
|
||||
def check(path=None):
|
||||
search_paths = [path] if path else Powerline.get_config_paths()
|
||||
|
||||
dirs = {
|
||||
'themes': defaultdict(lambda: []),
|
||||
'colorschemes': defaultdict(lambda: [])
|
||||
}
|
||||
for path in reversed(search_paths):
|
||||
for subdir in ('themes', 'colorschemes'):
|
||||
d = os.path.join(path, subdir)
|
||||
if os.path.isdir(d):
|
||||
for ext in os.listdir(d):
|
||||
extd = os.path.join(d, ext)
|
||||
if os.path.isdir(extd):
|
||||
dirs[subdir][ext].append(extd)
|
||||
elif os.path.exists(d):
|
||||
hadproblem = True
|
||||
sys.stderr.write('Path {0} is supposed to be a directory, but it is not\n'.format(d))
|
||||
|
||||
configs = {
|
||||
'themes': defaultdict(lambda: {}),
|
||||
'colorschemes': defaultdict(lambda: {})
|
||||
}
|
||||
for subdir in ('themes', 'colorschemes'):
|
||||
for ext in dirs[subdir]:
|
||||
for d in dirs[subdir][ext]:
|
||||
for config in os.listdir(d):
|
||||
if config.endswith('.json'):
|
||||
configs[subdir][ext][config[:-5]] = os.path.join(d, config)
|
||||
|
||||
diff = set(configs['themes']) ^ set(configs['colorschemes'])
|
||||
if diff:
|
||||
hadproblem = True
|
||||
for ext in diff:
|
||||
sys.stderr.write('{0} extension {1} present only in {2}\n'.format(
|
||||
ext,
|
||||
'configuration' if (ext in dirs['themes'] and ext in dirs['colorschemes']) else 'directory',
|
||||
'themes' if ext in configs['themes'] else 'colorschemes',
|
||||
))
|
||||
|
||||
main_config = load_json_config(search_paths, 'config', load=load, open=open_file)
|
||||
hadproblem = main_spec.match(main_config, data={'configs': configs}, context=(('', main_config),))[1]
|
||||
|
||||
import_paths = [os.path.expanduser(path) for path in main_config.get('common', {}).get('paths', [])]
|
||||
|
||||
colors_config = load_json_config(search_paths, 'colors', load=load, open=open_file)
|
||||
if colors_spec.match(colors_config, context=(('', colors_config),))[1]:
|
||||
hadproblem = True
|
||||
|
||||
colorscheme_configs = defaultdict(lambda: {})
|
||||
for ext in configs['colorschemes']:
|
||||
data = {'ext': ext, 'colors_config': colors_config}
|
||||
for colorscheme, cfile in configs['colorschemes'][ext].items():
|
||||
with open_file(cfile) as config_file_fp:
|
||||
config = load(config_file_fp)
|
||||
colorscheme_configs[ext][colorscheme] = config
|
||||
if ext == 'vim':
|
||||
spec = vim_colorscheme_spec
|
||||
else:
|
||||
spec = colorscheme_spec
|
||||
if spec.match(config, context=(('', config),), data=data)[1]:
|
||||
hadproblem = True
|
||||
|
||||
theme_configs = defaultdict(lambda: {})
|
||||
for ext in configs['themes']:
|
||||
for theme, sfile in configs['themes'][ext].items():
|
||||
with open_file(sfile) as config_file_fp:
|
||||
config = load(config_file_fp)
|
||||
theme_configs[ext][theme] = config
|
||||
for ext, configs in theme_configs.items():
|
||||
data = {'ext': ext, 'colorscheme_configs': colorscheme_configs, 'import_paths': import_paths,
|
||||
'main_config': main_config, 'ext_theme_configs': configs, 'colors_config': colors_config}
|
||||
for theme, config in configs.items():
|
||||
data['theme'] = theme
|
||||
if theme_spec.match(config, context=(('', config),), data=data)[1]:
|
||||
hadproblem = True
|
||||
return hadproblem
|
|
@ -58,6 +58,7 @@ class YAMLError(Exception):
|
|||
|
||||
|
||||
def echoerr(*args, **kwargs):
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.write(format_error(*args, **kwargs) + '\n')
|
||||
|
||||
def format_error(context=None, context_mark=None, problem=None, problem_mark=None, note=None):
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
'''Powerline configuration checker.'''
|
||||
import argparse
|
||||
from powerline.lint import check
|
||||
import sys
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('-p', '--config_path', metavar='PATH')
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = parser.parse_args()
|
||||
sys.exit(check(args.config_path))
|
1
setup.py
1
setup.py
|
@ -25,6 +25,7 @@ setup(
|
|||
url='https://github.com/Lokaltog/powerline',
|
||||
scripts=[
|
||||
'scripts/powerline',
|
||||
'scripts/powerline-lint',
|
||||
],
|
||||
keywords='',
|
||||
packages=find_packages(exclude=('tests', 'tests.*')),
|
||||
|
|
|
@ -5,6 +5,6 @@ if python -c 'import sys; sys.exit(1 * (sys.version_info[0] != 2))' ; then
|
|||
pip install mercurial bzr
|
||||
if python -c 'import sys; sys.exit(1 * (sys.version_info[1] >= 7))' ; then
|
||||
# Python 2.6
|
||||
pip install unittest2
|
||||
pip install unittest2 argparse
|
||||
fi
|
||||
fi
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#!/bin/sh
|
||||
: ${PYTHON:=python}
|
||||
FAILED=0
|
||||
if ${PYTHON} -c 'import sys; sys.exit(1 * (sys.version_info >= (2, 7)))' ; then
|
||||
# Python 2.6
|
||||
export PYTHONPATH="${PYTHONPATH}:`realpath .`"
|
||||
|
@ -9,5 +10,11 @@ if ${PYTHON} -c 'import sys; sys.exit(1 * (sys.version_info >= (2, 7)))' ; then
|
|||
fi
|
||||
done
|
||||
else
|
||||
${PYTHON} setup.py test
|
||||
if ! ${PYTHON} setup.py test ; then
|
||||
FAILED=1
|
||||
fi
|
||||
fi
|
||||
if ! ${PYTHON} scripts/powerline-lint ; then
|
||||
FAILED=1
|
||||
fi
|
||||
exit $FAILED
|
||||
|
|
Loading…
Reference in New Issue