Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# zigcli Copilot Instructions

## Build, test, and formatting commands

- `zig build` builds all supported binaries and examples for the current host/target.
- `make build` performs a release build and injects build metadata (`version`, `git_commit`, `build_date`).
- `zig fmt --check .` checks formatting.
- `zig fmt .` fixes formatting.
- `zig build test --summary all` runs the full test suite.
- `zig build test-zigcli --summary all` runs tests for the reusable root module exported from `src/lib.zig`.
- `zig build test-<name> --summary all` runs one binary/helper test target, for example `zig build test-loc --summary all`, `zig build test-tree --summary all`, or `zig build test-util --summary all`.
- `make ci` runs the same repo-level checks used by CI: formatting plus tests.
- `zig build docs` generates Zig API docs into `zig-out/docs`.
- `zig build run-<name> -- <args>` runs a specific CLI during development.

## High-level architecture

- This repository combines two things in one codebase: reusable Zig packages and standalone CLI programs.
- The reusable import surface is the single root module `zigcli`, defined in `src/lib.zig`. It currently re-exports `structargs`, `pretty_table`, `gitignore`, `term`, `csv`, and `progress`.
- The build is centralized in `build.zig`. It registers the `zigcli` root module once, injects a private `build_info` module, then uses comptime loops to auto-register binaries and examples.
- Every standalone program in `src/bin/*.zig` gets matching `run-<name>`, `install-<name>`, and `test-<name>` build steps automatically.
- Tests are mostly co-located inside the source files with Zig `test` blocks. `test-zigcli` exercises the reusable module root; per-binary tests come from the individual source files.
- `src/bin/util.zig` is the shared helper layer for binaries. It provides allocator setup, build metadata formatting, string helpers, verbose logging, and a few small platform helpers.
- The binaries are also the best real examples of how the reusable packages fit together. For example, `src/bin/loc.zig`, `src/bin/tree.zig`, and `src/bin/pretty-csv.zig` show typical integration of `structargs`, `pretty_table`, `gitignore`, and shared utilities.
- `examples/` contains smaller demo programs for the packages, while `docs/` is a Hugo site for project documentation.
- Platform support is intentionally enforced in `build.zig`, not ad hoc in each command. Some binaries are skipped entirely when the host/target OS is unsupported:
- `pidof`, `night-shift`, and `dark-mode` are macOS-only.
- `timeout` is skipped on Windows.
- `progress-it` only builds on macOS and Linux.
- `zfetch` has OS-specific behavior and linking.
- `zigfetch` depends on the external `zig-curl` package from `build.zig.zon`.

## Key codebase conventions

- Target the current toolchain declared by the repo, not older assistant docs. `build.zig.zon` currently requires Zig `0.15.2`.
- When changing package exports, update `src/lib.zig` and keep the root import pattern as `const zigcli = @import("zigcli");`.
- New CLI binaries should follow the existing pattern:
- import `zigcli` plus `src/bin/util.zig`
- define an options struct for `structargs`
- populate `__shorts__` and `__messages__` for help text
- pass `version_string = util.get_build_info()` into `structargs.parse`
- Shared binary behavior belongs in `src/bin/util.zig` rather than being copied between commands.
- Allocator handling follows existing Zig 0.15 patterns:
- binaries commonly start from `var gpa = util.Allocator.instance; defer gpa.deinit();`
- temporary per-operation data often uses `std.heap.ArenaAllocator`
- `std.ArrayList` is used in unmanaged style (`.empty`, then pass the allocator to `append`, `appendSlice`, and `deinit`)
- I/O follows the newer Zig 0.15 writer API. Representative binaries create a buffered writer with `stdout.writer(&buf)` and pass `&writer.interface` to helpers that accept `*std.Io.Writer`.
- Many CLIs honor `.gitignore` filtering through `zigcli.gitignore.GitignoreStack`; preserve that behavior unless the command explicitly opts out with a `no-gitignore` flag.
- Tests live beside the implementation. Prefer adding `test` blocks to the relevant module or binary source file instead of creating separate test harnesses unless the existing structure already does so.
- Build-time platform gating belongs in `build.zig` via `sourceSupported()` and `configureCompileStep()`. If a new binary needs frameworks, libc, or OS restrictions, wire it there so the generated build steps stay accurate.
- Release metadata is compile-time data supplied by the private `build_info` module. Reuse `util.get_build_info()` instead of inventing per-command version output.

## Existing project guidance worth keeping aligned with

