9 Commits
0.3.5 ... 0.6.3

Author SHA1 Message Date
e9351fbd9d Fix bug in CombinedConfig: need to allow for config values not defined in the possible CLI args. 2019-02-18 16:06:10 -06:00
8036af6bc8 Add CombinedConfig.getVal that raises an exception if the config key is not found. 2019-02-18 15:38:30 -06:00
b9a69809eb Merge in fix lost from 0.5.1 2019-02-18 15:34:09 -06:00
9750ac16b3 Add version of queryParamsToCliArgs that accepts a Table[string,string] to support Jester 0.4 2019-01-17 13:19:59 -06:00
7b274bfb98 Update to support Nim 0.19 (removal of string nil specifically). 2018-12-09 03:23:12 -06:00
969af93425 Bugfix for new HTTP<->CLI functionality. 2018-10-03 03:42:10 -05:00
e0eb8fd4c2 Add functions for a simple CLI<->HTTP layer. 2018-10-02 12:09:18 -05:00
d368e85e33 Refactor color functions to provide a unified interface for fore/back ground. 2018-05-29 14:18:14 -05:00
49342818ec Add basic support for ANSI color escape codes.
Nim's `terminal` library provides ANSI terminal escape support, but
only when writing directly to a File handle. This commit adds the
`term{FG,BG}` procedures and `termReset` constant to help with embedding
ANSI escape codes in string data.
2018-05-29 12:53:42 -05:00
2 changed files with 62 additions and 21 deletions

View File

