Coverage for src/lektor_ng/inifile.py: 94%

77 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-08-08 16:45 +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 

8 

9GLOBAL_NAME = "xyz" 

10 

11 

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 

22 

23 

24@dc.dataclass 

25class IniFile: 

26 filename: str 

27 is_new: bool = False 

28 

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) 

32 

33 def __iter__(self) -> Iterator[str]: 

34 if GLOBAL_NAME in self.config: 

35 for option in self.config[GLOBAL_NAME]: 

36 yield option 

37 

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}" 

43 

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) 

53 

54 def items(self) -> Generator[tuple[str, Any], None, None]: 

55 for key in self: 

56 yield key, self.get(key) 

57 

58 def __getitem__(self, name: str) -> Any: 

59 return self.get(name) 

60 

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 

69 

70 def sections(self) -> list[str]: 

71 return self.config.sections() 

72 

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 

81 

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) 

87 

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 ) 

102 

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())