Skip to content

Instantly share code, notes, and snippets.

@apergos
Created May 24, 2021 05:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save apergos/2e4ad0396c3442c4133e1351c130ea19 to your computer and use it in GitHub Desktop.
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
#!/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