diff options
| author | robot-piglet <[email protected]> | 2025-11-03 13:19:34 +0300 |
|---|---|---|
| committer | robot-piglet <[email protected]> | 2025-11-03 13:30:30 +0300 |
| commit | d560383ad38f2b482e41032dec7607891c3f8ffe (patch) | |
| tree | 8980d2e3091cf4d7586773e179aae0391a7a5800 /contrib/python/iniconfig | |
| parent | 73672db4e0da266a2d32be8e83983f1b556eb68d (diff) | |
Intermediate changes
commit_hash:3eaa0363a0a20d6baf96d02762d2656dc18cf1b1
Diffstat (limited to 'contrib/python/iniconfig')
| -rw-r--r-- | contrib/python/iniconfig/.dist-info/METADATA | 14 | ||||
| -rw-r--r-- | contrib/python/iniconfig/iniconfig/__init__.py | 177 | ||||
| -rw-r--r-- | contrib/python/iniconfig/iniconfig/_parse.py | 109 | ||||
| -rw-r--r-- | contrib/python/iniconfig/iniconfig/_version.py | 19 | ||||
| -rw-r--r-- | contrib/python/iniconfig/iniconfig/exceptions.py | 6 | ||||
| -rw-r--r-- | contrib/python/iniconfig/patches/01-arcadia.patch | 32 | ||||
| -rw-r--r-- | contrib/python/iniconfig/ya.make | 2 |
7 files changed, 240 insertions, 119 deletions
diff --git a/contrib/python/iniconfig/.dist-info/METADATA b/contrib/python/iniconfig/.dist-info/METADATA index 3a8ef46a3b2..fc3c00df97b 100644 --- a/contrib/python/iniconfig/.dist-info/METADATA +++ b/contrib/python/iniconfig/.dist-info/METADATA @@ -1,29 +1,27 @@ Metadata-Version: 2.4 Name: iniconfig -Version: 2.1.0 +Version: 2.3.0 Summary: brain-dead simple config-ini parsing -Project-URL: Homepage, https://github.com/pytest-dev/iniconfig Author-email: Ronny Pfannschmidt <[email protected]>, Holger Krekel <[email protected]> License-Expression: MIT -License-File: LICENSE +Project-URL: Homepage, https://github.com/pytest-dev/iniconfig Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX -Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Utilities -Requires-Python: >=3.8 +Requires-Python: >=3.10 Description-Content-Type: text/x-rst +License-File: LICENSE +Dynamic: license-file iniconfig: brain-dead simple parsing of ini files ======================================================= diff --git a/contrib/python/iniconfig/iniconfig/__init__.py b/contrib/python/iniconfig/iniconfig/__init__.py index ed6499bc6cb..3098daf7e89 100644 --- a/contrib/python/iniconfig/iniconfig/__init__.py +++ b/contrib/python/iniconfig/iniconfig/__init__.py @@ -1,42 +1,31 @@ -""" brain-dead simple parser for ini-style files. +"""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 import Final +from collections.abc import Callable +from collections.abc import Iterator +from collections.abc import Mapping +from typing import Final +from typing import TypeVar +from typing import overload __all__ = ["IniConfig", "ParseError", "COMMENTCHARS", "iscommentline"] -from .exceptions import ParseError from . import _parse -from ._parse import COMMENTCHARS, iscommentline +from ._parse import COMMENTCHARS +from ._parse import iscommentline +from .exceptions import ParseError _D = TypeVar("_D") _T = TypeVar("_T") class SectionWrapper: - config: Final[IniConfig] + config: Final["IniConfig"] name: Final[str] - def __init__(self, config: IniConfig, name: str) -> None: + def __init__(self, config: "IniConfig", name: str) -> None: self.config = config self.name = name @@ -44,16 +33,14 @@ class SectionWrapper: return self.config.lineof(self.name, name) @overload - def get(self, key: str) -> str | None: - ... + def get(self, key: str) -> str | None: ... @overload def get( self, key: str, convert: Callable[[str], _T], - ) -> _T | None: - ... + ) -> _T | None: ... @overload def get( @@ -61,12 +48,10 @@ class SectionWrapper: key: str, default: None, convert: Callable[[str], _T], - ) -> _T | None: - ... + ) -> _T | None: ... @overload - def get(self, key: str, default: _D, convert: None = None) -> str | _D: - ... + def get(self, key: str, default: _D, convert: None = None) -> str | _D: ... @overload def get( @@ -74,8 +59,7 @@ class SectionWrapper: key: str, default: _D, convert: Callable[[str], _T], - ) -> _T | _D: - ... + ) -> _T | _D: ... # TODO: investigate possible mypy bug wrt matching the passed over data def get( # type: ignore [misc] @@ -105,47 +89,101 @@ class SectionWrapper: class IniConfig: path: Final[str] sections: Final[Mapping[str, Mapping[str, str]]] + _sources: Final[Mapping[tuple[str, str | None], int]] def __init__( self, path: str | os.PathLike[str], data: str | None = None, encoding: str = "utf-8", + *, + _sections: Mapping[str, Mapping[str, str]] | None = None, + _sources: Mapping[tuple[str, str | None], int] | None = None, ) -> None: self.path = os.fspath(path) - if data is None: - if os.path.basename(self.path).startswith('pkg:'): - import pkgutil - basename = os.path.basename(self.path) - _, package, resource = basename.split(':') - content = pkgutil.get_data(package, resource) - data = content.decode('utf-8') - else: - with open(self.path, encoding=encoding) as fp: - data = fp.read() + # Determine sections and sources + if _sections is not None and _sources is not None: + # Use provided pre-parsed data (called from parse()) + sections_data = _sections + sources = _sources + else: + # Parse the data (backward compatible path) + if data is None: + if os.path.basename(self.path).startswith('pkg:'): + import pkgutil - tokens = _parse.parse_lines(self.path, data.splitlines(True)) + basename = os.path.basename(self.path) + _, package, resource = basename.split(':') + content = pkgutil.get_data(package, resource) + data = content.decode('utf-8') + else: + with open(self.path, encoding=encoding) as fp: + data = fp.read() - self._sources = {} - sections_data: dict[str, dict[str, str]] - self.sections = sections_data = {} + # Use old behavior (no stripping) for backward compatibility + sections_data, sources = _parse.parse_ini_data( + self.path, data, strip_inline_comments=False + ) - 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 + # Assign once to Final attributes + self._sources = sources + self.sections = sections_data + + @classmethod + def parse( + cls, + path: str | os.PathLike[str], + data: str | None = None, + encoding: str = "utf-8", + *, + strip_inline_comments: bool = True, + strip_section_whitespace: bool = False, + ) -> "IniConfig": + """Parse an INI file. + + Args: + path: Path to the INI file (used for error messages) + data: Optional INI content as string. If None, reads from path. + encoding: Encoding to use when reading the file (default: utf-8) + strip_inline_comments: Whether to strip inline comments from values + (default: True). When True, comments starting with # or ; are + removed from values, matching the behavior for section comments. + strip_section_whitespace: Whether to strip whitespace from section and key names + (default: False). When True, strips Unicode whitespace from section and key names, + addressing issue #4. When False, preserves existing behavior for backward compatibility. + + Returns: + IniConfig instance with parsed configuration + + Example: + # With comment stripping (default): + config = IniConfig.parse("setup.cfg") + # value = "foo" instead of "foo # comment" + + # Without comment stripping (old behavior): + config = IniConfig.parse("setup.cfg", strip_inline_comments=False) + # value = "foo # comment" + + # With section name stripping (opt-in for issue #4): + config = IniConfig.parse("setup.cfg", strip_section_whitespace=True) + # section names and keys have Unicode whitespace stripped + """ + fspath = os.fspath(path) + + if data is None: + with open(fspath, encoding=encoding) as fp: + data = fp.read() + + sections_data, sources = _parse.parse_ini_data( + fspath, + data, + strip_inline_comments=strip_inline_comments, + strip_section_whitespace=strip_section_whitespace, + ) + + # Call constructor with pre-parsed sections and sources + return cls(path=fspath, _sections=sections_data, _sources=sources) def lineof(self, section: str, name: str | None = None) -> int | None: lineno = self._sources.get((section, name)) @@ -156,8 +194,7 @@ class IniConfig: self, section: str, name: str, - ) -> str | None: - ... + ) -> str | None: ... @overload def get( @@ -165,8 +202,7 @@ class IniConfig: section: str, name: str, convert: Callable[[str], _T], - ) -> _T | None: - ... + ) -> _T | None: ... @overload def get( @@ -175,14 +211,12 @@ class IniConfig: name: str, default: None, convert: Callable[[str], _T], - ) -> _T | None: - ... + ) -> _T | None: ... @overload def get( self, section: str, name: str, default: _D, convert: None = None - ) -> str | _D: - ... + ) -> str | _D: ... @overload def get( @@ -191,8 +225,7 @@ class IniConfig: name: str, default: _D, convert: Callable[[str], _T], - ) -> _T | _D: - ... + ) -> _T | _D: ... def get( # type: ignore self, diff --git a/contrib/python/iniconfig/iniconfig/_parse.py b/contrib/python/iniconfig/iniconfig/_parse.py index 2d03437bb1c..57b9b44e4cc 100644 --- a/contrib/python/iniconfig/iniconfig/_parse.py +++ b/contrib/python/iniconfig/iniconfig/_parse.py @@ -1,33 +1,88 @@ -from __future__ import annotations -from .exceptions import ParseError - +from collections.abc import Mapping from typing import NamedTuple +from .exceptions import ParseError COMMENTCHARS = "#;" -class _ParsedLine(NamedTuple): +class ParsedLine(NamedTuple): lineno: int section: str | None name: str | None value: str | None -def parse_lines(path: str, line_iter: list[str]) -> list[_ParsedLine]: - result: list[_ParsedLine] = [] +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) + 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)) + 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)) + result.append(ParsedLine(lineno, section, None, None)) # continuation elif name is None and data is not None: if not result: @@ -44,7 +99,13 @@ def parse_lines(path: str, line_iter: list[str]) -> list[_ParsedLine]: return result -def _parseline(path: str, line: str, lineno: int) -> tuple[str | None, str | None]: +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 = "" @@ -58,7 +119,11 @@ def _parseline(path: str, line: str, lineno: int) -> tuple[str | None, str | Non for c in COMMENTCHARS: line = line.split(c)[0].rstrip() if line[-1] == "]": - return line[1:-1], None + 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(): @@ -70,11 +135,27 @@ def _parseline(path: str, line: str, lineno: int) -> tuple[str | None, str | Non try: name, value = line.split(":", 1) except ValueError: - raise ParseError(path, lineno, "unexpected line: %r" % line) - return name.strip(), value.strip() + 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: - return None, line.strip() + 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: diff --git a/contrib/python/iniconfig/iniconfig/_version.py b/contrib/python/iniconfig/iniconfig/_version.py index e058e2c6573..b982b024d86 100644 --- a/contrib/python/iniconfig/iniconfig/_version.py +++ b/contrib/python/iniconfig/iniconfig/_version.py @@ -1,7 +1,14 @@ # file generated by setuptools-scm # don't change, don't track in version control -__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"] +__all__ = [ + "__version__", + "__version_tuple__", + "version", + "version_tuple", + "__commit_id__", + "commit_id", +] TYPE_CHECKING = False if TYPE_CHECKING: @@ -9,13 +16,19 @@ if TYPE_CHECKING: from typing import Union VERSION_TUPLE = Tuple[Union[int, str], ...] + COMMIT_ID = Union[str, None] else: VERSION_TUPLE = object + COMMIT_ID = object version: str __version__: str __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE +commit_id: COMMIT_ID +__commit_id__: COMMIT_ID -__version__ = version = '2.1.0' -__version_tuple__ = version_tuple = (2, 1, 0) +__version__ = version = '2.3.0' +__version_tuple__ = version_tuple = (2, 3, 0) + +__commit_id__ = commit_id = None diff --git a/contrib/python/iniconfig/iniconfig/exceptions.py b/contrib/python/iniconfig/iniconfig/exceptions.py index 8c4dc9a8b00..d078bc65950 100644 --- a/contrib/python/iniconfig/iniconfig/exceptions.py +++ b/contrib/python/iniconfig/iniconfig/exceptions.py @@ -1,8 +1,4 @@ -from __future__ import annotations -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from typing import Final +from typing import Final class ParseError(Exception): diff --git a/contrib/python/iniconfig/patches/01-arcadia.patch b/contrib/python/iniconfig/patches/01-arcadia.patch index d583d659ca6..22c5dcf61ec 100644 --- a/contrib/python/iniconfig/patches/01-arcadia.patch +++ b/contrib/python/iniconfig/patches/01-arcadia.patch @@ -1,21 +1,21 @@ --- contrib/python/iniconfig/iniconfig/__init__.py (index) +++ contrib/python/iniconfig/iniconfig/__init__.py (working tree) @@ -114,8 +114,16 @@ class IniConfig: - ) -> None: - self.path = os.fspath(path) - if data is None: -- with open(self.path, encoding=encoding) as fp: -- data = fp.read() -+ if os.path.basename(self.path).startswith('pkg:'): -+ import pkgutil + else: + # Parse the data (backward compatible path) + if data is None: +- with open(self.path, encoding=encoding) as fp: +- data = fp.read() ++ if os.path.basename(self.path).startswith('pkg:'): ++ import pkgutil + -+ basename = os.path.basename(self.path) -+ _, package, resource = basename.split(':') -+ content = pkgutil.get_data(package, resource) -+ data = content.decode('utf-8') -+ else: -+ with open(self.path, encoding=encoding) as fp: -+ data = fp.read() - - tokens = _parse.parse_lines(self.path, data.splitlines(True)) ++ basename = os.path.basename(self.path) ++ _, package, resource = basename.split(':') ++ content = pkgutil.get_data(package, resource) ++ data = content.decode('utf-8') ++ else: ++ with open(self.path, encoding=encoding) as fp: ++ data = fp.read() + # Use old behavior (no stripping) for backward compatibility + sections_data, sources = _parse.parse_ini_data( diff --git a/contrib/python/iniconfig/ya.make b/contrib/python/iniconfig/ya.make index 20492d75c6b..e644dce094f 100644 --- a/contrib/python/iniconfig/ya.make +++ b/contrib/python/iniconfig/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(2.1.0) +VERSION(2.3.0) LICENSE(MIT) |
