Forked from goFrendiAsgard/gomodorokanbanreminder.py
Created
August 13, 2016 01:46
-
-
Save agusmakmun/0555149739b40fc671738adfaba048f6 to your computer and use it in GitHub Desktop.
Go Frendi's pomodoro, kanban, and reminder application. Work on terminal, written by using Python
This file contains hidden or 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
import os, json, time, datetime, math, curses | |
#global variables | |
KANBAN_FILE = os.path.expanduser("~/.kanban.json") | |
DEFAULT_KANBAN = { | |
"tasks" : [], | |
"boards" : ["To do", "Doing", "Done"], | |
"work_time" : 25 * 60, | |
"rest_time" : 5 * 60 | |
} | |
COUNTER = DEFAULT_KANBAN["work_time"] | |
def str_to_timestamp(string): | |
return time.mktime(datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S").timetuple()) | |
def timestamp_to_str(timestamp): | |
time_tuple = time.localtime(timestamp) | |
return time.strftime("%Y-%m-%d %H:%M:%S", time_tuple) | |
def menu(): | |
print("(p) Pomodoro (a) Add Task (d) Delete Task") | |
print("(c) Configuration (e) Edit Task (q) Quit Program") | |
def load_kanban(): | |
if not os.path.exists(KANBAN_FILE): | |
return DEFAULT_KANBAN | |
else: | |
with open(KANBAN_FILE, "r") as infile: | |
return json.load(infile) | |
def save_kanban(kanban): | |
with open(KANBAN_FILE, "w+") as outfile: | |
json.dump(kanban, outfile) | |
def change_configuration(kanban): | |
# prompt and save work time | |
work_time = raw_input("Work time in minutes (previous value: %d) : " % (int(kanban["work_time"])/60)) | |
if work_time.strip().isdigit(): | |
work_time = int(work_time) | |
kanban["work_time"] = work_time * 60 | |
# prompt and save rest time | |
rest_time = raw_input("Rest time in minutes (previous value: %d) : " % (int(kanban["rest_time"])/60)) | |
if rest_time.strip().isdigit(): | |
rest_time = int(rest_time) | |
kanban["rest_time"] = rest_time * 60 | |
# prompt and save boards | |
boards = raw_input("Boards, separated by '|' (previous value: '%s') : " % (" | ".join(kanban["boards"]))) | |
if boards.strip() != "": | |
boards = boards.split("|") | |
for i,board in enumerate(boards): | |
boards[i] = board.strip() | |
kanban["boards"] = boards | |
# return the modified kanban | |
return kanban | |
def get_available_board_option(kanban): | |
boards = list(kanban["boards"]) | |
for i, board in enumerate(boards): | |
boards[i] = "'" + board + "'" | |
return ", ".join(kanban["boards"]) | |
def normalize_board(board_name, kanban): | |
boards = kanban["boards"] | |
for board in boards: | |
if board.replace(" ", "").lower() == board_name.replace(" ", "").lower(): | |
return board | |
return board_name.strip() | |
def add_task(kanban): | |
# task name | |
task_name = raw_input("Task name : ") | |
# board | |
default_board = kanban["boards"][0] if len(kanban)>0 else "" | |
board = raw_input("Board name (available : %s), (default : '%s') : " %(get_available_board_option(kanban), default_board)) | |
board = normalize_board(board, kanban) | |
if board not in kanban["boards"]: | |
kanban["boards"].append(board) | |
# remind_on | |
remind_on = raw_input("Remind on (format : Y-m-d H:M:S), (default : '') : ") | |
# remind_until | |
remind_until = raw_input("Remind until (format : Y-m-d H:M:S), (default : '') : ") | |
if remind_until == "" and remind_on != "": | |
remind_until = timestamp_to_str(str_to_timestamp(remind_on)+(60*30)) # default remind until 30 minutes | |
# add to kanban | |
new_task = { | |
"name" : task_name, | |
"board" : board, | |
"remind_on" : remind_on, | |
"remind_until" : remind_until | |
} | |
kanban["tasks"].append(new_task) | |
return kanban | |
def delete_task(kanban): | |
task_id = raw_input("Task id : ") | |
if task_id.isdigit(): | |
kanban["tasks"].pop(int(task_id)-1) | |
return kanban | |
def edit_task(kanban): | |
task_id = raw_input("Task id : ") | |
if task_id.isdigit(): | |
task_id = int(task_id) - 1 | |
task = kanban["tasks"][task_id] | |
# task name | |
default_task_name = task["name"] | |
task_name = raw_input("Task name (previous value : %s) : " % (default_task_name)) | |
if task_name.strip() != "": | |
task["name"] = task_name | |
# board | |
default_board = task["board"] | |
board = raw_input("Board name (available : %s), (previous value : '%s') : " %(get_available_board_option(kanban), default_board)) | |
board = normalize_board(board, kanban) | |
if board.strip() != "": | |
if board not in kanban["boards"]: | |
kanban["boards"].append(board) | |
task["board"] = board | |
# remind_on | |
default_remind_on = task["remind_on"] | |
remind_on = raw_input("Remind on (format : yyyy-mm-dd H:i:s), (previous value : '%s') : " % (default_remind_on)) | |
if remind_on.strip() != "": | |
task["remind_on"] = remind_on | |
elif default_remind_on.strip() != "": | |
keep_value = raw_input("Do you want to keep the previous value '%s' (y/n)" % (default_remind_on)) | |
if keep_value.lower() == "n": | |
task["remind_on"] = "" | |
# remind_until | |
if task["remind_on"].strip() == "": | |
task["remind_until"] = "" | |
else: | |
default_remind_until = task["remind_until"] | |
remind_until = raw_input("Remind until (format : yyyy-mm-dd H:i:s), (previous value : '%s') : " % (default_remind_until)) | |
if remind_until.strip() != "": | |
task["remind_until"] = remind_until | |
return kanban | |
def format_seconds(seconds): | |
m, s = divmod(seconds, 60) | |
h, m = divmod(m, 60) | |
return "%d:%02d:%02d" % (h, m, s) | |
def kanban_display(kanban): | |
boards = kanban["boards"] | |
tasks = kanban["tasks"] | |
board_section = {} | |
board_width = {} | |
max_card_count = 0 | |
reminded_tasks = [] | |
current_time = time.time() | |
for i, task in enumerate(tasks): | |
if task["remind_on"].strip() != "" and task["remind_until"].strip() != "": | |
time_start = str_to_timestamp(task["remind_on"]) | |
time_stop = str_to_timestamp(task["remind_until"]) | |
if time_start < current_time and time_stop > current_time: | |
reminded_tasks.append(str(i+1) + " . " + task["name"]) | |
# get board_width and board_section | |
for board in boards: | |
# get board_task | |
board_task = [] | |
for i, card in enumerate(tasks): | |
if card["board"] == board: | |
card["id"] = str(i+1) | |
board_task.append(card) | |
# get max_card_count | |
if len(board_task) > max_card_count: | |
max_card_count = len(board_task) | |
# get max_width | |
if len(board_task) == 0: | |
max_width = len(board) | |
else: | |
max_width = max(len(board),23) # 23 is count of character for yyyy-mm-dd H:i:s | |
for i, card in enumerate(board_task): | |
width = len(card["id"]) + 3 + len(card["name"]) | |
if width > max_width: | |
max_width = width | |
board_section[board] = board_task | |
board_width[board] = max_width | |
# prepare kanban | |
first_line = [] | |
card_title_list = [] | |
card_remind_on_list = [] | |
card_remind_until_list = [] | |
for board in boards: | |
width = board_width[board] | |
board_title = board.ljust(width, " ") | |
first_line.append(board_title) | |
for i in range(max_card_count): | |
card_title = [] | |
card_remind_on = [] | |
card_remind_until = [] | |
for board in boards: | |
width = board_width[board] | |
if i>=len(board_section[board]): | |
card_title.append("".ljust(width, " ")) | |
card_remind_on.append("".rjust(width, " ")) | |
card_remind_until.append("".rjust(width, " ")) | |
else: | |
card = board_section[board][i] | |
title = card["id"] + " . " + card["name"] | |
card_title.append(title.ljust(width, " ")) | |
card_remind_on.append(card["remind_on"].rjust(width, " ")) | |
card_remind_until.append(card["remind_until"].rjust(width, " ")) | |
card_title_list.append(card_title) | |
card_remind_on_list.append(card_remind_on) | |
card_remind_until_list.append(card_remind_until) | |
# draw kanban | |
display = "" | |
if len(reminded_tasks) > 0: | |
display += "Reminder :\n" | |
for task in reminded_tasks: | |
display += "* %s\n" %(task) | |
if len(reminded_tasks) > 0: | |
display += "\n\n" | |
# the kanban | |
first_line = " | ".join(first_line) | |
display += first_line + "\n" # board title | |
display += "=" * len(first_line) + "\n" # the separator | |
for i in range(max_card_count): | |
card_title = " | ".join(card_title_list[i]) | |
card_remind_on = " | ".join(card_remind_on_list[i]) | |
card_remind_until = " | ".join(card_remind_until_list[i]) | |
display += card_title + "\n" | |
display += card_remind_on + "\n" | |
display += card_remind_until + "\n" | |
display += "-" * len(first_line) + "\n" # the separator | |
return display | |
def display_pomodoro(stdscr, counter, is_working, is_pause, kanban): | |
formatted_counter = format_seconds(counter) | |
counter_status = "PAUSED" if is_pause else " " | |
working_status = "WORKING" if is_working else "REST " | |
command_bar_1 = "(w) Work Mode (r) Rest Mode (space) Pause/Resume" | |
command_bar_2 = " (x) Exit Pomodoro (q) Quit Program" | |
# clear the screen and redraw | |
try: | |
stdscr.addstr(0,0, "%s | %s %s" %(working_status, formatted_counter, counter_status), curses.A_BOLD) | |
stdscr.addstr(1,0, command_bar_1) | |
stdscr.addstr(2,0, command_bar_2) | |
stdscr.addstr(4,0, kanban_display(kanban)) | |
stdscr.refresh() | |
except curses.error: | |
pass | |
def pomodoro(kanban): | |
quit = False | |
# some variables | |
is_working = True | |
is_pause = False | |
counter = int(kanban["work_time"]) | |
initial_time = math.ceil(time.time()) | |
# create stdscr object, make getch non-blocking | |
stdscr = curses.initscr() | |
stdscr.nodelay(1) | |
# turn off echo, hide cursor | |
curses.noecho() | |
curses.curs_set(0) | |
while True: | |
# the process: calculate counter | |
current_time = math.floor(time.time()) | |
if current_time - initial_time >=1: | |
initial_time = current_time | |
if not is_pause: # if is_pause, don't reduce the counter | |
stdscr.clear() | |
counter -= 1 | |
# the display | |
display_pomodoro(stdscr, counter, is_working, is_pause, kanban) | |
# the process: calculate is_working | |
if counter <= 0: | |
counter = int(kanban["rest_time"]) if is_working else int(kanban["work_time"]) | |
curses.beep() | |
# the command | |
command = stdscr.getch() | |
if command == ord("x") or command == ord("X"): # exit | |
break | |
elif command == ord(" "): # pause/resume | |
is_pause = not is_pause | |
elif not is_working and (command == ord("w") or command == ord("W")): # work | |
is_working = True | |
counter = int(kanban["work_time"]) | |
curses.beep() | |
elif is_working and (command == ord("r") or command == ord("R")): # resume | |
is_working = False | |
counter = int(kanban["rest_time"]) | |
curses.beep() | |
elif command == ord("q") or command == ord("Q"): #quit | |
quit = True | |
break | |
curses.endwin() | |
return quit | |
if __name__ == "__main__": | |
# load kanban | |
kanban = load_kanban() | |
choice = "p" | |
while True: | |
if choice == "p" or choice == "P": # user choose "pomodoro & kanban" | |
quit = pomodoro(kanban) | |
if quit: | |
break | |
elif choice == "c" or choice == "C": # user choose "configuration" | |
kanban = change_configuration(kanban) | |
elif choice == "a" or choice == "A": # user choose "add task" | |
kanban = add_task(kanban) | |
elif choice == "e" or choice == "E": # user choose "edit task" | |
kanban = edit_task(kanban) | |
elif choice == "d" or choice == "D": # user choose "delete task" | |
kanban = delete_task(kanban) | |
elif choice == "q" or choice == "Q": # user choose "exit" | |
break | |
else: | |
print("Invalid command") | |
# save kanban and show pomodoro | |
save_kanban(kanban) | |
print("\n"*100) # hack to clear the screen | |
print(kanban_display(kanban)) | |
menu() # show the menu and read the user"s choice | |
choice = raw_input("Your choice : ") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment