diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semiconginev2/contrib/settings.nim	Wed Jul 17 23:41:51 2024 +0700
@@ -0,0 +1,100 @@
+const CONFIGROOT: string = "."
+const CONFIGEXTENSION: string = "ini"
+# by default enable hot-reload of runtime-configuration only in debug builds
+const CONFIGHOTRELOAD: bool = not defined(release)
+# milliseconds to wait between checks for settings hotreload
+const CONFIGHOTRELOADINTERVAL: int = 1000
+
+when CONFIGHOTRELOAD:
+  var
+    configUpdates: Channel[(string, string)]
+  configUpdates.open()
+
+# runtime configuration
+# =====================
+# namespace is the path from the CONFIGROOT to the according settings file without the file extension
+# a settings file must always have the extension CONFIGEXTENSION
+# a fully qualified settings identifier can be in the form {namespace}.{section}.{key}
+# {key} and {section} may not contain dots
+
+# a "namespace" is the path from the settings root to an *.CONFIGEXTENSION file, without the file extension
+# settings is a namespace <-> settings mapping
+var allsettings: Table[string, Config]
+
+proc configRoot(): string =
+  joinPath(absolutePath(getAppDir()), CONFIGROOT)
+
+proc getFile(namespace: string): string =
+  joinPath(configRoot(), namespace & "." & CONFIGEXTENSION)
+
+iterator walkConfigNamespaces(): string =
+  for file in walkDirRec(dir = configRoot(), relative = true, checkDir = true):
+    if file.endsWith("." & CONFIGEXTENSION):
+      yield file[0 ..< ^(CONFIGEXTENSION.len + 1)]
+
+proc loadAllConfig(): Table[string, Config] =
+  for ns in walkConfigNamespaces():
+    result[ns] = ns.getFile().loadConfig()
+
+proc ReloadSettings*() =
+  allsettings = loadAllConfig()
+
+proc configStr(key, section, namespace: string): string =
+  when CONFIGHOTRELOAD:
+    while configUpdates.peek() > 0:
+      let (updatedNamespace, updatedConfig) = configUpdates.recv()
+      allsettings[updatedNamespace] = loadConfig(newStringStream(updatedConfig))
+  if not allsettings.hasKey(namespace):
+    raise newException(Exception, &"Settings {namespace}.{section}.{key} was not found")
+  allsettings[namespace].getSectionValue(section, key)
+
+proc Setting*[T: int|float|string](key, section, namespace: string): T =
+  when T is int:
+    let value = configStr(key, section, namespace)
+    if parseInt(value, result) == 0:
+      raise newException(Exception, &"Unable to parse int from settings {namespace}.{section}.{key}: {value}")
+  elif T is float:
+    let value = configStr(key, section, namespace)
+    if parseFloat(value, result) == 0:
+      raise newException(Exception, &"Unable to parse float from settings {namespace}.{section}.{key}: {value}")
+  else:
+    result = configStr(key, section, namespace)
+
+proc Setting*[T: int|float|string](identifier: string): T =
+  # identifier can be in the form:
+  # {namespace}.{key}
+  # {namespace}.{section}.{key}
+  let parts = identifier.rsplit(".")
+  if parts.len == 1:
+    raise newException(Exception, &"Setting with name {identifier} has no namespace")
+  if parts.len == 2: result = Setting[T](parts[1], "", parts[0])
+  else: result = Setting[T](parts[^1], parts[^2], joinPath(parts[0 .. ^3]))
+
+proc HadConfigUpdate*(): bool =
+  when CONFIGHOTRELOAD == true:
+    result = configUpdates.peek() > 0
+
+allsettings = loadAllConfig()
+
+when CONFIGHOTRELOAD == true:
+  import std/times
+
+  proc configFileWatchdog() {.thread.} =
+    var configModTimes: Table[string, times.Time]
+    while true:
+      for namespace in walkConfigNamespaces():
+        if not (namespace in configModTimes):
+          configModTimes[namespace] = times.Time()
+        let lastMod = namespace.getFile().getLastModificationTime()
+        if lastMod > configModTimes[namespace]:
+          configModTimes[namespace] = lastMod
+          let configStr = newFileStream(namespace.getFile()).readAll()
+          configUpdates.send((namespace, configStr))
+      sleep CONFIGHOTRELOADINTERVAL
+  var thethread: Thread[void]
+  createThread(thethread, configFileWatchdog)
+
+if not defined(release):
+  setLogFilter(lvlAll)
+else:
+  setLogFilter(lvlWarn)