Search

intermediate

Building a CLI Tool

Create a command-line application with argument parsing, file I/O, and error handling.

Learning Objectives

  • Parse command-line arguments
  • Read and write files
  • Handle errors gracefully
  • Build a complete CLI application

Prerequisites


Project Setup

Create a new project for the CLI tool:

Terminal window
mkdir draft-cli
cd draft-cli
touch main.draft

We’ll build a simple todo manager that reads from and writes to a JSON file.


Reading Arguments

Draft provides access to command-line arguments through the process module.

import process
action main()
@args = process.args()
write(args)
fin action.

Run it:

Terminal window
draft run main.draft hello world

Output:

["hello", "world"]

Parsing Subcommands

Let’s build a simple argument parser:

import process
action main()
@args = process.args()
if args.length == 0 then
write("Usage: todo <add|list|done> [task]")
exit(1)
fi
@command = args[0]
if command == "add" then
add_task(args[1])
elif command == "list" then
list_tasks()
elif command == "done" then
mark_done(args[1])
else
write("Unknown command: " + command)
exit(1)
fi
fin action.

File I/O

Use the fs module to read and write files.

import fs
action main()
@tasks_path = "tasks.json"
if fs.exists(tasks_path) then
@content = fs.read(tasks_path)
write("Existing tasks found")
else
fs.write(tasks_path, "[]")
write("Created new task file")
fi
fin action.

Error Handling

Wrap risky operations in error blocks:

import fs
action main()
try
@content = fs.read("tasks.json")
@tasks = json.decode(content)
catch error
write("Failed to load tasks: " + error.message)
exit(1)
end try
fin action.

Custom Errors

Define and raise custom errors:

action validate_task(task)
if task.length == 0 then
raise error("Task cannot be empty")
fi
fin action.

Complete Working Example

Here is a complete todo CLI:

import process
import fs
import json
const TASKS_FILE = "tasks.json"
action main()
@args = process.args()
if args.length == 0 then
show_help()
exit(0)
fi
@command = args[0]
if command == "add" then
add_task(args[1])
elif command == "list" then
list_tasks()
elif command == "done" then
mark_done(args[1])
else
show_help()
exit(1)
fi
fin action.
action add_task(description)
@tasks = load_tasks()
@new_task = {
id: tasks.length + 1,
description: description,
done: false
}
tasks.push(new_task)
save_tasks(tasks)
write("Added: " + description)
fin action.
action list_tasks()
@tasks = load_tasks()
if tasks.length == 0 then
write("No tasks yet.")
return
fi
loop i from 0 to tasks.length - 1
@task = tasks[i]
@status = task.done ? "[x]" : "[ ]"
write(status + " " + task.id + ": " + task.description)
again
fin action.
action mark_done(id_string)
@tasks = load_tasks()
@id = int(id_string)
@found = false
loop i from 0 to tasks.length - 1
if tasks[i].id == id then
tasks[i].done = true
found = true
fi
again
if found then
save_tasks(tasks)
write("Marked task " + id_string + " as done")
else
write("Task not found")
exit(1)
fi
fin action.
action load_tasks()
if not fs.exists(TASKS_FILE) then
return []
fi
try
@content = fs.read(TASKS_FILE)
return json.decode(content)
catch error
return []
end try
fin action.
action save_tasks(tasks)
@json_data = json.encode(tasks)
fs.write(TASKS_FILE, json_data)
fin action.
action show_help()
write("Usage: todo <add|list|done> [task]")
write(" add <task> Add a new task")
write(" list List all tasks")
write(" done <id> Mark a task as done")
fin action.

Try It Yourself

  1. Save the code as main.draft.
  2. Build it: draft build main.draft -o todo.
  3. Run ./todo add "Learn Draft".
  4. Run ./todo list.
  5. Run ./todo done 1.

Checkpoint

  1. Which module provides file I/O?

    • io
    • fs
    • file
  2. How do you exit with a non-zero status?

    • exit(1)
    • return 1
    • quit(1)
  3. What does json.encode do?

    • Parses JSON into a value
    • Converts a value to a JSON string
    • Validates JSON syntax
Answers
  1. fs
  2. exit(1)
  3. Converts a value to a JSON string

Summary

You built a working CLI tool with argument parsing, file persistence, and error handling. The patterns you learned apply to any command-line application.

Next Steps