2 @package libtextworker.get_config
3 @brief Contains classes for generic INI files parsing
5 See the documentation in /usage/getconfig.
17 from .general
import WalkCreation, libTewException
18 from warnings
import warn
19 from watchdog.observers
import Observer
22 __all__ = [
"ConfigurationError",
"GetConfig"]
25 from commentedconfigparser
import CommentedConfigParser
as ConfigParser
27 from configparser
import ConfigParser
30 def __init__(this, path: str, msg: str, section: str,
31 option: str =
"\\not_specified\\"):
33 full =
"Configuration file {}, path [{}->{}]: {}"
34 full = full.format(path, section, option, msg)
35 libTewException.__init__(this, full)
37 class GetConfig(ConfigParser, FileSystemEventHandler):
39 yes_values: list = [
"yes",
"True",
True,
"1", 1,
"on"]
40 no_values: list = [
"no",
"False",
False,
"0", 0,
"off"]
45 detailedlogs: bool =
True
47 _observer = Observer()
49 for item
in yes_values:
52 for item
in no_values:
55 def __init__(this, config: dict[str] | str |
None, file: str, **kwds):
57 A customized INI file parser.
58 @param config (dict[str] or str) : Your stock settings, used to reset the file or do some comparisions
59 @param file : Configuration file
60 @param **kwds : To pass to configparser.ConfigParser (base class)
62 When initialized, GetConfig loads all default configs (from config parameter) and store it in
63 a dictionary for further actions (backup/restore file).
65 @since 0.1.3: Allow config parameter as a str object
66 @since 0.1.4: JSON support, allow config parameter to be None, file system watch
68 ConfigParser.__init__(this, **kwds)
70 if isinstance(config, str):
71 this.read_string(config)
72 elif isinstance(config, dict):
73 this.read_dict(config)
77 this.cfg[key] = this[key]
82 def readf(this, file: str, encoding: str |
None =
None):
84 Reads all settings from a file.
85 Mostly for application/GetConfig internal use.
87 WalkCreation(os.path.dirname(file))
88 if not os.path.exists(file):
89 this.write(open(file,
"w"))
92 this.read_dict(json.loads(open(file,
"r").read()))
94 this.read(file, encoding)
97 this._observer.schedule(this, file)
98 this._observer.start()
101 if this._observer.is_alive():
102 this._observer.stop()
103 this._observer.join()
105 def reset(this, restore: bool =
False):
107 Loads default settings to GetConfig and loaded file.
108 Also restore backups if restore is True and GetConfig.backups is not empty.
110 os.remove(this._file)
112 this[key] = this.cfg[key]
114 if restore
and this.backups:
115 for key
in this.backups:
116 this[key] = this.backups[key]
122 Writes current settings to loaded file.
124 with open(this._file,
"w")
as f:
126 this.read(this._file)
129 def backup(this, keys: dict, direct_to_keys: bool =
False) -> dict:
131 Backs up user data, specified by the keys parameter (dictionary).
132 Returns the successfully *updated* dictionary (if direct_to_keys is True),
133 or the self-made dict.
135 target = keys
if direct_to_keys
else this.backups
137 for subelm
in keys[key]:
138 target[key][subelm] = this[key][subelm]
142 def full_backup(this, noFile: bool, path: str, use_json: bool =
False):
145 Backup all settings by writing to GetConfig.backups and/or another file.
146 @param noFile (bool): Don't write to any file else.
147 @param path (str): Target backup file
148 @param use_json (bool = False): Use the backup file in JSON format
150 if path == this._file:
151 raise Exception(
"GetConfig.full_backup: filepath must be the loaded file!")
153 for section
in this.sections():
154 this.backups[section] = this[section]
157 with open(path,
"w")
as f:
163 def restore(this, keys: dict[str, str] |
None, optional_path: str):
167 @param keys (dict[str, str] or None): Keys + options to restore.
168 Optional but self.backups must not be empty.
169 @param optional_path (str): The name says it all. If specified,
170 both this path and self._file will be written.
171 You can also use move() function for a more complex method.
174 if not keys
and this.backups:
175 raise AttributeError(
176 "GetConfig.restore: this.backups and keys parameter are empty/died"
179 for option
in keys[key]:
180 this.set_and_update(key, option, keys[key][option])
183 with open(optional_path,
"w")
as f:
186 def getkey(this, section: str, option: str, needed: bool =
False,
187 make: bool =
False, noraiseexp: bool =
False, raw: bool =
False) -> typing.Any |
None:
189 Try to get the value of an option under the spectified section.
191 @param section, option: Target section->option
192 @param needed (boolean=False): The target option is needed - should use with make & noraiseexp.
193 @param make (boolean=False): Create the option if it is not found from the search
194 @param noraiseexp (boolean=False): Make getkey() raise exceptions or not (when neccessary)
195 @param raw (boolean=False): Don't use aliases for the value we get.
197 @return False if the option does not exist and needed parameter set to False.
200 def bringitback() -> typing.Any | None:
201 target = this.backups
207 if not target[section]:
208 raise ConfigurationError(this._file,
"Unable to find the section in both GetConfig.backups and GetConfig.cfg!",
210 if not target[section][option]:
211 raise ConfigurationError(this._file,
"Unable to find the option in both GetConfig.backups and GetConfig.cfg!",
213 if not section
in this.sections():
214 this.add_section(section)
215 value_ = target[section][option]
217 this.set_and_update(section, option, value_)
219 this.set(section, option, value_)
223 value = this.get(section, option)
226 value = bringitback()
235 value = value.removeprefix(key).removesuffix(key)
237 if not value
in this.aliases
or raw
is True:
240 return this.aliases[value]
242 def aliasyesno(this, yesvalue=None, novalue=None):
244 this.yes_values.append(yesvalue)
245 this.aliases[yesvalue] =
True
248 this.no_values.append(novalue)
249 this.aliases[novalue] =
False
251 def alias(this, value, value2):
252 this.aliases[value] = value2
254 def move(this, list_: dict[str, dict[str, str]]):
258 Move configurations found from the file that GetConfig currently uses.
263 "section1->option1": {
264 "newpath": "section_one->option1",
267 "special->option0": {
268 "newpath": "special_thing->option0",
269 "file": "~/.config/test.ini"
275 "list_" is a dictionary - each key is a dictionary which defines how a setting should be moved.
276 The key name is your setting (section->option). Under that 'key' is 2 options:
277 * "newpath" specifies the location of the setting in the target file (section->option)
278 * "file" specifies the location of the target file we'll move the options to. Ignore it or leave it "unchanged" to tell
279 the function that you don't want to move the setting else where than the current file.
280 * "delete_entire_section" (ignorable, values are 'yes' and 'no') allows you to remove the old section after the move.
283 for section
in list_.keys():
285 section_ = section.split(
"->")[0]
286 option_ = section.split(
"->")[1]
288 if not section_
in this.sections():
290 if not option_
in this[section]:
293 value = this[section_][option_]
296 newsection = list_[section][
"newpath"].split(
"->")[0]
297 newoption = list_[section][
"newpath"].split(
"->")[1]
298 if not "file" in list_[section]
or list_[section][
"file"] ==
"unchanged":
299 if not newsection
in this.sections():
300 this.add_section(newsection)
301 this.set_and_update(newsection, newoption, value)
303 newobj =
GetConfig(
None, list_[section][
"file"])
304 newobj.add_section(newsection)
305 newobj.set_and_update(newsection, newoption, value)
307 if "delete_entire_section" in list_[section] \
308 and list_[section_][
"delete_entire_section"]
in this.yes_values:
309 this.remove_section(section_)
314 Set an option, and eventually apply it to the file.
316 this.set(section, option, value)
320 FileSystemEventHandler
323 def on_any_event(this, event: FileSystemEvent):
324 assert event.src_path == this._file
325 assert event.is_directory ==
False
327 if isinstance(event, (FileModifiedEvent, FileCreatedEvent)):
328 this.readf(event.src_path)
329 elif isinstance(event, (FileOpenedEvent, FileClosedEvent)):
332 warn(f
"{event.src_path} has gone!")
def __init__(this, dict[str]|str|None config, str file, **kwds)
def restore(this, dict[str, str]|None keys, str optional_path)
typing.Any|None getkey(this, str section, str option, bool needed=False, bool make=False, bool noraiseexp=False, bool raw=False)
def reset(this, bool restore=False)
dict backup(this, dict keys, bool direct_to_keys=False)
def set_and_update(this, str section, str option, str|None value=None)
def move(this, dict[str, dict[str, str]] list_)
def full_backup(this, bool noFile, str path, bool use_json=False)
def readf(this, str file, str|None encoding=None)