Changeset 33

Show
Ignore:
Timestamp:
16/09/04 15:07:05 (4 years ago)
Author:
steve
Message:

interpolated strings

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/airspeed.py

    r32 r33  
    3030        if loader is None: loader = NullLoader() 
    3131        self.ensure_compiled() 
    32         self.root_element.evaluate(namespace, fileobj, loader) 
     32        self.root_element.evaluate(fileobj, namespace, loader) 
    3333 
    3434 
     
    190190 
    191191class Text(_Element): 
    192     PLAIN = re.compile(r'((?:[^\\\$#]|\\[\$#])+|\$[^!\{a-z0-9_]|\$$|\\\\)(.*)$', re.S + re.I) 
     192    PLAIN = re.compile(r'((?:[^\\\$#]|\\[\$#])+|\$[^!\{a-z0-9_]|\$$|\\.)(.*)$', re.S + re.I) 
    193193    ESCAPED_CHAR = re.compile(r'\\([\\\$#])') 
    194194 
     
    199199        self.text = self.ESCAPED_CHAR.sub(unescape, text) 
    200200 
    201     def evaluate(self, namespace, stream, loader): 
     201    def evaluate(self, stream, namespace, loader): 
    202202        stream.write(self.text) 
    203203 
     
    210210        self.value = int(self.value) 
    211211 
    212     def calculate(self, namespace): 
     212    def calculate(self, namespace, loader): 
    213213        return self.value 
    214214 
    215215 
    216216class StringLiteral(_Element): 
    217     STRING = re.compile(r'"((?:\\["nrbt\\\\]|[^"\n\r\\])+)"(.*)', re.S) 
    218     ESCAPED_CHAR = re.compile(r'\\([nrbt"\\])'
     217    STRING = re.compile(r"'((?:\\['nrbt\\\\\\$]|[^'\n\r\\])+)'(.*)", re.S) 
     218    ESCAPED_CHAR = re.compile(r"\\([nrbt'\\])"
    219219 
    220220    def parse(self): 
    221221        value, = self.identity_match(self.STRING) 
    222222        def unescape(match): 
    223             return {'n': '\n', 'r': '\r', 'b': '\b', 't': '\t', '"': '"', '\\': '\\', "'": "'"}[match.group(1)] 
     223            return {'n': '\n', 'r': '\r', 'b': '\b', 't': '\t', '"': '"', '\\': '\\', "'": "'"}.get(match.group(1), '\\' + match.group(1)) 
    224224        self.value = self.ESCAPED_CHAR.sub(unescape, value) 
    225225 
    226     def calculate(self, namespace): 
     226    def calculate(self, namespace, loader): 
    227227        return self.value 
    228228 
    229  
    230 class SingleQuotedStringLiteral(StringLiteral): 
    231     STRING = re.compile(r"'((?:\\['nrbt\\\\]|[^'\n\r\\])+)'(.*)", re.S) 
    232     ESCAPED_CHAR = re.compile(r"\\([nrbt'\\])") 
     229class InterpolatedStringLiteral(StringLiteral): 
     230    STRING = re.compile(r'"((?:\\["nrbt\\\\\\$]|[^"\n\r\\])+)"(.*)', re.S) 
     231    ESCAPED_CHAR = re.compile(r'\\([nrbt"\\])') 
     232 
     233    def parse(self): 
     234        StringLiteral.parse(self) 
     235        self.block = Block(self.value, 0) 
     236 
     237    def calculate(self, namespace, loader): 
     238        output = StringIO.StringIO() 
     239        self.block.evaluate(output, namespace, loader) 
     240        return output.getvalue() 
    233241 
    234242 
     
    239247        self.value1, self.value2 = map(int, self.identity_match(self.RANGE)) 
    240248 
    241     def calculate(self, namespace): 
     249    def calculate(self, namespace, loader): 
    242250        if self.value2 < self.value1: 
    243251            return xrange(self.value1, self.value2 - 1, -1) 
     
    259267                self.values.append(value) 
    260268 
    261     def calculate(self, namespace): 
     269    def calculate(self, namespace, loader): 
    262270        for value in self.values: 
    263             yield value.calculate(namespace
     271            yield value.calculate(namespace, loader
    264272 
    265273 
    266274class _EmptyValues: 
    267     def calculate(self, namespace): 
     275    def calculate(self, namespace, loader): 
    268276        return [] 
    269277 
     
    286294class Value(_Element): 
    287295    def parse(self): 
    288         self.expression = self.next_element((SimpleReference, IntegerLiteral, StringLiteral, SingleQuotedStringLiteral, ArrayLiteral)) 
    289  
    290     def calculate(self, namespace): 
    291         return self.expression.calculate(namespace
     296        self.expression = self.next_element((SimpleReference, IntegerLiteral, StringLiteral, InterpolatedStringLiteral, ArrayLiteral)) 
     297 
     298    def calculate(self, namespace, loader): 
     299        return self.expression.calculate(namespace, loader
    292300 
    293301 
     
    301309        except NoMatch: pass 
    302310 
    303     def calculate(self, current_object, top_namespace): 
     311    def calculate(self, current_object, loader, top_namespace): 
    304312        look_in_dict = True 
    305313        if not isinstance(current_object, LocalNamespace): 
     
    317325            return None ## TODO: an explicit 'not found' exception? 
    318326        if self.parameters is not None: 
    319             result = result(*self.parameters.calculate(top_namespace)) 
     327            result = result(*self.parameters.calculate(top_namespace, loader)) 
    320328        return result 
    321329 
     
    328336        self.expression = self.next_element(VariableExpression) 
    329337 
    330     def calculate(self, current_object, global_namespace): 
    331         return self.expression.calculate(current_object, global_namespace) 
     338    def calculate(self, current_object, loader, global_namespace): 
     339        return self.expression.calculate(current_object, loader, global_namespace) 
    332340 
    333341 
     
    340348        except NoMatch: pass 
    341349 
    342     def calculate(self, namespace, global_namespace=None): 
     350    def calculate(self, namespace, loader, global_namespace=None): 
    343351        if global_namespace == None: 
    344352            global_namespace = namespace 
    345         value = self.part.calculate(namespace, global_namespace) 
     353        value = self.part.calculate(namespace, loader, global_namespace) 
    346354        if self.subexpression: 
    347             value = self.subexpression.calculate(value, namespace) 
     355            value = self.subexpression.calculate(value, loader, namespace) 
    348356        return value 
    349357 
     
    361369        self.require_match(self.END, ')') 
    362370 
    363     def calculate(self, namespace): 
    364         return self.values.calculate(namespace
     371    def calculate(self, namespace, loader): 
     372        return self.values.calculate(namespace, loader
    365373 
    366374 
     
    374382        if self.braces: self.require_match(self.CLOSING_BRACE, '}') 
    375383 
    376     def evaluate(self, namespace, stream, loader): 
    377         value = self.expression.calculate(namespace
     384    def evaluate(self, stream, namespace, loader): 
     385        value = self.expression.calculate(namespace, loader
    378386        if value is None: 
    379387            if self.silent: value = '' 
     
    392400 
    393401class Null: 
    394     def evaluate(self, namespace, stream, loader): pass 
     402    def evaluate(self, stream, namespace, loader): pass 
    395403 
    396404 
     
    428436        self.require_match(self.END, ') or >') 
    429437 
    430     def calculate(self, namespace): 
     438    def calculate(self, namespace, loader): 
    431439        if self.binary_operator is None: 
    432             return self.value.calculate(namespace
    433         value1, value2 = self.value.calculate(namespace), self.value2.calculate(namespace
     440            return self.value.calculate(namespace, loader
     441        value1, value2 = self.value.calculate(namespace, loader), self.value2.calculate(namespace, loader
    434442        return self.binary_operator.apply_to(value1, value2) 
    435443 
     
    478486        self.require_next_element(End, '#else, #elseif or #end') 
    479487 
    480     def evaluate(self, namespace, stream, loader): 
    481         if self.condition.calculate(namespace): 
    482             self.block.evaluate(namespace, stream, loader) 
     488    def evaluate(self, stream, namespace, loader): 
     489        if self.condition.calculate(namespace, loader): 
     490            self.block.evaluate(stream, namespace, loader) 
    483491        else: 
    484492            for elseif in self.elseifs: 
    485                 if elseif.calculate(namespace): 
    486                     elseif.evaluate(namespace, stream, loader) 
     493                if elseif.calculate(namespace, loader): 
     494                    elseif.evaluate(stream, namespace, loader) 
    487495                    return 
    488             self.else_block.evaluate(namespace, stream, loader) 
     496            self.else_block.evaluate(stream, namespace, loader) 
    489497 
    490498 
     
    498506        self.require_match(self.END, ')') 
    499507 
    500     def calculate(self, namespace): 
    501         namespace[self.var_name] = self.value.calculate(namespace
     508    def evaluate(self, stream, namespace, loader): 
     509        namespace[self.var_name] = self.value.calculate(namespace, loader
    502510 
    503511 
     
    524532        self.require_next_element(End, 'block') 
    525533 
    526     def evaluate(self, namespace, stream, loader): 
     534    def evaluate(self, stream, namespace, loader): 
    527535        macro_key = '#' + self.macro_name.lower() 
    528536        if macro_key in namespace: 
     
    530538        namespace[macro_key] = self 
    531539 
    532     def execute_macro(self, namespace, stream, arg_value_elements, loader): 
     540    def execute_macro(self, stream, namespace, arg_value_elements, loader): 
    533541        if len(arg_value_elements) != len(self.arg_names): 
    534542            raise Exception("expected %d arguments, got %d" % (len(self.arg_names), len(arg_value_elements))) 
    535543        macro_namespace = LocalNamespace(namespace) 
    536544        for arg_name, arg_value in zip(self.arg_names, arg_value_elements): 
    537             macro_namespace[arg_name] = arg_value.calculate(namespace
    538         self.block.evaluate(macro_namespace, stream, loader) 
     545            macro_namespace[arg_name] = arg_value.calculate(namespace, loader
     546        self.block.evaluate(stream, macro_namespace, loader) 
    539547 
    540548 
     
    558566        self.require_match(self.CLOSE_PAREN, 'argument value or )') 
    559567 
    560     def evaluate(self, namespace, stream, loader): 
     568    def evaluate(self, stream, namespace, loader): 
    561569        try: macro = namespace['#' + self.macro_name] 
    562570        except KeyError: raise Exception('no such macro: ' + self.macro_name) 
    563         macro.execute_macro(namespace, stream, self.args, loader) 
     571        macro.execute_macro(stream, namespace, self.args, loader) 
    564572 
    565573 
     
    572580        self.identity_match(self.START) 
    573581        self.require_match(self.OPEN_PAREN, '(') 
    574         self.name = self.require_next_element((StringLiteral, SingleQuotedStringLiteral, SimpleReference), 'template name') 
     582        self.name = self.require_next_element((StringLiteral, InterpolatedStringLiteral, SimpleReference), 'template name') 
    575583        self.require_match(self.CLOSE_PAREN, ')') 
    576584 
    577     def evaluate(self, namespace, stream, loader): 
    578         stream.write(loader.load_text(self.name.calculate(namespace))) 
     585    def evaluate(self, stream, namespace, loader): 
     586        stream.write(loader.load_text(self.name.calculate(namespace, loader))) 
    579587 
    580588 
     
    587595        self.identity_match(self.START) 
    588596        self.require_match(self.OPEN_PAREN, '(') 
    589         self.name = self.require_next_element((StringLiteral, SingleQuotedStringLiteral, SimpleReference), 'template name') 
     597        self.name = self.require_next_element((StringLiteral, InterpolatedStringLiteral, SimpleReference), 'template name') 
    590598        self.require_match(self.CLOSE_PAREN, ')') 
    591599 
    592     def evaluate(self, namespace, stream, loader): 
    593         template = loader.load_template(self.name.calculate(namespace)) 
     600    def evaluate(self, stream, namespace, loader): 
     601        template = loader.load_template(self.name.calculate(namespace, loader)) 
    594602        ## TODO: local namespace? 
    595603        template.merge_to(namespace, stream, loader=loader) 
     
    603611        self.assignment = self.require_next_element(Assignment, 'assignment') 
    604612 
    605     def evaluate(self, namespace, stream, loader): 
    606         self.assignment.calculate(namespace
     613    def evaluate(self, stream, namespace, loader): 
     614        self.assignment.evaluate(stream, namespace, loader
    607615 
    608616 
     
    625633        self.require_next_element(End, '#end') 
    626634 
    627     def evaluate(self, namespace, stream, loader): 
    628         iterable = self.value.calculate(namespace
     635    def evaluate(self, stream, namespace, loader): 
     636        iterable = self.value.calculate(namespace, loader
    629637        counter = 1 
    630638        try: 
     
    633641                namespace['velocityCount'] = counter 
    634642                namespace[self.loop_var_name] = item 
    635                 self.block.evaluate(namespace, stream, loader) 
     643                self.block.evaluate(stream, namespace, loader) 
    636644                counter += 1 
    637645        except TypeError: 
    638             print iterable 
    639646            raise 
    640647 
     
    646653            raise self.syntax_error('block element') 
    647654 
    648     def evaluate(self, namespace, stream, loader): 
     655    def evaluate(self, stream, namespace, loader): 
    649656        namespace = LocalNamespace(namespace) 
    650         self.block.evaluate(namespace, stream, loader) 
     657        self.block.evaluate(stream, namespace, loader) 
    651658 
    652659 
     
    658665            except NoMatch: break 
    659666 
    660     def evaluate(self, namespace, stream, loader): 
     667    def evaluate(self, stream, namespace, loader): 
    661668        for child in self.children: 
    662             child.evaluate(namespace, stream, loader) 
     669            child.evaluate(stream, namespace, loader) 
  • trunk/airspeed_test.py

    r32 r33  
    179179        self.assertEquals("1296", template.merge(locals())) 
    180180 
    181     def test_can_call_function_with_one_parameter(self): 
     181    def test_can_call_function_with_two_parameters(self): 
    182182        def multiply(number1, number2): 
    183183            return number1 * number2 
     
    398398        self.assertEquals(" $user.name ", template.merge({'user':MyObject()})) 
    399399 
     400    def test_variables_expanded_in_double_quoted_strings(self): 
     401        template = airspeed.Template('#set($hello="hello, $name is my name")$hello') 
     402        self.assertEquals("hello, Steve is my name", template.merge({'name':'Steve'})) 
     403 
     404    def test_escaped_variable_references_not_expanded_in_double_quoted_strings(self): 
     405        template = airspeed.Template('#set($hello="hello, \\$name is my name")$hello') 
     406        self.assertEquals("hello, $name is my name", template.merge({'name':'Steve'})) 
     407 
     408    def test_macros_expanded_in_double_quoted_strings(self): 
     409        template = airspeed.Template('#macro(hi $person)$person says hello#end#set($hello="#hi($name)")$hello') 
     410        self.assertEquals("Steve says hello", template.merge({'name':'Steve'})) 
     411 
    400412# 
    401413# TODO: 
    402414# 
     415#  Report locations for template errors in strings 
    403416#  Math expressions 
    404417#  Gobbling up whitespace (tricky!) 
     
    406419#  Bind #macro calls at compile time? 
    407420#  #stop ? 
    408 #  Interpolated strings -- what about \$ etc? 
    409 #  Directives inside string literals 
    410421#  map literals 
    411422#  Sub-object assignment:  #set( $customer.Behavior = $primate )