import inspect from functools import partial from django.template import TemplateSyntaxError def isiterable(obj): ''' Check if arg is iterable. Object is iterable when implements metod __iter__. ''' return hasattr(obj, '__iter__') def isstring(obj): ''' Check whether `obj` is a string instance, i.e. str or unicode instance. ''' return isinstance(obj, basestring) _ARGS_TYPES = (_BOTH, _ARGS_ONLY, _KWARGS_ONLY) = (0, 1, 2) def _parse_args_and_kwargs(parser, bits_iter, sep=",", tagname=None, type=_BOTH, stop_test=lambda bit: False, return_stop_bit=False): ''' :param type: indicates to support _ARGS_ONLY, _KWARGS_ONLY or _BOTH. ''' assert type in _ARGS_TYPES, "'type' argument must be one of: _BOTH, _ARGS_ONLY, _KWARGS_ONLY." args_only, kwargs_only = (type == _ARGS_ONLY), (type == _KWARGS_ONLY) args = [] kwargs = {} tagname = " '%s'" % tagname if tagname else "" bit_test = stop_test if inspect.isfunction(stop_test) else\ (lambda bit: bit in stop_test) if (isiterable(stop_test) and not isstring(stop_test)) else\ (lambda bit: bit == stop_test) stop_bit = None for bit in bits_iter: if bit_test(bit): # if we would like to stop on the bit that passes the test: #bits_iter = itertools.chain((bit,), bits_iter) stop_bit = bit break for arg in bit.split(sep): if '=' in arg: if args_only: raise TemplateSyntaxError("Tag%s does not support kwargs arguments." % tagname) k, v = arg.split('=', 1) k = str(k.strip()) if k in kwargs: raise TemplateSyntaxError("Duplicate key '%s' in%s tag kwargs." % (k, tagname)) kwargs[k] = parser.compile_filter(v) elif arg: if kwargs_only: raise TemplateSyntaxError("Tag%s does not support non-kwargs arguments." % tagname) args.append(parser.compile_filter(arg)) ret = (args,) if args_only else\ (kwargs,) if kwargs_only else\ (args, kwargs) if return_stop_bit: ret += (stop_bit,) if len(ret) == 1: return ret[0] return ret parse_args_and_kwargs = partial(_parse_args_and_kwargs, type=_BOTH) parse_args_and_kwargs.__name__ = 'parse_args_and_kwargs' parse_args_and_kwargs.__doc__ =\ ''' Parses bits created form token "arg1,key1=val1, arg2 , ..." after splitting contents. :param sep: Single bit separator; by default ",". :rtype sep: str :param stop_test: Optional test to stop parsing earlier. This can be one of: * single string with keyword to stop on * list of string keywords to stop on * function which takes only current bit of `bits_iter` as an argument and returns boolean value. Attention: `bits_iter` will be stopped after the bit that passes the test. To obtain the stop bit use `return_stop_bit` flag and capture the additional return value. :rtype stop_test: str or list or callable :returns: List of args and dictionary of kwargs with values compiled with the parser.compile_filter() function. If `return_stop_bit` is True then also the the last bit at which parsing stopped (see `stop_test`). :rtype: tuple(list, dict [, basestring or None]) ''' parse_args = partial(_parse_args_and_kwargs, type=_ARGS_ONLY) parse_args.__name__ = 'parse_args' parse_args.__doc__ =\ ''' Parses bits created form token "arg1,arg2 , ...", after splitting contents. See `parse_args_and_kwargs` for params details. :returns: List of args with values compiled with the parser.compile_filter() function. If `return_stop_bit` is True then also the the last bit at which parsing stopped (see `stop_test`). :rtype: list or tuple(list, basestring or None) ''' parse_kwargs = partial(_parse_args_and_kwargs, type=_KWARGS_ONLY) parse_kwargs.__name__ = 'parse_kwargs' parse_kwargs.__doc__ =\ ''' Parses bits created form token "key1=val1, key2=val2, ...", after splitting contents. See parse_args_and_kwargs` for params details. :returns: Dictionary of kwargs with values compiled with the parser.compile_filter() function. If `return_stop_bit` is True then also the the last bit at which parsing stopped (see `stop_test`). :rtype: dict or tuple(dict, basestring or None) '''