From cd9c3479e17fbdb84918e255cf6de74edf0ceab1 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <sebastian.koslowski@gmail.com>
Date: Tue, 3 Oct 2017 18:47:06 +0200
Subject: grc: refactor: move Param cls to core.params

---
 grc/gui/canvas/param.py | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

(limited to 'grc/gui/canvas/param.py')

diff --git a/grc/gui/canvas/param.py b/grc/gui/canvas/param.py
index e2c335d9cf..845ff5a926 100644
--- a/grc/gui/canvas/param.py
+++ b/grc/gui/canvas/param.py
@@ -18,10 +18,8 @@
 from __future__ import absolute_import
 
 from .drawable import Drawable
-
 from .. import ParamWidgets, Utils, Constants
-
-from ...core.Param import Param as CoreParam
+from ...core.params import Param as CoreParam
 
 
 class Param(CoreParam):
-- 
cgit v1.2.3


From 14d79b777e773e299a1ffa0dd12d2508a46370a0 Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <sebastian.koslowski@gmail.com>
Date: Wed, 8 Nov 2017 21:28:35 +0100
Subject: grc: move some of the param checkers to separate file

---
 grc/core/Constants.py                   |  20 ++---
 grc/core/params/dtypes.py               | 103 +++++++++++++++++++++
 grc/core/params/param.py                | 154 ++++++++------------------------
 grc/core/utils/descriptors/evaluated.py |   7 +-
 grc/gui/Utils.py                        |   4 +-
 grc/gui/canvas/param.py                 |   4 +-
 6 files changed, 161 insertions(+), 131 deletions(-)
 create mode 100644 grc/core/params/dtypes.py

(limited to 'grc/gui/canvas/param.py')

diff --git a/grc/core/Constants.py b/grc/core/Constants.py
index 59c5dff35e..8ed8899c70 100644
--- a/grc/core/Constants.py
+++ b/grc/core/Constants.py
@@ -20,6 +20,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 from __future__ import absolute_import
 
 import os
+import numbers
 import stat
 
 import numpy
