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