vibefpmgui.build_configs
Build configurations: named sets of Build options for a project, kept in a
JSON file beside its fpm.toml (FILE_NAME unless another is opened), so
they can be shared (e.g. committed with the project).
The file::
{
"vibefpm": 1,
"configurations": [
{"name": "release (ifx)", "profile": "release", "features": [],
"compiler": "ifx", "flags": "", "target": "", "extra_args": "",
"program_args": "", "parallel_tests": 4, "coverage": false,
"verbose": false}
]
}
A configuration has every Build option but the fpm executable (a path on this computer, kept in the app's settings). An option left out of a configuration has its default; unknown keys are kept as they are.
Beside the configurations, "new_configuration" is New configuration's Build
options (those not saved as one): a configuration without a name. Then
"options" (one set, for every configuration in it) are the GUI's for the
project: see vibefpmgui.project_options.
ConfigFile is a project's file as the app has it: read whole (a file that
can't be read whole isn't used, nor written over), and written a part at a
time, the rest kept as it is.
1"""Build configurations: named sets of Build options for a project, kept in a 2JSON file beside its fpm.toml (`FILE_NAME` unless another is opened), so 3they can be shared (e.g. committed with the project). 4 5The file:: 6```json 7 { 8 "vibefpm": 1, 9 "configurations": [ 10 {"name": "release (ifx)", "profile": "release", "features": [], 11 "compiler": "ifx", "flags": "", "target": "", "extra_args": "", 12 "program_args": "", "parallel_tests": 4, "coverage": false, 13 "verbose": false} 14 ] 15 } 16``` 17 18A configuration has every Build option but the fpm executable (a path on 19this computer, kept in the app's settings). An option left out of a 20configuration has its default; unknown keys are kept as they are. 21 22Beside the configurations, "new_configuration" is New configuration's Build 23options (those not saved as one): a configuration without a name. Then 24"options" (one set, for every configuration in it) are the GUI's for the 25project: see `vibefpmgui.project_options`. 26 27`ConfigFile` is a project's file as the app has it: read whole (a file that 28can't be read whole isn't used, nor written over), and written a part at a 29time, the rest kept as it is. 30""" 31 32from __future__ import annotations 33 34import json 35import os 36import tempfile 37from dataclasses import asdict, dataclass, field, fields 38from pathlib import Path 39 40FILE_NAME = "vibefpm.json" 41FORMAT = 1 # the file's "vibefpm" version 42DEFAULT_PROFILE = "(default)" # (as the Profile list shows it; "" in the file) 43 44 45class ConfigError(Exception): 46 """A configuration file that can't be read (the message says why).""" 47 48 49@dataclass 50class Configuration: 51 name: str 52 profile: str = DEFAULT_PROFILE 53 features: list[str] = field(default_factory=list) 54 compiler: str = "" 55 flags: str = "" 56 target: str = "" 57 extra_args: str = "" 58 program_args: str = "" 59 parallel_tests: int = 1 60 coverage: bool = False 61 verbose: bool = False 62 63 def to_json(self, named: bool = True) -> dict: 64 """As the file has it (``named`` False: New configuration's, without a name).""" 65 data = asdict(self) 66 data["profile"] = "" if self.profile == DEFAULT_PROFILE else self.profile 67 if not named: 68 del data["name"] 69 return data 70 71 @classmethod 72 def from_json(cls, data: dict, name: str | None = None) -> Configuration: 73 """A configuration from the file (a value of the wrong type: its 74 default). ``name``: one without a name in the file (New configuration's).""" 75 if name is None: 76 name = data.get("name") 77 if not isinstance(name, str) or not name.strip(): 78 raise ConfigError("A configuration has no name.") 79 config = cls(name.strip()) 80 for f in fields(cls): 81 if f.name == "name" or f.name not in data: 82 continue 83 value, default = data[f.name], getattr(config, f.name) 84 if isinstance(default, bool): 85 ok = isinstance(value, bool) 86 elif isinstance(default, int): 87 ok = isinstance(value, int) and not isinstance(value, bool) and value >= 1 88 elif isinstance(default, list): 89 ok = isinstance(value, list) and all(isinstance(v, str) for v in value) 90 else: 91 ok = isinstance(value, str) 92 if ok: 93 setattr(config, f.name, list(value) if isinstance(value, list) else value) 94 if not config.profile: 95 config.profile = DEFAULT_PROFILE 96 return config 97 98 99def default_path(manifest: Path) -> Path: 100 """Where a project's configurations go unless another file was opened.""" 101 return Path(manifest).parent / FILE_NAME 102 103 104def manifest_for(path: Path) -> Path | None: 105 """The fpm.toml a configuration file belongs to (beside it), if there is one.""" 106 manifest = Path(path).parent / "fpm.toml" 107 return manifest if manifest.is_file() else None 108 109 110def _read(path: Path) -> dict: 111 try: 112 data = json.loads(Path(path).read_text(encoding="utf-8")) 113 except OSError as exc: 114 raise ConfigError(f"Can't read {path}: {exc.strerror or exc}") from exc 115 except (ValueError, UnicodeDecodeError) as exc: 116 raise ConfigError(f"{Path(path).name} isn't valid JSON: {exc}") from exc 117 if not isinstance(data, dict) or not isinstance(data.get("configurations", []), list): 118 raise ConfigError(f"{Path(path).name} isn't a VibeFPM configuration file " 119 "(no \"configurations\" list).") 120 return data 121 122 123def load(path: Path) -> list[Configuration]: 124 """The configurations in a file, in its order (none if it doesn't exist).""" 125 if not Path(path).exists(): 126 return [] 127 configs, seen = [], set() 128 for item in _read(path).get("configurations", []): 129 if not isinstance(item, dict): 130 raise ConfigError(f"{Path(path).name}: a configuration isn't an object.") 131 config = Configuration.from_json(item) 132 if config.name in seen: 133 raise ConfigError(f"{Path(path).name}: two configurations are named “{config.name}”.") 134 seen.add(config.name) 135 configs.append(config) 136 return configs 137 138 139def _write(path: Path, data: dict): 140 """Write the file whole or not at all (a temporary file, then renamed).""" 141 path = Path(path) 142 text = json.dumps(data, indent=2, ensure_ascii=False) + "\n" 143 handle, temporary = tempfile.mkstemp(prefix=".vibefpm-", suffix=".json", dir=path.parent) 144 try: 145 with os.fdopen(handle, "w", encoding="utf-8", newline="\n") as f: 146 f.write(text) 147 os.replace(temporary, path) 148 except BaseException: 149 Path(temporary).unlink(missing_ok=True) 150 raise 151 152 153def _with_configurations(path: Path, change) -> list[Configuration]: 154 """Read the file (or start one), let ``change`` edit its list of 155 configurations (as JSON objects), and write it back; the result. Other 156 keys, and keys of a configuration this app doesn't know, are kept; a 157 file that can't be read whole isn't written over (ConfigError).""" 158 load(path) # (every configuration checked first) 159 data = _read(path) if Path(path).exists() else {} 160 items = [item for item in data.get("configurations", []) if isinstance(item, dict)] 161 change(items) 162 data = {"vibefpm": data.get("vibefpm", FORMAT), **{k: v for k, v in data.items() if k != "vibefpm"}, 163 "configurations": items} 164 _write(path, data) 165 return load(path) 166 167 168def save(path: Path, config: Configuration) -> list[Configuration]: 169 """Add a configuration to the file, or replace the one with its name.""" 170 def change(items: list[dict]): 171 new = config.to_json() 172 for i, item in enumerate(items): 173 if item.get("name") == config.name: 174 items[i] = {**item, **new} 175 return 176 items.append(new) 177 return _with_configurations(path, change) 178 179 180def remove(path: Path, name: str) -> list[Configuration]: 181 """Take the configuration named ``name`` out of the file.""" 182 return _with_configurations(path, lambda items: items.__setitem__( 183 slice(None), [item for item in items if item.get("name") != name])) 184 185 186# Build options a file's "options" had before they went to "new_configuration" 187# (the app's settings keys for them): moved there when it's read. 188_OLD_OPTION_FIELDS = {"profile": "profile", "compiler": "compiler", "flags": "flags", "extra": "extra_args", 189 "target": "target", "program_args": "program_args", "verbose": "verbose", 190 "coverage": "coverage", "test_jobs": "parallel_tests", "features": "features"} 191 192 193def _object(data: dict, key: str, path: Path) -> dict | None: 194 value = data.get(key) 195 if value is not None and not isinstance(value, dict): 196 raise ConfigError(f"{Path(path).name}: \"{key}\" isn't an object.") 197 return value 198 199 200_NONE = object() # (an option not in the file) 201 202 203class ConfigFile: 204 """A project's configuration file, as the app has it: ``configurations``, 205 New configuration's Build options (``new_configuration``: None if the 206 file has none) and the GUI's ``options`` (None: there's no file, or it 207 can't be read, ``error`` saying why; then none of it is used, and it 208 isn't written over).""" 209 210 def __init__(self, path: Path): 211 self.path = Path(path) 212 self.configurations: list[Configuration] = [] 213 self.new_configuration: Configuration | None = None 214 self.options: dict | None = None 215 self.error = "" 216 self._written: tuple | None = None # (options, new configuration) as the file has them 217 218 def exists(self) -> bool: 219 return self.path.is_file() 220 221 def _load(self) -> tuple[list[Configuration], dict | None, dict | None]: 222 """Everything in the file, checked (ConfigError if any of it can't be read).""" 223 if not self.exists(): 224 return [], None, None 225 configurations = load(self.path) 226 data = _read(self.path) 227 return configurations, _object(data, "options", self.path), _object(data, "new_configuration", self.path) 228 229 def read(self) -> ConfigFile: 230 """Read it all (the file's options and New configuration replace those had).""" 231 try: 232 configurations, options, new = self._load() 233 except ConfigError as exc: 234 self.configurations, self.options, self.new_configuration = [], None, None 235 self.error, self._written = str(exc), None 236 return self 237 self.configurations, self.error = configurations, "" 238 self._take_gui(options, new) 239 return self 240 241 def _take_gui(self, options: dict | None, new: dict | None): 242 self._written = (options, new) if self.exists() else None 243 self.options = dict(options or {}) if self.exists() else None 244 new = dict(new or {}) 245 for key, name in _OLD_OPTION_FIELDS.items(): # (from a file written before: moved) 246 if self.options is not None and key in self.options: 247 new.setdefault(name, self.options.pop(key)) 248 self.new_configuration = Configuration.from_json(new, name="") if new else None 249 250 def reread_configurations(self): 251 """Its configurations read again (the project reloaded: the file may 252 have changed; its options and New configuration are the window's 253 until they're written, or it's saved in the editor: see reread).""" 254 try: 255 self.configurations, self.error = self._load()[0], "" 256 except ConfigError as exc: 257 self.configurations, self.error = [], str(exc) 258 259 def reread(self) -> tuple[set[str], bool]: 260 """Read it all again (it was saved in the editor): (the options 261 changed in it since it was last read or written, whether New 262 configuration was). Those changed are taken (``options`` and 263 ``new_configuration``: the file's).""" 264 try: 265 configurations, options, new = self._load() 266 except ConfigError as exc: 267 self.configurations, self.error = [], str(exc) 268 return set(), False 269 self.configurations, self.error = configurations, "" 270 if not self.exists(): 271 return set(), False 272 before, before_new = self._written or (None, None) 273 before, options_now = before or {}, options or {} 274 changed = {key for key in {*before, *options_now} if before.get(key, _NONE) != options_now.get(key, _NONE)} 275 new_changed = (new or {}) != (before_new or {}) 276 if changed or new_changed: 277 self._take_gui(options, new) 278 return changed, new_changed 279 280 def start_options(self): 281 """It was just made (the first configuration saved): it holds the 282 project's options from now on (none yet).""" 283 if self.options is None and self.exists() and not self.error: 284 self.options, self._written = {}, ({}, None) 285 286 def _check_writable(self): 287 if self.error and self.exists(): # (not written over: fix it, or move it away) 288 raise ConfigError(self.error) 289 290 def save(self, config: Configuration): 291 """Add a configuration, or replace the one with its name (OSError, ConfigError).""" 292 self._check_writable() 293 self.configurations = save(self.path, config) 294 295 def remove(self, name: str): 296 """Take a configuration out (OSError, ConfigError).""" 297 self._check_writable() 298 self.configurations = remove(self.path, name) 299 300 def write_options(self, options: dict, new: Configuration) -> bool: 301 """Write the options and New configuration, if they changed (True: 302 written; OSError, ConfigError).""" 303 if self.options is None or self.error: 304 return False 305 written = (options, new.to_json(named=False)) 306 if written == self._written: 307 return False 308 save_options(self.path, *written) 309 self.options, self.new_configuration, self._written = dict(options), new, written 310 return True 311 312 313def save_options(path: Path, options: dict, new_configuration: dict): 314 """Put ``options`` and New configuration's Build options in the file 315 (which must exist and be readable), its configurations and anything else 316 in it kept, in the order it's read: the configurations, New 317 configuration, the options.""" 318 load(path) # (every configuration checked first: a file that can't be read isn't written over) 319 data = {k: v for k, v in _read(path).items() if k not in ("new_configuration", "options")} 320 data["new_configuration"] = new_configuration 321 data["options"] = options 322 _write(path, data)
46class ConfigError(Exception): 47 """A configuration file that can't be read (the message says why)."""
A configuration file that can't be read (the message says why).
50@dataclass 51class Configuration: 52 name: str 53 profile: str = DEFAULT_PROFILE 54 features: list[str] = field(default_factory=list) 55 compiler: str = "" 56 flags: str = "" 57 target: str = "" 58 extra_args: str = "" 59 program_args: str = "" 60 parallel_tests: int = 1 61 coverage: bool = False 62 verbose: bool = False 63 64 def to_json(self, named: bool = True) -> dict: 65 """As the file has it (``named`` False: New configuration's, without a name).""" 66 data = asdict(self) 67 data["profile"] = "" if self.profile == DEFAULT_PROFILE else self.profile 68 if not named: 69 del data["name"] 70 return data 71 72 @classmethod 73 def from_json(cls, data: dict, name: str | None = None) -> Configuration: 74 """A configuration from the file (a value of the wrong type: its 75 default). ``name``: one without a name in the file (New configuration's).""" 76 if name is None: 77 name = data.get("name") 78 if not isinstance(name, str) or not name.strip(): 79 raise ConfigError("A configuration has no name.") 80 config = cls(name.strip()) 81 for f in fields(cls): 82 if f.name == "name" or f.name not in data: 83 continue 84 value, default = data[f.name], getattr(config, f.name) 85 if isinstance(default, bool): 86 ok = isinstance(value, bool) 87 elif isinstance(default, int): 88 ok = isinstance(value, int) and not isinstance(value, bool) and value >= 1 89 elif isinstance(default, list): 90 ok = isinstance(value, list) and all(isinstance(v, str) for v in value) 91 else: 92 ok = isinstance(value, str) 93 if ok: 94 setattr(config, f.name, list(value) if isinstance(value, list) else value) 95 if not config.profile: 96 config.profile = DEFAULT_PROFILE 97 return config
64 def to_json(self, named: bool = True) -> dict: 65 """As the file has it (``named`` False: New configuration's, without a name).""" 66 data = asdict(self) 67 data["profile"] = "" if self.profile == DEFAULT_PROFILE else self.profile 68 if not named: 69 del data["name"] 70 return data
As the file has it (named False: New configuration's, without a name).
72 @classmethod 73 def from_json(cls, data: dict, name: str | None = None) -> Configuration: 74 """A configuration from the file (a value of the wrong type: its 75 default). ``name``: one without a name in the file (New configuration's).""" 76 if name is None: 77 name = data.get("name") 78 if not isinstance(name, str) or not name.strip(): 79 raise ConfigError("A configuration has no name.") 80 config = cls(name.strip()) 81 for f in fields(cls): 82 if f.name == "name" or f.name not in data: 83 continue 84 value, default = data[f.name], getattr(config, f.name) 85 if isinstance(default, bool): 86 ok = isinstance(value, bool) 87 elif isinstance(default, int): 88 ok = isinstance(value, int) and not isinstance(value, bool) and value >= 1 89 elif isinstance(default, list): 90 ok = isinstance(value, list) and all(isinstance(v, str) for v in value) 91 else: 92 ok = isinstance(value, str) 93 if ok: 94 setattr(config, f.name, list(value) if isinstance(value, list) else value) 95 if not config.profile: 96 config.profile = DEFAULT_PROFILE 97 return config
A configuration from the file (a value of the wrong type: its
default). name: one without a name in the file (New configuration's).
100def default_path(manifest: Path) -> Path: 101 """Where a project's configurations go unless another file was opened.""" 102 return Path(manifest).parent / FILE_NAME
Where a project's configurations go unless another file was opened.
105def manifest_for(path: Path) -> Path | None: 106 """The fpm.toml a configuration file belongs to (beside it), if there is one.""" 107 manifest = Path(path).parent / "fpm.toml" 108 return manifest if manifest.is_file() else None
The fpm.toml a configuration file belongs to (beside it), if there is one.
124def load(path: Path) -> list[Configuration]: 125 """The configurations in a file, in its order (none if it doesn't exist).""" 126 if not Path(path).exists(): 127 return [] 128 configs, seen = [], set() 129 for item in _read(path).get("configurations", []): 130 if not isinstance(item, dict): 131 raise ConfigError(f"{Path(path).name}: a configuration isn't an object.") 132 config = Configuration.from_json(item) 133 if config.name in seen: 134 raise ConfigError(f"{Path(path).name}: two configurations are named “{config.name}”.") 135 seen.add(config.name) 136 configs.append(config) 137 return configs
The configurations in a file, in its order (none if it doesn't exist).
169def save(path: Path, config: Configuration) -> list[Configuration]: 170 """Add a configuration to the file, or replace the one with its name.""" 171 def change(items: list[dict]): 172 new = config.to_json() 173 for i, item in enumerate(items): 174 if item.get("name") == config.name: 175 items[i] = {**item, **new} 176 return 177 items.append(new) 178 return _with_configurations(path, change)
Add a configuration to the file, or replace the one with its name.
181def remove(path: Path, name: str) -> list[Configuration]: 182 """Take the configuration named ``name`` out of the file.""" 183 return _with_configurations(path, lambda items: items.__setitem__( 184 slice(None), [item for item in items if item.get("name") != name]))
Take the configuration named name out of the file.
204class ConfigFile: 205 """A project's configuration file, as the app has it: ``configurations``, 206 New configuration's Build options (``new_configuration``: None if the 207 file has none) and the GUI's ``options`` (None: there's no file, or it 208 can't be read, ``error`` saying why; then none of it is used, and it 209 isn't written over).""" 210 211 def __init__(self, path: Path): 212 self.path = Path(path) 213 self.configurations: list[Configuration] = [] 214 self.new_configuration: Configuration | None = None 215 self.options: dict | None = None 216 self.error = "" 217 self._written: tuple | None = None # (options, new configuration) as the file has them 218 219 def exists(self) -> bool: 220 return self.path.is_file() 221 222 def _load(self) -> tuple[list[Configuration], dict | None, dict | None]: 223 """Everything in the file, checked (ConfigError if any of it can't be read).""" 224 if not self.exists(): 225 return [], None, None 226 configurations = load(self.path) 227 data = _read(self.path) 228 return configurations, _object(data, "options", self.path), _object(data, "new_configuration", self.path) 229 230 def read(self) -> ConfigFile: 231 """Read it all (the file's options and New configuration replace those had).""" 232 try: 233 configurations, options, new = self._load() 234 except ConfigError as exc: 235 self.configurations, self.options, self.new_configuration = [], None, None 236 self.error, self._written = str(exc), None 237 return self 238 self.configurations, self.error = configurations, "" 239 self._take_gui(options, new) 240 return self 241 242 def _take_gui(self, options: dict | None, new: dict | None): 243 self._written = (options, new) if self.exists() else None 244 self.options = dict(options or {}) if self.exists() else None 245 new = dict(new or {}) 246 for key, name in _OLD_OPTION_FIELDS.items(): # (from a file written before: moved) 247 if self.options is not None and key in self.options: 248 new.setdefault(name, self.options.pop(key)) 249 self.new_configuration = Configuration.from_json(new, name="") if new else None 250 251 def reread_configurations(self): 252 """Its configurations read again (the project reloaded: the file may 253 have changed; its options and New configuration are the window's 254 until they're written, or it's saved in the editor: see reread).""" 255 try: 256 self.configurations, self.error = self._load()[0], "" 257 except ConfigError as exc: 258 self.configurations, self.error = [], str(exc) 259 260 def reread(self) -> tuple[set[str], bool]: 261 """Read it all again (it was saved in the editor): (the options 262 changed in it since it was last read or written, whether New 263 configuration was). Those changed are taken (``options`` and 264 ``new_configuration``: the file's).""" 265 try: 266 configurations, options, new = self._load() 267 except ConfigError as exc: 268 self.configurations, self.error = [], str(exc) 269 return set(), False 270 self.configurations, self.error = configurations, "" 271 if not self.exists(): 272 return set(), False 273 before, before_new = self._written or (None, None) 274 before, options_now = before or {}, options or {} 275 changed = {key for key in {*before, *options_now} if before.get(key, _NONE) != options_now.get(key, _NONE)} 276 new_changed = (new or {}) != (before_new or {}) 277 if changed or new_changed: 278 self._take_gui(options, new) 279 return changed, new_changed 280 281 def start_options(self): 282 """It was just made (the first configuration saved): it holds the 283 project's options from now on (none yet).""" 284 if self.options is None and self.exists() and not self.error: 285 self.options, self._written = {}, ({}, None) 286 287 def _check_writable(self): 288 if self.error and self.exists(): # (not written over: fix it, or move it away) 289 raise ConfigError(self.error) 290 291 def save(self, config: Configuration): 292 """Add a configuration, or replace the one with its name (OSError, ConfigError).""" 293 self._check_writable() 294 self.configurations = save(self.path, config) 295 296 def remove(self, name: str): 297 """Take a configuration out (OSError, ConfigError).""" 298 self._check_writable() 299 self.configurations = remove(self.path, name) 300 301 def write_options(self, options: dict, new: Configuration) -> bool: 302 """Write the options and New configuration, if they changed (True: 303 written; OSError, ConfigError).""" 304 if self.options is None or self.error: 305 return False 306 written = (options, new.to_json(named=False)) 307 if written == self._written: 308 return False 309 save_options(self.path, *written) 310 self.options, self.new_configuration, self._written = dict(options), new, written 311 return True
A project's configuration file, as the app has it: configurations,
New configuration's Build options (new_configuration: None if the
file has none) and the GUI's options (None: there's no file, or it
can't be read, error saying why; then none of it is used, and it
isn't written over).
211 def __init__(self, path: Path): 212 self.path = Path(path) 213 self.configurations: list[Configuration] = [] 214 self.new_configuration: Configuration | None = None 215 self.options: dict | None = None 216 self.error = "" 217 self._written: tuple | None = None # (options, new configuration) as the file has them
230 def read(self) -> ConfigFile: 231 """Read it all (the file's options and New configuration replace those had).""" 232 try: 233 configurations, options, new = self._load() 234 except ConfigError as exc: 235 self.configurations, self.options, self.new_configuration = [], None, None 236 self.error, self._written = str(exc), None 237 return self 238 self.configurations, self.error = configurations, "" 239 self._take_gui(options, new) 240 return self
Read it all (the file's options and New configuration replace those had).
251 def reread_configurations(self): 252 """Its configurations read again (the project reloaded: the file may 253 have changed; its options and New configuration are the window's 254 until they're written, or it's saved in the editor: see reread).""" 255 try: 256 self.configurations, self.error = self._load()[0], "" 257 except ConfigError as exc: 258 self.configurations, self.error = [], str(exc)
Its configurations read again (the project reloaded: the file may have changed; its options and New configuration are the window's until they're written, or it's saved in the editor: see reread).
260 def reread(self) -> tuple[set[str], bool]: 261 """Read it all again (it was saved in the editor): (the options 262 changed in it since it was last read or written, whether New 263 configuration was). Those changed are taken (``options`` and 264 ``new_configuration``: the file's).""" 265 try: 266 configurations, options, new = self._load() 267 except ConfigError as exc: 268 self.configurations, self.error = [], str(exc) 269 return set(), False 270 self.configurations, self.error = configurations, "" 271 if not self.exists(): 272 return set(), False 273 before, before_new = self._written or (None, None) 274 before, options_now = before or {}, options or {} 275 changed = {key for key in {*before, *options_now} if before.get(key, _NONE) != options_now.get(key, _NONE)} 276 new_changed = (new or {}) != (before_new or {}) 277 if changed or new_changed: 278 self._take_gui(options, new) 279 return changed, new_changed
Read it all again (it was saved in the editor): (the options
changed in it since it was last read or written, whether New
configuration was). Those changed are taken (options and
new_configuration: the file's).
281 def start_options(self): 282 """It was just made (the first configuration saved): it holds the 283 project's options from now on (none yet).""" 284 if self.options is None and self.exists() and not self.error: 285 self.options, self._written = {}, ({}, None)
It was just made (the first configuration saved): it holds the project's options from now on (none yet).
291 def save(self, config: Configuration): 292 """Add a configuration, or replace the one with its name (OSError, ConfigError).""" 293 self._check_writable() 294 self.configurations = save(self.path, config)
Add a configuration, or replace the one with its name (OSError, ConfigError).
296 def remove(self, name: str): 297 """Take a configuration out (OSError, ConfigError).""" 298 self._check_writable() 299 self.configurations = remove(self.path, name)
Take a configuration out (OSError, ConfigError).
301 def write_options(self, options: dict, new: Configuration) -> bool: 302 """Write the options and New configuration, if they changed (True: 303 written; OSError, ConfigError).""" 304 if self.options is None or self.error: 305 return False 306 written = (options, new.to_json(named=False)) 307 if written == self._written: 308 return False 309 save_options(self.path, *written) 310 self.options, self.new_configuration, self._written = dict(options), new, written 311 return True
Write the options and New configuration, if they changed (True: written; OSError, ConfigError).
314def save_options(path: Path, options: dict, new_configuration: dict): 315 """Put ``options`` and New configuration's Build options in the file 316 (which must exist and be readable), its configurations and anything else 317 in it kept, in the order it's read: the configurations, New 318 configuration, the options.""" 319 load(path) # (every configuration checked first: a file that can't be read isn't written over) 320 data = {k: v for k, v in _read(path).items() if k not in ("new_configuration", "options")} 321 data["new_configuration"] = new_configuration 322 data["options"] = options 323 _write(path, data)
Put options and New configuration's Build options in the file
(which must exist and be readable), its configurations and anything else
in it kept, in the order it's read: the configurations, New
configuration, the options.