diff options
Diffstat (limited to 'grc/core/FlowGraph.py')
-rw-r--r-- | grc/core/FlowGraph.py | 289 |
1 files changed, 112 insertions, 177 deletions
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py index bf5bf6d93e..3ad95eb207 100644 --- a/grc/core/FlowGraph.py +++ b/grc/core/FlowGraph.py @@ -17,24 +17,17 @@ from __future__ import absolute_import, print_function -import imp -import time -import re -from operator import methodcaller import collections +import imp +import itertools import sys +from operator import methodcaller, attrgetter -from . import Messages +from . import Messages, blocks from .Constants import FLOW_GRAPH_FILE_FORMAT_VERSION -from .Element import Element -from .utils import expr_utils, shlex - -_parameter_matcher = re.compile('^(parameter)$') -_monitors_searcher = re.compile('(ctrlport_monitor)') -_bussink_searcher = re.compile('^(bus_sink)$') -_bussrc_searcher = re.compile('^(bus_source)$') -_bus_struct_sink_searcher = re.compile('^(bus_structure_sink)$') -_bus_struct_src_searcher = re.compile('^(bus_structure_source)$') +from .base import Element +from .utils import expr_utils +from .utils.backports import shlex class FlowGraph(Element): @@ -52,11 +45,10 @@ class FlowGraph(Element): the flow graph object """ Element.__init__(self, parent) - self._timestamp = time.ctime() - self._options_block = self.parent_platform.get_new_block(self, 'options') + self._options_block = self.parent_platform.make_block(self, 'options') self.blocks = [self._options_block] - self.connections = [] + self.connections = set() self._eval_cache = {} self.namespace = {} @@ -66,15 +58,14 @@ class FlowGraph(Element): def __str__(self): return 'FlowGraph - {}({})'.format(self.get_option('title'), self.get_option('id')) - def get_imports(self): + def imports(self): """ Get a set of all import statements in this flow graph namespace. Returns: - a set of import statements + a list of import statements """ - imports = sum([block.get_imports() for block in self.iter_enabled_blocks()], []) - return sorted(set(imports)) + return [block.templates.render('imports') for block in self.iter_enabled_blocks()] def get_variables(self): """ @@ -85,7 +76,7 @@ class FlowGraph(Element): a sorted list of variable blocks in order of dependency (indep -> dep) """ variables = [block for block in self.iter_enabled_blocks() if block.is_variable] - return expr_utils.sort_objects(variables, methodcaller('get_id'), methodcaller('get_var_make')) + return expr_utils.sort_objects(variables, attrgetter('name'), methodcaller('get_var_make')) def get_parameters(self): """ @@ -94,47 +85,21 @@ class FlowGraph(Element): Returns: a list of parameterized variables """ - parameters = [b for b in self.iter_enabled_blocks() if _parameter_matcher.match(b.key)] + parameters = [b for b in self.iter_enabled_blocks() if b.key == 'parameter'] return parameters def get_monitors(self): """ Get a list of all ControlPort monitors """ - monitors = [b for b in self.iter_enabled_blocks() if _monitors_searcher.search(b.key)] + monitors = [b for b in self.iter_enabled_blocks() if 'ctrlport_monitor' in b.key] return monitors def get_python_modules(self): """Iterate over custom code block ID and Source""" for block in self.iter_enabled_blocks(): if block.key == 'epy_module': - yield block.get_id(), block.get_param('source_code').get_value() - - def get_bussink(self): - bussink = [b for b in self.get_enabled_blocks() if _bussink_searcher.search(b.key)] - - for i in bussink: - for j in i.params.values(): - if j.name == 'On/Off' and j.get_value() == 'on': - return True - return False - - def get_bussrc(self): - bussrc = [b for b in self.get_enabled_blocks() if _bussrc_searcher.search(b.key)] - - for i in bussrc: - for j in i.params.values(): - if j.name == 'On/Off' and j.get_value() == 'on': - return True - return False - - def get_bus_structure_sink(self): - bussink = [b for b in self.get_enabled_blocks() if _bus_struct_sink_searcher.search(b.key)] - return bussink - - def get_bus_structure_src(self): - bussrc = [b for b in self.get_enabled_blocks() if _bus_struct_src_searcher.search(b.key)] - return bussrc + yield block.name, block.params[1].get_value() def iter_enabled_blocks(self): """ @@ -180,7 +145,7 @@ class FlowGraph(Element): Returns: the value held by that param """ - return self._options_block.get_param(key).get_evaluated() + return self._options_block.params[key].get_evaluated() def get_run_command(self, file_path, split=False): run_command = self.get_option('run_command') @@ -195,16 +160,19 @@ class FlowGraph(Element): ############################################## # Access Elements ############################################## - def get_block(self, id): + def get_block(self, name): for block in self.blocks: - if block.get_id() == id: + if block.name == name: return block - raise KeyError('No block with ID {!r}'.format(id)) + raise KeyError('No block with name {!r}'.format(name)) def get_elements(self): - return self.blocks + self.connections + elements = list(self.blocks) + elements.extend(self.connections) + return elements - get_children = get_elements + def children(self): + return itertools.chain(self.blocks, self.connections) def rewrite(self): """ @@ -216,7 +184,7 @@ class FlowGraph(Element): def renew_namespace(self): namespace = {} # Load imports - for expr in self.get_imports(): + for expr in self.imports(): try: exec(expr, namespace) except: @@ -232,19 +200,19 @@ class FlowGraph(Element): # Load parameters np = {} # params don't know each other - for parameter in self.get_parameters(): + for parameter_block in self.get_parameters(): try: - value = eval(parameter.get_param('value').to_code(), namespace) - np[parameter.get_id()] = value + value = eval(parameter_block.params['value'].to_code(), namespace) + np[parameter_block.name] = value except: pass namespace.update(np) # Merge param namespace # Load variables - for variable in self.get_variables(): + for variable_block in self.get_variables(): try: - value = eval(variable.get_var_value(), namespace) - namespace[variable.get_id()] = value + value = eval(variable_block.value, namespace, variable_block.namespace) + namespace[variable_block.name] = value except: pass @@ -252,41 +220,37 @@ class FlowGraph(Element): self._eval_cache.clear() self.namespace.update(namespace) - def evaluate(self, expr): + def evaluate(self, expr, namespace=None, local_namespace=None): """ Evaluate the expression. - - Args: - expr: the string expression - @throw Exception bad expression - - Returns: - the evaluated data """ # Evaluate if not expr: raise Exception('Cannot evaluate empty statement.') - return self._eval_cache.setdefault(expr, eval(expr, self.namespace)) + if namespace is not None: + return eval(expr, namespace, local_namespace) + else: + return self._eval_cache.setdefault(expr, eval(expr, self.namespace)) ############################################## # Add/remove stuff ############################################## - def new_block(self, key, **kwargs): + def new_block(self, block_id, **kwargs): """ Get a new block of the specified key. Add the block to the list of elements. Args: - key: the block key + block_id: the block key Returns: the new block or None if not found """ - if key == 'options': + if block_id == 'options': return self._options_block try: - block = self.parent_platform.get_new_block(self, key, **kwargs) + block = self.parent_platform.make_block(self, block_id, **kwargs) self.blocks.append(block) except KeyError: block = None @@ -304,12 +268,17 @@ class FlowGraph(Element): Returns: the new connection """ - connection = self.parent_platform.Connection( - parent=self, porta=porta, portb=portb) - self.connections.append(connection) + parent=self, source=porta, sink=portb) + self.connections.add(connection) return connection + def disconnect(self, *ports): + to_be_removed = [con for con in self.connections + if any(port in con for port in ports)] + for con in to_be_removed: + self.remove_element(con) + def remove_element(self, element): """ Remove the element from the list of elements. @@ -321,21 +290,14 @@ class FlowGraph(Element): return if element.is_port: - # Found a port, set to parent signal block - element = element.parent + element = element.parent_block # remove parent block if element in self.blocks: # Remove block, remove all involved connections - for port in element.get_ports(): - for connection in port.get_connections(): - self.remove_element(connection) + self.disconnect(*element.ports()) self.blocks.remove(element) elif element in self.connections: - if element.is_bus(): - for port in element.source_port.get_associated_ports(): - for connection in port.get_connections(): - self.remove_element(connection) self.connections.remove(element) ############################################## @@ -349,70 +311,61 @@ class FlowGraph(Element): Returns: a nested data odict """ - # sort blocks and connections for nicer diffs - blocks = sorted(self.blocks, key=lambda b: ( - b.key != 'options', # options to the front - not b.key.startswith('variable'), # then vars - str(b) - )) - connections = sorted(self.connections, key=str) - n = collections.OrderedDict() - n['timestamp'] = self._timestamp - n['block'] = [b.export_data() for b in blocks] - n['connection'] = [c.export_data() for c in connections] - instructions = collections.OrderedDict() - instructions['created'] = '.'.join(self.parent.config.version_parts) - instructions['format'] = FLOW_GRAPH_FILE_FORMAT_VERSION - return {'flow_graph': n, '_instructions': instructions} - - def import_data(self, n): + def block_order(b): + return not b.key.startswith('variable'), b.name # todo: vars still first ?!? + + data = collections.OrderedDict() + data['options'] = self._options_block.export_data() + data['blocks'] = [b.export_data() for b in sorted(self.blocks, key=block_order) + if b is not self._options_block] + data['connections'] = sorted(c.export_data() for c in self.connections) + data['metadata'] = {'file_format': FLOW_GRAPH_FILE_FORMAT_VERSION} + return data + + def _build_depending_hier_block(self, block_id): + # we're before the initial fg update(), so no evaluated values! + # --> use raw value instead + path_param = self._options_block.params['hier_block_src_path'] + file_path = self.parent_platform.find_file_in_paths( + filename=block_id + '.grc', + paths=path_param.get_value(), + cwd=self.grc_file_path + ) + if file_path: # grc file found. load and get block + self.parent_platform.load_and_generate_flow_graph(file_path, hier_only=True) + return self.new_block(block_id) # can be None + + def import_data(self, data): """ Import blocks and connections into this flow graph. Clear this flow graph of all previous blocks and connections. Any blocks or connections in error will be ignored. Args: - n: the nested data odict + data: the nested data odict """ # Remove previous elements del self.blocks[:] - del self.connections[:] - # set file format - try: - instructions = n.get('_instructions', {}) - file_format = int(instructions.get('format', '0')) or _guess_file_format_1(n) - except: - file_format = 0 + self.connections.clear() - fg_n = n and n.get('flow_graph', {}) # use blank data if none provided - self._timestamp = fg_n.get('timestamp', time.ctime()) + file_format = data['metadata']['file_format'] # build the blocks + self._options_block.import_data(name='', **data.get('options', {})) self.blocks.append(self._options_block) - for block_n in fg_n.get('block', []): - key = block_n['key'] - block = self.new_block(key) - - if not block: - # we're before the initial fg update(), so no evaluated values! - # --> use raw value instead - path_param = self._options_block.get_param('hier_block_src_path') - file_path = self.parent_platform.find_file_in_paths( - filename=key + '.grc', - paths=path_param.get_value(), - cwd=self.grc_file_path - ) - if file_path: # grc file found. load and get block - self.parent_platform.load_and_generate_flow_graph(file_path, hier_only=True) - block = self.new_block(key) # can be None - - if not block: # looks like this block key cannot be found - # create a dummy block instead - block = self.new_block('_dummy', missing_key=key, - params_n=block_n.get('param', [])) - print('Block key "%s" not found' % key) - - block.import_data(block_n) + + for block_data in data.get('blocks', []): + block_id = block_data['id'] + block = ( + self.new_block(block_id) or + self._build_depending_hier_block(block_id) or + self.new_block(block_id='_dummy', missing_block_id=block_id, **block_data) + ) + + if isinstance(block, blocks.DummyBlock): + print('Block id "%s" not found' % block_id) + + block.import_data(**block_data) self.rewrite() # evaluate stuff like nports before adding connections @@ -420,9 +373,9 @@ class FlowGraph(Element): def verify_and_get_port(key, block, dir): ports = block.sinks if dir == 'sink' else block.sources for port in ports: - if key == port.key: + if key == port.key or key + '0' == port.key: break - if not key.isdigit() and port.get_type() == '' and key == port.name: + if not key.isdigit() and port.dtype == '' and key == port.name: break else: if block.is_dummy_block: @@ -431,34 +384,32 @@ class FlowGraph(Element): raise LookupError('%s key %r not in %s block keys' % (dir, key, dir)) return port - errors = False - for connection_n in fg_n.get('connection', []): - # get the block ids and port keys - source_block_id = connection_n.get('source_block_id') - sink_block_id = connection_n.get('sink_block_id') - source_key = connection_n.get('source_key') - sink_key = connection_n.get('sink_key') + had_connect_errors = False + _blocks = {block.name: block for block in self.blocks} + for src_blk_id, src_port_id, snk_blk_id, snk_port_id in data.get('connections', []): try: - source_block = self.get_block(source_block_id) - sink_block = self.get_block(sink_block_id) + source_block = _blocks[src_blk_id] + sink_block = _blocks[snk_blk_id] # fix old, numeric message ports keys if file_format < 1: - source_key, sink_key = _update_old_message_port_keys( - source_key, sink_key, source_block, sink_block) + src_port_id, snk_port_id = _update_old_message_port_keys( + src_port_id, snk_port_id, source_block, sink_block) # build the connection - source_port = verify_and_get_port(source_key, source_block, 'source') - sink_port = verify_and_get_port(sink_key, sink_block, 'sink') + source_port = verify_and_get_port(src_port_id, source_block, 'source') + sink_port = verify_and_get_port(snk_port_id, sink_block, 'sink') + self.connect(source_port, sink_port) - except LookupError as e: + + except (KeyError, LookupError) as e: Messages.send_error_load( 'Connection between {}({}) and {}({}) could not be made.\n\t{}'.format( - source_block_id, source_key, sink_block_id, sink_key, e)) - errors = True + src_blk_id, src_port_id, snk_blk_id, snk_port_id, e)) + had_connect_errors = True self.rewrite() # global rewrite - return errors + return had_connect_errors def _update_old_message_port_keys(source_key, sink_key, source_block, sink_block): @@ -475,27 +426,11 @@ def _update_old_message_port_keys(source_key, sink_key, source_block, sink_block message port. """ try: - # get ports using the "old way" (assuming liner indexed keys) + # get ports using the "old way" (assuming linear indexed keys) source_port = source_block.sources[int(source_key)] sink_port = sink_block.sinks[int(sink_key)] - if source_port.get_type() == "message" and sink_port.get_type() == "message": + if source_port.dtype == "message" and sink_port.dtype == "message": source_key, sink_key = source_port.key, sink_port.key except (ValueError, IndexError): pass return source_key, sink_key # do nothing - - -def _guess_file_format_1(n): - """ - Try to guess the file format for flow-graph files without version tag - """ - try: - has_non_numeric_message_keys = any(not ( - connection_n.get('source_key', '').isdigit() and - connection_n.get('sink_key', '').isdigit() - ) for connection_n in n.get('flow_graph', []).get('connection', [])) - if has_non_numeric_message_keys: - return 1 - except: - pass - return 0 |