snapshot of https://github.com/ActivityWatch/aw-core.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Build / Test on ubuntu-latest, py-3.8 (push) Successful in 4m21s
Lint / lint (push) Successful in 25s
Build / Test on macOS-latest, py-3.8 (push) Has been cancelled
Build / Test on windows-latest, py-3.8 (push) Has been cancelled
CodeQL / Analyze (python) (push) Failing after 1s
Some checks failed
Build / Test on ubuntu-latest, py-3.8 (push) Successful in 4m21s
Lint / lint (push) Successful in 25s
Build / Test on macOS-latest, py-3.8 (push) Has been cancelled
Build / Test on windows-latest, py-3.8 (push) Has been cancelled
CodeQL / Analyze (python) (push) Failing after 1s
This commit is contained in:
116
aw_core/config.py
Normal file
116
aw_core/config.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import logging
|
||||
import os
|
||||
from typing import Union
|
||||
|
||||
import tomlkit
|
||||
from deprecation import deprecated
|
||||
|
||||
from aw_core import dirs
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _merge(a: dict, b: dict, path=None):
|
||||
"""
|
||||
Recursively merges b into a, with b taking precedence.
|
||||
|
||||
From: https://stackoverflow.com/a/7205107/965332
|
||||
"""
|
||||
if path is None:
|
||||
path = []
|
||||
for key in b:
|
||||
if key in a:
|
||||
if isinstance(a[key], dict) and isinstance(b[key], dict):
|
||||
_merge(a[key], b[key], path + [str(key)])
|
||||
elif a[key] == b[key]:
|
||||
pass # same leaf value
|
||||
else:
|
||||
a[key] = b[key]
|
||||
else:
|
||||
a[key] = b[key]
|
||||
return a
|
||||
|
||||
|
||||
def _comment_out_toml(s: str):
|
||||
# Only comment out keys, not headers or empty lines
|
||||
return "\n".join(
|
||||
[
|
||||
"#" + line if line.strip() and not line.strip().startswith("[") else line
|
||||
for line in s.split("\n")
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def load_config_toml(
|
||||
appname: str, default_config: str
|
||||
) -> Union[dict, tomlkit.container.Container]:
|
||||
config_dir = dirs.get_config_dir(appname)
|
||||
config_file_path = os.path.join(config_dir, f"{appname}.toml")
|
||||
|
||||
# Run early to ensure input is valid toml before writing
|
||||
default_config_toml = tomlkit.parse(default_config)
|
||||
|
||||
# Override defaults from existing config file
|
||||
if os.path.isfile(config_file_path):
|
||||
with open(config_file_path) as f:
|
||||
config = f.read()
|
||||
config_toml = tomlkit.parse(config)
|
||||
else:
|
||||
# If file doesn't exist, write with commented-out default config
|
||||
with open(config_file_path, "w") as f:
|
||||
f.write(_comment_out_toml(default_config))
|
||||
config_toml = dict()
|
||||
|
||||
config = _merge(default_config_toml, config_toml)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def save_config_toml(appname: str, config: str) -> None:
|
||||
# Check that passed config string is valid toml
|
||||
assert tomlkit.parse(config)
|
||||
|
||||
config_dir = dirs.get_config_dir(appname)
|
||||
config_file_path = os.path.join(config_dir, f"{appname}.toml")
|
||||
|
||||
with open(config_file_path, "w") as f:
|
||||
f.write(config)
|
||||
|
||||
|
||||
@deprecated(
|
||||
details="Use the load_config_toml function instead",
|
||||
deprecated_in="0.5.3",
|
||||
)
|
||||
def load_config(appname, default_config):
|
||||
"""
|
||||
Take the defaults, and if a config file exists, use the settings specified
|
||||
there as overrides for their respective defaults.
|
||||
"""
|
||||
config = default_config
|
||||
|
||||
config_dir = dirs.get_config_dir(appname)
|
||||
config_file_path = os.path.join(config_dir, f"{appname}.toml")
|
||||
|
||||
# Override defaults from existing config file
|
||||
if os.path.isfile(config_file_path):
|
||||
with open(config_file_path) as f:
|
||||
config.read_file(f)
|
||||
|
||||
# Overwrite current config file (necessary in case new default would be added)
|
||||
save_config(appname, config)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@deprecated(
|
||||
details="Use the save_config_toml function instead",
|
||||
deprecated_in="0.5.3",
|
||||
)
|
||||
def save_config(appname, config):
|
||||
config_dir = dirs.get_config_dir(appname)
|
||||
config_file_path = os.path.join(config_dir, f"{appname}.ini")
|
||||
with open(config_file_path, "w") as f:
|
||||
config.write(f)
|
||||
# Flush and fsync to lower risk of corrupted files
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
Reference in New Issue
Block a user