blob: 84801bad19d5caee3f87585c35de984de37a56d9 [file] [log] [blame]
Philipp Le6203e732022-04-26 21:50:26 +02001# SPDX-License-Identifier: MPL-2.0
2# Copyright (c) 2022 Philipp Le <philipp@philipple.de>.
3# This Source Code Form is subject to the terms of the Mozilla Public
4# License, v. 2.0. If a copy of the MPL was not distributed with this
5# file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
7from __future__ import annotations
8
9import toml
10import json
11from typing import Dict, AnyStr, Callable, Type, Any, Optional
12from pydantic import BaseModel, PrivateAttr
13import appdirs
14import os
15import logging
16
17
18class ConfigObject(BaseModel):
19 _KEY: AnyStr = PrivateAttr()
20
21 @classmethod
22 def get_key(cls) -> AnyStr:
23 return cls._KEY
24
25
26class Store:
27 __instances: Dict[str, Store] = {}
28
29 def __init__(self, raw_objs: Dict[AnyStr, Any], save_cb: Optional[Callable[[Store], None]] = None):
30 self._raw_objs: Dict[AnyStr, Any] = raw_objs
31 self._cfg_objs: Dict[AnyStr, ConfigObject] = {}
32 self._save_cb: Optional[Callable[[Store], None]] = save_cb
33
34 @staticmethod
35 def get_instance(name: str = 'default') -> Store:
36 if name in Store.__instances:
37 return Store.__instances[name]
38 else:
39 raise KeyError(f'The Store instance {name} does not exist.')
40
41 @classmethod
42 def load_obj(cls, obj: Dict[AnyStr, Any], name: str = 'default', *args, **kwargs) -> Store:
43 if name not in Store.__instances:
44 Store.__instances[name] = cls(obj, *args, **kwargs)
45 return Store.__instances[name]
46 else:
47 raise KeyError(f'The Store instance {name} already exists.')
48
49 @classmethod
50 def new(cls, name: str = 'default', *args, **kwargs) -> Store:
51 if name not in Store.__instances:
52 Store.__instances[name] = cls({}, *args, **kwargs)
53 return Store.__instances[name]
54 else:
55 raise KeyError(f'The Store instance {name} already exists.')
56
57 def dump_obj(self) -> Dict[AnyStr, Any]:
58 obj: Dict[AnyStr, Any] = self._raw_objs
59 for key, item in self._cfg_objs.items():
60 obj[key] = json.loads(item.json())
61 return obj
62
63 def get_config(self, tp: Type[ConfigObject]):
64 key = tp.get_key()
65 if key not in self._cfg_objs:
66 if key in self._raw_objs:
67 self._cfg_objs[key] = tp.parse_obj(self._raw_objs[key])
68 else:
69 self._cfg_objs[key] = tp()
70 return self._cfg_objs[key]
71
72 def save(self) -> None:
73 if self._save_cb is not None:
74 self._save_cb(self)
75
76
77class TomlStore(Store):
78 def __init__(self, raw_objs: Dict[AnyStr, Any], toml_file: str):
79 super().__init__(raw_objs, save_cb=self._do_save)
80 self.toml_file: str = toml_file
81
82 @classmethod
83 def load_or_create_toml(cls, toml_file: str, name: str = 'default') -> TomlStore:
84 try:
85 s = Store.get_instance(name)
86 except KeyError:
87 try:
88 s = TomlStore.load_toml(toml_file, name)
89 logging.info(f'Loaded config from {toml_file}')
90 except FileNotFoundError:
91 s = TomlStore.new(name, toml_file=toml_file)
92 logging.info(f'New config at {toml_file}')
93 return s
94
95 @classmethod
96 def load_toml(cls, toml_file: str, name: str = 'default') -> TomlStore:
97 obj = toml.load(toml_file)
98 return TomlStore.load_obj(obj, name, toml_file=toml_file)
99
100 def dump_toml(self) -> None:
101 base_dir = os.path.dirname(self.toml_file)
102 if not os.path.exists(base_dir):
103 logging.info(f'Creating directory {base_dir}')
104 os.mkdir(base_dir)
105 with open(self.toml_file, 'w', encoding='utf-8') as f:
106 toml.dump(self.dump_obj(), f)
107
108 def _do_save(self, s: Store) -> None:
109 assert id(self) == id(s)
110 self.dump_toml()
111
112
113DEFAULT_PATH = os.path.join(
114 appdirs.user_config_dir('dcs_interactive', 'Philipp Le'),
115 'config.toml'
116)
117
118
119def default_store() -> TomlStore:
120 return TomlStore.load_or_create_toml(DEFAULT_PATH, 'default')
121