@ -1,4 +1,4 @@
import docopt, json, osproc, posix, nre, streams, strtabs, unicode import docopt, json, osproc, posix, nre, streams, strtabs, tables, terminal, unicode
import os except sleep import os except sleep
import strutils except toUpper, toLower import strutils except toUpper, toLower
@ -8,12 +8,14 @@ type
docopt*: Table[string, Value] docopt*: Table[string, Value]
json*: JsonNode 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 argKey = "--" & key
let envKey = key.replace('-', '_').toUpper let envKey = key.replace('-', '_').toUpper
let jsonKey = key.replace(re"(-\w)", proc (m: RegexMatch): string = ($m)[1..1].toUpper) let jsonKey = key.replace(re"(-\w)", proc (m: RegexMatch): string = ($m)[1..1].toUpper)
if cfg.docopt[argKey]: return $cfg.docopt[argKey] if cfg.docopt.contains(argKey) and cfg.docopt[argKey]: return $cfg.docopt[argKey]
elif existsEnv(envKey): return getEnv(envKey) elif existsEnv(envKey): return getEnv(envKey)
elif cfg.json.hasKey(jsonKey): elif cfg.json.hasKey(jsonKey):
let node = cfg.json[jsonKey] let node = cfg.json[jsonKey]
@ -25,8 +27,11 @@ proc getVal*(cfg: CombinedConfig, key, default: string): string =
of JNull: return "" of JNull: return ""
of JObject: return $node of JObject: return $node
of JArray: return $node of JArray: return $node
else: return default 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 = proc loadEnv*(): StringTableRef =
result = newStringTable() result = newStringTable()
@ -39,20 +44,20 @@ type HandleProcMsgCB* = proc (outMsg: TaintedString,
errMsg: TaintedString, cmd: string): void errMsg: TaintedString, cmd: string): void
proc sendMsg*(h: HandleProcMsgCB, outMsg: TaintedString, proc sendMsg*(h: HandleProcMsgCB, outMsg: TaintedString,
errMsg: TaintedString = nil, cmd: string = ""): void = errMsg: TaintedString = "", cmd: string = ""): void =
if h != nil: h(outMsg, errMsg, cmd) if h != nil: h(outMsg, errMsg, cmd)
proc makeProcMsgHandler*(outSink, errSink: File, prefixCmd: bool = true): HandleProcMsgCB = proc makeProcMsgHandler*(outSink, errSink: File, prefixCmd: bool = true): HandleProcMsgCB =
result = proc(outMsg, errMsg: TaintedString, cmd: string): void {.closure.} = result = proc(outMsg, errMsg: TaintedString, cmd: string): void {.closure.} =
let prefix = if cmd == nil or not prefixCmd: "" else: cmd & ": " let prefix = if cmd.len == 0 or not prefixCmd: "" else: cmd & ": "
if outMsg != nil: outSink.writeLine(prefix & outMsg) if outMsg.len > 0: outSink.writeLine(prefix & outMsg)
if errMsg != nil: errSink.writeLine(prefix & errMsg) if errMsg.len > 0: errSink.writeLine(prefix & errMsg)
proc makeProcMsgHandler*(outSink, errSink: Stream, prefixCmd: bool = true): HandleProcMsgCB = proc makeProcMsgHandler*(outSink, errSink: Stream, prefixCmd: bool = true): HandleProcMsgCB =
result = proc(outMsg, errMsg: TaintedString, cmd: string): void {.closure.} = result = proc(outMsg, errMsg: TaintedString, cmd: string): void {.closure.} =
let prefix = if cmd == nil or not prefixCmd: "" else: cmd & ": " let prefix = if cmd.len == 0 or not prefixCmd: "" else: cmd & ": "
if outMsg != nil: outSink.writeLine(prefix & outMsg) if outMsg.len > 0: outSink.writeLine(prefix & outMsg)
if errMsg != nil: errSink.writeLine(prefix & errMsg) if errMsg.len > 0: errSink.writeLine(prefix & errMsg)
proc combineProcMsgHandlers*(a, b: HandleProcMsgCB): HandleProcMsgCB = proc combineProcMsgHandlers*(a, b: HandleProcMsgCB): HandleProcMsgCB =
if a == nil: result = b if a == nil: result = b
@ -70,9 +75,9 @@ proc waitFor*(p: Process, msgCB: HandleProcMsgCB, procCmd: string = ""): int =
var line = newStringOfCap(120).TaintedString var line = newStringOfCap(120).TaintedString
while true: while true:
if pout.readLine(line): if pout.readLine(line):
msgCB.sendMsg(line, nil, procCmd) msgCB.sendMsg(line, "", procCmd)
elif perr.readLine(line): elif perr.readLine(line):
msgCB.sendMsg(nil, line, procCmd) msgCB.sendMsg("", line, procCmd)
else: else:
result = peekExitCode(p) result = peekExitCode(p)
if result != -1: break if result != -1: break
@ -91,15 +96,15 @@ proc execWithOutput*(command: string, workingDir:string = "",
args: openArray[string] = [], env: StringTableRef = nil, args: openArray[string] = [], env: StringTableRef = nil,
options: set[ProcessOption] = {poUsePath}, options: set[ProcessOption] = {poUsePath},
msgCB: HandleProcMsgCB = nil): msgCB: HandleProcMsgCB = nil):
tuple[output: TaintedString, error: TaintedString, exitCode: int] = tuple[output, error: TaintedString, exitCode: int] =
result = (TaintedString"", TaintedString"", -1) result = (TaintedString"", TaintedString"", -1)
var outSeq, errSeq: seq[TaintedString] var outSeq, errSeq: seq[TaintedString]
outSeq = @[]; errSeq = @[] outSeq = @[]; errSeq = @[]
var outputCollector = combineProcMsgHandlers(msgCB, var outputCollector = combineProcMsgHandlers(msgCB,
proc(outMsg, errMsg: TaintedString, cmd: string): void {.closure.} = proc(outMsg, errMsg: TaintedString, cmd: string): void {.closure.} =
if outMsg != nil: outSeq.add(outMsg) if outMsg.len > 0: outSeq.add(outMsg)
if errMsg != nil: errSeq.add(errMsg)) if errMsg.len > 0: errSeq.add(errMsg))
result[2] = exec(command, workingDir, args, env, options, outputCollector) result[2] = exec(command, workingDir, args, env, options, outputCollector)
result[0] = outSeq.join("\n") result[0] = outSeq.join("\n")
@ -145,15 +150,15 @@ proc daemonize*(pidfile, si, so, se: string, daemonMain: proc(): void): Pid =
flushFile(stdout) flushFile(stdout)
flushFile(stderr) flushFile(stderr)
if not si.isNil and si != "": if si.len > 0:
fi = open(si, fmRead) fi = open(si, fmRead)
discard dup2(getFileHandle(fi), getFileHandle(stdin)) discard dup2(getFileHandle(fi), getFileHandle(stdin))
if not so.isNil and so != "": if so.len > 0:
fo = open(so, fmAppend) fo = open(so, fmAppend)
discard dup2(getFileHandle(fo), getFileHandle(stdout)) discard dup2(getFileHandle(fo), getFileHandle(stdout))
if not se.isNil and so != "": if se.len > 0:
fe = open(se, fmAppend) fe = open(se, fmAppend)
discard dup2(getFileHandle(fe), getFileHandle(stderr)) discard dup2(getFileHandle(fe), getFileHandle(stderr))
@ -171,3 +176,39 @@ proc daemonize*(pidfile, si, so, se: string, daemonMain: proc(): void): Pid =
daemonMain() daemonMain()
return pid1 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 # Package
version = "0.3.5" version = "0.6.3"
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"
# Dependencies # Dependencies
requires @["nim >= 0.18.0", "docopt"] requires @["nim >= 0.19.0", "docopt >= 0.6.8"]