From 7f7fa2f91467fdb2b11312be8562e7b51fdeb199 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <sebastian.koslowski@gmail.com>
Date: Tue, 3 May 2016 17:13:08 +0200
Subject: grc: added yaml/mako support

Includes basic converter from XML/Cheetah to YAML/Mako based block format.
---
 grc/core/blocks/_build.py | 69 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)
 create mode 100644 grc/core/blocks/_build.py

(limited to 'grc/core/blocks/_build.py')

diff --git a/grc/core/blocks/_build.py b/grc/core/blocks/_build.py
new file mode 100644
index 0000000000..9a50086cea
--- /dev/null
+++ b/grc/core/blocks/_build.py
@@ -0,0 +1,69 @@
+# 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 absolute_import
+
+import re
+
+from .block import Block
+from ._flags import Flags
+from ._templates import MakoTemplates
+
+
+def build(id, label='', category='', flags='', documentation='',
+          checks=None, value=None,
+          parameters=None, inputs=None, outputs=None, templates=None, **kwargs):
+    block_id = id
+
+    cls = type(block_id, (Block,), {})
+    cls.key = block_id
+
+    cls.label = label or block_id.title()
+    cls.category = [cat.strip() for cat in category.split('/') if cat.strip()]
+
+    cls.flags = Flags(flags)
+    if re.match(r'options$|variable|virtual', block_id):
+        cls.flags += Flags.NOT_DSP + Flags.DISABLE_BYPASS
+
+    cls.documentation = {'': documentation.strip('\n\t ').replace('\\\n', '')}
+
+    cls.checks = [_single_mako_expr(check, block_id) for check in (checks or [])]
+
+    cls.parameters_data = parameters or []
+    cls.inputs_data = inputs or []
+    cls.outputs_data = outputs or []
+    cls.extra_data = kwargs
+
+    templates = templates or {}
+    cls.templates = MakoTemplates(
+        imports=templates.get('imports', ''),
+        make=templates.get('make', ''),
+        callbacks=templates.get('callbacks', []),
+        var_make=templates.get('var_make', ''),
+    )
+    # todo: MakoTemplates.compile() to check for errors
+
+    cls.value = _single_mako_expr(value, block_id)
+
+    return cls
+
+
+def _single_mako_expr(value, block_id):
+    match = re.match(r'\s*\$\{\s*(.*?)\s*\}\s*', str(value))
+    if value and not match:
+        raise ValueError('{} is not a mako substitution in {}'.format(value, block_id))
+    return match.group(1) if match else None
-- 
cgit v1.2.3


From ff8caa034ac65c0c28b2a542b7e72f23390ccd46 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <sebastian.koslowski@gmail.com>
Date: Sun, 1 Oct 2017 09:51:27 +0200
Subject: [grc] fix conversion of <check> and rename them to asserts

---
 grc/converter/block.py           |  6 +++---
 grc/core/blocks/_build.py        |  4 ++--
 grc/core/blocks/block.py         | 14 +++++++-------
 grc/core/schema_checker/block.py |  2 +-
 4 files changed, 13 insertions(+), 13 deletions(-)

(limited to 'grc/core/blocks/_build.py')

diff --git a/grc/converter/block.py b/grc/converter/block.py
index 04e5c905a0..0e362d97c0 100644
--- a/grc/converter/block.py
+++ b/grc/converter/block.py
@@ -95,14 +95,14 @@ def convert_block_xml(node):
 
     data['outputs'] = [convert_port_xml(port_node, converter.to_python_dec)
                        for port_node in node.iterfind('source')] or no_value
-
-    data['checks'] = [converter.to_python_dec(check_node.text)
-                      for check_node in node.iterfind('checks')] or no_value
     data['value'] = (
         converter.to_python_dec(node.findtext('var_value')) or
         ('${ value }' if block_id.startswith('variable') else no_value)
     )
 
+    data['asserts'] = [converter.to_python_dec(check_node.text)
+                       for check_node in node.iterfind('check')] or no_value
+
     data['templates'] = convert_templates(node, converter.to_mako, block_id) or no_value
 
     docs = node.findtext('doc')