@@ -54,7 +55,7 @@ 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 = (
+PARAM_TYPE_NAMES = {
     'raw', 'enum',
     'complex', 'real', 'float', 'int',
     'complex_vector', 'real_vector', 'float_vector', 'int_vector',
@@ -63,18 +64,17 @@ PARAM_TYPE_NAMES = (
     'id', 'stream_id',
     'gui_hint',
     'import',
-)
+}
+
+PARAM_TYPE_MAP = {
+    'complex': numbers.Complex,
+    'float': numbers.Real,
+    'real': numbers.Real,
+    'int': numbers.Integral,
+}
 
 # 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, 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 palette from:
 #   http://www.google.com/design/spec/style/color.html#color-color-palette
diff --git a/grc/core/params/dtypes.py b/grc/core/params/dtypes.py
new file mode 100644
index 0000000000..cddfdefe45
--- /dev/null
+++ b/grc/core/params/dtypes.py
@@ -0,0 +1,103 @@
+# Copyright 2008-2017 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 __future__ import absolute_import
+
+import re
+
+from six.moves import builtins
+
+from .. import blocks
+from .. import Constants
+
+
+# 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, AttributeError):
+    pass
+
+
+validators = {}
+
+
+def validates(*dtypes):
+    def decorator(func):
+        for dtype in dtypes:
+            assert dtype in Constants.PARAM_TYPE_NAMES
+            validators[dtype] = func
+        return func
+    return decorator
+
+
+class ValidateError(Exception):
+    """Raised by validate functions"""
+
+
+@validates('id')
+def validate_block_id(param):
+    value = param.value
+    # Can python use this as a variable?
+    if not re.match(r'^[a-z|A-Z]\w*$', value):
+        raise ValidateError('ID "{}" must begin with a letter and may contain letters, numbers, '
+                            'and underscores.'.format(value))
+    if value in ID_BLACKLIST:
+        raise ValidateError('ID "{}" is blacklisted.'.format(value))
+    block_names = [block.name for block in param.parent_flowgraph.iter_enabled_blocks()]
+    # Id should only appear once, or zero times if block is disabled
+    if param.key == 'id' and block_names.count(value) > 1:
+        raise ValidateError('ID "{}" is not unique.'.format(value))
+    elif value not in block_names:
+        raise ValidateError('ID "{}" does not exist.'.format(value))
+    return value
+
+
+@validates('stream_id')
+def validate_stream_id(param):
+    value = param.value
+    stream_ids = [
+        block.params['stream_id'].value
+        for block in param.parent_flowgraph.iter_enabled_blocks()
+        if isinstance(block, blocks.VirtualSink)
+    ]
+    # Check that the virtual sink's stream id is unique
+    if isinstance(param.parent_block, blocks.VirtualSink) and stream_ids.count(value) >= 2:
+        # Id should only appear once, or zero times if block is disabled
+        raise ValidateError('Stream ID "{}" is not unique.'.format(value))
+    # Check that the virtual source's steam id is found
+    elif isinstance(param.parent_block, blocks.VirtualSource) and value not in stream_ids:
+        raise ValidateError('Stream ID "{}" is not found.'.format(value))
+
+
+@validates('complex', 'real', 'float', 'int')
+def validate_scalar(param):
+    valid_types = Constants.PARAM_TYPE_MAP[param.dtype]
+    if not isinstance(param.value, valid_types):
+        raise ValidateError('Expression {!r} is invalid for type {!r}.'.format(
+            param.value, param.dtype))
+
+
+@validates('complex_vector', 'real_vector', 'float_vector', 'int_vector')
+def validate_vector(param):
+    # todo: check vector types
+
+    valid_types = Constants.PARAM_TYPE_MAP[param.dtype.split('_', 1)[0]]
+    if not all(isinstance(item, valid_types) for item in param.value):
+        raise ValidateError('Expression {!r} is invalid for type {!r}.'.format(
+            param.value, param.dtype))
diff --git a/grc/core/params/param.py b/grc/core/params/param.py
index 787be9a19f..30a48bb434 100644
--- a/grc/core/params/param.py
+++ b/grc/core/params/param.py
@@ -18,28 +18,19 @@
 from __future__ import absolute_import
 
 import ast
-import numbers
-import re
 import collections
 import textwrap
 
 import six
-from six.moves import builtins, range
+from six.moves import range
 
-from .. import Constants, blocks
+from .. import Constants
 from ..base import Element
 from ..utils.descriptors import Evaluated, EvaluatedEnum, setup_names
 
+from . import dtypes
 from .template_arg import TemplateArg
 
-# 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, AttributeError):
-    pass
-
 
 @setup_names
 class Param(Element):
@@ -147,6 +138,10 @@ class Param(Element):
         except Exception as e:
             self.add_error_message(str(e))
 
+        rewriter = getattr(dtypes, 'rewrite_' + self.dtype, None)
+        if rewriter:
+            rewriter(self)
+
     def validate(self):
         """
         Validate the param.
@@ -156,6 +151,13 @@ class Param(Element):
         if self.dtype not in Constants.PARAM_TYPE_NAMES:
             self.add_error_message('Type "{}" is not a possible type.'.format(self.dtype))
 
+        validator = dtypes.validators.get(self.dtype, None)
+        if self._init and validator:
+            try:
+                validator(self)
+            except dtypes.ValidateError as e:
+                self.add_error_message(e.message)
+
     def get_evaluated(self):
         return self._evaluated
 
@@ -173,68 +175,41 @@ class Param(Element):
         expr = self.get_value()
 
         #########################
-        # Enum Type
+        # ID and Enum types (not evaled)
         #########################
-        if self.is_enum():
+        if dtype in ('id', 'stream_id') or self.is_enum():
             return expr
 
         #########################
         # Numeric Types
         #########################
         elif dtype in ('raw', 'complex', 'real', 'float', 'int', 'hex', 'bool'):
-            # Raise exception if python cannot evaluate this value
-            try:
-                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 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
+            if expr:
+                try:
+                    value = self.parent_flowgraph.evaluate(expr)
+                except Exception as e:
+                    raise Exception('Value "{}" cannot be evaluated:\n{}'.format(expr, e))
             else:
-                raise TypeError('Type "{}" not handled'.format(dtype))
+                value = 0
+            if dtype == 'hex':
+                value = hex(value)
+            elif dtype == 'bool':
+                value = bool(value)
+            return value
+
         #########################
         # Numeric Vector Types
         #########################
         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
-
+                return []   # Turn a blank string into an empty list, so it will eval
             try:
                 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 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
@@ -242,7 +217,7 @@ class Param(Element):
         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:
-                value = self.parent.parent.evaluate(expr)
+                value = self.parent_flowgraph.evaluate(expr)
                 if not isinstance(value, str):
                     raise Exception()
             except:
@@ -252,28 +227,10 @@ class Param(Element):
                 ast.parse(value)  # Raises SyntaxError
             return value
         #########################
-        # Unique ID Type
-        #########################
-        elif dtype == 'id':
-            self.validate_block_id()
-            return expr
-
-        #########################
-        # Stream ID Type
-        #########################
-        elif dtype == 'stream_id':
-            self.validate_stream_id()
-            return expr
-
-        #########################
         # GUI Position/Hint
         #########################
         elif dtype == 'gui_hint':
-            if self.parent_block.state == 'disabled':
-                return ''
-            else:
-                return self.parse_gui_hint(expr)
-
+            return self.parse_gui_hint(expr) if self.parent_block.state == 'enabled' else ''
         #########################
         # Import Type
         #########################
@@ -292,37 +249,6 @@ class Param(Element):
         else:
             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):
         """
         Convert the value to code.
@@ -333,24 +259,20 @@ class Param(Element):
             a string representing the code
         """
         self._init = True
-        v = self.get_value()
-        t = self.dtype
+        value = self.get_value()
         # String types
-        if t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'):
+        if self.dtype in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'):
             if not self._init:
                 self.evaluate()
-            return repr(v) if self._stringify_flag else v
+            return repr(value) if self._stringify_flag else value
 
         # Vector types
-        elif t in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'):
+        elif self.dtype in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'):
             if not self._init:
                 self.evaluate()
-            if self._lisitify_flag:
-                return '(%s, )' % v
-            else:
-                return '(%s)' % v
+            return '[' + value + ']' if self._lisitify_flag else value
         else:
-            return v
+            return value
 
     def get_opt(self, item):
         return self.options.attributes[self.get_value()][item]
@@ -470,7 +392,7 @@ class Param(Element):
         optionally a given key
 
         Args:
-            type: the specified type
+            dtype: the specified type
             key: the key to match against
 
         Returns:
diff --git a/grc/core/utils/descriptors/evaluated.py b/grc/core/utils/descriptors/evaluated.py
index e8bce6e6ed..0e1b68761c 100644
--- a/grc/core/utils/descriptors/evaluated.py
+++ b/grc/core/utils/descriptors/evaluated.py
@@ -79,9 +79,10 @@ class Evaluated(object):
 
 class EvaluatedEnum(Evaluated):
     def __init__(self, allowed_values, default=None, name=None):
-        self.allowed_values = allowed_values if isinstance(allowed_values, (list, tuple)) else \
-            allowed_values.split()
-        default = default if default is not None else self.allowed_values[0]
+        if isinstance(allowed_values, six.string_types):
+            allowed_values = set(allowed_values.split())
+        self.allowed_values = allowed_values
+        default = default if default is not None else next(iter(self.allowed_values))
         super(EvaluatedEnum, self).__init__(str, default, name)
 
     def default_eval_func(self, instance):
diff --git a/grc/gui/Utils.py b/grc/gui/Utils.py
index f47c2e6b97..1b32e91439 100644
--- a/grc/gui/Utils.py
+++ b/grc/gui/Utils.py
@@ -19,6 +19,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
 from __future__ import absolute_import
 
+import numbers
+
 from gi.repository import GLib
 import cairo
 import six
@@ -91,7 +93,7 @@ def num_to_str(num):
                 return template.format(value / factor, symbol.strip())
         return template.format(value, '')
 
-    if isinstance(num, Constants.COMPLEX_TYPES):
+    if isinstance(num, numbers.Complex):
         num = complex(num)  # Cast to python complex
         if num == 0:
             return '0'
diff --git a/grc/gui/canvas/param.py b/grc/gui/canvas/param.py
index 845ff5a926..5777423c68 100644
--- a/grc/gui/canvas/param.py
+++ b/grc/gui/canvas/param.py
@@ -17,6 +17,8 @@
 
 from __future__ import absolute_import
 
+import numbers
+
 from .drawable import Drawable
 from .. import ParamWidgets, Utils, Constants
 from ...core.params import Param as CoreParam
@@ -126,7 +128,7 @@ class Param(CoreParam):
         t = self.dtype
         if isinstance(e, bool):
             return str(e)
-        elif isinstance(e, Constants.COMPLEX_TYPES):
+        elif isinstance(e, numbers.Complex):
             dt_str = Utils.num_to_str(e)
         elif isinstance(e, Constants.VECTOR_TYPES):
             # Vector types
-- 
cgit v1.2.3