Created
May 24, 2021 05:01
-
-
Save apergos/2e4ad0396c3442c4133e1351c130ea19 to your computer and use it in GitHub Desktop.
convert an asciicast file to a format nicer for hand editing, and back as well
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
''' | |
the most wordy python script ever. this could probably be done in 2 lines | |
of awek but I was too lazy to look up the details. | |
convert an asciicast file ([timestamp, type, text-string]) to one | |
with [time-since-last-entry, type, text-string] entries or vice versa, | |
so that an asciicast file can be converted to interval format, lines | |
removed that are mistakes or unwanted, and then converted back | |
for eventual conversion to e.g. svg via termtosvg. | |
''' | |
import sys | |
import getopt | |
import re | |
from decimal import Decimal | |
def usage(message=None): | |
''' | |
display a nice usage message along with an optional message | |
describing an error | |
''' | |
if message: | |
sys.stderr.write(message + "\n") | |
usage_message = """Usage: $0 --in <path> --out <path> --write rel|abs | |
[--verbose] | |
or: $0 --help | |
Converts an asciicast file from absolute timestamp format to relative, so that | |
the timestamp in each entry is the number of seconds, possibly fractional, since | |
the last entry; this makes editing the converted file to remove mistaken entries | |
much wimpler, as the problem entries can simply be deleted without affecting | |
the rest of the file. | |
When manual editing is complete, the resulting file can be converted from | |
relative to absolute timestamp format for use with termtosvg or similar programs. | |
Arguments: | |
--in (-i): path to input file | |
--out (-o): path to output file | |
--write (-w): if "rel" is specified, the input is expected to contain absolute | |
timestamps i.e. timestamps relative to the beginning of the | |
recording session, and it will be converted to relative timestamps, | |
i.e. each entry relative to the previous entry. | |
if "abs" is specified, the input is expected to contain relative | |
timestamps and they will be converted to absolute timestamps. | |
--verbose (-v): write some progress messages some day | |
--help (-h): show this help message | |
""" | |
sys.stderr.write(usage_message) | |
sys.exit(1) | |
def get_default_opts(): | |
''' | |
initialize args with default values and return them | |
''' | |
args = {'infile': None, 'outfile': None, 'timestamp': None, 'verbose': False} | |
return args | |
def process_opts(): | |
''' | |
get command-line args and values, falling back to defaults | |
where needed, whining about bad args | |
''' | |
try: | |
(options, remainder) = getopt.gnu_getopt( | |
sys.argv[1:], "i:o:w:vh", ["in=", "out=", "write=", "verbose", "help"]) | |
except getopt.GetoptError as err: | |
usage("Unknown option specified: " + str(err)) | |
args = get_default_opts() | |
for (opt, val) in options: | |
if opt in ["-i", "--in"]: | |
args['infile'] = val | |
elif opt in ["-o", "--out"]: | |
args['outfile'] = val | |
elif opt in ["-w", "--write"]: | |
args['timestamp'] = val | |
elif opt in ["-v", "--verbose"]: | |
args['verbose'] = True | |
elif opt in ["-h", "--help"]: | |
usage('Help for this script\n') | |
else: | |
usage("Unknown option specified: <%s>" % opt) | |
if remainder: | |
usage("Unknown option(s) specified: {opt}".format(opt=remainder[0])) | |
check_opts(args) | |
return args | |
def check_opts(args): | |
''' | |
whine if mandatory args not supplied | |
''' | |
for key, value in {'infile': 'in', 'outfile': 'out', 'timestamp': 'write'}.items(): | |
if key not in args or not args[key]: | |
usage("Mandatory argument '{name}' not specified\n".format(name=value)) | |
if args['timestamp'] not in ["rel", "abs"]: | |
usage("'timestamp' arg must be one of 'rel' or 'abs'") | |
def convert_line(line, tstype, old_ts): | |
''' | |
given a line that ought to be of format [timestamp, "i"|"o", string-value-here], | |
and the timestamp from the previous entry in the unconverted format, convert the | |
timestamp in this line to the given tstype, reconstruct the line, and return it | |
if the line is not of that format, the line will be returned as is | |
''' | |
if ',' not in line: | |
print("no comma") | |
return line | |
current_ts, rest = line.split(',', 1) | |
if not current_ts.startswith('['): | |
return line | |
current_ts = current_ts[1:] | |
if not re.match(r"^\d*[.]\d*$", current_ts): | |
return line | |
if tstype == 'rel': | |
# abs to rel timestamps | |
new_ts = Decimal(current_ts) - Decimal(old_ts) | |
ts_to_save = current_ts | |
else: | |
# rel to abs timestamps | |
new_ts = Decimal(current_ts) + Decimal(old_ts) | |
ts_to_save = str(new_ts) | |
new_ts_string = "{:.6f}".format(new_ts) | |
return ts_to_save, '[' + new_ts_string + ',' + rest | |
def convert(infh, outfh, tstype): | |
''' | |
actually do the conversion, given the conversion type ('rel' or 'abs') | |
and an open input and output file handle to read from/write to. | |
''' | |
old_ts = 0 | |
while True: | |
line = infh.readline() | |
if not line: | |
break | |
if not line.startswith('['): | |
outfh.write(line) | |
else: | |
ts_from_line, new_line = convert_line(line, tstype, old_ts) | |
outfh.write(new_line) | |
old_ts = ts_from_line | |
def do_main(): | |
'''entry point''' | |
args = process_opts() | |
with open(args['infile'], "r") as infh: | |
with open(args['outfile'], "w") as outfh: | |
convert(infh, outfh, args['timestamp']) | |
if __name__ == '__main__': | |
do_main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment