1__all__ = ['seqToKV', 'kvToSeq', 'dictToKV', 'kvToDict']
2
3from openid import oidutil
4
5import types
6
7class KVFormError(ValueError):
8    pass
9
10def seqToKV(seq, strict=False):
11    """Represent a sequence of pairs of strings as newline-terminated
12    key:value pairs. The pairs are generated in the order given.
13
14    @param seq: The pairs
15    @type seq: [(str, (unicode|str))]
16
17    @return: A string representation of the sequence
18    @rtype: str
19    """
20    def err(msg):
21        formatted = 'seqToKV warning: %s: %r' % (msg, seq)
22        if strict:
23            raise KVFormError(formatted)
24        else:
25            oidutil.log(formatted)
26
27    lines = []
28    for k, v in seq:
29        if isinstance(k, types.StringType):
30            k = k.decode('UTF8')
31        elif not isinstance(k, types.UnicodeType):
32            err('Converting key to string: %r' % k)
33            k = str(k)
34
35        if '\n' in k:
36            raise KVFormError(
37                'Invalid input for seqToKV: key contains newline: %r' % (k,))
38
39        if ':' in k:
40            raise KVFormError(
41                'Invalid input for seqToKV: key contains colon: %r' % (k,))
42
43        if k.strip() != k:
44            err('Key has whitespace at beginning or end: %r' % (k,))
45
46        if isinstance(v, types.StringType):
47            v = v.decode('UTF8')
48        elif not isinstance(v, types.UnicodeType):
49            err('Converting value to string: %r' % (v,))
50            v = str(v)
51
52        if '\n' in v:
53            raise KVFormError(
54                'Invalid input for seqToKV: value contains newline: %r' % (v,))
55
56        if v.strip() != v:
57            err('Value has whitespace at beginning or end: %r' % (v,))
58
59        lines.append(k + ':' + v + '\n')
60
61    return ''.join(lines).encode('UTF8')
62
63def kvToSeq(data, strict=False):
64    """
65
66    After one parse, seqToKV and kvToSeq are inverses, with no warnings::
67
68        seq = kvToSeq(s)
69        seqToKV(kvToSeq(seq)) == seq
70    """
71    def err(msg):
72        formatted = 'kvToSeq warning: %s: %r' % (msg, data)
73        if strict:
74            raise KVFormError(formatted)
75        else:
76            oidutil.log(formatted)
77
78    lines = data.split('\n')
79    if lines[-1]:
80        err('Does not end in a newline')
81    else:
82        del lines[-1]
83
84    pairs = []
85    line_num = 0
86    for line in lines:
87        line_num += 1
88
89        # Ignore blank lines
90        if not line.strip():
91            continue
92
93        pair = line.split(':', 1)
94        if len(pair) == 2:
95            k, v = pair
96            k_s = k.strip()
97            if k_s != k:
98                fmt = ('In line %d, ignoring leading or trailing '
99                       'whitespace in key %r')
100                err(fmt % (line_num, k))
101
102            if not k_s:
103                err('In line %d, got empty key' % (line_num,))
104
105            v_s = v.strip()
106            if v_s != v:
107                fmt = ('In line %d, ignoring leading or trailing '
108                       'whitespace in value %r')
109                err(fmt % (line_num, v))
110
111            pairs.append((k_s.decode('UTF8'), v_s.decode('UTF8')))
112        else:
113            err('Line %d does not contain a colon' % line_num)
114
115    return pairs
116
117def dictToKV(d):
118    seq = d.items()
119    seq.sort()
120    return seqToKV(seq)
121
122def kvToDict(s):
123    return dict(kvToSeq(s))
124