mattrobenolt/txtar

Go-style txtar archives for Zig

★ 3Forks 0ZigGitHub ↗Compare

README

txtar

txtar is a small Zig 0.16 library and command-line tool for Go-style text archives.

A txtar archive is plain text: an optional comment followed by file entries. Each file starts with a marker line, then its contents.

this is the archive comment
-- foo.txt --
hello
-- dir/bar.txt --
world

This package is modeled after Go's golang.org/x/tools/txtar, but uses Zig ownership conventions and std.Io streaming APIs.

Library

Use parse and format when the whole archive comfortably fits in memory.

const std = @import("std");
const txtar = @import("txtar");

pub fn example(gpa: std.mem.Allocator, input: []const u8) ![]u8 {
    var archive = try txtar.parse(gpa, input);
    defer archive.deinit(gpa);

    // archive.comment is owned by archive.
    // archive.files contains owned name/data slices.

    return txtar.format(gpa, archive);
}

parse returns an owned txtar.Archive:

pub const Archive = struct {
    pub const empty: Archive = .{ .comment = "", .files = &.{} };

    comment: []const u8,
    files: []const File,
};

pub const File = struct {
    name: []const u8,
    data: []const u8,
};

Call archive.deinit(gpa) when done. The allocator passed to deinit must be the same allocator passed to parse.

Both parsing and formatting normalize a missing trailing newline. That matches Go's txtar behavior: comments and file contents are line-oriented, so an archive ending in world is treated like one ending in world\n.

format assumes the archive is valid txtar data: file names should be non-empty, and comments or file contents should not contain marker lines unless you intend them to start a new file entry when parsed again.

Streaming API

Use Reader and Writer when you want to avoid loading every file into memory.

var reader: std.Io.Reader = .fixed(input);
var archive: txtar.Reader = .init(&reader);

var comment_writer: std.Io.Writer.Discarding = .init(&.{});
try archive.writeCommentTo(&comment_writer.writer);

while (try archive.next()) |entry| {
    std.debug.print("file: {s}\n", .{entry.name});
    try entry.writeTo(output_writer);
}

To write an archive incrementally:

var archive: txtar.Writer = .init(output_writer);
try archive.writeComment("generated by my tool\n");

var entry = try archive.beginEntry("foo.txt");
try entry.writeAll("hello\n");
try entry.finish();

EntryWriter.finish appends a newline when the entry data does not already end in one. If you start an entry, finish it before starting the next one.

Installation

Homebrew

brew install --cask mattrobenolt/stuff/txtar

CLI

The repository also builds a txtar command.

Create an archive:

txtar -c -f archive.txtar LICENSE src/root.zig

Extract an archive:

txtar -x -f archive.txtar -C out

Verbose mode prints added or extracted paths. Short flags can be combined, and -f/-C may take their value as either the rest of the same argument or the next argument:

txtar -cvf archive.txtar src
txtar -xvf archive.txtar -C out

The extractor rejects absolute paths and .. path components.

Development

The project requires Zig 0.16.0. zig build test runs the library and CLI tests.

nix develop
zig build test
zig build
zig build run -- -cf sample.txtar LICENSE

Contributors

mattrobenolt

Issues