summaryrefslogtreecommitdiff
path: root/grc/core
diff options
context:
space:
mode:
Diffstat (limited to 'grc/core')
-rw-r--r--grc/core/Block.py1182
-rw-r--r--grc/core/CMakeLists.txt35
-rw-r--r--grc/core/Config.py29
-rw-r--r--grc/core/Connection.py146
-rw-r--r--grc/core/Constants.py96
-rw-r--r--grc/core/Element.py124
-rw-r--r--grc/core/Element.pyi35
-rw-r--r--grc/core/FlowGraph.py257
-rw-r--r--grc/core/Messages.py7
-rw-r--r--grc/core/Param.py427
-rw-r--r--grc/core/ParseXML.py67
-rw-r--r--grc/core/Platform.py160
-rw-r--r--grc/core/Port.py261
-rw-r--r--grc/core/generator/CMakeLists.txt30
-rw-r--r--grc/core/generator/FlowGraphProxy.py28
-rw-r--r--grc/core/generator/Generator.py96
-rw-r--r--grc/core/generator/__init__.py3
-rw-r--r--grc/core/generator/flow_graph.tmpl32
-rw-r--r--grc/core/utils/CMakeLists.txt25
-rw-r--r--grc/core/utils/__init__.py10
-rw-r--r--grc/core/utils/_complexity.py (renamed from grc/core/utils/complexity.py)15
-rw-r--r--grc/core/utils/epy_block_io.py11
-rw-r--r--grc/core/utils/expr_utils.py20
-rw-r--r--grc/core/utils/extract_docs.py28
-rw-r--r--grc/core/utils/odict.py115
25 files changed, 1392 insertions, 1847 deletions
diff --git a/grc/core/Block.py b/grc/core/Block.py
index fba9371963..8350828092 100644
--- a/grc/core/Block.py
+++ b/grc/core/Block.py
@@ -17,226 +17,202 @@ 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 collections
import itertools
+import ast
+
+import six
+from six.moves import map, range
from Cheetah.Template import Template
-from .utils import epy_block_io, odict
+from . import utils
+
from . Constants import (
BLOCK_FLAG_NEED_QT_GUI,
- ADVANCED_PARAM_TAB, DEFAULT_PARAM_TAB,
+ ADVANCED_PARAM_TAB,
BLOCK_FLAG_THROTTLE, BLOCK_FLAG_DISABLE_BYPASS,
BLOCK_FLAG_DEPRECATED,
- BLOCK_ENABLED, BLOCK_BYPASSED, BLOCK_DISABLED
)
-from . Element import Element
+from . Element import Element, lazy_property
-def _get_keys(lst):
- return [elem.get_key() for elem in lst]
-
-
-def _get_elem(lst, key):
- try:
- return lst[_get_keys(lst).index(key)]
- except ValueError:
- raise ValueError('Key "{}" not found in {}.'.format(key, _get_keys(lst)))
+def _get_elem(iterable, key):
+ items = list(iterable)
+ for item in items:
+ if item.key == key:
+ return item
+ return ValueError('Key "{}" not found in {}.'.format(key, items))
class Block(Element):
is_block = True
- def __init__(self, flow_graph, n):
- """
- Make a new block from nested data.
+ STATE_LABELS = ['disabled', 'enabled', 'bypassed']
- Args:
- flow: graph the parent element
- n: the nested odict
+ def __init__(self, parent, key, name, **n):
+ """Make a new block from nested data."""
+ super(Block, self).__init__(parent)
+
+ self.key = key
+ self.name = name
+ self.category = [cat.strip() for cat in n.get('category', '').split('/') if cat.strip()]
+ self.flags = n.get('flags', '')
+ self._doc = n.get('doc', '').strip('\n').replace('\\\n', '')
- Returns:
- block a new block
- """
- # Grab the data
- self._doc = (n.find('doc') or '').strip('\n').replace('\\\n', '')
- self._imports = map(lambda i: i.strip(), n.findall('import'))
- self._make = n.find('make')
- self._var_make = n.find('var_make')
- self._checks = n.findall('check')
- self._callbacks = n.findall('callback')
- self._bus_structure_source = n.find('bus_structure_source') or ''
- self._bus_structure_sink = n.find('bus_structure_sink') or ''
- self.port_counters = [itertools.count(), itertools.count()]
-
- # Build the block
- Element.__init__(self, flow_graph)
-
- # Grab the data
- params = n.findall('param')
- sources = n.findall('source')
- sinks = n.findall('sink')
- self._name = n.find('name')
- self._key = n.find('key')
- category = (n.find('category') or '').split('/')
- self.category = [cat.strip() for cat in category if cat.strip()]
- self._flags = n.find('flags') or ''
# Backwards compatibility
- if n.find('throttle') and BLOCK_FLAG_THROTTLE not in self._flags:
- self._flags += BLOCK_FLAG_THROTTLE
- self._grc_source = n.find('grc_source') or ''
- self._block_wrapper_path = n.find('block_wrapper_path')
- self._bussify_sink = n.find('bus_sink')
- self._bussify_source = n.find('bus_source')
- self._var_value = n.find('var_value') or '$value'
+ if n.get('throttle') and BLOCK_FLAG_THROTTLE not in self.flags:
+ self.flags += BLOCK_FLAG_THROTTLE
- # Get list of param tabs
- n_tabs = n.find('param_tab_order') or None
- self._param_tab_labels = n_tabs.findall('tab') if n_tabs is not None else [DEFAULT_PARAM_TAB]
-
- # Create the param objects
- self._params = list()
-
- # Add the id param
- self.get_params().append(self.get_parent().get_parent().Param(
- block=self,
- n=odict({
- 'name': 'ID',
- 'key': 'id',
- 'type': 'id',
- })
- ))
- self.get_params().append(self.get_parent().get_parent().Param(
- block=self,
- n=odict({
- 'name': 'Enabled',
- 'key': '_enabled',
- 'type': 'raw',
- 'value': 'True',
- 'hide': 'all',
- })
- ))
- for param in itertools.imap(lambda n: self.get_parent().get_parent().Param(block=self, n=n), params):
- key = param.get_key()
- # Test against repeated keys
- if key in self.get_param_keys():
- raise Exception('Key "{}" already exists in params'.format(key))
- # Store the param
- self.get_params().append(param)
- # Create the source objects
- self._sources = list()
- for source in map(lambda n: self.get_parent().get_parent().Port(block=self, n=n, dir='source'), sources):
- key = source.get_key()
- # Test against repeated keys
- if key in self.get_source_keys():
- raise Exception('Key "{}" already exists in sources'.format(key))
- # Store the port
- self.get_sources().append(source)
- self.back_ofthe_bus(self.get_sources())
- # Create the sink objects
- self._sinks = list()
- for sink in map(lambda n: self.get_parent().get_parent().Port(block=self, n=n, dir='sink'), sinks):
- key = sink.get_key()
- # Test against repeated keys
- if key in self.get_sink_keys():
- raise Exception('Key "{}" already exists in sinks'.format(key))
- # Store the port
- self.get_sinks().append(sink)
- self.back_ofthe_bus(self.get_sinks())
- self.current_bus_structure = {'source': '', 'sink': ''}
+ self._imports = [i.strip() for i in n.get('import', [])]
+ self._make = n.get('make')
+ self._var_make = n.get('var_make')
+ self._var_value = n.get('var_value', '$value')
+ self._checks = n.get('check', [])
+ self._callbacks = n.get('callback', [])
+
+ self._grc_source = n.get('grc_source', '')
+ self.block_wrapper_path = n.get('block_wrapper_path')
# Virtual source/sink and pad source/sink blocks are
# indistinguishable from normal GR blocks. Make explicit
# checks for them here since they have no work function or
# buffers to manage.
- self.is_virtual_or_pad = self._key in (
+ self.is_virtual_or_pad = self.key in (
"virtual_source", "virtual_sink", "pad_source", "pad_sink")
- self.is_variable = self._key.startswith('variable')
- self.is_import = (self._key == 'import')
+ self.is_variable = self.key.startswith('variable')
+ self.is_import = (self.key == 'import')
# Disable blocks that are virtual/pads or variables
if self.is_virtual_or_pad or self.is_variable:
- self._flags += BLOCK_FLAG_DISABLE_BYPASS
-
- if not (self.is_virtual_or_pad or self.is_variable or self._key == 'options'):
- self.get_params().append(self.get_parent().get_parent().Param(
- block=self,
- n=odict({'name': 'Block Alias',
- 'key': 'alias',
- 'type': 'string',
- 'hide': 'part',
- 'tab': ADVANCED_PARAM_TAB
- })
- ))
-
- if (len(sources) or len(sinks)) and not self.is_virtual_or_pad:
- self.get_params().append(self.get_parent().get_parent().Param(
- block=self,
- n=odict({'name': 'Core Affinity',
- 'key': 'affinity',
- 'type': 'int_vector',
- 'hide': 'part',
- 'tab': ADVANCED_PARAM_TAB
- })
- ))
- if len(sources) and not self.is_virtual_or_pad:
- self.get_params().append(self.get_parent().get_parent().Param(
- block=self,
- n=odict({'name': 'Min Output Buffer',
- 'key': 'minoutbuf',
- 'type': 'int',
- 'hide': 'part',
- 'value': '0',
- 'tab': ADVANCED_PARAM_TAB
- })
- ))
- self.get_params().append(self.get_parent().get_parent().Param(
- block=self,
- n=odict({'name': 'Max Output Buffer',
- 'key': 'maxoutbuf',
- 'type': 'int',
- 'hide': 'part',
- 'value': '0',
- 'tab': ADVANCED_PARAM_TAB
- })
- ))
-
- self.get_params().append(self.get_parent().get_parent().Param(
- block=self,
- n=odict({'name': 'Comment',
- 'key': 'comment',
- 'type': '_multiline',
- 'hide': 'part',
- 'value': '',
- 'tab': ADVANCED_PARAM_TAB
- })
- ))
+ self.flags += BLOCK_FLAG_DISABLE_BYPASS
- self._epy_source_hash = -1 # for epy blocks
- self._epy_reload_error = None
+ params_n = n.get('param', [])
+ sources_n = n.get('source', [])
+ sinks_n = n.get('sink', [])
- if self._bussify_sink:
- self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
- if self._bussify_source:
- self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
+ # Get list of param tabs
+ self.params = collections.OrderedDict()
+ self._init_params(
+ params_n=params_n,
+ has_sinks=len(sinks_n),
+ has_sources=len(sources_n)
+ )
- def get_bus_structure(self, direction):
- if direction == 'source':
- bus_structure = self._bus_structure_source
- else:
- bus_structure = self._bus_structure_sink
+ self.sources = self._init_ports(sources_n, direction='source')
+ self.sinks = self._init_ports(sinks_n, direction='sink')
+ self.active_sources = [] # on rewrite
+ self.active_sinks = [] # on rewrite
- bus_structure = self.resolve_dependencies(bus_structure)
+ self.states = {'_enabled': True}
- if not bus_structure:
- return '' # TODO: Don't like empty strings. should change this to None eventually
+ self._init_bus_ports(n)
- try:
- clean_bus_structure = self.get_parent().evaluate(bus_structure)
- return clean_bus_structure
- except:
- return ''
+ def _init_params(self, params_n, has_sources, has_sinks):
+ param_factory = self.parent_platform.get_new_param
+
+ def add_param(key, **kwargs):
+ self.params[key] = param_factory(self, key=key, **kwargs)
+
+ add_param(key='id', name='ID', type='id')
+
+ if not (self.is_virtual_or_pad or self.is_variable or self.key == 'options'):
+ add_param(key='alias', name='Block Alias', type='string',
+ hide='part', tab=ADVANCED_PARAM_TAB)
+
+ if not self.is_virtual_or_pad and (has_sources or has_sinks):
+ add_param(key='affinity', name='Core Affinity', type='int_vector',
+ hide='part', tab=ADVANCED_PARAM_TAB)
+
+ if not self.is_virtual_or_pad and has_sources:
+ add_param(key='minoutbuf', name='Min Output Buffer', type='int',
+ hide='part', value='0', tab=ADVANCED_PARAM_TAB)
+ add_param(key='maxoutbuf', name='Max Output Buffer', type='int',
+ hide='part', value='0', tab=ADVANCED_PARAM_TAB)
+
+ base_params_n = {}
+ for param_n in params_n:
+ key = param_n['key']
+ if key in self.params:
+ raise Exception('Key "{}" already exists in params'.format(key))
+
+ base_key = param_n.get('base_key', None)
+ param_n_ext = base_params_n.get(base_key, {}).copy()
+ param_n_ext.update(param_n)
+ self.params[key] = param_factory(self, **param_n_ext)
+ base_params_n[key] = param_n_ext
+
+ add_param(key='comment', name='Comment', type='_multiline', hide='part',
+ value='', tab=ADVANCED_PARAM_TAB)
+
+ def _init_ports(self, ports_n, direction):
+ port_factory = self.parent_platform.get_new_port
+ ports = []
+ port_keys = set()
+ stream_port_keys = itertools.count()
+ for i, port_n in enumerate(ports_n):
+ port_n.setdefault('key', str(next(stream_port_keys)))
+ port = port_factory(parent=self, direction=direction, **port_n)
+ key = port.key
+ if key in port_keys:
+ raise Exception('Key "{}" already exists in {}'.format(key, direction))
+ port_keys.add(key)
+ ports.append(port)
+ return ports
+
+ ##############################################
+ # validation and rewrite
+ ##############################################
+ def rewrite(self):
+ """
+ Add and remove ports to adjust for the nports.
+ """
+ Element.rewrite(self)
+
+ def rekey(ports):
+ """Renumber non-message/message ports"""
+ domain_specific_port_index = collections.defaultdict(int)
+ for port in [p for p in ports if p.key.isdigit()]:
+ domain = port.domain
+ port.key = str(domain_specific_port_index[domain])
+ domain_specific_port_index[domain] += 1
+
+ # Adjust nports
+ for ports in (self.sources, self.sinks):
+ self._rewrite_nports(ports)
+ self.back_ofthe_bus(ports)
+ rekey(ports)
+
+ self._rewrite_bus_ports()
+
+ # disconnect hidden ports
+ for port in itertools.chain(self.sources, self.sinks):
+ if port.get_hide():
+ for connection in port.get_connections():
+ self.parent_flowgraph.remove_element(connection)
+
+
+ self.active_sources = [p for p in self.get_sources_gui() if not p.get_hide()]
+ self.active_sinks = [p for p in self.get_sinks_gui() if not p.get_hide()]
+
+ def _rewrite_nports(self, ports):
+ for port in ports:
+ if port.is_clone: # Not a master port and no left-over clones
+ continue
+ nports = port.get_nports() or 1
+ for clone in port.clones[nports-1:]:
+ # Remove excess connections
+ for connection in clone.get_connections():
+ self.parent_flowgraph.remove_element(connection)
+ port.remove_clone(clone)
+ ports.remove(clone)
+ # Add more cloned ports
+ for j in range(1 + len(port.clones), nports):
+ clone = port.add_clone()
+ ports.insert(ports.index(port) + j, clone)
def validate(self):
"""
@@ -245,114 +221,95 @@ class Block(Element):
Evaluate the checks: each check must evaluate to True.
"""
Element.validate(self)
- # Evaluate the checks
+ self._run_checks()
+ self._validate_generate_mode_compat()
+ self._validate_var_value()
+
+ def _run_checks(self):
+ """Evaluate the checks"""
for check in self._checks:
check_res = self.resolve_dependencies(check)
try:
- if not self.get_parent().evaluate(check_res):
+ if not self.parent.evaluate(check_res):
self.add_error_message('Check "{}" failed.'.format(check))
except:
self.add_error_message('Check "{}" did not evaluate.'.format(check))
- # For variables check the value (only if var_value is used
- if self.is_variable and self._var_value != '$value':
- value = self._var_value
- try:
- value = self.get_var_value()
- self.get_parent().evaluate(value)
- except Exception as err:
- self.add_error_message('Value "{}" cannot be evaluated:\n{}'.format(value, err))
-
- # check if this is a GUI block and matches the selected generate option
- current_generate_option = self.get_parent().get_option('generate_options')
+ def _validate_generate_mode_compat(self):
+ """check if this is a GUI block and matches the selected generate option"""
+ current_generate_option = self.parent.get_option('generate_options')
def check_generate_mode(label, flag, valid_options):
block_requires_mode = (
- flag in self.get_flags() or
- self.get_name().upper().startswith(label)
+ flag in self.flags or self.name.upper().startswith(label)
)
if block_requires_mode and current_generate_option not in valid_options:
self.add_error_message("Can't generate this block in mode: {} ".format(
repr(current_generate_option)))
check_generate_mode('QT GUI', BLOCK_FLAG_NEED_QT_GUI, ('qt_gui', 'hb_qt_gui'))
- if self._epy_reload_error:
- self.get_param('_source_code').add_error_message(str(self._epy_reload_error))
-
- def rewrite(self):
- """
- Add and remove ports to adjust for the nports.
- """
- Element.rewrite(self)
- # Check and run any custom rewrite function for this block
- getattr(self, 'rewrite_' + self._key, lambda: None)()
-
- # Adjust nports, disconnect hidden ports
- for ports in (self.get_sources(), self.get_sinks()):
- for i, master_port in enumerate(ports):
- nports = master_port.get_nports() or 1
- num_ports = 1 + len(master_port.get_clones())
- if master_port.get_hide():
- for connection in master_port.get_connections():
- self.get_parent().remove_element(connection)
- if not nports and num_ports == 1: # Not a master port and no left-over clones
- continue
- # Remove excess cloned ports
- for port in master_port.get_clones()[nports-1:]:
- # Remove excess connections
- for connection in port.get_connections():
- self.get_parent().remove_element(connection)
- master_port.remove_clone(port)
- ports.remove(port)
- # Add more cloned ports
- for j in range(num_ports, nports):
- port = master_port.add_clone()
- ports.insert(ports.index(master_port) + j, port)
- self.back_ofthe_bus(ports)
- # Renumber non-message/message ports
- domain_specific_port_index = collections.defaultdict(int)
- for port in filter(lambda p: p.get_key().isdigit(), ports):
- domain = port.get_domain()
- port._key = str(domain_specific_port_index[domain])
- domain_specific_port_index[domain] += 1
+ def _validate_var_value(self):
+ """or variables check the value (only if var_value is used)"""
+ if self.is_variable and self._var_value != '$value':
+ value = self._var_value
+ try:
+ value = self.get_var_value()
+ self.parent.evaluate(value)
+ except Exception as err:
+ self.add_error_message('Value "{}" cannot be evaluated:\n{}'.format(value, err))
- def port_controller_modify(self, direction):
- """
- Change the port controller.
+ ##############################################
+ # props
+ ##############################################
- Args:
- direction: +1 or -1
+ @lazy_property
+ def is_throtteling(self):
+ return BLOCK_FLAG_THROTTLE in self.flags
- Returns:
- true for change
- """
- changed = False
- # Concat the nports string from the private nports settings of all ports
- nports_str = ' '.join([port._nports for port in self.get_ports()])
- # Modify all params whose keys appear in the nports string
- for param in self.get_params():
- if param.is_enum() or param.get_key() not in nports_str:
- continue
- # Try to increment the port controller by direction
- try:
- value = param.get_evaluated()
- value = value + direction
- if 0 < value:
- param.set_value(value)
- changed = True
- except:
- pass
- return changed
+ @lazy_property
+ def is_deprecated(self):
+ return BLOCK_FLAG_DEPRECATED in self.flags
- def get_doc(self):
- platform = self.get_parent().get_parent()
- documentation = platform.block_docstrings.get(self._key, {})
+ @property
+ def documentation(self):
+ documentation = self.parent_platform.block_docstrings.get(self.key, {})
from_xml = self._doc.strip()
if from_xml:
documentation[''] = from_xml
return documentation
+ @property
+ def comment(self):
+ return self.params['comment'].get_value()
+
+ @property
+ def state(self):
+ """Gets the block's current state."""
+ try:
+ return self.STATE_LABELS[int(self.states['_enabled'])]
+ except ValueError:
+ return 'enabled'
+
+ @state.setter
+ def state(self, value):
+ """Sets the state for the block."""
+ try:
+ encoded = self.STATE_LABELS.index(value)
+ except ValueError:
+ encoded = 1
+ self.states['_enabled'] = encoded
+
+ # Enable/Disable Aliases
+ @property
+ def enabled(self):
+ """Get the enabled state of the block"""
+ return self.state != 'disabled'
+
+ ##############################################
+ # Getters (old)
+ ##############################################
+
def get_imports(self, raw=False):
"""
Resolve all import statements.
@@ -365,7 +322,8 @@ class Block(Element):
"""
if raw:
return self._imports
- return filter(lambda i: i, sum(map(lambda i: self.resolve_dependencies(i).split('\n'), self._imports), []))
+ return [i for i in sum((self.resolve_dependencies(i).split('\n')
+ for i in self._imports), []) if i]
def get_make(self, raw=False):
if raw:
@@ -390,176 +348,20 @@ class Block(Element):
if 'self.' in callback:
return callback
return 'self.{}.{}'.format(self.get_id(), callback)
- return map(make_callback, self._callbacks)
+ return [make_callback(c) for c in self._callbacks]
def is_virtual_sink(self):
- return self.get_key() == 'virtual_sink'
+ return self.key == 'virtual_sink'
def is_virtual_source(self):
- return self.get_key() == 'virtual_source'
-
- ###########################################################################
- # Custom rewrite functions
- ###########################################################################
-
- def rewrite_epy_block(self):
- flowgraph = self.get_parent()
- platform = flowgraph.get_parent()
- param_blk = self.get_param('_io_cache')
- param_src = self.get_param('_source_code')
-
- src = param_src.get_value()
- src_hash = hash((self.get_id(), src))
- if src_hash == self._epy_source_hash:
- return
-
- try:
- blk_io = epy_block_io.extract(src)
-
- except Exception as e:
- self._epy_reload_error = ValueError(str(e))
- try: # Load last working block io
- blk_io_args = eval(param_blk.get_value())
- if len(blk_io_args) == 6:
- blk_io_args += ([],) # add empty callbacks
- blk_io = epy_block_io.BlockIO(*blk_io_args)
- except Exception:
- return
- else:
- self._epy_reload_error = None # Clear previous errors
- param_blk.set_value(repr(tuple(blk_io)))
-
- # print "Rewriting embedded python block {!r}".format(self.get_id())
-
- self._epy_source_hash = src_hash
- self._name = blk_io.name or blk_io.cls
- self._doc = blk_io.doc
- self._imports[0] = 'import ' + self.get_id()
- self._make = '{0}.{1}({2})'.format(self.get_id(), blk_io.cls, ', '.join(
- '{0}=${{ {0} }}'.format(key) for key, _ in blk_io.params))
- self._callbacks = ['{0} = ${{ {0} }}'.format(attr) for attr in blk_io.callbacks]
-
- params = {}
- for param in list(self._params):
- if hasattr(param, '__epy_param__'):
- params[param.get_key()] = param
- self._params.remove(param)
-
- for key, value in blk_io.params:
- try:
- param = params[key]
- param.set_default(value)
- except KeyError: # need to make a new param
- name = key.replace('_', ' ').title()
- n = odict(dict(name=name, key=key, type='raw', value=value))
- param = platform.Param(block=self, n=n)
- setattr(param, '__epy_param__', True)
- self._params.append(param)
-
- def update_ports(label, ports, port_specs, direction):
- ports_to_remove = list(ports)
- iter_ports = iter(ports)
- ports_new = []
- port_current = next(iter_ports, None)
- for key, port_type, vlen in port_specs:
- reuse_port = (
- port_current is not None and
- port_current.get_type() == port_type and
- port_current.get_vlen() == vlen and
- (key.isdigit() or port_current.get_key() == key)
- )
- if reuse_port:
- ports_to_remove.remove(port_current)
- port, port_current = port_current, next(iter_ports, None)
- else:
- n = odict(dict(name=label + str(key), type=port_type, key=key))
- if port_type == 'message':
- n['name'] = key
- n['optional'] = '1'
- if vlen > 1:
- n['vlen'] = str(vlen)
- port = platform.Port(block=self, n=n, dir=direction)
- ports_new.append(port)
- # replace old port list with new one
- del ports[:]
- ports.extend(ports_new)
- # remove excess port connections
- for port in ports_to_remove:
- for connection in port.get_connections():
- flowgraph.remove_element(connection)
-
- update_ports('in', self.get_sinks(), blk_io.sinks, 'sink')
- update_ports('out', self.get_sources(), blk_io.sources, 'source')
- self.rewrite()
-
- def back_ofthe_bus(self, portlist):
- portlist.sort(key=lambda p: p._type == 'bus')
-
- def filter_bus_port(self, ports):
- buslist = [p for p in ports if p._type == 'bus']
- return buslist or ports
-
- # Main functions to get and set the block state
- # Also kept get_enabled and set_enabled to keep compatibility
- def get_state(self):
- """
- Gets the block's current state.
-
- Returns:
- ENABLED - 0
- BYPASSED - 1
- DISABLED - 2
- """
- try:
- return int(eval(self.get_param('_enabled').get_value()))
- except:
- return BLOCK_ENABLED
-
- def set_state(self, state):
- """
- Sets the state for the block.
-
- Args:
- ENABLED - 0
- BYPASSED - 1
- DISABLED - 2
- """
- if state in [BLOCK_ENABLED, BLOCK_BYPASSED, BLOCK_DISABLED]:
- self.get_param('_enabled').set_value(str(state))
- else:
- self.get_param('_enabled').set_value(str(BLOCK_ENABLED))
-
- # Enable/Disable Aliases
- def get_enabled(self):
- """
- Get the enabled state of the block.
-
- Returns:
- true for enabled
- """
- return not (self.get_state() == BLOCK_DISABLED)
-
- def set_enabled(self, enabled):
- """
- Set the enabled state of the block.
-
- Args:
- enabled: true for enabled
-
- Returns:
- True if block changed state
- """
- old_state = self.get_state()
- new_state = BLOCK_ENABLED if enabled else BLOCK_DISABLED
- self.set_state(new_state)
- return old_state != new_state
+ return self.key == 'virtual_source'
# Block bypassing
def get_bypassed(self):
"""
Check if the block is bypassed
"""
- return self.get_state() == BLOCK_BYPASSED
+ return self.state == 'bypassed'
def set_bypassed(self):
"""
@@ -568,8 +370,8 @@ class Block(Element):
Returns:
True if block chagnes state
"""
- if self.get_state() != BLOCK_BYPASSED and self.can_bypass():
- self.set_state(BLOCK_BYPASSED)
+ if self.state != 'bypassed' and self.can_bypass():
+ self.state = 'bypassed'
return True
return False
@@ -577,112 +379,60 @@ class Block(Element):
""" Check the number of sinks and sources and see if this block can be bypassed """
# Check to make sure this is a single path block
# Could possibly support 1 to many blocks
- if len(self.get_sources()) != 1 or len(self.get_sinks()) != 1:
+ if len(self.sources) != 1 or len(self.sinks) != 1:
return False
- if not (self.get_sources()[0].get_type() == self.get_sinks()[0].get_type()):
+ if not (self.sources[0].get_type() == self.sinks[0].get_type()):
return False
- if self.bypass_disabled():
+ if BLOCK_FLAG_DISABLE_BYPASS in self.flags:
return False
return True
def __str__(self):
- return 'Block - {} - {}({})'.format(self.get_id(), self.get_name(), self.get_key())
+ return 'Block - {} - {}({})'.format(self.get_id(), self.name, self.key)
def get_id(self):
- return self.get_param('id').get_value()
-
- def get_name(self):
- return self._name
-
- def get_key(self):
- return self._key
+ return self.params['id'].get_value()
def get_ports(self):
- return self.get_sources() + self.get_sinks()
+ return self.sources + self.sinks
def get_ports_gui(self):
- return self.filter_bus_port(self.get_sources()) + self.filter_bus_port(self.get_sinks())
+ return self.get_sources_gui() + self.get_sinks_gui()
+
+ def active_ports(self):
+ return itertools.chain(self.active_sources, self.active_sinks)
def get_children(self):
- return self.get_ports() + self.get_params()
+ return self.get_ports() + self.params.values()
def get_children_gui(self):
- return self.get_ports_gui() + self.get_params()
-
- def get_block_wrapper_path(self):
- return self._block_wrapper_path
-
- def get_comment(self):
- return self.get_param('comment').get_value()
-
- def get_flags(self):
- return self._flags
-
- def throtteling(self):
- return BLOCK_FLAG_THROTTLE in self._flags
-
- def bypass_disabled(self):
- return BLOCK_FLAG_DISABLE_BYPASS in self._flags
-
- @property
- def is_deprecated(self):
- return BLOCK_FLAG_DEPRECATED in self._flags
+ return self.get_ports_gui() + self.params.values()
##############################################
- # Access Params
+ # Access
##############################################
- def get_param_tab_labels(self):
- return self._param_tab_labels
-
- def get_param_keys(self):
- return _get_keys(self._params)
def get_param(self, key):
- return _get_elem(self._params, key)
-
- def get_params(self):
- return self._params
-
- def has_param(self, key):
- try:
- _get_elem(self._params, key)
- return True
- except:
- return False
-
- ##############################################
- # Access Sinks
- ##############################################
- def get_sink_keys(self):
- return _get_keys(self._sinks)
+ return self.params[key]
def get_sink(self, key):
- return _get_elem(self._sinks, key)
-
- def get_sinks(self):
- return self._sinks
+ return _get_elem(self.sinks, key)
def get_sinks_gui(self):
- return self.filter_bus_port(self.get_sinks())
-
- ##############################################
- # Access Sources
- ##############################################
- def get_source_keys(self):
- return _get_keys(self._sources)
+ return self.filter_bus_port(self.sinks)
def get_source(self, key):
- return _get_elem(self._sources, key)
-
- def get_sources(self):
- return self._sources
+ return _get_elem(self.sources, key)
def get_sources_gui(self):
- return self.filter_bus_port(self.get_sources())
+ return self.filter_bus_port(self.sources)
def get_connections(self):
- return sum([port.get_connections() for port in self.get_ports()], [])
+ return sum((port.get_connections() for port in self.get_ports()), [])
+ ##############################################
+ # Resolve
+ ##############################################
def resolve_dependencies(self, tmpl):
"""
Resolve a paramater dependency with cheetah templates.
@@ -696,103 +446,14 @@ class Block(Element):
tmpl = str(tmpl)
if '$' not in tmpl:
return tmpl
- n = dict((param.get_key(), param.template_arg)
- for param in self.get_params()) # TODO: cache that
+ # TODO: cache that
+ n = {key: param.template_arg for key, param in six.iteritems(self.params)}
try:
return str(Template(tmpl, n))
except Exception as err:
return "Template error: {}\n {}".format(tmpl, err)
##############################################
- # Controller Modify
- ##############################################
- def type_controller_modify(self, direction):
- """
- Change the type controller.
-
- Args:
- direction: +1 or -1
-
- Returns:
- true for change
- """
- changed = False
- type_param = None
- for param in filter(lambda p: p.is_enum(), self.get_params()):
- children = self.get_ports() + self.get_params()
- # Priority to the type controller
- if param.get_key() in ' '.join(map(lambda p: p._type, children)): type_param = param
- # Use param if type param is unset
- if not type_param:
- type_param = param
- if type_param:
- # Try to increment the enum by direction
- try:
- keys = type_param.get_option_keys()
- old_index = keys.index(type_param.get_value())
- new_index = (old_index + direction + len(keys)) % len(keys)
- type_param.set_value(keys[new_index])
- changed = True
- except:
- pass
- return changed
-
- def form_bus_structure(self, direc):
- if direc == 'source':
- get_p = self.get_sources
- get_p_gui = self.get_sources_gui
- bus_structure = self.get_bus_structure('source')
- else:
- get_p = self.get_sinks
- get_p_gui = self.get_sinks_gui
- bus_structure = self.get_bus_structure('sink')
-
- struct = [range(len(get_p()))]
- if True in map(lambda a: isinstance(a.get_nports(), int), get_p()):
- structlet = []
- last = 0
- for j in [i.get_nports() for i in get_p() if isinstance(i.get_nports(), int)]:
- structlet.extend(map(lambda a: a+last, range(j)))
- last = structlet[-1] + 1
- struct = [structlet]
- if bus_structure:
-
- struct = bus_structure
-
- self.current_bus_structure[direc] = struct
- return struct
-
- def bussify(self, n, direc):
- if direc == 'source':
- get_p = self.get_sources
- get_p_gui = self.get_sources_gui
- bus_structure = self.get_bus_structure('source')
- else:
- get_p = self.get_sinks
- get_p_gui = self.get_sinks_gui
- bus_structure = self.get_bus_structure('sink')
-
- for elt in get_p():
- for connect in elt.get_connections():
- self.get_parent().remove_element(connect)
-
- if ('bus' not in map(lambda a: a.get_type(), get_p())) and len(get_p()) > 0:
- struct = self.form_bus_structure(direc)
- self.current_bus_structure[direc] = struct
- if get_p()[0].get_nports():
- n['nports'] = str(1)
-
- for i in range(len(struct)):
- n['key'] = str(len(get_p()))
- n = odict(n)
- port = self.get_parent().get_parent().Port(block=self, n=n, dir=direc)
- get_p().append(port)
- elif 'bus' in map(lambda a: a.get_type(), get_p()):
- for elt in get_p_gui():
- get_p().remove(elt)
- self.current_bus_structure[direc] = ''
-
- ##############################################
# Import/Export Methods
##############################################
def export_data(self):
@@ -802,17 +463,19 @@ class Block(Element):
Returns:
a nested data odict
"""
- n = odict()
- n['key'] = self.get_key()
- n['param'] = map(lambda p: p.export_data(), sorted(self.get_params(), key=str))
- if 'bus' in map(lambda a: a.get_type(), self.get_sinks()):
- n['bus_sink'] = str(1)
- if 'bus' in map(lambda a: a.get_type(), self.get_sources()):
- n['bus_source'] = str(1)
- return n
+ n = collections.OrderedDict()
+ n['key'] = self.key
+
+ params = (param.export_data() for param in six.itervalues(self.params))
+ states = (collections.OrderedDict([('key', key), ('value', repr(value))])
+ for key, value in six.iteritems(self.states))
+ n['param'] = sorted(itertools.chain(states, params), key=lambda p: p['key'])
- def get_hash(self):
- return hash(tuple(map(hash, self.get_params())))
+ if any('bus' in a.get_type() for a in self.sinks):
+ n['bus_sink'] = '1'
+ if any('bus' in a.get_type() for a in self.sources):
+ n['bus_source'] = '1'
+ return n
def import_data(self, n):
"""
@@ -826,27 +489,296 @@ class Block(Element):
Args:
n: the nested data odict
"""
- my_hash = 0
- while self.get_hash() != my_hash:
- params_n = n.findall('param')
- for param_n in params_n:
- key = param_n.find('key')
- value = param_n.find('value')
- # The key must exist in this block's params
- if key in self.get_param_keys():
- self.get_param(key).set_value(value)
+ param_data = {p['key']: p['value'] for p in n.get('param', [])}
+
+ for key in self.states:
+ try:
+ self.states[key] = ast.literal_eval(param_data.pop(key))
+ except (KeyError, SyntaxError, ValueError):
+ pass
+
+ def get_hash():
+ return hash(tuple(hash(v) for v in self.params.values()))
+
+ pre_rewrite_hash = -1
+ while pre_rewrite_hash != get_hash():
+ for key, value in six.iteritems(param_data):
+ try:
+ self.params[key].set_value(value)
+ except KeyError:
+ continue
# Store hash and call rewrite
- my_hash = self.get_hash()
+ pre_rewrite_hash = get_hash()
self.rewrite()
- bussinks = n.findall('bus_sink')
- if len(bussinks) > 0 and not self._bussify_sink:
- self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
- elif len(bussinks) > 0:
- self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
- self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
- bussrcs = n.findall('bus_source')
- if len(bussrcs) > 0 and not self._bussify_source:
- self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
- elif len(bussrcs) > 0:
- self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
- self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
+
+ self._import_bus_stuff(n)
+
+ ##############################################
+ # Bus ports stuff
+ ##############################################
+
+ def get_bus_structure(self, direction):
+ bus_structure = self.resolve_dependencies(self._bus_structure[direction])
+ if not bus_structure:
+ return
+ try:
+ return self.parent_flowgraph.evaluate(bus_structure)
+ except:
+ return
+
+ @staticmethod
+ def back_ofthe_bus(portlist):
+ portlist.sort(key=lambda p: p._type == 'bus')
+
+ @staticmethod
+ def filter_bus_port(ports):
+ buslist = [p for p in ports if p._type == 'bus']
+ return buslist or ports
+
+ def _import_bus_stuff(self, n):
+ bus_sinks = n.get('bus_sink', [])
+ if len(bus_sinks) > 0 and not self._bussify_sink:
+ self.bussify('sink')
+ elif len(bus_sinks) > 0:
+ self.bussify('sink')
+ self.bussify('sink')
+ bus_sources = n.get('bus_source', [])
+ if len(bus_sources) > 0 and not self._bussify_source:
+ self.bussify('source')
+ elif len(bus_sources) > 0:
+ self.bussify('source')
+ self.bussify('source')
+
+ def form_bus_structure(self, direc):
+ ports = self.sources if direc == 'source' else self.sinks
+ struct = self.get_bus_structure(direc)
+
+ if not struct:
+ struct = [list(range(len(ports)))]
+
+ elif any(isinstance(p.get_nports(), int) for p in ports):
+ last = 0
+ structlet = []
+ for port in ports:
+ nports = port.get_nports()
+ if not isinstance(nports, int):
+ continue
+ structlet.extend(a + last for a in range(nports))
+ last += nports
+ struct = [structlet]
+
+ self.current_bus_structure[direc] = struct
+ return struct
+
+ def bussify(self, direc):
+ ports = self.sources if direc == 'source' else self.sinks
+
+ for elt in ports:
+ for connect in elt.get_connections():
+ self.parent.remove_element(connect)
+
+ if ports and all('bus' != p.get_type() for p in ports):
+ struct = self.current_bus_structure[direc] = self.form_bus_structure(direc)
+ n = {'type': 'bus'}
+ if ports[0].get_nports():
+ n['nports'] = '1'
+
+ for i, structlet in enumerate(struct):
+ name = 'bus{}#{}'.format(i, len(structlet))
+ port = self.parent_platform.get_new_port(
+ self, direction=direc, key=str(len(ports)), name=name, **n)
+ ports.append(port)
+ elif any('bus' == p.get_type() for p in ports):
+ get_p_gui = self.get_sources_gui if direc == 'source' else self.get_sinks_gui
+ for elt in get_p_gui():
+ ports.remove(elt)
+ self.current_bus_structure[direc] = ''
+
+ def _init_bus_ports(self, n):
+ self.current_bus_structure = {'source': '', 'sink': ''}
+ self._bus_structure = {'source': n.get('bus_structure_source', ''),
+ 'sink': n.get('bus_structure_sink', '')}
+ self._bussify_sink = n.get('bus_sink')
+ self._bussify_source = n.get('bus_source')
+ if self._bussify_sink:
+ self.bussify('sink')
+ if self._bussify_source:
+ self.bussify('source')
+
+ def _rewrite_bus_ports(self):
+ return # fixme: probably broken
+
+ def doit(ports, ports_gui, direc):
+ if not self.current_bus_structure[direc]:
+ return
+
+ bus_structure = self.form_bus_structure(direc)
+ for port in ports_gui[len(bus_structure):]:
+ for connect in port.get_connections():
+ self.parent_flowgraph.remove_element(connect)
+ ports.remove(port)
+
+ port_factory = self.parent_platform.get_new_port
+
+ if len(ports_gui) < len(bus_structure):
+ for i in range(len(ports_gui), len(bus_structure)):
+ port = port_factory(self, direction=direc, key=str(1 + i),
+ name='bus', type='bus')
+ ports.append(port)
+
+ doit(self.sources, self.get_sources_gui(), 'source')
+ doit(self.sinks, self.get_sinks_gui(), 'sink')
+
+ if 'bus' in [a.get_type() for a in self.get_sources_gui()]:
+ for i in range(len(self.get_sources_gui())):
+ if not self.get_sources_gui()[i].get_connections():
+ continue
+ source = self.get_sources_gui()[i]
+ sink = []
+
+ for j in range(len(source.get_connections())):
+ sink.append(source.get_connections()[j].sink_port)
+ for elt in source.get_connections():
+ self.parent_flowgraph.remove_element(elt)
+ for j in sink:
+ self.parent_flowgraph.connect(source, j)
+
+
+class EPyBlock(Block):
+
+ def __init__(self, flow_graph, **n):
+ super(EPyBlock, self).__init__(flow_graph, **n)
+ self._epy_source_hash = -1 # for epy blocks
+ self._epy_reload_error = None
+
+ def rewrite(self):
+ Element.rewrite(self)
+
+ param_blk = self.params['_io_cache']
+ param_src = self.params['_source_code']
+
+ src = param_src.get_value()
+ src_hash = hash((self.get_id(), src))
+ if src_hash == self._epy_source_hash:
+ return
+
+ try:
+ blk_io = utils.epy_block_io.extract(src)
+
+ except Exception as e:
+ self._epy_reload_error = ValueError(str(e))
+ try: # Load last working block io
+ blk_io_args = eval(param_blk.get_value())
+ if len(blk_io_args) == 6:
+ blk_io_args += ([],) # add empty callbacks
+ blk_io = utils.epy_block_io.BlockIO(*blk_io_args)
+ except Exception:
+ return
+ else:
+ self._epy_reload_error = None # Clear previous errors
+ param_blk.set_value(repr(tuple(blk_io)))
+
+ # print "Rewriting embedded python block {!r}".format(self.get_id())
+
+ self._epy_source_hash = src_hash
+ self.name = blk_io.name or blk_io.cls
+ self._doc = blk_io.doc
+ self._imports[0] = 'import ' + self.get_id()
+ self._make = '{0}.{1}({2})'.format(self.get_id(), blk_io.cls, ', '.join(
+ '{0}=${{ {0} }}'.format(key) for key, _ in blk_io.params))
+ self._callbacks = ['{0} = ${{ {0} }}'.format(attr) for attr in blk_io.callbacks]
+ self._update_params(blk_io.params)
+ self._update_ports('in', self.sinks, blk_io.sinks, 'sink')
+ self._update_ports('out', self.sources, blk_io.sources, 'source')
+
+ super(EPyBlock, self).rewrite()
+
+ def _update_params(self, params_in_src):
+ param_factory = self.parent_platform.get_new_param
+ params = {}
+ for param in list(self.params):
+ if hasattr(param, '__epy_param__'):
+ params[param.key] = param
+ del self.params[param.key]
+
+ for key, value in params_in_src:
+ try:
+ param = params[key]
+ if param.default == param.value:
+ param.set_value(value)
+ param.default = str(value)
+ except KeyError: # need to make a new param
+ param = param_factory(
+ parent=self, key=key, type='raw', value=value,
+ name=key.replace('_', ' ').title(),
+ )
+ setattr(param, '__epy_param__', True)
+ self.params[key] = param
+
+ def _update_ports(self, label, ports, port_specs, direction):
+ port_factory = self.parent_platform.get_new_port
+ ports_to_remove = list(ports)
+ iter_ports = iter(ports)
+ ports_new = []
+ port_current = next(iter_ports, None)
+ for key, port_type, vlen in port_specs:
+ reuse_port = (
+ port_current is not None and
+ port_current.get_type() == port_type and
+ port_current.get_vlen() == vlen and
+ (key.isdigit() or port_current.key == key)
+ )
+ if reuse_port:
+ ports_to_remove.remove(port_current)
+ port, port_current = port_current, next(iter_ports, None)
+ else:
+ n = dict(name=label + str(key), type=port_type, key=key)
+ if port_type == 'message':
+ n['name'] = key
+ n['optional'] = '1'
+ if vlen > 1:
+ n['vlen'] = str(vlen)
+ port = port_factory(self, direction=direction, **n)
+ ports_new.append(port)
+ # replace old port list with new one
+ del ports[:]
+ ports.extend(ports_new)
+ # remove excess port connections
+ for port in ports_to_remove:
+ for connection in port.get_connections():
+ self.parent_flowgraph.remove_element(connection)
+
+ def validate(self):
+ super(EPyBlock, self).validate()
+ if self._epy_reload_error:
+ self.params['_source_code'].add_error_message(str(self._epy_reload_error))
+
+
+class DummyBlock(Block):
+
+ is_dummy_block = True
+ build_in_param_keys = 'id alias affinity minoutbuf maxoutbuf comment'
+
+ def __init__(self, parent, key, missing_key, params_n):
+ super(DummyBlock, self).__init__(parent=parent, key=missing_key, name='Missing Block')
+ param_factory = self.parent_platform.get_new_param
+ for param_n in params_n:
+ key = param_n['key']
+ self.params.setdefault(key, param_factory(self, key=key, name=key, type='string'))
+
+ def is_valid(self):
+ return False
+
+ @property
+ def enabled(self):
+ return False
+
+ def add_missing_port(self, key, dir):
+ port = self.parent_platform.get_new_port(
+ parent=self, direction=dir, key=key, name='?', type='',
+ )
+ if port.is_source:
+ self.sources.append(port)
+ else:
+ self.sinks.append(port)
+ return port
diff --git a/grc/core/CMakeLists.txt b/grc/core/CMakeLists.txt
deleted file mode 100644
index f340127873..0000000000
--- a/grc/core/CMakeLists.txt
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2011 Free Software Foundation, Inc.
-#
-# This file is part of GNU Radio
-#
-# GNU Radio 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 3, or (at your option)
-# any later version.
-#
-# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to
-# the Free Software Foundation, Inc., 51 Franklin Street,
-# Boston, MA 02110-1301, USA.
-
-file(GLOB py_files "*.py")
-
-GR_PYTHON_INSTALL(
- FILES ${py_files}
- DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/core
-)
-
-file(GLOB dtd_files "*.dtd")
-
-install(
- FILES ${dtd_files} default_flow_graph.grc
- DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/core
-)
-
-add_subdirectory(generator)
-add_subdirectory(utils)
diff --git a/grc/core/Config.py b/grc/core/Config.py
index 744ad06ba9..cc199a348f 100644
--- a/grc/core/Config.py
+++ b/grc/core/Config.py
@@ -17,6 +17,8 @@ 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 os
from os.path import expanduser, normpath, expandvars, exists
@@ -24,16 +26,14 @@ from . import Constants
class Config(object):
-
- key = 'grc'
name = 'GNU Radio Companion (no gui)'
license = __doc__.strip()
website = 'http://gnuradio.org'
hier_block_lib_dir = os.environ.get('GRC_HIER_PATH', Constants.DEFAULT_HIER_BLOCK_LIB_DIR)
- def __init__(self, prefs_file, version, version_parts=None, name=None):
- self.prefs = prefs_file
+ def __init__(self, version, version_parts=None, name=None, prefs=None):
+ self._gr_prefs = prefs if prefs else DummyPrefs()
self.version = version
self.version_parts = version_parts or version[1:].split('-', 1)[0].split('.')[:3]
if name:
@@ -46,8 +46,8 @@ class Config(object):
paths_sources = (
self.hier_block_lib_dir,
os.environ.get('GRC_BLOCKS_PATH', ''),
- self.prefs.get_string('grc', 'local_blocks_path', ''),
- self.prefs.get_string('grc', 'global_blocks_path', ''),
+ self._gr_prefs.get_string('grc', 'local_blocks_path', ''),
+ self._gr_prefs.get_string('grc', 'global_blocks_path', ''),
)
collected_paths = sum((paths.split(path_list_sep)
@@ -62,7 +62,22 @@ class Config(object):
def default_flow_graph(self):
user_default = (
os.environ.get('GRC_DEFAULT_FLOW_GRAPH') or
- self.prefs.get_string('grc', 'default_flow_graph', '') or
+ self._gr_prefs.get_string('grc', 'default_flow_graph', '') or
os.path.join(self.hier_block_lib_dir, 'default_flow_graph.grc')
)
return user_default if exists(user_default) else Constants.DEFAULT_FLOW_GRAPH
+
+
+class DummyPrefs(object):
+
+ def get_string(self, category, item, default):
+ return str(default)
+
+ def set_string(self, category, item, value):
+ pass
+
+ def get_long(self, category, item, default):
+ return int(default)
+
+ def save(self):
+ pass
diff --git a/grc/core/Connection.py b/grc/core/Connection.py
index c028d89ddc..066532149b 100644
--- a/grc/core/Connection.py
+++ b/grc/core/Connection.py
@@ -17,16 +17,21 @@ 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 collections
+
+from six.moves import range
+
from . import Constants
-from .Element import Element
-from .utils import odict
+from .Element import Element, lazy_property
class Connection(Element):
is_connection = True
- def __init__(self, flow_graph, porta, portb):
+ def __init__(self, parent, porta, portb):
"""
Make a new connection given the parent and 2 ports.
@@ -39,75 +44,87 @@ class Connection(Element):
Returns:
a new connection
"""
- Element.__init__(self, flow_graph)
+ Element.__init__(self, parent)
+
+ source, sink = self._get_sink_source(porta, portb)
+
+ self.source_port = source
+ self.sink_port = sink
+
+ # Ensure that this connection (source -> sink) is unique
+ if self in self.parent_flowgraph.connections:
+ raise LookupError('This connection between source and sink is not unique.')
+
+ if self.is_bus():
+ self._make_bus_connect()
+
+ def __eq__(self, other):
+ if not isinstance(other, self.__class__):
+ return NotImplemented
+ return self.source_port == other.source_port and self.sink_port == other.sink_port
+
+ @staticmethod
+ def _get_sink_source(porta, portb):
source = sink = None
# Separate the source and sink
for port in (porta, portb):
if port.is_source:
source = port
- else:
+ if port.is_sink:
sink = port
if not source:
raise ValueError('Connection could not isolate source')
if not sink:
raise ValueError('Connection could not isolate sink')
- busses = len(filter(lambda a: a.get_type() == 'bus', [source, sink])) % 2
- if not busses == 0:
- raise ValueError('busses must get with busses')
+ return source, sink
- if not len(source.get_associated_ports()) == len(sink.get_associated_ports()):
- raise ValueError('port connections must have same cardinality')
- # Ensure that this connection (source -> sink) is unique
- for connection in flow_graph.connections:
- if connection.get_source() is source and connection.get_sink() is sink:
- raise LookupError('This connection between source and sink is not unique.')
- self._source = source
- self._sink = sink
- if source.get_type() == 'bus':
-
- sources = source.get_associated_ports()
- sinks = sink.get_associated_ports()
-
- for i in range(len(sources)):
- try:
- flow_graph.connect(sources[i], sinks[i])
- except:
- pass
+ @lazy_property
+ def source_block(self):
+ return self.source_port.parent_block
+
+ @lazy_property
+ def sink_block(self):
+ return self.sink_port.parent_block
+
+ @property
+ def enabled(self):
+ """
+ Get the enabled state of this connection.
+
+ Returns:
+ true if source and sink blocks are enabled
+ """
+ return self.source_block.enabled and self.sink_block.enabled
def __str__(self):
return 'Connection (\n\t{}\n\t\t{}\n\t{}\n\t\t{}\n)'.format(
- self.get_source().get_parent(),
- self.get_source(),
- self.get_sink().get_parent(),
- self.get_sink(),
+ self.source_block, self.source_port, self.sink_block, self.sink_port,
)
def is_bus(self):
- return self.get_source().get_type() == self.get_sink().get_type() == 'bus'
+ return self.source_port.get_type() == 'bus'
def validate(self):
"""
Validate the connections.
The ports must match in io size.
"""
- """
- Validate the connections.
- The ports must match in type.
- """
Element.validate(self)
- platform = self.get_parent().get_parent()
- source_domain = self.get_source().get_domain()
- sink_domain = self.get_sink().get_domain()
+ platform = self.parent_platform
+
+ source_domain = self.source_port.domain
+ sink_domain = self.sink_port.domain
+
if (source_domain, sink_domain) not in platform.connection_templates:
self.add_error_message('No connection known for domains "{}", "{}"'.format(
- source_domain, sink_domain))
+ source_domain, sink_domain))
too_many_other_sinks = (
not platform.domains.get(source_domain, []).get('multiple_sinks', False) and
- len(self.get_source().get_enabled_connections()) > 1
+ len(self.source_port.get_enabled_connections()) > 1
)
too_many_other_sources = (
not platform.domains.get(sink_domain, []).get('multiple_sources', False) and
- len(self.get_sink().get_enabled_connections()) > 1
+ len(self.sink_port.get_enabled_connections()) > 1
)
if too_many_other_sinks:
self.add_error_message(
@@ -116,30 +133,11 @@ class Connection(Element):
self.add_error_message(
'Domain "{}" can have only one upstream block'.format(sink_domain))
- source_size = Constants.TYPE_TO_SIZEOF[self.get_source().get_type()] * self.get_source().get_vlen()
- sink_size = Constants.TYPE_TO_SIZEOF[self.get_sink().get_type()] * self.get_sink().get_vlen()
+ source_size = Constants.TYPE_TO_SIZEOF[self.source_port.get_type()] * self.source_port.get_vlen()
+ sink_size = Constants.TYPE_TO_SIZEOF[self.sink_port.get_type()] * self.sink_port.get_vlen()
if source_size != sink_size:
self.add_error_message('Source IO size "{}" does not match sink IO size "{}".'.format(source_size, sink_size))
- def get_enabled(self):
- """
- Get the enabled state of this connection.
-
- Returns:
- true if source and sink blocks are enabled
- """
- return self.get_source().get_parent().get_enabled() and \
- self.get_sink().get_parent().get_enabled()
-
- #############################
- # Access Ports
- #############################
- def get_sink(self):
- return self._sink
-
- def get_source(self):
- return self._source
-
##############################################
# Import/Export Methods
##############################################
@@ -150,9 +148,23 @@ class Connection(Element):
Returns:
a nested data odict
"""
- n = odict()
- n['source_block_id'] = self.get_source().get_parent().get_id()
- n['sink_block_id'] = self.get_sink().get_parent().get_id()
- n['source_key'] = self.get_source().get_key()
- n['sink_key'] = self.get_sink().get_key()
+ n = collections.OrderedDict()
+ n['source_block_id'] = self.source_block.get_id()
+ n['sink_block_id'] = self.sink_block.get_id()
+ n['source_key'] = self.source_port.key
+ n['sink_key'] = self.sink_port.key
return n
+
+ def _make_bus_connect(self):
+ source, sink = self.source_port, self.sink_port
+
+ if source.get_type() == sink.get_type() == 'bus':
+ raise ValueError('busses must get with busses')
+
+ sources = source.get_associated_ports()
+ sinks = sink.get_associated_ports()
+ if len(sources) != len(sinks):
+ raise ValueError('port connections must have same cardinality')
+
+ for ports in zip(sources, sinks):
+ self.parent_flowgraph.connect(*ports)
diff --git a/grc/core/Constants.py b/grc/core/Constants.py
index edd3442a94..caf301be60 100644
--- a/grc/core/Constants.py
+++ b/grc/core/Constants.py
@@ -17,10 +17,14 @@ 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 os
-import numpy
import stat
+import numpy
+import six
+
# Data files
DATA_DIR = os.path.dirname(__file__)
FLOW_GRAPH_DTD = os.path.join(DATA_DIR, 'flow_graph.dtd')
@@ -50,29 +54,35 @@ BLOCK_FLAG_DISABLE_BYPASS = 'disable_bypass'
BLOCK_FLAG_NEED_QT_GUI = 'need_qt_gui'
BLOCK_FLAG_DEPRECATED = 'deprecated'
-# Block States
-BLOCK_DISABLED = 0
-BLOCK_ENABLED = 1
-BLOCK_BYPASSED = 2
-
# File creation modes
TOP_BLOCK_FILE_MODE = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | \
stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH
HIER_BLOCK_FILE_MODE = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH
+PARAM_TYPE_NAMES = (
+ 'raw', 'enum',
+ 'complex', 'real', 'float', 'int',
+ 'complex_vector', 'real_vector', 'float_vector', 'int_vector',
+ 'hex', 'string', 'bool',
+ 'file_open', 'file_save', '_multiline', '_multiline_python_external',
+ 'id', 'stream_id',
+ 'gui_hint',
+ 'import',
+)
+
# Define types, native python + numpy
VECTOR_TYPES = (tuple, list, set, numpy.ndarray)
COMPLEX_TYPES = [complex, numpy.complex, numpy.complex64, numpy.complex128]
REAL_TYPES = [float, numpy.float, numpy.float32, numpy.float64]
-INT_TYPES = [int, long, numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.uint64,
+INT_TYPES = [int, numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.uint64,
numpy.uint, numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64]
# Cast to tuple for isinstance, concat subtypes
COMPLEX_TYPES = tuple(COMPLEX_TYPES + REAL_TYPES + INT_TYPES)
REAL_TYPES = tuple(REAL_TYPES + INT_TYPES)
INT_TYPES = tuple(INT_TYPES)
-# Updating colors. Using the standard color pallette from:
-# http://www.google.com/design/spec/style/color.html#color-color-palette
+# Updating colors. Using the standard color palette from:
+# http://www.google.com/design/spec/style/color.html#color-color-palette
# Most are based on the main, primary color standard. Some are within
# that color's spectrum when it was deemed necessary.
GRC_COLOR_BROWN = '#795548'
@@ -95,54 +105,32 @@ GRC_COLOR_GREY = '#BDBDBD'
GRC_COLOR_WHITE = '#FFFFFF'
CORE_TYPES = ( # name, key, sizeof, color
- ('Complex Float 64', 'fc64', 16, GRC_COLOR_BROWN),
- ('Complex Float 32', 'fc32', 8, GRC_COLOR_BLUE),
- ('Complex Integer 64', 'sc64', 16, GRC_COLOR_LIGHT_GREEN),
- ('Complex Integer 32', 'sc32', 8, GRC_COLOR_GREEN),
- ('Complex Integer 16', 'sc16', 4, GRC_COLOR_AMBER),
- ('Complex Integer 8', 'sc8', 2, GRC_COLOR_PURPLE),
- ('Float 64', 'f64', 8, GRC_COLOR_CYAN),
- ('Float 32', 'f32', 4, GRC_COLOR_ORANGE),
- ('Integer 64', 's64', 8, GRC_COLOR_LIME),
- ('Integer 32', 's32', 4, GRC_COLOR_TEAL),
- ('Integer 16', 's16', 2, GRC_COLOR_YELLOW),
- ('Integer 8', 's8', 1, GRC_COLOR_PURPLE_A400),
- ('Bits (unpacked byte)', 'bit', 1, GRC_COLOR_PURPLE_A100),
- ('Async Message', 'message', 0, GRC_COLOR_GREY),
- ('Bus Connection', 'bus', 0, GRC_COLOR_WHITE),
- ('Wildcard', '', 0, GRC_COLOR_WHITE),
+ ('Complex Float 64', 'fc64', 16, GRC_COLOR_BROWN),
+ ('Complex Float 32', 'fc32', 8, GRC_COLOR_BLUE),
+ ('Complex Integer 64', 'sc64', 16, GRC_COLOR_LIGHT_GREEN),
+ ('Complex Integer 32', 'sc32', 8, GRC_COLOR_GREEN),
+ ('Complex Integer 16', 'sc16', 4, GRC_COLOR_AMBER),
+ ('Complex Integer 8', 'sc8', 2, GRC_COLOR_PURPLE),
+ ('Float 64', 'f64', 8, GRC_COLOR_CYAN),
+ ('Float 32', 'f32', 4, GRC_COLOR_ORANGE),
+ ('Integer 64', 's64', 8, GRC_COLOR_LIME),
+ ('Integer 32', 's32', 4, GRC_COLOR_TEAL),
+ ('Integer 16', 's16', 2, GRC_COLOR_YELLOW),
+ ('Integer 8', 's8', 1, GRC_COLOR_PURPLE_A400),
+ ('Bits (unpacked byte)', 'bit', 1, GRC_COLOR_PURPLE_A100),
+ ('Async Message', 'message', 0, GRC_COLOR_GREY),
+ ('Bus Connection', 'bus', 0, GRC_COLOR_WHITE),
+ ('Wildcard', '', 0, GRC_COLOR_WHITE),
)
ALIAS_TYPES = {
'complex': (8, GRC_COLOR_BLUE),
- 'float': (4, GRC_COLOR_ORANGE),
- 'int': (4, GRC_COLOR_TEAL),
- 'short': (2, GRC_COLOR_YELLOW),
- 'byte': (1, GRC_COLOR_PURPLE_A400),
- 'bits': (1, GRC_COLOR_PURPLE_A100),
+ 'float': (4, GRC_COLOR_ORANGE),
+ 'int': (4, GRC_COLOR_TEAL),
+ 'short': (2, GRC_COLOR_YELLOW),
+ 'byte': (1, GRC_COLOR_PURPLE_A400),
+ 'bits': (1, GRC_COLOR_PURPLE_A100),
}
-TYPE_TO_COLOR = dict()
-TYPE_TO_SIZEOF = dict()
-
-for name, key, sizeof, color in CORE_TYPES:
- TYPE_TO_COLOR[key] = color
- TYPE_TO_SIZEOF[key] = sizeof
-
-for key, (sizeof, color) in ALIAS_TYPES.iteritems():
- TYPE_TO_COLOR[key] = color
- TYPE_TO_SIZEOF[key] = sizeof
-
-# Coloring
-COMPLEX_COLOR_SPEC = '#3399FF'
-FLOAT_COLOR_SPEC = '#FF8C69'
-INT_COLOR_SPEC = '#00FF99'
-SHORT_COLOR_SPEC = '#FFFF66'
-BYTE_COLOR_SPEC = '#FF66FF'
-COMPLEX_VECTOR_COLOR_SPEC = '#3399AA'
-FLOAT_VECTOR_COLOR_SPEC = '#CC8C69'
-INT_VECTOR_COLOR_SPEC = '#00CC99'
-SHORT_VECTOR_COLOR_SPEC = '#CCCC33'
-BYTE_VECTOR_COLOR_SPEC = '#CC66CC'
-ID_COLOR_SPEC = '#DDDDDD'
-WILDCARD_COLOR_SPEC = '#FFFFFF'
+TYPE_TO_SIZEOF = {key: sizeof for name, key, sizeof, color in CORE_TYPES}
+TYPE_TO_SIZEOF.update((key, sizeof) for key, (sizeof, _) in ALIAS_TYPES.items())
diff --git a/grc/core/Element.py b/grc/core/Element.py
index 67c36e12b4..86e0746655 100644
--- a/grc/core/Element.py
+++ b/grc/core/Element.py
@@ -1,28 +1,50 @@
-"""
-Copyright 2008, 2009, 2015 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
-"""
+# Copyright 2008, 2009, 2015, 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
+
+import weakref
+import functools
+
+
+class lazy_property(object):
+
+ def __init__(self, func):
+ self.func = func
+ functools.update_wrapper(self, func)
+
+ def __get__(self, instance, cls):
+ if instance is None:
+ return self
+ value = self.func(instance)
+ setattr(instance, self.func.__name__, value)
+ return value
+
+
+def nop_write(prop):
+ """Make this a property with a nop setter"""
+ def nop(self, value):
+ pass
+ return prop.setter(nop)
class Element(object):
def __init__(self, parent=None):
- self._parent = parent
- self._error_messages = list()
+ self._parent = weakref.ref(parent) if parent else lambda: None
+ self._error_messages = []
##################################################
# Element Validation API
@@ -33,6 +55,7 @@ class Element(object):
Call this base method before adding error messages in the subclass.
"""
del self._error_messages[:]
+
for child in self.get_children():
child.validate()
@@ -43,7 +66,9 @@ class Element(object):
Returns:
true when the element is enabled and has no error messages or is bypassed
"""
- return (not self.get_error_messages() or not self.get_enabled()) or self.get_bypassed()
+ if not self.enabled or self.get_bypassed():
+ return True
+ return not next(self.iter_error_messages(), False)
def add_error_message(self, msg):
"""
@@ -63,11 +88,20 @@ class Element(object):
Returns:
a list of error message strings
"""
- error_messages = list(self._error_messages) # Make a copy
- for child in filter(lambda c: c.get_enabled() and not c.get_bypassed(), self.get_children()):
- for msg in child.get_error_messages():
- error_messages.append("{}:\n\t{}".format(child, msg.replace("\n", "\n\t")))
- return error_messages
+ return [msg if elem is self else "{}:\n\t{}".format(elem, msg.replace("\n", "\n\t"))
+ for elem, msg in self.iter_error_messages()]
+
+ def iter_error_messages(self):
+ """
+ Iterate over error messages. Yields tuples of (element, message)
+ """
+ for msg in self._error_messages:
+ yield self, msg
+ for child in self.get_children():
+ if not child.enabled or child.get_bypassed():
+ continue
+ for element_msg in child.iter_error_messages():
+ yield element_msg
def rewrite(self):
"""
@@ -77,7 +111,8 @@ class Element(object):
for child in self.get_children():
child.rewrite()
- def get_enabled(self):
+ @property
+ def enabled(self):
return True
def get_bypassed(self):
@@ -86,8 +121,39 @@ class Element(object):
##############################################
# Tree-like API
##############################################
- def get_parent(self):
- return self._parent
+ @property
+ def parent(self):
+ return self._parent()
+
+ def get_parent_by_type(self, cls):
+ parent = self.parent
+ if parent is None:
+ return None
+ elif isinstance(parent, cls):
+ return parent
+ else:
+ return parent.get_parent_by_type(cls)
+
+ @lazy_property
+ def parent_platform(self):
+ from .Platform import Platform
+ return self.get_parent_by_type(Platform)
+
+ @lazy_property
+ def parent_flowgraph(self):
+ from .FlowGraph import FlowGraph
+ return self.get_parent_by_type(FlowGraph)
+
+ @lazy_property
+ def parent_block(self):
+ from .Block import Block
+ return self.get_parent_by_type(Block)
+
+ def reset_parents_by_type(self):
+ """Reset all lazy properties"""
+ for name, obj in vars(Element): # explicitly only in Element, not subclasses
+ if isinstance(obj, lazy_property):
+ delattr(self, name)
def get_children(self):
return list()
diff --git a/grc/core/Element.pyi b/grc/core/Element.pyi
index c81180a33e..2a2aed401c 100644
--- a/grc/core/Element.pyi
+++ b/grc/core/Element.pyi
@@ -1,4 +1,4 @@
-# Copyright 2008, 2009, 2015, 2016 Free Software Foundation, Inc.
+# 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
@@ -15,40 +15,27 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+from typing import Union
+
from . import Platform, FlowGraph, Block
-def lazy_property(func):
- return func
+lazy_property = property # fixme: descriptors don't seems to be supported
class Element(object):
- def __init__(self, parent=None):
- ...
+ def __init__(self, parent: Union[None, 'Element'] = None): ...
- @property
- def parent(self):
- ...
+ @lazy_property
+ def parent(self) -> 'Element': ...
- def get_parent_by_type(self, cls):
- parent = self.parent
- if parent is None:
- return None
- elif isinstance(parent, cls):
- return parent
- else:
- return parent.get_parent_by_type(cls)
+ def get_parent_by_type(self, cls) -> Union[None, 'Element']: ...
@lazy_property
- def parent_platform(self): -> Platform.Platform
- ...
+ def parent_platform(self) -> Platform.Platform: ...
@lazy_property
- def parent_flowgraph(self): -> FlowGraph.FlowGraph
- ...
+ def parent_flowgraph(self) -> FlowGraph.FlowGraph: ...
@lazy_property
- def parent_block(self): -> Block.Block
- ...
-
-
+ def parent_block(self) -> Block.Block: ...
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
index ecae11cf1a..bf5bf6d93e 100644
--- a/grc/core/FlowGraph.py
+++ b/grc/core/FlowGraph.py
@@ -15,17 +15,19 @@
# 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
+
import imp
-from itertools import ifilter, chain
-from operator import methodcaller, attrgetter
+import time
import re
+from operator import methodcaller
+import collections
import sys
-import time
from . import Messages
from .Constants import FLOW_GRAPH_FILE_FORMAT_VERSION
from .Element import Element
-from .utils import odict, expr_utils, shlex
+from .utils import expr_utils, shlex
_parameter_matcher = re.compile('^(parameter)$')
_monitors_searcher = re.compile('(ctrlport_monitor)')
@@ -39,36 +41,31 @@ class FlowGraph(Element):
is_flow_graph = True
- def __init__(self, platform):
+ def __init__(self, parent):
"""
Make a flow graph from the arguments.
Args:
- platform: a platforms with blocks and contrcutors
+ parent: a platforms with blocks and element factories
Returns:
the flow graph object
"""
- Element.__init__(self, platform)
- self._elements = []
+ Element.__init__(self, parent)
self._timestamp = time.ctime()
+ self._options_block = self.parent_platform.get_new_block(self, 'options')
- self.platform = platform # todo: make this a lazy prop
- self.blocks = []
+ self.blocks = [self._options_block]
self.connections = []
self._eval_cache = {}
self.namespace = {}
self.grc_file_path = ''
- self._options_block = self.new_block('options')
def __str__(self):
return 'FlowGraph - {}({})'.format(self.get_option('title'), self.get_option('id'))
- ##############################################
- # TODO: Move these to new generator package
- ##############################################
def get_imports(self):
"""
Get a set of all import statements in this flow graph namespace.
@@ -87,7 +84,7 @@ class FlowGraph(Element):
Returns:
a sorted list of variable blocks in order of dependency (indep -> dep)
"""
- variables = filter(attrgetter('is_variable'), self.iter_enabled_blocks())
+ 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'))
def get_parameters(self):
@@ -97,54 +94,53 @@ class FlowGraph(Element):
Returns:
a list of parameterized variables
"""
- parameters = filter(lambda b: _parameter_matcher.match(b.get_key()), self.iter_enabled_blocks())
+ parameters = [b for b in self.iter_enabled_blocks() if _parameter_matcher.match(b.key)]
return parameters
def get_monitors(self):
"""
Get a list of all ControlPort monitors
"""
- monitors = filter(lambda b: _monitors_searcher.search(b.get_key()),
- self.iter_enabled_blocks())
+ monitors = [b for b in self.iter_enabled_blocks() if _monitors_searcher.search(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.get_key() == 'epy_module':
+ if block.key == 'epy_module':
yield block.get_id(), block.get_param('source_code').get_value()
def get_bussink(self):
- bussink = filter(lambda b: _bussink_searcher.search(b.get_key()), self.get_enabled_blocks())
+ bussink = [b for b in self.get_enabled_blocks() if _bussink_searcher.search(b.key)]
for i in bussink:
- for j in i.get_params():
- if j.get_name() == 'On/Off' and j.get_value() == 'on':
+ 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 = filter(lambda b: _bussrc_searcher.search(b.get_key()), self.get_enabled_blocks())
+ bussrc = [b for b in self.get_enabled_blocks() if _bussrc_searcher.search(b.key)]
for i in bussrc:
- for j in i.get_params():
- if j.get_name() == 'On/Off' and j.get_value() == 'on':
+ 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 = filter(lambda b: _bus_struct_sink_searcher.search(b.get_key()), self.get_enabled_blocks())
+ 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 = filter(lambda b: _bus_struct_src_searcher.search(b.get_key()), self.get_enabled_blocks())
+ bussrc = [b for b in self.get_enabled_blocks() if _bus_struct_src_searcher.search(b.key)]
return bussrc
def iter_enabled_blocks(self):
"""
Get an iterator of all blocks that are enabled and not bypassed.
"""
- return ifilter(methodcaller('get_enabled'), self.blocks)
+ return (block for block in self.blocks if block.enabled)
def get_enabled_blocks(self):
"""
@@ -162,7 +158,7 @@ class FlowGraph(Element):
Returns:
a list of blocks
"""
- return filter(methodcaller('get_bypassed'), self.blocks)
+ return [block for block in self.blocks if block.get_bypassed()]
def get_enabled_connections(self):
"""
@@ -171,7 +167,7 @@ class FlowGraph(Element):
Returns:
a list of connections
"""
- return filter(methodcaller('get_enabled'), self.connections)
+ return [connection for connection in self.connections if connection.enabled]
def get_option(self, key):
"""
@@ -206,19 +202,6 @@ class FlowGraph(Element):
raise KeyError('No block with ID {!r}'.format(id))
def get_elements(self):
- """
- Get a list of all the elements.
- Always ensure that the options block is in the list (only once).
-
- Returns:
- the element list
- """
- options_block_count = self.blocks.count(self._options_block)
- if not options_block_count:
- self.blocks.append(self._options_block)
- for i in range(options_block_count-1):
- self.blocks.remove(self._options_block)
-
return self.blocks + self.connections
get_children = get_elements
@@ -227,26 +210,22 @@ class FlowGraph(Element):
"""
Flag the namespace to be renewed.
"""
-
self.renew_namespace()
- for child in chain(self.blocks, self.connections):
- child.rewrite()
-
- self.bus_ports_rewrite()
+ Element.rewrite(self)
def renew_namespace(self):
namespace = {}
# Load imports
for expr in self.get_imports():
try:
- exec expr in namespace
+ exec(expr, namespace)
except:
pass
for id, expr in self.get_python_modules():
try:
module = imp.new_module(id)
- exec expr in module.__dict__
+ exec(expr, module.__dict__)
namespace[id] = module
except:
pass
@@ -293,7 +272,7 @@ class FlowGraph(Element):
# Add/remove stuff
##############################################
- def new_block(self, key):
+ def new_block(self, key, **kwargs):
"""
Get a new block of the specified key.
Add the block to the list of elements.
@@ -304,8 +283,10 @@ class FlowGraph(Element):
Returns:
the new block or None if not found
"""
+ if key == 'options':
+ return self._options_block
try:
- block = self.platform.get_new_block(self, key)
+ block = self.parent_platform.get_new_block(self, key, **kwargs)
self.blocks.append(block)
except KeyError:
block = None
@@ -324,8 +305,8 @@ class FlowGraph(Element):
the new connection
"""
- connection = self.platform.Connection(
- flow_graph=self, porta=porta, portb=portb)
+ connection = self.parent_platform.Connection(
+ parent=self, porta=porta, portb=portb)
self.connections.append(connection)
return connection
@@ -336,22 +317,25 @@ class FlowGraph(Element):
If the element is a block, remove its connections.
If the element is a connection, just remove the connection.
"""
+ if element is self._options_block:
+ return
+
if element.is_port:
# Found a port, set to parent signal block
- element = element.get_parent()
+ element = element.parent
if element in self.blocks:
# Remove block, remove all involved connections
for port in element.get_ports():
- map(self.remove_element, port.get_connections())
+ for connection in port.get_connections():
+ self.remove_element(connection)
self.blocks.remove(element)
elif element in self.connections:
if element.is_bus():
- cons_list = []
- for i in map(lambda a: a.get_connections(), element.get_source().get_associated_ports()):
- cons_list.extend(i)
- map(self.remove_element, cons_list)
+ for port in element.source_port.get_associated_ports():
+ for connection in port.get_connections():
+ self.remove_element(connection)
self.connections.remove(element)
##############################################
@@ -367,25 +351,24 @@ class FlowGraph(Element):
"""
# sort blocks and connections for nicer diffs
blocks = sorted(self.blocks, key=lambda b: (
- b.get_key() != 'options', # options to the front
- not b.get_key().startswith('variable'), # then vars
+ b.key != 'options', # options to the front
+ not b.key.startswith('variable'), # then vars
str(b)
))
connections = sorted(self.connections, key=str)
- n = odict()
+ 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 = odict({
- 'created': '.'.join(self.get_parent().config.version_parts),
- 'format': FLOW_GRAPH_FILE_FORMAT_VERSION,
- })
- return odict({'flow_graph': n, '_instructions': instructions})
+ 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):
"""
Import blocks and connections into this flow graph.
- Clear this flowgraph of all previous blocks and connections.
+ Clear this flow graph of all previous blocks and connections.
Any blocks or connections in error will be ignored.
Args:
@@ -396,38 +379,37 @@ class FlowGraph(Element):
del self.connections[:]
# set file format
try:
- instructions = n.find('_instructions') or {}
+ instructions = n.get('_instructions', {})
file_format = int(instructions.get('format', '0')) or _guess_file_format_1(n)
except:
file_format = 0
- fg_n = n and n.find('flow_graph') or odict() # use blank data if none provided
- self._timestamp = fg_n.find('timestamp') or time.ctime()
+ fg_n = n and n.get('flow_graph', {}) # use blank data if none provided
+ self._timestamp = fg_n.get('timestamp', time.ctime())
# build the blocks
- self._options_block = self.new_block('options')
- for block_n in fg_n.findall('block'):
- key = block_n.find('key')
- block = self._options_block if key == 'options' else self.new_block(key)
+ 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.platform.find_file_in_paths(
+ 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.platform.load_and_generate_flow_graph(file_path, hier_only=True)
+ 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_block')
- # Ugly ugly ugly
- _initialize_dummy_block(block, block_n)
+ 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)
@@ -436,26 +418,26 @@ class FlowGraph(Element):
# build the connections
def verify_and_get_port(key, block, dir):
- ports = block.get_sinks() if dir == 'sink' else block.get_sources()
+ ports = block.sinks if dir == 'sink' else block.sources
for port in ports:
- if key == port.get_key():
+ if key == port.key:
break
- if not key.isdigit() and port.get_type() == '' and key == port.get_name():
+ if not key.isdigit() and port.get_type() == '' and key == port.name:
break
else:
if block.is_dummy_block:
- port = _dummy_block_add_port(block, key, dir)
+ port = block.add_missing_port(key, dir)
else:
raise LookupError('%s key %r not in %s block keys' % (dir, key, dir))
return port
errors = False
- for connection_n in fg_n.findall('connection'):
+ for connection_n in fg_n.get('connection', []):
# get the block ids and port keys
- source_block_id = connection_n.find('source_block_id')
- sink_block_id = connection_n.find('sink_block_id')
- source_key = connection_n.find('source_key')
- sink_key = connection_n.find('sink_key')
+ 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')
try:
source_block = self.get_block(source_block_id)
sink_block = self.get_block(sink_block_id)
@@ -478,61 +460,6 @@ class FlowGraph(Element):
self.rewrite() # global rewrite
return errors
- ##############################################
- # Needs to go
- ##############################################
- def bus_ports_rewrite(self):
- # todo: move to block.rewrite()
- for block in self.blocks:
- for direc in ['source', 'sink']:
- if direc == 'source':
- get_p = block.get_sources
- get_p_gui = block.get_sources_gui
- bus_structure = block.form_bus_structure('source')
- else:
- get_p = block.get_sinks
- get_p_gui = block.get_sinks_gui
- bus_structure = block.form_bus_structure('sink')
-
- if 'bus' in map(lambda a: a.get_type(), get_p_gui()):
- if len(get_p_gui()) > len(bus_structure):
- times = range(len(bus_structure), len(get_p_gui()))
- for i in times:
- for connect in get_p_gui()[-1].get_connections():
- block.get_parent().remove_element(connect)
- get_p().remove(get_p_gui()[-1])
- elif len(get_p_gui()) < len(bus_structure):
- n = {'name': 'bus', 'type': 'bus'}
- if True in map(
- lambda a: isinstance(a.get_nports(), int),
- get_p()):
- n['nports'] = str(1)
-
- times = range(len(get_p_gui()), len(bus_structure))
-
- for i in times:
- n['key'] = str(len(get_p()))
- n = odict(n)
- port = block.get_parent().get_parent().Port(
- block=block, n=n, dir=direc)
- get_p().append(port)
-
- if 'bus' in map(lambda a: a.get_type(),
- block.get_sources_gui()):
- for i in range(len(block.get_sources_gui())):
- if len(block.get_sources_gui()[
- i].get_connections()) > 0:
- source = block.get_sources_gui()[i]
- sink = []
-
- for j in range(len(source.get_connections())):
- sink.append(
- source.get_connections()[j].get_sink())
- for elt in source.get_connections():
- self.remove_element(elt)
- for j in sink:
- self.connect(source, j)
-
def _update_old_message_port_keys(source_key, sink_key, source_block, sink_block):
"""
@@ -549,10 +476,10 @@ def _update_old_message_port_keys(source_key, sink_key, source_block, sink_block
"""
try:
# get ports using the "old way" (assuming liner indexed keys)
- source_port = source_block.get_sources()[int(source_key)]
- sink_port = sink_block.get_sinks()[int(sink_key)]
+ 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":
- source_key, sink_key = source_port.get_key(), sink_port.get_key()
+ source_key, sink_key = source_port.key, sink_port.key
except (ValueError, IndexError):
pass
return source_key, sink_key # do nothing
@@ -564,39 +491,11 @@ def _guess_file_format_1(n):
"""
try:
has_non_numeric_message_keys = any(not (
- connection_n.find('source_key').isdigit() and
- connection_n.find('sink_key').isdigit()
- ) for connection_n in n.find('flow_graph').findall('connection'))
+ 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
-
-
-def _initialize_dummy_block(block, block_n):
- """
- This is so ugly... dummy-fy a block
- Modify block object to get the behaviour for a missing block
- """
-
- block._key = block_n.find('key')
- block.is_dummy_block = lambda: True
- block.is_valid = lambda: False
- block.get_enabled = lambda: False
- for param_n in block_n.findall('param'):
- if param_n['key'] not in block.get_param_keys():
- new_param_n = odict({'key': param_n['key'], 'name': param_n['key'], 'type': 'string'})
- params = block.get_parent().get_parent().Param(block=block, n=new_param_n)
- block.get_params().append(params)
-
-
-def _dummy_block_add_port(block, key, dir):
- """ This is so ugly... Add a port to a dummy-field block """
- port_n = odict({'name': '?', 'key': key, 'type': ''})
- port = block.get_parent().get_parent().Port(block=block, n=port_n, dir=dir)
- if port.is_source:
- block.get_sources().append(port)
- else:
- block.get_sinks().append(port)
- return port
diff --git a/grc/core/Messages.py b/grc/core/Messages.py
index 8daa12c33f..f546c3b62e 100644
--- a/grc/core/Messages.py
+++ b/grc/core/Messages.py
@@ -16,9 +16,10 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+from __future__ import absolute_import
+
import traceback
import sys
-import os
# A list of functions that can receive a message.
MESSENGERS_LIST = list()
@@ -124,8 +125,8 @@ def send_fail_save(file_path):
send('>>> Error: Cannot save: %s\n' % file_path)
-def send_fail_connection():
- send('>>> Error: Cannot create connection.\n')
+def send_fail_connection(msg=''):
+ send('>>> Error: Cannot create connection.\n' + ('\t{}\n'.format(msg) if msg else ''))
def send_fail_load_preferences(prefs_file_path):
diff --git a/grc/core/Param.py b/grc/core/Param.py
index a9a664f74a..be86f0aecb 100644
--- a/grc/core/Param.py
+++ b/grc/core/Param.py
@@ -17,20 +17,19 @@ 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 ast
-import weakref
import re
+import collections
+
+from six.moves import builtins, filter, map, range, zip
from . import Constants
-from .Constants import VECTOR_TYPES, COMPLEX_TYPES, REAL_TYPES, INT_TYPES
-from .Element import Element
-from .utils import odict
+from .Element import Element, nop_write
# Blacklist certain ids, its not complete, but should help
-import __builtin__
-
-
-ID_BLACKLIST = ['self', 'options', 'gr', 'math', 'firdes'] + dir(__builtin__)
+ID_BLACKLIST = ['self', 'options', 'gr', 'math', 'firdes'] + dir(builtins)
try:
from gnuradio import gr
ID_BLACKLIST.extend(attr for attr in dir(gr.top_block()) if not attr.startswith('_'))
@@ -41,86 +40,6 @@ _check_id_matcher = re.compile('^[a-z|A-Z]\w*$')
_show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook)$')
-def _get_keys(lst):
- return [elem.get_key() for elem in lst]
-
-
-def _get_elem(lst, key):
- try:
- return lst[_get_keys(lst).index(key)]
- except ValueError:
- raise ValueError('Key "{}" not found in {}.'.format(key, _get_keys(lst)))
-
-
-def num_to_str(num):
- """ Display logic for numbers """
- def eng_notation(value, fmt='g'):
- """Convert a number to a string in engineering notation. E.g., 5e-9 -> 5n"""
- template = '{:' + fmt + '}{}'
- magnitude = abs(value)
- for exp, symbol in zip(range(9, -15-1, -3), 'GMk munpf'):
- factor = 10 ** exp
- if magnitude >= factor:
- return template.format(value / factor, symbol.strip())
- return template.format(value, '')
-
- if isinstance(num, COMPLEX_TYPES):
- num = complex(num) # Cast to python complex
- if num == 0:
- return '0'
- output = eng_notation(num.real) if num.real else ''
- output += eng_notation(num.imag, '+g' if output else 'g') + 'j' if num.imag else ''
- return output
- else:
- return str(num)
-
-
-class Option(Element):
-
- def __init__(self, param, n):
- Element.__init__(self, param)
- self._name = n.find('name')
- self._key = n.find('key')
- self._opts = dict()
- opts = n.findall('opt')
- # Test against opts when non enum
- if not self.get_parent().is_enum() and opts:
- raise Exception('Options for non-enum types cannot have sub-options')
- # Extract opts
- for opt in opts:
- # Separate the key:value
- try:
- key, value = opt.split(':')
- except:
- raise Exception('Error separating "{}" into key:value'.format(opt))
- # Test against repeated keys
- if key in self._opts:
- raise Exception('Key "{}" already exists in option'.format(key))
- # Store the option
- self._opts[key] = value
-
- def __str__(self):
- return 'Option {}({})'.format(self.get_name(), self.get_key())
-
- def get_name(self):
- return self._name
-
- def get_key(self):
- return self._key
-
- ##############################################
- # Access Opts
- ##############################################
- def get_opt_keys(self):
- return self._opts.keys()
-
- def get_opt(self, key):
- return self._opts[key]
-
- def get_opts(self):
- return self._opts.values()
-
-
class TemplateArg(object):
"""
A cheetah template argument created from a param.
@@ -130,10 +49,12 @@ class TemplateArg(object):
"""
def __init__(self, param):
- self._param = weakref.proxy(param)
+ self._param = param
def __getitem__(self, item):
- return str(self._param.get_opt(item)) if self._param.is_enum() else NotImplemented
+ param = self._param
+ opts = param.options_opts[param.get_value()]
+ return str(opts[item]) if param.is_enum() else NotImplemented
def __str__(self):
return str(self._param.to_code())
@@ -146,181 +67,66 @@ class Param(Element):
is_param = True
- def __init__(self, block, n):
- """
- Make a new param from nested data.
+ def __init__(self, parent, key, name, type='raw', value='', **n):
+ """Make a new param from nested data"""
+ super(Param, self).__init__(parent)
+ self.key = key
+ self._name = name
+ self.value = self.default = value
+ self._type = type
- Args:
- block: the parent element
- n: the nested odict
- """
- # If the base key is a valid param key, copy its data and overlay this params data
- base_key = n.find('base_key')
- if base_key and base_key in block.get_param_keys():
- n_expanded = block.get_param(base_key)._n.copy()
- n_expanded.update(n)
- n = n_expanded
- # Save odict in case this param will be base for another
- self._n = n
- # Parse the data
- self._name = n.find('name')
- self._key = n.find('key')
- value = n.find('value') or ''
- self._type = n.find('type') or 'raw'
- self._hide = n.find('hide') or ''
- self._tab_label = n.find('tab') or block.get_param_tab_labels()[0]
- if self._tab_label not in block.get_param_tab_labels():
- block.get_param_tab_labels().append(self._tab_label)
- # Build the param
- Element.__init__(self, block)
- # Create the Option objects from the n data
- self._options = list()
+ self._hide = n.get('hide', '')
+ self.tab_label = n.get('tab', Constants.DEFAULT_PARAM_TAB)
self._evaluated = None
- for option in map(lambda o: Option(param=self, n=o), n.findall('option')):
- key = option.get_key()
- # Test against repeated keys
- if key in self.get_option_keys():
- raise Exception('Key "{}" already exists in options'.format(key))
- # Store the option
- self.get_options().append(option)
- # Test the enum options
- if self.is_enum():
- # Test against options with identical keys
- if len(set(self.get_option_keys())) != len(self.get_options()):
- raise Exception('Options keys "{}" are not unique.'.format(self.get_option_keys()))
- # Test against inconsistent keys in options
- opt_keys = self.get_options()[0].get_opt_keys()
- for option in self.get_options():
- if set(opt_keys) != set(option.get_opt_keys()):
- raise Exception('Opt keys "{}" are not identical across all options.'.format(opt_keys))
- # If a value is specified, it must be in the options keys
- if value or value in self.get_option_keys():
- self._value = value
- else:
- self._value = self.get_option_keys()[0]
- if self.get_value() not in self.get_option_keys():
- raise Exception('The value "{}" is not in the possible values of "{}".'.format(self.get_value(), self.get_option_keys()))
- else:
- self._value = value or ''
- self._default = value
+
+ self.options = []
+ self.options_names = []
+ self.options_opts = {}
+ self._init_options(options_n=n.get('option', []))
+
self._init = False
self._hostage_cells = list()
self.template_arg = TemplateArg(self)
- def get_types(self):
- return (
- 'raw', 'enum',
- 'complex', 'real', 'float', 'int',
- 'complex_vector', 'real_vector', 'float_vector', 'int_vector',
- 'hex', 'string', 'bool',
- 'file_open', 'file_save', '_multiline', '_multiline_python_external',
- 'id', 'stream_id',
- 'gui_hint',
- 'import',
- )
-
- def __repr__(self):
- """
- Get the repr (nice string format) for this param.
-
- Returns:
- the string representation
- """
- ##################################################
- # Truncate helper method
- ##################################################
- def _truncate(string, style=0):
- max_len = max(27 - len(self.get_name()), 3)
- if len(string) > max_len:
- if style < 0: # Front truncate
- string = '...' + string[3-max_len:]
- elif style == 0: # Center truncate
- string = string[:max_len/2 - 3] + '...' + string[-max_len/2:]
- elif style > 0: # Rear truncate
- string = string[:max_len-3] + '...'
- return string
-
- ##################################################
- # Simple conditions
- ##################################################
- if not self.is_valid():
- return _truncate(self.get_value())
- if self.get_value() in self.get_option_keys():
- return self.get_option(self.get_value()).get_name()
-
- ##################################################
- # Split up formatting by type
- ##################################################
- # Default center truncate
- truncate = 0
- e = self.get_evaluated()
- t = self.get_type()
- if isinstance(e, bool):
- return str(e)
- elif isinstance(e, COMPLEX_TYPES):
- dt_str = num_to_str(e)
- elif isinstance(e, VECTOR_TYPES):
- # Vector types
- if len(e) > 8:
- # Large vectors use code
- dt_str = self.get_value()
- truncate = 1
- else:
- # Small vectors use eval
- dt_str = ', '.join(map(num_to_str, e))
- elif t in ('file_open', 'file_save'):
- dt_str = self.get_value()
- truncate = -1
- else:
- # Other types
- dt_str = str(e)
-
- # Done
- return _truncate(dt_str, truncate)
-
- def __repr2__(self):
- """
- Get the repr (nice string format) for this param.
+ def _init_options(self, options_n):
+ """Create the Option objects from the n data"""
+ option_keys = set()
+ for option_n in options_n:
+ key, name = option_n['key'], option_n['name']
+ # Test against repeated keys
+ if key in option_keys:
+ raise KeyError('Key "{}" already exists in options'.format(key))
+ option_keys.add(key)
+ # Store the option
+ self.options.append(key)
+ self.options_names.append(name)
- Returns:
- the string representation
- """
if self.is_enum():
- return self.get_option(self.get_value()).get_name()
- return self.get_value()
-
- def __str__(self):
- return 'Param - {}({})'.format(self.get_name(), self.get_key())
+ self._init_enum(options_n)
- def get_color(self):
- """
- Get the color that represents this param's type.
+ def _init_enum(self, options_n):
+ opt_ref = None
+ for option_n in options_n:
+ key, opts_raw = option_n['key'], option_n.get('opt', [])
+ try:
+ self.options_opts[key] = opts = dict(opt.split(':') for opt in opts_raw)
+ except TypeError:
+ raise ValueError('Error separating opts into key:value')
+
+ if opt_ref is None:
+ opt_ref = set(opts.keys())
+ elif opt_ref != set(opts):
+ raise ValueError('Opt keys ({}) are not identical across all options.'
+ ''.format(', '.join(opt_ref)))
+ if not self.value:
+ self.value = self.default = self.options[0]
+ elif self.value not in self.options:
+ self.value = self.default = self.options[0] # TODO: warn
+ # raise ValueError('The value {!r} is not in the possible values of {}.'
+ # ''.format(self.get_value(), ', '.join(self.options)))
- Returns:
- a hex color code.
- """
- try:
- return {
- # Number types
- 'complex': Constants.COMPLEX_COLOR_SPEC,
- 'real': Constants.FLOAT_COLOR_SPEC,
- 'float': Constants.FLOAT_COLOR_SPEC,
- 'int': Constants.INT_COLOR_SPEC,
- # Vector types
- 'complex_vector': Constants.COMPLEX_VECTOR_COLOR_SPEC,
- 'real_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
- 'float_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
- 'int_vector': Constants.INT_VECTOR_COLOR_SPEC,
- # Special
- 'bool': Constants.INT_COLOR_SPEC,
- 'hex': Constants.INT_COLOR_SPEC,
- 'string': Constants.BYTE_VECTOR_COLOR_SPEC,
- 'id': Constants.ID_COLOR_SPEC,
- 'stream_id': Constants.ID_COLOR_SPEC,
- 'raw': Constants.WILDCARD_COLOR_SPEC,
- }[self.get_type()]
- except:
- return '#FFFFFF'
+ def __str__(self):
+ return 'Param - {}({})'.format(self.name, self.key)
def get_hide(self):
"""
@@ -333,20 +139,17 @@ class Param(Element):
Returns:
hide the hide property string
"""
- hide = self.get_parent().resolve_dependencies(self._hide).strip()
+ hide = self.parent.resolve_dependencies(self._hide).strip()
if hide:
return hide
# Hide ID in non variable blocks
- if self.get_key() == 'id' and not _show_id_matcher.match(self.get_parent().get_key()):
+ if self.key == 'id' and not _show_id_matcher.match(self.parent.key):
return 'part'
# Hide port controllers for type and nports
- if self.get_key() in ' '.join(map(lambda p: ' '.join([p._type, p._nports]),
- self.get_parent().get_ports())):
+ if self.key in ' '.join([' '.join([p._type, p._nports]) for p in self.parent.get_ports()]):
return 'part'
# Hide port controllers for vlen, when == 1
- if self.get_key() in ' '.join(map(
- lambda p: p._vlen, self.get_parent().get_ports())
- ):
+ if self.key in ' '.join(p._vlen for p in self.parent.get_ports()):
try:
if int(self.get_evaluated()) == 1:
return 'part'
@@ -360,13 +163,13 @@ class Param(Element):
The value must be evaluated and type must a possible type.
"""
Element.validate(self)
- if self.get_type() not in self.get_types():
+ if self.get_type() not in Constants.PARAM_TYPE_NAMES:
self.add_error_message('Type "{}" is not a possible type.'.format(self.get_type()))
self._evaluated = None
try:
self._evaluated = self.evaluate()
- except Exception, e:
+ except Exception as e:
self.add_error_message(str(e))
def get_evaluated(self):
@@ -398,22 +201,22 @@ class Param(Element):
elif t in ('raw', 'complex', 'real', 'float', 'int', 'hex', 'bool'):
# Raise exception if python cannot evaluate this value
try:
- e = self.get_parent().get_parent().evaluate(v)
- except Exception, e:
+ e = self.parent_flowgraph.evaluate(v)
+ except Exception as e:
raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e))
# Raise an exception if the data is invalid
if t == 'raw':
return e
elif t == 'complex':
- if not isinstance(e, COMPLEX_TYPES):
+ if not isinstance(e, Constants.COMPLEX_TYPES):
raise Exception('Expression "{}" is invalid for type complex.'.format(str(e)))
return e
elif t == 'real' or t == 'float':
- if not isinstance(e, REAL_TYPES):
+ if not isinstance(e, Constants.REAL_TYPES):
raise Exception('Expression "{}" is invalid for type float.'.format(str(e)))
return e
elif t == 'int':
- if not isinstance(e, INT_TYPES):
+ if not isinstance(e, Constants.INT_TYPES):
raise Exception('Expression "{}" is invalid for type integer.'.format(str(e)))
return e
elif t == 'hex':
@@ -433,29 +236,29 @@ class Param(Element):
v = '()'
# Raise exception if python cannot evaluate this value
try:
- e = self.get_parent().get_parent().evaluate(v)
- except Exception, e:
+ e = self.parent.parent.evaluate(v)
+ except Exception as e:
raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e))
# Raise an exception if the data is invalid
if t == 'complex_vector':
- if not isinstance(e, VECTOR_TYPES):
+ if not isinstance(e, Constants.VECTOR_TYPES):
self._lisitify_flag = True
e = [e]
- if not all([isinstance(ei, COMPLEX_TYPES) for ei in e]):
+ if not all([isinstance(ei, Constants.COMPLEX_TYPES) for ei in e]):
raise Exception('Expression "{}" is invalid for type complex vector.'.format(str(e)))
return e
elif t == 'real_vector' or t == 'float_vector':
- if not isinstance(e, VECTOR_TYPES):
+ if not isinstance(e, Constants.VECTOR_TYPES):
self._lisitify_flag = True
e = [e]
- if not all([isinstance(ei, REAL_TYPES) for ei in e]):
+ if not all([isinstance(ei, Constants.REAL_TYPES) for ei in e]):
raise Exception('Expression "{}" is invalid for type float vector.'.format(str(e)))
return e
elif t == 'int_vector':
- if not isinstance(e, VECTOR_TYPES):
+ if not isinstance(e, Constants.VECTOR_TYPES):
self._lisitify_flag = True
e = [e]
- if not all([isinstance(ei, INT_TYPES) for ei in e]):
+ if not all([isinstance(ei, Constants.INT_TYPES) for ei in e]):
raise Exception('Expression "{}" is invalid for type integer vector.'.format(str(e)))
return e
#########################
@@ -464,7 +267,7 @@ class Param(Element):
elif t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'):
# Do not check if file/directory exists, that is a runtime issue
try:
- e = self.get_parent().get_parent().evaluate(v)
+ e = self.parent.parent.evaluate(v)
if not isinstance(e, str):
raise Exception()
except:
@@ -485,7 +288,7 @@ class Param(Element):
if v in ID_BLACKLIST:
raise Exception('ID "{}" is blacklisted.'.format(v))
- if self._key == 'id':
+ if self.key == 'id':
# Id should only appear once, or zero times if block is disabled
if ids.count(v) > 1:
raise Exception('ID "{}" is not unique.'.format(v))
@@ -502,16 +305,16 @@ class Param(Element):
elif t == 'stream_id':
# Get a list of all stream ids used in the virtual sinks
ids = [param.get_value() for param in filter(
- lambda p: p.get_parent().is_virtual_sink(),
+ lambda p: p.parent.is_virtual_sink(),
self.get_all_params(t),
)]
# Check that the virtual sink's stream id is unique
- if self.get_parent().is_virtual_sink():
+ if self.parent.is_virtual_sink():
# Id should only appear once, or zero times if block is disabled
if ids.count(v) > 1:
raise Exception('Stream ID "{}" is not unique.'.format(v))
# Check that the virtual source's steam id is found
- if self.get_parent().is_virtual_source():
+ if self.parent.is_virtual_source():
if v not in ids:
raise Exception('Stream ID "{}" is not found.'.format(v))
return v
@@ -559,12 +362,12 @@ class Param(Element):
# New namespace
n = dict()
try:
- exec v in n
+ exec(v, n)
except ImportError:
raise Exception('Import "{}" failed.'.format(v))
except Exception:
raise Exception('Bad import syntax: "{}".'.format(v))
- return filter(lambda k: str(k) != '__builtins__', n.keys())
+ return [k for k in list(n.keys()) if str(k) != '__builtins__']
#########################
else:
@@ -610,62 +413,46 @@ class Param(Element):
Returns:
a list of params
"""
- return sum([filter(lambda p: ((p.get_type() == type) and ((key is None) or (p.get_key() == key))), block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])
+ params = []
+ for block in self.parent_flowgraph.get_enabled_blocks():
+ params.extend(p for k, p in block.params.items() if p.get_type() == type and (key is None or key == k))
+ return params
def is_enum(self):
return self._type == 'enum'
def get_value(self):
- value = self._value
- if self.is_enum() and value not in self.get_option_keys():
- value = self.get_option_keys()[0]
+ value = self.value
+ if self.is_enum() and value not in self.options:
+ value = self.options[0]
self.set_value(value)
return value
def set_value(self, value):
# Must be a string
- self._value = str(value)
+ self.value = str(value)
def set_default(self, value):
- if self._default == self._value:
+ if self.default == self.value:
self.set_value(value)
- self._default = str(value)
+ self.default = str(value)
def get_type(self):
- return self.get_parent().resolve_dependencies(self._type)
+ return self.parent.resolve_dependencies(self._type)
def get_tab_label(self):
- return self._tab_label
-
- def get_name(self):
- return self.get_parent().resolve_dependencies(self._name).strip()
+ return self.tab_label
- def get_key(self):
- return self._key
+ @nop_write
+ @property
+ def name(self):
+ return self.parent.resolve_dependencies(self._name).strip()
##############################################
# Access Options
##############################################
- def get_option_keys(self):
- return _get_keys(self.get_options())
-
- def get_option(self, key):
- return _get_elem(self.get_options(), key)
-
- def get_options(self):
- return self._options
-
- ##############################################
- # Access Opts
- ##############################################
- def get_opt_keys(self):
- return self.get_option(self.get_value()).get_opt_keys()
-
- def get_opt(self, key):
- return self.get_option(self.get_value()).get_opt(key)
-
- def get_opts(self):
- return self.get_option(self.get_value()).get_opts()
+ def opt_value(self, key):
+ return self.options_opts[self.get_value()][key]
##############################################
# Import/Export Methods
@@ -677,7 +464,7 @@ class Param(Element):
Returns:
a nested data odict
"""
- n = odict()
- n['key'] = self.get_key()
+ n = collections.OrderedDict()
+ n['key'] = self.key
n['value'] = self.get_value()
return n
diff --git a/grc/core/ParseXML.py b/grc/core/ParseXML.py
index c9f6541ee7..163289ba06 100644
--- a/grc/core/ParseXML.py
+++ b/grc/core/ParseXML.py
@@ -17,9 +17,13 @@ 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 lxml import etree
-from .utils import odict
+import six
+from six.moves import map
+
xml_failures = {}
etree.set_default_parser(etree.XMLParser(remove_comments=True))
@@ -75,17 +79,35 @@ def from_file(xml_file):
the nested data with grc version information
"""
xml = etree.parse(xml_file)
- nested_data = _from_file(xml.getroot())
+
+ tag, nested_data = _from_file(xml.getroot())
+ nested_data = {tag: nested_data, '_instructions': {}}
# Get the embedded instructions and build a dictionary item
- nested_data['_instructions'] = {}
xml_instructions = xml.xpath('/processing-instruction()')
- for inst in filter(lambda i: i.target == 'grc', xml_instructions):
- nested_data['_instructions'] = odict(inst.attrib)
+ for inst in xml_instructions:
+ if inst.target != 'grc':
+ continue
+ nested_data['_instructions'] = dict(inst.attrib)
return nested_data
-def _from_file(xml):
+WANT_A_LIST = {
+ '/block': 'import callback param check sink source'.split(),
+ '/block/param_tab_order': 'tab'.split(),
+ '/block/param': 'option'.split(),
+ '/block/param/option': 'opt'.split(),
+ '/flow_graph': 'block connection'.split(),
+ '/flow_graph/block': 'param'.split(),
+ '/cat': 'cat block'.split(),
+ '/cat/cat': 'cat block'.split(),
+ '/cat/cat/cat': 'cat block'.split(),
+ '/cat/cat/cat/cat': 'cat block'.split(),
+ '/domain': 'connection'.split(),
+}
+
+
+def _from_file(xml, parent_tag=''):
"""
Recursively parse the xml tree into nested data format.
@@ -96,21 +118,24 @@ def _from_file(xml):
the nested data
"""
tag = xml.tag
+ tag_path = parent_tag + '/' + tag
+
if not len(xml):
- return odict({tag: xml.text or ''}) # store empty tags (text is None) as empty string
- nested_data = odict()
+ return tag, xml.text or '' # store empty tags (text is None) as empty string
+
+ nested_data = {}
for elem in xml:
- key, value = _from_file(elem).items()[0]
- if key in nested_data:
- nested_data[key].append(value)
+ key, value = _from_file(elem, tag_path)
+
+ if key in WANT_A_LIST.get(tag_path, []):
+ try:
+ nested_data[key].append(value)
+ except KeyError:
+ nested_data[key] = [value]
else:
- nested_data[key] = [value]
- # Delistify if the length of values is 1
- for key, values in nested_data.iteritems():
- if len(values) == 1:
- nested_data[key] = values[0]
+ nested_data[key] = value
- return odict({tag: nested_data})
+ return tag, nested_data
def to_file(nested_data, xml_file):
@@ -127,7 +152,7 @@ def to_file(nested_data, xml_file):
if instructions:
xml_data += etree.tostring(etree.ProcessingInstruction(
'grc', ' '.join(
- "{0}='{1}'".format(*item) for item in instructions.iteritems())
+ "{0}='{1}'".format(*item) for item in six.iteritems(instructions))
), xml_declaration=True, pretty_print=True, encoding='utf-8')
xml_data += etree.tostring(_to_file(nested_data)[0],
pretty_print=True, encoding='utf-8')
@@ -146,14 +171,14 @@ def _to_file(nested_data):
the xml tree filled with child nodes
"""
nodes = list()
- for key, values in nested_data.iteritems():
+ for key, values in six.iteritems(nested_data):
# Listify the values if not a list
if not isinstance(values, (list, set, tuple)):
values = [values]
for value in values:
node = etree.Element(key)
- if isinstance(value, (str, unicode)):
- node.text = unicode(value)
+ if isinstance(value, (str, six.text_type)):
+ node.text = six.text_type(value)
else:
node.extend(_to_file(value))
nodes.append(node)
diff --git a/grc/core/Platform.py b/grc/core/Platform.py
index b73dade2e8..73937f1299 100644
--- a/grc/core/Platform.py
+++ b/grc/core/Platform.py
@@ -17,9 +17,14 @@ 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
+
import os
import sys
+import six
+from six.moves import range
+
from . import ParseXML, Messages, Constants
from .Config import Config
@@ -27,31 +32,22 @@ from .Element import Element
from .generator import Generator
from .FlowGraph import FlowGraph
from .Connection import Connection
-from .Block import Block
-from .Port import Port
+from . import Block
+from .Port import Port, PortClone
from .Param import Param
-from .utils import odict, extract_docs
+from .utils import extract_docs
class Platform(Element):
- Config = Config
- Generator = Generator
- FlowGraph = FlowGraph
- Connection = Connection
- Block = Block
- Port = Port
- Param = Param
-
is_platform = True
def __init__(self, *args, **kwargs):
""" Make a platform for GNU Radio """
- Element.__init__(self)
+ Element.__init__(self, parent=None)
self.config = self.Config(*args, **kwargs)
-
self.block_docstrings = {}
self.block_docstrings_loaded_callback = lambda: None # dummy to be replaced by BlockTreeWindow
@@ -60,22 +56,23 @@ class Platform(Element):
callback_finished=lambda: self.block_docstrings_loaded_callback()
)
- # Create a dummy flow graph for the blocks
- self._flow_graph = Element(self)
- self._flow_graph.connections = []
-
- self.blocks = odict()
- self._blocks_n = odict()
+ self.blocks = {}
+ self._blocks_n = {}
self._block_categories = {}
self.domains = {}
self.connection_templates = {}
self._auto_hier_block_generate_chain = set()
+ # Create a dummy flow graph for the blocks
+ self._flow_graph = Element.__new__(FlowGraph)
+ Element.__init__(self._flow_graph, self)
+ self._flow_graph.connections = []
+
self.build_block_library()
def __str__(self):
- return 'Platform - {}({})'.format(self.config.key, self.config.name)
+ return 'Platform - {}'.format(self.config.name)
@staticmethod
def find_file_in_paths(filename, paths, cwd):
@@ -128,12 +125,13 @@ class Platform(Element):
return None, None
if flow_graph.get_option('generate_options').startswith('hb'):
- self.load_block_xml(generator.get_file_path_xml())
+ self.load_block_xml(generator.file_path_xml)
return flow_graph, generator.file_path
def build_block_library(self):
"""load the blocks and block tree from the search paths"""
self._docstring_extractor.start()
+
# Reset
self.blocks.clear()
self._blocks_n.clear()
@@ -155,10 +153,11 @@ class Platform(Element):
# print >> sys.stderr, 'Warning: Block validation failed:\n\t%s\n\tIgnoring: %s' % (e, xml_file)
pass
except Exception as e:
- print >> sys.stderr, 'Warning: XML parsing failed:\n\t%r\n\tIgnoring: %s' % (e, xml_file)
+ raise
+ print('Warning: XML parsing failed:\n\t%r\n\tIgnoring: %s' % (e, xml_file), file=sys.stderr)
# Add blocks to block tree
- for key, block in self.blocks.iteritems():
+ for key, block in six.iteritems(self.blocks):
category = self._block_categories.get(key, block.category)
# Blocks with empty categories are hidden
if not category:
@@ -180,26 +179,27 @@ class Platform(Element):
yield block_path
elif os.path.isdir(block_path):
for dirpath, dirnames, filenames in os.walk(block_path):
- for filename in sorted(filter(lambda f: f.endswith('.xml'), filenames)):
+ for filename in sorted(f for f in filenames if f.endswith('.xml')):
yield os.path.join(dirpath, filename)
def load_block_xml(self, xml_file):
"""Load block description from xml file"""
# Validate and import
ParseXML.validate_dtd(xml_file, Constants.BLOCK_DTD)
- n = ParseXML.from_file(xml_file).find('block')
+ n = ParseXML.from_file(xml_file).get('block', {})
n['block_wrapper_path'] = xml_file # inject block wrapper path
- # Get block instance and add it to the list of blocks
- block = self.Block(self._flow_graph, n)
- key = block.get_key()
+ key = n.pop('key')
+
if key in self.blocks:
- print >> sys.stderr, 'Warning: Block with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file)
- else: # Store the block
- self.blocks[key] = block
- self._blocks_n[key] = n
+ print('Warning: Block with key "{}" already exists.\n'
+ '\tIgnoring: {}'.format(key, xml_file), file=sys.stderr)
+ return
+ # Store the block
+ self.blocks[key] = block = self.get_new_block(self._flow_graph, key, **n)
+ self._blocks_n[key] = n
self._docstring_extractor.query(
- block.get_key(),
+ key,
block.get_imports(raw=True),
block.get_make(raw=True)
)
@@ -211,62 +211,62 @@ class Platform(Element):
path = []
def load_category(cat_n):
- path.append(cat_n.find('name').strip())
- for block_key in cat_n.findall('block'):
+ path.append(cat_n.get('name').strip())
+ for block_key in cat_n.get('block', []):
if block_key not in self._block_categories:
self._block_categories[block_key] = list(path)
- for sub_cat_n in cat_n.findall('cat'):
+ for sub_cat_n in cat_n.get('cat', []):
load_category(sub_cat_n)
path.pop()
- load_category(xml.find('cat'))
+ load_category(xml.get('cat', {}))
def load_domain_xml(self, xml_file):
"""Load a domain properties and connection templates from XML"""
ParseXML.validate_dtd(xml_file, Constants.DOMAIN_DTD)
- n = ParseXML.from_file(xml_file).find('domain')
+ n = ParseXML.from_file(xml_file).get('domain')
- key = n.find('key')
+ key = n.get('key')
if not key:
- print >> sys.stderr, 'Warning: Domain with emtpy key.\n\tIgnoring: {}'.format(xml_file)
+ print('Warning: Domain with emtpy key.\n\tIgnoring: {}'.format(xml_file), file=sys.stderr)
return
if key in self.domains: # test against repeated keys
- print >> sys.stderr, 'Warning: Domain with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file)
+ print('Warning: Domain with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file), file=sys.stderr)
return
- #to_bool = lambda s, d: d if s is None else s.lower() not in ('false', 'off', '0', '')
+ # to_bool = lambda s, d: d if s is None else s.lower() not in ('false', 'off', '0', '')
def to_bool(s, d):
if s is not None:
return s.lower() not in ('false', 'off', '0', '')
return d
- color = n.find('color') or ''
+ color = n.get('color') or ''
try:
- import gtk # ugly but handy
- gtk.gdk.color_parse(color)
- except (ValueError, ImportError):
+ chars_per_color = 2 if len(color) > 4 else 1
+ tuple(int(color[o:o + 2], 16) / 255.0 for o in range(1, 3 * chars_per_color, chars_per_color))
+ except ValueError:
if color: # no color is okay, default set in GUI
- print >> sys.stderr, 'Warning: Can\'t parse color code "{}" for domain "{}" '.format(color, key)
+ print('Warning: Can\'t parse color code "{}" for domain "{}" '.format(color, key), file=sys.stderr)
color = None
self.domains[key] = dict(
- name=n.find('name') or key,
- multiple_sinks=to_bool(n.find('multiple_sinks'), True),
- multiple_sources=to_bool(n.find('multiple_sources'), False),
+ name=n.get('name') or key,
+ multiple_sinks=to_bool(n.get('multiple_sinks'), True),
+ multiple_sources=to_bool(n.get('multiple_sources'), False),
color=color
)
- for connection_n in n.findall('connection'):
- key = (connection_n.find('source_domain'), connection_n.find('sink_domain'))
+ for connection_n in n.get('connection', []):
+ key = (connection_n.get('source_domain'), connection_n.get('sink_domain'))
if not all(key):
- print >> sys.stderr, 'Warning: Empty domain key(s) in connection template.\n\t{}'.format(xml_file)
+ print('Warning: Empty domain key(s) in connection template.\n\t{}'.format(xml_file), file=sys.stderr)
elif key in self.connection_templates:
- print >> sys.stderr, 'Warning: Connection template "{}" already exists.\n\t{}'.format(key, xml_file)
+ print('Warning: Connection template "{}" already exists.\n\t{}'.format(key, xml_file), file=sys.stderr)
else:
- self.connection_templates[key] = connection_n.find('make') or ''
+ self.connection_templates[key] = connection_n.get('make') or ''
def _save_docstring_extraction_result(self, key, docstrings):
docs = {}
- for match, docstring in docstrings.iteritems():
+ for match, docstring in six.iteritems(docstrings):
if not docstring or match.endswith('_sptr'):
continue
docstring = docstring.replace('\n\n', '\n').strip()
@@ -294,14 +294,48 @@ class Platform(Element):
ParseXML.validate_dtd(flow_graph_file, Constants.FLOW_GRAPH_DTD)
return ParseXML.from_file(flow_graph_file)
+ def get_blocks(self):
+ return list(self.blocks.values())
+
+ def get_generate_options(self):
+ gen_opts = self.blocks['options'].get_param('generate_options')
+ generate_mode_default = gen_opts.get_value()
+ return [(key, name, key == generate_mode_default)
+ for key, name in zip(gen_opts.options, gen_opts.options_names)]
+
+ ##############################################
+ # Factories
+ ##############################################
+ Config = Config
+ Generator = Generator
+ FlowGraph = FlowGraph
+ Connection = Connection
+ block_classes = {
+ None: Block.Block, # default
+ 'epy_block': Block.EPyBlock,
+ '_dummy': Block.DummyBlock,
+ }
+ port_classes = {
+ None: Port, # default
+ 'clone': PortClone, # default
+ }
+ param_classes = {
+ None: Param, # default
+ }
+
def get_new_flow_graph(self):
- return self.FlowGraph(platform=self)
+ return self.FlowGraph(parent=self)
- def get_blocks(self):
- return self.blocks.values()
+ def get_new_block(self, parent, key, **kwargs):
+ cls = self.block_classes.get(key, self.block_classes[None])
+ if not kwargs:
+ kwargs = self._blocks_n[key]
+ return cls(parent, key=key, **kwargs)
- def get_new_block(self, flow_graph, key):
- return self.Block(flow_graph, n=self._blocks_n[key])
+ def get_new_param(self, parent, **kwargs):
+ cls = self.param_classes[kwargs.pop('cls_key', None)]
+ return cls(parent, **kwargs)
- def get_colors(self):
- return [(name, color) for name, key, sizeof, color in Constants.CORE_TYPES]
+ def get_new_port(self, parent, **kwargs):
+ cls = self.port_classes[kwargs.pop('cls_key', None)]
+ return cls(parent, **kwargs)
diff --git a/grc/core/Port.py b/grc/core/Port.py
index 8549656c9b..9ca443efa1 100644
--- a/grc/core/Port.py
+++ b/grc/core/Port.py
@@ -17,11 +17,13 @@ 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 itertools import chain
-from .Constants import DEFAULT_DOMAIN, GR_STREAM_DOMAIN, GR_MESSAGE_DOMAIN
-from .Element import Element
+from six.moves import filter
+from .Element import Element, lazy_property
from . import Constants
@@ -43,7 +45,7 @@ def _sources_from_virtual_sink_port(sink_port, _traversed=None):
"""
source_ports_per_virtual_connection = (
# there can be multiple ports per virtual connection
- _sources_from_virtual_source_port(c.get_source(), _traversed) # type: list
+ _sources_from_virtual_source_port(c.source_port, _traversed) # type: list
for c in sink_port.get_enabled_connections()
)
return list(chain(*source_ports_per_virtual_connection)) # concatenate generated lists of ports
@@ -59,8 +61,8 @@ def _sources_from_virtual_source_port(source_port, _traversed=None):
raise LoopError('Loop found when resolving port type')
_traversed.add(source_port)
- block = source_port.get_parent()
- flow_graph = block.get_parent()
+ block = source_port.parent_block
+ flow_graph = source_port.parent_flow_graph
if not block.is_virtual_source():
return [source_port] # nothing to resolve, we're done
@@ -70,11 +72,11 @@ def _sources_from_virtual_source_port(source_port, _traversed=None):
# currently the validation does not allow multiple virtual sinks and one virtual source
# but in the future it may...
connected_virtual_sink_blocks = (
- b for b in flow_graph.get_enabled_blocks()
+ b for b in flow_graph.iter_enabled_blocks()
if b.is_virtual_sink() and b.get_param('stream_id').get_value() == stream_id
)
source_ports_per_virtual_connection = (
- _sources_from_virtual_sink_port(b.get_sinks()[0], _traversed) # type: list
+ _sources_from_virtual_sink_port(b.sinks[0], _traversed) # type: list
for b in connected_virtual_sink_blocks
)
return list(chain(*source_ports_per_virtual_connection)) # concatenate generated lists of ports
@@ -94,7 +96,7 @@ def _sinks_from_virtual_source_port(source_port, _traversed=None):
"""
sink_ports_per_virtual_connection = (
# there can be multiple ports per virtual connection
- _sinks_from_virtual_sink_port(c.get_sink(), _traversed) # type: list
+ _sinks_from_virtual_sink_port(c.sink_port, _traversed) # type: list
for c in source_port.get_enabled_connections()
)
return list(chain(*sink_ports_per_virtual_connection)) # concatenate generated lists of ports
@@ -110,8 +112,8 @@ def _sinks_from_virtual_sink_port(sink_port, _traversed=None):
raise LoopError('Loop found when resolving port type')
_traversed.add(sink_port)
- block = sink_port.get_parent()
- flow_graph = block.get_parent()
+ block = sink_port.parent_block
+ flow_graph = sink_port.parent_flow_graph
if not block.is_virtual_sink():
return [sink_port]
@@ -119,11 +121,11 @@ def _sinks_from_virtual_sink_port(sink_port, _traversed=None):
stream_id = block.get_param('stream_id').get_value()
connected_virtual_source_blocks = (
- b for b in flow_graph.get_enabled_blocks()
+ b for b in flow_graph.iter_enabled_blocks()
if b.is_virtual_source() and b.get_param('stream_id').get_value() == stream_id
)
sink_ports_per_virtual_connection = (
- _sinks_from_virtual_source_port(b.get_sources()[0], _traversed) # type: list
+ _sinks_from_virtual_source_port(b.sources[0], _traversed) # type: list
for b in connected_virtual_source_blocks
)
return list(chain(*sink_ports_per_virtual_connection)) # concatenate generated lists of ports
@@ -132,8 +134,9 @@ def _sinks_from_virtual_sink_port(sink_port, _traversed=None):
class Port(Element):
is_port = True
+ is_clone = False
- def __init__(self, block, n, dir):
+ def __init__(self, parent, direction, **n):
"""
Make a new port from nested data.
@@ -144,50 +147,44 @@ class Port(Element):
"""
self._n = n
if n['type'] == 'message':
- n['domain'] = GR_MESSAGE_DOMAIN
+ n['domain'] = Constants.GR_MESSAGE_DOMAIN
+
if 'domain' not in n:
- n['domain'] = DEFAULT_DOMAIN
- elif n['domain'] == GR_MESSAGE_DOMAIN:
+ n['domain'] = Constants.DEFAULT_DOMAIN
+ elif n['domain'] == Constants.GR_MESSAGE_DOMAIN:
n['key'] = n['name']
n['type'] = 'message' # For port color
- if not n.find('key'):
- n['key'] = str(next(block.port_counters[dir == 'source']))
# Build the port
- Element.__init__(self, block)
+ Element.__init__(self, parent)
# Grab the data
- self._name = n['name']
- self._key = n['key']
- self._type = n['type'] or ''
- self._domain = n['domain']
- self._hide = n.find('hide') or ''
- self._dir = dir
+ self.name = n['name']
+ self.key = n['key']
+ self.domain = n.get('domain')
+ self._type = n.get('type', '')
+ self.inherit_type = not self._type
+ self._hide = n.get('hide', '')
+ self._dir = direction
self._hide_evaluated = False # Updated on rewrite()
- self._nports = n.find('nports') or ''
- self._vlen = n.find('vlen') or ''
- self._optional = n.find('optional') or ''
+ self._nports = n.get('nports', '')
+ self._vlen = n.get('vlen', '')
+ self._optional = bool(n.get('optional'))
self._optional_evaluated = False # Updated on rewrite()
- self._clones = [] # References to cloned ports (for nports > 1)
+ self.clones = [] # References to cloned ports (for nports > 1)
def __str__(self):
if self.is_source:
- return 'Source - {}({})'.format(self.get_name(), self.get_key())
+ return 'Source - {}({})'.format(self.name, self.key)
if self.is_sink:
- return 'Sink - {}({})'.format(self.get_name(), self.get_key())
-
- def get_types(self):
- return Constants.TYPE_TO_SIZEOF.keys()
-
- def is_type_empty(self):
- return not self._n['type'] or not self.get_parent().resolve_dependencies(self._n['type'])
+ return 'Sink - {}({})'.format(self.name, self.key)
def validate(self):
- if self.get_type() not in self.get_types():
+ Element.validate(self)
+ if self.get_type() not in Constants.TYPE_TO_SIZEOF.keys():
self.add_error_message('Type "{}" is not a possible type.'.format(self.get_type()))
- platform = self.get_parent().get_parent().get_parent()
- if self.get_domain() not in platform.domains:
- self.add_error_message('Domain key "{}" is not registered.'.format(self.get_domain()))
+ if self.domain not in self.parent_platform.domains:
+ self.add_error_message('Domain key "{}" is not registered.'.format(self.domain))
if not self.get_enabled_connections() and not self.get_optional():
self.add_error_message('Port is not connected.')
@@ -196,22 +193,22 @@ class Port(Element):
Handle the port cloning for virtual blocks.
"""
del self._error_messages[:]
- if self.is_type_empty():
+ if self.inherit_type:
self.resolve_empty_type()
- hide = self.get_parent().resolve_dependencies(self._hide).strip().lower()
+ hide = self.parent_block.resolve_dependencies(self._hide).strip().lower()
self._hide_evaluated = False if hide in ('false', 'off', '0') else bool(hide)
- optional = self.get_parent().resolve_dependencies(self._optional).strip().lower()
+ optional = self.parent_block.resolve_dependencies(self._optional).strip().lower()
self._optional_evaluated = False if optional in ('false', 'off', '0') else bool(optional)
# Update domain if was deduced from (dynamic) port type
type_ = self.get_type()
- if self._domain == GR_STREAM_DOMAIN and type_ == "message":
- self._domain = GR_MESSAGE_DOMAIN
- self._key = self._name
- if self._domain == GR_MESSAGE_DOMAIN and type_ != "message":
- self._domain = GR_STREAM_DOMAIN
- self._key = '0' # Is rectified in rewrite()
+ if self.domain == Constants.GR_STREAM_DOMAIN and type_ == "message":
+ self.domain = Constants.GR_MESSAGE_DOMAIN
+ self.key = self.name
+ if self.domain == Constants.GR_MESSAGE_DOMAIN and type_ != "message":
+ self.domain = Constants.GR_STREAM_DOMAIN
+ self.key = '0' # Is rectified in rewrite()
def resolve_virtual_source(self):
"""Only used by Generator after validation is passed"""
@@ -220,7 +217,7 @@ class Port(Element):
def resolve_empty_type(self):
def find_port(finder):
try:
- return next((p for p in finder(self) if not p.is_type_empty()), None)
+ return next((p for p in finder(self) if not p.inherit_type), None)
except LoopError as error:
self.add_error_message(str(error))
except (StopIteration, Exception) as error:
@@ -242,9 +239,9 @@ class Port(Element):
Returns:
the vector length or 1
"""
- vlen = self.get_parent().resolve_dependencies(self._vlen)
+ vlen = self.parent_block.resolve_dependencies(self._vlen)
try:
- return int(self.get_parent().get_parent().evaluate(vlen))
+ return max(1, int(self.parent_flowgraph.evaluate(vlen)))
except:
return 1
@@ -258,52 +255,17 @@ class Port(Element):
the number of ports or 1
"""
if self._nports == '':
- return ''
+ return 1
- nports = self.get_parent().resolve_dependencies(self._nports)
+ nports = self.parent_block.resolve_dependencies(self._nports)
try:
- return max(1, int(self.get_parent().get_parent().evaluate(nports)))
+ return max(1, int(self.parent_flowgraph.evaluate(nports)))
except:
return 1
def get_optional(self):
return self._optional_evaluated
- def get_color(self):
- """
- Get the color that represents this port's type.
- Codes differ for ports where the vec length is 1 or greater than 1.
-
- Returns:
- a hex color code.
- """
- try:
- color = Constants.TYPE_TO_COLOR[self.get_type()]
- vlen = self.get_vlen()
- if vlen == 1:
- return color
- color_val = int(color[1:], 16)
- r = (color_val >> 16) & 0xff
- g = (color_val >> 8) & 0xff
- b = (color_val >> 0) & 0xff
- dark = (0, 0, 30, 50, 70)[min(4, vlen)]
- r = max(r-dark, 0)
- g = max(g-dark, 0)
- b = max(b-dark, 0)
- # TODO: Change this to .format()
- return '#%.2x%.2x%.2x' % (r, g, b)
- except:
- return '#FFFFFF'
-
- def get_clones(self):
- """
- Get the clones of this master port (nports > 1)
-
- Returns:
- a list of ports
- """
- return self._clones
-
def add_clone(self):
"""
Create a clone of this (master) port and store a reference in self._clones.
@@ -315,24 +277,23 @@ class Port(Element):
the cloned port
"""
# Add index to master port name if there are no clones yet
- if not self._clones:
- self._name = self._n['name'] + '0'
+ if not self.clones:
+ self.name = self._n['name'] + '0'
# Also update key for none stream ports
- if not self._key.isdigit():
- self._key = self._name
-
- # Prepare a copy of the odict for the clone
- n = self._n.copy()
- # Remove nports from the key so the copy cannot be a duplicator
- if 'nports' in n:
- n.pop('nports')
- n['name'] = self._n['name'] + str(len(self._clones) + 1)
+ if not self.key.isdigit():
+ self.key = self.name
+
+ name = self._n['name'] + str(len(self.clones) + 1)
# Dummy value 99999 will be fixed later
- n['key'] = '99999' if self._key.isdigit() else n['name']
+ key = '99999' if self.key.isdigit() else name
# Clone
- port = self.__class__(self.get_parent(), n, self._dir)
- self._clones.append(port)
+ port_factory = self.parent_platform.get_new_port
+ port = port_factory(self.parent, direction=self._dir,
+ name=name, key=key,
+ master=self, cls_key='clone')
+
+ self.clones.append(port)
return port
def remove_clone(self, port):
@@ -340,37 +301,24 @@ class Port(Element):
Remove a cloned port (from the list of clones only)
Remove the index 0 of the master port name (and key9 if there are no more clones left
"""
- self._clones.remove(port)
+ self.clones.remove(port)
# Remove index from master port name if there are no more clones
- if not self._clones:
- self._name = self._n['name']
+ if not self.clones:
+ self.name = self._n['name']
# Also update key for none stream ports
- if not self._key.isdigit():
- self._key = self._name
-
- def get_name(self):
- number = ''
- if self.get_type() == 'bus':
- busses = filter(lambda a: a._dir == self._dir, self.get_parent().get_ports_gui())
- number = str(busses.index(self)) + '#' + str(len(self.get_associated_ports()))
- return self._name + number
-
- def get_key(self):
- return self._key
+ if not self.key.isdigit():
+ self.key = self.name
- @property
+ @lazy_property
def is_sink(self):
return self._dir == 'sink'
- @property
+ @lazy_property
def is_source(self):
return self._dir == 'source'
def get_type(self):
- return self.get_parent().resolve_dependencies(self._type)
-
- def get_domain(self):
- return self._domain
+ return self.parent_block.resolve_dependencies(self._type)
def get_hide(self):
return self._hide_evaluated
@@ -382,8 +330,8 @@ class Port(Element):
Returns:
a list of connection objects
"""
- connections = self.get_parent().get_parent().connections
- connections = filter(lambda c: c.get_source() is self or c.get_sink() is self, connections)
+ connections = self.parent_flowgraph.connections
+ connections = [c for c in connections if c.source_port is self or c.sink_port is self]
return connections
def get_enabled_connections(self):
@@ -393,22 +341,51 @@ class Port(Element):
Returns:
a list of connection objects
"""
- return filter(lambda c: c.get_enabled(), self.get_connections())
+ return [c for c in self.get_connections() if c.enabled]
def get_associated_ports(self):
if not self.get_type() == 'bus':
return [self]
+
+ block = self.parent_block
+ if self.is_source:
+ block_ports = block.sources
+ bus_structure = block.current_bus_structure['source']
else:
- if self.is_source:
- get_ports = self.get_parent().get_sources
- bus_structure = self.get_parent().current_bus_structure['source']
- else:
- get_ports = self.get_parent().get_sinks
- bus_structure = self.get_parent().current_bus_structure['sink']
-
- ports = [i for i in get_ports() if not i.get_type() == 'bus']
- if bus_structure:
- busses = [i for i in get_ports() if i.get_type() == 'bus']
- bus_index = busses.index(self)
- ports = filter(lambda a: ports.index(a) in bus_structure[bus_index], ports)
- return ports
+ block_ports = block.sinks
+ bus_structure = block.current_bus_structure['sink']
+
+ ports = [i for i in block_ports if not i.get_type() == 'bus']
+ if bus_structure:
+ bus_index = [i for i in block_ports if i.get_type() == 'bus'].index(self)
+ ports = [p for i, p in enumerate(ports) if i in bus_structure[bus_index]]
+ return ports
+
+
+class PortClone(Port):
+
+ is_clone = True
+
+ def __init__(self, parent, direction, master, name, key):
+ """
+ Make a new port from nested data.
+
+ Args:
+ block: the parent element
+ n: the nested odict
+ dir: the direction
+ """
+ Element.__init__(self, parent)
+ self.master = master
+ self.name = name
+ self._key = key
+ self._nports = '1'
+
+ def __getattr__(self, item):
+ return getattr(self.master, item)
+
+ def add_clone(self):
+ raise NotImplementedError()
+
+ def remove_clone(self, port):
+ raise NotImplementedError()
diff --git a/grc/core/generator/CMakeLists.txt b/grc/core/generator/CMakeLists.txt
deleted file mode 100644
index 492ad7c4ad..0000000000
--- a/grc/core/generator/CMakeLists.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright 2011 Free Software Foundation, Inc.
-#
-# This file is part of GNU Radio
-#
-# GNU Radio 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 3, or (at your option)
-# any later version.
-#
-# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to
-# the Free Software Foundation, Inc., 51 Franklin Street,
-# Boston, MA 02110-1301, USA.
-
-file(GLOB py_files "*.py")
-
-GR_PYTHON_INSTALL(
- FILES ${py_files}
- DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/core/generator
-)
-
-install(FILES
- flow_graph.tmpl
- DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/core/generator
-)
diff --git a/grc/core/generator/FlowGraphProxy.py b/grc/core/generator/FlowGraphProxy.py
index 3723005576..23ccf95c4b 100644
--- a/grc/core/generator/FlowGraphProxy.py
+++ b/grc/core/generator/FlowGraphProxy.py
@@ -16,13 +16,17 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-class FlowGraphProxy(object):
+from __future__ import absolute_import
+from six.moves import range
+
+
+class FlowGraphProxy(object): # TODO: move this in a refactored Generator
def __init__(self, fg):
- self._fg = fg
+ self.orignal_flowgraph = fg
def __getattr__(self, item):
- return getattr(self._fg, item)
+ return getattr(self.orignal_flowgraph, item)
def get_hier_block_stream_io(self, direction):
"""
@@ -34,7 +38,7 @@ class FlowGraphProxy(object):
Returns:
a list of dicts with: type, label, vlen, size, optional
"""
- return filter(lambda p: p['type'] != "message", self.get_hier_block_io(direction))
+ return [p for p in self.get_hier_block_io(direction) if p['type'] != "message"]
def get_hier_block_message_io(self, direction):
"""
@@ -46,7 +50,7 @@ class FlowGraphProxy(object):
Returns:
a list of dicts with: type, label, vlen, size, optional
"""
- return filter(lambda p: p['type'] == "message", self.get_hier_block_io(direction))
+ return [p for p in self.get_hier_block_io(direction) if p['type'] == "message"]
def get_hier_block_io(self, direction):
"""
@@ -66,12 +70,12 @@ class FlowGraphProxy(object):
'label': str(pad.get_param('label').get_evaluated()),
'type': str(pad.get_param('type').get_evaluated()),
'vlen': str(pad.get_param('vlen').get_value()),
- 'size': pad.get_param('type').get_opt('size'),
+ 'size': pad.get_param('type').opt_value('size'),
'optional': bool(pad.get_param('optional').get_evaluated()),
}
num_ports = pad.get_param('num_streams').get_evaluated()
if num_ports > 1:
- for i in xrange(num_ports):
+ for i in range(num_ports):
clone = master.copy()
clone['label'] += str(i)
ports.append(clone)
@@ -86,7 +90,7 @@ class FlowGraphProxy(object):
Returns:
a list of pad source blocks in this flow graph
"""
- pads = filter(lambda b: b.get_key() == 'pad_source', self.get_enabled_blocks())
+ pads = [b for b in self.get_enabled_blocks() if b.key == 'pad_source']
return sorted(pads, lambda x, y: cmp(x.get_id(), y.get_id()))
def get_pad_sinks(self):
@@ -96,7 +100,7 @@ class FlowGraphProxy(object):
Returns:
a list of pad sink blocks in this flow graph
"""
- pads = filter(lambda b: b.get_key() == 'pad_sink', self.get_enabled_blocks())
+ pads = [b for b in self.get_enabled_blocks() if b.key == 'pad_sink']
return sorted(pads, lambda x, y: cmp(x.get_id(), y.get_id()))
def get_pad_port_global_key(self, port):
@@ -113,14 +117,14 @@ class FlowGraphProxy(object):
# using the block param 'type' instead of the port domain here
# to emphasize that hier block generation is domain agnostic
is_message_pad = pad.get_param('type').get_evaluated() == "message"
- if port.get_parent() == pad:
+ if port.parent == pad:
if is_message_pad:
key = pad.get_param('label').get_value()
else:
- key = str(key_offset + int(port.get_key()))
+ key = str(key_offset + int(port.key))
return key
else:
# assuming we have either only sources or sinks
if not is_message_pad:
key_offset += len(pad.get_ports())
- return -1 \ No newline at end of file
+ return -1
diff --git a/grc/core/generator/Generator.py b/grc/core/generator/Generator.py
index dda226c6b2..316ed5014d 100644
--- a/grc/core/generator/Generator.py
+++ b/grc/core/generator/Generator.py
@@ -16,11 +16,16 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+from __future__ import absolute_import
+
import codecs
import os
import tempfile
+import operator
+import collections
from Cheetah.Template import Template
+import six
from .FlowGraphProxy import FlowGraphProxy
from .. import ParseXML, Messages
@@ -28,7 +33,7 @@ from ..Constants import (
TOP_BLOCK_FILE_MODE, BLOCK_FLAG_NEED_QT_GUI,
HIER_BLOCK_FILE_MODE, BLOCK_DTD
)
-from ..utils import expr_utils, odict
+from ..utils import expr_utils
DATA_DIR = os.path.dirname(__file__)
FLOW_GRAPH_TEMPLATE = os.path.join(DATA_DIR, 'flow_graph.tmpl')
@@ -83,20 +88,18 @@ class TopBlockGenerator(object):
self.file_path = os.path.join(dirname, filename)
self._dirname = dirname
- def get_file_path(self):
- return self.file_path
-
def write(self):
"""generate output and write it to files"""
# Do throttle warning
- throttling_blocks = filter(lambda b: b.throtteling(), self._flow_graph.get_enabled_blocks())
+ throttling_blocks = [b for b in self._flow_graph.get_enabled_blocks()
+ if b.is_throtteling]
if not throttling_blocks and not self._generate_options.startswith('hb'):
Messages.send_warning("This flow graph may not have flow control: "
"no audio or RF hardware blocks found. "
"Add a Misc->Throttle block to your flow "
"graph to avoid CPU congestion.")
if len(throttling_blocks) > 1:
- keys = set(map(lambda b: b.get_key(), throttling_blocks))
+ keys = set([b.key for b in throttling_blocks])
if len(keys) > 1 and 'blocks_throttle' in keys:
Messages.send_warning("This flow graph contains a throttle "
"block and another rate limiting block, "
@@ -139,18 +142,18 @@ class TopBlockGenerator(object):
return code
blocks_all = expr_utils.sort_objects(
- filter(lambda b: b.get_enabled() and not b.get_bypassed(), fg.blocks),
- lambda b: b.get_id(), _get_block_sort_text
+ [b for b in fg.blocks if b.enabled and not b.get_bypassed()],
+ operator.methodcaller('get_id'), _get_block_sort_text
)
- deprecated_block_keys = set(block.get_name() for block in blocks_all if block.is_deprecated)
+ deprecated_block_keys = set(b.name for b in blocks_all if b.is_deprecated)
for key in deprecated_block_keys:
Messages.send_warning("The block {!r} is deprecated.".format(key))
# List of regular blocks (all blocks minus the special ones)
- blocks = filter(lambda b: b not in (imports + parameters), blocks_all)
+ blocks = [b for b in blocks_all if b not in imports and b not in parameters]
for block in blocks:
- key = block.get_key()
+ key = block.key
file_path = os.path.join(self._dirname, block.get_id() + '.py')
if key == 'epy_block':
src = block.get_param('_source_code').get_value()
@@ -159,17 +162,17 @@ class TopBlockGenerator(object):
src = block.get_param('source_code').get_value()
output.append((file_path, src))
- # Filter out virtual sink connections
- def cf(c):
- return not (c.is_bus() or c.get_sink().get_parent().is_virtual_sink())
- connections = filter(cf, fg.get_enabled_connections())
+ # Filter out bus and virtual sink connections
+ connections = [con for con in fg.get_enabled_connections()
+ if not (con.is_bus() or con.sink_block.is_virtual_sink())]
# Get the virtual blocks and resolve their connections
- virtual = filter(lambda c: c.get_source().get_parent().is_virtual_source(), connections)
+ connection_factory = fg.parent_platform.Connection
+ virtual = [c for c in connections if c.source_block.is_virtual_source()]
for connection in virtual:
- sink = connection.get_sink()
- for source in connection.get_source().resolve_virtual_source():
- resolved = fg.get_parent().Connection(flow_graph=fg, porta=source, portb=sink)
+ sink = connection.sink_port
+ for source in connection.source_port.resolve_virtual_source():
+ resolved = connection_factory(fg.orignal_flowgraph, source, sink)
connections.append(resolved)
# Remove the virtual connection
connections.remove(connection)
@@ -183,20 +186,19 @@ class TopBlockGenerator(object):
for block in bypassed_blocks:
# Get the upstream connection (off of the sink ports)
# Use *connections* not get_connections()
- source_connection = filter(lambda c: c.get_sink() == block.get_sinks()[0], connections)
+ source_connection = [c for c in connections if c.sink_port == block.sinks[0]]
# The source connection should never have more than one element.
assert (len(source_connection) == 1)
# Get the source of the connection.
- source_port = source_connection[0].get_source()
+ source_port = source_connection[0].source_port
# Loop through all the downstream connections
- for sink in filter(lambda c: c.get_source() == block.get_sources()[0], connections):
- if not sink.get_enabled():
+ for sink in (c for c in connections if c.source_port == block.sources[0]):
+ if not sink.enabled:
# Ignore disabled connections
continue
- sink_port = sink.get_sink()
- connection = fg.get_parent().Connection(flow_graph=fg, porta=source_port, portb=sink_port)
+ connection = connection_factory(fg.orignal_flowgraph, source_port, sink.sink_port)
connections.append(connection)
# Remove this sink connection
connections.remove(sink)
@@ -205,11 +207,11 @@ class TopBlockGenerator(object):
# List of connections where each endpoint is enabled (sorted by domains, block names)
connections.sort(key=lambda c: (
- c.get_source().get_domain(), c.get_sink().get_domain(),
- c.get_source().get_parent().get_id(), c.get_sink().get_parent().get_id()
+ c.source_port.domain, c.sink_port.domain,
+ c.source_block.get_id(), c.sink_block.get_id()
))
- connection_templates = fg.get_parent().connection_templates
+ connection_templates = fg.parent.connection_templates
# List of variable names
var_ids = [var.get_id() for var in parameters + variables]
@@ -259,7 +261,7 @@ class HierBlockGenerator(TopBlockGenerator):
file_path: where to write the py file (the xml goes into HIER_BLOCK_LIB_DIR)
"""
TopBlockGenerator.__init__(self, flow_graph, file_path)
- platform = flow_graph.get_parent()
+ platform = flow_graph.parent
hier_block_lib_dir = platform.config.hier_block_lib_dir
if not os.path.exists(hier_block_lib_dir):
@@ -267,18 +269,15 @@ class HierBlockGenerator(TopBlockGenerator):
self._mode = HIER_BLOCK_FILE_MODE
self.file_path = os.path.join(hier_block_lib_dir, self._flow_graph.get_option('id') + '.py')
- self._file_path_xml = self.file_path + '.xml'
-
- def get_file_path_xml(self):
- return self._file_path_xml
+ self.file_path_xml = self.file_path + '.xml'
def write(self):
"""generate output and write it to files"""
TopBlockGenerator.write(self)
- ParseXML.to_file(self._build_block_n_from_flow_graph_io(), self.get_file_path_xml())
- ParseXML.validate_dtd(self.get_file_path_xml(), BLOCK_DTD)
+ ParseXML.to_file(self._build_block_n_from_flow_graph_io(), self.file_path_xml)
+ ParseXML.validate_dtd(self.file_path_xml, BLOCK_DTD)
try:
- os.chmod(self.get_file_path_xml(), self._mode)
+ os.chmod(self.file_path_xml, self._mode)
except:
pass
@@ -294,12 +293,12 @@ class HierBlockGenerator(TopBlockGenerator):
parameters = self._flow_graph.get_parameters()
def var_or_value(name):
- if name in map(lambda p: p.get_id(), parameters):
- return "$"+name
+ if name in (p.get_id() for p in parameters):
+ return "$" + name
return name
# Build the nested data
- block_n = odict()
+ block_n = collections.OrderedDict()
block_n['name'] = self._flow_graph.get_option('title') or \
self._flow_graph.get_option('id').replace('_', ' ').title()
block_n['key'] = block_key
@@ -324,7 +323,7 @@ class HierBlockGenerator(TopBlockGenerator):
# Parameters
block_n['param'] = list()
for param in parameters:
- param_n = odict()
+ param_n = collections.OrderedDict()
param_n['name'] = param.get_param('label').get_value() or param.get_id()
param_n['key'] = param.get_id()
param_n['value'] = param.get_param('value').get_value()
@@ -342,7 +341,7 @@ class HierBlockGenerator(TopBlockGenerator):
for direction in ('sink', 'source'):
block_n[direction] = list()
for port in self._flow_graph.get_hier_block_io(direction):
- port_n = odict()
+ port_n = collections.OrderedDict()
port_n['name'] = port['label']
port_n['type'] = port['type']
if port['type'] != "message":
@@ -375,14 +374,18 @@ class QtHierBlockGenerator(HierBlockGenerator):
def _build_block_n_from_flow_graph_io(self):
n = HierBlockGenerator._build_block_n_from_flow_graph_io(self)
- block_n = n['block']
+ block_n = collections.OrderedDict()
+
+ # insert flags after category
+ for key, value in six.iteritems(n['block']):
+ block_n[key] = value
+ if key == 'category':
+ block_n['flags'] = BLOCK_FLAG_NEED_QT_GUI
if not block_n['name'].upper().startswith('QT GUI'):
block_n['name'] = 'QT GUI ' + block_n['name']
- block_n.insert_after('category', 'flags', BLOCK_FLAG_NEED_QT_GUI)
-
- gui_hint_param = odict()
+ gui_hint_param = collections.OrderedDict()
gui_hint_param['name'] = 'GUI Hint'
gui_hint_param['key'] = 'gui_hint'
gui_hint_param['value'] = ''
@@ -394,4 +397,5 @@ class QtHierBlockGenerator(HierBlockGenerator):
"\n#set $win = 'self.%s' % $id"
"\n${gui_hint()($win)}"
)
- return n
+
+ return {'block': block_n}
diff --git a/grc/core/generator/__init__.py b/grc/core/generator/__init__.py
index f44b94a85d..98f410c8d4 100644
--- a/grc/core/generator/__init__.py
+++ b/grc/core/generator/__init__.py
@@ -15,4 +15,5 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-from Generator import Generator
+from __future__ import absolute_import
+from .Generator import Generator
diff --git a/grc/core/generator/flow_graph.tmpl b/grc/core/generator/flow_graph.tmpl
index 2adb555486..202362c925 100644
--- a/grc/core/generator/flow_graph.tmpl
+++ b/grc/core/generator/flow_graph.tmpl
@@ -206,17 +206,17 @@ gr.io_signaturev($(len($io_sigs)), $(len($io_sigs)), [$(', '.join($size_strs))])
$indent($blk.get_make())
#else
self.$blk.get_id() = $indent($blk.get_make())
- #if $blk.has_param('alias') and $blk.get_param('alias').get_evaluated()
- (self.$blk.get_id()).set_block_alias("$blk.get_param('alias').get_evaluated()")
+ #if 'alias' in $blk.params and $blk.params['alias'].get_evaluated()
+ (self.$blk.get_id()).set_block_alias("$blk.params['alias'].get_evaluated()")
#end if
- #if $blk.has_param('affinity') and $blk.get_param('affinity').get_evaluated()
- (self.$blk.get_id()).set_processor_affinity($blk.get_param('affinity').get_evaluated())
+ #if 'affinity' in $blk.params and $blk.params['affinity'].get_evaluated()
+ (self.$blk.get_id()).set_processor_affinity($blk.params['affinity'].get_evaluated())
#end if
- #if (len($blk.get_sources())>0) and $blk.has_param('minoutbuf') and (int($blk.get_param('minoutbuf').get_evaluated()) > 0)
- (self.$blk.get_id()).set_min_output_buffer($blk.get_param('minoutbuf').get_evaluated())
+ #if len($blk.sources) > 0 and 'minoutbuf' in $blk.params and int($blk.params['minoutbuf'].get_evaluated()) > 0
+ (self.$blk.get_id()).set_min_output_buffer($blk.params['minoutbuf'].get_evaluated())
#end if
- #if (len($blk.get_sources())>0) and $blk.has_param('maxoutbuf') and (int($blk.get_param('maxoutbuf').get_evaluated()) > 0)
- (self.$blk.get_id()).set_max_output_buffer($blk.get_param('maxoutbuf').get_evaluated())
+ #if len($blk.sources) > 0 and 'maxoutbuf' in $blk.params and int($blk.params['maxoutbuf'].get_evaluated()) > 0
+ (self.$blk.get_id()).set_max_output_buffer($blk.params['maxoutbuf'].get_evaluated())
#end if
#end if
#end for
@@ -226,12 +226,12 @@ gr.io_signaturev($(len($io_sigs)), $(len($io_sigs)), [$(', '.join($size_strs))])
## However, port names for IO pads should be self.
########################################################
#def make_port_sig($port)
- #if $port.get_parent().get_key() in ('pad_source', 'pad_sink')
+ #if $port.parent.key in ('pad_source', 'pad_sink')
#set block = 'self'
#set key = $flow_graph.get_pad_port_global_key($port)
#else
- #set block = 'self.' + $port.get_parent().get_id()
- #set key = $port.get_key()
+ #set block = 'self.' + $port.parent.get_id()
+ #set key = $port.key
#end if
#if not $key.isdigit()
#set key = repr($key)
@@ -245,9 +245,9 @@ gr.io_signaturev($(len($io_sigs)), $(len($io_sigs)), [$(', '.join($size_strs))])
$DIVIDER
#end if
#for $con in $connections
- #set global $source = $con.get_source()
- #set global $sink = $con.get_sink()
- #include source=$connection_templates[($source.get_domain(), $sink.get_domain())]
+ #set global $source = $con.source_port
+ #set global $sink = $con.sink_port
+ #include source=$connection_templates[($source.domain, $sink.domain)]
#end for
########################################################
@@ -377,8 +377,8 @@ def main(top_block_cls=$(class_name), options=None):
tb.wait()
qapp.aboutToQuit.connect(quitting)
#for $m in $monitors
- if $m.has_param('en'):
- if $m.get_param('en').get_value():
+ if 'en' in $m.params:
+ if $m.params['en'].get_value():
(tb.$m.get_id()).start()
else:
sys.stderr.write("Monitor '{0}' does not have an enable ('en') parameter.".format("tb.$m.get_id()"))
diff --git a/grc/core/utils/CMakeLists.txt b/grc/core/utils/CMakeLists.txt
deleted file mode 100644
index 3ba65258a5..0000000000
--- a/grc/core/utils/CMakeLists.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright 2015 Free Software Foundation, Inc.
-#
-# This file is part of GNU Radio
-#
-# GNU Radio 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 3, or (at your option)
-# any later version.
-#
-# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to
-# the Free Software Foundation, Inc., 51 Franklin Street,
-# Boston, MA 02110-1301, USA.
-
-file(GLOB py_files "*.py")
-
-GR_PYTHON_INSTALL(
- FILES ${py_files}
- DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/core/utils
-)
diff --git a/grc/core/utils/__init__.py b/grc/core/utils/__init__.py
index 6b23da2723..d095179a10 100644
--- a/grc/core/utils/__init__.py
+++ b/grc/core/utils/__init__.py
@@ -15,8 +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
-import expr_utils
-import epy_block_io
-import extract_docs
+from __future__ import absolute_import
-from odict import odict
+from . import expr_utils
+from . import epy_block_io
+from . import extract_docs
+
+from ._complexity import calculate_flowgraph_complexity
diff --git a/grc/core/utils/complexity.py b/grc/core/utils/_complexity.py
index baa8040db4..c0f3ae9de4 100644
--- a/grc/core/utils/complexity.py
+++ b/grc/core/utils/_complexity.py
@@ -4,12 +4,12 @@ def calculate_flowgraph_complexity(flowgraph):
dbal = 0
for block in flowgraph.blocks:
# Skip options block
- if block.get_key() == 'options':
+ if block.key == 'options':
continue
# Don't worry about optional sinks?
- sink_list = filter(lambda c: not c.get_optional(), block.get_sinks())
- source_list = filter(lambda c: not c.get_optional(), block.get_sources())
+ sink_list = [c for c in block.sinks if not c.get_optional()]
+ source_list = [c for c in block.sources if not c.get_optional()]
sinks = float(len(sink_list))
sources = float(len(source_list))
base = max(min(sinks, sources), 1)
@@ -22,14 +22,15 @@ def calculate_flowgraph_complexity(flowgraph):
multi = 1
# Connection ratio multiplier
- sink_multi = max(float(sum(map(lambda c: len(c.get_connections()), sink_list)) / max(sinks, 1.0)), 1.0)
- source_multi = max(float(sum(map(lambda c: len(c.get_connections()), source_list)) / max(sources, 1.0)), 1.0)
- dbal = dbal + (base * multi * sink_multi * source_multi)
+ sink_multi = max(float(sum(len(c.get_connections()) for c in sink_list) / max(sinks, 1.0)), 1.0)
+ source_multi = max(float(sum(len(c.get_connections()) for c in source_list) / max(sources, 1.0)), 1.0)
+ dbal += base * multi * sink_multi * source_multi
blocks = float(len(flowgraph.blocks))
connections = float(len(flowgraph.connections))
elements = blocks + connections
- disabled_connections = len(filter(lambda c: not c.get_enabled(), flowgraph.connections))
+ disabled_connections = sum(not c.enabled for c in flowgraph.connections)
+
variables = elements - blocks - connections
enabled = float(len(flowgraph.get_enabled_blocks()))
diff --git a/grc/core/utils/epy_block_io.py b/grc/core/utils/epy_block_io.py
index a094ab7ad5..823116adb9 100644
--- a/grc/core/utils/epy_block_io.py
+++ b/grc/core/utils/epy_block_io.py
@@ -1,7 +1,12 @@
+from __future__ import absolute_import
+
import inspect
import collections
+import six
+from six.moves import zip
+
TYPE_MAP = {
'complex64': 'complex', 'complex': 'complex',
@@ -32,10 +37,10 @@ def _ports(sigs, msgs):
def _find_block_class(source_code, cls):
ns = {}
try:
- exec source_code in ns
+ exec(source_code, ns)
except Exception as e:
raise ValueError("Can't interpret source code: " + str(e))
- for var in ns.itervalues():
+ for var in six.itervalues(ns):
if inspect.isclass(var) and issubclass(var, cls):
return var
raise ValueError('No python block class found in code')
@@ -53,7 +58,7 @@ def extract(cls):
spec = inspect.getargspec(cls.__init__)
init_args = spec.args[1:]
- defaults = map(repr, spec.defaults or ())
+ defaults = [repr(arg) for arg in (spec.defaults or ())]
doc = cls.__doc__ or cls.__init__.__doc__ or ''
cls_name = cls.__name__
diff --git a/grc/core/utils/expr_utils.py b/grc/core/utils/expr_utils.py
index 2059ceff9f..555bd709b1 100644
--- a/grc/core/utils/expr_utils.py
+++ b/grc/core/utils/expr_utils.py
@@ -17,7 +17,12 @@ 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
+
import string
+
+import six
+
VAR_CHARS = string.letters + string.digits + '_'
@@ -50,7 +55,7 @@ class graph(object):
self._graph[src_node_key].remove(dest_node_key)
def get_nodes(self):
- return self._graph.keys()
+ return list(self._graph.keys())
def get_edges(self, node_key):
return self._graph[node_key]
@@ -85,7 +90,7 @@ def expr_split(expr, var_chars=VAR_CHARS):
toks.append(char)
tok = ''
toks.append(tok)
- return filter(lambda t: t, toks)
+ return [t for t in toks if t]
def expr_replace(expr, replace_dict):
@@ -101,7 +106,7 @@ def expr_replace(expr, replace_dict):
"""
expr_splits = expr_split(expr, var_chars=VAR_CHARS + '.')
for i, es in enumerate(expr_splits):
- if es in replace_dict.keys():
+ if es in list(replace_dict.keys()):
expr_splits[i] = replace_dict[es]
return ''.join(expr_splits)
@@ -118,7 +123,7 @@ def get_variable_dependencies(expr, vars):
a subset of vars used in the expression
"""
expr_toks = expr_split(expr)
- return set(var for var in vars if var in expr_toks)
+ return set(v for v in vars if v in expr_toks)
def get_graph(exprs):
@@ -131,12 +136,12 @@ def get_graph(exprs):
Returns:
a graph of variable deps
"""
- vars = exprs.keys()
+ vars = list(exprs.keys())
# Get dependencies for each expression, load into graph
var_graph = graph()
for var in vars:
var_graph.add_node(var)
- for var, expr in exprs.iteritems():
+ for var, expr in six.iteritems(exprs):
for dep in get_variable_dependencies(expr, vars):
if dep != var:
var_graph.add_edge(dep, var)
@@ -159,7 +164,7 @@ def sort_variables(exprs):
# Determine dependency order
while var_graph.get_nodes():
# Get a list of nodes with no edges
- indep_vars = filter(lambda var: not var_graph.get_edges(var), var_graph.get_nodes())
+ indep_vars = [var for var in var_graph.get_nodes() if not var_graph.get_edges(var)]
if not indep_vars:
raise Exception('circular dependency caught in sort_variables')
# Add the indep vars to the end of the list
@@ -189,3 +194,4 @@ def sort_objects(objects, get_id, get_expr):
sorted_ids = sort_variables(id2expr)
# Return list of sorted objects
return [id2obj[id] for id in sorted_ids]
+
diff --git a/grc/core/utils/extract_docs.py b/grc/core/utils/extract_docs.py
index a6e0bc971e..cff8a81099 100644
--- a/grc/core/utils/extract_docs.py
+++ b/grc/core/utils/extract_docs.py
@@ -17,15 +17,19 @@ 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
+
import sys
import re
import subprocess
import threading
import json
-import Queue
import random
import itertools
+import six
+from six.moves import queue, filter, range
+
###############################################################################
# The docstring extraction
@@ -124,7 +128,7 @@ class SubprocessLoader(object):
self.callback_query_result = callback_query_result
self.callback_finished = callback_finished or (lambda: None)
- self._queue = Queue.Queue()
+ self._queue = queue.Queue()
self._thread = None
self._worker = None
self._shutdown = threading.Event()
@@ -157,14 +161,14 @@ class SubprocessLoader(object):
cmd, args = self._last_cmd
if cmd == 'query':
msg += " (crashed while loading {0!r})".format(args[0])
- print >> sys.stderr, msg
+ print(msg, file=sys.stderr)
continue # restart
else:
break # normal termination, return
finally:
self._worker.terminate()
else:
- print >> sys.stderr, "Warning: docstring loader crashed too often"
+ print("Warning: docstring loader crashed too often", file=sys.stderr)
self._thread = None
self._worker = None
self.callback_finished()
@@ -203,9 +207,9 @@ class SubprocessLoader(object):
key, docs = args
self.callback_query_result(key, docs)
elif cmd == 'error':
- print args
+ print(args)
else:
- print >> sys.stderr, "Unknown response:", cmd, args
+ print("Unknown response:", cmd, args, file=sys.stderr)
def query(self, key, imports=None, make=None):
""" Request docstring extraction for a certain key """
@@ -270,12 +274,12 @@ if __name__ == '__worker__':
elif __name__ == '__main__':
def callback(key, docs):
- print key
- for match, doc in docs.iteritems():
- print '-->', match
- print doc.strip()
- print
- print
+ print(key)
+ for match, doc in six.iteritems(docs):
+ print('-->', match)
+ print(doc.strip())
+ print()
+ print()
r = SubprocessLoader(callback)
diff --git a/grc/core/utils/odict.py b/grc/core/utils/odict.py
deleted file mode 100644
index 85927e869f..0000000000
--- a/grc/core/utils/odict.py
+++ /dev/null
@@ -1,115 +0,0 @@
-"""
-Copyright 2008-2015 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 UserDict import DictMixin
-
-
-class odict(DictMixin):
-
- def __init__(self, d={}):
- self._keys = list(d.keys())
- self._data = dict(d.copy())
-
- def __setitem__(self, key, value):
- if key not in self._data:
- self._keys.append(key)
- self._data[key] = value
-
- def __getitem__(self, key):
- return self._data[key]
-
- def __delitem__(self, key):
- del self._data[key]
- self._keys.remove(key)
-
- def keys(self):
- return list(self._keys)
-
- def copy(self):
- copy_dict = odict()
- copy_dict._data = self._data.copy()
- copy_dict._keys = list(self._keys)
- return copy_dict
-
- def insert_after(self, pos_key, key, val):
- """
- Insert the new key, value entry after the entry given by the position key.
- If the positional key is None, insert at the end.
-
- Args:
- pos_key: the positional key
- key: the key for the new entry
- val: the value for the new entry
- """
- index = (pos_key is None) and len(self._keys) or self._keys.index(pos_key)
- if key in self._keys:
- raise KeyError('Cannot insert, key "{}" already exists'.format(str(key)))
- self._keys.insert(index+1, key)
- self._data[key] = val
-
- def insert_before(self, pos_key, key, val):
- """
- Insert the new key, value entry before the entry given by the position key.
- If the positional key is None, insert at the begining.
-
- Args:
- pos_key: the positional key
- key: the key for the new entry
- val: the value for the new entry
- """
- index = (pos_key is not None) and self._keys.index(pos_key) or 0
- if key in self._keys:
- raise KeyError('Cannot insert, key "{}" already exists'.format(str(key)))
- self._keys.insert(index, key)
- self._data[key] = val
-
- def find(self, key):
- """
- Get the value for this key if exists.
-
- Args:
- key: the key to search for
-
- Returns:
- the value or None
- """
- if key in self:
- return self[key]
- return None
-
- def findall(self, key):
- """
- Get a list of values for this key.
-
- Args:
- key: the key to search for
-
- Returns:
- a list of values or empty list
- """
- obj = self.find(key)
- if obj is None:
- obj = list()
- if isinstance(obj, list):
- return obj
- return [obj]
-
- def clear(self):
- self._data.clear()
- del self._keys[:] \ No newline at end of file