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
- Completed Variables and Values
- Draft compiler installed and working
Project Setup
Create a new project for the CLI tool:
mkdir draft-clicd draft-clitouch main.draftWe’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:
draft run main.draft hello worldOutput:
["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) fifin 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") fifin 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 tryfin action.Custom Errors
Define and raise custom errors:
action validate_task(task) if task.length == 0 then raise error("Task cannot be empty") fifin action.Complete Working Example
Here is a complete todo CLI:
import processimport fsimport 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) fifin 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) againfin 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) fifin 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 tryfin 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
- Save the code as
main.draft. - Build it:
draft build main.draft -o todo. - Run
./todo add "Learn Draft". - Run
./todo list. - Run
./todo done 1.
Checkpoint
-
Which module provides file I/O?
iofsfile
-
How do you exit with a non-zero status?
exit(1)return 1quit(1)
-
What does
json.encodedo?- Parses JSON into a value
- Converts a value to a JSON string
- Validates JSON syntax
Answers
fsexit(1)- 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
- Building an HTTP Server — Create a web API with routing and middleware.
