summaryrefslogtreecommitdiff
path: root/grc/core/Param.py
diff options
context:
space:
mode:
Diffstat (limited to 'grc/core/Param.py')
-rw-r--r--grc/core/Param.py481
1 files changed, 206 insertions, 275 deletions
diff --git a/grc/core/Param.py b/grc/core/Param.py
index be86f0aecb..e8c81383f3 100644
--- a/grc/core/Param.py
+++ b/grc/core/Param.py
@@ -20,27 +20,27 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
from __future__ import absolute_import
import ast
+import numbers
import re
import collections
+import six
from six.moves import builtins, filter, map, range, zip
-from . import Constants
-from .Element import Element, nop_write
+from . import Constants, blocks
+from .base import Element
+from .utils.descriptors import Evaluated, EvaluatedEnum, setup_names
# Blacklist certain ids, its not complete, but should help
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('_'))
-except ImportError:
+except (ImportError, AttributeError):
pass
-_check_id_matcher = re.compile('^[a-z|A-Z]\w*$')
-_show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook)$')
-
-class TemplateArg(object):
+class TemplateArg(str):
"""
A cheetah template argument created from a param.
The str of this class evaluates to the param's to code method.
@@ -48,123 +48,119 @@ class TemplateArg(object):
The __call__ or () method can return the param evaluated to a raw python data type.
"""
- def __init__(self, param):
- self._param = param
+ def __new__(cls, param):
+ value = param.to_code()
+ instance = str.__new__(cls, value)
+ setattr(instance, '_param', param)
+ return instance
- def __getitem__(self, item):
+ def __getattr__(self, item):
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())
+ attributes = param.options.attributes[param.get_value()]
+ return str(attributes.get(item)) or NotImplemented
def __call__(self):
return self._param.get_evaluated()
+@setup_names
class Param(Element):
is_param = True
- def __init__(self, parent, key, name, type='raw', value='', **n):
+ name = Evaluated(str, default='no name')
+ dtype = EvaluatedEnum(Constants.PARAM_TYPE_NAMES, default='raw')
+ hide = EvaluatedEnum('none all part')
+
+ # region init
+ def __init__(self, parent, id, label='', dtype='raw', default='',
+ options=None, option_labels=None, option_attributes=None,
+ category='', hide='none', **_):
"""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
+ self.key = id
+ self.name = label.strip() or id.title()
+ self.category = category or Constants.DEFAULT_PARAM_TAB
- self._hide = n.get('hide', '')
- self.tab_label = n.get('tab', Constants.DEFAULT_PARAM_TAB)
- self._evaluated = None
+ self.dtype = dtype
+ self.value = self.default = str(default)
- self.options = []
- self.options_names = []
- self.options_opts = {}
- self._init_options(options_n=n.get('option', []))
+ self.options = self._init_options(options or [], option_labels or [],
+ option_attributes or {})
+ self.hide = hide or 'none'
+ # end of args ########################################################
+ self._evaluated = None
+ self._stringify_flag = False
+ self._lisitify_flag = False
self._init = False
- self._hostage_cells = list()
- self.template_arg = TemplateArg(self)
-
- 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)
- if self.is_enum():
- self._init_enum(options_n)
+ @property
+ def template_arg(self):
+ return TemplateArg(self)
+
+ def _init_options(self, values, labels, attributes):
+ """parse option and option attributes"""
+ options = collections.OrderedDict()
+ options.attributes = collections.defaultdict(dict)
- 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', [])
+ padding = [''] * max(len(values), len(labels))
+ attributes = {key: value + padding for key, value in six.iteritems(attributes)}
+
+ for i, option in enumerate(values):
+ # Test against repeated keys
+ if option in options:
+ raise KeyError('Value "{}" already exists in options'.format(option))
+ # get label
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)))
+ label = str(labels[i])
+ except IndexError:
+ label = str(option)
+ # Store the option
+ options[option] = label
+ options.attributes[option] = {attrib: values[i] for attrib, values in six.iteritems(attributes)}
+
+ default = next(iter(options)) if options else ''
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
+ self.value = self.default = default
+
+ if self.is_enum() and self.value not in options:
+ self.value = self.default = default # TODO: warn
# raise ValueError('The value {!r} is not in the possible values of {}.'
# ''.format(self.get_value(), ', '.join(self.options)))
+ return options
+ # endregion
def __str__(self):
return 'Param - {}({})'.format(self.name, self.key)
- def get_hide(self):
- """
- Get the hide value from the base class.
- Hide the ID parameter for most blocks. Exceptions below.
- If the parameter controls a port type, vlen, or nports, return part.
- If the parameter is an empty grid position, return part.
- These parameters are redundant to display in the flow graph view.
+ def __repr__(self):
+ return '{!r}.param[{}]'.format(self.parent, self.key)
- Returns:
- hide the hide property string
- """
- hide = self.parent.resolve_dependencies(self._hide).strip()
- if hide:
- return hide
- # Hide ID in non variable blocks
- if self.key == 'id' and not _show_id_matcher.match(self.parent.key):
- return 'part'
- # Hide port controllers for type and nports
- 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.key in ' '.join(p._vlen for p in self.parent.get_ports()):
- try:
- if int(self.get_evaluated()) == 1:
- return 'part'
- except:
- pass
- return hide
+ def is_enum(self):
+ return self.get_raw('dtype') == 'enum'
- def validate(self):
- """
- Validate the param.
- The value must be evaluated and type must a possible type.
- """
- Element.validate(self)
- if self.get_type() not in Constants.PARAM_TYPE_NAMES:
- self.add_error_message('Type "{}" is not a possible type.'.format(self.get_type()))
+ def get_value(self):
+ value = self.value
+ if self.is_enum() and value not in self.options:
+ value = self.default
+ self.set_value(value)
+ return value
+
+ def set_value(self, value):
+ # Must be a string
+ self.value = str(value)
+
+ def set_default(self, value):
+ if self.default == self.value:
+ self.set_value(value)
+ self.default = str(value)
+
+ def rewrite(self):
+ Element.rewrite(self)
+ del self.name
+ del self.dtype
+ del self.hide
self._evaluated = None
try:
@@ -172,6 +168,15 @@ class Param(Element):
except Exception as e:
self.add_error_message(str(e))
+ def validate(self):
+ """
+ Validate the param.
+ The value must be evaluated and type must a possible type.
+ """
+ Element.validate(self)
+ if self.dtype not in Constants.PARAM_TYPE_NAMES:
+ self.add_error_message('Type "{}" is not a possible type.'.format(self.dtype))
+
def get_evaluated(self):
return self._evaluated
@@ -185,150 +190,112 @@ class Param(Element):
self._init = True
self._lisitify_flag = False
self._stringify_flag = False
- self._hostage_cells = list()
- t = self.get_type()
- v = self.get_value()
+ dtype = self.dtype
+ expr = self.get_value()
#########################
# Enum Type
#########################
if self.is_enum():
- return v
+ return expr
#########################
# Numeric Types
#########################
- elif t in ('raw', 'complex', 'real', 'float', 'int', 'hex', 'bool'):
+ elif dtype in ('raw', 'complex', 'real', 'float', 'int', 'hex', 'bool'):
# Raise exception if python cannot evaluate this value
try:
- e = self.parent_flowgraph.evaluate(v)
- except Exception as e:
- raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e))
+ value = self.parent_flowgraph.evaluate(expr)
+ except Exception as value:
+ raise Exception('Value "{}" cannot be evaluated:\n{}'.format(expr, value))
# Raise an exception if the data is invalid
- if t == 'raw':
- return e
- elif t == 'complex':
- 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, Constants.REAL_TYPES):
- raise Exception('Expression "{}" is invalid for type float.'.format(str(e)))
- return e
- elif t == 'int':
- if not isinstance(e, Constants.INT_TYPES):
- raise Exception('Expression "{}" is invalid for type integer.'.format(str(e)))
- return e
- elif t == 'hex':
- return hex(e)
- elif t == 'bool':
- if not isinstance(e, bool):
- raise Exception('Expression "{}" is invalid for type bool.'.format(str(e)))
- return e
+ if dtype == 'raw':
+ return value
+ elif dtype == 'complex':
+ if not isinstance(value, Constants.COMPLEX_TYPES):
+ raise Exception('Expression "{}" is invalid for type complex.'.format(str(value)))
+ return value
+ elif dtype in ('real', 'float'):
+ if not isinstance(value, Constants.REAL_TYPES):
+ raise Exception('Expression "{}" is invalid for type float.'.format(str(value)))
+ return value
+ elif dtype == 'int':
+ if not isinstance(value, Constants.INT_TYPES):
+ raise Exception('Expression "{}" is invalid for type integer.'.format(str(value)))
+ return value
+ elif dtype == 'hex':
+ return hex(value)
+ elif dtype == 'bool':
+ if not isinstance(value, bool):
+ raise Exception('Expression "{}" is invalid for type bool.'.format(str(value)))
+ return value
else:
- raise TypeError('Type "{}" not handled'.format(t))
+ raise TypeError('Type "{}" not handled'.format(dtype))
#########################
# Numeric Vector Types
#########################
- elif t in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'):
- if not v:
- # Turn a blank string into an empty list, so it will eval
- v = '()'
- # Raise exception if python cannot evaluate this value
+ elif dtype in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'):
+ default = []
+
+ if not expr:
+ return default # Turn a blank string into an empty list, so it will eval
+
try:
- e = self.parent.parent.evaluate(v)
- except Exception as e:
- raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e))
+ value = self.parent.parent.evaluate(expr)
+ except Exception as value:
+ raise Exception('Value "{}" cannot be evaluated:\n{}'.format(expr, value))
+
+ if not isinstance(value, Constants.VECTOR_TYPES):
+ self._lisitify_flag = True
+ value = [value]
+
# Raise an exception if the data is invalid
- if t == 'complex_vector':
- if not isinstance(e, Constants.VECTOR_TYPES):
- self._lisitify_flag = True
- e = [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, Constants.VECTOR_TYPES):
- self._lisitify_flag = True
- e = [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, Constants.VECTOR_TYPES):
- self._lisitify_flag = True
- e = [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
+ if dtype == 'complex_vector' and not all(isinstance(item, numbers.Complex) for item in value):
+ raise Exception('Expression "{}" is invalid for type complex vector.'.format(value))
+ elif dtype in ('real_vector', 'float_vector') and not all(isinstance(item, numbers.Real) for item in value):
+ raise Exception('Expression "{}" is invalid for type float vector.'.format(value))
+ elif dtype == 'int_vector' and not all(isinstance(item, Constants.INT_TYPES) for item in value):
+ raise Exception('Expression "{}" is invalid for type integer vector.'.format(str(value)))
+ return value
#########################
# String Types
#########################
- elif t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'):
+ elif dtype 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.parent.parent.evaluate(v)
- if not isinstance(e, str):
+ value = self.parent.parent.evaluate(expr)
+ if not isinstance(value, str):
raise Exception()
except:
self._stringify_flag = True
- e = str(v)
- if t == '_multiline_python_external':
- ast.parse(e) # Raises SyntaxError
- return e
+ value = str(expr)
+ if dtype == '_multiline_python_external':
+ ast.parse(value) # Raises SyntaxError
+ return value
#########################
# Unique ID Type
#########################
- elif t == 'id':
- # Can python use this as a variable?
- if not _check_id_matcher.match(v):
- raise Exception('ID "{}" must begin with a letter and may contain letters, numbers, and underscores.'.format(v))
- ids = [param.get_value() for param in self.get_all_params(t, 'id')]
-
- if v in ID_BLACKLIST:
- raise Exception('ID "{}" is blacklisted.'.format(v))
-
- 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))
- else:
- # Id should exist to be a reference
- if ids.count(v) < 1:
- raise Exception('ID "{}" does not exist.'.format(v))
-
- return v
+ elif dtype == 'id':
+ self.validate_block_id()
+ return expr
#########################
# Stream ID Type
#########################
- 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.parent.is_virtual_sink(),
- self.get_all_params(t),
- )]
- # Check that the virtual sink's stream id is unique
- 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.parent.is_virtual_source():
- if v not in ids:
- raise Exception('Stream ID "{}" is not found.'.format(v))
- return v
+ elif dtype == 'stream_id':
+ self.validate_stream_id()
+ return expr
#########################
# GUI Position/Hint
#########################
- elif t == 'gui_hint':
- if ':' in v:
- tab, pos = v.split(':')
- elif '@' in v:
- tab, pos = v, ''
+ elif dtype == 'gui_hint':
+ if ':' in expr:
+ tab, pos = expr.split(':')
+ elif '@' in expr:
+ tab, pos = expr, ''
else:
- tab, pos = '', v
+ tab, pos = '', expr
if '@' in tab:
tab, index = tab.split('@')
@@ -358,20 +325,51 @@ class Param(Element):
#########################
# Import Type
#########################
- elif t == 'import':
+ elif dtype == 'import':
# New namespace
n = dict()
try:
- exec(v, n)
+ exec(expr, n)
except ImportError:
- raise Exception('Import "{}" failed.'.format(v))
+ raise Exception('Import "{}" failed.'.format(expr))
except Exception:
- raise Exception('Bad import syntax: "{}".'.format(v))
+ raise Exception('Bad import syntax: "{}".'.format(expr))
return [k for k in list(n.keys()) if str(k) != '__builtins__']
#########################
else:
- raise TypeError('Type "{}" not handled'.format(t))
+ raise TypeError('Type "{}" not handled'.format(dtype))
+
+ def validate_block_id(self):
+ value = self.value
+ # Can python use this as a variable?
+ if not re.match(r'^[a-z|A-Z]\w*$', value):
+ raise Exception('ID "{}" must begin with a letter and may contain letters, numbers, '
+ 'and underscores.'.format(value))
+ if value in ID_BLACKLIST:
+ raise Exception('ID "{}" is blacklisted.'.format(value))
+ block_names = [block.name for block in self.parent_flowgraph.iter_enabled_blocks()]
+ # Id should only appear once, or zero times if block is disabled
+ if self.key == 'id' and block_names.count(value) > 1:
+ raise Exception('ID "{}" is not unique.'.format(value))
+ elif value not in block_names:
+ raise Exception('ID "{}" does not exist.'.format(value))
+ return value
+
+ def validate_stream_id(self):
+ value = self.value
+ stream_ids = [
+ block.params['stream_id'].value
+ for block in self.parent_flowgraph.iter_enabled_blocks()
+ if isinstance(block, blocks.VirtualSink)
+ ]
+ # Check that the virtual sink's stream id is unique
+ if isinstance(self.parent_block, blocks.VirtualSink) and stream_ids.count(value) >= 2:
+ # Id should only appear once, or zero times if block is disabled
+ raise Exception('Stream ID "{}" is not unique.'.format(value))
+ # Check that the virtual source's steam id is found
+ elif isinstance(self.parent_block, blocks.VirtualSource) and value not in stream_ids:
+ raise Exception('Stream ID "{}" is not found.'.format(value))
def to_code(self):
"""
@@ -382,8 +380,9 @@ class Param(Element):
Returns:
a string representing the code
"""
+ self._init = True
v = self.get_value()
- t = self.get_type()
+ t = self.dtype
# String types
if t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'):
if not self._init:
@@ -400,71 +399,3 @@ class Param(Element):
return '(%s)' % v
else:
return v
-
- def get_all_params(self, type, key=None):
- """
- Get all the params from the flowgraph that have the given type and
- optionally a given key
-
- Args:
- type: the specified type
- key: the key to match against
-
- Returns:
- a list of params
- """
- 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.options:
- value = self.options[0]
- self.set_value(value)
- return value
-
- def set_value(self, value):
- # Must be a string
- self.value = str(value)
-
- def set_default(self, value):
- if self.default == self.value:
- self.set_value(value)
- self.default = str(value)
-
- def get_type(self):
- return self.parent.resolve_dependencies(self._type)
-
- def get_tab_label(self):
- return self.tab_label
-
- @nop_write
- @property
- def name(self):
- return self.parent.resolve_dependencies(self._name).strip()
-
- ##############################################
- # Access Options
- ##############################################
- def opt_value(self, key):
- return self.options_opts[self.get_value()][key]
-
- ##############################################
- # Import/Export Methods
- ##############################################
- def export_data(self):
- """
- Export this param's key/value.
-
- Returns:
- a nested data odict
- """
- n = collections.OrderedDict()
- n['key'] = self.key
- n['value'] = self.get_value()
- return n