File: teletype.py 1 #!/usr/bin/python 2 3 # The MIT License (MIT) 4 # 5 # Copyright (c) 2026 pacman64 6 # 7 # Permission is hereby granted, free of charge, to any person obtaining a copy 8 # of this software and associated documentation files (the "Software"), to deal 9 # in the Software without restriction, including without limitation the rights 10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 # copies of the Software, and to permit persons to whom the Software is 12 # furnished to do so, subject to the following conditions: 13 # 14 # The above copyright notice and this permission notice shall be included in 15 # all copies or substantial portions of the Software. 16 # 17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 # SOFTWARE. 24 25 26 from itertools import islice 27 from sys import argv, exit, stderr, stdin, stdout 28 from time import sleep 29 30 31 info = ''' 32 teletype [options...] [files...] 33 34 Simulate the cadence of old-fashioned teletype machines, by slowing down 35 the output of ASCII/UTF-8 symbols from the inputs given. 36 37 All options can start with either a single or a double leading dash: 38 39 -h, -help show this help message 40 ''' 41 42 43 def run_teletype(src): 44 'Emit data runes with a delay for each symbol.' 45 46 prev = '\n' 47 48 for line in src: 49 # ignore trailing LF/CRLF from lines 50 line = line.rstrip('\r\n').rstrip('\n') 51 52 # emit runes with a delay, which is the whole point of this script; 53 # the exception is ANSI-style runs of bytes, which are emitted all 54 # together 55 ansi = False 56 for e in list(line): 57 # an escape byte marks the start of an ANSI-style sequence 58 if e == '\x1b': 59 ansi = True 60 61 if not ansi: 62 # delay each visible item's appeareance by 15 ms 63 sleep(0.015) 64 65 # an `m` means the end of an ANSI-style sequence, even if there 66 # wasn't one preceding it 67 if e == 'm': 68 ansi = False 69 70 # emit the item, ensuring it shows right away 71 stdout.write(e) 72 stdout.flush() 73 74 # time-bunch line-feeds together, by waiting only before the first 75 # one in its run of line-feed bytes 76 if prev != '': 77 # wait for 750 ms before ending the line(s) 78 sleep(0.75) 79 prev = line 80 81 # ensure a line-feed ends each line, even the last one 82 stdout.write('\n') 83 stdout.flush() 84 85 86 try: 87 # handle the help option, if given 88 if len(argv) > 1 and argv[1] in ('-h', '--h', '-help', '--help'): 89 print(info.strip()) 90 exit(0) 91 92 # ignore leading end-of-options `--`, if present 93 skip = 2 if len(argv) > 1 and argv[1] == '--' else 1 94 95 # handle all named inputs, in the order given 96 for name in islice(argv, skip, None): 97 with open(name, encoding='utf-8') as inp: 98 run_teletype(inp) 99 100 # read from stdin, if no named inputs were given 101 if len(argv) - skip < 1: 102 run_teletype(stdin) 103 except BrokenPipeError: 104 # quit quietly, instead of showing a confusing error message 105 stderr.close() 106 except KeyboardInterrupt: 107 exit(2) 108 except Exception as e: 109 # chances are, func `open` failed because some file given doesn't exist 110 print(str(e), file=stderr) 111 exit(1)