Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
public/
zig-out/
zig-cache/
zig-pkg/
.zig-cache/
.DS_Store
.tool-versions
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
47 changes: 47 additions & 0 deletions GEMINI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Zig Cookbook - Project Context

## Project Overview
`zig-cookbook` is a multilingual collection of Zig (0.16.x) programming recipes and examples. It demonstrates common programming tasks using Zig's standard library and specialized packages. The project generates a static documentation website using the [zine](https://zine-ssg.io) static site generator.

## Key Technologies
- **Zig (0.16.0)**: The primary programming language.
- **Zine**: Static site generator for documentation.
- **Docker**: Used for running databases (Postgres, MySQL) required by some examples.
- **system dependencies**: Some examples link against C libraries like `libpq`, `mysqlclient`, and `sqlite3`.

## Directory Structure
- `assets/src/`: Contains all Zig example source files (e.g., `01-01.zig`).
- `src/`: Contains localized documentation in Super Markdown (`.smd`) format, organized by language (`en-US`, `zh-CN`).
- `lib/`: C header files and helper source code for C interop examples.
- `layouts/`: Templates and UI components for the documentation website.
- `i18n/`: Internationalization configuration files (`.ziggy`).

## Building and Running
The project uses the standard Zig build system.

### Running Examples
- **Specific Example**: `zig build run-{chapter}-{seq}` (e.g., `zig build run-01-01`).
- **All Examples**: `zig build run-all`.
- **Compile Check**: `zig build check`.

### Local Documentation
- **Preview Site**: `make serve` (starts `zine` on port 1313).
- **Zine Preview**: Alternatively, use `zine` directly.

### Dependencies & Environment
- **Install System Libraries**: `make install-deps` (supports macOS via brew and Linux via apt).
- **Databases**: `docker-compose up -d` to start the required Postgres and MySQL instances.
- **Environment Variables**: `source env.sh` may be required on some systems to set `PKG_CONFIG_PATH` for database clients.

## Development Conventions
- **Zig Version**: Targets Zig 0.16.x. Adheres to modern Zig idioms like `std.Io` and `std.process.Init` entry points.
- **Testing**: Many examples include `std.testing` assertions within their `main` or as helper blocks to verify correctness.
- **C Interop**: Uses `b.addTranslateC` in `build.zig` to interface with C libraries.
- **Formatting**: The project uses `prettier` for linting `.smd` files (via `make lint`).

## Project-Specific Tips
- When adding a new recipe:
1. Add the Zig code to `assets/src/`.
2. Create corresponding `.smd` files in `src/en-US/` and `src/zh-CN/`.
3. Update `build.zig` if the example has special library dependencies.
- Use `std.debug.print` for output in examples as they are meant for learning.
2 changes: 1 addition & 1 deletion README-zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

