13 Commits
0.3.3 ... 0.6.5

Author SHA1 Message Date
48641b8476 Make stripAnsi GC-safe. 2020-11-12 04:26:43 -06:00
438ea5f7b3 Bugfix for queryParamsToCliArgs. 2020-03-23 08:19:40 -05:00
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
95f3bc48bd Make the CombinedConfig.getVal function more tolerant of non-string values. 2018-05-18 11:18:40 -05:00
64fdcb359d Bump version to use Nim 0.18.0 (stable) instead of 0.18.1 (not yet released). 2018-05-14 09:06:45 -05:00
2 changed files with 76 additions and 24 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 os except sleep
import strutils except toUpper, toLower
type type
CombinedConfig* = object CombinedConfig* = object
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): return cfg.json[jsonKey].getStr elif cfg.json.hasKey(jsonKey):
else: return default 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 = proc loadEnv*(): StringTableRef =
result = newStringTable() result = newStringTable()
@ -28,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
@ -59,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
@ -80,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, let 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")
@ -134,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))
@ -160,3 +176,40 @@ 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
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 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.add("--" & k)
if v != "true": result.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,10 @@
# Package # Package
version = "0.3.3" version = "0.6.5"
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.1", "docopt"] requires @["nim >= 0.19.0", "docopt >= 0.6.8"]