_parse.py 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. from __future__ import annotations
  2. from .exceptions import ParseError
  3. from typing import NamedTuple
  4. COMMENTCHARS = "#;"
  5. class _ParsedLine(NamedTuple):
  6. lineno: int
  7. section: str | None
  8. name: str | None
  9. value: str | None
  10. def parse_lines(path: str, line_iter: list[str]) -> list[_ParsedLine]:
  11. result: list[_ParsedLine] = []
  12. section = None
  13. for lineno, line in enumerate(line_iter):
  14. name, data = _parseline(path, line, lineno)
  15. # new value
  16. if name is not None and data is not None:
  17. result.append(_ParsedLine(lineno, section, name, data))
  18. # new section
  19. elif name is not None and data is None:
  20. if not name:
  21. raise ParseError(path, lineno, "empty section name")
  22. section = name
  23. result.append(_ParsedLine(lineno, section, None, None))
  24. # continuation
  25. elif name is None and data is not None:
  26. if not result:
  27. raise ParseError(path, lineno, "unexpected value continuation")
  28. last = result.pop()
  29. if last.name is None:
  30. raise ParseError(path, lineno, "unexpected value continuation")
  31. if last.value:
  32. last = last._replace(value=f"{last.value}\n{data}")
  33. else:
  34. last = last._replace(value=data)
  35. result.append(last)
  36. return result
  37. def _parseline(path: str, line: str, lineno: int) -> tuple[str | None, str | None]:
  38. # blank lines
  39. if iscommentline(line):
  40. line = ""
  41. else:
  42. line = line.rstrip()
  43. if not line:
  44. return None, None
  45. # section
  46. if line[0] == "[":
  47. realline = line
  48. for c in COMMENTCHARS:
  49. line = line.split(c)[0].rstrip()
  50. if line[-1] == "]":
  51. return line[1:-1], None
  52. return None, realline.strip()
  53. # value
  54. elif not line[0].isspace():
  55. try:
  56. name, value = line.split("=", 1)
  57. if ":" in name:
  58. raise ValueError()
  59. except ValueError:
  60. try:
  61. name, value = line.split(":", 1)
  62. except ValueError:
  63. raise ParseError(path, lineno, "unexpected line: %r" % line)
  64. return name.strip(), value.strip()
  65. # continuation
  66. else:
  67. return None, line.strip()
  68. def iscommentline(line: str) -> bool:
  69. c = line.lstrip()[:1]
  70. return c in COMMENTCHARS