Line-ending conversion adapters for Zig's std.Io.Reader and std.Io.Writer interfaces. Wrap an existing reader or writer and the conversion happens transparently as data streams through.
  • Zig 98.4%
  • Nix 1.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jeffrey C. Ollie bc2ad0c39b
add throughput benchmarks
`zig build bench` streams 8 MiB input profiles (typical lines, dense
2-char lines, no endings, lone CRs) through each adapter into a
discarding writer and reports best-of-5 MB/s against a chunked memcpy
baseline. Pass-through runs at memcpy speed; converting workloads run
at 2.4-3.8 GB/s on typical text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV3MLFkp4cZoKgu4HCyD7
2026-08-30 04:18:24 -05:00
LICENSES add CRLF/LF line-ending converters for std.Io readers and writers 2026-08-30 03:41:19 -05:00
src add throughput benchmarks 2026-08-30 04:18:24 -05:00
.gitignore add CRLF/LF line-ending converters for std.Io readers and writers 2026-08-30 03:41:19 -05:00
build.zig add throughput benchmarks 2026-08-30 04:18:24 -05:00
build.zig.zon add README and a docs build step 2026-08-30 04:06:20 -05:00
flake.lock first 2026-08-30 03:20:42 -05:00
flake.nix first 2026-08-30 03:20:42 -05:00
README.md add throughput benchmarks 2026-08-30 04:18:24 -05:00
REUSE.toml add CRLF/LF line-ending converters for std.Io readers and writers 2026-08-30 03:41:19 -05:00

zig-crlf

Line-ending conversion adapters for Zig's std.Io.Reader and std.Io.Writer interfaces. Wrap an existing reader or writer and the conversion happens transparently as data streams through.

Requires Zig 0.16.0.

Semantics

  • CrlfToLfReader / CrlfToLfWriter convert CRLF (\r\n) to LF (\n). A CR that is not followed by LF is passed through unchanged.
  • LfToCrlfReader / LfToCrlfWriter convert LF (\n) to CRLF (\r\n). An LF that is already preceded by CR is passed through unchanged, so existing CRLF sequences are not doubled.

Because lone CRs pass through and existing CRLFs are never doubled, LF → CRLF → LF round trips losslessly.

Usage

Fetch and add the dependency:

zig fetch --save <url>
const mod = b.addModule("my_module", .{ ... });
mod.addImport("zig_crlf", b.dependency("zig_crlf", .{}).module("zig_crlf"));

Converting while reading:

const zig_crlf = @import("zig_crlf");

var buffer: [4096]u8 = undefined;
var converted: zig_crlf.CrlfToLfReader = .init(some_reader, &buffer);
// Read LF-terminated data from converted.interface, e.g.:
const line = try converted.interface.takeDelimiterExclusive('\n');

Converting while writing:

var buffer: [4096]u8 = undefined;
var converted: zig_crlf.LfToCrlfWriter = .init(some_writer, &buffer);
try converted.writer.print("hello\n", .{}); // writes "hello\r\n" downstream
try converted.writer.flush();

CrlfToLfWriter is the one special case: a trailing CR cannot be forwarded until the byte after it is seen, so call finish() instead of flush() once all data has been written.

The underlying reader passed to CrlfToLfReader must have a buffer capacity of at least 2 bytes so a CRLF pair split across two fills can be reassembled.

CLI

The package also builds a small demo filter:

zig build
zig-out/bin/zig_crlf --to-lf   < dos.txt  > unix.txt
zig-out/bin/zig_crlf --to-crlf < unix.txt > dos.txt

Development

zig build test        # run unit tests (fuzz bodies run once as smoke tests)
zig build test --fuzz # fuzz the adapters against reference implementations
zig build docs        # generate API documentation into zig-out/docs
zig build bench       # run throughput benchmarks (ReleaseFast)

Note that src/test_runner.zig is a vendored copy of Zig's default test runner patched for a zig 0.16.0 bug that breaks compilation in fuzz mode, and tests are built with the LLVM backend because the self-hosted backend emits no fuzz coverage (ziglang/zig#30655).

License

MIT. This project is REUSE compliant.