Coverage for src/twofas/cli_settings.py: 100%

55 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-01-29 11:15 +0100

1""" 

2This file deals with managing settings for 2fas. 

3""" 

4 

5import typing 

6from pathlib import Path 

7from typing import Any 

8 

9import tomli_w 

10from configuraptor import TypedConfig, asdict, beautify, singleton 

11from configuraptor.core import convert_key 

12 

13config = Path("~/.config").expanduser() 

14config.mkdir(exist_ok=True) 

15DEFAULT_SETTINGS = config / "2fas.toml" 

16DEFAULT_SETTINGS.touch(exist_ok=True) 

17 

18CONFIG_KEY = "tool.2fas" 

19 

20 

21def expand_path(file: str | Path) -> str: 

22 """ 

23 Expand ~/... into /home/<user>/... 

24 """ 

25 return str(Path(file).expanduser()) 

26 

27 

28def expand_paths(paths: typing.Iterable[str]) -> list[str]: 

29 """ 

30 Expand multiple paths. 

31 """ 

32 return [expand_path(f) for f in paths] 

33 

34 

35@beautify 

36class CliSettings(TypedConfig, singleton.Singleton): 

37 """ 

38 Class for the ~/.config/2fas.toml settings file. 

39 """ 

40 

41 files: list[str] | None 

42 default_file: str | None 

43 auto_verbose: bool = False 

44 

45 def add_file(self, filename: str | None, _config_file: str | Path = DEFAULT_SETTINGS) -> None: 

46 """ 

47 Add a new 2fas file to the configs history list. 

48 """ 

49 if not filename: 

50 return 

51 

52 filename = expand_path(filename) 

53 

54 files = self.files or [] 

55 if filename not in files: 

56 files.append(filename) 

57 

58 set_cli_setting("files", expand_paths(files), _config_file) 

59 

60 self.files = expand_paths(files) 

61 

62 def remove_file(self, filenames: str | typing.Iterable[str], _config_file: str | Path = DEFAULT_SETTINGS) -> None: 

63 """ 

64 Remove a known 2fas file from the config's history list. 

65 """ 

66 if isinstance(filenames, str | Path): 

67 filenames = [filenames] 

68 

69 filenames = set(expand_paths(filenames)) 

70 

71 files = [expand_path(_) for _ in (self.files or []) if _ not in filenames] 

72 

73 if self.default_file in filenames: 

74 new_default = files[0] if files else None 

75 set_cli_setting("default-file", new_default, _config_file) 

76 self.default_file = new_default 

77 

78 set_cli_setting("files", files, _config_file) 

79 self.files = files 

80 

81 

82def load_cli_settings(input_file: str | Path = DEFAULT_SETTINGS, **overwrite: Any) -> CliSettings: 

83 """ 

84 Load the config file into a CliSettings instance. 

85 """ 

86 return CliSettings.load([input_file, overwrite], key=CONFIG_KEY) 

87 

88 

89def get_cli_setting(key: str, filename: str | Path = DEFAULT_SETTINGS) -> typing.Any: 

90 """ 

91 Get a setting from the config file. 

92 """ 

93 key = convert_key(key) 

94 settings = load_cli_settings(filename) 

95 return getattr(settings, key) 

96 

97 

98def set_cli_setting(key: str, value: typing.Any, filename: str | Path = DEFAULT_SETTINGS) -> None: 

99 """ 

100 Update a setting in the config file. 

101 """ 

102 filepath = Path(filename) 

103 key = convert_key(key) 

104 

105 settings = load_cli_settings(filepath) 

106 settings.update(**{key: value}, _convert_types=True) 

107 

108 inner_data = asdict( 

109 settings, 

110 with_top_level_key=False, 

111 ) 

112 

113 # toml can't deal with None, so skip those: 

114 inner_data = {k: v for k, v in inner_data.items() if v is not None} 

115 outer_data = {"tool": {"2fas": inner_data}} 

116 

117 filepath.write_text(tomli_w.dumps(outer_data))