Changeset 20

Show
Ignore:
Timestamp:
18/08/04 14:22:13 (8 years ago)
Author:
steve
Message:

breaking into smaller object to reduce conditional logic

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • trunk/airspeed.py

    r19 r20  
    2121 
    2222    def merge_to(self, namespace, fileobj): 
    23         output = [] 
    2423        if not self.evaluator: 
    2524            self.evaluator = TemplateBody(self.content) 
     
    5049        except KeyError: return self.parent[key] 
    5150 
     51    def __repr__(self): 
     52        return dict.__repr__(self) + '->' + repr(self.parent) 
     53 
    5254 
    5355class _Element: 
    54     def match_or_reject(self, pattern, text): 
     56    def identity_match(self, pattern, text): 
    5557        m = pattern.match(text) 
    5658        if not m: raise NoMatch() 
     
    6264        return m.groups() 
    6365 
    64     def next_element(self, element_class, text): 
    65         element = element_class(text) 
    66         return element, element.remaining_text 
    67  
    68     def require_next_element(self, element_class, text, expected): 
    69         try: element = element_class(text) 
    70         except NoMatch: raise TemplateSyntaxError(self, expected, text) 
    71         else: return element, element.remaining_text 
     66    def next_element(self, element_spec, text): 
     67        if callable(element_spec): 
     68            element = element_spec(text) 
     69            return element, element.remaining_text 
     70        else: 
     71            for element_class in element_spec: 
     72                try: element = element_class(text) 
     73                except NoMatch: pass 
     74                else: return element, element.remaining_text 
     75            raise NoMatch() 
     76 
     77    def require_next_element(self, element_spec, text, expected): 
     78        if callable(element_spec): 
     79            try: element = element_spec(text) 
     80            except NoMatch: raise TemplateSyntaxError(self, expected, text) 
     81            else: return element, element.remaining_text 
     82        else: 
     83            for element_class in element_spec: 
     84                try: element = element_class(text) 
     85                except NoMatch: pass 
     86                else: return element, element.remaining_text 
     87            expected = ', '.join([cls.__name__ for cls in element_spec]) 
     88            raise TemplateSyntaxError(self, 'one of: ' + expected, text) 
    7289 
    7390 
     
    7693    ESCAPED_CHAR = re.compile(r'\\([\\\$#])') 
    7794    def __init__(self, text): 
    78         text, self.remaining_text = self.match_or_reject(self.MY_PATTERN, text) 
     95        text, self.remaining_text = self.identity_match(self.MY_PATTERN, text) 
    7996        def unescape(match): 
    8097            return match.group(1) 
     
    85102 
    86103 
    87  
    88104class IntegerLiteral(_Element): 
    89105    MY_PATTERN = re.compile(r'^(\d+)(.*)', re.S) 
    90106    def __init__(self, text): 
    91         self.value, self.remaining_text = self.match_or_reject(self.MY_PATTERN, text) 
     107        self.value, self.remaining_text = self.identity_match(self.MY_PATTERN, text) 
    92108        self.value = int(self.value) 
    93109 
     
    100116    ESCAPED_CHAR = re.compile(r'\\([nrbt"\\])') 
    101117    def __init__(self, text): 
    102         value, self.remaining_text = self.match_or_reject(self.MY_PATTERN, text) 
     118        value, self.remaining_text = self.identity_match(self.MY_PATTERN, text) 
    103119        def unescape(match): 
    104120            return {'n': '\n', 'r': '\r', 'b': '\b', 't': '\t', '"': '"', '\\': '\\'}[match.group(1)] 
     
    111127class Value(_Element): 
    112128    def __init__(self, text): 
    113         if text.startswith('$'): 
    114             self.expression = Expression(text[1:]) 
    115         else: 
    116             try: 
    117                 self.expression = IntegerLiteral(text) 
    118             except NoMatch: 
    119                 self.expression = StringLiteral(text) 
    120         self.remaining_text = self.expression.remaining_text 
     129        self.expression, self.remaining_text = self.next_element((PlainReference, IntegerLiteral, StringLiteral), text) 
    121130 
    122131    def calculate(self, namespace): 
     
    124133 
    125134 
     135class NameOrCall(_Element): 
     136    NAME_PATTERN = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)(.*)$', re.S) 
     137    parameters = None 
     138    def __init__(self, text): 
     139        self.name, text = self.identity_match(self.NAME_PATTERN, text) 
     140        try: self.parameters, text = self.next_element(ParameterList, text) 
     141        except NoMatch: pass 
     142        self.remaining_text = text 
     143 
     144    def calculate(self, namespace, top_namespace): 
     145        try: result = getattr(namespace, self.name) 
     146        except AttributeError: 
     147            try: result = namespace[self.name] 
     148            except KeyError: result = None 
     149        if result is None: 
     150            return None ## TODO: an explicit 'not found' exception? 
     151        if self.parameters is not None: 
     152            values = [value.calculate(top_namespace) for value in self.parameters.values] 
     153            result = result(*values) 
     154        return result 
     155 
     156 
    126157class Expression(_Element): 
    127     NAME_PATTERN = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)(.*)$', re.S) 
    128158    def __init__(self, text): 
    129159        self.names_and_calls = [] 
    130         try: text = self.read_name_or_call(text) 
    131         except NoMatch: raise TemplateSyntaxError(self, 'name or call', text) 
     160        part, text = self.require_next_element(NameOrCall, text, 'name') 
     161        self.names_and_calls.append(part) 
    132162        while text.startswith('.'): 
    133163            try: 
    134                 text = self.read_name_or_call(text[1:]) 
    135             except NoMatch:   # for the '$name. blah' case 
    136                 break 
     164                part, text = self.next_element(NameOrCall, text[1:]) 
     165                self.names_and_calls.append(part) 
     166            except NoMatch: break  # for the '$name. blah' case 
    137167        self.remaining_text = text 
    138168 
    139     def read_name_or_call(self, text): 
    140         name, text = self.match_or_reject(self.NAME_PATTERN, text) 
    141         parameter_list = None 
    142         try: 
    143             parameter_list, text = self.next_element(ParameterList, text) 
    144         except NoMatch: 
    145             pass 
    146         self.names_and_calls.append((name, parameter_list)) 
    147         return text 
    148  
    149     def calculate(self, namespace): 
    150         result = namespace 
    151         for name, parameters in self.names_and_calls: 
    152             try: result = getattr(result, name) 
    153             except AttributeError: 
    154                 try: result = result[name] 
    155                 except KeyError: pass 
    156             if result in (None, namespace): return None ## TODO: an explicit 'not found' exception? 
    157             if parameters is not None: 
    158                 values = [value.calculate(namespace) for value in parameters.values] 
    159                 result = result(*values) 
    160         return result 
     169    def calculate(self, namespace): 
     170        value = namespace 
     171        for part in self.names_and_calls: 
     172            value = part.calculate(value, namespace) 
     173            if value is None: return None 
     174        return value 
    161175 
    162176 
     
    168182    def __init__(self, text): 
    169183        self.values = [] 
    170         text, = self.match_or_reject(self.OPENING_PATTERN, text) 
     184        text, = self.identity_match(self.OPENING_PATTERN, text) 
    171185        try: value, text = self.next_element(Value, text) 
    172186        except NoMatch: 
     
    186200    CLOSING_BRACE_PATTERN = re.compile(r'^\}(.*)$', re.S) 
    187201    def __init__(self, text): 
    188         self.silent, self.braces, text = self.match_or_reject(self.MY_PATTERN, text) 
     202        self.silent, self.braces, text = self.identity_match(self.MY_PATTERN, text) 
    189203        self.expression, text = self.require_next_element(Expression, text, 'expression') 
    190204        if self.braces: 
     
    197211            if self.silent: value = '' 
    198212            else: 
    199                 value_as_str = '.'.join([name for name, params in self.expression.names_and_calls]) 
     213                value_as_str = '.'.join([name.name for name in self.expression.names_and_calls]) 
    200214                if self.braces: value = '${%s}' % value_as_str 
    201215                else: value = '$%s' % value_as_str 
     
    203217 
    204218 
     219class PlainReference(_Element): 
     220    def __init__(self, text): 
     221        if not text.startswith('$'): raise NoMatch() 
     222        self.expression, self.remaining_text = self.require_next_element(Expression, text[1:], 'name') 
     223        self.calculate = self.expression.calculate 
     224 
     225 
    205226class Null: 
    206227    def evaluate(self, namespace, stream): pass 
     
    210231    COMMENT_PATTERN = re.compile('^#(?:#.*?(?:\n|$)|\*.*?\*#(?:[ \t]*\n)?)(.*)$', re.M + re.S) 
    211232    def __init__(self, text): 
    212         self.remaining_text, = self.match_or_reject(self.COMMENT_PATTERN, text) 
     233        self.remaining_text, = self.identity_match(self.COMMENT_PATTERN, text) 
    213234 
    214235 
     
    220241        self.expression, text = self.next_element(Value, text) 
    221242        self.remaining_text, = self.require_match(self.CLOSING_PATTERN, text, ')') 
    222  
    223     def calculate(self, namespace): 
    224         return self.expression.calculate(namespace) 
     243        self.calculate = self.expression.calculate 
    225244 
    226245 
     
    228247    END = re.compile(r'^#end(.*)', re.I + re.S) 
    229248    def __init__(self, text): 
    230         self.remaining_text, = self.match_or_reject(self.END, text) 
     249        self.remaining_text, = self.identity_match(self.END, text) 
     250 
     251 
     252class ElseBlock(_Element): 
     253    START = re.compile(r'^#else(.*)$', re.S + re.I) 
     254    def __init__(self, text): 
     255        text, = self.identity_match(self.START, text) 
     256        self.block, self.remaining_text = self.require_next_element(Block, text, 'block') 
     257        self.evaluate = self.block.evaluate 
     258 
     259 
     260class ElseifBlock(_Element): 
     261    START = re.compile(r'^#elseif\b\s*(.*)$', re.S + re.I) 
     262    def __init__(self, text): 
     263        text, = self.identity_match(self.START, text) 
     264        self.condition, text = self.require_next_element(Condition, text, 'condition') 
     265        self.block, self.remaining_text = self.require_next_element(Block, text, 'block') 
     266        self.calculate = self.condition.calculate 
     267        self.evaluate = self.block.evaluate 
    231268 
    232269 
     
    234271    START = re.compile(r'^#if\b\s*(.*)$', re.S + re.I) 
    235272    START_ELSEIF = re.compile(r'^#elseif\b\s*(.*)$', re.S + re.I) 
    236     START_ELSE = re.compile(r'^#else(.*)$', re.S + re.I) 
    237273    else_block = Null() 
    238274 
    239275    def __init__(self, text): 
    240         text, = self.match_or_reject(self.START, text) 
     276        text, = self.identity_match(self.START, text) 
    241277        self.condition, text = self.next_element(Condition, text) 
    242278        self.block, text = self.next_element(Block, text) 
    243         self.elseif_conditions = [] 
     279        self.elseifs = [] 
    244280        while True: 
    245             m = self.START_ELSEIF.match(text) 
    246             if not m: break 
    247             text = m.group(1) 
    248             elseif_condition, text = self.require_next_element(Condition, text, 'condition') 
    249             elseif_block, text = self.require_next_element(Block, text, 'block') 
    250             self.elseif_conditions.append((elseif_condition, elseif_block)) 
    251         m = self.START_ELSE.match(text) 
    252         if m: 
    253             self.else_block, text = self.require_next_element(Block, m.group(1), 'block') 
     281            try: 
     282                elseif_block, text = self.next_element(ElseifBlock, text) 
     283                self.elseifs.append(elseif_block) 
     284            except NoMatch: 
     285                break 
     286        try: self.else_block, text = self.next_element(ElseBlock, text) 
     287        except NoMatch: pass 
    254288        end, self.remaining_text = self.require_next_element(End, text, '#else, #elseif or #end') 
    255289 
     
    258292            self.block.evaluate(namespace, stream) 
    259293        else: 
    260             for elseif, block in self.elseif_conditions: 
     294            for elseif in self.elseifs: 
    261295                if elseif.calculate(namespace): 
    262                     block.evaluate(namespace, stream) 
     296                    elseif.evaluate(namespace, stream) 
    263297                    return 
    264298            self.else_block.evaluate(namespace, stream) 
    265299 
    266300 
    267 class SetDirective(_Element): 
    268     START = re.compile(r'^#set\s*\(\s*\$([a-z_][a-z0-9_]*)\s*=\s*(.*)$', re.S + re.I) 
     301class Assignment(_Element): 
     302    START = re.compile(r'^\s*\(\s*\$([a-z_][a-z0-9_]*)\s*=\s*(.*)$', re.S) 
    269303    CLOSING_PATTERN = re.compile(r'^\s*\)(?:[ \t]*\r?\n)?(.*)$', re.S + re.M) 
    270304    def __init__(self, text): 
    271         ## Could be cleaner b/c syntax error if no '(' 
    272         self.var_name, text = self.match_or_reject(self.START, text) 
     305        self.var_name, text = self.identity_match(self.START, text) 
    273306        self.value, text = self.next_element(Value, text) 
    274307        self.remaining_text, = self.require_match(self.CLOSING_PATTERN, text, ')') 
    275308 
    276     def evaluate(self, namespace, stream): 
     309    def calculate(self, namespace): 
    277310        namespace[self.var_name] = self.value.calculate(namespace) 
     311 
     312 
     313class SetDirective(_Element): 
     314    START = re.compile(r'^#set\b(.*)', re.S + re.I) 
     315    def __init__(self, text): 
     316        text, = self.identity_match(self.START, text) 
     317        self.assignment, self.remaining_text = self.require_next_element(Assignment, text, 'assignment') 
     318 
     319    def evaluate(self, namespace, stream): 
     320        self.assignment.calculate(namespace) 
    278321 
    279322 
     
    283326    def __init__(self, text): 
    284327        ## Could be cleaner b/c syntax error if no '(' 
    285         self.loop_var_name, text = self.match_or_reject(self.START, text) 
     328        self.loop_var_name, text = self.identity_match(self.START, text) 
    286329        self.value, text = self.next_element(Value, text) 
    287330        text, = self.require_match(self.CLOSING_PATTERN, text, ')') 
     
    315358        self.children = [] 
    316359        while text: 
    317             child = None 
    318             for child_type in (Text, Placeholder, Comment, IfDirective, SetDirective, ForeachDirective): 
    319                 try: 
    320                     child, text = self.next_element(child_type, text) 
    321                     self.children.append(child) 
    322                     break 
    323                 except NoMatch: 
    324                     continue 
    325             if child is None: 
     360            try: 
     361                child, text = self.next_element((Text, Placeholder, Comment, IfDirective, SetDirective, ForeachDirective), text) 
     362                self.children.append(child) 
     363            except NoMatch: 
    326364                break 
    327365        self.remaining_text = text 
     
    330368        for child in self.children: 
    331369            child.evaluate(namespace, stream) 
    332  
    333