10 Commits
0.2.1 ... 0.5.1

Author SHA1 Message Date
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
4ed613523b toUpper moved to unicode module. 2018-05-11 21:33:57 -05:00
441063a393 Update to support Nim 0.18 2018-04-02 14:39:49 -05:00
cce4e5ec80 Expand effect list of exec to keep up with Nim compiler (need to investigate later). 2017-11-15 21:03:03 -06:00
9fba13a965 Refactor exec interface. 2017-08-16 11:01:22 -05:00
2 changed files with 89 additions and 42 deletions

View File

@ -1,11 +1,15 @@
import docopt, json, osproc, posix, nre, streams, strutils, strtabs import docopt, json, osproc, posix, nre, streams, strtabs, 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
TermColor = ForegroundColor or BackgroundColor
proc getVal*(cfg: CombinedConfig, key, default: string): string = proc getVal*(cfg: CombinedConfig, key, default: string): string =
let argKey = "--" & key let argKey = "--" & key
let envKey = key.replace('-', '_').toUpper let envKey = key.replace('-', '_').toUpper
@ -13,7 +17,16 @@ proc getVal*(cfg: CombinedConfig, key, default: string): string =
if cfg.docopt[argKey]: return $cfg.docopt[argKey] if 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):
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: return default else: return default
@ -31,48 +44,15 @@ proc sendMsg*(h: HandleProcMsgCB, outMsg: TaintedString,
errMsg: TaintedString = nil, cmd: string = ""): void = errMsg: TaintedString = nil, cmd: string = ""): void =
if h != nil: h(outMsg, errMsg, cmd) if h != nil: h(outMsg, errMsg, cmd)
proc waitForWithOutput*(p: Process, msgCB: HandleProcMsgCB, proc makeProcMsgHandler*(outSink, errSink: File, prefixCmd: bool = true): HandleProcMsgCB =
procCmd: string = ""):
tuple[output: TaintedString, error: TaintedString, exitCode: int] =
var pout = outputStream(p)
var perr = errorStream(p)
result = (TaintedString"", TaintedString"", -1)
var line = newStringOfCap(120).TaintedString
while true:
if pout.readLine(line):
msgCB.sendMsg(line, nil, procCmd)
result[0].string.add(line.string)
result[0].string.add("\n")
elif perr.readLine(line):
msgCB.sendMsg(nil, line, procCmd)
result[1].string.add(line.string)
result[1].string.add("\n")
else:
result[2] = peekExitCode(p)
if result[2] != -1: break
close(p)
proc exec*(command: string, workingDir: string = "",
args: openArray[string] = [], env: StringTableRef = nil,
options: set[ProcessOption] = {poUsePath},
msgCB: HandleProcMsgCB = nil):
tuple[output: TaintedString, error: TaintedString, exitCode: int]
{.tags: [ExecIOEffect, ReadIOEffect], gcsafe.} =
var p = startProcess(command, workingDir, args, env, options)
result = waitForWithOutput(p, msgCb, command)
proc makeProcMsgHandler*(outSink, errSink: File): HandleProcMsgCB =
result = proc(outMsg, errMsg: TaintedString, cmd: string): void {.closure.} = result = proc(outMsg, errMsg: TaintedString, cmd: string): void {.closure.} =
let prefix = if cmd != nil: cmd & ": " else: "" let prefix = if cmd == nil or not prefixCmd: "" else: cmd & ": "
if outMsg != nil: outSink.writeLine(prefix & outMsg) if outMsg != nil: outSink.writeLine(prefix & outMsg)
if errMsg != nil: errSink.writeLine(prefix & errMsg) if errMsg != nil: errSink.writeLine(prefix & errMsg)
proc makeProcMsgHandler*(outSink, errSink: Stream): 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: cmd & ": " else: "" let prefix = if cmd == nil or not prefixCmd: "" else: cmd & ": "
if outMsg != nil: outSink.writeLine(prefix & outMsg) if outMsg != nil: outSink.writeLine(prefix & outMsg)
if errMsg != nil: errSink.writeLine(prefix & errMsg) if errMsg != nil: errSink.writeLine(prefix & errMsg)
@ -84,6 +64,48 @@ proc combineProcMsgHandlers*(a, b: HandleProcMsgCB): HandleProcMsgCB =
a(cmd, outMsg, errMsg) a(cmd, outMsg, errMsg)
b(cmd, outMsg, errMsg) b(cmd, outMsg, errMsg)
proc waitFor*(p: Process, msgCB: HandleProcMsgCB, procCmd: string = ""): int =
var pout = outputStream(p)
var perr = errorStream(p)
var line = newStringOfCap(120).TaintedString
while true:
if pout.readLine(line):
msgCB.sendMsg(line, nil, procCmd)
elif perr.readLine(line):
msgCB.sendMsg(nil, line, procCmd)
else:
result = peekExitCode(p)
if result != -1: break
close(p)
proc exec*(command: string, workingDir: string = "",
args: openArray[string] = [], env: StringTableRef = nil,
options: set[ProcessOption] = {poUsePath},
msgCB: HandleProcMsgCB = nil): int
{.tags: [ExecIOEffect, ReadIOEffect, RootEffect], gcsafe.} =
var p = startProcess(command, workingDir, args, env, options)
result = waitFor(p, msgCB, command)
proc execWithOutput*(command: string, workingDir:string = "",
args: openArray[string] = [], env: StringTableRef = nil,
options: set[ProcessOption] = {poUsePath},
msgCB: HandleProcMsgCB = nil):
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))
result[2] = exec(command, workingDir, args, env, options, outputCollector)
result[0] = outSeq.join("\n")
result[1] = errSeq.join("\n")
## Daemonize ## Daemonize
@ -99,7 +121,7 @@ proc onStop(sig: cint) {.noconv.} =
quit(QuitSuccess) quit(QuitSuccess)
proc daemonize*(pidfile, si, so, se: string, daemonMain: proc(): void): int = proc daemonize*(pidfile, si, so, se: string, daemonMain: proc(): void): Pid =
if fileExists(pidfile): if fileExists(pidfile):
raise newException(IOError, "pidfile " & pidfile & " already exists, daemon already running?") raise newException(IOError, "pidfile " & pidfile & " already exists, daemon already running?")
@ -151,3 +173,28 @@ proc daemonize*(pidfile, si, so, se: string, daemonMain: proc(): void): int =
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: 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.2.1" version = "0.5.1"
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.15.3", "docopt"] requires @["nim >= 0.18.0", "docopt"]