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.
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)
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.
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.
Every task on screen looks and behaves the same, so it's a class (see the chapter on defining your own tags):
color=null, so that there's a field to change.inline=false against the right-hand edge of the task
(see the chapter on positioning).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).
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).
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.
"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.
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.
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:
For everything else, the Language Reference lists every tag and field in ioL, with the details of how each one works. Have fun!