comparison semiconginev2/contrib/settings.nim @ 1226:c8e3037aca66 compiletime-tests

add: contrib stuff
author sam <sam@basx.dev>
date Wed, 17 Jul 2024 23:41:51 +0700
parents
children 01e9f41d35b1
comparison
equal deleted inserted replaced
1225:27cd1c21290e 1226:c8e3037aca66
1 const CONFIGROOT: string = "."
2 const CONFIGEXTENSION: string = "ini"
3 # by default enable hot-reload of runtime-configuration only in debug builds
4 const CONFIGHOTRELOAD: bool = not defined(release)
5 # milliseconds to wait between checks for settings hotreload
6 const CONFIGHOTRELOADINTERVAL: int = 1000
7
8 when CONFIGHOTRELOAD:
9 var
10 configUpdates: Channel[(string, string)]
11 configUpdates.open()
12
13 # runtime configuration
14 # =====================
15 # namespace is the path from the CONFIGROOT to the according settings file without the file extension
16 # a settings file must always have the extension CONFIGEXTENSION
17 # a fully qualified settings identifier can be in the form {namespace}.{section}.{key}
18 # {key} and {section} may not contain dots
19
20 # a "namespace" is the path from the settings root to an *.CONFIGEXTENSION file, without the file extension
21 # settings is a namespace <-> settings mapping
22 var allsettings: Table[string, Config]
23
24 proc configRoot(): string =
25 joinPath(absolutePath(getAppDir()), CONFIGROOT)
26
27 proc getFile(namespace: string): string =
28 joinPath(configRoot(), namespace & "." & CONFIGEXTENSION)
29
30 iterator walkConfigNamespaces(): string =
31 for file in walkDirRec(dir = configRoot(), relative = true, checkDir = true):
32 if file.endsWith("." & CONFIGEXTENSION):
33 yield file[0 ..< ^(CONFIGEXTENSION.len + 1)]
34
35 proc loadAllConfig(): Table[string, Config] =
36 for ns in walkConfigNamespaces():
37 result[ns] = ns.getFile().loadConfig()
38
39 proc ReloadSettings*() =
40 allsettings = loadAllConfig()
41
42 proc configStr(key, section, namespace: string): string =
43 when CONFIGHOTRELOAD:
44 while configUpdates.peek() > 0:
45 let (updatedNamespace, updatedConfig) = configUpdates.recv()
46 allsettings[updatedNamespace] = loadConfig(newStringStream(updatedConfig))
47 if not allsettings.hasKey(namespace):
48 raise newException(Exception, &"Settings {namespace}.{section}.{key} was not found")
49 allsettings[namespace].getSectionValue(section, key)
50
51 proc Setting*[T: int|float|string](key, section, namespace: string): T =
52 when T is int:
53 let value = configStr(key, section, namespace)
54 if parseInt(value, result) == 0:
55 raise newException(Exception, &"Unable to parse int from settings {namespace}.{section}.{key}: {value}")
56 elif T is float:
57 let value = configStr(key, section, namespace)
58 if parseFloat(value, result) == 0:
59 raise newException(Exception, &"Unable to parse float from settings {namespace}.{section}.{key}: {value}")
60 else:
61 result = configStr(key, section, namespace)
62
63 proc Setting*[T: int|float|string](identifier: string): T =
64 # identifier can be in the form:
65 # {namespace}.{key}
66 # {namespace}.{section}.{key}
67 let parts = identifier.rsplit(".")
68 if parts.len == 1:
69 raise newException(Exception, &"Setting with name {identifier} has no namespace")
70 if parts.len == 2: result = Setting[T](parts[1], "", parts[0])
71 else: result = Setting[T](parts[^1], parts[^2], joinPath(parts[0 .. ^3]))
72
73 proc HadConfigUpdate*(): bool =
74 when CONFIGHOTRELOAD == true:
75 result = configUpdates.peek() > 0
76
77 allsettings = loadAllConfig()
78
79 when CONFIGHOTRELOAD == true:
80 import std/times
81
82 proc configFileWatchdog() {.thread.} =
83 var configModTimes: Table[string, times.Time]
84 while true:
85 for namespace in walkConfigNamespaces():
86 if not (namespace in configModTimes):
87 configModTimes[namespace] = times.Time()
88 let lastMod = namespace.getFile().getLastModificationTime()
89 if lastMod > configModTimes[namespace]:
90 configModTimes[namespace] = lastMod
91 let configStr = newFileStream(namespace.getFile()).readAll()
92 configUpdates.send((namespace, configStr))
93 sleep CONFIGHOTRELOADINTERVAL
94 var thethread: Thread[void]
95 createThread(thethread, configFileWatchdog)
96
97 if not defined(release):
98 setLogFilter(lvlAll)
99 else:
100 setLogFilter(lvlWarn)