Coverage for src/lektor_ng/inifile.py: 94%
77 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-03 19:18 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-03 19:18 +0000
1import dataclasses as dc
2import io
3import os
4from collections.abc import Generator, Iterator
5from configparser import ConfigParser, MissingSectionHeaderError
6from pathlib import Path
7from typing import Any
9GLOBAL_NAME = "xyz"
12def config_parser_load(filename: str | Path) -> tuple[ConfigParser, bool]:
13 path = Path(filename)
14 config = ConfigParser()
15 try:
16 is_new = config.read(path)
17 return config, not bool(is_new)
18 except MissingSectionHeaderError:
19 text = path.read_text()
20 config.read_string(f"[{GLOBAL_NAME}]\n{text}")
21 return config, False
24@dc.dataclass
25class IniFile:
26 filename: str
27 is_new: bool = False
29 def __post_init__(self) -> None:
30 self.filename = os.path.abspath(self.filename)
31 self.config, self.is_new = config_parser_load(self.filename)
33 def __iter__(self) -> Iterator[str]:
34 if GLOBAL_NAME in self.config:
35 for option in self.config[GLOBAL_NAME]:
36 yield option
38 for section in self.config:
39 if section in {GLOBAL_NAME, "DEFAULT"}:
40 continue
41 for option in self.config[section]:
42 yield f"{section}.{option}"
44 def get(self, name: str, default: Any = None) -> Any:
45 section, _, option = name.rpartition(".")
46 while section.endswith("."):
47 section, option = section[:-1], f".{option}"
48 if not section:
49 return self.config[GLOBAL_NAME][option] if option in self.config[GLOBAL_NAME] else default
50 if section not in self.sections():
51 return default
52 return self.config[section].get(option, default)
54 def items(self) -> Generator[tuple[str, Any], None, None]:
55 for key in self:
56 yield key, self.get(key)
58 def __getitem__(self, name: str) -> Any:
59 return self.get(name)
61 def __setitem__(self, name: str, value: Any) -> None:
62 section, _, option = name.rpartition(".")
63 if not section:
64 self.config[GLOBAL_NAME][option] = value
65 return
66 if section not in self.sections():
67 self.config.add_section(section)
68 self.config[section][option] = value
70 def sections(self) -> list[str]:
71 return self.config.sections()
73 def section_as_dict(self, name: str) -> dict[str, Any]:
74 result = {}
75 for section in self.sections():
76 if section != name:
77 continue
78 for option in self.config[section]:
79 result[option] = self.config[section][option]
80 return result
82 def get_int(self, name: str, default: Any = None) -> bool | None:
83 value = self.get(name, default)
84 if value is None:
85 return None
86 return int(value)
88 def get_bool(self, name: str, default: Any = False) -> bool | None:
89 value = self.get(name)
90 if value is None:
91 return None
92 return bool(
93 {
94 "0": False,
95 "no": False,
96 "false": False,
97 "1": True,
98 "yes": True,
99 "true": True,
100 }.get(value, default)
101 )
103 def save(self, create_folder=False) -> None:
104 raise NotImplementedError("not ready")
105 buffer = io.StringIO()
106 self.config.write(buffer)
107 with open(self.filename, "w") as fp:
108 fp.write(buffer.getvalue())