def parse_kw_args(tagname, bits, args_spec=None, restrict=False):
    """ keywords arguments parser for template tags

    returns a list of (argname, value) tuples
    (NB: keeps ordering and is easily turned into a dict).

    Params:
    * tagname : the name of calling tag (for error messages)
    * bits : sequence of tokens to parse as kw args
    * args_spec : (optional) dict of argname=>validator for kwargs, cf below
    * restrict : if True, only argnames in args_specs will be accepted

    If restrict=False and args_spec is None (default), this will just try
    to parse a sequence of key=val strings into a 

    About args_spec validators :
    * A validator can be either a callable, a regular expression or None.

    * If it's a callable, the callable must take the value as argument and
    return a (possibly different) value, which will become the final value
    for the argument. Any exception raised by the validator will be
    considered a rejection.

    * If it's a regexp, the value will be matched against it. A failure
    will be considered as a rejection.

    * Using None as validator only makes sense with the restrict flag set
    to True. This is useful when the only validation is on the argument
    name being expected.
    """
    
    args = []

    if restrict:
        if args_spec is None:
            raise ValueError("you must pass an args_spec dict if you want to restrict allowed args")        
        allowed = list(args_spec.keys())
        do_validate = True
    else:
        do_validate = args_spec is not None
        
    for bit in bits:
        try:
            name, val = bit.split('=')
        except ValueError:
            raise template.TemplateSyntaxError(
                "keyword arguments to '%s' tag must have 'key=value' form (got : '%s')" \
                % (tagname, bit)
                )
        
        name = str(name)        
        if do_validate:
            if restrict:
                if name in allowed:
                    # we only want each name once
                    del allowed[allowed.index(name)]
                else:
                    raise template.TemplateSyntaxError(
                        "keyword arguments to '%s' tag must be one of % (got : '%s')" \
                        % (tagname, ",".join(allowed), name)
                        )

                validate = args_spec[name]
            else: 
                validate = args_spec.get(name, None)
                
            if validate is not None:
                if callable(validate):
                    try:
                        val = validate(val)
                    except Exception, e:
                        raise template.TemplateSyntaxError(
                            "invalid optional argument '%s' for '%s' tag: '%s' (%s)" \
                            % (tagname, name, val, e)
                            )
                else:
                    # assume re
                    if re.match(validate, val) is None:
                        raise template.TemplateSyntaxError(
                            "invalid optional argument '%s' for '%s' tag: '%s' (doesn't match '%s')" \
                            % (tagname, name, val, validate)
                        )
                    
        # should be ok if we managed to get here        
        args.append((name, val))
    
    return args