service-config-foundry 0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. service-config-foundry-0.1/LICENSE +21 -0
  2. service-config-foundry-0.1/PKG-INFO +304 -0
  3. service-config-foundry-0.1/README.md +284 -0
  4. service-config-foundry-0.1/service_config_foundry/__init__.py +3 -0
  5. service-config-foundry-0.1/service_config_foundry/config_parser.py +74 -0
  6. service-config-foundry-0.1/service_config_foundry/file_type.py +212 -0
  7. service-config-foundry-0.1/service_config_foundry/sections/__init__.py +11 -0
  8. service-config-foundry-0.1/service_config_foundry/sections/automount.py +6 -0
  9. service-config-foundry-0.1/service_config_foundry/sections/install.py +8 -0
  10. service-config-foundry-0.1/service_config_foundry/sections/mount.py +10 -0
  11. service-config-foundry-0.1/service_config_foundry/sections/path.py +12 -0
  12. service-config-foundry-0.1/service_config_foundry/sections/scope.py +7 -0
  13. service-config-foundry-0.1/service_config_foundry/sections/service.py +44 -0
  14. service-config-foundry-0.1/service_config_foundry/sections/slice.py +7 -0
  15. service-config-foundry-0.1/service_config_foundry/sections/socket.py +12 -0
  16. service-config-foundry-0.1/service_config_foundry/sections/swap.py +8 -0
  17. service-config-foundry-0.1/service_config_foundry/sections/timer.py +14 -0
  18. service-config-foundry-0.1/service_config_foundry/sections/unit.py +13 -0
  19. service-config-foundry-0.1/service_config_foundry/service.py +192 -0
  20. service-config-foundry-0.1/service_config_foundry/service_location.py +37 -0
  21. service-config-foundry-0.1/service_config_foundry/utils.py +78 -0
  22. service-config-foundry-0.1/service_config_foundry.egg-info/PKG-INFO +304 -0
  23. service-config-foundry-0.1/service_config_foundry.egg-info/SOURCES.txt +25 -0
  24. service-config-foundry-0.1/service_config_foundry.egg-info/dependency_links.txt +1 -0
  25. service-config-foundry-0.1/service_config_foundry.egg-info/top_level.txt +1 -0
  26. service-config-foundry-0.1/setup.cfg +4 -0
  27. service-config-foundry-0.1/setup.py +27 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Yush Raj Kapoor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,304 @@
