scripts: Adopted -k/--keep-open in treemap.py/codemap.py
It probably makes sense to adopt this in all ascii-art scripts that operate on files.
This commit is contained in:
+197
-13
@@ -12,10 +12,18 @@ import bisect
|
||||
import collections as co
|
||||
import csv
|
||||
import fnmatch
|
||||
import io
|
||||
import itertools as it
|
||||
import math as mt
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
|
||||
try:
|
||||
import inotify_simple
|
||||
except ModuleNotFoundError:
|
||||
inotify_simple = None
|
||||
|
||||
|
||||
# we don't actually need that many chars/colors thanks to the
|
||||
@@ -45,6 +53,107 @@ def openio(path, mode='r', buffering=-1):
|
||||
else:
|
||||
return open(path, mode, buffering)
|
||||
|
||||
if inotify_simple is None:
|
||||
Inotify = None
|
||||
else:
|
||||
class Inotify(inotify_simple.INotify):
|
||||
def __init__(self, paths):
|
||||
super().__init__()
|
||||
|
||||
# wait for interesting events
|
||||
flags = (inotify_simple.flags.ATTRIB
|
||||
| inotify_simple.flags.CREATE
|
||||
| inotify_simple.flags.DELETE
|
||||
| inotify_simple.flags.DELETE_SELF
|
||||
| inotify_simple.flags.MODIFY
|
||||
| inotify_simple.flags.MOVED_FROM
|
||||
| inotify_simple.flags.MOVED_TO
|
||||
| inotify_simple.flags.MOVE_SELF)
|
||||
|
||||
# recurse into directories
|
||||
for path in paths:
|
||||
if os.path.isdir(path):
|
||||
for dir, _, files in os.walk(path):
|
||||
self.add_watch(dir, flags)
|
||||
for f in files:
|
||||
self.add_watch(os.path.join(dir, f), flags)
|
||||
else:
|
||||
self.add_watch(path, flags)
|
||||
|
||||
class RingIO:
|
||||
def __init__(self, maxlen=None, head=False):
|
||||
self.maxlen = maxlen
|
||||
self.head = head
|
||||
self.lines = co.deque(maxlen=maxlen)
|
||||
self.tail = io.StringIO()
|
||||
|
||||
# trigger automatic sizing
|
||||
if maxlen == 0:
|
||||
self.resize(0)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.lines)
|
||||
|
||||
def write(self, s):
|
||||
# note using split here ensures the trailing string has no newline
|
||||
lines = s.split('\n')
|
||||
|
||||
if len(lines) > 1 and self.tail.getvalue():
|
||||
self.tail.write(lines[0])
|
||||
lines[0] = self.tail.getvalue()
|
||||
self.tail = io.StringIO()
|
||||
|
||||
self.lines.extend(lines[:-1])
|
||||
|
||||
if lines[-1]:
|
||||
self.tail.write(lines[-1])
|
||||
|
||||
def resize(self, maxlen):
|
||||
self.maxlen = maxlen
|
||||
if maxlen == 0:
|
||||
maxlen = shutil.get_terminal_size((80, 5))[1]
|
||||
if maxlen != self.lines.maxlen:
|
||||
self.lines = co.deque(self.lines, maxlen=maxlen)
|
||||
|
||||
canvas_lines = 1
|
||||
def draw(self):
|
||||
# did terminal size change?
|
||||
if self.maxlen == 0:
|
||||
self.resize(0)
|
||||
|
||||
# copy lines
|
||||
lines = self.lines.copy()
|
||||
# pad to fill any existing canvas, but truncate to terminal size
|
||||
h = shutil.get_terminal_size((80, 5))[1]
|
||||
lines.extend('' for _ in range(
|
||||
len(lines),
|
||||
min(RingIO.canvas_lines, h)))
|
||||
while len(lines) > h:
|
||||
if self.head:
|
||||
lines.pop()
|
||||
else:
|
||||
lines.popleft()
|
||||
|
||||
# first thing first, give ourself a canvas
|
||||
while RingIO.canvas_lines < len(lines):
|
||||
sys.stdout.write('\n')
|
||||
RingIO.canvas_lines += 1
|
||||
|
||||
# write lines from top to bottom so later lines overwrite earlier
|
||||
# lines, note [xA/[xB stop at terminal boundaries
|
||||
for i, line in enumerate(lines):
|
||||
# move cursor, clear line, disable/reenable line wrapping
|
||||
sys.stdout.write('\r')
|
||||
if len(lines)-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dA' % (len(lines)-1-i))
|
||||
sys.stdout.write('\x1b[K')
|
||||
sys.stdout.write('\x1b[?7l')
|
||||
sys.stdout.write(line)
|
||||
sys.stdout.write('\x1b[?7h')
|
||||
if len(lines)-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dB' % (len(lines)-1-i))
|
||||
sys.stdout.flush()
|
||||
|
||||
# parse different data representations
|
||||
def dat(x, *args):
|
||||
try:
|
||||
@@ -709,7 +818,7 @@ def partition_squarify(children, total, x, y, width, height, *,
|
||||
i = j
|
||||
|
||||
|
||||
def main(csv_paths, *,
|
||||
def main_(f, csv_paths, *,
|
||||
by=None,
|
||||
fields=None,
|
||||
defines=[],
|
||||
@@ -731,6 +840,12 @@ def main(csv_paths, *,
|
||||
label=False,
|
||||
no_label=False,
|
||||
**args):
|
||||
# give f an writeln function
|
||||
def writeln(s=''):
|
||||
f.write(s)
|
||||
f.write('\n')
|
||||
f.writeln = writeln
|
||||
|
||||
# figure out what color should be
|
||||
if color == 'auto':
|
||||
color = sys.stdout.isatty()
|
||||
@@ -761,20 +876,20 @@ def main(csv_paths, *,
|
||||
# figure out width/height
|
||||
if width is None:
|
||||
width_ = min(80, shutil.get_terminal_size((80, 5))[0])
|
||||
elif width:
|
||||
elif width > 0:
|
||||
width_ = width
|
||||
else:
|
||||
width_ = shutil.get_terminal_size((80, 5))[0]
|
||||
width_ = max(0, shutil.get_terminal_size((80, 5))[0] + width)
|
||||
|
||||
if height is None:
|
||||
height_ = (2
|
||||
if not no_header
|
||||
and (title is not None or not no_stats)
|
||||
else 1)
|
||||
elif height:
|
||||
elif height > 0:
|
||||
height_ = height
|
||||
else:
|
||||
height_ = shutil.get_terminal_size((80, 5))[1] - 1
|
||||
height_ = max(0, shutil.get_terminal_size((80, 5))[1] + height)
|
||||
|
||||
# first collect results from CSV files
|
||||
fields_, results = collect(csv_paths, defines)
|
||||
@@ -1001,19 +1116,70 @@ def main(csv_paths, *,
|
||||
stat['mean'], stat['stddev'],
|
||||
stat['min'], stat['max'])
|
||||
if title and not no_stats:
|
||||
print('%s%*s%s' % (
|
||||
f.writeln('%s%*s%s' % (
|
||||
title_,
|
||||
max(width_-len(stat_)-len(title_), 0), ' ',
|
||||
stat_))
|
||||
elif title:
|
||||
print(title_)
|
||||
f.writeln(title_)
|
||||
elif not no_stats:
|
||||
print(stat_)
|
||||
f.writeln(stat_)
|
||||
|
||||
# draw canvas
|
||||
for row in range(canvas.height//canvas.yscale):
|
||||
line = canvas.draw(row)
|
||||
print(line)
|
||||
f.writeln(line)
|
||||
|
||||
|
||||
def main(csv_paths, *,
|
||||
height=None,
|
||||
keep_open=False,
|
||||
head=False,
|
||||
cat=False,
|
||||
sleep=False,
|
||||
**args):
|
||||
# keep-open?
|
||||
if keep_open:
|
||||
try:
|
||||
while True:
|
||||
# register inotify before running the command, this avoids
|
||||
# modification race conditions
|
||||
if Inotify:
|
||||
inotify = Inotify(csv_paths)
|
||||
|
||||
if cat:
|
||||
main_(sys.stdout, csv_paths,
|
||||
# make space for shell prompt
|
||||
height=height if height is not False else -1,
|
||||
**args)
|
||||
else:
|
||||
ring = RingIO(head=head)
|
||||
main_(ring, csv_paths,
|
||||
height=height if height is not False else 0,
|
||||
**args)
|
||||
ring.draw()
|
||||
|
||||
# try to inotifywait
|
||||
if Inotify:
|
||||
ptime = time.time()
|
||||
inotify.read()
|
||||
inotify.close()
|
||||
# sleep a minimum amount of time to avoid flickering
|
||||
time.sleep(max(0, (sleep or 0.01) - (time.time()-ptime)))
|
||||
else:
|
||||
time.sleep(sleep or 2)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
if not cat:
|
||||
sys.stdout.write('\n')
|
||||
|
||||
# single-pass?
|
||||
else:
|
||||
main_(sys.stdout, csv_paths,
|
||||
# make space for shell prompt
|
||||
height=height if height is not False else -1,
|
||||
**args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -1103,14 +1269,15 @@ if __name__ == "__main__":
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Width in columns. 0 uses the terminal width. Defaults to "
|
||||
"min(terminal, 80).")
|
||||
help="Width in columns. <=0 uses the terminal width. Defaults "
|
||||
"to min(terminal, 80).")
|
||||
parser.add_argument(
|
||||
'-H', '--height',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Height in rows. 0 uses the terminal height. Defaults to 1.")
|
||||
const=False,
|
||||
help="Height in rows. <=0 uses the terminal height. Defaults "
|
||||
"to 1.")
|
||||
parser.add_argument(
|
||||
'--no-header',
|
||||
action='store_true',
|
||||
@@ -1198,6 +1365,23 @@ if __name__ == "__main__":
|
||||
'--no-label',
|
||||
action='store_true',
|
||||
help="Don't render any labels.")
|
||||
parser.add_argument(
|
||||
'-k', '--keep-open',
|
||||
action='store_true',
|
||||
help="Continue to open and redraw the CSV files in a loop.")
|
||||
parser.add_argument(
|
||||
'-^', '--head',
|
||||
action='store_true',
|
||||
help="Show the first n lines.")
|
||||
parser.add_argument(
|
||||
'-z', '--cat',
|
||||
action='store_true',
|
||||
help="Pipe directly to stdout.")
|
||||
parser.add_argument(
|
||||
'-s', '--sleep',
|
||||
type=float,
|
||||
help="Time in seconds to sleep between redraws when running "
|
||||
"with -k. Defaults to 2 seconds.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
Reference in New Issue
Block a user