Added teepipe.py and watch.py
This commit is contained in:
parent
3a33c3795b
commit
b2a2cc9a19
@ -36,6 +36,7 @@ PERF_SCRIPT = ['./scripts/perf.py']
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
# allow '-' for stdin/stdout
|
||||
if path == '-':
|
||||
if mode == 'r':
|
||||
return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
|
||||
|
@ -125,6 +125,7 @@ class CodeResult(co.namedtuple('CodeResult', [
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
# allow '-' for stdin/stdout
|
||||
if path == '-':
|
||||
if mode == 'r':
|
||||
return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
|
||||
|
@ -200,6 +200,7 @@ class CovResult(co.namedtuple('CovResult', [
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
# allow '-' for stdin/stdout
|
||||
if path == '-':
|
||||
if mode == 'r':
|
||||
return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
|
||||
|
@ -125,6 +125,7 @@ class DataResult(co.namedtuple('DataResult', [
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
# allow '-' for stdin/stdout
|
||||
if path == '-':
|
||||
if mode == 'r':
|
||||
return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
|
||||
|
@ -146,6 +146,7 @@ class PerfResult(co.namedtuple('PerfResult', [
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
# allow '-' for stdin/stdout
|
||||
if path == '-':
|
||||
if mode == 'r':
|
||||
return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
|
||||
|
@ -132,6 +132,7 @@ class PerfBdResult(co.namedtuple('PerfBdResult', [
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
# allow '-' for stdin/stdout
|
||||
if path == '-':
|
||||
if mode == 'r':
|
||||
return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
|
||||
|
@ -18,6 +18,12 @@ import os
|
||||
import shutil
|
||||
import time
|
||||
|
||||
try:
|
||||
import inotify_simple
|
||||
except ModuleNotFoundError:
|
||||
inotify_simple = None
|
||||
|
||||
|
||||
COLORS = [
|
||||
'1;34', # bold blue
|
||||
'1;31', # bold red
|
||||
@ -79,6 +85,7 @@ def si(x, w=4):
|
||||
return '%s%s%s' % ('-' if x < 0 else '', s, SI_PREFIXES[p])
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
# allow '-' for stdin/stdout
|
||||
if path == '-':
|
||||
if mode == 'r':
|
||||
return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
|
||||
@ -87,6 +94,31 @@ def openio(path, mode='r', buffering=-1):
|
||||
else:
|
||||
return open(path, mode, buffering)
|
||||
|
||||
def inotifywait(paths):
|
||||
# wait for interesting events
|
||||
inotify = inotify_simple.INotify()
|
||||
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):
|
||||
inotify.add_watch(dir, flags)
|
||||
for f in files:
|
||||
inotify.add_watch(os.path.join(dir, f), flags)
|
||||
else:
|
||||
inotify.add_watch(path, flags)
|
||||
|
||||
# wait for event
|
||||
inotify.read()
|
||||
|
||||
class LinesIO:
|
||||
def __init__(self, maxlen=None):
|
||||
self.maxlen = maxlen
|
||||
@ -118,28 +150,41 @@ class LinesIO:
|
||||
if maxlen != self.lines.maxlen:
|
||||
self.lines = co.deque(self.lines, maxlen=maxlen)
|
||||
|
||||
last_lines = 1
|
||||
canvas_lines = 1
|
||||
def draw(self):
|
||||
# did terminal size change?
|
||||
if self.maxlen == 0:
|
||||
self.resize(0)
|
||||
|
||||
# first thing first, give ourself a canvas
|
||||
while LinesIO.last_lines < len(self.lines):
|
||||
while LinesIO.canvas_lines < len(self.lines):
|
||||
sys.stdout.write('\n')
|
||||
LinesIO.last_lines += 1
|
||||
LinesIO.canvas_lines += 1
|
||||
|
||||
for j, line in enumerate(self.lines):
|
||||
# clear the bottom of the canvas if we shrink
|
||||
shrink = LinesIO.canvas_lines - len(self.lines)
|
||||
if shrink > 0:
|
||||
for i in range(shrink):
|
||||
sys.stdout.write('\r')
|
||||
if shrink-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dA' % (shrink-1-i))
|
||||
sys.stdout.write('\x1b[K')
|
||||
if shrink-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dB' % (shrink-1-i))
|
||||
sys.stdout.write('\x1b[%dA' % shrink)
|
||||
LinesIO.canvas_lines = len(self.lines)
|
||||
|
||||
for i, line in enumerate(self.lines):
|
||||
# move cursor, clear line, disable/reenable line wrapping
|
||||
sys.stdout.write('\r')
|
||||
if len(self.lines)-1-j > 0:
|
||||
sys.stdout.write('\x1b[%dA' % (len(self.lines)-1-j))
|
||||
if len(self.lines)-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dA' % (len(self.lines)-1-i))
|
||||
sys.stdout.write('\x1b[K')
|
||||
sys.stdout.write('\x1b[?7l')
|
||||
sys.stdout.write(line)
|
||||
sys.stdout.write('\x1b[?7h')
|
||||
if len(self.lines)-1-j > 0:
|
||||
sys.stdout.write('\x1b[%dB' % (len(self.lines)-1-j))
|
||||
if len(self.lines)-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dB' % (len(self.lines)-1-i))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
@ -697,8 +742,16 @@ def main(csv_paths, *,
|
||||
ring = LinesIO()
|
||||
draw(ring)
|
||||
ring.draw()
|
||||
# don't just flood open calls
|
||||
time.sleep(sleep or 0.1)
|
||||
|
||||
# try to inotifywait
|
||||
if inotify_simple is not None:
|
||||
ptime = time.time()
|
||||
inotifywait(csv_paths)
|
||||
# sleep for a minimum amount of time, this helps issues
|
||||
# around rapidly updating files
|
||||
time.sleep(max(0, (sleep or 0.01) - (time.time()-ptime)))
|
||||
else:
|
||||
time.sleep(sleep or 0.1)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
|
@ -43,6 +43,7 @@ LEXEMES = {
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
# allow '-' for stdin/stdout
|
||||
if path == '-':
|
||||
if mode == 'r':
|
||||
return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
|
||||
|
@ -119,6 +119,7 @@ class StackResult(co.namedtuple('StackResult', [
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
# allow '-' for stdin/stdout
|
||||
if path == '-':
|
||||
if mode == 'r':
|
||||
return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
|
||||
|
@ -119,6 +119,7 @@ class StructResult(co.namedtuple('StructResult', ['file', 'struct', 'size'])):
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
# allow '-' for stdin/stdout
|
||||
if path == '-':
|
||||
if mode == 'r':
|
||||
return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
|
||||
|
@ -546,6 +546,7 @@ def table(Result, results, diff_results=None, *,
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
# allow '-' for stdin/stdout
|
||||
if path == '-':
|
||||
if mode == 'r':
|
||||
return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
|
||||
|
@ -12,12 +12,15 @@
|
||||
import collections as co
|
||||
import io
|
||||
import os
|
||||
import select
|
||||
import shutil
|
||||
import sys
|
||||
import threading as th
|
||||
import time
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
# allow '-' for stdin/stdout
|
||||
if path == '-':
|
||||
if mode == 'r':
|
||||
return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
|
||||
@ -57,48 +60,71 @@ class LinesIO:
|
||||
if maxlen != self.lines.maxlen:
|
||||
self.lines = co.deque(self.lines, maxlen=maxlen)
|
||||
|
||||
last_lines = 1
|
||||
canvas_lines = 1
|
||||
def draw(self):
|
||||
# did terminal size change?
|
||||
if self.maxlen == 0:
|
||||
self.resize(0)
|
||||
|
||||
# first thing first, give ourself a canvas
|
||||
while LinesIO.last_lines < len(self.lines):
|
||||
while LinesIO.canvas_lines < len(self.lines):
|
||||
sys.stdout.write('\n')
|
||||
LinesIO.last_lines += 1
|
||||
LinesIO.canvas_lines += 1
|
||||
|
||||
for j, line in enumerate(self.lines):
|
||||
# clear the bottom of the canvas if we shrink
|
||||
shrink = LinesIO.canvas_lines - len(self.lines)
|
||||
if shrink > 0:
|
||||
for i in range(shrink):
|
||||
sys.stdout.write('\r')
|
||||
if shrink-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dA' % (shrink-1-i))
|
||||
sys.stdout.write('\x1b[K')
|
||||
if shrink-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dB' % (shrink-1-i))
|
||||
sys.stdout.write('\x1b[%dA' % shrink)
|
||||
LinesIO.canvas_lines = len(self.lines)
|
||||
|
||||
for i, line in enumerate(self.lines):
|
||||
# move cursor, clear line, disable/reenable line wrapping
|
||||
sys.stdout.write('\r')
|
||||
if len(self.lines)-1-j > 0:
|
||||
sys.stdout.write('\x1b[%dA' % (len(self.lines)-1-j))
|
||||
if len(self.lines)-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dA' % (len(self.lines)-1-i))
|
||||
sys.stdout.write('\x1b[K')
|
||||
sys.stdout.write('\x1b[?7l')
|
||||
sys.stdout.write(line)
|
||||
sys.stdout.write('\x1b[?7h')
|
||||
if len(self.lines)-1-j > 0:
|
||||
sys.stdout.write('\x1b[%dB' % (len(self.lines)-1-j))
|
||||
if len(self.lines)-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dB' % (len(self.lines)-1-i))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def main(path='-', *, lines=5, cat=False, sleep=0.01, keep_open=False):
|
||||
def main(path='-', *, lines=5, cat=False, sleep=None, keep_open=False):
|
||||
if cat:
|
||||
ring = sys.stdout
|
||||
else:
|
||||
ring = LinesIO(lines)
|
||||
|
||||
ptime = time.time()
|
||||
# if sleep print in background thread to avoid getting stuck in a read call
|
||||
event = th.Event()
|
||||
lock = th.Lock()
|
||||
if not cat:
|
||||
done = False
|
||||
def background():
|
||||
while not done:
|
||||
event.wait()
|
||||
event.clear()
|
||||
with lock:
|
||||
ring.draw()
|
||||
time.sleep(sleep or 0.01)
|
||||
th.Thread(target=background, daemon=True).start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
with openio(path) as f:
|
||||
for line in f:
|
||||
ring.write(line)
|
||||
|
||||
# need to redraw?
|
||||
if not cat and time.time()-ptime >= sleep:
|
||||
ring.draw()
|
||||
ptime = time.time()
|
||||
with lock:
|
||||
ring.write(line)
|
||||
event.set()
|
||||
|
||||
if not keep_open:
|
||||
break
|
||||
@ -111,6 +137,8 @@ def main(path='-', *, lines=5, cat=False, sleep=0.01, keep_open=False):
|
||||
pass
|
||||
|
||||
if not cat:
|
||||
done = True
|
||||
lock.acquire() # avoids https://bugs.python.org/issue42717
|
||||
sys.stdout.write('\n')
|
||||
|
||||
|
||||
|
73
scripts/teepipe.py
Executable file
73
scripts/teepipe.py
Executable file
@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# tee, but for pipes
|
||||
#
|
||||
# Example:
|
||||
# ./scripts/tee.py in_pipe out_pipe1 out_pipe2
|
||||
#
|
||||
# Copyright (c) 2022, The littlefs authors.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
|
||||
import os
|
||||
import io
|
||||
import time
|
||||
import sys
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
# allow '-' for stdin/stdout
|
||||
if path == '-':
|
||||
if mode == 'r':
|
||||
return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
|
||||
else:
|
||||
return os.fdopen(os.dup(sys.stdout.fileno()), mode, buffering)
|
||||
else:
|
||||
return open(path, mode, buffering)
|
||||
|
||||
def main(in_path, out_paths, *, keep_open=False):
|
||||
out_pipes = [openio(p, 'wb', 0) for p in out_paths]
|
||||
try:
|
||||
with openio(in_path, 'rb', 0) as f:
|
||||
while True:
|
||||
buf = f.read(io.DEFAULT_BUFFER_SIZE)
|
||||
if not buf:
|
||||
if not keep_open:
|
||||
break
|
||||
# don't just flood reads
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
|
||||
for p in out_pipes:
|
||||
try:
|
||||
p.write(buf)
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
except FileNotFoundError as e:
|
||||
print("error: file not found %r" % in_path)
|
||||
sys.exit(-1)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(
|
||||
description="tee, but for pipes.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'in_path',
|
||||
help="Path to read from.")
|
||||
parser.add_argument(
|
||||
'out_paths',
|
||||
nargs='+',
|
||||
help="Path to write to.")
|
||||
parser.add_argument(
|
||||
'-k', '--keep-open',
|
||||
action='store_true',
|
||||
help="Reopen the pipe on EOF, useful when multiple "
|
||||
"processes are writing.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
@ -36,6 +36,7 @@ PERF_SCRIPT = ['./scripts/perf.py']
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
# allow '-' for stdin/stdout
|
||||
if path == '-':
|
||||
if mode == 'r':
|
||||
return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
|
||||
|
@ -17,10 +17,10 @@ import math as m
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import threading as th
|
||||
import time
|
||||
|
||||
|
||||
|
||||
CHARS = 'rpe.'
|
||||
COLORS = ['42', '45', '44', '']
|
||||
|
||||
@ -42,6 +42,7 @@ CHARS_BRAILLE = (
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
# allow '-' for stdin/stdout
|
||||
if path == '-':
|
||||
if mode == 'r':
|
||||
return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
|
||||
@ -81,32 +82,44 @@ class LinesIO:
|
||||
if maxlen != self.lines.maxlen:
|
||||
self.lines = co.deque(self.lines, maxlen=maxlen)
|
||||
|
||||
last_lines = 1
|
||||
canvas_lines = 1
|
||||
def draw(self):
|
||||
# did terminal size change?
|
||||
if self.maxlen == 0:
|
||||
self.resize(0)
|
||||
|
||||
# first thing first, give ourself a canvas
|
||||
while LinesIO.last_lines < len(self.lines):
|
||||
while LinesIO.canvas_lines < len(self.lines):
|
||||
sys.stdout.write('\n')
|
||||
LinesIO.last_lines += 1
|
||||
LinesIO.canvas_lines += 1
|
||||
|
||||
for j, line in enumerate(self.lines):
|
||||
# clear the bottom of the canvas if we shrink
|
||||
shrink = LinesIO.canvas_lines - len(self.lines)
|
||||
if shrink > 0:
|
||||
for i in range(shrink):
|
||||
sys.stdout.write('\r')
|
||||
if shrink-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dA' % (shrink-1-i))
|
||||
sys.stdout.write('\x1b[K')
|
||||
if shrink-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dB' % (shrink-1-i))
|
||||
sys.stdout.write('\x1b[%dA' % shrink)
|
||||
LinesIO.canvas_lines = len(self.lines)
|
||||
|
||||
for i, line in enumerate(self.lines):
|
||||
# move cursor, clear line, disable/reenable line wrapping
|
||||
sys.stdout.write('\r')
|
||||
if len(self.lines)-1-j > 0:
|
||||
sys.stdout.write('\x1b[%dA' % (len(self.lines)-1-j))
|
||||
if len(self.lines)-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dA' % (len(self.lines)-1-i))
|
||||
sys.stdout.write('\x1b[K')
|
||||
sys.stdout.write('\x1b[?7l')
|
||||
sys.stdout.write(line)
|
||||
sys.stdout.write('\x1b[?7h')
|
||||
if len(self.lines)-1-j > 0:
|
||||
sys.stdout.write('\x1b[%dB' % (len(self.lines)-1-j))
|
||||
if len(self.lines)-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dB' % (len(self.lines)-1-i))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
|
||||
# space filling Hilbert-curve
|
||||
#
|
||||
# note we memoize the last curve since this is a bit expensive
|
||||
@ -801,23 +814,39 @@ def main(path='-', *,
|
||||
else:
|
||||
ring = LinesIO(lines)
|
||||
|
||||
ptime = time.time()
|
||||
# if sleep print in background thread to avoid getting stuck in a read call
|
||||
event = th.Event()
|
||||
lock = th.Lock()
|
||||
if sleep:
|
||||
done = False
|
||||
def background():
|
||||
while not done:
|
||||
event.wait()
|
||||
event.clear()
|
||||
with lock:
|
||||
draw(ring)
|
||||
if not cat:
|
||||
ring.draw()
|
||||
time.sleep(sleep or 0.01)
|
||||
th.Thread(target=background, daemon=True).start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
with openio(path) as f:
|
||||
changed = 0
|
||||
for line in f:
|
||||
changed += parse(line)
|
||||
with lock:
|
||||
changed += parse(line)
|
||||
|
||||
# need to redraw?
|
||||
if (changed
|
||||
and (not coalesce or changed >= coalesce)
|
||||
and (not sleep or time.time()-ptime >= sleep)):
|
||||
draw(ring)
|
||||
if not cat:
|
||||
ring.draw()
|
||||
changed = 0
|
||||
ptime = time.time()
|
||||
# need to redraw?
|
||||
if changed and (not coalesce or changed >= coalesce):
|
||||
if sleep:
|
||||
event.set()
|
||||
else:
|
||||
draw(ring)
|
||||
if not cat:
|
||||
ring.draw()
|
||||
changed = 0
|
||||
|
||||
if not keep_open:
|
||||
break
|
||||
@ -829,6 +858,9 @@ def main(path='-', *,
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
if sleep:
|
||||
done = True
|
||||
lock.acquire() # avoids https://bugs.python.org/issue42717
|
||||
if not cat:
|
||||
sys.stdout.write('\n')
|
||||
|
||||
|
265
scripts/watch.py
Executable file
265
scripts/watch.py
Executable file
@ -0,0 +1,265 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Traditional watch command, but with higher resolution updates and a bit
|
||||
# different options/output format
|
||||
#
|
||||
# Example:
|
||||
# ./scripts/watch.py -s0.1 date
|
||||
#
|
||||
# Copyright (c) 2022, The littlefs authors.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
|
||||
import collections as co
|
||||
import errno
|
||||
import fcntl
|
||||
import io
|
||||
import os
|
||||
import pty
|
||||
import re
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess as sp
|
||||
import sys
|
||||
import termios
|
||||
import time
|
||||
|
||||
try:
|
||||
import inotify_simple
|
||||
except ModuleNotFoundError:
|
||||
inotify_simple = None
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
# allow '-' for stdin/stdout
|
||||
if path == '-':
|
||||
if mode == 'r':
|
||||
return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
|
||||
else:
|
||||
return os.fdopen(os.dup(sys.stdout.fileno()), mode, buffering)
|
||||
else:
|
||||
return open(path, mode, buffering)
|
||||
|
||||
def inotifywait(paths):
|
||||
# wait for interesting events
|
||||
inotify = inotify_simple.INotify()
|
||||
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):
|
||||
inotify.add_watch(dir, flags)
|
||||
for f in files:
|
||||
inotify.add_watch(os.path.join(dir, f), flags)
|
||||
else:
|
||||
inotify.add_watch(path, flags)
|
||||
|
||||
# wait for event
|
||||
inotify.read()
|
||||
|
||||
class LinesIO:
|
||||
def __init__(self, maxlen=None):
|
||||
self.maxlen = maxlen
|
||||
self.lines = co.deque(maxlen=maxlen)
|
||||
self.tail = io.StringIO()
|
||||
|
||||
# trigger automatic sizing
|
||||
if maxlen == 0:
|
||||
self.resize(0)
|
||||
|
||||
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)
|
||||
|
||||
# first thing first, give ourself a canvas
|
||||
while LinesIO.canvas_lines < len(self.lines):
|
||||
sys.stdout.write('\n')
|
||||
LinesIO.canvas_lines += 1
|
||||
|
||||
# clear the bottom of the canvas if we shrink
|
||||
shrink = LinesIO.canvas_lines - len(self.lines)
|
||||
if shrink > 0:
|
||||
for i in range(shrink):
|
||||
sys.stdout.write('\r')
|
||||
if shrink-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dA' % (shrink-1-i))
|
||||
sys.stdout.write('\x1b[K')
|
||||
if shrink-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dB' % (shrink-1-i))
|
||||
sys.stdout.write('\x1b[%dA' % shrink)
|
||||
LinesIO.canvas_lines = len(self.lines)
|
||||
|
||||
for i, line in enumerate(self.lines):
|
||||
# move cursor, clear line, disable/reenable line wrapping
|
||||
sys.stdout.write('\r')
|
||||
if len(self.lines)-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dA' % (len(self.lines)-1-i))
|
||||
sys.stdout.write('\x1b[K')
|
||||
sys.stdout.write('\x1b[?7l')
|
||||
sys.stdout.write(line)
|
||||
sys.stdout.write('\x1b[?7h')
|
||||
if len(self.lines)-1-i > 0:
|
||||
sys.stdout.write('\x1b[%dB' % (len(self.lines)-1-i))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def main(command, *,
|
||||
lines=0,
|
||||
cat=False,
|
||||
sleep=None,
|
||||
keep_open=False,
|
||||
keep_open_paths=None,
|
||||
exit_on_error=False):
|
||||
returncode = 0
|
||||
try:
|
||||
while True:
|
||||
# reset ring each run
|
||||
if cat:
|
||||
ring = sys.stdout
|
||||
else:
|
||||
ring = LinesIO(lines)
|
||||
|
||||
try:
|
||||
# run the command under a pseudoterminal
|
||||
mpty, spty = pty.openpty()
|
||||
|
||||
# forward terminal size
|
||||
w, h = shutil.get_terminal_size((80, 5))
|
||||
if lines:
|
||||
h = lines
|
||||
fcntl.ioctl(spty, termios.TIOCSWINSZ,
|
||||
struct.pack('HHHH', h, w, 0, 0))
|
||||
|
||||
proc = sp.Popen(command,
|
||||
stdout=spty,
|
||||
stderr=spty,
|
||||
close_fds=False)
|
||||
os.close(spty)
|
||||
mpty = os.fdopen(mpty, 'r', 1)
|
||||
|
||||
while True:
|
||||
try:
|
||||
line = mpty.readline()
|
||||
except OSError as e:
|
||||
if e.errno != errno.EIO:
|
||||
raise
|
||||
break
|
||||
if not line:
|
||||
break
|
||||
|
||||
ring.write(line)
|
||||
if not cat:
|
||||
ring.draw()
|
||||
|
||||
mpty.close()
|
||||
proc.wait()
|
||||
if exit_on_error and proc.returncode != 0:
|
||||
returncode = proc.returncode
|
||||
break
|
||||
except OSError as e:
|
||||
if e.errno != errno.ETXTBSY:
|
||||
raise
|
||||
pass
|
||||
|
||||
# try to inotifywait
|
||||
if keep_open and inotify_simple is not None:
|
||||
if keep_open_paths:
|
||||
paths = set(keep_paths)
|
||||
else:
|
||||
# guess inotify paths from command
|
||||
paths = set()
|
||||
for p in command:
|
||||
for p in {
|
||||
p,
|
||||
re.sub('^-.', '', p),
|
||||
re.sub('^--[^=]+=', '', p)}:
|
||||
if p and os.path.exists(p):
|
||||
paths.add(p)
|
||||
ptime = time.time()
|
||||
inotifywait(paths)
|
||||
# sleep for a minimum amount of time, this helps issues around
|
||||
# rapidly updating files
|
||||
time.sleep(max(0, (sleep or 0.1) - (time.time()-ptime)))
|
||||
else:
|
||||
time.sleep(sleep or 0.1)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
if not cat:
|
||||
sys.stdout.write('\n')
|
||||
sys.exit(returncode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Traditional watch command, but with higher resolution "
|
||||
"updates and a bit different options/output format.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'command',
|
||||
nargs=argparse.REMAINDER,
|
||||
help="Command to run.")
|
||||
parser.add_argument(
|
||||
'-n', '--lines',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Show this many lines of history. 0 uses the terminal height. "
|
||||
"Defaults to 0.")
|
||||
parser.add_argument(
|
||||
'-z', '--cat',
|
||||
action='store_true',
|
||||
help="Pipe directly to stdout.")
|
||||
parser.add_argument(
|
||||
'-s', '--sleep',
|
||||
type=float,
|
||||
help="Seconds to sleep between runs. Defaults to 0.1.")
|
||||
parser.add_argument(
|
||||
'-k', '--keep-open',
|
||||
action='store_true',
|
||||
help="Try to use inotify to wait for changes.")
|
||||
parser.add_argument(
|
||||
'-K', '--keep-open-path',
|
||||
dest='keep_open_paths',
|
||||
action='append',
|
||||
help="Use this path for inotify. Defaults to guessing.")
|
||||
parser.add_argument(
|
||||
'-e', '--exit-on-error',
|
||||
action='store_true',
|
||||
help="Exit on error.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_args()).items()
|
||||
if v is not None}))
|
Loading…
x
Reference in New Issue
Block a user