Files
vector/.venv-test/lib/python3.12/site-packages/iniconfig/_parse.py
T
Sadmin f20080305d feat: ж/д слой + cant_swim в скоринг + профили вне модели
Определение специфических рекомендаций (матрица профилей §8):

1. Ж/д слой (закрыт мёртвый railway ×2.5 у РАС):
   - /api/v1/water/{case_id} отдаёт railway=rail как LineString
     (без service/industrial/military веток), кэш общий v2;
   - railway_warning «перекрыть/проверить немедленно» по профилям;
   - SearchMap: Polyline слой ж/д (тёмно-красный), счётчики 💧/🚂.

2. cant_swim → профиль не_умеет_плавать (water ×3.0, без изменения
   радиуса, critical_warning «обследовать водоёмы НЕМЕДЛЕННО»):
   - раньше чекбокс влиял только на текст, в скоринге был пробел;
   - derive в analyze._derive_profiles — работает и для closed_cases.

3. unmodeled_profiles: ДЦП/слабое зрение/слух — честная пометка
   «вне поведенческой модели» с пояснением (vector_tasks B12:
   профили без аналога не выдавать за учтённые); блок на фронте
   в карточке здоровья.

Площадь воды: сферический эксцесс, проверен на квадрате 53° (744017 м²
vs 743272 точного). Тесты: 202 passed (новый test_cant_swim_profile).
2026-09-09 13:12:55 +03:00

164 lines
5.1 KiB
Python

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