Ticket #959: filters-multi-args.patch

File filters-multi-args.patch, 14.1 KB (added by rjwittams, 18 years ago)
  • django/core/template/defaultfilters.py

    === django/core/template/defaultfilters.py
    ==================================================================
     
    178178    "Converts newlines into <br />s"
    179179    return value.replace('\n', '<br />')
    180180
    181 def removetags(value, tags):
    182     "Removes a space separated list of [X]HTML tags from the output"
    183     tags = [re.escape(tag) for tag in tags.split()]
     181def removetags(value, *tags):
     182    "Removes a list of [X]HTML tags from the output"
     183    tags = [re.escape(tag) for tag in tags]
    184184    tags_re = '(%s)' % '|'.join(tags)
    185185    starttag_re = re.compile(r'<%s(/?>|(\s+[^>]*>))' % tags_re)
    186186    endtag_re = re.compile('</%s>' % tags_re)
     
    196196    return strip_tags(value)
    197197
    198198###################
     199# COLLECTIONS     #
     200###################
     201
     202def lookup(value, arg):
     203    return value[arg]
     204
     205###################
    199206# LISTS           #
    200207###################
    201208
     
    383390# MISC            #
    384391###################
    385392
    386 def filesizeformat(bytes):
     393def filesizeformat(bytes, format=None):
    387394    """
    388395    Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
    389396    bytes, etc).
    390397    """
     398    base = format=='iso' and 1000 or 1024
    391399    bytes = float(bytes)
    392     if bytes < 1024:
     400    if bytes < base:
    393401        return "%d byte%s" % (bytes, bytes != 1 and 's' or '')
    394     if bytes < 1024 * 1024:
     402    if bytes < base * base:
    395403        return "%.1f KB" % (bytes / 1024)
    396     if bytes < 1024 * 1024 * 1024:
    397         return "%.1f MB" % (bytes / (1024 * 1024))
    398     return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
     404    if bytes < base * base * base:
     405        return "%.1f MB" % (bytes / (base * base))
     406    return "%.1f GB" % (bytes / (base * base * base))
    399407
    400408def pluralize(value):
    401409    "Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'"
     
    423431    return pformat(value)
    424432
    425433# Syntax: register.filter(name of filter, callback)
     434
    426435register.filter(add)
    427436register.filter(addslashes)
    428437register.filter(capfirst)
     
    447456register.filter(linebreaksbr)
    448457register.filter(linenumbers)
    449458register.filter(ljust)
     459register.filter(lookup)
    450460register.filter(lower)
    451461register.filter(make_list)
    452462register.filter(phone2numeric)
  • django/core/template/__init__.py

    === django/core/template/__init__.py
    ==================================================================
     
    6767
    6868# template syntax constants
    6969FILTER_SEPARATOR = '|'
    70 FILTER_ARGUMENT_SEPARATOR = ':'
     70FILTER_ARGUMENT_INTRODUCTION = ':'
     71FILTER_ARGUMENT_SEPARATOR = ','
    7172VARIABLE_ATTRIBUTE_SEPARATOR = '.'
    7273BLOCK_TAG_START = '{%'
    7374BLOCK_TAG_END = '%}'
     
    494495            self.pointer = i
    495496            return s
    496497
     498def prepare_filter_res():
     499        re_args = {
     500        'str': r"""[^"\\]*(?:\\.[^"\\]*)*""",
     501        'var_chars': "A-Za-z0-9\_\." ,
     502        'filter_sep': re.escape(FILTER_SEPARATOR),
     503        'arg_intro': re.escape(FILTER_ARGUMENT_INTRODUCTION),
     504        'arg_sep' : re.escape(FILTER_ARGUMENT_SEPARATOR),
     505        'i18n_open' : re.escape("_("),
     506        'i18n_close' : re.escape(")"),
     507        }
     508       
     509        opening_rs = r"""
     510        ^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s|
     511        ^"(?P<constant>%(str)s)"|
     512        ^(?P<var>[%(var_chars)s]+)
     513        """
     514   
     515        filter_name_rs = r"""
     516        ^%(filter_sep)s(?P<filter_name>\w+)
     517        """
     518 
     519        arg_common = r"""
     520         (?:
     521          %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s|
     522          "(?P<constant_arg>%(str)s)"|
     523          (?P<var_arg>[%(var_chars)s]+)
     524         )
     525        """
     526       
     527        first_arg_rs = r"""^%(arg_intro)s""" + arg_common
     528        further_arg_rs = r"""^%(arg_sep)s""" + arg_common
     529   
     530        raw_strings = [opening_rs, filter_name_rs, first_arg_rs, further_arg_rs]
     531        return [re.compile((s % re_args).replace("\n", "").replace(" ", "")) for s in raw_strings]
    497532
    498 
    499 
    500 filter_raw_string = r"""
    501 ^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s|
    502 ^"(?P<constant>%(str)s)"|
    503 ^(?P<var>[%(var_chars)s]+)|
    504  (?:%(filter_sep)s
    505      (?P<filter_name>\w+)
    506          (?:%(arg_sep)s
    507              (?:
    508               %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s|
    509               "(?P<constant_arg>%(str)s)"|
    510               (?P<var_arg>[%(var_chars)s]+)
    511              )
    512          )?
    513  )""" % {
    514     'str': r"""[^"\\]*(?:\\.[^"\\]*)*""",
    515     'var_chars': "A-Za-z0-9\_\." ,
    516     'filter_sep': re.escape(FILTER_SEPARATOR),
    517     'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
    518     'i18n_open' : re.escape("_("),
    519     'i18n_close' : re.escape(")"),
    520   }
    521 
    522 filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "")
    523 filter_re = re.compile(filter_raw_string)
    524 
    525533class FilterExpression(object):
    526534    """
    527535    Parses a variable token and its optional filters (all as a single string),
    528     and return a list of tuples of the filter name and arguments.
     536    and return a list of tuples of the filter function and arguments.
    529537    Sample:
    530538        >>> token = 'variable|default:"Default value"|date:"Y-m-d"'
    531539        >>> p = FilterParser(token)
     
    537545    This class should never be instantiated outside of the
    538546    get_filters_from_token helper function.
    539547    """
     548   
     549    opening_re, filter_name_re, first_arg_re, further_arg_re = prepare_filter_res()
     550   
     551   
    540552    def __init__(self, token, parser):
    541553        self.token = token
    542         matches = filter_re.finditer(token)
    543554        var = None
    544555        filters = []
    545         upto = 0
    546         for match in matches:
    547             start = match.start()
    548             if upto != start:
    549                 raise TemplateSyntaxError, "Could not parse some characters: %s|%s|%s"  % \
    550                                            (token[:upto], token[upto:start], token[start:])
     556       
     557        remainder = token
     558        # match an opening
     559        match = FilterExpression.opening_re.match(remainder)
     560        if match:
     561            var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")
     562            if var and (var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_'):
     563                raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % var
     564           
     565            if i18n_constant:
     566                var = '"%s"' %  _(i18n_constant.replace('\\', '') )
     567            elif constant:
     568                var = '"%s"' % constant.replace('\\','')
     569           
     570            remainder = remainder[match.end():]
    551571            if var == None:
    552                 var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")
    553                 if i18n_constant:
    554                     var = '"%s"' %  _(i18n_constant)
    555                 elif constant:
    556                     var = '"%s"' % constant
    557                 upto = match.end()
    558                 if var == None:
    559                     raise TemplateSyntaxError, "Could not find variable at start of %s" % token
    560                 elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
    561                     raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % var
    562             else:
    563                 filter_name = match.group("filter_name")
    564                 args = []
    565                 constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg")
    566                 if i18n_arg:
    567                     args.append((False, _(i18n_arg.replace('\\', ''))))
    568                 elif constant_arg:
    569                     args.append((False, constant_arg.replace('\\', '')))
    570                 elif var_arg:
    571                     args.append((True, var_arg))
    572                 filter_func = parser.find_filter(filter_name)
    573                 self.args_check(filter_name,filter_func, args)
    574                 filters.append( (filter_func,args))
    575                 upto = match.end()
    576         if upto != len(token):
    577             raise TemplateSyntaxError, "Could not parse the remainder: %s" % token[upto:]
     572                raise TemplateSyntaxError, "Could not find variable at start of %s" % token
     573        else:
     574            raise TemplateSyntaxError, "Could not find variable at start of |%s|" % token
     575       
     576        #match a number of filters
     577        # first, maybe match a filter name
     578        match = FilterExpression.filter_name_re.match(remainder)
     579        while match:
     580            filter_name = match.group("filter_name")
     581            filter_func = parser.find_filter(filter_name)
     582            remainder = remainder[match.end():]
     583            args = []
     584            # maybe match a first argument
     585            match = FilterExpression.first_arg_re.match(remainder)
     586            if match:
     587           
     588                args.append(self.getarg(match))
     589                remainder = remainder[match.end():]
     590                # match a number of further arguments
     591                match = FilterExpression.further_arg_re.match(remainder)
     592                while match:
     593                    args.append(self.getarg(match))
     594                    remainder = remainder[match.end():]
     595                    match = FilterExpression.further_arg_re.match(remainder)
     596            self.args_check(filter_name,filter_func, args)
     597            filters.append((filter_func, args))   
     598            # maybe match another filter name
     599            match = FilterExpression.filter_name_re.match(remainder)
     600       
     601        if len(remainder) != 0:
     602            raise TemplateSyntaxError, "Could not parse the remainder of the filter expression: %s" % remainder
    578603        self.var , self.filters = var, filters
    579604
     605    def getarg(self, match):
     606        constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg")
     607        if i18n_arg:
     608            return (False, _(i18n_arg.replace('\\', '')))
     609        elif constant_arg:
     610            return (False, constant_arg.replace('\\', ''))
     611        elif var_arg:
     612            return (True, var_arg)
     613
    580614    def resolve(self, context):
    581615        try:
    582616            obj = resolve_variable(self.var, context)
     
    602636            nondefs = args[:-len(defaults)]
    603637        else:
    604638            nondefs = args
     639       
    605640        # Args without defaults must be provided.
    606641        try:
    607642            for arg in nondefs:
     
    610645            # Not enough
    611646            raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen)
    612647
    613         # Defaults can be overridden.
    614         defaults = defaults and list(defaults) or []
    615         try:
    616             for parg in provided:
    617                 defaults.pop(0)
    618         except IndexError:
    619             # Too many.
    620             raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen)
     648        #if varargs is true, it will eat up the rest of the args.
     649        if not varargs:
     650            # Defaults can be overridden.
     651            defaults = defaults and list(defaults) or []
     652            try:
     653                for parg in provided:
     654                    defaults.pop(0)
     655            except IndexError:
     656                # Too many.
     657                raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen)
    621658
    622659        return True
    623660    args_check = staticmethod(args_check)
     
    629666    """
    630667    Returns the resolved variable, which may contain attribute syntax, within
    631668    the given context. The variable may be a hard-coded string (if it begins
    632     and ends with single or double quote marks).
     669    and ends with single or double quote marks), or an integer or float literal.
    633670
    634671    >>> c = {'article': {'section':'News'}}
    635672    >>> resolve_variable('article.section', c)
     
    645682
    646683    (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
    647684    """
    648     if path[0] in ('"', "'") and path[0] == path[-1]:
     685    if path[0] in '0123456789':
     686        number_type = '.' in path and float or int
     687        try:
     688           current = number_type(path)
     689        except ValueError:
     690           current = ''
     691    elif path[0] in ('"', "'") and path[0] == path[-1]:
    649692        current = path[1:-1]
    650693    else:
    651694        current = context
  • tests/othertests/templates.py

    === tests/othertests/templates.py
    ==================================================================
     
    101101    'basic-syntax28': ("{% %}", {}, template.TemplateSyntaxError),
    102102
    103103    # Chained filters, with an argument to the first one
    104     'basic-syntax29': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"),
     104    'basic-syntax29': ('{{ var|removetags:"b","i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"),
    105105
    106106    # Escaped string as argument
    107107    'basic-syntax30': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote" hah'),
     
    112112    # Default argument testing
    113113    'basic-syntax32' : (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),
    114114
     115    # Lookup
     116    'basic-syntax33' : (r'{{ nicks|lookup:"robert"}} {{nicks|lookup:lead}}',
     117                         {'nicks': {'robert': 'rjwittams', 'adrian': 'adrian_h'},
     118                                    'lead': 'adrian', }, 'rjwittams adrian_h'  ),
     119                                   
     120    #Numbers
     121    'basic-syntax34' : (r'{{ 1|add:2 }} {{1.0|add:2.0}}', {}, '3 3'),
     122
    115123    ### IF TAG ################################################################
    116124    'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
    117125    'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
     
    347355        raise Exception, msg
    348356
    349357if __name__ == "__main__":
     358    template.libraries['django.templatetags.testtags'] = \
     359       template.get_library('tests.testapp.templatetags.testtags')
    350360    run_tests(1, True)
Back to Top