# tickwatch # Author: Euxane TRAN-GIRARD # Licence: EUPL-1.2 import std/sugar import std/math import std/unicode import std/times import std/os const TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mmZZZ " SKIPPED_SYMBOL = "." UNKNOWN_SYMBOL = "!" NUMERIC_SYMBOLS* = "123456789".toRunes UNICODE_SYMBOLS* = "_▁▂▃▄▅▆▇█".toRunes type Scale* = proc(val: float): float {.noSideEffect.} proc indicator(symbols: seq[Rune], min, max, val: float, scale: Scale): Rune = let division = scale(max - min) / float(symbols.len) let bucket = int(scale(val - min) / division) symbols[bucket.clamp(0, symbols.len - 1)] proc indicator*(symbols: seq[Rune], min, max, val: int, scale: Scale): Rune = symbols.indicator(min.float, max.float, val.float, scale) func millisecond(t: DateTime): int = t.nanosecond div (1_000_000) func msUntilNextSec(t: DateTime): int = 1000 - t.millisecond + 1 proc puts(f: File, s: string) = f.write s f.flushFile proc puts(f: File, r: Rune) = f.puts r.toUTF8 func formatTimestampDateTime*(dt: DateTime): string = dt.format(TIMESTAMP_FORMAT) func formatTimestampUnix*(dt: DateTime): string = ($dt.toTime.toUnix) & " " func formatTimestampNone*(dt: DateTime): string = "" proc loop*( probe: (timeout: Duration) -> int, getSymbol: (int) -> Rune, timestampHeader: (DateTime) -> string, ) = while true: stdout.puts timestampHeader(now()) for tick in 0..<60: let t = now() if tick < t.second: stdout.puts SKIPPED_SYMBOL continue let timeout = initDuration(milliseconds = t.msUntilNextSec) try: let val = probe(timeout) stdout.puts getSymbol(val) except CatchableError: stdout.puts UNKNOWN_SYMBOL let tAfter = now() if tAfter < t + timeout: sleep(tAfter.msUntilNextSec) stdout.puts "\n" proc flushAndQuit*() {.noconv.} = stdout.puts "\n" quit(0) when defined(test): import std/unittest import std/sequtils suite "logger": test "indicator value": proc id(val: float): float = val let indics = (0..30).mapIt(NUMERIC_SYMBOLS.indicator(5, 20, it, id)) check indics.deduplicate(isSorted = true) == NUMERIC_SYMBOLS