Coverage for src/twofas/cli_settings.py: 100%
59 statements
« prev ^ index » next coverage.py v7.4.1, created at 2025-01-12 18:05 +0100
« prev ^ index » next coverage.py v7.4.1, created at 2025-01-12 18:05 +0100
1"""
2This file deals with managing settings for 2fas.
3"""
5import typing
6from pathlib import Path
7from typing import Any
9import tomli_w
10from configuraptor import TypedConfig, asdict, beautify, singleton
11from configuraptor.core import convert_key
13config = Path("~/.config").expanduser()
14config.mkdir(exist_ok=True)
15DEFAULT_SETTINGS = config / "2fas.toml"
16DEFAULT_SETTINGS.touch(exist_ok=True)
18CONFIG_KEY = "tool.2fas"
21def expand_path(file: str | Path | None) -> str:
22 """
23 Expand ~/... into /home/<user>/...
24 """
25 if not file:
26 return ""
28 return str(Path(file).expanduser().absolute())
31def expand_paths(paths: typing.Iterable[str]) -> list[str]:
32 """
33 Expand multiple paths.
34 """
35 return [expand_path(f) for f in paths]
38@beautify
39class CliSettings(TypedConfig, singleton.Singleton):
40 """
41 Class for the ~/.config/2fas.toml settings file.
42 """
44 files: list[str] | None
45 default_file: str | None
46 auto_verbose: bool = False
48 def add_file(self, filename: str | None, _config_file: str | Path = DEFAULT_SETTINGS) -> str | None:
49 """
50 Add a new 2fas file to the configs history list.
51 """
52 if not filename:
53 return None
55 filename = expand_path(filename)
57 files = self.files or []
58 if filename not in files:
59 files.append(filename)
61 set_cli_setting("files", expand_paths(files), _config_file)
63 self.files = expand_paths(files)
64 return expand_path(filename)
66 def remove_file(self, filenames: str | typing.Iterable[str], _config_file: str | Path = DEFAULT_SETTINGS) -> None:
67 """
68 Remove a known 2fas file from the config's history list.
69 """
70 if isinstance(filenames, str | Path):
71 filenames = [filenames]
73 filenames_to_remove = set(expand_paths(filenames))
74 current_files = expand_paths(self.files or [])
75 files = [_ for _ in current_files if _ not in filenames_to_remove]
77 if expand_path(self.default_file) in filenames_to_remove:
78 new_default = files[0] if files else None
79 set_cli_setting("default-file", new_default, _config_file)
80 self.default_file = new_default
82 set_cli_setting("files", files, _config_file)
83 self.files = files
86def load_cli_settings(input_file: str | Path = DEFAULT_SETTINGS, **overwrite: Any) -> CliSettings:
87 """
88 Load the config file into a CliSettings instance.
89 """
90 return CliSettings.load([input_file, overwrite], key=CONFIG_KEY)
93def get_cli_setting(key: str, filename: str | Path = DEFAULT_SETTINGS) -> typing.Any:
94 """
95 Get a setting from the config file.
96 """
97 key = convert_key(key)
98 settings = load_cli_settings(filename)
99 return getattr(settings, key)
102def set_cli_setting(key: str, value: typing.Any, filename: str | Path = DEFAULT_SETTINGS) -> None:
103 """
104 Update a setting in the config file.
105 """
106 filepath = Path(filename)
107 key = convert_key(key)
109 settings = load_cli_settings(filepath)
110 settings.update(**{key: value}, _convert_types=True)
112 inner_data = asdict(
113 settings,
114 with_top_level_key=False,
115 )
117 # toml can't deal with None, so skip those:
118 inner_data = {k: v for k, v in inner_data.items() if v is not None}
119 outer_data = {"tool": {"2fas": inner_data}}
121 filepath.write_text(tomli_w.dumps(outer_data))