-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathgenbar
More file actions
executable file
·146 lines (115 loc) · 4.96 KB
/
genbar
File metadata and controls
executable file
·146 lines (115 loc) · 4.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#!/usr/bin/env python3
"""Generate a bar from stdin
This script asynchronously runs each command specified in stdin and
replaces each command with its respective output and outputs it on
every newline.
Commands are specified using the <<cmd>> format. Each must continuously
run and output every few seconds. Commands must flush their buffer.
"""
from subprocess import Popen, PIPE
from threading import Thread
import re
import shlex
import signal
import sys
import os
import argparse
try:
ENV_PATH = os.environ.copy()
ENV_PATH['PATH'] = ENV_PATH['TENDERBLOCKS'] + ':' + ENV_PATH['PATH']
except KeyError:
ENV_PATH = os.environ
class LineGen(object):
""" Generate line with asynchronous outputs of commands """
def __init__(self, base, delims, settings):
# mini util function for recording m and returning '{}'
def repl(m):
self.cmds.append(m.group(1))
return '{}'
# set args to corresponding attributes
self.LDELIM, self.RDELIM = delims[0], delims[1]
self.base = base
# for extra settings
self.settings = settings
# empty default args list
self.cmds = []
# escape all '{' and '}' characters
self.template = re.sub(r'[{}]', lambda m: m.group()*2, self.base)
# replace all cmds with '{}' and record them to cmds list
# NOTE: SIDE EFFECT: builds cmds list
self.template = re.sub(
self.LDELIM + r'([^'+self.RDELIM+']*)' + self.RDELIM,
repl, self.template)
# unescape '{' & '}' in cmds
self.cmds = [re.sub(r'{{', '{', c) for c in self.cmds]
self.cmds = [re.sub(r'}}', '}', c) for c in self.cmds]
# initialize lists with null values
self.procs = [None]*len(self.cmds)
self.outs = ['']*len(self.cmds)
# handle interupt signals (ignoring given params)
signal.signal(signal.SIGINT, lambda *_: self.stop())
signal.signal(signal.SIGTERM, lambda *_: self.stop())
signal.signal(signal.SIGPIPE, lambda *_: self.stop())
def start(self):
""" Start processes """
# mini util function for assigning processes to threads
def assig_p(id):
# macro for Popen (gets repetitive without this)
def openP(): return Popen(curcmd, stdin=lastout, stdout=PIPE,
universal_newlines=True, env=ENV_PATH,
shell=False)
# start process
# Apparently a lot of people absolutely hate the use of
# `shell=True` so we parse pipes ourselves here
split = shlex.split(self.cmds[id])
curcmd = []
lastout = None
# iterate through elements of the input command
for i, a in enumerate(split):
# intermediate commands get piped into eachother
if a == '|':
lastout = openP().stdout
curcmd = []
continue
else:
curcmd.append(a)
# if it's the last command
if i == len(split)-1:
self.procs[id] = openP()
# wait for new stdout line from command output
for line in self.procs[id].stdout:
self.outs[id] = line if line[-1] != '\n' else line[:-1]
print(self.template.format(*self.outs), flush=True)
# assign each cmd process to a thread
for i in range(len(self.cmds)):
Thread(target=assig_p, args=(i,)).start()
def stop(self):
""" Terminate each process """
print('Stopping...', file=sys.stderr)
for i, p in enumerate(self.procs):
print(' - Stopping proc %s: \'%s\''
% (i, ' '.join(p.args)), file=sys.stderr)
p.stdout.close()
# NOTE: We technically dont need terminate
# because stdout.close sends SIGPIPE already
p.terminate()
print('Done', [p.poll() for p in self.procs], file=sys.stderr)
if __name__ == '__main__':
# parse args
parser = argparse.ArgumentParser(description='Generate string to stdout'
' from asynchronous command outputs.'
' Replace the string between the begin'
' delimiter and end delimiter with the'
' command it contains\n'
'Default delimiters are << and >>'
' respectively.'
)
parser.add_argument('-b', action='store', dest='begin', default='<<')
parser.add_argument('-e', action='store', dest='end', default='>>')
settings = parser.parse_args()
# args parsed
# create linegen object
linegen = LineGen(sys.stdin.read(),
(settings.begin, settings.end), settings)
# start linegen processes
linegen.start()