# -*- coding: utf8 -*- # # ***** BEGIN GPL LICENSE BLOCK ***** # # -------------------------------------------------------------------------- # Blender 2.5 Extensions Framework # -------------------------------------------------------------------------- # # Authors: # Doug Hammond # # This program 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. # # This program 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, see . # # ***** END GPL LICENCE BLOCK ***** # ''' Pure logic and validation class. By using a Subject object, and a dict of described logic tests, it is possible to arrive at a True or False result for various purposes: 1. Data validation 2. UI control visibility A Subject can be any object whose members are readable with getattr() : class Subject(object): a = 0 b = 1 c = 'foo' d = True e = False f = 8 g = 'bar' Tests are described thus: Use the special list types Logic_AND and Logic_OR to describe combinations of values and other members. Use Logic_Operator for numerical comparison. # With regards to Subject, each of these evaluate to True: TESTA = { 'a': 0, 'c': Logic_OR([ 'foo', 'bar' ]), 'd': Logic_AND([True, True]), 'f': Logic_AND([8, {'b': 1}]), 'e': {'b': Logic_Operator({'gte':1, 'lt':3}) }, 'g': Logic_OR([ 'baz', Logic_AND([{'b': 1}, {'f': 8}]) ]) } # With regards to Subject, each of these evaluate to False: TESTB = { 'a': 'foo', 'c': Logic_OR([ 'bar', 'baz' ]), 'd': Logic_AND([ True, 'foo' ]), 'f': Logic_AND([9, {'b': 1}]), 'e': {'b': Logic_Operator({'gte':-10, 'lt': 1}) }, 'g': Logic_OR([ 'baz', Logic_AND([{'b':0}, {'f': 8}]) ]) } # With regards to Subject, this test is invalid TESTC = { 'n': 0 } # Tests are executed thus: S = Subject() L = Logician(S) L.execute(TESTA) ''' class Logic_AND(list): pass class Logic_OR(list): pass class Logic_Operator(dict): pass class Logician(object): ''' Given a subject and a dict that describes tests to perform on its members, this class will evaluate True or False results for each member/test pair. See the examples below for test syntax. ''' subject = None def __init__(self, subject): self.subject = subject def get_member(self, member_name): ''' Get a member value from the subject object. Raise exception is subject is None or member not found. ''' if self.subject is None: raise Exception('Cannot run tests on a subject which is None') return getattr(self.subject, member_name) def test_logic(self, member, logic, operator='eq'): ''' Find the type of test to run on member, and perform that test ''' if type(logic) is dict: return self.test_dict(member, logic) elif type(logic) is Logic_AND: return self.test_and(member, logic) elif type(logic) is Logic_OR: return self.test_or(member, logic) elif type(logic) is Logic_Operator: return self.test_operator(member, logic) else: # compare the value, I think using Logic_Operator() here allows completeness in test_operator(), # but I can't put my finger on why for the minute return self.test_operator(member, Logic_Operator({operator: logic})) def test_operator(self, member, value): ''' execute the operators contained within value and expect that ALL operators are True ''' # something in this method is incomplete, what if operand is a dict, Logic_AND, Logic_OR or another Logic_Operator ? # do those constructs even make any sense ? result = True for operator, operand in value.items(): operator = operator.lower().strip() if operator in ['eq', '==']: result &= member==operand if operator in ['not', '!=']: result &= member!=operand if operator in ['lt', '<']: result &= member']: result &= member>operand if operator in ['gte', '>=']: result &= member>=operand if operator in ['and', '&']: result &= member&operand if operator in ['or', '|']: result &= member|operand if operator in ['len']: result &= len(member)==operand # I can think of some more, but they're probably not useful. return result def test_or(self, member, logic): ''' member is a value, logic is a set of values, ANY of which can be True ''' result = False for test in logic: result |= self.test_logic(member, test) return result def test_and(self, member, logic): ''' member is a value, logic is a list of values, ALL of which must be True ''' result = True for test in logic: result &= self.test_logic(member, test) return result def test_dict(self, member, logic): ''' member is a value, logic is a dict of other members to compare to. All other member tests must be True ''' result = True for other_member, test in logic.items(): result &= self.test_logic(self.get_member(other_member), test) return result def execute(self, test): ''' subject is an object, test is a dict of {member: test} pairs to perform on subject's members. each key in test is a member of subject. ''' for member_name, logic in test.items(): result = self.test_logic(self.get_member(member_name), logic) print('member %s is %s' % (member_name, result)) # A couple of name aliases class Validation(Logician): pass class Visibility(Logician): pass