Compare commits
6 Commits
a33500f6d4
...
0.12.0
| Author | SHA1 | Date | |
|---|---|---|---|
| fccaf54dc5 | |||
| 8e381d15d0 | |||
| b4710e7070 | |||
| c60f7e1576 | |||
| c0e96f99a8 | |||
| c1dbcd90a4 |
28
cliutils.nim
28
cliutils.nim
@@ -1,39 +1,17 @@
|
|||||||
import std/[nre, terminal, strtabs, tables, unicode]
|
import std/[strtabs, strutils, tables]
|
||||||
import strutils except toUpper, toLower
|
|
||||||
|
|
||||||
|
import ./cliutils/ansiterm
|
||||||
import ./cliutils/config
|
import ./cliutils/config
|
||||||
import ./cliutils/daemonize
|
import ./cliutils/daemonize
|
||||||
import ./cliutils/procutil
|
import ./cliutils/procutil
|
||||||
import ./cliutils/queue_logger
|
import ./cliutils/queue_logger
|
||||||
|
|
||||||
|
export ansiterm
|
||||||
export config
|
export config
|
||||||
export daemonize
|
export daemonize
|
||||||
export procutil
|
export procutil
|
||||||
export queue_logger
|
export queue_logger
|
||||||
|
|
||||||
type
|
|
||||||
TermColor = ForegroundColor or BackgroundColor
|
|
||||||
|
|
||||||
const termReset* = "\e[0;m"
|
|
||||||
|
|
||||||
proc termColor*(color: TermColor, bright, bold = false): string =
|
|
||||||
var colorVal = ord(color)
|
|
||||||
if bright: inc(colorVal, 60)
|
|
||||||
return "\e[" & $colorVal & (if bold: ";1" else: "") & "m"
|
|
||||||
|
|
||||||
|
|
||||||
proc withColor*(str: string, color: TermColor, bright, bold, skipReset = false): string =
|
|
||||||
if skipReset:
|
|
||||||
return termColor(color, bright, bold) & str
|
|
||||||
else:
|
|
||||||
return termColor(color, bright, bold) & str & termReset
|
|
||||||
|
|
||||||
|
|
||||||
proc stripAnsi*(str: string): string =
|
|
||||||
let STRIP_ANSI_REGEX = re"\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]"
|
|
||||||
return str.replace(STRIP_ANSI_REGEX, "")
|
|
||||||
|
|
||||||
|
|
||||||
proc doParseQueryParams[T](queryParams: T): seq[string] =
|
proc doParseQueryParams[T](queryParams: T): seq[string] =
|
||||||
result = @[]
|
result = @[]
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Package
|
# Package
|
||||||
|
|
||||||
version = "0.10.0"
|
version = "0.12.0"
|
||||||
author = "Jonathan Bernard"
|
author = "Jonathan Bernard"
|
||||||
description = "Helper functions for writing command line interfaces."
|
description = "Helper functions for writing command line interfaces."
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -8,4 +8,4 @@ license = "MIT"
|
|||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
|
||||||
requires @["nim >= 1.6.0", "docopt >= 0.6.8"]
|
requires @["nim >= 1.6.0", "docopt >= 0.6.8", "regex", "identcasing"]
|
||||||
|
|||||||
172
cliutils/ansiterm.nim
Normal file
172
cliutils/ansiterm.nim
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import std/[nre, sequtils, strutils, unicode]
|
||||||
|
|
||||||
|
# See
|
||||||
|
# - https://gist.github.com/ConnerWill/d4b6c776b509add763e17f9f113fd25b
|
||||||
|
# - https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
|
|
||||||
|
const CSI = "\x1b["
|
||||||
|
const RESET_FORMATTING* = "\x1b[0m"
|
||||||
|
const ANSI_ESCAPE_CODE_ENDINGS*: seq[Rune] =
|
||||||
|
toRunes(toSeq('A'..'Z') & toSeq('a'..'z'))
|
||||||
|
|
||||||
|
let FORMATTING_REGEX* = re("\x1b\\[([0-9;]*)([a-zA-Z])")
|
||||||
|
|
||||||
|
type
|
||||||
|
TrueColor* = tuple[r, g, b: int]
|
||||||
|
|
||||||
|
CursorType* = enum
|
||||||
|
ctBlockBlink = 1, ctBlock, ctUnderlineBlink, ctUnderline, ctBarBlink, ctBar
|
||||||
|
|
||||||
|
EraseMode* = enum
|
||||||
|
emToEnd, emToStart, emAll
|
||||||
|
|
||||||
|
TerminalColors* = enum
|
||||||
|
cBlack = 0, cRed, cGreen, cYellow, cBlue, cMagenta, cCyan, cWhite,
|
||||||
|
cDefault = 9
|
||||||
|
|
||||||
|
cBrightBlack = 60, cBrightRed, cBrightGreen, cBrightYellow, cBrightBlue,
|
||||||
|
cBrightMagenta, cBrightCyan, cBrightWhite
|
||||||
|
|
||||||
|
TerminalStyle* = enum
|
||||||
|
tsBold = 1, tsDim, tsItalic, tsUnderline, tsBlink, tsInverse, tsHidden,
|
||||||
|
tsStrikethrough
|
||||||
|
|
||||||
|
proc stripFormatting*(text: string): string =
|
||||||
|
text.replace(FORMATTING_REGEX, "")
|
||||||
|
|
||||||
|
proc stripAnsi*(text: string): string = stripFormatting(text)
|
||||||
|
|
||||||
|
func ansiAwareSubstring*(s: string, start, length: int): string =
|
||||||
|
var outputRunes = newSeq[Rune]()
|
||||||
|
var curAnsiSequences = newSeq[Rune]()
|
||||||
|
var runes = s.toRunes
|
||||||
|
var visibleRunesSeen = 0
|
||||||
|
var visibleOutputLen = 0
|
||||||
|
var i = 0
|
||||||
|
|
||||||
|
while i < runes.len and visibleOutputLen < length:
|
||||||
|
|
||||||
|
if runes.len > i + runeLen(CSI) and
|
||||||
|
$runes[i..<i + len(CSI)] == CSI:
|
||||||
|
# Recognize ANSI escape codes...
|
||||||
|
|
||||||
|
var j = i + runeLen(CSI)
|
||||||
|
while j < runes.len and runes[j] notin ANSI_ESCAPE_CODE_ENDINGS: j += 1
|
||||||
|
|
||||||
|
# and remember it if we're before the start of the substring
|
||||||
|
if visibleRunesSeen < start: curAnsiSequences.add(runes[i..j])
|
||||||
|
|
||||||
|
# or add it to the output without increasing our substring length
|
||||||
|
else: outputRunes.add(runes[i..j])
|
||||||
|
|
||||||
|
# either way we want to pick up after it
|
||||||
|
i = j
|
||||||
|
|
||||||
|
else:
|
||||||
|
if visibleRunesSeen >= start:
|
||||||
|
outputRunes.add(runes[i])
|
||||||
|
inc visibleOutputLen
|
||||||
|
inc visibleRunesSeen
|
||||||
|
|
||||||
|
inc i
|
||||||
|
|
||||||
|
result = $(curAnsiSequences & outputRunes)
|
||||||
|
|
||||||
|
|
||||||
|
func ansiAwareSubstring*[T, U](s: string, r: HSlice[T,U]): string =
|
||||||
|
let idxStart =
|
||||||
|
when r.a is BackwardsIndex: s.runeLen - int(r.a)
|
||||||
|
else: int(r.a)
|
||||||
|
|
||||||
|
let idxEnd =
|
||||||
|
when r.b is BackwardsIndex: s.runeLen - int(r.b)
|
||||||
|
else: int(r.b)
|
||||||
|
|
||||||
|
ansiAwareSubstring(s, idxStart, idxEnd - idxStart + 1)
|
||||||
|
|
||||||
|
|
||||||
|
func ansiEscSeq*(style: set[TerminalStyle]): string =
|
||||||
|
CSI & toSeq(style).mapIt($int(it)).join(";") & "m"
|
||||||
|
|
||||||
|
func ansiEscSeq*(fg: int): string = CSI & "38;5;" & $fg & "m"
|
||||||
|
func ansiEscSeq*(bg: int): string = CSI & "48;5;" & $bg & "m"
|
||||||
|
|
||||||
|
|
||||||
|
func ansiEscSeq*(fg: TrueColor): string =
|
||||||
|
"$#38;2;$#;$#;$#m" % [CSI, $fg.r, $fg.g, $fg.b]
|
||||||
|
|
||||||
|
func ansiEscSeq*(bg: TrueColor): string =
|
||||||
|
"$#48;2;$#;$#;$#m" % [CSI, $bg.r, $bg.g, $bg.b]
|
||||||
|
|
||||||
|
|
||||||
|
func ansiEscSeq*(fg: TerminalColors): string = CSI & $(int(fg) + 30) & "m"
|
||||||
|
func ansiEscSeq*(bg: TerminalColors): string = CSI & $(int(bg) + 40) & "m"
|
||||||
|
|
||||||
|
|
||||||
|
func ansiEscSeq*(fg: TerminalColors, bg: TerminalColors, style: set[TerminalStyle]): string =
|
||||||
|
result = CSI
|
||||||
|
if style != {}: result &= toSeq(style).mapIt($int(it)).join(";") & ";"
|
||||||
|
result &= $(int(fg) + 30) & ";" & $(int(bg) + 40) & "m"
|
||||||
|
|
||||||
|
|
||||||
|
func termFmt*(text: string, fg = cDefault, bg = cDefault, style: set[TerminalStyle] = {}): string =
|
||||||
|
return ansiEscSeq(fg, bg, style) & text & RESET_FORMATTING
|
||||||
|
|
||||||
|
|
||||||
|
func termFmt*[C: int | TrueColor](
|
||||||
|
text: string,
|
||||||
|
fg, bg: C,
|
||||||
|
style: set[TerminalStyle] = {}): string =
|
||||||
|
return ansiEscSeq(style) & ansiEscSeq(fg = fg) & ansiEscSeq(bg = bg) &
|
||||||
|
text & RESET_FORMATTING
|
||||||
|
|
||||||
|
|
||||||
|
func color*(text: string, fg = cDefault, bg = cDefault): string =
|
||||||
|
return termFmt(text, fg, bg, {})
|
||||||
|
|
||||||
|
func invert*(text: string): string = return CSI & "7m" & text & RESET_FORMATTING
|
||||||
|
func striketrough*(text: string): string = return CSI & "9m" & text & RESET_FORMATTING
|
||||||
|
|
||||||
|
|
||||||
|
func eraseDisplay*(mode = emToEnd): string = return CSI & $int(mode) & "J"
|
||||||
|
func eraseLine*(mode = emToEnd): string = return CSI & $int(mode) & "K"
|
||||||
|
|
||||||
|
|
||||||
|
func blinkSlow*(text: string): string = return CSI & "5m" & text & RESET_FORMATTING
|
||||||
|
func blinkFast*(text: string): string = return CSI & "6m" & text & RESET_FORMATTING
|
||||||
|
|
||||||
|
|
||||||
|
func cursorUp*(n: int = 1): string = return CSI & $int(n) & "A"
|
||||||
|
func cursorDown*(n: int = 1): string = return CSI & $int(n) & "B"
|
||||||
|
func cursorForward*(n: int = 1): string = return CSI & $int(n) & "C"
|
||||||
|
func cursorBackward*(n: int = 1): string = return CSI & $int(n) & "D"
|
||||||
|
func cursorNextLine*(n: int = 1): string = return CSI & $int(n) & "E"
|
||||||
|
func cursorPrevLine*(n: int = 1): string = return CSI & $int(n) & "F"
|
||||||
|
func cursorHorizontalAbsolute*(col: int): string = return CSI & $int(col) & "G"
|
||||||
|
|
||||||
|
|
||||||
|
func cursorPosition*(row, col: int): string =
|
||||||
|
return CSI & $int(row) & ";" & $int(col) & "H"
|
||||||
|
|
||||||
|
|
||||||
|
proc cursorType*(ct: CursorType): string = return CSI & $int(ct) & " q"
|
||||||
|
func saveCursorPosition*(): string = return CSI & "s"
|
||||||
|
func restoreCursorPosition*(): string = return CSI & "u"
|
||||||
|
|
||||||
|
|
||||||
|
func scrollUp*(n: int = 1): string = return CSI & $int(n) & "S"
|
||||||
|
func scrollDown*(n: int = 1): string = return CSI & $int(n) & "T"
|
||||||
|
|
||||||
|
|
||||||
|
proc write*(f: File, x, y: int, text: string) =
|
||||||
|
f.write(cursorPosition(y, x) & text)
|
||||||
|
|
||||||
|
|
||||||
|
proc write*(x, y: int, text: string) =
|
||||||
|
stdout.write(cursorPosition(y, x) & text)
|
||||||
|
|
||||||
|
|
||||||
|
proc setCursorType*(ct: CursorType) = stdout.write(cursorType(ct))
|
||||||
|
proc setCursorPosition*(x, y: int) = stdout.write(cursorPosition(y, x))
|
||||||
|
proc hideCursor*() = stdout.write(CSI & "?25l")
|
||||||
|
proc showCursor*() = stdout.write(CSI & "?25l")
|
||||||
@@ -1,19 +1,56 @@
|
|||||||
import std/[json, nre, os, sequtils, strtabs, strutils]
|
import std/[json, os, sequtils, strtabs, strutils]
|
||||||
import docopt
|
import docopt, identcasing, regex
|
||||||
|
|
||||||
type
|
type
|
||||||
CombinedConfig* = object
|
CombinedConfig* = object
|
||||||
docopt*: Table[string, Value]
|
docopt*: Table[string, Value]
|
||||||
json*: JsonNode
|
json*: JsonNode
|
||||||
|
|
||||||
proc keyNames(key: string): tuple[arg, env, json: string] {.raises: [KeyError].} =
|
ValueProvenance* {.pure.} = enum arguments, environment, json, default
|
||||||
|
|
||||||
|
const argStyleRegex = re2"(--)?[[:lower:][:digit:]][[:lower:][:digit:]\-]+"
|
||||||
|
const envStyleRegex = re2"[[:upper:][:digit:]_]+"
|
||||||
|
const jsonStyleRegex = re2"[[:lower:]][[:lower:][:upper:][:digit:]]+"
|
||||||
|
|
||||||
|
proc keyNames*(key: string): tuple[arg, env, json: string]
|
||||||
|
{.raises: [KeyError].} =
|
||||||
|
## CombinedConfig tries to be idiomatic in its usage of key names. For
|
||||||
|
## environment variables it uses `UPPER_SNAKE_CASE`-style naming. For
|
||||||
|
## arguments it uses `lower-kebab-case`, and for JSON it uses
|
||||||
|
## `lowerCamelCase`.
|
||||||
|
##
|
||||||
|
## *keyNames* takes a key in any one of the three styles and returns all
|
||||||
|
## three. This is primarily a helper for other parsing functions, but is
|
||||||
|
## exposed as it is sometimes useful.
|
||||||
try:
|
try:
|
||||||
result = (
|
if match(key, argStyleRegex):
|
||||||
"--" & key,
|
let unprefixed =
|
||||||
key.replace('-', '_').toUpper,
|
if key.startsWith("--"): key[2..^1]
|
||||||
key.replace(re"(-\w)", proc (m: RegexMatch): string = ($m)[1..1].toUpper))
|
else: key
|
||||||
|
|
||||||
|
return (
|
||||||
|
"--" & unprefixed,
|
||||||
|
convertCase(unprefixed, lowerKebabCase, upperSnakeCase),
|
||||||
|
convertCase(unprefixed, lowerKebabCase, lowerCamelCase))
|
||||||
|
|
||||||
|
elif match(key, envStyleRegex):
|
||||||
|
return (
|
||||||
|
"--" & convertCase(key, upperSnakeCase, lowerKebabCase),
|
||||||
|
key,
|
||||||
|
convertCase(key, upperSnakeCase, lowerCamelCase))
|
||||||
|
|
||||||
|
elif match(key, jsonStyleRegex):
|
||||||
|
return (
|
||||||
|
"--" & convertCase(key, lowerCamelCase, lowerKebabCase),
|
||||||
|
convertCase(key, lowerCamelCase, upperSnakeCase),
|
||||||
|
key)
|
||||||
|
|
||||||
|
else: raise newException(KeyError,
|
||||||
|
"key style must be --argument-style, ENV_STYLE, or jsonStyle")
|
||||||
except Exception:
|
except Exception:
|
||||||
raise newException(KeyError, "invalid config key: '" & key & "'")
|
raise newException(KeyError, "invalid config key '" & key & "': " &
|
||||||
|
getCurrentExceptionMsg())
|
||||||
|
|
||||||
|
|
||||||
template walkFieldDefs*(t: NimNode, body: untyped) =
|
template walkFieldDefs*(t: NimNode, body: untyped) =
|
||||||
let tTypeImpl = t.getTypeImpl
|
let tTypeImpl = t.getTypeImpl
|
||||||
@@ -51,6 +88,7 @@ proc initCombinedConfig*(
|
|||||||
|
|
||||||
result = CombinedConfig(docopt: docopt, json: parseFile(filename))
|
result = CombinedConfig(docopt: docopt, json: parseFile(filename))
|
||||||
|
|
||||||
|
|
||||||
proc findConfigFile*(name: string, otherLocations: seq[string] = @[]): string =
|
proc findConfigFile*(name: string, otherLocations: seq[string] = @[]): string =
|
||||||
let cfgLocations = @[
|
let cfgLocations = @[
|
||||||
$getEnv(name.strip(chars = {'.'}).toUpper()),
|
$getEnv(name.strip(chars = {'.'}).toUpper()),
|
||||||
@@ -61,42 +99,88 @@ proc findConfigFile*(name: string, otherLocations: seq[string] = @[]): string =
|
|||||||
if result == "" or not fileExists(result):
|
if result == "" or not fileExists(result):
|
||||||
raise newException(ValueError, "could not find configuration file")
|
raise newException(ValueError, "could not find configuration file")
|
||||||
|
|
||||||
proc getVal*(cfg: CombinedConfig, key: string): string =
|
|
||||||
|
proc getValueAndProvenance*(
|
||||||
|
cfg: CombinedConfig,
|
||||||
|
key: string): tuple[value: string, provenance: ValueProvenance]
|
||||||
|
{. raises: [ValueError] .} =
|
||||||
let (argKey, envKey, jsonKey) = keyNames(key)
|
let (argKey, envKey, jsonKey) = keyNames(key)
|
||||||
|
|
||||||
if cfg.docopt.contains(argKey) and cfg.docopt[argKey]: return $cfg.docopt[argKey]
|
if cfg.docopt.contains(argKey) and cfg.docopt[argKey]:
|
||||||
elif existsEnv(envKey): return getEnv(envKey)
|
return ($cfg.docopt[argKey], ValueProvenance.arguments)
|
||||||
|
elif existsEnv(envKey):
|
||||||
|
return (getEnv(envKey), ValueProvenance.environment)
|
||||||
elif cfg.json.hasKey(jsonKey):
|
elif cfg.json.hasKey(jsonKey):
|
||||||
let node = cfg.json[jsonKey]
|
let node = cfg.json[jsonKey]
|
||||||
case node.kind
|
let value =
|
||||||
of JString: return node.getStr
|
case node.kind
|
||||||
of JInt: return $node.getInt
|
of JString: node.getStr
|
||||||
of JFloat: return $node.getFloat
|
of JInt: $node.getInt
|
||||||
of JBool: return $node.getBool
|
of JFloat: $node.getFloat
|
||||||
of JNull: return ""
|
of JBool: $node.getBool
|
||||||
of JObject: return $node
|
of JNull: ""
|
||||||
of JArray: return $node
|
of JObject: $node
|
||||||
|
of JArray: $node
|
||||||
|
return (value, ValueProvenance.json)
|
||||||
else: raise newException(ValueError, "cannot find a configuration value for \"" & key & "\"")
|
else: raise newException(ValueError, "cannot find a configuration value for \"" & key & "\"")
|
||||||
|
|
||||||
|
|
||||||
|
proc getValueAndProvenance*(
|
||||||
|
cfg: CombinedConfig,
|
||||||
|
key: string,
|
||||||
|
default: string): tuple[value: string, provenance: ValueProvenance]
|
||||||
|
{.raises: [].} =
|
||||||
|
try: return getValueAndProvenance(cfg, key)
|
||||||
|
except CatchableError: return (default, ValueProvenance.default)
|
||||||
|
|
||||||
|
|
||||||
|
proc getVal*(cfg: CombinedConfig, key: string): string =
|
||||||
|
getValueAndProvenance(cfg, key).value
|
||||||
|
|
||||||
|
|
||||||
proc getVal*(cfg: CombinedConfig, key, default: string): string {.raises: [].} =
|
proc getVal*(cfg: CombinedConfig, key, default: string): string {.raises: [].} =
|
||||||
try: return getVal(cfg, key)
|
try: return getVal(cfg, key)
|
||||||
except CatchableError: return default
|
except CatchableError: return default
|
||||||
|
|
||||||
proc getJson*(
|
|
||||||
|
proc `[]`*(cfg: CombinedConfig, key: string): string = getVal(cfg, key)
|
||||||
|
|
||||||
|
|
||||||
|
proc getJsonValueAndProvenance*(
|
||||||
cfg: CombinedConfig,
|
cfg: CombinedConfig,
|
||||||
key: string
|
key: string): tuple[value: JsonNode, provenance: ValueProvenance]
|
||||||
): JsonNode {.raises: [KeyError, ValueError, IOError, OSError]} =
|
{.raises: [KeyError, ValueError, IOError, OSError]} =
|
||||||
|
|
||||||
let (argKey, envKey, jsonKey) = keyNames(key)
|
let (argKey, envKey, jsonKey) = keyNames(key)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if cfg.docopt.contains(argKey) and cfg.docopt[argKey]: return parseJson($cfg.docopt[argKey])
|
if cfg.docopt.contains(argKey) and cfg.docopt[argKey]:
|
||||||
elif existsEnv(envKey): return parseJson(getEnv(envKey))
|
return (parseJson($cfg.docopt[argKey]), ValueProvenance.arguments)
|
||||||
elif cfg.json.hasKey(jsonKey): return cfg.json[jsonKey]
|
elif existsEnv(envKey):
|
||||||
|
return (parseJson(getEnv(envKey)), ValueProvenance.environment)
|
||||||
|
elif cfg.json.hasKey(jsonKey):
|
||||||
|
return (cfg.json[jsonKey], ValueProvenance.json)
|
||||||
else: raise newException(ValueError, "cannot find a configuration value for \"" & key & "\"")
|
else: raise newException(ValueError, "cannot find a configuration value for \"" & key & "\"")
|
||||||
except Exception:
|
except Exception:
|
||||||
raise newException(ValueError, "cannot parse [" & getVal(cfg, key) & "] as JSON:" & getCurrentExceptionMsg())
|
raise newException(ValueError, "cannot parse [" & getVal(cfg, key) & "] as JSON:" & getCurrentExceptionMsg())
|
||||||
|
|
||||||
|
|
||||||
|
proc getJsonValueAndProvenance*(
|
||||||
|
cfg: CombinedConfig,
|
||||||
|
key: string,
|
||||||
|
default: JsonNode): tuple[value: JsonNode, provenance: ValueProvenance]
|
||||||
|
{.raises: [].} =
|
||||||
|
try: return getJsonValueAndProvenance(cfg, key)
|
||||||
|
except CatchableError: return (default, ValueProvenance.default)
|
||||||
|
|
||||||
|
|
||||||
|
proc getJson*(
|
||||||
|
cfg: CombinedConfig,
|
||||||
|
key: string
|
||||||
|
): JsonNode {.raises: [KeyError, ValueError, IOError, OSError]} =
|
||||||
|
getJsonValueAndProvenance(cfg, key).value
|
||||||
|
|
||||||
|
|
||||||
proc getJson*(
|
proc getJson*(
|
||||||
cfg: CombinedConfig,
|
cfg: CombinedConfig,
|
||||||
key: string,
|
key: string,
|
||||||
@@ -106,6 +190,7 @@ proc getJson*(
|
|||||||
try: return getJson(cfg, key)
|
try: return getJson(cfg, key)
|
||||||
except CatchableError: return default
|
except CatchableError: return default
|
||||||
|
|
||||||
|
|
||||||
proc hasKey*(cfg: CombinedConfig, key: string): bool =
|
proc hasKey*(cfg: CombinedConfig, key: string): bool =
|
||||||
let (argKey, envKey, jsonKey) = keyNames(key)
|
let (argKey, envKey, jsonKey) = keyNames(key)
|
||||||
|
|
||||||
@@ -114,6 +199,10 @@ proc hasKey*(cfg: CombinedConfig, key: string): bool =
|
|||||||
existsEnv(envKey) or
|
existsEnv(envKey) or
|
||||||
cfg.json.hasKey(jsonKey)
|
cfg.json.hasKey(jsonKey)
|
||||||
|
|
||||||
|
|
||||||
|
proc contains*(cfg: CombinedConfig, key: string): bool = hasKey(cfg, key)
|
||||||
|
|
||||||
|
|
||||||
proc loadEnv*(): StringTableRef =
|
proc loadEnv*(): StringTableRef =
|
||||||
result = newStringTable()
|
result = newStringTable()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user