mirror of https://github.com/japl-lang/japl.git
155 lines
5.2 KiB
Nim
155 lines
5.2 KiB
Nim
# Copyright 2020 Mattia Giambirtone
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
## A quick library for writing debug logs, errors, fatals and progress bars
|
|
## for the test suite.
|
|
##
|
|
## Global variables:
|
|
##
|
|
## totalLog (can be written to with the proc log)
|
|
## verbose (can be set with the proc setVerbosity)
|
|
## logfiles (can be set with the proc setLogfiles)
|
|
##
|
|
## The rationale behind all three is that they have one value accross
|
|
## one jats process/instance, and they would bloat up every single proc
|
|
## signature, because they are needed for the proc log to work.
|
|
|
|
import terminal
|
|
import strformat
|
|
import times
|
|
import strutils
|
|
|
|
|
|
type LogLevel* {.pure.} = enum
|
|
## All the different possible log levels
|
|
Debug, # always written to file only (large outputs, such as the entire output of the failing test or stacktrace)
|
|
Info, # important information about the progress of the test suite
|
|
Enumeration, # a white output for the enumerate option
|
|
Error, # failing tests (printed with yellow)
|
|
Fatal # always printed with red, halts the entire suite (test parsing errors, printed with red)
|
|
|
|
# log config: which log levels to show, show in silent mode and save to the
|
|
# detailed debug logs
|
|
const echoedLogs = {LogLevel.Info, LogLevel.Error, LogLevel.Fatal,
|
|
LogLevel.Enumeration}
|
|
const echoedLogsSilent = {LogLevel.Fatal, LogLevel.Enumeration} # will be echoed even if test suite is silent
|
|
const savedLogs = {LogLevel.Debug, LogLevel.Info, LogLevel.Error,
|
|
LogLevel.Fatal, LogLevel.Enumeration}
|
|
|
|
# aesthetic config:
|
|
# progress bar length
|
|
const progbarLength = 25
|
|
# log level colors
|
|
const logColors = [LogLevel.Debug: fgDefault, LogLevel.Info: fgGreen,
|
|
LogLevel.Enumeration: fgDefault,
|
|
LogLevel.Error: fgYellow, LogLevel.Fatal: fgRed]
|
|
|
|
# global vars for the proc log
|
|
var totalLog = ""
|
|
var verbose = true
|
|
var logfiles: seq[string]
|
|
|
|
# simple interfaces with the globals
|
|
proc setVerbosity*(verb: bool) =
|
|
## Sets the logging verbosity
|
|
verbose = verb
|
|
|
|
proc getTotalLog*: string =
|
|
## Returns all the detailed logs in ever logged in the jats instance
|
|
totalLog
|
|
|
|
proc setLogfiles*(files: seq[string]) =
|
|
## Sets files to write logs to
|
|
logfiles = files
|
|
|
|
# main logging command
|
|
proc log*(level: LogLevel, msg: string) =
|
|
## Adds a line to the total logs/stdout depending on config, together
|
|
## with the timestamp
|
|
let msg = &"[{$level} - {$getTime()}] {msg}"
|
|
if level in savedLogs:
|
|
totalLog &= msg & "\n"
|
|
if logfiles.len() > 0:
|
|
for file in logfiles:
|
|
let handle = file.open(fmAppend)
|
|
handle.writeLine(msg)
|
|
handle.close()
|
|
if (verbose and (level in echoedLogs)) or ((not verbose) and (level in echoedLogsSilent)):
|
|
setForegroundColor(logColors[level])
|
|
echo msg
|
|
setForegroundColor(fgDefault)
|
|
|
|
|
|
type FatalError* = ref object of CatchableError
|
|
|
|
proc fatal*(msg: string) =
|
|
## Creates a fatal error, logs it and raises it as an exception
|
|
log(LogLevel.Fatal, msg)
|
|
let e = new(FatalError)
|
|
e.msg = msg
|
|
raise e
|
|
|
|
|
|
# progress bar stuff
|
|
|
|
type Buffer* = ref object
|
|
## Represents an updateable line on the terminal
|
|
contents: string
|
|
previous: string
|
|
termwidth: int
|
|
|
|
proc newBuffer*: Buffer =
|
|
## Creates a Buffer, hides the cursor
|
|
hideCursor()
|
|
new(result)
|
|
|
|
proc updateProgressBar*(buf: Buffer, text: string, total: int, current: int) =
|
|
## Fills a buffer with a progress bar with label (text) total cells (total)
|
|
## and filled cells (current)
|
|
if total <= 0:
|
|
return
|
|
var newline = ""
|
|
newline &= "["
|
|
let ratio = current / total
|
|
let filledCount = int(ratio * progbarLength)
|
|
if filledCount > 0:
|
|
newline &= "=".repeat(filledCount)
|
|
if progbarLength - filledCount - 1 > 0:
|
|
newline &= " ".repeat(progbarLength - filledCount - 1)
|
|
newline &= &"] ({current}/{total}) {text}"
|
|
# to avoid process switching during half-written progress bars and whatnot all terminal editing happens at the end
|
|
let w = terminalWidth()
|
|
if w > newline.len():
|
|
newline &= " ".repeat(w - newline.len() - 1)
|
|
else:
|
|
newline = newline[0..w-2]
|
|
buf.contents = newline
|
|
|
|
proc clearLineAndWrite(text: string, oldsize: int) =
|
|
## writes text to the beginning of the line
|
|
# oldsize is there for history, and so that the implementation
|
|
# of line clearing is flexible
|
|
write stdout, "\r" & text & "\r"
|
|
|
|
proc render*(buf: Buffer) =
|
|
if verbose: #and buf.previous != buf.contents:
|
|
clearLineAndWrite(buf.contents, buf.previous.len())
|
|
buf.previous = buf.contents
|
|
|
|
proc endBuffer*(buf: Buffer) =
|
|
## Ends the existence of a buffer
|
|
## restores terminal status for good scrolling experience
|
|
showCursor()
|
|
|