From c8d49bda3d0270b3e27f4dece918f03a225a7ebe Mon Sep 17 00:00:00 2001 From: Noelle Leigh Date: Sun, 1 Jun 2025 20:56:06 -0400 Subject: [PATCH 01/15] Use r-strings for embedded train car art That fixes these SyntaxWarnings: tilde-train.py:60: SyntaxWarning: invalid escape sequence '\_' | | | |__/V\_| | tilde-train.py:76: SyntaxWarning: invalid escape sequence '\-' == - / \-/ \-----/ \-/ \ - == tilde-train.py:86: SyntaxWarning: invalid escape sequence '\-' - / \-/ \------/ \-/ \ - --- tilde-train.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tilde-train.py b/tilde-train.py index 21d8fa6..7982a03 100755 --- a/tilde-train.py +++ b/tilde-train.py @@ -53,7 +53,7 @@ print_train = False ## print train to file (instead of the screen scroll) train = [""]*max_y ## empty train of correct height. cars = [] -engine = """ ____ +engine = r""" ____ |____| ------------ | | === | ------ | ___| |__| |_____| | O | | @@ -65,7 +65,7 @@ engine = """ ____ //// \__/ \__/ \__/ \__/ """ engine = engine.split("\n") -caboose = """ || +caboose = r""" || ============= || =========| |========== | ---- ---- | @@ -77,7 +77,7 @@ caboose = """ || \__/ \__/ \__/ \__/ """ caboose = caboose.split("\n") -default_car = """ ---------------------------- +default_car = r""" ---------------------------- | | | YOUR TRAIN CAR HERE! | | Just create a | From 29a8ffa91ee19bfd2971678034813d459632d515 Mon Sep 17 00:00:00 2001 From: Noelle Leigh Date: Sun, 1 Jun 2025 20:59:25 -0400 Subject: [PATCH 02/15] Update details in file header for the fork. Also remove trailing whitespace in the header. --- tilde-train.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tilde-train.py b/tilde-train.py index 7982a03..a14a174 100755 --- a/tilde-train.py +++ b/tilde-train.py @@ -1,17 +1,16 @@ #!/usr/bin/env python3 -## _ _ _ _ _ _ -## | |_(_) |__| |___ | |_ _ _ __ _(_)_ _ -## | _| | / _` / -_)| _| '_/ _` | | ' \ +## _ _ _ _ _ _ +## | |_(_) |__| |___ | |_ _ _ __ _(_)_ _ +## | _| | / _` / -_)| _| '_/ _` | | ' \ ## \__|_|_\__,_\___(_)__|_| \__,_|_|_||_| ## ## tilde.train is an instance of TerminalTrain. It was originally developed -## by cmccabe on tilde.town but is now maintained in tildegit with the main -## development ocurring on rawtext.club. +## by cmccabe on tilde.town (https://tildegit.org/cmccabe/TerminalTrain) but is now +## maintained by vilmibm. ## -## If you want to contribute to code improvement, or if you have suggestions -## for cmccabe, create a pull request here: https://tildegit.org/cmccabe/TerminalTrain -## or email cmccabe at cmccabe@rawtext.club +## If you want to contribute code improvements, create a pull request here: +## https://git.tilde.town/vilmibm/tilde-train ## ## ----------------- ## @@ -27,7 +26,7 @@ ## * turn main loop into a function, so cmd line arg reader can call it (with -p) and quit. ## * figure out why tilde.train doesn't work in some terminals (sthg sthg unicode...) ## * BUGFIX-1 - something about inclusion default cars adding extra "links" to the train. -## * the -p (print train) option should print all cars, not limited to the max_cars value. +## * the -p (print train) option should print all cars, not limited to the max_cars value. ## * related to BUGFIX-1, that seems to impact spacers (links) between cars. ## * allow users to create multiple frames so their cars can be animated (difficulty=med+) ## * allow user configurable speed and number of train cars From 6d4e7c791481a511a78049bf3728aee1db0ce4fa Mon Sep 17 00:00:00 2001 From: Noelle Leigh Date: Sun, 1 Jun 2025 21:18:55 -0400 Subject: [PATCH 03/15] Rewrite car templates as list of strings This lets us trim all the trailing whitespace in the file automatically. --- tilde-train.py | 70 ++++++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/tilde-train.py b/tilde-train.py index a14a174..62d79b2 100755 --- a/tilde-train.py +++ b/tilde-train.py @@ -52,40 +52,42 @@ print_train = False ## print train to file (instead of the screen scroll) train = [""]*max_y ## empty train of correct height. cars = [] -engine = r""" ____ - |____| ------------ - | | === | ------ | - ___| |__| |_____| | O | | - | | | |__/V\_| | - [[ | | - | | ------------ | ~town | - |__|______________|__________| - //// / _\__/__\__/__\ / \ -//// \__/ \__/ \__/ \__/ """ -engine = engine.split("\n") +engine = [ + r" ____ ", + r" |____| ------------", + r" | | === | ------ |", + r" ___| |__| |_____| | O | |", + r" | | | |__/V\_| |", + r" [[ | |", + r" | | ------------ | ~town |", + r" |__|______________|__________|", + r" //// / _\__/__\__/__\ / \ ", + r"//// \__/ \__/ \__/ \__/ ", +] -caboose = r""" || - ============= || -=========| |========== - | ---- ---- | - | | | | | | - | ---- ---- | - | tilde.town railways | -==| |== -== - / \-/ \-----/ \-/ \ - == - \__/ \__/ \__/ \__/ """ -caboose = caboose.split("\n") - -default_car = r""" ---------------------------- -| | -| YOUR TRAIN CAR HERE! | -| Just create a | -| ~/.choochoo file! | -| __ __ __ __ | - - / \-/ \------/ \-/ \ - - \__/ \__/ \__/ \__/""" -default_car = default_car.split("\n") +caboose = [ + r" || ", + r" ============= || ", + r"=========| |========== ", + r" | ---- ---- | ", + r" | | | | | | ", + r" | ---- ---- | ", + r" | tilde.town railways | ", + r"==| |== ", + r"== - / \-/ \-----/ \-/ \ - == ", + r" \__/ \__/ \__/ \__/ ", +] +default_car = [ + r" ---------------------------- ", + r"| |", + r"| YOUR TRAIN CAR HERE! |", + r"| Just create a |", + r"| ~/.choochoo file! |", + r"| __ __ __ __ |", + r" - / \-/ \------/ \-/ \ - ", + r" \__/ \__/ \__/ \__/ ", +] def print_help(): print("") @@ -240,13 +242,13 @@ def print_all_cars(): choochoo_list = choochoo_string.split("\n") if len(choochoo_list) > max_y+1: continue ## the train car was too tall; skip it. - + car = validate_car(choochoo_list) ## printing is only a DEBUG feature. if car != 0: print("") print(fname + ":") print("\n".join(car)) ## print the car to stdout - + ## HOW TO CLOSE THE FILE HANDLE? fname.close(), close(fname), ...? except: pass; From 7a74050c5d3362228d0bccdf290b0250f779b5a4 Mon Sep 17 00:00:00 2001 From: Noelle Leigh Date: Sun, 1 Jun 2025 21:27:31 -0400 Subject: [PATCH 04/15] Rewrite print_help to a single string --- tilde-train.py | 53 +++++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/tilde-train.py b/tilde-train.py index 62d79b2..a706dd7 100755 --- a/tilde-train.py +++ b/tilde-train.py @@ -42,6 +42,7 @@ import curses from signal import signal, SIGINT import time ## allowing the loop steps of train animation to be slowed import string ## for input validation +from inspect import cleandoc traincarFN = ".choochoo" max_x = 35 ## max length of train car. @@ -90,30 +91,34 @@ default_car = [ ] def print_help(): - print("") - print("~ ~ Hooray! You've found the tilde.train! ~ ~") - print("") - print("To add your own car to a future train, create") - print("a .choochoo file in your home directory and") - print("make sure it is 'other' readable, for example:") - print("") - print(" chmod 644 ~/.choochoo") - print("") - print("The file should contain an ascii drawing of a") - print("train car no more than " + str(max_x) + " characters wide") - print("and " + str(max_y) + " characters tall.") - print("") - print("Only printable ascii characters are accepted for now.") - print("Run the command again followed by a -t switch to test") - print("your .choochoo file and report any non accepted chars.") - print("") - print("Each train contains a random selection of cars") - print("from across tilde.town user home directories.") - print("Don't worry, yours will be coming around the") - print("bend soon!") - print("") - print("~ ~ ~ ~ ~ ~") - print("") + print( + cleandoc( + f""" + ~ ~ Hooray! You've found the tilde.train! ~ ~ + + To add your own car to a future train, create + a .choochoo file in your home directory and + make sure it is 'other' readable, for example: + + chmod 644 ~/.choochoo + + The file should contain an ascii drawing of a + train car no more than {max_x} characters wide + and {max_y} characters tall. + + Only printable ascii characters are accepted for now. + Run the command again followed by a -t switch to test + your .choochoo file and report any non accepted chars. + + Each train contains a random selection of cars + from across tilde.town user home directories. + Don't worry, yours will be coming around the + bend soon! + + ~ ~ ~ ~ ~ ~ + """ + ) + ) def test_user_car(): From 4cc4b2ead487fbb00abfd22c10b7edabed4086de Mon Sep 17 00:00:00 2001 From: Noelle Leigh Date: Sun, 1 Jun 2025 21:32:47 -0400 Subject: [PATCH 05/15] replace exit() and quit() with sys.exit() --- tilde-train.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tilde-train.py b/tilde-train.py index a706dd7..be654ec 100755 --- a/tilde-train.py +++ b/tilde-train.py @@ -129,7 +129,7 @@ def test_user_car(): except: print("ERROR: Couldn't open " + fname) print("Either it doesn't exist, or is not readble by the tilde.train script.") - exit() + sys.exit(1) choochoo_string = myfile.read() choochoo_list = choochoo_string.split("\n") @@ -166,7 +166,7 @@ def test_user_car(): print(string.printable.strip()) print("") print("Yours contained " + bad_chars) - exit() + sys.exit(1) # print("") # print("Test results:") @@ -178,17 +178,17 @@ def test_user_car(): print("FAIL. Your train car is too tall.") print("It should be no taller than " + str(max_y) + " lines in height.") myfile.close() - exit() + sys.exit(1) if train_length > max_x: print("FAIL. Your train car is too long.") print("It should be no longer than " + str(max_x) + " characters in length.") myfile.close() - exit() + sys.exit(1) print("PASS. Your train car will work on the tilde.town tracks! :)") myfile.close() - exit() + sys.exit() def link_car(car): for idx,row in enumerate(car): @@ -302,24 +302,24 @@ def chuggachugga(stdscr): def handler(signal_received, frame): print("Oops. The train broke. The engineer is looking into it!") print("(Note: the train does not work in all terminals yet.)") - exit(0) + sys.exit(1) default_car = validate_car(default_car) if len(sys.argv) == 2 and ("-h" in sys.argv[1] or "help" in sys.argv[1]): print_help() - quit() + sys.exit() if len(sys.argv) == 2 and ("-t" in sys.argv[1] or "test" in sys.argv[1]): test_user_car() - quit() + sys.exit() if len(sys.argv) == 2 and ("-p" in sys.argv[1] or "print" in sys.argv[1]): print_train = True if len(sys.argv) == 2 and ("-a" in sys.argv[1] or "all" in sys.argv[1]): print_all_cars() - quit() + sys.exit() ## start a loop that collects all .choochoo files and processes on in each loop @@ -368,7 +368,7 @@ if print_train: print("
")
   print(train_str)
   print("
") - quit() + sys.exit() pad_str = " "*train_len train.insert(0,pad_str) From 8441c6acfb4d25324946bbd37ace75ee4b7524e7 Mon Sep 17 00:00:00 2001 From: Noelle Leigh Date: Sun, 1 Jun 2025 21:45:39 -0400 Subject: [PATCH 06/15] Use pathlib in test_user_car --- tilde-train.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/tilde-train.py b/tilde-train.py index be654ec..4cc6226 100755 --- a/tilde-train.py +++ b/tilde-train.py @@ -37,12 +37,12 @@ from random import shuffle ## allowing us to randomize selection of cars. import glob ## allowing us to search the file system for .choochoo files. import sys ## so we can read command line arguments. -import getpass ## so we can get the user's username import curses from signal import signal, SIGINT import time ## allowing the loop steps of train animation to be slowed import string ## for input validation from inspect import cleandoc +from pathlib import Path traincarFN = ".choochoo" max_x = 35 ## max length of train car. @@ -122,16 +122,15 @@ def print_help(): def test_user_car(): - username = getpass.getuser() - fname = "/home/" + username + "/" + traincarFN + fname = Path.home() / traincarFN try: - myfile = open(fname, 'r') - except: - print("ERROR: Couldn't open " + fname) - print("Either it doesn't exist, or is not readble by the tilde.train script.") - sys.exit(1) + choochoo_string = fname.read_text("utf-8") + except OSError as err: + raise OSError( + f"Couldn't open {fname}\n" + "Either it doesn't exist, or is not readble by the tilde.train script." + ) from err - choochoo_string = myfile.read() choochoo_list = choochoo_string.split("\n") car = "\n".join(choochoo_list) @@ -177,17 +176,14 @@ def test_user_car(): if train_height > max_y+1: print("FAIL. Your train car is too tall.") print("It should be no taller than " + str(max_y) + " lines in height.") - myfile.close() sys.exit(1) if train_length > max_x: print("FAIL. Your train car is too long.") print("It should be no longer than " + str(max_x) + " characters in length.") - myfile.close() sys.exit(1) print("PASS. Your train car will work on the tilde.town tracks! :)") - myfile.close() sys.exit() def link_car(car): From fc80d291ab9b0b94c5ef5d1bade715a6a476c6c9 Mon Sep 17 00:00:00 2001 From: Noelle Leigh Date: Sun, 1 Jun 2025 21:56:09 -0400 Subject: [PATCH 07/15] Add type hints to function signatures For better code intelligence --- tilde-train.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tilde-train.py b/tilde-train.py index 4cc6226..1115c00 100755 --- a/tilde-train.py +++ b/tilde-train.py @@ -186,14 +186,14 @@ def test_user_car(): print("PASS. Your train car will work on the tilde.town tracks! :)") sys.exit() -def link_car(car): +def link_car(car: list[str]): for idx,row in enumerate(car): car[idx] = " " + row car[len(car)-3] = "+" + car[len(car)-3][1:] car[len(car)-2] = "+" + car[len(car)-2][1:] return car -def validate_car(car): +def validate_car(car: list[str]): ## this function (1) checks that a train car isn't too tall or too long ## (2) pads it vertically or on the right side if it is too short or if ## not all lines are the same length and (3) removes bad characters. @@ -256,7 +256,7 @@ def print_all_cars(): # print "Cannot open " + fname # for debuggering purposes -def chuggachugga(stdscr): +def chuggachugga(stdscr: curses.window): curses.curs_set(0) h, w = stdscr.getmaxyx() x_pos = w-1 From 56f7c8d278faae90dd4901efc2c0fb0ade08a076 Mon Sep 17 00:00:00 2001 From: Noelle Leigh Date: Sun, 1 Jun 2025 22:20:02 -0400 Subject: [PATCH 08/15] Make each default car a separate list in memory So the link strings don't just grow over time --- tilde-train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tilde-train.py b/tilde-train.py index 1115c00..ea12c90 100755 --- a/tilde-train.py +++ b/tilde-train.py @@ -339,7 +339,7 @@ for fname in glob.glob('/home/*/' + traincarFN): ##print "Cannot open " + fname # for debuggering purposes while len(cars) < max_cars: - cars.append(default_car) ## add default cars if train too short + cars.append([*default_car]) ## add copies of default cars if train too short shuffle(cars) cars = cars[0:max_cars] From 681fa6514c259f554043e94aafe6f90b26d70efd Mon Sep 17 00:00:00 2001 From: Noelle Leigh Date: Sun, 1 Jun 2025 22:21:10 -0400 Subject: [PATCH 09/15] validate_car: Don't reassign input variable This makes it easier to track what's happening in the function --- tilde-train.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tilde-train.py b/tilde-train.py index ea12c90..d248aa8 100755 --- a/tilde-train.py +++ b/tilde-train.py @@ -198,40 +198,40 @@ def validate_car(car: list[str]): ## (2) pads it vertically or on the right side if it is too short or if ## not all lines are the same length and (3) removes bad characters. - car = "\n".join(car) - car = ''.join([i if ord(i) < 128 else ' ' for i in car]) - car = car.split("\n") + car_str = "\n".join(car) + car_str = ''.join([i if ord(i) < 128 else ' ' for i in car_str]) + car_list = car_str.split("\n") ## remove blank lines from top and bottom of car, ## so we can estimate its true size. - while car[0].strip() == "": - car.pop(0) ## clear top - while car[(len(car)-1)].strip() == "": - car.pop() ## clear bottom + while car_list[0].strip() == "": + car_list.pop(0) ## clear top + while car_list[(len(car_list)-1)].strip() == "": + car_list.pop() ## clear bottom ## len(choochoo_list) is the height of the train car, in number of rows. - if len(car) > max_y+1 or len(car) == 0: + if len(car_list) > max_y+1 or len(car_list) == 0: return 0 ## train car too tall or non-existant; skip it. ## vertically pad short cars with 1 space (lines will be lengthened later). - while len(car) < max_y: - car = [" "] + car + while len(car_list) < max_y: + car_list = [" "] + car_list - for idx,row in enumerate(car): - car[idx] = row.rstrip() + for idx,row in enumerate(car_list): + car_list[idx] = row.rstrip() - longest_line = len(max(car, key=len)) ## longest line in .choochoo file. + longest_line = len(max(car_list, key=len)) ## longest line in .choochoo file. - for idx,row in enumerate(car): + for idx,row in enumerate(car_list): if len(row) > max_x+1: ## check length of each row in .choochoo file. return 0 ## train car too long; skip it. elif "\t" in row or "\v" in row or "\f" in row or "\r" in row: return 0 # skip if contains tabs, vert tabs, line feeds or carriage ret elif len(row) < longest_line: padding = " "*(longest_line - len(row)) - car[idx] += padding ## add padding spaces. + car_list[idx] += padding ## add padding spaces. - return car + return car_list def print_all_cars(): From 2ba6a9c0ca021b7dc005b714169254390443e22e Mon Sep 17 00:00:00 2001 From: Noelle Leigh Date: Sun, 1 Jun 2025 22:21:45 -0400 Subject: [PATCH 10/15] Add type hint for cars --- tilde-train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tilde-train.py b/tilde-train.py index d248aa8..40fbf33 100755 --- a/tilde-train.py +++ b/tilde-train.py @@ -51,7 +51,7 @@ max_cars = 10 ## most cars to include in one train. print_train = False ## print train to file (instead of the screen scroll) train = [""]*max_y ## empty train of correct height. -cars = [] +cars: list[list[str]] = [] engine = [ r" ____ ", From 93a8273b6dc31463f054e257f895722156ca2e0b Mon Sep 17 00:00:00 2001 From: Noelle Leigh Date: Sun, 1 Jun 2025 22:24:08 -0400 Subject: [PATCH 11/15] Replace double # with single --- tilde-train.py | 154 ++++++++++++++++++++++++------------------------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/tilde-train.py b/tilde-train.py index 40fbf33..3ed807d 100755 --- a/tilde-train.py +++ b/tilde-train.py @@ -1,56 +1,56 @@ #!/usr/bin/env python3 -## _ _ _ _ _ _ -## | |_(_) |__| |___ | |_ _ _ __ _(_)_ _ -## | _| | / _` / -_)| _| '_/ _` | | ' \ -## \__|_|_\__,_\___(_)__|_| \__,_|_|_||_| -## -## tilde.train is an instance of TerminalTrain. It was originally developed -## by cmccabe on tilde.town (https://tildegit.org/cmccabe/TerminalTrain) but is now -## maintained by vilmibm. -## -## If you want to contribute code improvements, create a pull request here: -## https://git.tilde.town/vilmibm/tilde-train -## -## ----------------- -## -## WHAT IS IT? -## like sl (the ls typo prank), but each car on the train is a x*y character ascii art -## produced by tilde.town. -## -## ( original sl source code of sl? https://github.com/mtoyoda/sl ) -## -## TODO: -## * loosen the restriction on allowable characters in train cars. right now, limited -## to characters in python's string.printable list. -## * turn main loop into a function, so cmd line arg reader can call it (with -p) and quit. -## * figure out why tilde.train doesn't work in some terminals (sthg sthg unicode...) -## * BUGFIX-1 - something about inclusion default cars adding extra "links" to the train. -## * the -p (print train) option should print all cars, not limited to the max_cars value. -## * related to BUGFIX-1, that seems to impact spacers (links) between cars. -## * allow users to create multiple frames so their cars can be animated (difficulty=med+) -## * allow user configurable speed and number of train cars -## * allow users to move the train up or down with arrow keys -## -- worked with asciimatics, but python curses blocks on getch() -## +# _ _ _ _ _ _ +# | |_(_) |__| |___ | |_ _ _ __ _(_)_ _ +# | _| | / _` / -_)| _| '_/ _` | | ' \ +# \__|_|_\__,_\___(_)__|_| \__,_|_|_||_| +# +# tilde.train is an instance of TerminalTrain. It was originally developed +# by cmccabe on tilde.town (https://tildegit.org/cmccabe/TerminalTrain) but is now +# maintained by vilmibm. +# +# If you want to contribute code improvements, create a pull request here: +# https://git.tilde.town/vilmibm/tilde-train +# +# ----------------- +# +# WHAT IS IT? +# like sl (the ls typo prank), but each car on the train is a x*y character ascii art +# produced by tilde.town. +# +# ( original sl source code of sl? https://github.com/mtoyoda/sl ) +# +# TODO: +# * loosen the restriction on allowable characters in train cars. right now, limited +# to characters in python's string.printable list. +# * turn main loop into a function, so cmd line arg reader can call it (with -p) and quit. +# * figure out why tilde.train doesn't work in some terminals (sthg sthg unicode...) +# * BUGFIX-1 - something about inclusion default cars adding extra "links" to the train. +# * the -p (print train) option should print all cars, not limited to the max_cars value. +# * related to BUGFIX-1, that seems to impact spacers (links) between cars. +# * allow users to create multiple frames so their cars can be animated (difficulty=med+) +# * allow user configurable speed and number of train cars +# * allow users to move the train up or down with arrow keys +# -- worked with asciimatics, but python curses blocks on getch() +# -from random import shuffle ## allowing us to randomize selection of cars. -import glob ## allowing us to search the file system for .choochoo files. -import sys ## so we can read command line arguments. +from random import shuffle # allowing us to randomize selection of cars. +import glob # allowing us to search the file system for .choochoo files. +import sys # so we can read command line arguments. import curses from signal import signal, SIGINT -import time ## allowing the loop steps of train animation to be slowed -import string ## for input validation +import time # allowing the loop steps of train animation to be slowed +import string # for input validation from inspect import cleandoc from pathlib import Path traincarFN = ".choochoo" -max_x = 35 ## max length of train car. -max_y = 10 ## max height of train car. -max_cars = 10 ## most cars to include in one train. -print_train = False ## print train to file (instead of the screen scroll) +max_x = 35 # max length of train car. +max_y = 10 # max height of train car. +max_cars = 10 # most cars to include in one train. +print_train = False # print train to file (instead of the screen scroll) -train = [""]*max_y ## empty train of correct height. +train = [""]*max_y # empty train of correct height. cars: list[list[str]] = [] engine = [ @@ -134,10 +134,10 @@ def test_user_car(): choochoo_list = choochoo_string.split("\n") car = "\n".join(choochoo_list) - car2 = car.replace("\t", "") ## do not allow tabs - car2 = car2.replace("\v", "") ## do not allow vertical tabs - car2 = car2.replace("\f", "") ## do not allow line feeds - car2 = car2.replace("\r", "") ## do not allow carriage returns + car2 = car.replace("\t", "") # do not allow tabs + car2 = car2.replace("\v", "") # do not allow vertical tabs + car2 = car2.replace("\f", "") # do not allow line feeds + car2 = car2.replace("\r", "") # do not allow carriage returns car2 = ''.join([i if string.printable.find(i) >= 0 else ' ' for i in car2]) print("") @@ -194,42 +194,42 @@ def link_car(car: list[str]): return car def validate_car(car: list[str]): -## this function (1) checks that a train car isn't too tall or too long -## (2) pads it vertically or on the right side if it is too short or if -## not all lines are the same length and (3) removes bad characters. +# this function (1) checks that a train car isn't too tall or too long +# (2) pads it vertically or on the right side if it is too short or if +# not all lines are the same length and (3) removes bad characters. car_str = "\n".join(car) car_str = ''.join([i if ord(i) < 128 else ' ' for i in car_str]) car_list = car_str.split("\n") - ## remove blank lines from top and bottom of car, - ## so we can estimate its true size. + # remove blank lines from top and bottom of car, + # so we can estimate its true size. while car_list[0].strip() == "": - car_list.pop(0) ## clear top + car_list.pop(0) # clear top while car_list[(len(car_list)-1)].strip() == "": - car_list.pop() ## clear bottom + car_list.pop() # clear bottom - ## len(choochoo_list) is the height of the train car, in number of rows. + # len(choochoo_list) is the height of the train car, in number of rows. if len(car_list) > max_y+1 or len(car_list) == 0: - return 0 ## train car too tall or non-existant; skip it. + return 0 # train car too tall or non-existant; skip it. - ## vertically pad short cars with 1 space (lines will be lengthened later). + # vertically pad short cars with 1 space (lines will be lengthened later). while len(car_list) < max_y: car_list = [" "] + car_list for idx,row in enumerate(car_list): car_list[idx] = row.rstrip() - longest_line = len(max(car_list, key=len)) ## longest line in .choochoo file. + longest_line = len(max(car_list, key=len)) # longest line in .choochoo file. for idx,row in enumerate(car_list): - if len(row) > max_x+1: ## check length of each row in .choochoo file. - return 0 ## train car too long; skip it. + if len(row) > max_x+1: # check length of each row in .choochoo file. + return 0 # train car too long; skip it. elif "\t" in row or "\v" in row or "\f" in row or "\r" in row: return 0 # skip if contains tabs, vert tabs, line feeds or carriage ret elif len(row) < longest_line: padding = " "*(longest_line - len(row)) - car_list[idx] += padding ## add padding spaces. + car_list[idx] += padding # add padding spaces. return car_list @@ -238,19 +238,19 @@ def print_all_cars(): for fname in glob.glob('/home/*/' + traincarFN): try: with open(fname, 'r') as myfile: - ## print fname ## debug, print file path and name + # print fname # debug, print file path and name choochoo_string = myfile.read() choochoo_list = choochoo_string.split("\n") if len(choochoo_list) > max_y+1: - continue ## the train car was too tall; skip it. + continue # the train car was too tall; skip it. - car = validate_car(choochoo_list) ## printing is only a DEBUG feature. + car = validate_car(choochoo_list) # printing is only a DEBUG feature. if car != 0: print("") print(fname + ":") - print("\n".join(car)) ## print the car to stdout + print("\n".join(car)) # print the car to stdout - ## HOW TO CLOSE THE FILE HANDLE? fname.close(), close(fname), ...? + # HOW TO CLOSE THE FILE HANDLE? fname.close(), close(fname), ...? except: pass; # print "Cannot open " + fname # for debuggering purposes @@ -286,12 +286,12 @@ def chuggachugga(stdscr: curses.window): stdscr.refresh() time.sleep(.03) -# ev = stdscr.getch() ## seems like curses waits for input, which won't work here. +# ev = stdscr.getch() # seems like curses waits for input, which won't work here. # if ev in (ord('Q'), ord('q')): # return -# elif ev == curses.KEY_UP: ## up +# elif ev == curses.KEY_UP: # up # y_pos += 1 -# elif ev == curses.KEY_DOWN: ## down +# elif ev == curses.KEY_DOWN: # down # y_pos -= 1 @@ -318,28 +318,28 @@ if len(sys.argv) == 2 and ("-a" in sys.argv[1] or "all" in sys.argv[1]): sys.exit() -## start a loop that collects all .choochoo files and processes on in each loop +# start a loop that collects all .choochoo files and processes on in each loop for fname in glob.glob('/home/*/' + traincarFN): car_len = 1 try: with open(fname, 'r') as myfile: - ## print fname ## debug, print file path and name + # print fname # debug, print file path and name choochoo_string = myfile.read() choochoo_list = choochoo_string.split("\n") if len(choochoo_list) > max_y+1: - continue ## the train car was too tall; skip it. + continue # the train car was too tall; skip it. - car = validate_car(choochoo_list) ## printing is only a DEBUG feature. + car = validate_car(choochoo_list) # printing is only a DEBUG feature. if car != 0: - cars.append(car) ## start a list of lists (list of cars) here. + cars.append(car) # start a list of lists (list of cars) here. - ## HOW TO CLOSE THE FILE HANDLE? fname.close(), close(fname), ...? + # HOW TO CLOSE THE FILE HANDLE? fname.close(), close(fname), ...? except: pass; - ##print "Cannot open " + fname # for debuggering purposes + #print "Cannot open " + fname # for debuggering purposes while len(cars) < max_cars: - cars.append([*default_car]) ## add copies of default cars if train too short + cars.append([*default_car]) # add copies of default cars if train too short shuffle(cars) cars = cars[0:max_cars] From db1464c2d288fa47335705804fd341f42e370c8a Mon Sep 17 00:00:00 2001 From: Noelle Leigh Date: Sun, 1 Jun 2025 22:25:22 -0400 Subject: [PATCH 12/15] Remove commented code --- tilde-train.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/tilde-train.py b/tilde-train.py index 3ed807d..f9e4ebf 100755 --- a/tilde-train.py +++ b/tilde-train.py @@ -167,9 +167,6 @@ def test_user_car(): print("Yours contained " + bad_chars) sys.exit(1) -# print("") -# print("Test results:") - train_height = len(choochoo_list) train_length = len(max(choochoo_list, key=len)) @@ -238,7 +235,6 @@ def print_all_cars(): for fname in glob.glob('/home/*/' + traincarFN): try: with open(fname, 'r') as myfile: - # print fname # debug, print file path and name choochoo_string = myfile.read() choochoo_list = choochoo_string.split("\n") if len(choochoo_list) > max_y+1: @@ -250,10 +246,8 @@ def print_all_cars(): print(fname + ":") print("\n".join(car)) # print the car to stdout - # HOW TO CLOSE THE FILE HANDLE? fname.close(), close(fname), ...? except: pass; -# print "Cannot open " + fname # for debuggering purposes def chuggachugga(stdscr: curses.window): @@ -264,37 +258,19 @@ def chuggachugga(stdscr: curses.window): while True: for idx,train_layer in enumerate(reversed(train)): -# screen.print_at(train_layer, x_pos, (y_pos-idx)) -# stdscr.addstr((y_pos-idx),x_pos,train_layer) train_snip_start = 0 if (x_pos >= 0) else min(abs(x_pos), len(train_layer)) train_snip_end = w-x_pos if (w-x_pos <= len(train_layer)) else len(train_layer) -# train_snip_end = min(len(train_layer),w-x_pos) x = max(0, x_pos) -# stdscr.addstr(2,2,"start: " + str(train_snip_start)) -# stdscr.addstr(3,2,"end: "+ str(train_snip_end)) -# stdscr.addstr(4,2,"x_pos: " + str(x_pos)) -# stdscr.addstr(5,2,"train len: " + str(len(train_layer))) stdscr.addstr((y_pos-idx),x,train_layer[train_snip_start:train_snip_end]) x_pos -= 1 if x_pos == -train_len: -# stdscr.addstr(2,5,"here we are!") -# time.sleep(10) return stdscr.refresh() time.sleep(.03) -# ev = stdscr.getch() # seems like curses waits for input, which won't work here. -# if ev in (ord('Q'), ord('q')): -# return -# elif ev == curses.KEY_UP: # up -# y_pos += 1 -# elif ev == curses.KEY_DOWN: # down -# y_pos -= 1 - - def handler(signal_received, frame): print("Oops. The train broke. The engineer is looking into it!") print("(Note: the train does not work in all terminals yet.)") @@ -333,10 +309,8 @@ for fname in glob.glob('/home/*/' + traincarFN): if car != 0: cars.append(car) # start a list of lists (list of cars) here. - # HOW TO CLOSE THE FILE HANDLE? fname.close(), close(fname), ...? except: pass; - #print "Cannot open " + fname # for debuggering purposes while len(cars) < max_cars: cars.append([*default_car]) # add copies of default cars if train too short From a07aa2685be03523abfbb656eec1197e79a18f0a Mon Sep 17 00:00:00 2001 From: Noelle Leigh Date: Mon, 2 Jun 2025 15:55:38 -0400 Subject: [PATCH 13/15] Add better error handling for curses failures --- tilde-train.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tilde-train.py b/tilde-train.py index f9e4ebf..efede48 100755 --- a/tilde-train.py +++ b/tilde-train.py @@ -345,5 +345,13 @@ train.insert(0,pad_str) train.append(pad_str) if __name__ == "__main__": - signal(SIGINT, handler) - curses.wrapper(chuggachugga) + signal(SIGINT, handler) + try: + curses.wrapper(chuggachugga) + except curses.error as err: + print( + f"{err}\n" + "Couldn't print the train for some reason. " + "Maybe your terminal window is too short?" + ) + sys.exit(1) From 0b53c462de7a0ec2df99e8876db8bbfa8ec5c873 Mon Sep 17 00:00:00 2001 From: Noelle Leigh Date: Mon, 2 Jun 2025 16:49:43 -0400 Subject: [PATCH 14/15] Make validate_car raise exception on failure instead of returning 0 --- tilde-train.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/tilde-train.py b/tilde-train.py index efede48..c71ff1d 100755 --- a/tilde-train.py +++ b/tilde-train.py @@ -90,6 +90,9 @@ default_car = [ r" \__/ \__/ \__/ \__/ ", ] +class CarError(Exception): + """Error related validating a car.""" + def print_help(): print( cleandoc( @@ -206,9 +209,11 @@ def validate_car(car: list[str]): while car_list[(len(car_list)-1)].strip() == "": car_list.pop() # clear bottom - # len(choochoo_list) is the height of the train car, in number of rows. - if len(car_list) > max_y+1 or len(car_list) == 0: - return 0 # train car too tall or non-existant; skip it. + # len(car_list) is the height of the train car, in number of rows. + if len(car_list) > max_y+1: + raise CarError(f"Car is too tall ({len(car_list)} > {max_y + 1}).") + if len(car_list) == 0: + raise CarError(f"Car is empty.") # vertically pad short cars with 1 space (lines will be lengthened later). while len(car_list) < max_y: @@ -221,9 +226,9 @@ def validate_car(car: list[str]): for idx,row in enumerate(car_list): if len(row) > max_x+1: # check length of each row in .choochoo file. - return 0 # train car too long; skip it. + raise CarError(f"Car is too wide ({len(row)} > {max_x + 1}).") elif "\t" in row or "\v" in row or "\f" in row or "\r" in row: - return 0 # skip if contains tabs, vert tabs, line feeds or carriage ret + raise CarError(f"Car contains illegal control characters.") elif len(row) < longest_line: padding = " "*(longest_line - len(row)) car_list[idx] += padding # add padding spaces. @@ -241,13 +246,12 @@ def print_all_cars(): continue # the train car was too tall; skip it. car = validate_car(choochoo_list) # printing is only a DEBUG feature. - if car != 0: - print("") - print(fname + ":") - print("\n".join(car)) # print the car to stdout + print("") + print(fname + ":") + print("\n".join(car)) # print the car to stdout except: - pass; + pass def chuggachugga(stdscr: curses.window): @@ -306,11 +310,10 @@ for fname in glob.glob('/home/*/' + traincarFN): continue # the train car was too tall; skip it. car = validate_car(choochoo_list) # printing is only a DEBUG feature. - if car != 0: - cars.append(car) # start a list of lists (list of cars) here. + cars.append(car) # start a list of lists (list of cars) here. except: - pass; + pass while len(cars) < max_cars: cars.append([*default_car]) # add copies of default cars if train too short From 6063c8b3b3c10a0f62801ceaca70c18aea2990be Mon Sep 17 00:00:00 2001 From: Noelle Leigh Date: Mon, 2 Jun 2025 17:14:51 -0400 Subject: [PATCH 15/15] Escape HTML characters when printing the train Since it's wrapped in a `
`, I assume it's meant to be inserted into
HTML?
---
 tilde-train.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/tilde-train.py b/tilde-train.py
index c71ff1d..2251870 100755
--- a/tilde-train.py
+++ b/tilde-train.py
@@ -41,6 +41,7 @@ import curses
 from signal import signal, SIGINT
 import time # allowing the loop steps of train animation to be slowed
 import string # for input validation
+from html import escape
 from inspect import cleandoc
 from pathlib import Path
 
@@ -339,7 +340,7 @@ train_str = "\n".join(train)
 
 if print_train:
   print("
")
-  print(train_str)
+  print(escape(train_str))
   print("
") sys.exit()