Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/iniconfig/_parse.py: 94%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from collections.abc import Mapping
2from typing import NamedTuple
4from .exceptions import ParseError
6COMMENTCHARS = "#;"
9class ParsedLine(NamedTuple):
10 lineno: int
11 section: str | None
12 name: str | None
13 value: str | None
16def parse_ini_data(
17 path: str,
18 data: str,
19 *,
20 strip_inline_comments: bool,
21 strip_section_whitespace: bool = False,
22) -> tuple[Mapping[str, Mapping[str, str]], Mapping[tuple[str, str | None], int]]:
23 """Parse INI data and return sections and sources mappings.
25 Args:
26 path: Path for error messages
27 data: INI content as string
28 strip_inline_comments: Whether to strip inline comments from values
29 strip_section_whitespace: Whether to strip whitespace from section and key names
30 (default: False). When True, addresses issue #4 by stripping Unicode whitespace.
32 Returns:
33 Tuple of (sections_data, sources) where:
34 - sections_data: mapping of section -> {name -> value}
35 - sources: mapping of (section, name) -> line number
36 """
37 tokens = parse_lines(
38 path,
39 data.splitlines(True),
40 strip_inline_comments=strip_inline_comments,
41 strip_section_whitespace=strip_section_whitespace,
42 )
44 sources: dict[tuple[str, str | None], int] = {}
45 sections_data: dict[str, dict[str, str]] = {}
47 for lineno, section, name, value in tokens:
48 if section is None:
49 raise ParseError(path, lineno, "no section header defined")
50 sources[section, name] = lineno
51 if name is None:
52 if section in sections_data:
53 raise ParseError(path, lineno, f"duplicate section {section!r}")
54 sections_data[section] = {}
55 else:
56 if name in sections_data[section]:
57 raise ParseError(path, lineno, f"duplicate name {name!r}")
58 assert value is not None
59 sections_data[section][name] = value
61 return sections_data, sources
64def parse_lines(
65 path: str,
66 line_iter: list[str],
67 *,
68 strip_inline_comments: bool = False,
69 strip_section_whitespace: bool = False,
70) -> list[ParsedLine]:
71 result: list[ParsedLine] = []
72 section = None
73 for lineno, line in enumerate(line_iter):
74 name, data = _parseline(
75 path, line, lineno, strip_inline_comments, strip_section_whitespace
76 )
77 # new value
78 if name is not None and data is not None:
79 result.append(ParsedLine(lineno, section, name, data))
80 # new section
81 elif name is not None and data is None:
82 if not name:
83 raise ParseError(path, lineno, "empty section name")
84 section = name
85 result.append(ParsedLine(lineno, section, None, None))
86 # continuation
87 elif name is None and data is not None:
88 if not result:
89 raise ParseError(path, lineno, "unexpected value continuation")
90 last = result.pop()
91 if last.name is None:
92 raise ParseError(path, lineno, "unexpected value continuation")
94 if last.value:
95 last = last._replace(value=f"{last.value}\n{data}")
96 else:
97 last = last._replace(value=data)
98 result.append(last)
99 return result
102def _parseline(
103 path: str,
104 line: str,
105 lineno: int,
106 strip_inline_comments: bool,
107 strip_section_whitespace: bool,
108) -> tuple[str | None, str | None]:
109 # blank lines
110 if iscommentline(line):
111 line = ""
112 else:
113 line = line.rstrip()
114 if not line:
115 return None, None
116 # section
117 if line[0] == "[":
118 realline = line
119 for c in COMMENTCHARS:
120 line = line.split(c)[0].rstrip()
121 if line[-1] == "]":
122 section_name = line[1:-1]
123 # Optionally strip whitespace from section name (issue #4)
124 if strip_section_whitespace:
125 section_name = section_name.strip()
126 return section_name, None
127 return None, realline.strip()
128 # value
129 elif not line[0].isspace():
130 try:
131 name, value = line.split("=", 1)
132 if ":" in name:
133 raise ValueError()
134 except ValueError:
135 try:
136 name, value = line.split(":", 1)
137 except ValueError:
138 raise ParseError(path, lineno, f"unexpected line: {line!r}") from None
140 # Strip key name (always for backward compatibility, optionally with unicode awareness)
141 key_name = name.strip()
143 # Strip value
144 value = value.strip()
145 # Strip inline comments from values if requested (issue #55)
146 if strip_inline_comments:
147 for c in COMMENTCHARS:
148 value = value.split(c)[0].rstrip()
150 return key_name, value
151 # continuation
152 else:
153 line = line.strip()
154 # Strip inline comments from continuations if requested (issue #55)
155 if strip_inline_comments:
156 for c in COMMENTCHARS:
157 line = line.split(c)[0].rstrip()
158 return None, line
161def iscommentline(line: str) -> bool:
162 c = line.lstrip()[:1]
163 return c in COMMENTCHARS