annotate semiconginev2/contrib/settings.nim @ 1242:e8b3dc80e48e

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