diff --git a/grc/core/blocks/_build.py b/grc/core/blocks/_build.py
index 9a50086cea..9221433387 100644
--- a/grc/core/blocks/_build.py
+++ b/grc/core/blocks/_build.py
@@ -25,7 +25,7 @@ from ._templates import MakoTemplates
 
 
 def build(id, label='', category='', flags='', documentation='',
-          checks=None, value=None,
+          value=None, asserts=None,
           parameters=None, inputs=None, outputs=None, templates=None, **kwargs):
     block_id = id
 
@@ -41,7 +41,7 @@ def build(id, label='', category='', flags='', documentation='',
 
     cls.documentation = {'': documentation.strip('\n\t ').replace('\\\n', '')}
 
-    cls.checks = [_single_mako_expr(check, block_id) for check in (checks or [])]
+    cls.asserts = [_single_mako_expr(a, block_id) for a in (asserts or [])]
 
     cls.parameters_data = parameters or []
     cls.inputs_data = inputs or []
diff --git a/grc/core/blocks/block.py b/grc/core/blocks/block.py
index 0d8a779486..adc046936d 100644
--- a/grc/core/blocks/block.py
+++ b/grc/core/blocks/block.py
@@ -55,7 +55,7 @@ class Block(Element):
     documentation = {'': ''}
 
     value = None
-    checks = []
+    asserts = []
 
     templates = MakoTemplates()
     parameters_data = []
@@ -190,18 +190,18 @@ class Block(Element):
         Evaluate the checks: each check must evaluate to True.
         """
         Element.validate(self)
-        self._run_checks()
+        self._run_asserts()
         self._validate_generate_mode_compat()
         self._validate_var_value()
 
-    def _run_checks(self):
+    def _run_asserts(self):
         """Evaluate the checks"""
-        for check in self.checks:
+        for expr in self.asserts:
             try:
-                if not self.evaluate(check):
-                    self.add_error_message('Check "{}" failed.'.format(check))
+                if not self.evaluate(expr):
+                    self.add_error_message('Assertion "{}" failed.'.format(expr))
             except:
-                self.add_error_message('Check "{}" did not evaluate.'.format(check))
+                self.add_error_message('Assertion "{}" did not evaluate.'.format(expr))
 
     def _validate_generate_mode_compat(self):
         """check if this is a GUI block and matches the selected generate option"""
diff --git a/grc/core/schema_checker/block.py b/grc/core/schema_checker/block.py
index db8830fddf..ea079b4276 100644
--- a/grc/core/schema_checker/block.py
+++ b/grc/core/schema_checker/block.py
@@ -44,7 +44,7 @@ BLOCK_SCHEME = expand(
     inputs=Spec(types=list, required=False, item_scheme=PORT_SCHEME),
     outputs=Spec(types=list, required=False, item_scheme=PORT_SCHEME),
 
-    checks=(list, str_),
+    asserts=(list, str_),
     value=str_,
 
     templates=Spec(types=dict, required=False, item_scheme=TEMPLATES_SCHEME),
-- 
cgit v1.2.3


From 1fa89b3704d7f476e4395eb9358d5a6d7642251b Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <sebastian.koslowski@gmail.com>
Date: Thu, 26 Oct 2017 20:15:22 +0200
Subject: grc: disable auto-conversion and implement json cache

---
 grc/core/Config.py                      |   6 --
 grc/core/Constants.py                   |   2 +
 grc/core/blocks/__init__.py             |   1 +
 grc/core/blocks/_build.py               |  10 +--
 grc/core/cache.py                       |  99 +++++++++++++++++++++++++++++
 grc/core/platform.py                    | 109 +++++++++++++++-----------------
 grc/core/schema_checker/block.py        |   2 +-
 grc/core/utils/__init__.py              |  12 ++++
 grc/core/utils/descriptors/evaluated.py |   6 +-
 grc/gui/Platform.py                     |   4 +-
 10 files changed, 178 insertions(+), 73 deletions(-)
 create mode 100644 grc/core/cache.py

(limited to 'grc/core/blocks/_build.py')

diff --git a/grc/core/Config.py b/grc/core/Config.py
index eb53e1751d..4accb74c63 100644
--- a/grc/core/Config.py
+++ b/grc/core/Config.py
@@ -31,8 +31,6 @@ class Config(object):
 
     hier_block_lib_dir = os.environ.get('GRC_HIER_PATH', Constants.DEFAULT_HIER_BLOCK_LIB_DIR)
 
-    yml_block_cache = os.path.expanduser('~/.cache/grc_gnuradio')  # FIXME: remove this as soon as converter is stable
-
     def __init__(self, version, version_parts=None, name=None, prefs=None):
         self._gr_prefs = prefs if prefs else DummyPrefs()
         self.version = version
@@ -40,9 +38,6 @@ class Config(object):
         if name:
             self.name = name
 
-        if not os.path.exists(self.yml_block_cache):
-            os.mkdir(self.yml_block_cache)
-
     @property
     def block_paths(self):
         path_list_sep = {'/': ':', '\\': ';'}[os.path.sep]
@@ -50,7 +45,6 @@ class Config(object):
         paths_sources = (
             self.hier_block_lib_dir,
             os.environ.get('GRC_BLOCKS_PATH', ''),
-            self.yml_block_cache,
             self._gr_prefs.get_string('grc', 'local_blocks_path', ''),
             self._gr_prefs.get_string('grc', 'global_blocks_path', ''),
         )
diff --git a/grc/core/Constants.py b/grc/core/Constants.py
index fc5383378c..59c5dff35e 100644
--- a/grc/core/Constants.py
+++ b/grc/core/Constants.py
@@ -31,6 +31,8 @@ BLOCK_DTD = os.path.join(DATA_DIR, 'block.dtd')
 DEFAULT_FLOW_GRAPH = os.path.join(DATA_DIR, 'default_flow_graph.grc')
 DEFAULT_HIER_BLOCK_LIB_DIR = os.path.expanduser('~/.grc_gnuradio')
 
+CACHE_FILE = os.path.expanduser('~/.cache/grc_gnuradio/cache.json')
+
 BLOCK_DESCRIPTION_FILE_FORMAT_VERSION = 1
 # File format versions:
 #  0: undefined / legacy
diff --git a/grc/core/blocks/__init__.py b/grc/core/blocks/__init__.py
index e4a085d477..4ca0d5d2bc 100644
--- a/grc/core/blocks/__init__.py
+++ b/grc/core/blocks/__init__.py
@@ -29,6 +29,7 @@ build_ins = {}
 
 
 def register_build_in(cls):
+    cls.loaded_from = '(build-in)'
     build_ins[cls.key] = cls
     return cls
 
diff --git a/grc/core/blocks/_build.py b/grc/core/blocks/_build.py
index 9221433387..ce3496d9c4 100644
--- a/grc/core/blocks/_build.py
+++ b/grc/core/blocks/_build.py
@@ -29,7 +29,7 @@ def build(id, label='', category='', flags='', documentation='',
           parameters=None, inputs=None, outputs=None, templates=None, **kwargs):
     block_id = id
 
-    cls = type(block_id, (Block,), {})
+    cls = type(str(block_id), (Block,), {})
     cls.key = block_id
 
     cls.label = label or block_id.title()
@@ -63,7 +63,9 @@ def build(id, label='', category='', flags='', documentation='',
 
 
 def _single_mako_expr(value, block_id):
-    match = re.match(r'\s*\$\{\s*(.*?)\s*\}\s*', str(value))
-    if value and not match:
+    if not value:
+        return None
+    value = value.strip()
+    if not (value.startswith('${') and value.endswith('}')):
         raise ValueError('{} is not a mako substitution in {}'.format(value, block_id))
-    return match.group(1) if match else None
+    return value[2:-1].strip()
diff --git a/grc/core/cache.py b/grc/core/cache.py
new file mode 100644
index 0000000000..b72255ce1f
--- /dev/null
+++ b/grc/core/cache.py
@@ -0,0 +1,99 @@
+# Copyright 2017 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 absolute_import, print_function
+
+from io import open
+import json
+import logging
+import os
+
+import six
+
+from .io import yaml
+
+logger = logging.getLogger(__name__)
+
+
+class Cache(object):
+
+    def __init__(self, filename):
+        self.cache_file = filename
+        self.cache = {}
+        self.need_cache_write = True
+        self._accessed_items = set()
+        try:
+            os.makedirs(os.path.dirname(filename))
+        except OSError:
+            pass
+        try:
+            self._converter_mtime = os.path.getmtime(filename)
+        except OSError:
+            self._converter_mtime = -1
+
+    def load(self):
+        try:
+            logger.debug("Loading block cache from: {}".format(self.cache_file))
+            with open(self.cache_file, encoding='utf-8') as cache_file:
+                self.cache = json.load(cache_file)
+            self.need_cache_write = False
+        except (IOError, ValueError):
+            self.need_cache_write = True
+
+    def get_or_load(self, filename):
+        self._accessed_items.add(filename)
+        if os.path.getmtime(filename) <= self._converter_mtime:
+            try:
+                return self.cache[filename]
+            except KeyError:
+                pass
+
+        with open(filename, encoding='utf-8') as fp:
+            data = yaml.safe_load(fp)
+        self.cache[filename] = data
+        self.need_cache_write = True
+        return data
+
+    def save(self):
+        if not self.need_cache_write:
+            return
+
+        logger.info('Saving %d entries to json cache', len(self.cache))
+        with open(self.cache_file, 'wb') as cache_file:
+            json.dump(self.cache, cache_file, encoding='utf-8')
+
+    def prune(self):
+        for filename in (set(self.cache) - self._accessed_items):
+            del self.cache[filename]
+
+    def __enter__(self):
+        self.load()
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self.save()
+
+
+def byteify(data):
+    if isinstance(data, dict):
+        return {byteify(key): byteify(value) for key, value in six.iteritems(data)}
+    elif isinstance(data, list):
+        return [byteify(element) for element in data]
+    elif isinstance(data, six.text_type) and six.PY2:
+        return data.encode('utf-8')
+    else:
+        return data
diff --git a/grc/core/platform.py b/grc/core/platform.py
index 538bacade2..54deef3455 100644
--- a/grc/core/platform.py
+++ b/grc/core/platform.py
@@ -19,7 +19,6 @@ from __future__ import absolute_import, print_function
 
 from codecs import open
 from collections import namedtuple
-import glob
 import os
 import logging
 from itertools import chain
@@ -33,6 +32,7 @@ from . import (
 )
 
 from .Config import Config
+from .cache import Cache
 from .base import Element
 from .io import yaml
 from .generator import Generator
@@ -141,44 +141,41 @@ class Platform(Element):
         self.connection_templates.clear()
         self._block_categories.clear()
 
-        # FIXME: remove this as soon as converter is stable
-        from ..converter import Converter
-        converter = Converter(self.config.block_paths, self.config.yml_block_cache)
-        converter.run()
-        logging.info('XML converter done.')
-
-        for file_path in self._iter_files_in_block_path(path):
-            try:
-                data = converter.cache[file_path]
-            except KeyError:
-                with open(file_path, encoding='utf-8') as fp:
-                    data = yaml.safe_load(fp)
-
-            if file_path.endswith('.block.yml'):
-                loader = self.load_block_description
-                scheme = schema_checker.BLOCK_SCHEME
-            elif file_path.endswith('.domain.yml'):
-                loader = self.load_domain_description
-                scheme = schema_checker.DOMAIN_SCHEME
-            elif file_path.endswith('.tree.yml'):
-                loader = self.load_category_tree_description
-                scheme = None
-            else:
-                continue
-
-            try:
-                checker = schema_checker.Validator(scheme)
-                passed = checker.run(data)
-                for msg in checker.messages:
-                    logger.warning('{:<40s} {}'.format(os.path.basename(file_path), msg))
-                if not passed:
-                    logger.info('YAML schema check failed for: ' + file_path)
-
-                loader(data, file_path)
-            except Exception as error:
-                logger.exception('Error while loading %s', file_path)
-                logger.exception(error)
-                raise
+        # # FIXME: remove this as soon as converter is stable
+        # from ..converter import Converter
+        # converter = Converter(self.config.block_paths, self.config.yml_block_cache)
+        # converter.run()
+        # logging.info('XML converter done.')
+
+        with Cache(Constants.CACHE_FILE) as cache:
+            for file_path in self._iter_files_in_block_path(path):
+                data = cache.get_or_load(file_path)
+
+                if file_path.endswith('.block.yml'):
+                    loader = self.load_block_description
+                    scheme = schema_checker.BLOCK_SCHEME
+                elif file_path.endswith('.domain.yml'):
+                    loader = self.load_domain_description
+                    scheme = schema_checker.DOMAIN_SCHEME
+                elif file_path.endswith('.tree.yml'):
+                    loader = self.load_category_tree_description
+                    scheme = None
+                else:
+                    continue
+
+                try:
+                    checker = schema_checker.Validator(scheme)
+                    passed = checker.run(data)
+                    for msg in checker.messages:
+                        logger.warning('{:<40s} {}'.format(os.path.basename(file_path), msg))
+                    if not passed:
+                        logger.info('YAML schema check failed for: ' + file_path)
+
+                    loader(data, file_path)
+                except Exception as error:
+                    logger.exception('Error while loading %s', file_path)
+                    logger.exception(error)
+                    raise
 
         for key, block in six.iteritems(self.blocks):
             category = self._block_categories.get(key, block.category)
@@ -201,10 +198,9 @@ class Platform(Element):
             if os.path.isfile(entry):
                 yield entry
             elif os.path.isdir(entry):
-                pattern = os.path.join(entry, '**.' + ext)
-                yield_from = glob.iglob(pattern)
-                for file_path in yield_from:
-                    yield file_path
+                for dirpath, dirnames, filenames in os.walk(entry):
+                    for filename in sorted(filter(lambda f: f.endswith('.' + ext), filenames)):
+                        yield os.path.join(dirpath, filename)
             else:
                 logger.debug('Ignoring invalid path entry %r', entry)
 
@@ -232,16 +228,18 @@ class Platform(Element):
             log.error('Unknown format version %d in %s', file_format, file_path)
             return
 
-        block_id = data.pop('id').rstrip('_')
+        block_id = data['id'] = data['id'].rstrip('_')
 
         if block_id in self.block_classes_build_in:
             log.warning('Not overwriting build-in block %s with %s', block_id, file_path)
             return
         if block_id in self.blocks:
-            log.warning('Block with id "%s" overwritten by %s', block_id, file_path)
+            log.warning('Block with id "%s" loaded from\n  %s\noverwritten by\n  %s',
+                        block_id, self.blocks[block_id].loaded_from, file_path)
 
         try:
-            block_cls = self.blocks[block_id] = self.new_block_class(block_id, **data)
+            block_cls = self.blocks[block_id] = self.new_block_class(**data)
+            block_cls.loaded_from = file_path
         except errors.BlockLoadError as error:
             log.error('Unable to load block %s', block_id)
             log.exception(error)
@@ -288,19 +286,12 @@ class Platform(Element):
         path = []
 
         def load_category(name, elements):
-            if not isinstance(name, str):
-                log.debug('invalid name %r', name)
-                return
-            if isinstance(elements, list):
-                pass
-            elif isinstance(elements, str):
-                elements = [elements]
-            else:
-                log.debug('Ignoring elements of %s', name)
+            if not isinstance(name, six.string_types):
+                log.debug('Invalid name %r', name)
                 return
             path.append(name)
-            for element in elements:
-                if isinstance(element, str):
+            for element in utils.to_list(elements):
+                if isinstance(element, six.string_types):
                     block_id = element
                     self._block_categories[block_id] = list(path)
                 elif isinstance(element, dict):
@@ -415,8 +406,8 @@ class Platform(Element):
             fg.import_data(data)
         return fg
 
-    def new_block_class(self, block_id, **data):
-        return blocks.build(block_id, **data)
+    def new_block_class(self, **data):
+        return blocks.build(**data)
 
     def make_block(self, parent, block_id, **kwargs):
         cls = self.block_classes[block_id]
diff --git a/grc/core/schema_checker/block.py b/grc/core/schema_checker/block.py
index ea079b4276..d511e36887 100644
--- a/grc/core/schema_checker/block.py
+++ b/grc/core/schema_checker/block.py
@@ -37,7 +37,7 @@ TEMPLATES_SCHEME = expand(
 BLOCK_SCHEME = expand(
     id=Spec(types=str_, required=True, item_scheme=None),
     label=str_,
-    category=(list, str_),
+    category=str_,
     flags=(list, str_),
 
     parameters=Spec(types=list, required=False, item_scheme=PARAM_SCHEME),
diff --git a/grc/core/utils/__init__.py b/grc/core/utils/__init__.py
index 660eb594a5..f2ac986fb4 100644
--- a/grc/core/utils/__init__.py
+++ b/grc/core/utils/__init__.py
@@ -17,5 +17,17 @@
 
 from __future__ import absolute_import
 
+import six
+
 from . import epy_block_io, expr_utils, extract_docs, flow_graph_complexity
 from .hide_bokeh_gui_options_if_not_installed import hide_bokeh_gui_options_if_not_installed
+
+
+def to_list(value):
+    if not value:
+        return []
+    elif isinstance(value, six.string_types):
+        return [value]
+    else:
+        return list(value)
+
diff --git a/grc/core/utils/descriptors/evaluated.py b/grc/core/utils/descriptors/evaluated.py
index 313cee5b96..e8bce6e6ed 100644
--- a/grc/core/utils/descriptors/evaluated.py
+++ b/grc/core/utils/descriptors/evaluated.py
@@ -15,6 +15,10 @@
 # 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 absolute_import
+
+import six
+
 
 class Evaluated(object):
     def __init__(self, expected_type, default, name=None):
@@ -62,7 +66,7 @@ class Evaluated(object):
     def __set__(self, instance, value):
         attribs = instance.__dict__
         value = value or self.default
-        if isinstance(value, str) and value.startswith('${') and value.endswith('}'):
+        if isinstance(value, six.text_type) and value.startswith('${') and value.endswith('}'):
             attribs[self.name_raw] = value[2:-1].strip()
         else:
             attribs[self.name] = type(self.default)(value)
diff --git a/grc/gui/Platform.py b/grc/gui/Platform.py
index 2a38bc619e..8eb79f3459 100644
--- a/grc/gui/Platform.py
+++ b/grc/gui/Platform.py
@@ -63,8 +63,8 @@ class Platform(CorePlatform):
     FlowGraph = canvas.FlowGraph
     Connection = canvas.Connection
 
-    def new_block_class(self, block_id, **data):
-        cls = CorePlatform.new_block_class(self, block_id, **data)
+    def new_block_class(self, **data):
+        cls = CorePlatform.new_block_class(self, **data)
         return canvas.Block.make_cls_with_base(cls)
 
     block_classes_build_in = {key: canvas.Block.make_cls_with_base(cls)
-- 
cgit v1.2.3


From fbc1627340ea3ed4bedc3424d5a2ec3736e66b4b Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <sebastian.koslowski@gmail.com>
Date: Wed, 8 Nov 2017 19:05:19 +0100
Subject: grc: move port and param init to block builder

---
 grc/core/blocks/_build.py | 76 ++++++++++++++++++++++++++++++++++++++++----
 grc/core/blocks/_flags.py | 16 +++++-----
 grc/core/blocks/block.py  | 81 ++++++++---------------------------------------
 3 files changed, 91 insertions(+), 82 deletions(-)

(limited to 'grc/core/blocks/_build.py')

diff --git a/grc/core/blocks/_build.py b/grc/core/blocks/_build.py
index ce3496d9c4..6db06040cf 100644
--- a/grc/core/blocks/_build.py
+++ b/grc/core/blocks/_build.py
@@ -17,8 +17,13 @@
 
 from __future__ import absolute_import
 
+import collections
+import itertools
 import re
 
+from ..Constants import ADVANCED_PARAM_TAB
+from ..utils import to_list
+
 from .block import Block
 from ._flags import Flags
 from ._templates import MakoTemplates
@@ -35,17 +40,18 @@ def build(id, label='', category='', flags='', documentation='',
     cls.label = label or block_id.title()
     cls.category = [cat.strip() for cat in category.split('/') if cat.strip()]
 
-    cls.flags = Flags(flags)
+    cls.flags = Flags(to_list(flags))
     if re.match(r'options$|variable|virtual', block_id):
-        cls.flags += Flags.NOT_DSP + Flags.DISABLE_BYPASS
+        cls.flags.set(Flags.NOT_DSP, Flags.DISABLE_BYPASS)
 
     cls.documentation = {'': documentation.strip('\n\t ').replace('\\\n', '')}
 
-    cls.asserts = [_single_mako_expr(a, block_id) for a in (asserts or [])]
+    cls.asserts = [_single_mako_expr(a, block_id) for a in to_list(asserts)]
 
-    cls.parameters_data = parameters or []
-    cls.inputs_data = inputs or []
-    cls.outputs_data = outputs or []
+    cls.inputs_data = _build_ports(inputs, 'sink') if inputs else []
+    cls.outputs_data = _build_ports(outputs, 'source') if outputs else []
+    cls.parameters_data = _build_params(parameters or [],
+                                        bool(cls.inputs_data), bool(cls.outputs_data), cls.flags)
     cls.extra_data = kwargs
 
     templates = templates or {}
@@ -62,6 +68,64 @@ def build(id, label='', category='', flags='', documentation='',
     return cls
 
 
+def _build_ports(ports_raw, direction):
+    ports = []
+    port_ids = set()
+    stream_port_ids = itertools.count()
+
+    for i, port_params in enumerate(ports_raw):
+        port = port_params.copy()
+        port['direction'] = direction
+
+        port_id = port.setdefault('id', str(next(stream_port_ids)))
+        if port_id in port_ids:
+            raise Exception('Port id "{}" already exists in {}s'.format(port_id, direction))
+        port_ids.add(port_id)
+
+        ports.append(port)
+    return ports
+
+
+def _build_params(params_raw, have_inputs, have_outputs, flags):
+    params = []
+
+    def add_param(**data):
+        params.append(data)
+
+    add_param(id='id', name='ID', dtype='id', hide='part')
+
+    if not flags.not_dsp:
+        add_param(id='alias', name='Block Alias', dtype='string',
+                  hide='part', category=ADVANCED_PARAM_TAB)
+
+        if have_outputs or have_inputs:
+            add_param(id='affinity', name='Core Affinity', dtype='int_vector',
+                      hide='part', category=ADVANCED_PARAM_TAB)
+
+        if have_outputs:
+            add_param(id='minoutbuf', name='Min Output Buffer', dtype='int',
+                      hide='part', value='0', category=ADVANCED_PARAM_TAB)
+            add_param(id='maxoutbuf', name='Max Output Buffer', dtype='int',
+                      hide='part', value='0', category=ADVANCED_PARAM_TAB)
+
+    base_params_n = {}
+    for param_data in params_raw:
+        param_id = param_data['id']
+        if param_id in params:
+            raise Exception('Param id "{}" is not unique'.format(param_id))
+
+        base_key = param_data.get('base_key', None)
+        param_data_ext = base_params_n.get(base_key, {}).copy()
+        param_data_ext.update(param_data)
+
+        add_param(**param_data_ext)
+        base_params_n[param_id] = param_data_ext
+
+    add_param(id='comment', name='Comment', dtype='_multiline', hide='part',
+              value='', category=ADVANCED_PARAM_TAB)
+    return params
+
+
 def _single_mako_expr(value, block_id):
     if not value:
         return None
diff --git a/grc/core/blocks/_flags.py b/grc/core/blocks/_flags.py
index ffea2ad569..bbedd6a2d7 100644
--- a/grc/core/blocks/_flags.py
+++ b/grc/core/blocks/_flags.py
@@ -17,10 +17,8 @@
 
 from __future__ import absolute_import
 
-import six
 
-
-class Flags(six.text_type):
+class Flags(object):
 
     THROTTLE = 'throttle'
     DISABLE_BYPASS = 'disable_bypass'
@@ -28,12 +26,14 @@ class Flags(six.text_type):
     DEPRECATED = 'deprecated'
     NOT_DSP = 'not_dsp'
 
+    def __init__(self, flags):
+        self.data = set(flags)
+
     def __getattr__(self, item):
         return item in self
 
-    def __add__(self, other):
-        if not isinstance(other, six.string_types):
-            return NotImplemented
-        return self.__class__(str(self) + other)
+    def __contains__(self, item):
+        return item in self.data
 
-    __iadd__ = __add__
+    def set(self, *flags):
+        self.data.update(flags)
diff --git a/grc/core/blocks/block.py b/grc/core/blocks/block.py
index adc046936d..3a3de43fce 100644
--- a/grc/core/blocks/block.py
+++ b/grc/core/blocks/block.py
@@ -29,7 +29,6 @@ from six.moves import range
 from ._templates import MakoTemplates
 from ._flags import Flags
 
-from ..Constants import ADVANCED_PARAM_TAB
 from ..base import Element
 from ..utils.descriptors import lazy_property
 
@@ -63,82 +62,28 @@ class Block(Element):
     outputs_data = []
 
     extra_data = {}
+    loaded_from = '(unknown)'
 
-    # region Init
     def __init__(self, parent):
         """Make a new block from nested data."""
         super(Block, self).__init__(parent)
-        self.params = self._init_params()
-        self.sinks = self._init_ports(self.inputs_data, direction='sink')
-        self.sources = self._init_ports(self.outputs_data, direction='source')
-
-        self.active_sources = []  # on rewrite
-        self.active_sinks = []  # on rewrite
-
-        self.states = {'state': True}
-
-    def _init_params(self):
-        is_dsp_block = not self.flags.not_dsp
-        has_inputs = bool(self.inputs_data)
-        has_outputs = bool(self.outputs_data)
-
-        params = collections.OrderedDict()
         param_factory = self.parent_platform.make_param
-
-        def add_param(id, **kwargs):
-            params[id] = param_factory(self, id=id, **kwargs)
-
-        add_param(id='id', name='ID', dtype='id',
-                  hide='none' if (self.key == 'options' or self.is_variable) else 'part')
-
-        if is_dsp_block:
-            add_param(id='alias', name='Block Alias', dtype='string',
-                      hide='part', category=ADVANCED_PARAM_TAB)
-
-            if has_outputs or has_inputs:
-                add_param(id='affinity', name='Core Affinity', dtype='int_vector',
-                          hide='part', category=ADVANCED_PARAM_TAB)
-
-            if has_outputs:
-                add_param(id='minoutbuf', name='Min Output Buffer', dtype='int',
-                          hide='part', value='0', category=ADVANCED_PARAM_TAB)
-                add_param(id='maxoutbuf', name='Max Output Buffer', dtype='int',
-                          hide='part', value='0', category=ADVANCED_PARAM_TAB)
-
-        base_params_n = {}
-        for param_data in self.parameters_data:
-            param_id = param_data['id']
-            if param_id in params:
-                raise Exception('Param id "{}" is not unique'.format(param_id))
-
-            base_key = param_data.get('base_key', None)
-            param_data_ext = base_params_n.get(base_key, {}).copy()
-            param_data_ext.update(param_data)
-
-            add_param(**param_data_ext)
-            base_params_n[param_id] = param_data_ext
-
-        add_param(id='comment', name='Comment', dtype='_multiline', hide='part',
-                  value='', category=ADVANCED_PARAM_TAB)
-        return params
-
-    def _init_ports(self, ports_n, direction):
-        ports = []
         port_factory = self.parent_platform.make_port
-        port_ids = set()
 
-        stream_port_ids = itertools.count()
+        self.params = collections.OrderedDict(
+            (data['id'], param_factory(parent=self, **data))
+            for data in self.parameters_data
+        )
+        if self.key == 'options' or self.is_variable:
+            self.params['id'].hide = 'part'
 
-        for i, port_data in enumerate(ports_n):
-            port_id = port_data.setdefault('id', str(next(stream_port_ids)))
-            if port_id in port_ids:
-                raise Exception('Port id "{}" already exists in {}s'.format(port_id, direction))
-            port_ids.add(port_id)
+        self.sinks = [port_factory(parent=self, **params) for params in self.inputs_data]
+        self.sources = [port_factory(parent=self, **params) for params in self.outputs_data]
 
-            port = port_factory(parent=self, direction=direction, **port_data)
-            ports.append(port)
-        return ports
-    # endregion
+        self.active_sources = []  # on rewrite
+        self.active_sinks = []  # on rewrite
+
+        self.states = {'state': True}
 
     # region Rewrite_and_Validation
     def rewrite(self):
-- 
cgit v1.2.3