[Zig Cookbook](https://github.com/zigcc/zig-cookbook) 是一系列简单的 Zig 示例程序合集,展示了完成常见编程任务的良好实践。

> - 主分支跟踪 Zig 0.15.x,并通过 GitHub Actions 在 Linux 和 macOS 上进行测试。
> - 主分支跟踪 Zig 0.16.x,并通过 GitHub Actions 在 Linux 和 macOS 上进行测试。
> - 更早版本的 Zig 支持可以在[其他分支](https://github.com/zigcc/zig-cookbook/branches)中找到。

# 如何使用
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

[Zig cookbook](https://github.com/zigcc/zig-cookbook) is a collection of simple Zig programs that demonstrate good practices to accomplish common programming tasks.

> - Main branch tracks Zig 0.15.x, and is tested on Linux and macOS via GitHub actions.
> - Main branch tracks Zig 0.16.x, and is tested on Linux and macOS via GitHub actions.
> - Earlier Zig support could be found in [other branches](https://github.com/zigcc/zig-cookbook/branches).

# How to use
Expand Down
11 changes: 6 additions & 5 deletions assets/src/01-01.zig
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
const std = @import("std");
const fs = std.fs;
const print = std.debug.print;

pub fn main() !void {
const file = try fs.cwd().openFile("tests/zig-zen.txt", .{});
defer file.close();
pub fn main(init: std.process.Init) !void {
const io = init.io;

const file = try std.Io.Dir.cwd().openFile(io, "tests/zig-zen.txt", .{});
defer file.close(io);

var file_buffer: [4096]u8 = undefined;
var reader = file.reader(&file_buffer);
var reader = file.reader(io, &file_buffer);
var line_no: usize = 0;
while (try reader.interface.takeDelimiter('\n')) |line| {
line_no += 1;
Expand Down
15 changes: 7 additions & 8 deletions assets/src/01-02.zig
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
const std = @import("std");
const fs = std.fs;
const print = std.debug.print;

const filename = "/tmp/zig-cookbook-01-02.txt";

pub fn main() !void {
pub fn main(init: std.process.Init) !void {
if (.windows == @import("builtin").os.tag) {
std.debug.print("MMap is not supported in Windows\n", .{});
return;
}

const file = try fs.cwd().createFile(filename, .{
const io = init.io;
const file = try std.Io.Dir.cwd().createFile(io, filename, .{
.read = true,
.truncate = true,
.exclusive = false, // Set to true will ensure this file is created by us
});
defer file.close();
defer file.close(io);
const content_to_write = "hello zig cookbook";

// Before mmap, we need to ensure file isn't empty
try file.setEndPos(content_to_write.len);
try file.setLength(io, content_to_write.len);

const md = try file.stat();
const md = try file.stat(io);
try std.testing.expectEqual(md.size, content_to_write.len);

const ptr = try std.posix.mmap(
null,
content_to_write.len,
std.posix.PROT.READ | std.posix.PROT.WRITE,
.{ .READ = true, .WRITE = true },
.{ .TYPE = .SHARED },
file.handle,
0,
Expand Down
25 changes: 11 additions & 14 deletions assets/src/01-03.zig
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
//! Find files that have been modified in the last 24 hours

const std = @import("std");
const builtin = @import("builtin");
const fs = std.fs;
const print = std.debug.print;

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
pub fn main(init: std.process.Init) !void {
const gpa = init.gpa;
const io = init.io;

var iter_dir = try fs.cwd().openDir("src", .{ .iterate = true });
defer iter_dir.close();
var iter_dir = try std.Io.Dir.cwd().openDir(io, "src", .{ .iterate = true });
defer iter_dir.close(io);

var walker = try iter_dir.walk(allocator);
var walker = try iter_dir.walk(gpa);
defer walker.deinit();

const now = std.time.nanoTimestamp();
while (try walker.next()) |entry| {
const now_ns = std.Io.Clock.real.now(io).nanoseconds;
while (try walker.next(io)) |entry| {
if (entry.kind != .file) {
continue;
}

const stat = try iter_dir.statFile(entry.path);
const last_modified = stat.mtime;
const duration = now - last_modified;
const stat = try iter_dir.statFile(io, entry.path, .{});
const last_modified = stat.mtime.nanoseconds;
const duration = now_ns - last_modified;
if (duration < std.time.ns_per_hour * 24) {
print("Last modified: {d} seconds ago, size:{d} bytes, filename: {s}\n", .{
@divTrunc(duration, std.time.ns_per_s),
Expand Down
6 changes: 3 additions & 3 deletions assets/src/01-04.zig
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
//! Test file/directory existence

const std = @import("std");
const fs = std.fs;

pub fn main() !void {
pub fn main(init: std.process.Init) !void {
const io = init.io;
const filename = "build.zig";
var found = true;
fs.cwd().access(filename, .{}) catch |e| switch (e) {
std.Io.Dir.cwd().access(io, filename, .{}) catch |e| switch (e) {
error.FileNotFound => found = false,
else => return e,
};
Expand Down
16 changes: 7 additions & 9 deletions assets/src/01-05.zig
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
const std = @import("std");
const fs = std.fs;
const print = std.debug.print;

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer if (gpa.deinit() != .ok) @panic("leak");
const allocator = gpa.allocator();
pub fn main(init: std.process.Init) !void {
const gpa = init.gpa;
const io = init.io;

// In order to walk the directry, `iterate` must be set to true.
var dir = try fs.cwd().openDir("zig-out", .{ .iterate = true });
defer dir.close();
var dir = try std.Io.Dir.cwd().openDir(io, "zig-out", .{ .iterate = true });
defer dir.close(io);

var walker = try dir.walk(allocator);
var walker = try dir.walk(gpa);
defer walker.deinit();

while (try walker.next()) |entry| {
while (try walker.next(io)) |entry| {
print("path: {s}, basename:{s}, type:{s}\n", .{
entry.path,
entry.basename,
Expand Down
23 changes: 11 additions & 12 deletions assets/src/02-01.zig
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
const std = @import("std");
const fs = std.fs;
const Sha256 = std.crypto.hash.sha2.Sha256;

// In real world, this may set to page_size, usually it's 4096.
const BUF_SIZE = 16;

fn sha256_digest(
file: fs.File,
file: std.Io.File,
io: std.Io,
) ![Sha256.digest_length]u8 {
var sha256 = Sha256.init(.{});
var file_buf: [BUF_SIZE]u8 = undefined;
var reader = file.reader(&file_buf);
var reader = file.reader(io, &file_buf);
var read_buf: [BUF_SIZE]u8 = undefined;
var n = try reader.interface.readSliceShort(&read_buf);
while (n != 0) {
Expand All @@ -21,21 +21,20 @@ fn sha256_digest(
return sha256.finalResult();
}

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
pub fn main(init: std.process.Init) !void {
const gpa = init.gpa;
const io = init.io;

const file = try fs.cwd().openFile("tests/zig-zen.txt", .{});
defer file.close();
const file = try std.Io.Dir.cwd().openFile(io, "tests/zig-zen.txt", .{});
defer file.close(io);

const digest = try sha256_digest(file);
const digest = try sha256_digest(file, io);
const hex_digest = try std.fmt.allocPrint(
allocator,
gpa,
"{x}",
.{&digest},
);
defer allocator.free(hex_digest);
defer gpa.free(hex_digest);

try std.testing.expectEqualStrings(
"2210e9263ece534df0beff39ec06850d127dc60aa17bbc7769c5dc2ea5f3e342",
Expand Down
18 changes: 9 additions & 9 deletions assets/src/02-03.zig
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
const std = @import("std");

pub fn main() !void {
var dbg = std.heap.DebugAllocator(.{}){};
defer _ = dbg.deinit();
const allocator = dbg.allocator();
pub fn main(init: std.process.Init) !void {
const gpa = init.gpa;
const io = init.io;

const password = "happy";

//Random salt (Must be at least 8 bytes, recommended 16+)
var raw: [8]u8 = undefined;
std.crypto.random.bytes(&raw);
const salt = try std.fmt.allocPrint(allocator, "{s}", .{std.fmt.bytesToHex(raw, .lower)});
defer allocator.free(salt);
try std.Io.randomSecure(io, &raw);
const salt = try std.fmt.allocPrint(gpa, "{s}", .{std.fmt.bytesToHex(raw, .lower)});
defer gpa.free(salt);

//Parameters for Argon2id
const params = std.crypto.pwhash.argon2.Params{
Expand All @@ -24,13 +23,14 @@ pub fn main() !void {
var derived: [dk_len]u8 = undefined;

try std.crypto.pwhash.argon2.kdf(
allocator,
gpa,
&derived,
password,
salt,
params,
.argon2id, //argon2i, argon2d and argon2id
io,
);

std.debug.print("Argon2id derived key: {s}\n", .{std.fmt.bytesToHex(derived, .lower)});
}
}
29 changes: 15 additions & 14 deletions assets/src/03-01.zig
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
const std = @import("std");
const time = std.time;
const Instant = time.Instant;
const Timer = time.Timer;
const Io = std.Io;
const print = std.debug.print;

fn expensive_function() void {
fn expensiveFunction(io: Io) !void {
// sleep 500ms
std.Thread.sleep(time.ns_per_ms * 500);
try Io.sleep(io, .fromMilliseconds(500), .awake);
}

pub fn main() !void {
// Method 1: Instant
const start = try Instant.now();
expensive_function();
const end = try Instant.now();
const elapsed1: f64 = @floatFromInt(end.since(start));
pub fn main(init: std.process.Init) !void {
const io = init.io;

// Method 1: Two timestamps on the awake (monotonic) clock.
const start = Io.Clock.awake.now(io);
try expensiveFunction(io);
const end = Io.Clock.awake.now(io);
const elapsed1: f64 = @floatFromInt(start.durationTo(end).nanoseconds);
print("Time elapsed is: {d:.3}ms\n", .{
elapsed1 / time.ns_per_ms,
});

// Method 2: Timer
var timer = try Timer.start();
expensive_function();
const elapsed2: f64 = @floatFromInt(timer.read());
// Method 2: Timestamp.untilNow
const before = Io.Clock.awake.now(io);
try expensiveFunction(io);
const elapsed2: f64 = @floatFromInt(before.untilNow(io, .awake).nanoseconds);
print("Time elapsed is: {d:.3}ms\n", .{
elapsed2 / time.ns_per_ms,
});
Expand Down
Loading
Loading