py-basic/src/stopwatch.py

193 lines
7.1 KiB
Python
Raw Normal View History

2023-04-20 23:32:21 +02:00
#!/usr/bin/env python3
# hard dependencies
2023-04-18 20:30:49 +02:00
import argparse
2023-04-16 17:30:32 +02:00
import time
2023-04-14 14:27:45 +02:00
import datetime
import sys
2023-04-16 17:30:32 +02:00
import time
2023-04-20 23:32:21 +02:00
import curses
2023-04-14 14:27:45 +02:00
2023-04-20 23:32:21 +02:00
# optional dependencies
try:
import beepy # pip install beepy
except:
beepy = None
try:
from multiprocessing import Process
except:
Process = None
# printing to stderr
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
2023-04-14 14:27:45 +02:00
2023-04-21 00:14:49 +02:00
class Animation:
X_SIZE = 8
Y_SIZE = 3
field = [
['>', '>' * (X_SIZE - 2), 'v',],
['^', ' ' * (X_SIZE - 2), 'v',] * (Y_SIZE - 2),
['^', '<' * (X_SIZE - 2), '<',],
]
def __init__(self) -> None:
pass
def tick(self) -> list[str]:
out_arr: list[str] = []
for line in self.field:
out_arr.append(self.__char_array_to_str(line))
return out_arr
def __char_array_to_str(self, chars) -> str :
out_str = ""
for char in chars:
out_str += char
return out_str
class Stopwatch:
2023-04-18 20:30:49 +02:00
beep_at: int
beep_at_time: datetime.datetime
has_beeped: bool = False
start_time: datetime.datetime
enable_sound: bool = False
2023-04-20 23:32:21 +02:00
screen: curses.window
next_line = 0
2023-04-21 00:14:49 +02:00
enable_animation: bool
2023-04-21 00:38:19 +02:00
old_ui: bool
2023-04-21 00:14:49 +02:00
animation: Animation
2023-04-18 20:30:49 +02:00
2023-04-20 23:32:21 +02:00
BUFFER_LINE = '=' * 120
COL0_Y = 0
COL1_Y = 44
COL2_Y = 90
2023-04-21 00:38:19 +02:00
def __init__(self, beep_at, enable_sound, screen: curses.window, enable_animation: bool, old_ui: bool) -> None:
2023-04-20 23:32:21 +02:00
self.screen = screen
self.screen.addstr(0, 0, self.BUFFER_LINE)
self.next_line += 2
2023-04-14 14:27:45 +02:00
self.start_time = datetime.datetime.now().replace(microsecond=0)
2023-04-21 00:14:49 +02:00
self.enable_animation = enable_animation
2023-04-21 00:38:19 +02:00
self.old_ui = old_ui
2023-04-18 20:30:49 +02:00
if enable_sound:
2023-04-20 23:32:21 +02:00
self.enable_sound = enable_sound
2023-04-18 20:30:49 +02:00
if not beep_at <= 0:
self.beep_at = beep_at
self.beep_at_time = self.start_time + datetime.timedelta(minutes=beep_at)
2023-04-20 23:32:21 +02:00
self.screen.addstr(self.next_line, self.COL0_Y, "Start time:\t%s" % self.start_time)
self.screen.addstr(self.next_line, self.COL1_Y, "Will beep at:\t%s" % self.beep_at_time)
self.screen.addstr(self.next_line, self.COL2_Y, "Beeping Time:\t%sm" % beep_at)
self.next_line += 2
self.screen.addstr(self.next_line, self.COL0_Y, self.BUFFER_LINE)
self.next_line += 5
# content goes here
self.screen.addstr(self.next_line, self.COL0_Y, self.BUFFER_LINE)
self.next_line -= 3
2023-04-18 20:30:49 +02:00
else:
self.beep_at = 0
2023-04-20 10:22:54 +02:00
self.beep_at_time = datetime.datetime.now()
2023-04-20 23:32:21 +02:00
self.screen.addstr(self.next_line, self.COL1_Y, "Start time:\t%s" % self.start_time)
self.next_line += 6
# content goes here
self.screen.addstr(self.next_line, self.COL0_Y, self.BUFFER_LINE)
self.next_line -= 4
2023-04-18 20:30:49 +02:00
2023-04-14 14:27:45 +02:00
def display(self) -> None:
2023-04-20 23:32:21 +02:00
remaining_time_str = ""
beep_notice_str = ""
2023-04-14 14:27:45 +02:00
while True:
2023-04-20 23:32:21 +02:00
nl_store = self.next_line
2023-04-14 14:27:45 +02:00
now = datetime.datetime.now().replace(microsecond=0)
elapsed = (now - self.start_time)
2023-04-20 23:32:21 +02:00
current_time_str = ("\rcurrent:\t%s" % now)
elapsed_time_str = ("elapsed: %s" % elapsed)
2023-04-20 10:22:54 +02:00
if self.beep_at > 0 and not self.has_beeped:
remaining = self.beep_at_time - now
2023-04-20 23:32:21 +02:00
remaining_time_str = ("remaining: %s" % remaining)
2023-04-18 20:30:49 +02:00
if elapsed.seconds / 60 >= self.beep_at and not self.has_beeped and not self.beep_at == 0:
2023-04-20 23:32:21 +02:00
current_time_str += '\a'
beep_notice_str = "🔔Beep!🔔"
2023-04-18 20:30:49 +02:00
self.has_beeped = True
2023-04-20 23:32:21 +02:00
if self.enable_sound and not beepy is None and not Process is None:
2023-04-18 20:30:49 +02:00
p = Process(target=beepy.beep, kwargs={"sound": "success"})
p.start()
2023-04-20 23:32:21 +02:00
#sys.stdout.write('\r' + current_time_str+"\t\t"+elapsed_time_str+"\t\t"+remaining_time_str+"\t\t"+beep_notice_str + "\t")
if self.beep_at > 0:
self.screen.addstr(self.next_line, self.COL0_Y, "current:\t%s" % (now))
self.screen.addstr(self.next_line, self.COL2_Y, "elapsed:\t%s" % (elapsed))
self.next_line += 1
if not self.has_beeped:
self.screen.addstr(self.next_line, self.COL2_Y, "remaining:\t%s" % (remaining))
else:
self.screen.addstr(self.next_line, self.COL2_Y, "overtime:\t%s" % (remaining))
else:
self.screen.addstr(self.next_line, self.COL1_Y, "current:\t%s" % (now))
self.next_line += 2
self.screen.addstr(self.next_line, self.COL1_Y, "elapsed:\t\t %s" % (elapsed))
2023-04-21 00:14:49 +02:00
if self.enable_animation:
self.next_line -= 1
if now.second % 4 == 0:
self.screen.addstr(self.next_line, self.COL1_Y + 16, "$-")
self.next_line += 1
self.screen.addstr(self.next_line, self.COL1_Y + 16, "--")
elif now.second % 4 == 1:
self.screen.addstr(self.next_line, self.COL1_Y + 16, "-$")
self.next_line += 1
self.screen.addstr(self.next_line, self.COL1_Y + 16, "--")
elif now.second % 4 == 2:
self.screen.addstr(self.next_line, self.COL1_Y + 16, "--")
self.next_line += 1
self.screen.addstr(self.next_line, self.COL1_Y + 16, "-$")
elif now.second % 4 == 3:
self.screen.addstr(self.next_line, self.COL1_Y + 16, "--")
self.next_line += 1
self.screen.addstr(self.next_line, self.COL1_Y + 16, "$-")
2023-04-20 23:32:21 +02:00
#sys.stdout.flush()
self.screen.refresh()
self.next_line = nl_store
2023-04-18 20:30:49 +02:00
time.sleep(0.1)
2023-04-14 14:27:45 +02:00
def main():
2023-04-18 20:30:49 +02:00
parser = argparse.ArgumentParser(prog="stopwatch", description='Simple CLI stopwatch.')
2023-04-20 23:32:21 +02:00
# TODO make this weird nargs thing better, generates a bad help page
2023-04-18 20:30:49 +02:00
parser.add_argument('-b', '--beep', metavar='N', type=float, nargs='+',
2023-04-20 23:32:21 +02:00
help='beep after N minutes', default=[0])
2023-04-18 20:30:49 +02:00
parser.add_argument('-s', '--sound',
2023-04-20 23:32:21 +02:00
action='store_true', help="activate sound")
parser.add_argument('-l', '--legacy-ui',
action='store_true', help="Use the old \"inline\" ui instead of the curses ui")
parser.add_argument('-a', '--no-animation',
action='store_true', help="don't show the animation")
2023-04-18 20:30:49 +02:00
args = parser.parse_args()
2023-04-14 14:27:45 +02:00
try:
2023-04-20 23:32:21 +02:00
stdscreen = curses.initscr()
# copied from my guide, idk what they do:
curses.noecho()
curses.cbreak()
2023-04-21 00:38:19 +02:00
if args.legacy_ui:
eprint("Old UI not implemented.")
2023-04-21 00:14:49 +02:00
timer = Stopwatch(
2023-04-20 23:32:21 +02:00
beep_at=args.beep[0],
enable_sound=args.sound,
2023-04-21 00:14:49 +02:00
screen=stdscreen,
2023-04-21 00:38:19 +02:00
enable_animation= not args.no_animation,
old_ui=args.legacy_ui
2023-04-20 23:32:21 +02:00
)
2023-04-14 14:27:45 +02:00
timer.display()
except KeyboardInterrupt:
pass
2023-04-20 23:32:21 +02:00
finally:
curses.echo()
curses.nocbreak()
curses.endwin()
2023-04-14 14:27:45 +02:00
if __name__ == "__main__":
main()