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