Skip to main content

Capstone — Build a Task Manager CLI

Congratulations — you have reached the end of the course. Now you build something real: a full command-line task manager, exactly like the one in examples/real_world/01_task_manager.adesh. It uses everything you have learned so far and runs as a polished, interactive CLI with a demo harness.

This is a build-along lesson. Type every block into task_manager.adesh (or run the original file) and run it after each section.

What we are building

╔══════════════════════════════════════════╗
║ AdeshLang Task Manager v1.0 ║
╚══════════════════════════════════════════╝

📖 Available Commands:
add <title> [desc] [priority] - Add a new task
list [filter] - List tasks (all/completed/pending)
complete <id> - Mark task as completed
delete <id> - Delete a task
show <id> - Show task details
stats - Show statistics

Step 1 — Model the data (types, struct-like objects, enums of priority)

First we define what a Task is: an object type with typed fields, including an optional completedAt:

type Task = {
id: i32,
title: string,
description: string,
completed: bool,
priority: string, // "low", "medium", "high"
createdAt: f64,
completedAt: f64?
};
  • type Task = { ... } declares an object type (from Lesson 13).
  • completedAt: f64? is a nullable fieldnull until the task is done (from Lesson 12).

Step 2 — The manager class (encapsulation, helpers, methods)

The TaskManager class owns the task array and every operation:

class TaskManager {
TaskManager() {
this.tasks = [];
this.nextId = 1;
}

fn addTask(title: string, description: string, priority: string): Task {
let task: Task = {
id: this.nextId,
title: title,
description: description,
completed: false,
priority: priority,
createdAt: clock(),
completedAt: null
};

this.tasks.push(task);
this.nextId = this.nextId + 1;
return task;
}

fn listTasks(filter: string?) {
if (len(this.tasks) == 0) {
print(" No tasks found. Add one with: add <title>");
return;
}

print(" ID | Status | Priority | Title");
print(" ────┼────────┼──────────┼─────────────────────");

for task in this.tasks {
if (filter != null) {
if (filter == "completed" && !task.completed) { continue; }
if (filter == "pending" && task.completed) { continue; }
}

print(" " + this._padNumber(task.id, 4) + " | "
+ (task.completed ? "✓ Done" : "○ Todo") + " | "
+ this._formatPriority(task.priority) + " | "
+ task.title);
}
}
// ... completeTask, deleteTask, getTask, showStats, helpers ...
}

Notice the helper methods prefixed _: _formatPriority and _padNumber. Keeping formatting logic in private methods keeps listTasks readable (from Lesson 14).

Step 3 — CLI handlers (functions, guard clauses, validation)

Each command is a function that validates its arguments and dispatches to the manager (from Lesson 15 and Lesson 6):

fn handleAdd(manager: TaskManager, args: [string]) {
if (len(args) < 1) { // ← guard clause
print("❌ Error: Task title required");
print(" Usage: add <title> [description] [priority]");
return;
}

let title = args[0];
let description = len(args) > 1 ? args[1] : "";
let priority = len(args) > 2 ? args[2] : "medium";

if (priority != "low" && priority != "medium" && priority != "high") {
print("❌ Error: Priority must be 'low', 'medium', or 'high'");
return;
}

let task = manager.addTask(title, description, priority);
print("✅ Task added successfully!");
print(" ID: " + str(task.id));
print(" Title: " + task.title);
}

Every command follows the same rhythm:

validate input → bail early on bad input → do the work → print a friendly result

Step 4 — The main program (orchestration)

main wires everything together: create the manager, seed demo data, run an "interactive demo" that simulates user commands:

