1from __future__ import absolute_import
2from .utils import odict
3import types
4import six
5from itertools import chain
6
7try:
8    from collections import Mapping as MappingType
9except ImportError:
10    import UserDict
11    MappingType = (UserDict.UserDict, UserDict.DictMixin, dict)
12
13def flatten(l, ltypes=(list, tuple)):
14    ltype = type(l)
15    l = list(l)
16    i = 0
17    while i < len(l):
18        while isinstance(l[i], ltypes):
19            if not l[i]:
20                l.pop(i)
21                i -= 1
22                break
23            else:
24                l[i:i + 1] = l[i]
25        i += 1
26    return ltype(l)
27
28def escape(s):
29    """Convert the characters &, <, >, ' and " in string s to HTML-safe
30    sequences.  Use this if you need to display text that might contain
31    such characters in HTML.  Marks return value as markup string.
32    """
33    if hasattr(s, '__html__'):
34        return s.__html__()
35    if isinstance(s, six.binary_type):
36        s = six.text_type(str(s), 'utf8')
37    elif isinstance(s, six.text_type):
38        s = s
39    else:
40        s = str(s)
41
42    return (s
43        .replace('&', '&amp;')
44        .replace('>', '&gt;')
45        .replace('<', '&lt;')
46        .replace("'", '&#39;')
47        .replace('"', '&#34;')
48    )
49
50def attrs (attrs=[],terse=False, undefined=None):
51    buf = []
52    if bool(attrs):
53        buf.append(u'')
54        for k,v in attrs:
55            if undefined is not None and isinstance(v, undefined):
56                continue
57            if v!=None and (v!=False or type(v)!=bool):
58                if k=='class' and isinstance(v, (list, tuple)):
59                    v = u' '.join(map(str,flatten(v)))
60                t = v==True and type(v)==bool
61                if t and not terse: v=k
62                buf.append(u'%s'%k if terse and t else u'%s="%s"'%(k,escape(v)))
63    return u' '.join(buf)
64
65
66def is_mapping(value):
67    return isinstance(value, MappingType)
68
69
70def is_iterable(ob):
71    if isinstance(ob, six.string_types):
72        return False
73    try:
74        iter(ob)
75        return True
76    except TypeError:
77        return False
78
79
80def get_cardinality(ob):
81    if isinstance(ob, six.string_types):
82        return 1
83    try:
84        return len(ob)
85    except TypeError:
86        return 1
87
88
89def iteration(obj, num_keys):
90    """
91    Jade iteration supports "for 'value' [, key]?" iteration only.
92    PyJade has implicitly supported value unpacking instead, without
93    the list indexes. Trying to not break existing code, the following
94    rules are applied:
95
96      1. If the object is a mapping type, return it as-is, and assume
97         the caller has the correct set of keys defined.
98
99      2. If the object's values are iterable (and not string-like):
100         a. If the number of keys matches the cardinality of the object's
101            values, return the object as-is.
102         b. If the number of keys is one more than the cardinality of
103            values, return a list of [v(0), v(1), ... v(n), index]
104
105      3. Else the object's values are not iterable, or are string like:
106         a. if there's only one key, return the list
107         b. otherwise return a list of (value,index) tuples
108
109    """
110
111    # If the object is a mapping type, return it as-is
112    if is_mapping(obj):
113        return obj
114
115    _marker = []
116
117    iter_obj = iter(obj)
118    head = next(iter_obj, _marker)
119    iter_obj = chain([head], iter_obj)
120
121    if head is _marker:
122        # Empty list
123        return []
124
125    if is_iterable(head):
126        if num_keys == get_cardinality(head) + 1:
127            return (tuple(item) + (ix,) for ix, item in enumerate(iter_obj))
128        else:
129            return iter_obj
130
131    elif num_keys == 2:
132        return ((item, ix) for ix, item in enumerate(iter_obj))
133
134    else:
135        return iter_obj
136