Compare commits

..

No commits in common. "dcccaebbab0abac849d458bc64ae6d4d5da4d970" and "1162faad18789c27ecb5652af2d861347dbfd569" have entirely different histories.

View File

@ -1,147 +1,138 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# _ _ _ _ _ _ ## _ _ _ _ _ _
# | |_(_) |__| |___ | |_ _ _ __ _(_)_ _ ## | |_(_) |__| |___ | |_ _ _ __ _(_)_ _
# | _| | / _` / -_)| _| '_/ _` | | ' \ ## | _| | / _` / -_)| _| '_/ _` | | ' \
# \__|_|_\__,_\___(_)__|_| \__,_|_|_||_| ## \__|_|_\__,_\___(_)__|_| \__,_|_|_||_|
# ##
# tilde.train is an instance of TerminalTrain. It was originally developed ## tilde.train is an instance of TerminalTrain. It was originally developed
# by cmccabe on tilde.town (https://tildegit.org/cmccabe/TerminalTrain) but is now ## by cmccabe on tilde.town but is now maintained in tildegit with the main
# maintained by vilmibm. ## development ocurring on rawtext.club.
# ##
# If you want to contribute code improvements, create a pull request here: ## If you want to contribute to code improvement, or if you have suggestions
# https://git.tilde.town/vilmibm/tilde-train ## for cmccabe, create a pull request here: https://tildegit.org/cmccabe/TerminalTrain
# ## or email cmccabe at cmccabe@rawtext.club
# ----------------- ##
# ## -----------------
# WHAT IS IT? ##
# like sl (the ls typo prank), but each car on the train is a x*y character ascii art ## WHAT IS IT?
# produced by tilde.town. ## 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 ) ##
# ## ( original sl source code of sl? https://github.com/mtoyoda/sl )
# TODO: ##
# * loosen the restriction on allowable characters in train cars. right now, limited ## TODO:
# to characters in python's string.printable list. ## * loosen the restriction on allowable characters in train cars. right now, limited
# * turn main loop into a function, so cmd line arg reader can call it (with -p) and quit. ## to characters in python's string.printable list.
# * figure out why tilde.train doesn't work in some terminals (sthg sthg unicode...) ## * turn main loop into a function, so cmd line arg reader can call it (with -p) and quit.
# * BUGFIX-1 - something about inclusion default cars adding extra "links" to the train. ## * figure out why tilde.train doesn't work in some terminals (sthg sthg unicode...)
# * the -p (print train) option should print all cars, not limited to the max_cars value. ## * BUGFIX-1 - something about inclusion default cars adding extra "links" to the train.
# * related to BUGFIX-1, that seems to impact spacers (links) between cars. ## * the -p (print train) option should print all cars, not limited to the max_cars value.
# * allow users to create multiple frames so their cars can be animated (difficulty=med+) ## * related to BUGFIX-1, that seems to impact spacers (links) between cars.
# * allow user configurable speed and number of train cars ## * allow users to create multiple frames so their cars can be animated (difficulty=med+)
# * allow users to move the train up or down with arrow keys ## * allow user configurable speed and number of train cars
# -- worked with asciimatics, but python curses blocks on getch() ## * 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. from random import shuffle ## allowing us to randomize selection of cars.
import glob # allowing us to search the file system for .choochoo files. import glob ## allowing us to search the file system for .choochoo files.
import sys # so we can read command line arguments. import sys ## so we can read command line arguments.
import getpass ## so we can get the user's username
import curses import curses
from signal import signal, SIGINT from signal import signal, SIGINT
import time # allowing the loop steps of train animation to be slowed import time ## allowing the loop steps of train animation to be slowed
import string # for input validation import string ## for input validation
from html import escape
from inspect import cleandoc
from pathlib import Path
traincarFN = ".choochoo" traincarFN = ".choochoo"
max_x = 35 # max length of train car. max_x = 35 ## max length of train car.
max_y = 10 # max height of train car. max_y = 10 ## max height of train car.
max_cars = 10 # most cars to include in one train. max_cars = 10 ## most cars to include in one train.
print_train = False # print train to file (instead of the screen scroll) 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]] = [] cars = []
engine = [ engine = """ ____
r" ____ ", |____| ------------
r" |____| ------------", | | === | ------ |
r" | | === | ------ |", ___| |__| |_____| | O | |
r" ___| |__| |_____| | O | |", | | | |__/V\_| |
r" | | | |__/V\_| |", [[ | |
r" [[ | |", | | ------------ | ~town |
r" | | ------------ | ~town |", |__|______________|__________|
r" |__|______________|__________|", //// / _\__/__\__/__\ / \
r" //// / _\__/__\__/__\ / \ ", //// \__/ \__/ \__/ \__/ """
r"//// \__/ \__/ \__/ \__/ ", engine = engine.split("\n")
]
caboose = [ caboose = """ ||
r" || ", ============= ||
r" ============= || ", =========| |==========
r"=========| |========== ", | ---- ---- |
r" | ---- ---- | ", | | | | | |
r" | | | | | | ", | ---- ---- |
r" | ---- ---- | ", | tilde.town railways |
r" | tilde.town railways | ", ==| |==
r"==| |== ", == - / \-/ \-----/ \-/ \ - ==
r"== - / \-/ \-----/ \-/ \ - == ", \__/ \__/ \__/ \__/ """
r" \__/ \__/ \__/ \__/ ", caboose = caboose.split("\n")
]
default_car = [ default_car = """ ----------------------------
r" ---------------------------- ", | |
r"| |", | YOUR TRAIN CAR HERE! |
r"| YOUR TRAIN CAR HERE! |", | Just create a |
r"| Just create a |", | ~/.choochoo file! |
r"| ~/.choochoo file! |", | __ __ __ __ |
r"| __ __ __ __ |", - / \-/ \------/ \-/ \ -
r" - / \-/ \------/ \-/ \ - ", \__/ \__/ \__/ \__/"""
r" \__/ \__/ \__/ \__/ ", default_car = default_car.split("\n")
]
class CarError(Exception):
"""Error related validating a car."""
def print_help(): def print_help():
print( print("")
cleandoc( print("~ ~ Hooray! You've found the tilde.train! ~ ~")
f""" print("")
~ ~ Hooray! You've found the tilde.train! ~ ~ print("To add your own car to a future train, create")
print("a .choochoo file in your home directory and")
To add your own car to a future train, create print("make sure it is 'other' readable, for example:")
a .choochoo file in your home directory and print("")
make sure it is 'other' readable, for example: print(" chmod 644 ~/.choochoo")
print("")
chmod 644 ~/.choochoo print("The file should contain an ascii drawing of a")
print("train car no more than " + str(max_x) + " characters wide")
The file should contain an ascii drawing of a print("and " + str(max_y) + " characters tall.")
train car no more than {max_x} characters wide print("")
and {max_y} characters tall. print("Only printable ascii characters are accepted for now.")
print("Run the command again followed by a -t switch to test")
Only printable ascii characters are accepted for now. print("your .choochoo file and report any non accepted chars.")
Run the command again followed by a -t switch to test print("")
your .choochoo file and report any non accepted chars. print("Each train contains a random selection of cars")
print("from across tilde.town user home directories.")
Each train contains a random selection of cars print("Don't worry, yours will be coming around the")
from across tilde.town user home directories. print("bend soon!")
Don't worry, yours will be coming around the print("")
bend soon! print("~ ~ ~ ~ ~ ~")
print("")
~ ~ ~ ~ ~ ~
"""
)
)
def test_user_car(): def test_user_car():
fname = Path.home() / traincarFN username = getpass.getuser()
fname = "/home/" + username + "/" + traincarFN
try: try:
choochoo_string = fname.read_text("utf-8") myfile = open(fname, 'r')
except OSError as err: except:
raise OSError( print("ERROR: Couldn't open " + fname)
f"Couldn't open {fname}\n" print("Either it doesn't exist, or is not readble by the tilde.train script.")
"Either it doesn't exist, or is not readble by the tilde.train script." exit()
) from err
choochoo_string = myfile.read()
choochoo_list = choochoo_string.split("\n") choochoo_list = choochoo_string.split("\n")
car = "\n".join(choochoo_list) car = "\n".join(choochoo_list)
car2 = car.replace("\t", "") # do not allow tabs car2 = car.replace("\t", "") ## do not allow tabs
car2 = car2.replace("\v", "") # do not allow vertical tabs car2 = car2.replace("\v", "") ## do not allow vertical tabs
car2 = car2.replace("\f", "") # do not allow line feeds car2 = car2.replace("\f", "") ## do not allow line feeds
car2 = car2.replace("\r", "") # do not allow carriage returns car2 = car2.replace("\r", "") ## do not allow carriage returns
car2 = ''.join([i if string.printable.find(i) >= 0 else ' ' for i in car2]) car2 = ''.join([i if string.printable.find(i) >= 0 else ' ' for i in car2])
print("") print("")
@ -169,7 +160,10 @@ def test_user_car():
print(string.printable.strip()) print(string.printable.strip())
print("") print("")
print("Yours contained " + bad_chars) print("Yours contained " + bad_chars)
sys.exit(1) exit()
# print("")
# print("Test results:")
train_height = len(choochoo_list) train_height = len(choochoo_list)
train_length = len(max(choochoo_list, key=len)) train_length = len(max(choochoo_list, key=len))
@ -177,85 +171,90 @@ def test_user_car():
if train_height > max_y+1: if train_height > max_y+1:
print("FAIL. Your train car is too tall.") print("FAIL. Your train car is too tall.")
print("It should be no taller than " + str(max_y) + " lines in height.") print("It should be no taller than " + str(max_y) + " lines in height.")
sys.exit(1) myfile.close()
exit()
if train_length > max_x: if train_length > max_x:
print("FAIL. Your train car is too long.") print("FAIL. Your train car is too long.")
print("It should be no longer than " + str(max_x) + " characters in length.") print("It should be no longer than " + str(max_x) + " characters in length.")
sys.exit(1) myfile.close()
exit()
print("PASS. Your train car will work on the tilde.town tracks! :)") print("PASS. Your train car will work on the tilde.town tracks! :)")
sys.exit() myfile.close()
exit()
def link_car(car: list[str]): def link_car(car):
for idx,row in enumerate(car): for idx,row in enumerate(car):
car[idx] = " " + row car[idx] = " " + row
car[len(car)-3] = "+" + car[len(car)-3][1:] car[len(car)-3] = "+" + car[len(car)-3][1:]
car[len(car)-2] = "+" + car[len(car)-2][1:] car[len(car)-2] = "+" + car[len(car)-2][1:]
return car return car
def validate_car(car: list[str]): def validate_car(car):
# this function (1) checks that a train car isn't too tall or too long ## 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 ## (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. ## not all lines are the same length and (3) removes bad characters.
car_str = "\n".join(car) car = "\n".join(car)
car_str = ''.join([i if ord(i) < 128 else ' ' for i in car_str]) car = ''.join([i if ord(i) < 128 else ' ' for i in car])
car_list = car_str.split("\n") car = car.split("\n")
# remove blank lines from top and bottom of car, ## remove blank lines from top and bottom of car,
# so we can estimate its true size. ## so we can estimate its true size.
while car_list[0].strip() == "": while car[0].strip() == "":
car_list.pop(0) # clear top car.pop(0) ## clear top
while car_list[(len(car_list)-1)].strip() == "": while car[(len(car)-1)].strip() == "":
car_list.pop() # clear bottom car.pop() ## clear bottom
# len(car_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: if len(car) > max_y+1 or len(car) == 0:
raise CarError(f"Car is too tall ({len(car_list)} > {max_y + 1}).") return 0 ## train car too tall or non-existant; skip it.
if len(car_list) == 0:
raise CarError(f"Car is empty.")
# 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: while len(car) < max_y:
car_list = [" "] + car_list car = [" "] + car
for idx,row in enumerate(car_list): for idx,row in enumerate(car):
car_list[idx] = row.rstrip() car[idx] = row.rstrip()
longest_line = len(max(car_list, key=len)) # longest line in .choochoo file. longest_line = len(max(car, key=len)) ## longest line in .choochoo file.
for idx,row in enumerate(car_list): for idx,row in enumerate(car):
if len(row) > max_x+1: # check length of each row in .choochoo file. if len(row) > max_x+1: ## check length of each row in .choochoo file.
raise CarError(f"Car is too wide ({len(row)} > {max_x + 1}).") return 0 ## train car too long; skip it.
elif "\t" in row or "\v" in row or "\f" in row or "\r" in row: elif "\t" in row or "\v" in row or "\f" in row or "\r" in row:
raise CarError(f"Car contains illegal control characters.") return 0 # skip if contains tabs, vert tabs, line feeds or carriage ret
elif len(row) < longest_line: elif len(row) < longest_line:
padding = " "*(longest_line - len(row)) padding = " "*(longest_line - len(row))
car_list[idx] += padding # add padding spaces. car[idx] += padding ## add padding spaces.
return car_list return car
def print_all_cars(): def print_all_cars():
for fname in glob.glob('/home/*/' + traincarFN): for fname in glob.glob('/home/*/' + traincarFN):
try: try:
with open(fname, 'r') as myfile: with open(fname, 'r') as myfile:
## print fname ## debug, print file path and name
choochoo_string = myfile.read() choochoo_string = myfile.read()
choochoo_list = choochoo_string.split("\n") choochoo_list = choochoo_string.split("\n")
if len(choochoo_list) > max_y+1: 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.
print("") if car != 0:
print(fname + ":") print("")
print("\n".join(car)) # print the car to stdout print(fname + ":")
print("\n".join(car)) ## print the car to stdout
## HOW TO CLOSE THE FILE HANDLE? fname.close(), close(fname), ...?
except: except:
pass pass;
# print "Cannot open " + fname # for debuggering purposes
def chuggachugga(stdscr: curses.window): def chuggachugga(stdscr):
curses.curs_set(0) curses.curs_set(0)
h, w = stdscr.getmaxyx() h, w = stdscr.getmaxyx()
x_pos = w-1 x_pos = w-1
@ -263,61 +262,82 @@ def chuggachugga(stdscr: curses.window):
while True: while True:
for idx,train_layer in enumerate(reversed(train)): 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_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 = 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) 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]) stdscr.addstr((y_pos-idx),x,train_layer[train_snip_start:train_snip_end])
x_pos -= 1 x_pos -= 1
if x_pos == -train_len: if x_pos == -train_len:
# stdscr.addstr(2,5,"here we are!")
# time.sleep(10)
return return
stdscr.refresh() stdscr.refresh()
time.sleep(.03) 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): def handler(signal_received, frame):
print("Oops. The train broke. The engineer is looking into it!") print("Oops. The train broke. The engineer is looking into it!")
print("(Note: the train does not work in all terminals yet.)") print("(Note: the train does not work in all terminals yet.)")
sys.exit(1) exit(0)
default_car = validate_car(default_car) default_car = validate_car(default_car)
if len(sys.argv) == 2 and ("-h" in sys.argv[1] or "help" in sys.argv[1]): if len(sys.argv) == 2 and ("-h" in sys.argv[1] or "help" in sys.argv[1]):
print_help() print_help()
sys.exit() quit()
if len(sys.argv) == 2 and ("-t" in sys.argv[1] or "test" in sys.argv[1]): if len(sys.argv) == 2 and ("-t" in sys.argv[1] or "test" in sys.argv[1]):
test_user_car() test_user_car()
sys.exit() quit()
if len(sys.argv) == 2 and ("-p" in sys.argv[1] or "print" in sys.argv[1]): if len(sys.argv) == 2 and ("-p" in sys.argv[1] or "print" in sys.argv[1]):
print_train = True print_train = True
if len(sys.argv) == 2 and ("-a" in sys.argv[1] or "all" in sys.argv[1]): if len(sys.argv) == 2 and ("-a" in sys.argv[1] or "all" in sys.argv[1]):
print_all_cars() print_all_cars()
sys.exit() quit()
# 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): for fname in glob.glob('/home/*/' + traincarFN):
car_len = 1 car_len = 1
try: try:
with open(fname, 'r') as myfile: 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_string = myfile.read()
choochoo_list = choochoo_string.split("\n") choochoo_list = choochoo_string.split("\n")
if len(choochoo_list) > max_y+1: 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.
cars.append(car) # start a list of lists (list of cars) here. 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: except:
pass pass;
##print "Cannot open " + fname # for debuggering purposes
while len(cars) < max_cars: while len(cars) < max_cars:
cars.append([*default_car]) # add copies of default cars if train too short cars.append(default_car) ## add default cars if train too short
shuffle(cars) shuffle(cars)
cars = cars[0:max_cars] cars = cars[0:max_cars]
@ -340,22 +360,14 @@ train_str = "\n".join(train)
if print_train: if print_train:
print("<pre>") print("<pre>")
print(escape(train_str)) print(train_str)
print("</pre>") print("</pre>")
sys.exit() quit()
pad_str = " "*train_len pad_str = " "*train_len
train.insert(0,pad_str) train.insert(0,pad_str)
train.append(pad_str) train.append(pad_str)
if __name__ == "__main__": if __name__ == "__main__":
signal(SIGINT, handler) signal(SIGINT, handler)
try: curses.wrapper(chuggachugga)
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)