hello_cli
A comprehensive CLI application demonstrating various commands and third-party integrations.
Source Code
Path: examples/hello_cli/
const std = @import("std");
const tk = @import("tokamak");
// Shared
const App = struct {
http_client: tk.http.StdClient,
hn_client: tk.ext.hackernews.Client,
gh_client: tk.ext.github.Client,
};
// CLI-only
const Cli = struct {
cmds: []const tk.cli.Command = &.{
.usage,
.cmd0("hello", "Print a greeting message", hello),
.cmd1("hn", "Show top Hacker News stories", hn_top),
.cmd1("gh", "List GitHub repos", gh_repos),
.cmd2("scrape", "Scrape a URL with optional CSS selector", scrape),
.cmd2("grep", "Search for pattern in file", grep),
.cmd3("substr", "Get substring with bounds checking", substr),
.cmd2("pdf", "Generate a sample PDF", writePdf),
},
fn hello() []const u8 {
return "Hello World!";
}
fn hn_top(hn_client: *tk.ext.hackernews.Client, arena: std.mem.Allocator, limit: ?u8) ![]const tk.ext.hackernews.Story {
return hn_client.getTopStories(arena, limit orelse 10);
}
fn gh_repos(gh_client: *tk.ext.github.Client, arena: std.mem.Allocator, owner: []const u8) ![]const tk.ext.github.Repository {
return gh_client.listRepos(arena, owner);
}
fn scrape(http_client: *tk.http.Client, arena: std.mem.Allocator, url: []const u8, qs: ?[]const u8) ![]const u8 {
const res = try http_client.request(arena, .{ .url = url });
const doc = try tk.dom.Document.parseFromSlice(arena, res.body);
defer doc.deinit();
var node = &doc.node;
if (qs) |sel| {
if (try doc.querySelector(sel)) |el| node = &el.node;
}
return try tk.html2md.html2md(arena, node, .{});
}
fn substr(str: []const u8, start: ?usize, end: ?usize) ![]const u8 {
if ((start orelse 0) > str.len or (end orelse str.len) > str.len) return error.OutOfBounds;
return str[start orelse 0 .. end orelse str.len];
}
fn writePdf(arena: std.mem.Allocator, filename: []const u8, title: []const u8) !void {
var doc = try tk.pdf.Document.init(arena);
defer doc.deinit();
var page = try doc.addPage(612, 792);
try page.addText(50, 750, title, .{ .size = 24, .bold = true });
try page.addText(50, 730, try std.fmt.allocPrint(arena, "Generated on: {f}", .{
tk.time.Date.today(),
}), .{ .size = 10 });
try page.addLine(50, 710, 550, 710);
try page.addRect(50, 600, 100, 50);
try page.addText(160, 620, "Rectangle", .{ .bold = true });
try page.moveTo(50, 500);
try page.lineTo(100, 550);
try page.lineTo(150, 500);
try page.closePath();
try page.stroke();
try page.addText(160, 520, "Triangle", .{ .bold = true });
try page.moveTo(50, 450);
try page.curveTo(75, 400, 125, 400, 150, 450);
try page.stroke();
try page.addText(160, 420, "Curve", .{ .bold = true });
const file = try std.fs.cwd().createFile(filename, .{});
defer file.close();
try file.writeAll(try doc.render(arena));
std.debug.print("PDF '{s}' generated successfully", .{filename});
}
fn grep(arena: std.mem.Allocator, file_path: []const u8, pattern: []const u8) !void {
var regex = try tk.regex.Regex.compile(arena, pattern);
defer regex.deinit(arena);
const file = try std.fs.cwd().openFile(file_path, .{});
defer file.close();
var buf: [4096]u8 = undefined;
var in = file.reader(&buf);
var grepper = tk.regex.Grep.init(&in.interface, ®ex);
while (try grepper.next()) |line| {
std.debug.print("{d}: {s}", .{ grepper.line, line });
}
}
};
pub fn main() !void {
std.debug.print(tk.ansi.clear, .{});
try tk.app.run(tk.cli.run, &.{ App, Cli });
}
Features Demonstrated
- CLI command framework (
tk.cli) - HTTP client integration
- HTML to Markdown conversion
- DOM parsing and querying
- PDF generation
- Regular expressions and grep functionality
- GitHub API integration
- Hacker News API integration
Available Commands
hello
Print a greeting message.
zig build run -- hello
hn <limit>
Show top Hacker News stories.
zig build run -- hn 5
gh <owner>
List GitHub repositories for a user.
zig build run -- gh cztomsik
scrape <url> [selector]
Scrape a URL and convert to Markdown, with optional CSS selector.
zig build run -- scrape https://example.com
zig build run -- scrape https://example.com "article.content"
grep <file> <pattern>
Search for a regex pattern in a file.
zig build run -- grep myfile.txt "TODO.*"
substr <str> [start] [end]
Get substring with bounds checking.
zig build run -- substr "Hello World" 0 5
pdf <filename> <title>
Generate a sample PDF with various shapes and text.
zig build run -- pdf output.pdf "My Document"
Architecture
The CLI uses a shared App struct for services (HTTP client, API clients) and a Cli struct for command definitions:
const App = struct {
http_client: tk.http.StdClient,
hn_client: tk.ext.hackernews.Client,
gh_client: tk.ext.github.Client,
};
// CLI-only
const Cli = struct {
cmds: []const tk.cli.Command = &.{
.usage,
.cmd0("hello", "Print a greeting message", hello),
.cmd1("hn", "Show top Hacker News stories", hn_top),
.cmd1("gh", "List GitHub repos", gh_repos),
.cmd2("scrape", "Scrape a URL with optional CSS selector", scrape),
.cmd2("grep", "Search for pattern in file", grep),
.cmd3("substr", "Get substring with bounds checking", substr),
.cmd2("pdf", "Generate a sample PDF", writePdf),
},
fn hello() []const u8 {
return "Hello World!";
}
Command Handler Patterns
Handlers can inject dependencies:
arena: std.mem.Allocator- Per-command arena allocator- Service dependencies (e.g.,
*tk.http.Client) - Command arguments as function parameters
Running
cd examples/hello_cli
zig build run -- <command> [args...]
Next Steps
- See clown-commander for a TUI application
- Check out hello_ai for AI agent integration