import sys import csv import os # This is a local script are we supposed to indicate that? import prompt_window import time from datetime import datetime, timedelta from pathlib import Path from pytz import timezone, utc from prompt_toolkit import PromptSession from prompt_toolkit.history import FileHistory #from prompt_toolkit.auto_suggest import AutoSuggestFromHistory from prompt_toolkit.completion import WordCompleter session = PromptSession(history=FileHistory(os.path.expanduser('~/.pomodoro_history'))) # Move to a file: words = WordCompleter(['NCHFA', 'PECE', 'Housing Works', 'Internal']) #os.path.dirname(os.path.realpath(__file__)) #completion_list = WordCompleter(history=FileHistory(os.path.dirname(os.path.realpath(__file__)) + '.auto_complete')) WORK_MIN = 25 # Minutes for a work session SHORT_MIN = 5 # Minutes for a short break LONG_MIN = 15 # Minutes for a long break (NOT YET IMPLEMENTED) INTERRUPTED_MIN = 1 # Minimum minutes to ask to record an interrupted Pomodoro QUIET = True GUI = True def pomodoro_prompt_plan(whatdid = ''): """ Ask about what task will be worked on (showing last thing worked on) """ text = "What're you gonna do? " # NOTE: If we can do system notifications that when clicked will focus the terminal # running pomodoro prompt, that might be even better than the pop-up GUI text entry. if GUI: whatnext = prompt_window.prompt(prompt=text, task=whatdid) else: whatnext = session.prompt(message = text + '\n', default = whatdid or '', vi_mode = True, completer = words, complete_while_typing = True).strip() # Pressing cancel returns None, but we want to just treat it as an empty string. if whatnext is None: whatnext = '' return whatnext def pomodoro_prompt_report(whatnext): """ Ask what task was completed, showing the last task claimed to be being worked on """ text = "What'd you do? " if GUI: whatdid = prompt_window.prompt(prompt=text, task=whatnext) else: whatdid = session.prompt(message = text + '\n', default = whatnext or '', vi_mode = True, completer = words, complete_while_typing = True).strip() # Pressing cancel returns None, but we want to just treat it as an empty string. if whatdid is None: whatdid = '' return whatdid def quit_prompt(): """ Confirm exit or start a new pomodoro """ print('\nPress p to start a new pomodoro, q to quit') choice = input('p/q: ').strip().lower() if choice in ('p', 'pomodoro'): return elif choice in ('q', 'quit', 'exit'): sys.exit(0) else: quit_prompt() def log_step(text, utc_start, end_section=False): """ Write detailed log of all events """ timelog_file = Path(prepare_file(utc_start, ext='log', daily=True)) timelog_file.touch(exist_ok=True) with timelog_file.open('a') as timelog: timelog.write(text + '\n') if end_section: timelog.write('\n\n') def record_task(whatnext, whatdid, start, end=None): """ Record completed pomodoro to CSV """ if end is None: end = datetime.now(utc) filepath = prepare_file(start) if not os.path.isfile(filepath): with open(filepath, 'w', newline='') as csvfile: log = csv.writer(csvfile) log.writerow(['started', 'recorded', 'description', 'intention']) with open(filepath, 'a', newline='') as csvfile: log = csv.writer(csvfile) log.writerow([start, end, whatdid, whatnext]) def str_minutes(time_diff): """ Return at least whole seconds from a time difference """ return str(time_diff).split('.')[0] def countdown_to(until): """ Display a timer counting down to until """ while datetime.now(utc) <= until: to_go = until-datetime.now(utc) print('\r', str_minutes(to_go), sep='', end='') time.sleep(1) def prepare_file(utc_start, ext='csv', daily=False): # Save timelogs relative to script directory (NOT from where called) logpath = os.path.dirname(os.path.realpath(__file__)) + '/log' # Ensure log directory exists. if not os.path.exists(logpath): os.makedirs(logpath) # We consider 3am the switchover to a new day, so we skip extra math # and use Pacific time to get three hours later than our Eastern time. logpath_tz = timezone('America/Los_Angeles') logpath_date = utc_start.astimezone(logpath_tz) timelog_path = logpath + '/' + str(logpath_date.year); if (daily): timelog_path += '-' \ + format(logpath_date.month, '02') + '-' \ + format(logpath_date.day, '02') timelog_path += '.' + ext return timelog_path def bing(): if GUI: from playsound import playsound playsound('res/timesup.aac') else: sys.stdout.write('\a') sys.stdout.flush() def main(): whatdid = '' global GUI, QUIET if "--no-gui" in sys.argv: GUI = False if "--noisy" in sys.argv: QUIET = False while True: whatnext = pomodoro_prompt_plan(whatdid) start = datetime.now(utc) end = start + timedelta(minutes=WORK_MIN) log_step('Start with plan: ' + whatnext, start) try: print('Working on: ' + (whatnext or "(...it must be a mystery)")) countdown_to(end) if not QUIET: bing() whatdid = pomodoro_prompt_report(whatnext) record_task(whatnext, whatdid, start) log_step('Completed pomodoro: ' + whatdid, start, True) print('\nWorked on: ' + whatdid) print('Break time!') # We're taking a break. Clear out task start/end times. start = None end = datetime.now(utc) + timedelta(minutes=SHORT_MIN) countdown_to(end) if not QUIET: bing() continue except KeyboardInterrupt: if start is None: quit_prompt() continue time_spent = datetime.now(utc) - start if time_spent < timedelta(minutes=INTERRUPTED_MIN): quit_prompt() continue time_spent_str = str_minutes(time_spent) print('\n{} time spent, save?'.format(time_spent_str)) choice = input('[y]/n: ').strip() if choice.lower() in ('y', 'yes', ''): whatdid = pomodoro_prompt_report(whatnext) record_task(whatnext, whatdid, start) log_step('Incomplete (' + time_spent_str + ') pomodoro: ' + whatdid, start, True) quit_prompt() else: print('What did you break?') # If we somehow end up here, try a last-ditch effort to save. record_task(whatnext, 'Incomplete, interrupted task:' + whatnext, start) log_step('Incomplete, interrupted task:' + whatnext, start, True) if __name__ == '__main__': main()