diff options
Diffstat (limited to 'grc/core/schema_checker')
-rw-r--r-- | grc/core/schema_checker/__init__.py | 5 | ||||
-rw-r--r-- | grc/core/schema_checker/block.py | 57 | ||||
-rw-r--r-- | grc/core/schema_checker/domain.py | 16 | ||||
-rw-r--r-- | grc/core/schema_checker/flow_graph.py | 23 | ||||
-rw-r--r-- | grc/core/schema_checker/utils.py | 27 | ||||
-rw-r--r-- | grc/core/schema_checker/validator.py | 102 |
6 files changed, 230 insertions, 0 deletions
diff --git a/grc/core/schema_checker/__init__.py b/grc/core/schema_checker/__init__.py new file mode 100644 index 0000000000..e92500ed4a --- /dev/null +++ b/grc/core/schema_checker/__init__.py @@ -0,0 +1,5 @@ +from .validator import Validator + +from .block import BLOCK_SCHEME +from .domain import DOMAIN_SCHEME +from .flow_graph import FLOW_GRAPH_SCHEME diff --git a/grc/core/schema_checker/block.py b/grc/core/schema_checker/block.py new file mode 100644 index 0000000000..db8830fddf --- /dev/null +++ b/grc/core/schema_checker/block.py @@ -0,0 +1,57 @@ +from .utils import Spec, expand, str_ + +PARAM_SCHEME = expand( + base_key=str_, # todo: rename/remove + + id=str_, + label=str_, + category=str_, + + dtype=str_, + default=object, + + options=list, + option_labels=list, + option_attributes=Spec(types=dict, required=False, item_scheme=(str_, list)), + + hide=str_, +) +PORT_SCHEME = expand( + label=str_, + domain=str_, + + id=str_, + dtype=str_, + vlen=(int, str_), + + multiplicity=(int, str_), + optional=(bool, int, str_), + hide=(bool, str_), +) +TEMPLATES_SCHEME = expand( + imports=str_, + var_make=str_, + make=str_, + callbacks=list, +) +BLOCK_SCHEME = expand( + id=Spec(types=str_, required=True, item_scheme=None), + label=str_, + category=(list, str_), + flags=(list, str_), + + parameters=Spec(types=list, required=False, item_scheme=PARAM_SCHEME), + inputs=Spec(types=list, required=False, item_scheme=PORT_SCHEME), + outputs=Spec(types=list, required=False, item_scheme=PORT_SCHEME), + + checks=(list, str_), + value=str_, + + templates=Spec(types=dict, required=False, item_scheme=TEMPLATES_SCHEME), + + documentation=str_, + + file_format=Spec(types=int, required=True, item_scheme=None), + + block_wrapper_path=str_, # todo: rename/remove +) diff --git a/grc/core/schema_checker/domain.py b/grc/core/schema_checker/domain.py new file mode 100644 index 0000000000..86c29ed3c6 --- /dev/null +++ b/grc/core/schema_checker/domain.py @@ -0,0 +1,16 @@ +from .utils import Spec, expand, str_ + +DOMAIN_CONNECTION = expand( + type=Spec(types=list, required=True, item_scheme=None), + connect=str_, +) + +DOMAIN_SCHEME = expand( + id=Spec(types=str_, required=True, item_scheme=None), + label=str_, + color=str_, + multiple_connections_per_input=bool, + multiple_connections_per_output=bool, + + templates=Spec(types=list, required=False, item_scheme=DOMAIN_CONNECTION) +)
\ No newline at end of file diff --git a/grc/core/schema_checker/flow_graph.py b/grc/core/schema_checker/flow_graph.py new file mode 100644 index 0000000000..746fbf4aa7 --- /dev/null +++ b/grc/core/schema_checker/flow_graph.py @@ -0,0 +1,23 @@ +from .utils import Spec, expand, str_ + +OPTIONS_SCHEME = expand( + parameters=Spec(types=dict, required=False, item_scheme=(str_, str_)), + states=Spec(types=dict, required=False, item_scheme=(str_, str_)), +) + +BLOCK_SCHEME = expand( + name=str_, + id=str_, + **OPTIONS_SCHEME +) + +FLOW_GRAPH_SCHEME = expand( + options=Spec(types=dict, required=False, item_scheme=OPTIONS_SCHEME), + blocks=Spec(types=dict, required=False, item_scheme=BLOCK_SCHEME), + connections=list, + + metadata=Spec(types=dict, required=True, item_scheme=expand( + file_format=Spec(types=int, required=True, item_scheme=None), + )) + +) diff --git a/grc/core/schema_checker/utils.py b/grc/core/schema_checker/utils.py new file mode 100644 index 0000000000..a9cf4c0175 --- /dev/null +++ b/grc/core/schema_checker/utils.py @@ -0,0 +1,27 @@ +import collections + +import six + +Spec = collections.namedtuple('Spec', 'types required item_scheme') + + +def expand(**kwargs): + def expand_spec(spec): + if not isinstance(spec, Spec): + types_ = spec if isinstance(spec, tuple) else (spec,) + spec = Spec(types=types_, required=False, item_scheme=None) + elif not isinstance(spec.types, tuple): + spec = Spec(types=(spec.types,), required=spec.required, + item_scheme=spec.item_scheme) + return spec + return {key: expand_spec(value) for key, value in kwargs.items()} + + +str_ = six.string_types + + +class Message(collections.namedtuple('Message', 'path type message')): + fmt = '{path}: {type}: {message}' + + def __str__(self): + return self.fmt.format(**self._asdict()) diff --git a/grc/core/schema_checker/validator.py b/grc/core/schema_checker/validator.py new file mode 100644 index 0000000000..ab4d43bc67 --- /dev/null +++ b/grc/core/schema_checker/validator.py @@ -0,0 +1,102 @@ +# Copyright 2016 Free Software Foundation, Inc. +# This file is part of GNU Radio +# +# GNU Radio Companion is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# GNU Radio Companion is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +from __future__ import print_function + +import six + +from .utils import Message, Spec + + +class Validator(object): + + def __init__(self, scheme=None): + self._path = [] + self.scheme = scheme + self.messages = [] + self.passed = False + + def run(self, data): + if not self.scheme: + return True + self._reset() + self._path.append('block') + self._check(data, self.scheme) + self._path.pop() + return self.passed + + def _reset(self): + del self.messages[:] + del self._path[:] + self.passed = True + + def _check(self, data, scheme): + if not data or not isinstance(data, dict): + self._error('Empty data or not a dict') + return + if isinstance(scheme, dict): + self._check_dict(data, scheme) + else: + self._check_var_key_dict(data, *scheme) + + def _check_var_key_dict(self, data, key_type, value_scheme): + for key, value in six.iteritems(data): + if not isinstance(key, key_type): + self._error('Key type {!r} for {!r} not in valid types'.format( + type(value).__name__, key)) + if isinstance(value_scheme, Spec): + self._check_dict(value, value_scheme) + elif not isinstance(value, value_scheme): + self._error('Value type {!r} for {!r} not in valid types'.format( + type(value).__name__, key)) + + def _check_dict(self, data, scheme): + for key, (types_, required, item_scheme) in six.iteritems(scheme): + try: + value = data[key] + except KeyError: + if required: + self._error('Missing required entry {!r}'.format(key)) + continue + + self._check_value(value, types_, item_scheme, label=key) + + for key in set(data).difference(scheme): + self._warn('Ignoring extra key {!r}'.format(key)) + + def _check_list(self, data, scheme, label): + for i, item in enumerate(data): + self._path.append('{}[{}]'.format(label, i)) + self._check(item, scheme) + self._path.pop() + + def _check_value(self, value, types_, item_scheme, label): + if not isinstance(value, types_): + self._error('Value type {!r} for {!r} not in valid types'.format( + type(value).__name__, label)) + if item_scheme: + if isinstance(value, list): + self._check_list(value, item_scheme, label) + elif isinstance(value, dict): + self._check(value, item_scheme) + + def _error(self, msg): + self.messages.append(Message('.'.join(self._path), 'error', msg)) + self.passed = False + + def _warn(self, msg): + self.messages.append(Message('.'.join(self._path), 'warn', msg)) |