summaryrefslogtreecommitdiff
path: root/grc/converter/main.py
diff options
context:
space:
mode:
Diffstat (limited to 'grc/converter/main.py')
-rw-r--r--grc/converter/main.py163
1 files changed, 163 insertions, 0 deletions
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