10 Commits
0.3.3 ... 0.6.2

2 changed files with 73 additions and 21 deletions

View File

@ -1,21 +1,37 @@
import docopt, json, osproc, posix, nre, streams, strutils, strtabs, unicode
import docopt, json, osproc, posix, nre, streams, strtabs, tables, terminal, unicode
import os except sleep
import strutils except toUpper, toLower
type
CombinedConfig* = object
docopt*: Table[string, Value]
json*: JsonNode
proc getVal*(cfg: CombinedConfig, key, default: string): string =
TermColor = ForegroundColor or BackgroundColor
proc getVal*(cfg: CombinedConfig, key: string): string =
let argKey = "--" & key
let envKey = key.replace('-', '_').toUpper
let jsonKey = key.replace(re"(-\w)", proc (m: RegexMatch): string = ($m)[1..1].toUpper)
if cfg.docopt[argKey]: return $cfg.docopt[argKey]
elif existsEnv(envKey): return getEnv(envKey)
elif cfg.json.hasKey(jsonKey): return cfg.json[jsonKey].getStr
else: return default
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 =
try: return getVal(cfg, key)
except: return default
proc loadEnv*(): StringTableRef =
result = newStringTable()
@ -28,20 +44,20 @@ type HandleProcMsgCB* = proc (outMsg: TaintedString,
errMsg: TaintedString, cmd: string): void
proc sendMsg*(h: HandleProcMsgCB, outMsg: TaintedString,
errMsg: TaintedString = nil, cmd: string = ""): void =
errMsg: TaintedString = "", cmd: string = ""): void =
if h != nil: h(outMsg, errMsg, cmd)
proc makeProcMsgHandler*(outSink, errSink: File, prefixCmd: bool = true): HandleProcMsgCB =
result = proc(outMsg, errMsg: TaintedString, cmd: string): void {.closure.} =
let prefix = if cmd == nil or not prefixCmd: "" else: cmd & ": "
if outMsg != nil: outSink.writeLine(prefix & outMsg)
if errMsg != nil: errSink.writeLine(prefix & errMsg)
let prefix = if cmd.len == 0 or not prefixCmd: "" else: cmd & ": "
if outMsg.len > 0: outSink.writeLine(prefix & outMsg)
if errMsg.len > 0: errSink.writeLine(prefix & errMsg)
proc makeProcMsgHandler*(outSink, errSink: Stream, prefixCmd: bool = true): HandleProcMsgCB =
result = proc(outMsg, errMsg: TaintedString, cmd: string): void {.closure.} =
let prefix = if cmd == nil or not prefixCmd: "" else: cmd & ": "
if outMsg != nil: outSink.writeLine(prefix & outMsg)
if errMsg != nil: errSink.writeLine(prefix & errMsg)
let prefix = if cmd.len == 0 or not prefixCmd: "" else: cmd & ": "
if outMsg.len > 0: outSink.writeLine(prefix & outMsg)
if errMsg.len > 0: errSink.writeLine(prefix & errMsg)
proc combineProcMsgHandlers*(a, b: HandleProcMsgCB): HandleProcMsgCB =
if a == nil: result = b
@ -59,9 +75,9 @@ proc waitFor*(p: Process, msgCB: HandleProcMsgCB, procCmd: string = ""): int =
var line = newStringOfCap(120).TaintedString
while true:
if pout.readLine(line):
msgCB.sendMsg(line, nil, procCmd)
msgCB.sendMsg(line, "", procCmd)
elif perr.readLine(line):
msgCB.sendMsg(nil, line, procCmd)
msgCB.sendMsg("", line, procCmd)
else:
result = peekExitCode(p)
if result != -1: break
@ -80,15 +96,15 @@ proc execWithOutput*(command: string, workingDir:string = "",
args: openArray[string] = [], env: StringTableRef = nil,
options: set[ProcessOption] = {poUsePath},
msgCB: HandleProcMsgCB = nil):
tuple[output: TaintedString, error: TaintedString, exitCode: int] =
tuple[output, error: TaintedString, exitCode: int] =
result = (TaintedString"", TaintedString"", -1)
var outSeq, errSeq: seq[TaintedString]
outSeq = @[]; errSeq = @[]
var outputCollector = combineProcMsgHandlers(msgCB,
proc(outMsg, errMsg: TaintedString, cmd: string): void {.closure.} =
if outMsg != nil: outSeq.add(outMsg)
if errMsg != nil: errSeq.add(errMsg))
if outMsg.len > 0: outSeq.add(outMsg)
if errMsg.len > 0: errSeq.add(errMsg))
result[2] = exec(command, workingDir, args, env, options, outputCollector)
result[0] = outSeq.join("\n")
@ -134,15 +150,15 @@ proc daemonize*(pidfile, si, so, se: string, daemonMain: proc(): void): Pid =
flushFile(stdout)
flushFile(stderr)
if not si.isNil and si != "":
if si.len > 0:
fi = open(si, fmRead)
discard dup2(getFileHandle(fi), getFileHandle(stdin))
if not so.isNil and so != "":
if so.len > 0:
fo = open(so, fmAppend)
discard dup2(getFileHandle(fo), getFileHandle(stdout))
if not se.isNil and so != "":
if se.len > 0:
fe = open(se, fmAppend)
discard dup2(getFileHandle(fe), getFileHandle(stderr))
@ -160,3 +176,39 @@ proc daemonize*(pidfile, si, so, se: string, daemonMain: proc(): void): Pid =
daemonMain()
return pid1
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 = false): string =
return termColor(color, bright, bold) & str
let STRIP_ANSI_REGEX = re"\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]"
proc stripAnsi*(str: string): string = return str.replace(STRIP_ANSI_REGEX, "")
proc queryParamsToCliArgs*(queryParams: Table[string, string]): seq[string] =
result = @[]
for k,v in queryParams:
# support ?arg1=val1&arg2=val2 -> cmd val1 val2
if k.startsWith("arg"): result.add(v)
else :
result[1].add("--" & k)
if v != "true": result[1].add(v) # support things like ?verbose=true -> cmd --verbose
proc queryParamsToCliArgs*(queryParams: StringTableRef): seq[string] =
result = @[]
for k,v in queryParams:
# support ?arg1=val1&arg2=val2 -> cmd val1 val2
if k.startsWith("arg"): result.add(v)
else :
result.add("--" & k)
if v != "true": result.add(v) # support things like ?verbose=true -> cmd --verbose

View File

@ -1,11 +1,11 @@
# Package
version = "0.3.3"
version = "0.6.2"
author = "Jonathan Bernard"
description = "Helper functions for writing command line interfaces."
license = "MIT"
# Dependencies
requires @["nim >= 0.18.1", "docopt"]
requires @["nim >= 0.19.0", "docopt >= 0.6.8"]