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/converter/main.py | 163 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 163 insertions(+)
 create mode 100644 grc/converter/main.py

(limited to 'grc/converter/main.py')

diff --git a/grc/converter/main.py b/grc/converter/main.py
new file mode 100644
index 0000000000..f979cc0281
--- /dev/null
+++ b/grc/converter/main.py
@@ -0,0 +1,163 @@
+# 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
+
+from codecs import open
+import json
+import logging
+import os
+
+import six
+
+from . import block_tree, block
+
+path = os.path
+logger = logging.getLogger(__name__)
+
+excludes = [
+    'qtgui_',
+    '.grc_gnuradio/',
+    'blks2',
+    'wxgui',
+    'epy_block.xml',
+    'virtual_sink.xml',
+    'virtual_source.xml',
+    'dummy.xml',
+    'variable_struct.xml',  # todo: re-implement as class
+    'digital_constellation',  # todo: fix template
+]
+
+
+class Converter(object):
+
+    def __init__(self, search_path, output_dir='~/.cache/grc_gnuradio'):
+        self.search_path = search_path
+        self.output_dir = output_dir
+
+        self._force = False
+
+        converter_module_path = path.dirname(__file__)
+        self._converter_mtime = max(path.getmtime(path.join(converter_module_path, module))
+                                    for module in os.listdir(converter_module_path)
+                                    if not module.endswith('flow_graph.py'))
+
+        self.cache_file = os.path.join(self.output_dir, '_cache.json')
+        self.cache = {}
+
+    def run(self, force=False):
+        self._force = force
+
+        try:
+            with open(self.cache_file, encoding='utf-8') as cache_file:
+                self.cache = byteify(json.load(cache_file))
+        except (IOError, ValueError):
+            self.cache = {}
+            self._force = True
+        need_cache_write = False
+
+        if not path.isdir(self.output_dir):
+            os.makedirs(self.output_dir)
+        if self._force:
+            for name in os.listdir(self.output_dir):
+                os.remove(os.path.join(self.output_dir, name))
+
+        for xml_file in self.iter_files_in_block_path():
+            if xml_file.endswith("block_tree.xml"):
+                changed = self.load_category_tree_xml(xml_file)
+            elif xml_file.endswith('domain.xml'):
+                continue
+            else:
+                changed = self.load_block_xml(xml_file)
+
+            if changed:
+                need_cache_write = True
+
+        if need_cache_write:
+            logger.info('Saving %d entries to json cache', len(self.cache))
+            with open(self.cache_file, 'w', encoding='utf-8') as cache_file:
+                json.dump(self.cache, cache_file)
+
+    def load_block_xml(self, xml_file):
+        """Load block description from xml file"""
+        if any(part in xml_file for part in excludes):
+            return
+
+        block_id_from_xml = path.basename(xml_file)[:-4]
+        yml_file = path.join(self.output_dir, block_id_from_xml + '.block.yml')
+
+        if not self.needs_conversion(xml_file, yml_file):
+            return  # yml file up-to-date
+
+        logger.info('Converting block %s', path.basename(xml_file))
+        data = block.from_xml(xml_file)
+        if block_id_from_xml != data['id']:
+            logger.warning('block_id and filename differ')
+        self.cache[yml_file] = data
+
+        with open(yml_file, 'w', encoding='utf-8') as yml_file:
+            block.dump(data, yml_file)
+        return True
+
+    def load_category_tree_xml(self, xml_file):
+        """Validate and parse category tree file and add it to list"""
+        module_name = path.basename(xml_file)[:-len('block_tree.xml')].rstrip('._-')
+        yml_file = path.join(self.output_dir, module_name + '.tree.yml')
+
+        if not self.needs_conversion(xml_file, yml_file):
+            return  # yml file up-to-date
+
+        logger.info('Converting module %s', path.basename(xml_file))
+        data = block_tree.from_xml(xml_file)
+        self.cache[yml_file] = data
+
+        with open(yml_file, 'w', encoding='utf-8') as yml_file:
+            block_tree.dump(data, yml_file)
+        return True
+
+    def needs_conversion(self, source, destination):
+        """Check if source has already been converted and destination is up-to-date"""
+        if self._force or not path.exists(destination):
+            return True
+        xml_time = path.getmtime(source)
+        yml_time = path.getmtime(destination)
+
+        return yml_time < xml_time or yml_time < self._converter_mtime
+
+    def iter_files_in_block_path(self, suffix='.xml'):
+        """Iterator for block descriptions and category trees"""
+        for block_path in self.search_path:
+            if path.isfile(block_path):
+                yield block_path
+            elif path.isdir(block_path):
+                for root, _, files in os.walk(block_path, followlinks=True):
+                    for name in files:
+                        if name.endswith(suffix):
+                            yield path.join(root, name)
+            else:
+                logger.warning('Invalid entry in search path: {}'.format(block_path))
+
+
+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, unicode):
+        return data.encode('utf-8')
+    else:
+        return data
-- 
cgit v1.2.3