1
+ Metadata-Version: 2.1
2
+ Name: service-config-foundry
3
+ Version: 0.1
4
+ Summary: Helps create non-templated systemd services.
5
+ Home-page: https://github.com/yushdotkapoor/service-config-foundry
6
+ Author: Yush Kapoor
7
+ Author-email: yushdotkapoor@gmail.com
8
+ License: UNKNOWN
9
+ Project-URL: Bug Tracker, https://github.com/yushdotkapoor/service-config-foundry/issues
10
+ Platform: UNKNOWN
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Intended Audience :: Developers
15
+ Requires-Python: >=3.0
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+
19
+ # service-config-foundry
20
+
21
+ **service-config-foundry** is a Python-based library that simplifies the process of creating, updating, replacing, and deleting systemd service files for Linux users. With this tool, you can easily manage systemd services programmatically, reducing the complexity of writing and maintaining service configuration files manually.
22
+
23
+ ## Features
24
+
25
+ - Create and manage non-templated systemd service files
26
+ - Support for various systemd file types including `.service`, `.timer`, `.socket`, `.mount`, and more
27
+ - Programmatic control over file attributes and configurations
28
+ - Replace and update existing services without conflicts
29
+ - Fully integrated Python API with no external dependencies
30
+
31
+ ## Installation
32
+
33
+ You can install the library using `pip` directly from the GitHub repository:
34
+
35
+ ```bash
36
+ pip install git+https://github.com/yushdotkapoor/service-config-foundry.git
37
+ ```
38
+
39
+ Alternatively, clone the repository to get started:
40
+
41
+ ```bash
42
+ git clone https://github.com/yushdotkapoor/service-config-foundry.git
43
+ cd service-config-foundry
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ Below are detailed examples of how to use `service-config-foundry` to create and manage systemd files of various types.
49
+
50
+ ### Example: Creating a Service, Timer, and Socket
51
+
52
+ ```python
53
+ from service import Service
54
+ from service_location import ServiceLocation
55
+
56
+ # Create a new service instance
57
+ service = Service("example", service_location=ServiceLocation.GLOBAL, auto_start=False, enable_at_startup=False, force_overwrite=False)
58
+
59
+ # Configure the service file
60
+ service_file = service.service_file
61
+ service_file.unit.description = "Example service for demonstration purposes"
62
+ service_file.service.user = "yushrajkapoor"
63
+ service_file.service.exec_start = "echo Hello, world >> /tmp/example.log"
64
+ service_file.install.wanted_by = "multi-user.target"
65
+
66
+ # Configure the timer file
67
+ timer_file = service.timer_file
68
+ timer_file.unit.description = "Example timer for demonstration purposes"
69
+ timer_file.timer.on_calendar = "*-*-* *:00:00"
70
+ timer_file.install.wanted_by = "timers.target"
71
+
72
+ # Configure the socket file
73
+ socket_file = service.socket_file
74
+ socket_file.unit.description = "Example socket for demonstration purposes"
75
+ socket_file.socket.listen_stream = "/run/example.sock"
76
+ socket_file.install.wanted_by = "sockets.target"
77
+
78
+ # Create or update the service, timer, and socket files
79
+ service.update()
80
+
81
+ # Enable the service to start at boot time
82
+ service.enable_service_at_startup()
83
+
84
+ # Start the service
85
+ service.start_service()
86
+
87
+ # Display the status of the service
88
+ service.status()
89
+ ```
90
+
91
+ ### Output Files
92
+
93
+ #### `example.service`
94
+ ```ini
95
+ [Unit]
96
+ Description = Example service for demonstration purposes
97
+
98
+ [Service]
99
+ User = yushrajkapoor
100
+ ExecStart = echo Hello, world >> /tmp/example.log
101
+
102
+ [Install]
103
+ WantedBy = multi-user.target
104
+ ```
105
+
106
+ #### `example.timer`
107
+ ```ini
108
+ [Unit]
109
+ Description = Example timer for demonstration purposes
110
+
111
+ [Timer]
112
+ OnCalendar = *-*-* *:00:00
113
+
114
+ [Install]
115
+ WantedBy = timers.target
116
+ ```
117
+
118
+ #### `example.socket`
119
+ ```ini
120
+ [Unit]
121
+ Description = Example socket for demonstration purposes
122
+
123
+ [Socket]
124
+ ListenStream = /run/example.sock
125
+
126
+ [Install]
127
+ WantedBy = sockets.target
128
+ ```
129
+
130
+ ### Example: Replacing an Existing Service
131
+
132
+ If you want to replace an existing service configuration, you can use the `replace()` method. This ensures that any old files are removed and replaced with the new configuration.
133
+
134
+ ```python
135
+ # Replace the existing service configuration
136
+ service.replace()
137
+ ```
138
+
139
+ This is particularly useful when you want to ensure that your updates overwrite any conflicting configurations from previous versions of the service.
140
+
141
+ ### Example: Creating Mount and Automount Files
142
+
143
+ ```python
144
+ # Configure the mount file
145
+ mount_file = service.mount_file
146
+ mount_file.unit.description = "Example mount for demonstration purposes"
147
+ mount_file.mount.what = "/dev/sda1"
148
+ mount_file.mount.where = "/mnt/example"
149
+ mount_file.mount.type = "ext4"
150
+ mount_file.install.wanted_by = "multi-user.target"
151
+
152
+ # Configure the automount file
153
+ automount_file = service.automount_file
154
+ automount_file.unit.description = "Example automount for demonstration purposes"
155
+ automount_file.automount.where = "/mnt/example"
156
+ automount_file.install.wanted_by = "multi-user.target"
157
+
158
+ # Create or update the files
159
+ service.update()
160
+ ```
161
+
162
+ #### `example.mount`
163
+ ```ini
164
+ [Unit]
165
+ Description = Example mount for demonstration purposes
166
+
167
+ [Mount]
168
+ What = /dev/sda1
169
+ Where = /mnt/example
170
+ Type = ext4
171
+
172
+ [Install]
173
+ WantedBy = multi-user.target
174
+ ```
175
+
176
+ #### `example.automount`
177
+ ```ini
178
+ [Unit]
179
+ Description = Example automount for demonstration purposes
180
+
181
+ [Automount]
182
+ Where = /mnt/example
183
+
184
+ [Install]
185
+ WantedBy = multi-user.target
186
+ ```
187
+
188
+ ### Example: Creating Swap and Path Files
189
+
190
+ ```python
191
+ # Configure the swap file
192
+ swap_file = service.swap_file
193
+ swap_file.unit.description = "Example swap for demonstration purposes"
194
+ swap_file.swap.what = "/swapfile"
195
+ swap_file.install.wanted_by = "multi-user.target"
196
+
197
+ # Configure the path file
198
+ path_file = service.path_file
199
+ path_file.unit.description = "Example path for demonstration purposes"
200
+ path_file.path.path_exists = "/tmp/example"
201
+ path_file.install.wanted_by = "multi-user.target"
202
+
203
+ # Create or update the files
204
+ service.update()
205
+ ```
206
+
207
+ #### `example.swap`
208
+ ```ini
209
+ [Unit]
210
+ Description = Example swap for demonstration purposes
211
+
212
+ [Swap]
213
+ What = /swapfile
214
+
215
+ [Install]
216
+ WantedBy = multi-user.target
217
+ ```
218
+
219
+ #### `example.path`
220
+ ```ini
221
+ [Unit]
222
+ Description = Example path for demonstration purposes
223
+
224
+ [Path]
225
+ PathExists = /tmp/example
226
+
227
+ [Install]
228
+ WantedBy = multi-user.target
229
+ ```
230
+
231
+ ### Example: Creating Slice and Scope Files
232
+
233
+ ```python
234
+ # Configure the slice file
235
+ slice_file = service.slice_file
236
+ slice_file.unit.description = "Example slice for demonstration purposes"
237
+
238
+ # Configure the scope file
239
+ scope_file = service.scope_file
240
+ scope_file.unit.description = "Example scope for demonstration purposes"
241
+ scope_file.scope.slice = "example.slice"
242
+
243
+ # Create or update the files
244
+ service.update()
245
+ ```
246
+
247
+ #### `example.slice`
248
+ ```ini
249
+ [Unit]
250
+ Description = Example slice for demonstration purposes
251
+ ```
252
+
253
+ #### `example.scope`
254
+ ```ini
255
+ [Unit]
256
+ Description = Example scope for demonstration purposes
257
+
258
+ [Scope]
259
+ Slice = example.slice
260
+ ```
261
+
262
+ ### Deleting a Service
263
+
264
+ To delete a service and its associated files:
265
+
266
+ ```python
267
+ service.delete()
268
+ ```
269
+
270
+ This will remove all files matching the service name in the systemd directory.
271
+
272
+ ## Configuration Options
273
+
274
+ `service-config-foundry` supports multiple systemd file types, including:
275
+
276
+ - `.service` - Main service configuration
277
+ - `.socket` - Socket configuration
278
+ - `.timer` - Timer configuration
279
+ - `.mount` - Mount point configuration
280
+ - `.automount` - Automount configuration
281
+ - `.swap` - Swap space configuration
282
+ - `.path` - Path configuration
283
+ - `.slice` - Slice configuration
284
+ - `.scope` - Scope configuration
285
+
286
+ ## License
287
+
288
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
289
+
290
+ ## Contributing
291
+
292
+ Contributions are welcome! If you encounter any issues or have suggestions for improvement, please open an issue or submit a pull request.
293
+
294
+ ## Contact
295
+
296
+ - **Author**: Yush Kapoor
297
+ - **Email**: [yushdotkapoor@gmail.com](mailto:yushdotkapoor@gmail.com)
298
+ - **GitHub**: [https://github.com/yushdotkapoor/service-config-foundry](https://github.com/yushdotkapoor/service-config-foundry)
299
+
300
+ ## Acknowledgments
301
+
302
+ Special thanks to the Linux and Python communities for their inspiration and support in creating this project.
303
+
304
+
@@ -0,0 +1,284 @@
1
+ # service-config-foundry
2
+
3
+ **service-config-foundry** is a Python-based library that simplifies the process of creating, updating, replacing, and deleting systemd service files for Linux users. With this tool, you can easily manage systemd services programmatically, reducing the complexity of writing and maintaining service configuration files manually.
4
+
5
+ ## Features
6
+
7
+ - Create and manage non-templated systemd service files
8
+ - Support for various systemd file types including `.service`, `.timer`, `.socket`, `.mount`, and more
9
+ - Programmatic control over file attributes and configurations
10
+ - Replace and update existing services without conflicts
11
+ - Fully integrated Python API with no external dependencies
12
+
13
+ ## Installation
14
+
15
+ You can install the library using `pip` directly from the GitHub repository:
16
+
17
+ ```bash
18
+ pip install git+https://github.com/yushdotkapoor/service-config-foundry.git
19
+ ```
20
+
21
+ Alternatively, clone the repository to get started:
22
+
23
+ ```bash
24
+ git clone https://github.com/yushdotkapoor/service-config-foundry.git
25
+ cd service-config-foundry
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ Below are detailed examples of how to use `service-config-foundry` to create and manage systemd files of various types.
31
+
32
+ ### Example: Creating a Service, Timer, and Socket
33
+
34
+ ```python
35
+ from service import Service
36
+ from service_location import ServiceLocation
37
+
38
+ # Create a new service instance
39
+ service = Service("example", service_location=ServiceLocation.GLOBAL, auto_start=False, enable_at_startup=False, force_overwrite=False)
40
+
41
+ # Configure the service file
42
+ service_file = service.service_file
43
+ service_file.unit.description = "Example service for demonstration purposes"
44
+ service_file.service.user = "yushrajkapoor"
45
+ service_file.service.exec_start = "echo Hello, world >> /tmp/example.log"
46
+ service_file.install.wanted_by = "multi-user.target"
47
+
48
+ # Configure the timer file
49
+ timer_file = service.timer_file
50
+ timer_file.unit.description = "Example timer for demonstration purposes"
51
+ timer_file.timer.on_calendar = "*-*-* *:00:00"
52
+ timer_file.install.wanted_by = "timers.target"
53
+
54
+ # Configure the socket file
55
+ socket_file = service.socket_file
56
+ socket_file.unit.description = "Example socket for demonstration purposes"
57
+ socket_file.socket.listen_stream = "/run/example.sock"
58
+ socket_file.install.wanted_by = "sockets.target"
59
+
60
+ # Create or update the service, timer, and socket files
61
+ service.update()
62
+
63
+ # Enable the service to start at boot time
64
+ service.enable_service_at_startup()
65
+
66
+ # Start the service
67
+ service.start_service()
68
+
69
+ # Display the status of the service
70
+ service.status()
71
+ ```
72
+
73
+ ### Output Files
74
+
75
+ #### `example.service`
76
+ ```ini
77
+ [Unit]
78
+ Description = Example service for demonstration purposes
79
+
80
+ [Service]
81
+ User = yushrajkapoor
82
+ ExecStart = echo Hello, world >> /tmp/example.log
83
+
84
+ [Install]
85
+ WantedBy = multi-user.target
86
+ ```
87
+
88
+ #### `example.timer`
89
+ ```ini
90
+ [Unit]
91
+ Description = Example timer for demonstration purposes
92
+
93
+ [Timer]
94
+ OnCalendar = *-*-* *:00:00
95
+
96
+ [Install]
97
+ WantedBy = timers.target
98
+ ```
99
+
100
+ #### `example.socket`
101
+ ```ini
102
+ [Unit]
103
+ Description = Example socket for demonstration purposes
104
+
105
+ [Socket]
106
+ ListenStream = /run/example.sock
107
+
108
+ [Install]
109
+ WantedBy = sockets.target
110
+ ```
111
+
112
+ ### Example: Replacing an Existing Service
113
+
114
+ If you want to replace an existing service configuration, you can use the `replace()` method. This ensures that any old files are removed and replaced with the new configuration.
115
+
116
+ ```python
117
+ # Replace the existing service configuration
118
+ service.replace()
119
+ ```
120
+
121
+ This is particularly useful when you want to ensure that your updates overwrite any conflicting configurations from previous versions of the service.
122
+
123
+ ### Example: Creating Mount and Automount Files
124
+
125
+ ```python
126
+ # Configure the mount file
127
+ mount_file = service.mount_file
128
+ mount_file.unit.description = "Example mount for demonstration purposes"
129
+ mount_file.mount.what = "/dev/sda1"
130
+ mount_file.mount.where = "/mnt/example"
131
+ mount_file.mount.type = "ext4"
132
+ mount_file.install.wanted_by = "multi-user.target"
133
+
134
+ # Configure the automount file
135
+ automount_file = service.automount_file
136
+ automount_file.unit.description = "Example automount for demonstration purposes"
137
+ automount_file.automount.where = "/mnt/example"
138
+ automount_file.install.wanted_by = "multi-user.target"
139
+
140
+ # Create or update the files
141
+ service.update()
142
+ ```
143
+
144
+ #### `example.mount`
145
+ ```ini
146
+ [Unit]
147
+ Description = Example mount for demonstration purposes
148
+
149
+ [Mount]
150
+ What = /dev/sda1
151
+ Where = /mnt/example
152
+ Type = ext4
153
+
154
+ [Install]
155
+ WantedBy = multi-user.target
156
+ ```
157
+
158
+ #### `example.automount`
159
+ ```ini
160
+ [Unit]
161
+ Description = Example automount for demonstration purposes
162
+
163
+ [Automount]
164
+ Where = /mnt/example
165
+
166
+ [Install]
167
+ WantedBy = multi-user.target
168
+ ```
169
+
170
+ ### Example: Creating Swap and Path Files
171
+
172
+ ```python
173
+ # Configure the swap file
174
+ swap_file = service.swap_file
175
+ swap_file.unit.description = "Example swap for demonstration purposes"
176
+ swap_file.swap.what = "/swapfile"
177
+ swap_file.install.wanted_by = "multi-user.target"
178
+
179
+ # Configure the path file
180
+ path_file = service.path_file
181
+ path_file.unit.description = "Example path for demonstration purposes"
182
+ path_file.path.path_exists = "/tmp/example"
183
+ path_file.install.wanted_by = "multi-user.target"
184
+
185
+ # Create or update the files
186
+ service.update()
187
+ ```
188
+
189
+ #### `example.swap`
190
+ ```ini
191
+ [Unit]
192
+ Description = Example swap for demonstration purposes
193
+
194
+ [Swap]
195
+ What = /swapfile
196
+
197
+ [Install]
198
+ WantedBy = multi-user.target
199
+ ```
200
+
201
+ #### `example.path`
202
+ ```ini
203
+ [Unit]
204
+ Description = Example path for demonstration purposes
205
+
206
+ [Path]
207
+ PathExists = /tmp/example
208
+
209
+ [Install]
210
+ WantedBy = multi-user.target
211
+ ```
212
+
213
+ ### Example: Creating Slice and Scope Files
214
+
215
+ ```python
216
+ # Configure the slice file
217
+ slice_file = service.slice_file
218
+ slice_file.unit.description = "Example slice for demonstration purposes"
219
+
220
+ # Configure the scope file
221
+ scope_file = service.scope_file
222
+ scope_file.unit.description = "Example scope for demonstration purposes"
223
+ scope_file.scope.slice = "example.slice"
224
+
225
+ # Create or update the files
226
+ service.update()
227
+ ```
228
+
229
+ #### `example.slice`
230
+ ```ini
231
+ [Unit]
232
+ Description = Example slice for demonstration purposes
233
+ ```
234
+
235
+ #### `example.scope`
236
+ ```ini
237
+ [Unit]
238
+ Description = Example scope for demonstration purposes
239
+
240
+ [Scope]
241
+ Slice = example.slice
242
+ ```
243
+
244
+ ### Deleting a Service
245
+
246
+ To delete a service and its associated files:
247
+
248
+ ```python
249
+ service.delete()
250
+ ```
251
+
252
+ This will remove all files matching the service name in the systemd directory.
253
+
254
+ ## Configuration Options
255
+
256
+ `service-config-foundry` supports multiple systemd file types, including:
257
+
258
+ - `.service` - Main service configuration
259
+ - `.socket` - Socket configuration
260
+ - `.timer` - Timer configuration
261
+ - `.mount` - Mount point configuration
262
+ - `.automount` - Automount configuration
263
+ - `.swap` - Swap space configuration
264
+ - `.path` - Path configuration
265
+ - `.slice` - Slice configuration
266
+ - `.scope` - Scope configuration
267
+
268
+ ## License
269
+
270
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
271
+
272
+ ## Contributing
273
+
274
+ Contributions are welcome! If you encounter any issues or have suggestions for improvement, please open an issue or submit a pull request.
275
+
276
+ ## Contact
277
+
278
+ - **Author**: Yush Kapoor
279
+ - **Email**: [yushdotkapoor@gmail.com](mailto:yushdotkapoor@gmail.com)
280
+ - **GitHub**: [https://github.com/yushdotkapoor/service-config-foundry](https://github.com/yushdotkapoor/service-config-foundry)
281
+
282
+ ## Acknowledgments
283
+
284
+ Special thanks to the Linux and Python communities for their inspiration and support in creating this project.
@@ -0,0 +1,3 @@
1
+ from .service import *
2
+
3
+ __version__ = "0.1"
@@ -0,0 +1,74 @@
1
+ from configparser import ConfigParser
2
+ from collections import defaultdict, OrderedDict
3
+
4
+
5
+ class CaseSensitiveConfigParser(ConfigParser):
6
+ def __init__(self, *args, **kwargs):
7
+ # Disable interpolation to prevent issues with list values
8
+ kwargs['interpolation'] = None
9
+ super().__init__(*args, **kwargs)
10
+ self._dict = OrderedDict
11
+
12
+ def optionxform(self, optionstr):
13
+ # Preserve case sensitivity for option names
14
+ return optionstr
15
+
16
+ def _read(self, fp, fpname):
17
+ """Override _read to allow duplicate keys."""
18
+ cur_section = None
19
+ for lineno, line in enumerate(fp, start=1):
20
+ comment_start = line.find("#")
21
+ if comment_start != -1:
22
+ line = line[:comment_start]
23
+ line = line.strip()
24
+ if not line:
25
+ continue
26
+ if line.startswith("[") and line.endswith("]"):
27
+ cur_section = line[1:-1].strip()
28
+ if cur_section not in self._sections:
29
+ self._sections[cur_section] = defaultdict(list)
30
+ else:
31
+ if cur_section is None:
32
+ raise ValueError(f"Missing section header in {fpname} at line {lineno}")
33
+ key, _, value = line.partition("=")
34
+ key = key.strip()
35
+ value = value.strip()
36
+ self._sections[cur_section][key].append(value)
37
+
38
+ def get(self, section, option, *, raw=False, vars=None, fallback=None):
39
+ """Override `get` to handle list values."""
40
+ if section not in self._sections:
41
+ if fallback is not None:
42
+ return fallback
43
+ raise KeyError(f"Section '{section}' not found")
44
+ if option not in self._sections[section]:
45
+ if fallback is not None:
46
+ return fallback
47
+ raise KeyError(f"Option '{option}' not found in section '{section}'")
48
+
49
+ values = self._sections[section][option]
50
+ if isinstance(values, list):
51
+ # Join list values into a single string for compatibility
52
+ return "\n".join(values) if not raw else values
53
+ return values
54
+
55
+ def items(self, section=None, *, raw=False, vars=None):
56
+ """Override items to handle list values for both section-specific and global cases."""
57
+ if section is None:
58
+ # Global case: return all sections and their items
59
+ return [(s, dict(self.items(s))) for s in self._sections]
60
+ if section not in self._sections:
61
+ raise KeyError(f"Section '{section}' not found")
62
+ # Section-specific case: return all key-value pairs in the section
63
+ return ((key, "\n".join(values) if isinstance(values, list) else values)
64
+ for key, values in self._sections[section].items())
65
+
66
+
67
+ def write(self, fp):
68
+ """Override the write method to handle lists for duplicate keys."""
69
+ for section in self._sections:
70
+ fp.write(f"[{section}]\n")
71
+ for key, values in self._sections[section].items():
72
+ for value in values:
73
+ fp.write(f"{key} = {value}\n")
74
+ fp.write("\n")