summaryrefslogtreecommitdiff
path: root/grc/core/schema_checker
diff options
context:
space:
mode:
Diffstat (limited to 'grc/core/schema_checker')
-rw-r--r--grc/core/schema_checker/__init__.py5
-rw-r--r--grc/core/schema_checker/block.py57
-rw-r--r--grc/core/schema_checker/domain.py16
-rw-r--r--grc/core/schema_checker/flow_graph.py23
-rw-r--r--grc/core/schema_checker/utils.py27
-rw-r--r--grc/core/schema_checker/validator.py102
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))