Sindbad~EG File Manager
from collections.abc import Mapping
from typing import NamedTuple
from .exceptions import ParseError
COMMENTCHARS = "#;"
class ParsedLine(NamedTuple):
lineno: int
section: str | None
name: str | None
value: str | None
def parse_ini_data(
path: str,
data: str,
*,
strip_inline_comments: bool,
strip_section_whitespace: bool = False,
) -> tuple[Mapping[str, Mapping[str, str]], Mapping[tuple[str, str | None], int]]:
"""Parse INI data and return sections and sources mappings.
Args:
path: Path for error messages
data: INI content as string
strip_inline_comments: Whether to strip inline comments from values
strip_section_whitespace: Whether to strip whitespace from section and key names
(default: False). When True, addresses issue #4 by stripping Unicode whitespace.
Returns:
Tuple of (sections_data, sources) where:
- sections_data: mapping of section -> {name -> value}
- sources: mapping of (section, name) -> line number
"""
tokens = parse_lines(
path,
data.splitlines(True),
strip_inline_comments=strip_inline_comments,
strip_section_whitespace=strip_section_whitespace,
)
sources: dict[tuple[str, str | None], int] = {}
sections_data: dict[str, dict[str, str]] = {}
for lineno, section, name, value in tokens:
if section is None:
raise ParseError(path, lineno, "no section header defined")
sources[section, name] = lineno
if name is None:
if section in sections_data:
raise ParseError(path, lineno, f"duplicate section {section!r}")
sections_data[section] = {}
else:
if name in sections_data[section]:
raise ParseError(path, lineno, f"duplicate name {name!r}")
assert value is not None
sections_data[section][name] = value
return sections_data, sources
def parse_lines(
path: str,
line_iter: list[str],
*,
strip_inline_comments: bool = False,
strip_section_whitespace: bool = False,
) -> list[ParsedLine]:
result: list[ParsedLine] = []
section = None
for lineno, line in enumerate(line_iter):
name, data = _parseline(
path, line, lineno, strip_inline_comments, strip_section_whitespace
)
# new value
if name is not None and data is not None:
result.append(ParsedLine(lineno, section, name, data))
# new section
elif name is not None and data is None:
if not name:
raise ParseError(path, lineno, "empty section name")
section = name
result.append(ParsedLine(lineno, section, None, None))
# continuation
elif name is None and data is not None:
if not result:
raise ParseError(path, lineno, "unexpected value continuation")
last = result.pop()
if last.name is None:
raise ParseError(path, lineno, "unexpected value continuation")
if last.value:
last = last._replace(value=f"{last.value}\n{data}")
else:
last = last._replace(value=data)
result.append(last)
return result
def _parseline(
path: str,
line: str,
lineno: int,
strip_inline_comments: bool,
strip_section_whitespace: bool,
) -> tuple[str | None, str | None]:
# blank lines
if iscommentline(line):
line = ""
else:
line = line.rstrip()
if not line:
return None, None
# section
if line[0] == "[":
realline = line
for c in COMMENTCHARS:
line = line.split(c)[0].rstrip()
if line[-1] == "]":
section_name = line[1:-1]
# Optionally strip whitespace from section name (issue #4)
if strip_section_whitespace:
section_name = section_name.strip()
return section_name, None
return None, realline.strip()
# value
elif not line[0].isspace():
try:
name, value = line.split("=", 1)
if ":" in name:
raise ValueError()
except ValueError:
try:
name, value = line.split(":", 1)
except ValueError:
raise ParseError(path, lineno, f"unexpected line: {line!r}") from None
# Strip key name (always for backward compatibility, optionally with unicode awareness)
key_name = name.strip()
# Strip value
value = value.strip()
# Strip inline comments from values if requested (issue #55)
if strip_inline_comments:
for c in COMMENTCHARS:
value = value.split(c)[0].rstrip()
return key_name, value
# continuation
else:
line = line.strip()
# Strip inline comments from continuations if requested (issue #55)
if strip_inline_comments:
for c in COMMENTCHARS:
line = line.split(c)[0].rstrip()
return None, line
def iscommentline(line: str) -> bool:
c = line.lstrip()[:1]
return c in COMMENTCHARS
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists