kvform.py 3.3 KB

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