fn main() {
let manager = new TaskManager();

print("📚 Demo Mode: Adding sample tasks...");
print("");

manager.addTask("Write documentation", "Complete the API docs", "high");
manager.addTask("Fix bug #42", "Memory leak in parser", "high");
manager.addTask("Code review", "Review PR #123", "medium");

manager.completeTask(1); // mark first task done

print("✅ Sample tasks added!");
print("");

handleHelp();
print("");

handleList(manager, []); // list all
print("");

// Simulated user session
print("➜ Command: add 'Deploy to production' 'Version 2.0 release' high");
handleAdd(manager, ["Deploy to production", "Version 2.0 release", "high"]);
print("");

print("➜ Command: list pending");
handleList(manager, ["pending"]);
print("");

print("➜ Command: stats");
handleStats(manager, []);
}

Then run it:

adesh task_manager.adesh

Output (condensed — the real run prints the full help text):

📚 Demo Mode: Adding sample tasks...

✅ Sample tasks added!

📖 Available Commands:
add <title> [desc] [priority] - Add a new task
list [filter] - List tasks (all/completed/pending)
complete <id> - Mark task as completed
delete <id> - Delete a task
show <id> - Show task details
stats - Show statistics
help - Show this help


📋 Task List

ID | Status | Priority | Title
────┼────────┼──────────┼─────────────────────
1 | ✓ Done | 🔴 High | Write documentation
2 | ○ Todo | 🔴 High | Fix bug #42
3 | ○ Todo | 🟡 Medium| Code review

➜ Command: add 'Deploy to production' 'Version 2.0 release' high
✅ Task added successfully!
ID: 4
Title: Deploy to production
Priority: high

➜ Command: list pending
📋 Task List (pending)

ID | Status | Priority | Title
────┼────────┼──────────┼─────────────────────
2 | ○ Todo | 🔴 High | Fix bug #42
3 | ○ Todo | 🟡 Medium| Code review
4 | ○ Todo | 🔴 High | Deploy to production

➜ Command: stats
📊 Statistics:
Total tasks: 4
Completed: 1
Pending: 3
High priority: 3
Completion rate: 25.0%

You get a magazine-style console output: box-drawing banners, emoji status markers, padded columns, and clear error messages. This is what "polished CLI" looks like — and the whole thing is plain functions, types, arrays, loops, conditionals, string formatting, and a class.

Step 5 — Extend it (your turn)

Now modify the program yourself. Ideas, from easy to harder:

  1. Add an export command — write all tasks to tasks.json:

    fn handleExport(manager: TaskManager, args: [string]) {
    import JSON;
    JSON.stringifyFile("tasks.json", manager.tasks, true);
    print("✅ Exported " + str(len(manager.tasks)) + " tasks to tasks.json");
    }
  2. Add a search <text> command — find tasks whose title contains text:

    fn handleSearch(manager: TaskManager, args: [string]) {
    if (len(args) < 1) { print("❌ Usage: search <text>"); return; }
    let q = args[0];
    for task in manager.tasks {
    if (task.title.includes(q) || task.description.includes(q)) {
    print(" #" + str(task.id) + " " + task.title);
    }
    }
    }
  3. Add a due field — a string date ("2026-09-10") on each task, plus a list overdue filter comparing against Time.dateToday().

  4. Refactor the demo — replace the hard-coded "interactive demo" with a loop that reads real user input:

    import IO;
    while (true) {
    print("➜ ", end=""); // prompt
    let line = IO.readLine();
    // parse words, dispatch to your command handlers
    }

What your capstone proves

ConceptWhere it appears
Types & nullabilitytype Task, completedAt: f64?
Conditionals & elifif/elif/else in _formatPriority
Loops & rangesfor task in this.tasks, range(len(...))
Functions8 command handlers + helper methods
Collectionsthis.tasks array, push, filtering
String formattingconcatenation, str(), padding, box art
Structs, enums & matchingobject types, ternary status/priority
OOPTaskManager class, _ helpers, new
Error handlingguard clauses + friendly errors
Stdlibclock(), parseInt, str, emoji/box output

Wrap-up

You did it — the entire course. From hello world to a full, typed, validated CLI application with statistics and polished output. The Task Manager in examples/real_world/ proves these skills are used daily by real AdeshLang programs.

Where to go next:

👏 Great job. Now go build something real with AdeshLang.