Project 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 - from a text menu.

3 modules · 2 recorded sessionstext-menu UI in the terminalupdated 2026-09-27

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 typed
  • tasks.eml - the task rules: add, find, mark done, remove, sort by priority (stable: equal priorities keep the order they were added in), pending
  • view.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")

Built on these corpus cases