Todo list
Add tasks with a priority, mark them done, delete them, and list them in the order added, by priority, or only the ones still pending - from a text menu.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
Add tasks with a priority (1 high, 2 medium, 3 low), mark them done, delete them, and list them three ways: in the order added, by priority, or only the ones still pending. A text menu; the tasks live while the program runs.
main.eml- the menu loop and the checks on what is typedtasks.eml- the task rules: add, find, mark done, remove, sort by priority (stable: equal priorities keep the order they were added in), pendingview.eml- the menu and the task lists as they appear on screen
Sessions: sessions/basic.in adds four tasks - two of them medium, so the priority list shows that equal priorities keep the order they were added in - and uses every list; sessions/bad-input.in types an unknown choice, an empty title and one of spaces only, a priority that is a word and one out of range, and ids that are not there.
Built on the verified corpus cases task-priority-bucketer (tasks with a 1-10 priority split into groups) and simple-queue (a list kept in arrival order).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal
== Todo: 0 tasks, 0 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 9
Unknown choice: 9
== Todo: 0 tasks, 0 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 1
title>
A task needs a title.
== Todo: 0 tasks, 0 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 1
title>
A task needs a title.
== Todo: 0 tasks, 0 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 1
title> Test
priority 1-3> high
Priority must be 1, 2 or 3.
== Todo: 0 tasks, 0 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 1
title> Test
priority 1-3> 5
Priority must be 1, 2 or 3.
== Todo: 0 tasks, 0 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 3
id> x
No task with that id.
== Todo: 0 tasks, 0 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 3
id> 42
No task with that id.
== Todo: 0 tasks, 0 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 4
id> 7
No task with that id.
== Todo: 0 tasks, 0 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 7
Bye.
What was typed (18 lines)
9
1
1
1
Test
high
1
Test
5
3
x
3
42
4
7
7
basic
interpreter: byte-equal
== Todo: 0 tasks, 0 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 1
title> Write report
priority 1-3> 2
Added #1.
== Todo: 1 task, 0 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 1
title> Water plants
priority 1-3> 3
Added #2.
== Todo: 2 tasks, 0 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 1
title> Fix bug
priority 1-3> 1
Added #3.
== Todo: 3 tasks, 0 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 1
title> Call bank
priority 1-3> 2
Added #4.
== Todo: 4 tasks, 0 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 2
-- all tasks: 4 --
[ ] #1 Write report (medium)
[ ] #2 Water plants (low)
[ ] #3 Fix bug (high)
[ ] #4 Call bank (medium)
== Todo: 4 tasks, 0 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 3
id> 2
Done: #2.
== Todo: 4 tasks, 1 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 5
-- by priority: 4 --
[ ] #3 Fix bug (high)
[ ] #1 Write report (medium)
[ ] #4 Call bank (medium)
[x] #2 Water plants (low)
== Todo: 4 tasks, 1 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 6
-- pending: 3 --
[ ] #1 Write report (medium)
[ ] #3 Fix bug (high)
[ ] #4 Call bank (medium)
== Todo: 4 tasks, 1 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 4
id> 3
Deleted #3.
== Todo: 3 tasks, 1 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 2
-- all tasks: 3 --
[ ] #1 Write report (medium)
[x] #2 Water plants (low)
[ ] #4 Call bank (medium)
== Todo: 3 tasks, 1 done ==
1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit
choice> 7
Bye.
What was typed (21 lines)
1
Write report
2
1
Water plants
3
1
Fix bug
1
1
Call bank
2
2
3
2
5
6
4
3
2
7
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P001 todo list: add tasks with a priority, mark them done, delete them, and
# list them in the order added, by priority, or only the ones still pending.
# The tasks live while the program runs; the first twenty projects do not
# save to files.
import tasks
import view
def ask_number(prompt):
# The whole number typed, or None if what was typed is not one.
try:
return int(input(prompt))
except ValueError:
return None
[] => todo
1 => next_id
True => running
while running:
view.menu(len(todo), tasks.count_done(todo))
input("choice> ") => choice
if choice == "1":
input("title> ") => title
if tasks.blank(title):
"A task needs a title." ^0
else:
ask_number("priority 1-3> ") => p
if p == None or p < 1 or p > 3:
"Priority must be 1, 2 or 3." ^0
else:
tasks.add(todo, next_id, title, p) => todo
("Added #" + str(next_id) + ".") ^0
next_id + 1 => next_id
elif choice == "2":
view.show("all tasks", todo)
elif choice == "3":
ask_number("id> ") => n
if n != None and tasks.mark_done(todo, n):
("Done: #" + str(n) + ".") ^0
else:
"No task with that id." ^0
elif choice == "4":
ask_number("id> ") => n
if n != None and tasks.find(todo, n) != None:
tasks.remove(todo, n) => todo
("Deleted #" + str(n) + ".") ^0
else:
"No task with that id." ^0
elif choice == "5":
view.show("by priority", tasks.by_priority(todo))
elif choice == "6":
view.show("pending", tasks.pending(todo))
elif choice == "7":
False => running
else:
("Unknown choice: " + choice) ^0
"Bye." ^0
Python projection (main.py)
import tasks
import view
def ask_number(prompt):
try:
return int(input(prompt))
except ValueError:
return None
todo = []
next_id = 1
running = True
while running:
view.menu(len(todo), tasks.count_done(todo))
choice = input("choice> ")
if choice == "1":
title = input("title> ")
if tasks.blank(title):
print("A task needs a title.")
else:
p = ask_number("priority 1-3> ")
if p == None or p < 1 or p > 3:
print("Priority must be 1, 2 or 3.")
else:
todo = tasks.add(todo, next_id, title, p)
print("Added #" + str(next_id) + ".")
next_id = next_id + 1
elif choice == "2":
view.show("all tasks", todo)
elif choice == "3":
n = ask_number("id> ")
if n != None and tasks.mark_done(todo, n):
print("Done: #" + str(n) + ".")
else:
print("No task with that id.")
elif choice == "4":
n = ask_number("id> ")
if n != None and tasks.find(todo, n) != None:
todo = tasks.remove(todo, n)
print("Deleted #" + str(n) + ".")
else:
print("No task with that id.")
elif choice == "5":
view.show("by priority", tasks.by_priority(todo))
elif choice == "6":
view.show("pending", tasks.pending(todo))
elif choice == "7":
running = False
else:
print("Unknown choice: " + choice)
print("Bye.")
tasks.eml
eml# P001 todo list - the task rules, kept apart from the screen so they can be
# read on their own. A task is a list [id, title, priority, done]: priority 1
# is high, 2 medium, 3 low; done is True or False. Ids are never reused.
def add(tasks, task_id, title, priority):
# A new list with the task at the end; the old list is left as it was.
return tasks + [[task_id, title, priority, False]]
def blank(text):
# True if the text is empty or only spaces - such a title is refused.
for c in text:
if c != " ":
return False
return True
def find(tasks, task_id):
# The task with this id, or None.
for t in tasks:
if t[0] == task_id:
return t
return None
def mark_done(tasks, task_id):
# Marks the task done in place. True if there was such a task.
find(tasks, task_id) => t
if t == None:
return False
True => t[3]
return True
def remove(tasks, task_id):
# A new list without the task with this id.
return [t for t in tasks if t[0] != task_id]
def by_priority(tasks):
# A new list, highest priority first. Tasks of equal priority keep the
# order they were added in: an insertion sort that places each task after
# every task already there with the same or a higher priority.
[] => ordered
for t in tasks:
0 => i
while i < len(ordered) and ordered[i][2] <= t[2]:
i + 1 => i
ordered[0:i] + [t] + ordered[i:len(ordered)] => ordered
return ordered
def pending(tasks):
# Only the tasks not done yet, in the order they were added.
return [t for t in tasks if not t[3]]
def count_done(tasks):
0 => n
for t in tasks:
if t[3]:
n + 1 => n
return n
Python projection (tasks.py)
def add(tasks, task_id, title, priority):
return tasks + [[task_id, title, priority, False]]
def blank(text):
for c in text:
if c != " ":
return False
return True
def find(tasks, task_id):
for t in tasks:
if t[0] == task_id:
return t
return None
def mark_done(tasks, task_id):
t = find(tasks, task_id)
if t == None:
return False
t[3] = True
return True
def remove(tasks, task_id):
return [t for t in tasks if t[0] != task_id]
def by_priority(tasks):
ordered = []
for t in tasks:
i = 0
while i < len(ordered) and ordered[i][2] <= t[2]:
i = i + 1
ordered = ordered[0:i] + [t] + ordered[i:len(ordered)]
return ordered
def pending(tasks):
return [t for t in tasks if not t[3]]
def count_done(tasks):
n = 0
for t in tasks:
if t[3]:
n = n + 1
return n
view.eml
eml# P001 todo list - what the screen shows: the menu and the task lists.
def priority_name(p):
if p == 1:
return "high"
if p == 2:
return "medium"
return "low"
def line(t):
# One task as a row: [x] #2 Water plants (low)
"[ ]" => mark
if t[3]:
"[x]" => mark
return mark + " #" + str(t[0]) + " " + t[1] + " (" + priority_name(t[2]) + ")"
def show(title, tasks):
"" ^0
("-- " + title + ": " + str(len(tasks)) + " --") ^0
if len(tasks) == 0:
" (nothing here)" ^0
for t in tasks:
(" " + line(t)) ^0
def count(n, word):
# "1 task", "2 tasks"
if n == 1:
return "1 " + word
return str(n) + " " + word + "s"
def menu(total, done):
"" ^0
("== Todo: " + count(total, "task") + ", " + str(done) + " done ==") ^0
"1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit" ^0
Python projection (view.py)
def priority_name(p):
if p == 1:
return "high"
if p == 2:
return "medium"
return "low"
def line(t):
mark = "[ ]"
if t[3]:
mark = "[x]"
return mark + " #" + str(t[0]) + " " + t[1] + " (" + priority_name(t[2]) + ")"
def show(title, tasks):
print("")
print("-- " + title + ": " + str(len(tasks)) + " --")
if len(tasks) == 0:
print(" (nothing here)")
for t in tasks:
print(" " + line(t))
def count(n, word):
if n == 1:
return "1 " + word
return str(n) + " " + word + "s"
def menu(total, done):
print("")
print("== Todo: " + count(total, "task") + ", " + str(done) + " done ==")
print("1) add 2) list 3) done 4) delete 5) by priority 6) pending 7) quit")