import std/[macrocache, typetraits], stew/shims/macros, ./defs #[ Overview of this module: - Create temporary configuration object with all fields optional. - Load this temporary object from every registered config files including env vars and windows regs if available. - If the CLI parser detect missing opt, it will try to obtain the value from temporary object starting from the first registered config file format. - If none of them have the missing value, it will load the default value from `defaultValue` pragma. ]# const configFileRegs = CacheSeq"confutils" func isOption(n: NimNode): bool = if n.kind != nnkBracketExpr: return false eqIdent(n[0], "Option") func makeOption(n: NimNode): NimNode = newNimNode(nnkBracketExpr).add(ident("Option"), n) template objectDecl(a): untyped = type a = object proc putReclist(n: NimNode, recList: NimNode) = recList.expectKind nnkRecList if n.kind == nnkObjectTy: n[2] = recList return for z in n: putReclist(z, recList) proc generateOptionalField(fieldName: NimNode, fieldType: NimNode): NimNode = let right = if isOption(fieldType): fieldType else: makeOption(fieldType) newIdentDefs(fieldName, right) proc optionalizeFields(CF, confType: NimNode): NimNode = # Generate temporary object type where # all fields are optional. result = getAst(objectDecl(CF)) var recList = newNimNode(nnkRecList) var recordDef = getImpl(confType) for field in recordFields(recordDef): if field.readPragma"hidden" != nil or field.readPragma"command" != nil or field.readPragma"argument" != nil: continue recList.add generateOptionalField(field.name, field.typ) result.putRecList(recList) proc genLoader(i: int, format, ext, path, optType, confType: NimNode): NimNode = var pathBlock: NimNode if eqIdent(format, "Envvar"): pathBlock = quote do: block: `path` elif eqIdent(format, "Winreg"): pathBlock = quote do: block: `path` / vendorName(`confType`) / appName(`confType`) else: # toml, json, yaml, etc pathBlock = quote do: block: `path` / vendorName(`confType`) / appName(`confType`) & "." & `ext` result = quote do: let fullPath = `pathBlock` try: result.data[`i`] = `format`.loadFile(fullPath, `optType`) except: echo "Error when loading: ", fullPath echo getCurrentExceptionMsg() proc generateSetters(optType, confType, CF: NimNode): (NimNode, NimNode, int) = var procs = newStmtList() assignments = newStmtList() recordDef = getImpl(confType) numSetters = 0 procs.add quote do: template cfSetter(a, b: untyped): untyped = when a is Option: a = b else: a = b.get() for field in recordFields(recordDef): if field.readPragma"hidden" != nil: continue if field.readPragma"command" != nil or field.readPragma"argument" != nil: assignments.add quote do: result.setters[`numSetters`] = defaultConfigFileSetter inc numSetters continue let setterName = ident($field.name & "CFSetter") let fieldName = field.name procs.add quote do: proc `setterName`(s: var `confType`, cf: `CF`): bool {. nimcall, gcsafe .} = for c in cf.data: if c.`fieldName`.isSome(): cfSetter(s.`fieldName`, c.`fieldName`) return true assignments.add quote do: result.setters[`numSetters`] = `setterName` inc numSetters result = (procs, assignments, numSetters) proc generateConfigFileSetters(optType, CF, confType: NimNode): NimNode = let T = confType.getType[1] let arrayLen = configFileRegs.len let settersType = genSym(nskType, "SettersType") var loaderStmts = newStmtList() for i in 0..