configurationengine/source/plugins/common/ConeRulePlugin/ruleplugin/relations.py
changeset 3 e7e0ae78773e
parent 0 2e8eeb919028
equal deleted inserted replaced
2:87cfa131b535 3:e7e0ae78773e
    38             return rules.RELATIONS.get(relation_name)
    38             return rules.RELATIONS.get(relation_name)
    39         except KeyError:
    39         except KeyError:
    40             raise exceptions.NotSupportedException("No Relation class found for name %s" % relation_name)
    40             raise exceptions.NotSupportedException("No Relation class found for name %s" % relation_name)
    41 
    41 
    42     @ classmethod
    42     @ classmethod
    43     def get_relations(cls, configuration, relation):
    43     def get_relations(cls, relation):
    44         try:
    44         try:
    45             relations = []
    45             relations = []
    46             (left_expression,relation_name,right_expression) = parse_rule(relation)
    46             (left_expression,relation_name,right_expression) = parse_rule(relation)
    47             relation = cls.get_relation_by_name(relation_name)(configuration, left_expression, right_expression)
    47             relation = cls.get_relation_by_name(relation_name)(left_expression, right_expression)
    48             relations.append(relation)
    48             relations.append(relation)
    49             propagated_relations = cls.get_relations(configuration, right_expression)
    49             propagated_relations = cls.get_relations(right_expression)
    50             if propagated_relations:
    50             if propagated_relations:
    51                 for relation in propagated_relations:
    51                 for relation in propagated_relations:
    52                     relations.append(relation)
    52                     relations.append(relation)
    53             return relations
    53             return relations
    54         except exceptions.ParseError:
    54         except exceptions.ParseError:
    55             return None
    55             return None
    56     
    56     
    57 
    57 
    58 class ConfigurationContext(rules.DefaultContext):
       
    59     
       
    60     def __init__(self, data):
       
    61         rules.DefaultContext.__init__(self, data)
       
    62         
       
    63         # A note on the callback variables that follow:
       
    64         # in order to collect rule execution results for the
       
    65         # generation report, a callback system is implemented in
       
    66         # ConfigurationContext.  It all boils down to ConfigureRelation
       
    67         # using configure_expression_result_callback to catch the result
       
    68         # in its execute() method. The ConfigurationContext just works as
       
    69         # a "callback hub" because a reference to the context is available
       
    70         # in all expression evaluations.
       
    71         
       
    72         # Callback called with a plugin.RelationExecutionResult object
       
    73         # when a ConfigureExpression has been evaluated
       
    74         self.configure_expression_result_callback = None
       
    75         
       
    76         # Callback called with the setting reference when a setting is dereferenced
       
    77         # as a terminal expression
       
    78         self.ref_terminal_callback = None
       
    79         
       
    80         # Callback called with the setting reference when a setting is dereferenced
       
    81         # inside an EvalExpression
       
    82         self.ref_eval_callback = None
       
    83         
       
    84         # Callback called with the setting reference when the value of a setting
       
    85         # is set inside a SetExpression
       
    86         self.ref_set_callback = None
       
    87         
       
    88     def handle_terminal(self, expression):
       
    89         try:
       
    90             value = self.data.get_default_view().get_feature(expression).get_value()
       
    91             
       
    92             # Got a valid ref, call the callback
       
    93             if self.ref_terminal_callback:
       
    94                 self.ref_terminal_callback(expression)
       
    95             
       
    96             return value
       
    97         except exceptions.NotFound,e:
       
    98             """ return the expression itself if it is not a fearef """
       
    99             #print "handle_terminal constant %s" % (expression)
       
   100             try:
       
   101                 return eval(expression)
       
   102             except (NameError,SyntaxError), e:
       
   103                 return expression
       
   104 
       
   105     def eval(self, ast, expression, value):
       
   106         #print "expression %s = %s" % (expression,value)
       
   107         pass
       
   108         
       
   109 class ConfigurationBaseRelation(rules.BaseRelation):
    58 class ConfigurationBaseRelation(rules.BaseRelation):
   110     def __init__(self, data, left, right):
    59     def __init__(self, left, right):
   111         self.context = ConfigurationContext(data)
    60         super(ConfigurationBaseRelation, self).__init__(None, left, right)
   112         super(ConfigurationBaseRelation, self).__init__(data, left, right)
    61         self.context = None
   113 
    62 
   114 class RequireRelation(ConfigurationBaseRelation):
    63 class RequireRelation(ConfigurationBaseRelation):
   115     KEY = 'requires'
    64     KEY = 'requires'
   116     def __init__(self, data, left, right):
    65     relation_name = 'requires'
   117         super(RequireRelation, self).__init__(data, left, right)
    66     def __init__(self, left, right):
   118         self.context = ConfigurationContext(data)
    67         super(RequireRelation, self).__init__(left, right)
   119 
    68 
   120 class ConfigureRelation(ConfigurationBaseRelation):
    69 class ConfigureRelation(ConfigurationBaseRelation):
   121     KEY = 'configures'
    70     KEY = 'configures'
   122     def __init__(self, data, left, right):
    71     relation_name = 'configures'
   123         self.context = ConfigurationContext(data)
    72     def __init__(self, left, right):
   124         super(ConfigureRelation, self).__init__(data, left, right)
    73         super(ConfigureRelation, self).__init__(left, right)
   125         
    74         
   126         # A plugin.RelationExecutionResult object is stored here
    75         # A plugin.RelationExecutionResult object is stored here
   127         self._execution_result = None
    76         self._execution_result = None
   128         
    77         
   129     
    78     
   130     def execute(self):
    79     def execute(self, context):
   131         self._execution_result = None
    80         self._execution_result = None
   132         exec_results = []
    81         exec_results = []
   133         
    82         
   134         # Call BaseRelation.execute() and catch any ConfigureExpression result objects
    83         result = rules.BaseRelation.execute(self, context)
   135         self.context.configure_expression_result_callback = lambda result: exec_results.append(result)
       
   136         result = rules.BaseRelation.execute(self)
       
   137         self.context.configure_expression_result_callback = None
       
   138         
    84         
   139         if len(exec_results) > 0:
    85         if len(exec_results) > 0:
   140             # There should be only one ConfigureExpression inside a ConfigureRelation
    86             # There should be only one ConfigureExpression inside a ConfigureRelation
   141             if len(exec_results) > 1:
    87             if len(exec_results) > 1:
   142                 log.warning("Execution of ConfigureRelation returned more than one result, ignoring all except the first")
    88                 log.warning("Execution of ConfigureRelation returned more than one result, ignoring all except the first")
   155         return True
   101         return True
   156     elif not left:
   102     elif not left:
   157         return True
   103         return True
   158     return False
   104     return False
   159 
   105 
   160 def handle_set(self, left, right):
       
   161     left.set_value(right)
       
   162 
       
   163 def handle_filenamejoin(self, left, right):
   106 def handle_filenamejoin(self, left, right):
   164     def extract_dirname(path):
   107     def extract_dirname(path):
   165         """Extract directory name (will always contain a trailing slash or backslash)"""
   108         """Extract directory name (will always contain a trailing slash or backslash)"""
   166         pos = max(path.rfind('/'), path.rfind('\\'))
   109         pos = max(path.rfind('/'), path.rfind('\\'))
   167         if pos == -1:   return path + '/'
   110         if pos == -1:   return path + '/'
   172         if pos == -1:   return path
   115         if pos == -1:   return path
   173         else:           return path[pos + 1:]
   116         else:           return path[pos + 1:]
   174     
   117     
   175     return extract_dirname(left) + extract_filename(right)
   118     return extract_dirname(left) + extract_filename(right)
   176 
   119 
   177 def handle_plus(self, left, right):
       
   178     return left + right
       
   179 
       
   180 def handle_minus(self, left, right):
       
   181     return left - right
       
   182 
       
   183 def handle_multiply(self, left, right):
       
   184     return left * right
       
   185 
       
   186 def handle_divide(self, left, right):
       
   187     return left / right
       
   188 
   120 
   189 class ConfigureExpression(rules.TwoOperatorExpression):
   121 class ConfigureExpression(rules.TwoOperatorExpression):
   190     PRECEDENCE = rules.PRECEDENCES['RELATION_OPERATORS']
   122     PRECEDENCE = rules.PRECEDENCES['RELATION_OPERATORS']
   191     KEY = 'configures'
   123     KEY = 'configures'
   192     OP = handle_configure
   124     OP = handle_configure
   193 
   125 
   194     def eval(self, context):
   126     def eval(self, context, **kwargs):
   195         input_refs = []
   127         input_refs = []
   196         affected_refs = []
   128         affected_refs = []
   197         
   129         
   198         # Evaluate the left-hand side expression, catching refs for the result
   130         # Evaluate the left-hand side expression
   199         try:
   131         evaluated_left = self.left.eval(context, **kwargs)
   200             context.ref_terminal_callback = lambda ref: input_refs.append(ref)
       
   201             context.ref_eval_callback = lambda ref: input_refs.append(ref)
       
   202             evaluated_left = self.left.eval(context)
       
   203         finally:
       
   204             context.ref_terminal_callback = None
       
   205             context.ref_eval_callback = None
       
   206         
       
   207         if evaluated_left:
   132         if evaluated_left:
   208             # If left evaluated to True, evaluate the right-hand side and
   133             # If left evaluated to True, evaluate the right-hand side
   209             # catch refs from SetExpression evaluations
   134             self.value = self.right.eval(context, **kwargs)
   210             try:
       
   211                 context.ref_set_callback = lambda ref: affected_refs.append(ref)
       
   212                 self.value = self.right.eval(context)
       
   213             finally:
       
   214                 context.ref_set_callback = None
       
   215         else:
   135         else:
   216             self.value = True
   136             self.value = True
   217         
   137         
   218         if not self.value:
   138         if not self.value:
   219             left_keys = []
   139             left_keys = []
   220             for ref in self.ast.extract_refs(str(self.left)):
   140             for ref in self.ast.extract_refs(str(self.left)):
   221                 for key in context.get_keys(ref):
   141                 for key in context.get_keys(ref):
   222                     left_keys.append(key)
   142                     left_keys.append(key)
   223 
       
   224             for key in left_keys:
   143             for key in left_keys:
   225                 self.ast.add_error(key, { 'error_string' : 'CONFIGURES right side value is "False"',
   144                 self.ast.add_error(key, { 'error_string' : 'CONFIGURES right side value is "False"',
   226                                           'left_key' : key,
   145                                           'left_key' : key,
   227                                           'rule' : self.ast.expression
   146                                           'rule' : self.ast.expression
   228                                           })
   147                                           })
   229         
       
   230         # Return a RelationExecutionResult if necessary
       
   231         if input_refs or affected_refs:
       
   232             if context.configure_expression_result_callback:
       
   233                 result = plugin.RelationExecutionResult(utils.distinct_array(input_refs),
       
   234                                                         utils.distinct_array(affected_refs))
       
   235                 context.configure_expression_result_callback(result)
       
   236                 
       
   237         return self.value
   148         return self.value
   238 
       
   239 class MultiplyExpression(rules.TwoOperatorExpression):
       
   240     expression = "multiply_operation"
       
   241     PRECEDENCE = rules.PRECEDENCES['MULDIV_OPERATORS']
       
   242     KEY= '*'
       
   243     OP = handle_multiply
       
   244 
       
   245 class DivideExpression(rules.TwoOperatorExpression):
       
   246     expression = "divide_operation"
       
   247     PRECEDENCE = rules.PRECEDENCES['MULDIV_OPERATORS']
       
   248     KEY= '/'
       
   249     OP = handle_divide
       
   250 
       
   251 class PlusExpression(rules.TwoOperatorExpression):
       
   252     expression = "plus_operation"
       
   253     PRECEDENCE = rules.PRECEDENCES['ADDSUB_OPERATORS']
       
   254     KEY= '+'
       
   255     OP = handle_plus
       
   256 
       
   257 class MinusExpression(rules.TwoOperatorExpression):
       
   258     expression = "minus_operation"
       
   259     PRECEDENCE = rules.PRECEDENCES['ADDSUB_OPERATORS']
       
   260     KEY= '-'
       
   261     OP = handle_minus
       
   262 
   149 
   263 class EvalExpression(rules.OneParamExpression):
   150 class EvalExpression(rules.OneParamExpression):
   264     expression = "__eval__"
   151     expression = "__eval__"
   265     PRECEDENCE = rules.PRECEDENCES['PREFIX_OPERATORS']
   152     PRECEDENCE = rules.PRECEDENCES['PREFIX_OPERATORS']
   266     KEY = '__eval__'
   153     KEY = '__eval__'
   267     
   154     
   268     def __init__(self, ast, expression):
   155     def __init__(self, ast, expression): 
   269         super(rules.OneParamExpression, self).__init__(ast)
   156         super(EvalExpression, self).__init__(ast, expression)
   270         self.expression = expression
   157         self.expression = expression
   271         self._str_to_eval = eval(expression.expression)
   158         self._str_to_eval = eval(expression.expression)
   272         #self.default_view = default_view
   159         #self.default_view = default_view
   273     
   160     
   274     def extract_refs(self):
   161     def extract_refs(self):
   275         result = []
   162         result = []
   276         result.extend(utils.extract_delimited_tokens(self._str_to_eval, delimiters=('${', '}')))
   163         result.extend(utils.extract_delimited_tokens(self._str_to_eval, delimiters=('${', '}')))
   277         result.extend(utils.extract_delimited_tokens(self._str_to_eval, delimiters=('@{', '}')))
   164         result.extend(utils.extract_delimited_tokens(self._str_to_eval, delimiters=('@{', '}')))
   278         return result
   165         return result
   279     
   166     
   280     def eval(self, context):
   167     def get_refs(self):
       
   168         return self.extract_refs()
       
   169     
       
   170     def eval(self, context, **kwargs):
   281         # Using the configuration to pass the eval globals dictionary to here,
   171         # Using the configuration to pass the eval globals dictionary to here,
   282         # since there isn't any easy way to do this more elegantly
   172         # since there isn't any easy way to do this more elegantly
   283         globals_and_locals = {}
   173         globals_and_locals = {}
   284         if hasattr(context.data, '_eval_expression_globals_dict'):
   174         if hasattr(context, '_eval_expression_globals_dict'):
   285             globals_and_locals = context.data._eval_expression_globals_dict
   175             globals_and_locals = context._eval_expression_globals_dict
   286         
   176         
   287         str_to_eval = self._str_to_eval
   177         str_to_eval = self._str_to_eval
   288         
   178         
   289         def expand_feature_ref(ref, index):
   179         def expand_feature_ref(ref, index):
   290             var_name = "__fea_%05d" % index
   180             var_name = "__fea_%05d" % index
   291             globals_and_locals[var_name] = context.data.get_default_view().get_feature(ref)
   181             globals_and_locals[var_name] = context.configuration.get_default_view().get_feature(ref)
   292             if context.ref_eval_callback:
       
   293                 context.ref_eval_callback(ref)
       
   294             return var_name
   182             return var_name
       
   183 
   295         def expand_value_ref(ref, index):
   184         def expand_value_ref(ref, index):
   296             var_name = "__feaval_%05d" % index
   185             var_name = "__feaval_%05d" % index
   297             globals_and_locals[var_name] = context.data.get_default_view().get_feature(ref).get_value()
   186             globals_and_locals[var_name] = context.configuration.get_default_view().get_feature(ref).get_value()
   298             if context.ref_eval_callback:
       
   299                 context.ref_eval_callback(ref)
       
   300             return var_name
   187             return var_name
   301         
   188         
   302         str_to_eval = utils.expand_delimited_tokens(str_to_eval, expand_feature_ref, delimiters=('@{', '}'))
   189         str_to_eval = utils.expand_delimited_tokens(str_to_eval, expand_feature_ref, delimiters=('@{', '}'))
   303         str_to_eval = utils.expand_delimited_tokens(str_to_eval, expand_value_ref, delimiters=('${', '}'))
   190         str_to_eval = utils.expand_delimited_tokens(str_to_eval, expand_value_ref, delimiters=('${', '}'))
   304         
   191         
   315             self.ast.add_error(self.expression, { 'error_string' : 'Invalid syntax in eval', 'str_to_eval' : str_to_eval, 'rule' : self.ast.expression })
   202             self.ast.add_error(self.expression, { 'error_string' : 'Invalid syntax in eval', 'str_to_eval' : str_to_eval, 'rule' : self.ast.expression })
   316         except Exception, e:
   203         except Exception, e:
   317             logging.getLogger('cone.ruleml').warning("Execution failed for eval: %s %s: %s" % (str_to_eval, type(e), e) )
   204             logging.getLogger('cone.ruleml').warning("Execution failed for eval: %s %s: %s" % (str_to_eval, type(e), e) )
   318             self.ast.add_error(self.expression, { 'error_string' : 'Execution failed for eval', 'str_to_eval' : str_to_eval, 'rule' : self.ast.expression })
   205             self.ast.add_error(self.expression, { 'error_string' : 'Execution failed for eval', 'str_to_eval' : str_to_eval, 'rule' : self.ast.expression })
   319 
   206 
   320 rules.OPERATORS[EvalExpression.KEY] = EvalExpression
       
   321 
   207 
   322 class FilenamejoinExpression(rules.TwoOperatorExpression):
   208 class FilenamejoinExpression(rules.TwoOperatorExpression):
   323     expression = "filenamejoin"
   209     expression = "filenamejoin"
   324     PRECEDENCE = rules.PRECEDENCES['ADDSUB_OPERATORS']
   210     PRECEDENCE = rules.PRECEDENCES['ADDSUB_OPERATORS']
   325     KEY = 'filenamejoin'
   211     KEY = 'filenamejoin'
   326     OP = handle_filenamejoin
   212     OP = handle_filenamejoin
   327     
   213     
       
   214 # Register relations and operators to rules
       
   215 rules.RELATIONS[RequireRelation.KEY] = RequireRelation
       
   216 rules.RELATIONS[ConfigureRelation.KEY] = ConfigureRelation
   328 rules.OPERATORS[FilenamejoinExpression.KEY] = FilenamejoinExpression
   217 rules.OPERATORS[FilenamejoinExpression.KEY] = FilenamejoinExpression
   329     
   218 rules.OPERATORS[EvalExpression.KEY] = EvalExpression
   330 class SetExpression(rules.TwoOperatorExpression):
   219 rules.OPERATORS[ConfigureExpression.KEY] = ConfigureExpression
   331     PRECEDENCE = rules.PRECEDENCES['SET_OPERATORS']
       
   332     KEY= '='
       
   333     OP = handle_set
       
   334 
       
   335     def eval(self, context):
       
   336         try:
       
   337             variable = context.data.get_default_view().get_feature(self.left.expression)
       
   338             value = self.right.eval(context)
       
   339             variable.set_value(value)
       
   340             logging.getLogger('cone.ruleml').info("Set %r = %r from %r" % (self.left.expression, value, self.right.expression) )
       
   341             if context.ref_set_callback:
       
   342                 context.ref_set_callback(self.left.expression)
       
   343             return True
       
   344         except exceptions.NotFound,e:
       
   345             self.ast.add_error(self.left.expression, { 'error_string' : 'Setting value failed, because of %s' % e,
       
   346                                'left_key' : self.left.expression,
       
   347                                'rule' : self.ast.expression})
       
   348             return False
       
   349 
       
   350 _relations_and_operators_backup = None
       
   351 
       
   352 def register():
       
   353     """
       
   354     Register the relations and operators to ConE rules.
       
   355     """
       
   356     global _relations_and_operators_backup
       
   357     if _relations_and_operators_backup is None:
       
   358         # Create the backup copies of the dictionaries
       
   359         rels_backup = rules.RELATIONS.copy()
       
   360         ops_backup = rules.OPERATORS.copy()
       
   361         assert rels_backup is not rules.RELATIONS
       
   362         assert ops_backup is not rules.OPERATORS
       
   363         _relations_and_operators_backup = (rels_backup, ops_backup)
       
   364         
       
   365         # Register relations and operators to rules
       
   366         rules.RELATIONS[RequireRelation.KEY] = RequireRelation
       
   367         rules.RELATIONS[ConfigureRelation.KEY] = ConfigureRelation
       
   368         rules.OPERATORS[ConfigureExpression.KEY] = ConfigureExpression
       
   369         rules.OPERATORS[PlusExpression.KEY] = PlusExpression
       
   370         rules.OPERATORS[SetExpression.KEY] = SetExpression
       
   371         rules.OPERATORS[MinusExpression.KEY] = MinusExpression
       
   372         rules.OPERATORS[MultiplyExpression.KEY] = MultiplyExpression
       
   373         rules.OPERATORS[DivideExpression.KEY] = DivideExpression
       
   374 
       
   375 def unregister():
       
   376     """
       
   377     Undo the changes made by a call to register().
       
   378     """
       
   379     global _relations_and_operators_backup
       
   380     if _relations_and_operators_backup is not None:
       
   381         rules.RELATIONS = _relations_and_operators_backup[0]
       
   382         rules.OPERATORS = _relations_and_operators_backup[1]
       
   383         _relations_and_operators_backup = None
       
   384 
   220 
   385 def parse_rule(rulestring):
   221 def parse_rule(rulestring):
   386     """
   222     """
   387     Divide the given rule string into (left side, relation, right side) components. 
   223     Divide the given rule string into (left side, relation, right side) components. 
   388     @return: Triple (left side, relation, right side)
   224     @return: Triple (left side, relation, right side)