summaryrefslogtreecommitdiff
path: root/grc/core/FlowGraph.py
diff options
context:
space:
mode:
Diffstat (limited to 'grc/core/FlowGraph.py')
-rw-r--r--grc/core/FlowGraph.py289
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