122 lines
3.9 KiB
Nim
122 lines
3.9 KiB
Nim
import std/[json, nre, os, sequtils, strtabs, strutils]
|
|
import docopt
|
|
|
|
type
|
|
CombinedConfig* = object
|
|
docopt*: Table[string, Value]
|
|
json*: JsonNode
|
|
|
|
proc keyNames(key: string): tuple[arg, env, json: string] {.raises: [KeyError].} =
|
|
try:
|
|
result = (
|
|
"--" & key,
|
|
key.replace('-', '_').toUpper,
|
|
key.replace(re"(-\w)", proc (m: RegexMatch): string = ($m)[1..1].toUpper))
|
|
except Exception:
|
|
raise newException(KeyError, "invalid config key: '" & key & "'")
|
|
|
|
template walkFieldDefs*(t: NimNode, body: untyped) =
|
|
let tTypeImpl = t.getTypeImpl
|
|
|
|
var nodeToItr: NimNode
|
|
if tTypeImpl.typeKind == ntyObject: nodeToItr = tTypeImpl[2]
|
|
elif tTypeImpl.typeKind == ntyTypeDesc: nodeToItr = tTypeImpl.getType[1].getType[2]
|
|
else: error $t & " is not an object or type desc (it's a " & $tTypeImpl.typeKind & ")."
|
|
|
|
for fieldDef {.inject.} in nodeToItr.children:
|
|
# ignore AST nodes that are not field definitions
|
|
if fieldDef.kind == nnkIdentDefs:
|
|
let fieldIdent {.inject.} = fieldDef[0]
|
|
let fieldType {.inject.} = fieldDef[1]
|
|
body
|
|
|
|
elif fieldDef.kind == nnkSym:
|
|
let fieldIdent {.inject.} = fieldDef
|
|
let fieldType {.inject.} = fieldDef.getType
|
|
body
|
|
|
|
|
|
# TODO: Generic config loader
|
|
# macro loadConfig*(filePath: string, cfgType: typed, args: Table[string, docopt.Value): untyped =
|
|
# result = newNimNode(nnkObjConstr).(cfgType)
|
|
#
|
|
# var idx = 0
|
|
# cfgType.walkFieldDefs:
|
|
# let valLookup = quote do: `getVal`(
|
|
|
|
proc initCombinedConfig*(
|
|
filename: string,
|
|
docopt: Table[string, Value] = initTable[string, Value]()
|
|
): CombinedConfig =
|
|
|
|
result = CombinedConfig(docopt: docopt, json: parseFile(filename))
|
|
|
|
proc findConfigFile*(name: string, otherLocations: seq[string] = @[]): string =
|
|
let cfgLocations = @[
|
|
$getEnv(name.strip(chars = {'.'}).toUpper()),
|
|
$getEnv("HOME") / name,
|
|
"." / name ] & otherLocations
|
|
|
|
result = cfgLocations.foldl(if fileExists(b): b else: a, "")
|
|
if result == "" or not fileExists(result):
|
|
raise newException(ValueError, "could not find configuration file")
|
|
|
|
proc getVal*(cfg: CombinedConfig, key: string): string =
|
|
let (argKey, envKey, jsonKey) = keyNames(key)
|
|
|
|
if cfg.docopt.contains(argKey) and cfg.docopt[argKey]: return $cfg.docopt[argKey]
|
|
elif existsEnv(envKey): return getEnv(envKey)
|
|
elif cfg.json.hasKey(jsonKey):
|
|
let node = cfg.json[jsonKey]
|
|
case node.kind
|
|
of JString: return node.getStr
|
|
of JInt: return $node.getInt
|
|
of JFloat: return $node.getFloat
|
|
of JBool: return $node.getBool
|
|
of JNull: return ""
|
|
of JObject: return $node
|
|
of JArray: return $node
|
|
else: raise newException(ValueError, "cannot find a configuration value for \"" & key & "\"")
|
|
|
|
proc getVal*(cfg: CombinedConfig, key, default: string): string {.raises: [].} =
|
|
try: return getVal(cfg, key)
|
|
except CatchableError: return default
|
|
|
|
proc getJson*(
|
|
cfg: CombinedConfig,
|
|
key: string
|
|
): JsonNode {.raises: [KeyError, ValueError, IOError, OSError]} =
|
|
|
|
let (argKey, envKey, jsonKey) = keyNames(key)
|
|
|
|
try:
|
|
if cfg.docopt.contains(argKey) and cfg.docopt[argKey]: return parseJson($cfg.docopt[argKey])
|
|
elif existsEnv(envKey): return parseJson(getEnv(envKey))
|
|
elif cfg.json.hasKey(jsonKey): return cfg.json[jsonKey]
|
|
else: raise newException(ValueError, "cannot find a configuration value for \"" & key & "\"")
|
|
except Exception:
|
|
raise newException(ValueError, "cannot parse value as JSON:" & getCurrentExceptionMsg())
|
|
|
|
proc getJson*(
|
|
cfg: CombinedConfig,
|
|
key: string,
|
|
default: JsonNode
|
|
) : JsonNode {.raises: []} =
|
|
|
|
try: return getJson(cfg, key)
|
|
except CatchableError: return default
|
|
|
|
proc hasKey*(cfg: CombinedConfig, key: string): bool =
|
|
let (argKey, envKey, jsonKey) = keyNames(key)
|
|
|
|
return
|
|
(cfg.docopt.contains(argKey) and cfg.docopt[argKey]) or
|
|
existsEnv(envKey) or
|
|
cfg.json.hasKey(jsonKey)
|
|
|
|
proc loadEnv*(): StringTableRef =
|
|
result = newStringTable()
|
|
|
|
for k, v in envPairs():
|
|
result[k] = v
|