- `README.org` explains the dual package-plus-program structure and is the best high-level product overview.
- `CLAUDE.md` and `GEMINI.md` already document common build/test flows and project shape, but some older wording about module layout is stale. Prefer the current structure in `src/lib.zig` and `src/*.zig`.
7 changes: 4 additions & 3 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ fn buildExamples(
inline for (.{
"structargs-demo",
"pretty-table-demo",
"progress-demo",
}) |name| {
try buildBinary(b, .{ .example = name }, optimize, target, all_tests);
}
Expand All @@ -142,7 +143,7 @@ fn buildBinaries(
"cowsay",
"pretty-csv",
"zfetch",
"progress",
"progress-it",
}) |name| {
try buildBinary(
b,
Expand Down Expand Up @@ -279,7 +280,7 @@ fn sourceSupported(
}
}

if (std.mem.eql(u8, source_name, "progress")) {
if (std.mem.eql(u8, source_name, "progress-it")) {
if (!(target_os == .macos or target_os == .linux)) {
return false;
}
Expand Down Expand Up @@ -349,7 +350,7 @@ fn configureCompileStep(
return;
}

if (std.mem.eql(u8, source_name, "progress")) {
if (std.mem.eql(u8, source_name, "progress-it")) {
if (target.result.os.tag == .linux) {
compile_step.linkLibC();
return;
Expand Down
150 changes: 150 additions & 0 deletions examples/progress-demo.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
const std = @import("std");
const zigcli = @import("zigcli");
const progress = zigcli.progress;

pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();

const stderr = std.fs.File.stderr();
var stderr_buffer: [2048]u8 = undefined;
var out = stderr.writer(&stderr_buffer);

try writeSectionHeading(&out.interface, "Single progress bar");
try demoBar(allocator);
try writeSectionHeading(&out.interface, "Custom bar width");
try demoWideBar(allocator);
try writeSectionHeading(&out.interface, "Spinner");
try demoSpinner(allocator);
try writeSectionHeading(&out.interface, "Multi-progress");
try demoMulti(allocator);
try writeSectionHeading(&out.interface, "Writer wrapper");
try demoWrapWriter(allocator, &out.interface);
try out.interface.writeAll("\n");
try out.interface.flush();
}

fn writeSectionHeading(
writer: *std.Io.Writer,
title: []const u8,
) !void {
try writer.print("\n=== {s} ===\n", .{title});
try writer.flush();
}

fn demoBar(allocator: std.mem.Allocator) !void {
var bar = try progress.Progress.bar(allocator, .{
.total = 20,
.style = progress.Style.defaultBar(),
.message = "copying files",
.prefix = "bar",
});
defer bar.deinit();

for (0..20) |_| {
bar.inc(1);
try bar.render();
std.Thread.sleep(120 * std.time.ns_per_ms);
}
try bar.finish();
}

fn demoWideBar(allocator: std.mem.Allocator) !void {
var style = progress.Style.defaultBar();
style.bar_width = 40;

var bar = try progress.Progress.bar(allocator, .{
.total = 16,
.style = style,
.message = "wide bar",
.prefix = "wide",
});
defer bar.deinit();

for (0..16) |_| {
bar.inc(1);
try bar.render();
std.Thread.sleep(110 * std.time.ns_per_ms);
}
try bar.finish();
}

fn demoSpinner(allocator: std.mem.Allocator) !void {
var spinner = try progress.Progress.spinner(allocator, .{
.style = progress.Style.defaultSpinner(),
.message = "resolving dependencies",
.prefix = "spin",
});
defer spinner.deinit();

for (0..16) |_| {
spinner.tick();
try spinner.render();
std.Thread.sleep(140 * std.time.ns_per_ms);
}
try spinner.finish();
}

fn demoMulti(allocator: std.mem.Allocator) !void {
var multi = progress.MultiProgress.init(allocator, .{});
defer multi.deinit();

const download = try multi.addBar(.{
.total = 12,
.message = "download",
.prefix = "task-1",
});
const unpack = try multi.addSpinner(.{
.message = "unpack",
.prefix = "task-2",
});

for (0..12) |_| {
download.inc(1);
unpack.tick();
try multi.render();
std.Thread.sleep(130 * std.time.ns_per_ms);
}

try download.finish();
try unpack.finish();
try multi.render();
}

fn demoWrapWriter(
allocator: std.mem.Allocator,
writer: *std.Io.Writer,
) !void {
var bar = try progress.Progress.bar(allocator, .{
.total = 5 * 8,
.style = .{
.unit = .bytes,
},
.message = "writing chunks",
.prefix = "io",
});
defer bar.deinit();

var sink: std.Io.Writer.Allocating = .init(allocator);
var wrapped = bar.wrapWriter(&sink.writer);

inline for (.{
"chunk-01",
"chunk-02",
"chunk-03",
"chunk-04",
"chunk-05",
}) |chunk| {
try wrapped.writeAll(chunk);
try bar.render();
std.Thread.sleep(160 * std.time.ns_per_ms);
}

try bar.finish();
try writer.print(
"Captured {d} bytes through ProgressWriter.\n",
.{sink.written().len},
);
sink.deinit();
}
2 changes: 1 addition & 1 deletion src/bin/progress.zig → src/bin/progress-it.zig
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ fn scanProc(
} else if (builtin.os.tag == .macos) {
return scanProcMacos(allocator, pid_filter, cmd_filter);
} else {
@compileError("progress is only supported on Linux and macOS");
@compileError("progress-it is only supported on Linux and macOS");
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
//!
//! The root module bundles the repository's reusable Zig packages behind one import root.
//! Import `zigcli` once, then access the individual packages as
//! `zigcli.pretty_table`, `zigcli.structargs`, `zigcli.gitignore`, `zigcli.term`, and `zigcli.csv`.
//! `zigcli.pretty_table`, `zigcli.structargs`, `zigcli.gitignore`, `zigcli.term`,
//! `zigcli.csv`, and `zigcli.progress`.

const std = @import("std");

Expand All @@ -21,6 +22,9 @@ pub const term = @import("term.zig");
/// The delimited text parsing package.
pub const csv = @import("csv.zig");

/// Progress bars, spinners, and multi-progress rendering.
pub const progress = @import("progress.zig");

test {
std.testing.refAllDecls(@This());
}
Loading
Loading