Created
June 7, 2018 00:59
-
-
Save yabberyabber/144d865f2449b6e0904e4ef9b44ee208 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env python3 | |
""" | |
Simple pomodoro tracker that has been heavily customized to my personal needs. | |
Each pomodoro will wait for user input before continuing on meaning that | |
working a little long into your break does not reduce the duration of your | |
break (the break timer doesn't start until you press enter). When it is time | |
to move on, pomodoro will present a notification repeatedly using libnotify. | |
The default pomodoro schedule is ---|--- where each - is 25 minutes of work | |
followed by 5 minutes of break and where | is 25 minutes of break. A | |
different schedule can be passed in just by passing that string as the first | |
argument to the program. Some shells will try to parse | as a pipe so | |
long breaks can also be represented as 0. | |
> ./main.py ___0___ | |
Represents the default arrangement. 3 work pomodoro, 1 break pomodoro, and 3 | |
work pomodoro | |
> ./main.py _0__0_ | |
1 work pomodoro, 1 break pomodoro, 2 work pomodoro, 1 break pomodoro, 1 work | |
pomodoro. | |
""" | |
import sys | |
import time | |
import subprocess | |
import threading | |
import itertools | |
import select | |
import datetime | |
def notify(summary, *body): | |
""" | |
Send a notification using the notify-send command (check that | |
libnotify is installed on local machine before running this) | |
""" | |
def child_process(): | |
subprocess.Popen(['notify-send', summary, '\n'.join(body)]) | |
t = threading.Thread(name='alarm', target=child_process, | |
args=()) | |
t.start() | |
def clear_stdin(): | |
""" | |
Flush standard in of any residual input | |
""" | |
while True: | |
if select.select([sys.stdin,], [], [], 0.0)[0]: | |
sys.stdin.readline() | |
else: | |
break | |
def delay(minutes): | |
""" | |
Wraper over time.sleep (wrapping this makes testing easier) | |
""" | |
time.sleep(minutes * 60) | |
class Pomodoro: | |
""" | |
Not a class; this is a namespace containing parameters that define pomodoro | |
""" | |
WORK = 1 | |
REST = 2 | |
LONG_REST = 3 | |
DURATION = { | |
WORK: 25, | |
REST: 5, | |
LONG_REST: 25, | |
} | |
def untiltext(duration): | |
untiltime = datetime.datetime.now() + datetime.timedelta(minutes=duration) | |
return "(until {})".format(untiltime.strftime("%I:%M")) | |
def alarm(phase, duration, schedule, wait_for_awk=False): | |
""" | |
Send the user a notification repeatedly until they acknowledge the it. Do | |
not continue along the schedule until the notification is acknowledge (by | |
pressing enter) | |
""" | |
summary = "Begin {} for {} minutes".format(phase, duration) | |
print() | |
print(summary) | |
print(schedule) | |
print(untiltext(duration)) | |
print("\n") | |
notify(summary, schedule, untiltext(duration)) | |
clear_stdin() | |
while True: | |
print("Press enter to continue") | |
stdin, stdout, stderr = select.select([sys.stdin,], [], [], 30.0) | |
if stdin: | |
print("\tInput awknowledged... beginning phase") | |
clear_stdin() | |
break | |
notify(summary, schedule, untiltext(duration)) | |
def parse_schedule(schedule): | |
""" | |
Take a string-representation of a pomodoro schedule and parse it into | |
a machine-representation. The machine representation is a list of | |
Pomodoro.WORK, Pomodoro.REST, or Pomodoro.LONG_REST. | |
""" | |
print("Pomodoro Schedule:") | |
result = [] | |
duration_minutes = 0 | |
for char in schedule: | |
if char in ['-', '_']: | |
result.append(Pomodoro.WORK) | |
duration_minutes += Pomodoro.DURATION[Pomodoro.WORK] | |
print("\tWork until {}".format(untiltext(duration_minutes))) | |
result.append(Pomodoro.REST) | |
duration_minutes += Pomodoro.DURATION[Pomodoro.REST] | |
print("\tShort rest until {}".format(untiltext(duration_minutes))) | |
elif char in ['|', ' ', '0']: | |
result.append(Pomodoro.LONG_REST) | |
duration_minutes += Pomodoro.DURATION[Pomodoro.LONG_REST] | |
print("\tLong rest until {}".format(untiltext(duration_minutes))) | |
else: | |
raise Exception( | |
("Invalid Schedule: '{}'. " | |
"Schedules can only contain _,|").format( | |
schedule)) | |
print("\n\n") | |
return result | |
def stringatize_schedule(schedule, num_completed): | |
result = "" | |
for idx, item in enumerate(schedule): | |
if idx < num_completed: | |
result += " * " | |
elif item == Pomodoro.WORK: | |
result += " _ " | |
elif item == Pomodoro.REST: | |
pass | |
elif item == Pomodoro.LONG_REST: | |
result += " | " | |
return result | |
def pomodoros(schedule): | |
""" | |
Execute a given pomodoro schedule where a schedule is a | |
list of Pomodoro.WORK, Pomodoro.REST, or Pomodoro.LONG_REST | |
""" | |
completed_pomodoro = 0 | |
for item in schedule: | |
duration = Pomodoro.DURATION[item] | |
schedule_str = stringatize_schedule(schedule, completed_pomodoro) | |
if item == Pomodoro.WORK: | |
alarm("work", duration, schedule_str, True) | |
if item == Pomodoro.REST: | |
alarm("short rest", duration, schedule_str, True) | |
completed_pomodoro += 1 | |
if item == Pomodoro.LONG_REST: | |
alarm("long rest", duration, schedule_str, True) | |
completed_pomodoro += 1 | |
delay(duration) | |
notify("Schedule completed") | |
if __name__ == "__main__": | |
schedule = sys.argv[1] if len(sys.argv) > 1 else '---|---' | |
pomodoros(parse_schedule(schedule)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment