INPUT /  OUTPUT /  LANGUAGE





A Complete Example

This last chapter puts together much of what you've learned, in one program: a to-do list. It's short, about 85 lines of Python, but it's a complete, useful application. The user can add tasks, mark them done, delete them, and the list is saved when they quit, ready for next time.

To Do   Add Save and quit
Water plants Done Delete
Read chapter 20 Done Delete
1 of 2 tasks left to do.

Here's the whole program. Read it through once, then we'll go through it part by part.

import json, os

SAVE_FILE = 'tasks.json'

def raw(text):
    """Text as raw data, so it's never read as markup."""
    return '[' + str(len(text.encode('utf-8'))) + '{' + text + '}]'

# ---- the program's data ----
tasks = {}          # task number -> {'text': ..., 'done': ...}
next_number = 1
if os.path.exists(SAVE_FILE):
    with open(SAVE_FILE) as f:
        for task in json.load(f):
            tasks[next_number] = task
            next_number += 1

# ---- the interface ----
print('''
<console.title {To Do}>
<console.margin 0>
<console.onExit.push <putLn {/quit}>>

<task:class _=label number=number fg=label.color *=
    <box width=95,% border=1 margin=0.5,em spacing=0.3,em {
        <number:scalar 0>
        <label:span color=null {}>
        <box inline=false top=0.3,em right=0.5,em border=0 {
            <button {Done} onClick=<putLn {/done }, number>>
            <button {Delete} onClick=<putLn {/delete }, number>>
        }>
    }>
>

<box inline=false top=0 left=0 right=0 height=2.5,em border=0 margin=0.5,em
     backgroundColor={midnightblue} color={white} {
    <span bold=true {To Do}></ ></ ></ >
    <newTask:input width=20,em onKeyDown=<if <eq key, 13> then=<putLn {/add }, newTask>, <newTask.clear>>>
    <button {Add} onClick=<putLn {/add }, newTask>, <newTask.clear>>
    <button {Save and quit} onClick=<putLn {/quit}>>
}>

<list:box inline=false top=2.5,em left=0 right=0 bottom=1.5,em border=0 margin=0.5,em scroll=true {}>

<status:box inline=false bottom=0 left=0 right=0 height=1.5,em border=0 margin=0.2,em
     backgroundColor={darkgreen} {}>
''')

def show_task(number):
    task = tasks[number]
    print('<list.push <t' + str(number) + ':task number=' + str(number) + ' ' + raw(task['text']) + '>>')
    if task['done']:
        print('<list.t' + str(number) + '.fg {gray}>')

def show_status():
    left = sum(1 for t in tasks.values() if not t['done'])
    print('<status {' + str(left) + ' of ' + str(len(tasks)) + ' tasks left to do.}>')

for number in tasks:
    show_task(number)
show_status()

# ---- respond to the user ----
while True:
    command = input()
    if command == '/quit':
        break
    elif command.startswith('/add '):
        text = command[5:].strip()
        if text:
            tasks[next_number] = {'text': text, 'done': False}
            show_task(next_number)
            next_number += 1
    elif command.startswith('/done '):
        number = int(command[6:])
        tasks[number]['done'] = True
        print('<list.t' + str(number) + '.fg {gray}>')
    elif command.startswith('/delete '):
        number = int(command[8:])
        del tasks[number]
        print('<list.t' + str(number) + '.delete>')
    show_status()

with open(SAVE_FILE, 'w') as f:
    json.dump(list(tasks.values()), f)

How it works

Who does what

Before writing a program like this, decide what the console does and what your program does. Here:

This is the split recommended in the chapter on logic: interface work in the console, real work in your program.

Loading the saved tasks

The program starts by reading tasks.json, if it exists, using Python's json module. Because the file name has no folder in front of it, the file is kept in the program's working directory. Each task gets a number, which is how the program and the console will refer to it from now on.

The task class

Every task on screen looks and behaves the same, so it's a class (see the chapter on defining your own tags):

The screen layout

The rest of the interface is the layout from the chapter on positioning: a bar along the top, a scrolling area for the list, and a status bar along the bottom. The input box in the top bar sends /add followed by the new task's text, either when the Add button is clicked, or when Enter is pressed (key 13, from the chapter on events).

Showing a task

show_task pushes a new task into the list, named t followed by its number: t1, t2 and so on. The unique names let the program find each task again later: list.t3 is task 3.

The task's text is sent with raw, as raw data. The user can type anything as a task, including < and {, and raw data guarantees it's displayed exactly as typed instead of being read as markup (see the chapter on instance names).

Responding to commands

The main loop reads one command at a time, and does what it says:

After every command, the status bar is brought up to date.

Quitting and saving

"Save and quit" sends /quit, and so does closing the window, thanks to onExit (see the chapter on events). Either way, the program leaves its loop, saves the tasks, and ends normally, and the console closes.

Trusting the user's input

The program follows the advice from the chapter on instance names. It ignores tasks that are empty. Every line the console sends starts with a command (/add, /done, /delete or /quit) that comes from the program's own buttons, never from the user, so the program knows what each line means before it looks at what the user typed. The task numbers sent by Done and Delete were put there by the program itself. And every task's text is displayed as raw data.


Where next?

You've now seen all the main ideas in ioL. Here are some ways to extend the to-do list, each using something from an earlier chapter:

  1. Show done tasks with a tick, or in a different style, rather than just grey.
  2. Add an "Undo" button to done tasks.
  3. Ask "Are you sure?" with a dialog before deleting a task.
  4. Add a due date to each task, and sort the list by it.
  5. Keep several lists, each in its own tab.

For everything else, the Language Reference lists every tag and field in ioL, with the details of how each one works. Have fun!