From 99b8ef09b954c69492be95b68989d6d75b0446ee Mon Sep 17 00:00:00 2001
From: Håkon Vågsether <haakonsv@gmail.com>
Date: Wed, 29 Aug 2018 17:25:11 +0200
Subject: grc: Fix problems with option_attributes

Enabling the option_attributes to be accessed by the rest of the
block's parameters by using a new string subtype. Also adding
validation.
---
 grc/core/blocks/_build.py  | 21 +++++++++++++++++++--
 grc/core/blocks/virtual.py |  4 ++--
 grc/core/io/yaml.py        |  2 ++
 grc/core/params/param.py   |  5 +++++
 4 files changed, 28 insertions(+), 4 deletions(-)

diff --git a/grc/core/blocks/_build.py b/grc/core/blocks/_build.py
index 6db06040cf..0c41d1ae70 100644
--- a/grc/core/blocks/_build.py
+++ b/grc/core/blocks/_build.py
@@ -20,9 +20,11 @@ from __future__ import absolute_import
 import collections
 import itertools
 import re
+import six
 
 from ..Constants import ADVANCED_PARAM_TAB
 from ..utils import to_list
+from ..Messages import send_warning
 
 from .block import Block
 from ._flags import Flags
@@ -51,7 +53,7 @@ def build(id, label='', category='', flags='', documentation='',
     cls.inputs_data = _build_ports(inputs, 'sink') if inputs else []
     cls.outputs_data = _build_ports(outputs, 'source') if outputs else []
     cls.parameters_data = _build_params(parameters or [],
-                                        bool(cls.inputs_data), bool(cls.outputs_data), cls.flags)
+                                        bool(cls.inputs_data), bool(cls.outputs_data), cls.flags, block_id)
     cls.extra_data = kwargs
 
     templates = templates or {}
@@ -86,7 +88,7 @@ def _build_ports(ports_raw, direction):
     return ports
 
 
-def _build_params(params_raw, have_inputs, have_outputs, flags):
+def _build_params(params_raw, have_inputs, have_outputs, flags, block_id):
     params = []
 
     def add_param(**data):
@@ -114,6 +116,9 @@ def _build_params(params_raw, have_inputs, have_outputs, flags):
         if param_id in params:
             raise Exception('Param id "{}" is not unique'.format(param_id))
 
+        if 'option_attributes' in param_data:
+            _validate_option_attributes(param_data, block_id)
+
         base_key = param_data.get('base_key', None)
         param_data_ext = base_params_n.get(base_key, {}).copy()
         param_data_ext.update(param_data)
@@ -133,3 +138,15 @@ def _single_mako_expr(value, block_id):
     if not (value.startswith('${') and value.endswith('}')):
         raise ValueError('{} is not a mako substitution in {}'.format(value, block_id))
     return value[2:-1].strip()
+
+
+def _validate_option_attributes(param_data, block_id):
+    if param_data['dtype'] != 'enum':
+        send_warning('{} - option_attributes are for enums only, ignoring'.format(block_id))
+        del param_data['option_attributes']
+    else:
+        for key in list(param_data['option_attributes'].keys()):
+            if key in dir(str):
+                del param_data['option_attributes'][key]
+                send_warning('{} - option_attribute "{}" overrides str, ignoring'.format(block_id, key))
+
diff --git a/grc/core/blocks/virtual.py b/grc/core/blocks/virtual.py
index 302fd76a1e..b74a01c985 100644
--- a/grc/core/blocks/virtual.py
+++ b/grc/core/blocks/virtual.py
@@ -45,7 +45,7 @@ class VirtualSink(Block):
         self.inputs_data = _build_ports(self.inputs, 'sink') if self.inputs else []
         self.outputs_data = _build_ports(self.outputs, 'source') if self.outputs else []
         self.parameters_data = _build_params(self.parameters or [],
-                                bool(self.inputs), bool(self.outputs), self.flags)
+                                bool(self.inputs), bool(self.outputs), self.flags, self.key)
 
         super(VirtualSink, self).__init__(parent, **kwargs)
         self.params['id'].hide = 'all'
@@ -78,7 +78,7 @@ class VirtualSource(Block):
         self.inputs_data = _build_ports(self.inputs, 'sink') if self.inputs else []
         self.outputs_data = _build_ports(self.outputs, 'source') if self.outputs else []
         self.parameters_data = _build_params(self.parameters or [],
-                                bool(self.inputs), bool(self.outputs), self.flags)
+                                bool(self.inputs), bool(self.outputs), self.flags, self.key)
 
         super(VirtualSource, self).__init__(parent, **kwargs)
         self.params['id'].hide = 'all'
diff --git a/grc/core/io/yaml.py b/grc/core/io/yaml.py
index 29b4cb81d6..8efbc4865d 100644
--- a/grc/core/io/yaml.py
+++ b/grc/core/io/yaml.py
@@ -22,6 +22,7 @@ from collections import OrderedDict
 import six
 import yaml
 
+from ..params.param import attributed_str
 
 class GRCDumper(yaml.SafeDumper):
     @classmethod
@@ -79,6 +80,7 @@ GRCDumper.add_representer(ListFlowing, GRCDumper.represent_list_flowing)
 GRCDumper.add_representer(tuple, GRCDumper.represent_list)
 GRCDumper.add_representer(MultiLineString, GRCDumper.represent_ml_string)
 GRCDumper.add_representer(yaml.nodes.ScalarNode, lambda r, n: n)
+GRCDumper.add_representer(attributed_str, GRCDumper.represent_str)
 
 
 def dump(data, stream=None, **kwargs):
diff --git a/grc/core/params/param.py b/grc/core/params/param.py
index ce40025228..69aa6fd811 100644
--- a/grc/core/params/param.py
+++ b/grc/core/params/param.py
@@ -31,6 +31,7 @@ from ..utils.descriptors import Evaluated, EvaluatedEnum, setup_names
 from . import dtypes
 from .template_arg import TemplateArg
 
+attributed_str = type('attributed_str', (str,), {})
 
 @setup_names
 class Param(Element):
@@ -178,6 +179,10 @@ class Param(Element):
         # ID and Enum types (not evaled)
         #########################
         if dtype in ('id', 'stream_id') or self.is_enum():
+            if self.options.attributes:
+                expr = attributed_str(expr)
+                for key, value in self.options.attributes[expr].items():
+                    setattr(expr, key, value)
             return expr
 
         #########################
-- 
cgit v1.2.3