{
  "id": "P001",
  "slug": "todo-list",
  "key": "P001-todo-list",
  "title": "Todo list",
  "summary": "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.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P001 - Todo list\n\nAdd tasks with a priority (1 high, 2 medium, 3 low), mark them done, delete\nthem, and list them three ways: in the order added, by priority, or only the\nones still pending. A text menu; the tasks live while the program runs.\n\n- `main.eml` - the menu loop and the checks on what is typed\n- `tasks.eml` - the task rules: add, find, mark done, remove, sort by priority\n  (stable: equal priorities keep the order they were added in), pending\n- `view.eml` - the menu and the task lists as they appear on screen\n\nSessions: `sessions/basic.in` adds four tasks - two of them medium, so the\npriority list shows that equal priorities keep the order they were added in -\nand uses every list; `sessions/bad-input.in` types an unknown choice, an empty\ntitle and one of spaces only, a priority that is a word and one out of range,\nand ids that are not there.\n\nBuilt on the verified corpus cases `task-priority-bucketer` (tasks with a\n1-10 priority split into groups) and `simple-queue` (a list kept in arrival\norder).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P001 todo list: add tasks with a priority, mark them done, delete them, and\n# list them in the order added, by priority, or only the ones still pending.\n# The tasks live while the program runs; the first twenty projects do not\n# save to files.\nimport tasks\nimport view\n\ndef ask_number(prompt):\n    # The whole number typed, or None if what was typed is not one.\n    try:\n        return int(input(prompt))\n    except ValueError:\n        return None\n\n[] => todo\n1 => next_id\nTrue => running\nwhile running:\n    view.menu(len(todo), tasks.count_done(todo))\n    input(\"choice> \") => choice\n    if choice == \"1\":\n        input(\"title> \") => title\n        if tasks.blank(title):\n            \"A task needs a title.\" ^0\n        else:\n            ask_number(\"priority 1-3> \") => p\n            if p == None or p < 1 or p > 3:\n                \"Priority must be 1, 2 or 3.\" ^0\n            else:\n                tasks.add(todo, next_id, title, p) => todo\n                (\"Added #\" + str(next_id) + \".\") ^0\n                next_id + 1 => next_id\n    elif choice == \"2\":\n        view.show(\"all tasks\", todo)\n    elif choice == \"3\":\n        ask_number(\"id> \") => n\n        if n != None and tasks.mark_done(todo, n):\n            (\"Done: #\" + str(n) + \".\") ^0\n        else:\n            \"No task with that id.\" ^0\n    elif choice == \"4\":\n        ask_number(\"id> \") => n\n        if n != None and tasks.find(todo, n) != None:\n            tasks.remove(todo, n) => todo\n            (\"Deleted #\" + str(n) + \".\") ^0\n        else:\n            \"No task with that id.\" ^0\n    elif choice == \"5\":\n        view.show(\"by priority\", tasks.by_priority(todo))\n    elif choice == \"6\":\n        view.show(\"pending\", tasks.pending(todo))\n    elif choice == \"7\":\n        False => running\n    else:\n        (\"Unknown choice: \" + choice) ^0\n\"Bye.\" ^0\n",
      "python": "import tasks\nimport view\n\ndef ask_number(prompt):\n    try:\n        return int(input(prompt))\n    except ValueError:\n        return None\n\ntodo = []\nnext_id = 1\nrunning = True\nwhile running:\n    view.menu(len(todo), tasks.count_done(todo))\n    choice = input(\"choice> \")\n    if choice == \"1\":\n        title = input(\"title> \")\n        if tasks.blank(title):\n            print(\"A task needs a title.\")\n        else:\n            p = ask_number(\"priority 1-3> \")\n            if p == None or p < 1 or p > 3:\n                print(\"Priority must be 1, 2 or 3.\")\n            else:\n                todo = tasks.add(todo, next_id, title, p)\n                print(\"Added #\" + str(next_id) + \".\")\n                next_id = next_id + 1\n    elif choice == \"2\":\n        view.show(\"all tasks\", todo)\n    elif choice == \"3\":\n        n = ask_number(\"id> \")\n        if n != None and tasks.mark_done(todo, n):\n            print(\"Done: #\" + str(n) + \".\")\n        else:\n            print(\"No task with that id.\")\n    elif choice == \"4\":\n        n = ask_number(\"id> \")\n        if n != None and tasks.find(todo, n) != None:\n            todo = tasks.remove(todo, n)\n            print(\"Deleted #\" + str(n) + \".\")\n        else:\n            print(\"No task with that id.\")\n    elif choice == \"5\":\n        view.show(\"by priority\", tasks.by_priority(todo))\n    elif choice == \"6\":\n        view.show(\"pending\", tasks.pending(todo))\n    elif choice == \"7\":\n        running = False\n    else:\n        print(\"Unknown choice: \" + choice)\nprint(\"Bye.\")\n"
    },
    {
      "name": "tasks.eml",
      "eml": "# P001 todo list - the task rules, kept apart from the screen so they can be\n# read on their own. A task is a list [id, title, priority, done]: priority 1\n# is high, 2 medium, 3 low; done is True or False. Ids are never reused.\n\ndef add(tasks, task_id, title, priority):\n    # A new list with the task at the end; the old list is left as it was.\n    return tasks + [[task_id, title, priority, False]]\n\ndef blank(text):\n    # True if the text is empty or only spaces - such a title is refused.\n    for c in text:\n        if c != \" \":\n            return False\n    return True\n\ndef find(tasks, task_id):\n    # The task with this id, or None.\n    for t in tasks:\n        if t[0] == task_id:\n            return t\n    return None\n\ndef mark_done(tasks, task_id):\n    # Marks the task done in place. True if there was such a task.\n    find(tasks, task_id) => t\n    if t == None:\n        return False\n    True => t[3]\n    return True\n\ndef remove(tasks, task_id):\n    # A new list without the task with this id.\n    return [t for t in tasks if t[0] != task_id]\n\ndef by_priority(tasks):\n    # A new list, highest priority first. Tasks of equal priority keep the\n    # order they were added in: an insertion sort that places each task after\n    # every task already there with the same or a higher priority.\n    [] => ordered\n    for t in tasks:\n        0 => i\n        while i < len(ordered) and ordered[i][2] <= t[2]:\n            i + 1 => i\n        ordered[0:i] + [t] + ordered[i:len(ordered)] => ordered\n    return ordered\n\ndef pending(tasks):\n    # Only the tasks not done yet, in the order they were added.\n    return [t for t in tasks if not t[3]]\n\ndef count_done(tasks):\n    0 => n\n    for t in tasks:\n        if t[3]:\n            n + 1 => n\n    return n\n",
      "python": "def add(tasks, task_id, title, priority):\n    return tasks + [[task_id, title, priority, False]]\n\ndef blank(text):\n    for c in text:\n        if c != \" \":\n            return False\n    return True\n\ndef find(tasks, task_id):\n    for t in tasks:\n        if t[0] == task_id:\n            return t\n    return None\n\ndef mark_done(tasks, task_id):\n    t = find(tasks, task_id)\n    if t == None:\n        return False\n    t[3] = True\n    return True\n\ndef remove(tasks, task_id):\n    return [t for t in tasks if t[0] != task_id]\n\ndef by_priority(tasks):\n    ordered = []\n    for t in tasks:\n        i = 0\n        while i < len(ordered) and ordered[i][2] <= t[2]:\n            i = i + 1\n        ordered = ordered[0:i] + [t] + ordered[i:len(ordered)]\n    return ordered\n\ndef pending(tasks):\n    return [t for t in tasks if not t[3]]\n\ndef count_done(tasks):\n    n = 0\n    for t in tasks:\n        if t[3]:\n            n = n + 1\n    return n\n"
    },
    {
      "name": "view.eml",
      "eml": "# P001 todo list - what the screen shows: the menu and the task lists.\n\ndef priority_name(p):\n    if p == 1:\n        return \"high\"\n    if p == 2:\n        return \"medium\"\n    return \"low\"\n\ndef line(t):\n    # One task as a row: [x] #2 Water plants (low)\n    \"[ ]\" => mark\n    if t[3]:\n        \"[x]\" => mark\n    return mark + \" #\" + str(t[0]) + \" \" + t[1] + \" (\" + priority_name(t[2]) + \")\"\n\ndef show(title, tasks):\n    \"\" ^0\n    (\"-- \" + title + \": \" + str(len(tasks)) + \" --\") ^0\n    if len(tasks) == 0:\n        \"  (nothing here)\" ^0\n    for t in tasks:\n        (\"  \" + line(t)) ^0\n\ndef count(n, word):\n    # \"1 task\", \"2 tasks\"\n    if n == 1:\n        return \"1 \" + word\n    return str(n) + \" \" + word + \"s\"\n\ndef menu(total, done):\n    \"\" ^0\n    (\"== Todo: \" + count(total, \"task\") + \", \" + str(done) + \" done ==\") ^0\n    \"1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\" ^0\n",
      "python": "def priority_name(p):\n    if p == 1:\n        return \"high\"\n    if p == 2:\n        return \"medium\"\n    return \"low\"\n\ndef line(t):\n    mark = \"[ ]\"\n    if t[3]:\n        mark = \"[x]\"\n    return mark + \" #\" + str(t[0]) + \" \" + t[1] + \" (\" + priority_name(t[2]) + \")\"\n\ndef show(title, tasks):\n    print(\"\")\n    print(\"-- \" + title + \": \" + str(len(tasks)) + \" --\")\n    if len(tasks) == 0:\n        print(\"  (nothing here)\")\n    for t in tasks:\n        print(\"  \" + line(t))\n\ndef count(n, word):\n    if n == 1:\n        return \"1 \" + word\n    return str(n) + \" \" + word + \"s\"\n\ndef menu(total, done):\n    print(\"\")\n    print(\"== Todo: \" + count(total, \"task\") + \", \" + str(done) + \" done ==\")\n    print(\"1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\")\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "9\n1\n\n1\n   \n1\nTest\nhigh\n1\nTest\n5\n3\nx\n3\n42\n4\n7\n7\n",
      "screen": "\n== Todo: 0 tasks, 0 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 9\nUnknown choice: 9\n\n== Todo: 0 tasks, 0 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 1\ntitle> \nA task needs a title.\n\n== Todo: 0 tasks, 0 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 1\ntitle>    \nA task needs a title.\n\n== Todo: 0 tasks, 0 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 1\ntitle> Test\npriority 1-3> high\nPriority must be 1, 2 or 3.\n\n== Todo: 0 tasks, 0 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 1\ntitle> Test\npriority 1-3> 5\nPriority must be 1, 2 or 3.\n\n== Todo: 0 tasks, 0 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 3\nid> x\nNo task with that id.\n\n== Todo: 0 tasks, 0 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 3\nid> 42\nNo task with that id.\n\n== Todo: 0 tasks, 0 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 4\nid> 7\nNo task with that id.\n\n== Todo: 0 tasks, 0 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\nWrite report\n2\n1\nWater plants\n3\n1\nFix bug\n1\n1\nCall bank\n2\n2\n3\n2\n5\n6\n4\n3\n2\n7\n",
      "screen": "\n== Todo: 0 tasks, 0 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 1\ntitle> Write report\npriority 1-3> 2\nAdded #1.\n\n== Todo: 1 task, 0 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 1\ntitle> Water plants\npriority 1-3> 3\nAdded #2.\n\n== Todo: 2 tasks, 0 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 1\ntitle> Fix bug\npriority 1-3> 1\nAdded #3.\n\n== Todo: 3 tasks, 0 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 1\ntitle> Call bank\npriority 1-3> 2\nAdded #4.\n\n== Todo: 4 tasks, 0 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 2\n\n-- all tasks: 4 --\n  [ ] #1 Write report (medium)\n  [ ] #2 Water plants (low)\n  [ ] #3 Fix bug (high)\n  [ ] #4 Call bank (medium)\n\n== Todo: 4 tasks, 0 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 3\nid> 2\nDone: #2.\n\n== Todo: 4 tasks, 1 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 5\n\n-- by priority: 4 --\n  [ ] #3 Fix bug (high)\n  [ ] #1 Write report (medium)\n  [ ] #4 Call bank (medium)\n  [x] #2 Water plants (low)\n\n== Todo: 4 tasks, 1 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 6\n\n-- pending: 3 --\n  [ ] #1 Write report (medium)\n  [ ] #3 Fix bug (high)\n  [ ] #4 Call bank (medium)\n\n== Todo: 4 tasks, 1 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 4\nid> 3\nDeleted #3.\n\n== Todo: 3 tasks, 1 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 2\n\n-- all tasks: 3 --\n  [ ] #1 Write report (medium)\n  [x] #2 Water plants (low)\n  [ ] #4 Call bank (medium)\n\n== Todo: 3 tasks, 1 done ==\n1) add  2) list  3) done  4) delete  5) by priority  6) pending  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "task-priority-bucketer",
      "caseId": "045-task-priority-bucketer",
      "title": "Task priority bucketer"
    },
    {
      "slug": "simple-queue",
      "caseId": "040-simple-queue",
      "title": "Case corpus: a self-authored FIFO queue"
    }
  ],
  "updated": "2026-09-27"
}
