Display labels side by side, log output.

* Fit as many labels as possible on each line instead of only one per line.
  Includes several spaces of padding in between each label.
* Add the `-o` option to also write logged output to a file. This is intended
  for use with the command-style invocation to keep a persistent log of the
  command's output for further investigation.
* Added a TODO file with ideas for future improvements.
This commit is contained in:
Jonathan Bernard 2017-07-06 00:52:59 -05:00
parent e958983e3e
commit 9a2fcf29dc
2 changed files with 39 additions and 11 deletions

2
TODO Normal file
View File

@ -0,0 +1,2 @@
* Implement -f functionality: follow a file.
* Respond to terminal resizing.

View File

@ -2,7 +2,7 @@
## ========= ## =========
## ##
## Little tool to extract expected information from log streams. ## Little tool to extract expected information from log streams.
import json, logging, ncurses, os, osproc, streams, strutils, threadpool import json, logging, ncurses, os, osproc, sequtils, streams, strutils, threadpool
import nre except toSeq import nre except toSeq
import private/ncurses_ext import private/ncurses_ext
@ -31,6 +31,9 @@ proc readStream(stream: Stream): void =
when isMainModule: when isMainModule:
var cmdProc: Process var cmdProc: Process
var inStream: Stream
var outFile: File
try: try:
let usage = """ let usage = """
Usage: Usage:
@ -44,6 +47,8 @@ Options:
-E<label>;<patterh> Add something to expect not to see in the log stream. -E<label>;<patterh> Add something to expect not to see in the log stream.
-d<def-file> Specify a JSON definitions blob. -d<def-file> Specify a JSON definitions blob.
-i<in-file> Read JSON definitions from <in-file>. -i<in-file> Read JSON definitions from <in-file>.
-o<out-file> Write the log to <out-file>. Usefull for viewing
output later when executing commands.
-f Similar to tail, do not stop when the end of file -f Similar to tail, do not stop when the end of file
is reached but wait for additional modifications is reached but wait for additional modifications
to the file. -f is ignored when the input is STDIN. to the file. -f is ignored when the input is STDIN.
@ -68,8 +73,6 @@ Expectations JSON definitions follow this format:
let args = commandLineParams() let args = commandLineParams()
var expectations: seq[Expectation] = @[] var expectations: seq[Expectation] = @[]
var inStream: Stream
var haveStream = false
var follow = false var follow = false
var cmd = "NOCMD" var cmd = "NOCMD"
@ -111,11 +114,18 @@ Expectations JSON definitions follow this format:
exitErr "no such file (" & filename & ")" exitErr "no such file (" & filename & ")"
inStream = newFileStream(filename) inStream = newFileStream(filename)
haveStream = true
except: except:
exitErr "could not open file (" & filename & "):\t\n" & exitErr "could not open file (" & filename & "):\t\n" &
getCurrentExceptionMsg() getCurrentExceptionMsg()
elif arg.startsWith("-o"):
let filename = arg[2..^1]
try:
outFile = open(filename, fmWrite)
except:
exitErr "could not open output file in write mode (" & filename &
"):\n\t" & getCurrentExceptionMsg()
elif arg.match(expPattern).isSome: elif arg.match(expPattern).isSome:
var m = arg.match(expPattern).get().captures() var m = arg.match(expPattern).get().captures()
expectations.add(Expectation( expectations.add(Expectation(
@ -128,10 +138,10 @@ Expectations JSON definitions follow this format:
elif arg == "--": cmd = "" elif arg == "--": cmd = ""
else: exitErr "unrecognized argument: " & arg else: exitErr "unrecognized argument: " & arg
if cmd == "NOCMD" and not haveStream: if cmd == "NOCMD" and inStream.isNil:
exitErr "no input file or command to execute." exitErr "no input file or command to execute."
if not haveStream and cmd != "NOCMD": elif inStream.isNil:
cmdProc = startProcess(cmd, "", [], nil, {poStdErrToStdOut, poEvalCommand, poUsePath}) cmdProc = startProcess(cmd, "", [], nil, {poStdErrToStdOut, poEvalCommand, poUsePath})
inStream = cmdProc.outputStream inStream = cmdProc.outputStream
@ -153,7 +163,10 @@ Expectations JSON definitions follow this format:
init_pair(1, GREEN, BLACK) init_pair(1, GREEN, BLACK)
init_pair(2, RED, BLACK) init_pair(2, RED, BLACK)
let dispHeight = max(expectations.len + 1, 2) # Figure out how much room we need to display our information
let labelWidth = mapIt(expectations, it.label.len).foldl(max(a, b)) + 10
let labelsPerLine = width div labelWidth
let dispHeight = max((expectations.len div labelsPerLine) + 1, 2)
let logwin = newwin(height - dispHeight, width, 0, 0) let logwin = newwin(height - dispHeight, width, 0, 0)
let dispwin = newwin(dispHeight, width, height - dispHeight, 0) let dispwin = newwin(dispHeight, width, height - dispHeight, 0)
dispwin.wborder(' ', ' ', '_', ' ', '_', '_', ' ', ' ') dispwin.wborder(' ', ' ', '_', ' ', '_', '_', ' ', ' ')
@ -173,6 +186,7 @@ Expectations JSON definitions follow this format:
if lineMsg.dataAvailable: if lineMsg.dataAvailable:
let line = lineMsg.msg let line = lineMsg.msg
if not outFile.isNil: outFile.writeLine(line)
for expect in expectations: for expect in expectations:
if not expect.found: if not expect.found:
if line.find(expect.pattern).isSome: if line.find(expect.pattern).isSome:
@ -181,21 +195,28 @@ Expectations JSON definitions follow this format:
if drawDisp or firstPass: if drawDisp or firstPass:
dispwin.wmove(1, 0) dispwin.wmove(1, 0)
var labelsOnLine = 0
for expect in expectations: for expect in expectations:
let text = var text =
if expect.found: " FOUND " if expect.found: " FOUND "
else: " MISSING " else: " MISSING "
text = align(expect.label & text, labelWidth)
if expect.expected == expect.found: if expect.expected == expect.found:
dispwin.wattron(COLOR_PAIR(1)) dispwin.wattron(COLOR_PAIR(1))
dispwin.wprintw(expect.label & text & "\n") dispwin.wprintw(text)
dispwin.wattroff(COLOR_PAIR(1)) dispwin.wattroff(COLOR_PAIR(1))
else: else:
dispwin.wattron(COLOR_PAIR(2)) dispwin.wattron(COLOR_PAIR(2))
dispwin.wprintw(expect.label & text & "\n") dispwin.wprintw(text)
dispwin.wattroff(COLOR_PAIR(2)) dispwin.wattroff(COLOR_PAIR(2))
if labelsOnLine == labelsPerLine - 1:
labelsOnLine = 0
dispwin.wprintw("\n")
else: labelsOnLine += 1
dispwin.wrefresh() dispwin.wrefresh()
firstPass = false firstPass = false
@ -234,6 +255,11 @@ Expectations JSON definitions follow this format:
exitErr getCurrentExceptionMsg() exitErr getCurrentExceptionMsg()
finally: finally:
if not cmdProc.isNil and cmdProc.running(): cmdProc.kill() if not cmdProc.isNil:
if running(cmdProc): kill(cmdProc)
else:
if not inStream.isNil: close(inStream)
if not outFile.isNil: close(outFile)
close(msgChannel) close(msgChannel)
endwin() endwin()