diff options
Diffstat (limited to 'grc/core')
-rw-r--r-- | grc/core/Block.py | 1182 | ||||
-rw-r--r-- | grc/core/CMakeLists.txt | 35 | ||||
-rw-r--r-- | grc/core/Config.py | 29 | ||||
-rw-r--r-- | grc/core/Connection.py | 146 | ||||
-rw-r--r-- | grc/core/Constants.py | 96 | ||||
-rw-r--r-- | grc/core/Element.py | 124 | ||||
-rw-r--r-- | grc/core/Element.pyi | 35 | ||||
-rw-r--r-- | grc/core/FlowGraph.py | 257 | ||||
-rw-r--r-- | grc/core/Messages.py | 7 | ||||
-rw-r--r-- | grc/core/Param.py | 427 | ||||
-rw-r--r-- | grc/core/ParseXML.py | 67 | ||||
-rw-r--r-- | grc/core/Platform.py | 160 | ||||
-rw-r--r-- | grc/core/Port.py | 261 | ||||
-rw-r--r-- | grc/core/generator/CMakeLists.txt | 30 | ||||
-rw-r--r-- | grc/core/generator/FlowGraphProxy.py | 28 | ||||
-rw-r--r-- | grc/core/generator/Generator.py | 96 | ||||
-rw-r--r-- | grc/core/generator/__init__.py | 3 | ||||
-rw-r--r-- | grc/core/generator/flow_graph.tmpl | 32 | ||||
-rw-r--r-- | grc/core/utils/CMakeLists.txt | 25 | ||||
-rw-r--r-- | grc/core/utils/__init__.py | 10 | ||||
-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.py | 11 | ||||
-rw-r--r-- | grc/core/utils/expr_utils.py | 20 | ||||
-rw-r--r-- | grc/core/utils/extract_docs.py | 28 | ||||
-rw-r--r-- | grc/core/utils/odict.py | 115 |
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 |