| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- """ brain-dead simple parser for ini-style files.
- (C) Ronny Pfannschmidt, Holger Krekel -- MIT licensed
- """
- from __future__ import annotations
- from typing import (
- Callable,
- Iterator,
- Mapping,
- Optional,
- Tuple,
- TypeVar,
- Union,
- TYPE_CHECKING,
- NoReturn,
- NamedTuple,
- overload,
- cast,
- )
- import os
- if TYPE_CHECKING:
- from typing_extensions import Final
- __all__ = ["IniConfig", "ParseError", "COMMENTCHARS", "iscommentline"]
- from .exceptions import ParseError
- from . import _parse
- from ._parse import COMMENTCHARS, iscommentline
- _D = TypeVar("_D")
- _T = TypeVar("_T")
- class SectionWrapper:
- config: Final[IniConfig]
- name: Final[str]
- def __init__(self, config: IniConfig, name: str) -> None:
- self.config = config
- self.name = name
- def lineof(self, name: str) -> int | None:
- return self.config.lineof(self.name, name)
- @overload
- def get(self, key: str) -> str | None:
- ...
- @overload
- def get(
- self,
- key: str,
- convert: Callable[[str], _T],
- ) -> _T | None:
- ...
- @overload
- def get(
- self,
- key: str,
- default: None,
- convert: Callable[[str], _T],
- ) -> _T | None:
- ...
- @overload
- def get(self, key: str, default: _D, convert: None = None) -> str | _D:
- ...
- @overload
- def get(
- self,
- key: str,
- default: _D,
- convert: Callable[[str], _T],
- ) -> _T | _D:
- ...
- # TODO: investigate possible mypy bug wrt matching the passed over data
- def get( # type: ignore [misc]
- self,
- key: str,
- default: _D | None = None,
- convert: Callable[[str], _T] | None = None,
- ) -> _D | _T | str | None:
- return self.config.get(self.name, key, convert=convert, default=default)
- def __getitem__(self, key: str) -> str:
- return self.config.sections[self.name][key]
- def __iter__(self) -> Iterator[str]:
- section: Mapping[str, str] = self.config.sections.get(self.name, {})
- def lineof(key: str) -> int:
- return self.config.lineof(self.name, key) # type: ignore[return-value]
- yield from sorted(section, key=lineof)
- def items(self) -> Iterator[tuple[str, str]]:
- for name in self:
- yield name, self[name]
- class IniConfig:
- path: Final[str]
- sections: Final[Mapping[str, Mapping[str, str]]]
- def __init__(
- self,
- path: str | os.PathLike[str],
- data: str | None = None,
- encoding: str = "utf-8",
- ) -> None:
- self.path = os.fspath(path)
- if data is None:
- with open(self.path, encoding=encoding) as fp:
- data = fp.read()
- tokens = _parse.parse_lines(self.path, data.splitlines(True))
- self._sources = {}
- sections_data: dict[str, dict[str, str]]
- self.sections = sections_data = {}
- for lineno, section, name, value in tokens:
- if section is None:
- raise ParseError(self.path, lineno, "no section header defined")
- self._sources[section, name] = lineno
- if name is None:
- if section in self.sections:
- raise ParseError(
- self.path, lineno, f"duplicate section {section!r}"
- )
- sections_data[section] = {}
- else:
- if name in self.sections[section]:
- raise ParseError(self.path, lineno, f"duplicate name {name!r}")
- assert value is not None
- sections_data[section][name] = value
- def lineof(self, section: str, name: str | None = None) -> int | None:
- lineno = self._sources.get((section, name))
- return None if lineno is None else lineno + 1
- @overload
- def get(
- self,
- section: str,
- name: str,
- ) -> str | None:
- ...
- @overload
- def get(
- self,
- section: str,
- name: str,
- convert: Callable[[str], _T],
- ) -> _T | None:
- ...
- @overload
- def get(
- self,
- section: str,
- name: str,
- default: None,
- convert: Callable[[str], _T],
- ) -> _T | None:
- ...
- @overload
- def get(
- self, section: str, name: str, default: _D, convert: None = None
- ) -> str | _D:
- ...
- @overload
- def get(
- self,
- section: str,
- name: str,
- default: _D,
- convert: Callable[[str], _T],
- ) -> _T | _D:
- ...
- def get( # type: ignore
- self,
- section: str,
- name: str,
- default: _D | None = None,
- convert: Callable[[str], _T] | None = None,
- ) -> _D | _T | str | None:
- try:
- value: str = self.sections[section][name]
- except KeyError:
- return default
- else:
- if convert is not None:
- return convert(value)
- else:
- return value
- def __getitem__(self, name: str) -> SectionWrapper:
- if name not in self.sections:
- raise KeyError(name)
- return SectionWrapper(self, name)
- def __iter__(self) -> Iterator[SectionWrapper]:
- for name in sorted(self.sections, key=self.lineof): # type: ignore
- yield SectionWrapper(self, name)
- def __contains__(self, arg: str) -> bool:
- return arg in self.sections
|