This repository has been archived on 2026-07-20. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
lines/lines.zig
T
2026-07-04 20:44:28 +02:00

122 lines
4.4 KiB
Zig

const std = @import("std");
// req: cli/002
fn usage(file: std.Io.File, io: std.Io) !void {
var buffer: [1024]u8 = undefined;
var writer = file.writer(io, &buffer);
try writer.interface.writeAll(
\\usage: lines FILE START [END]
\\
\\Print a 1-based inclusive line range from FILE to stdout.
\\START and END must be positive decimal integers; END defaults to START.
\\No writes, no regex, no program language, no stdin prompts.
\\
\\output:
\\ stdout: selected file lines only, with original line order preserved
\\ stderr: usage and diagnostics only
\\
\\exit status:
\\ 0 on success, 1 on runtime/input errors, 2 on usage errors
\\
\\examples:
\\ lines src/main.zig 40 80
\\ lines README.md 1 20 | grep usage
\\
);
try writer.interface.flush();
}
fn fail(io: std.Io, comptime fmt: []const u8, args: anytype) noreturn {
var buffer: [512]u8 = undefined;
var writer = std.Io.File.stderr().writer(io, &buffer);
writer.interface.print(fmt, args) catch {};
writer.interface.flush() catch {};
std.process.exit(1);
}
fn parseLineNo(text: []const u8, name: []const u8, io: std.Io) u64 {
const value = std.fmt.parseInt(u64, text, 10) catch fail(io, "lines: invalid {s}: {s}\n", .{ name, text });
if (value == 0) fail(io, "lines: {s} must be positive\n", .{name});
return value;
}
fn stdoutFailure(out: *std.Io.File.Writer, io: std.Io) noreturn {
if (out.err) |err| switch (err) {
error.BrokenPipe => std.process.exit(0),
else => fail(io, "lines: stdout: {s}\n", .{@errorName(err)}),
};
fail(io, "lines: stdout: WriteFailed\n", .{});
}
fn writeStdout(out: *std.Io.File.Writer, io: std.Io, bytes: []const u8) void {
out.interface.writeAll(bytes) catch stdoutFailure(out, io);
}
fn flushStdout(out: *std.Io.File.Writer, io: std.Io) void {
out.interface.flush() catch stdoutFailure(out, io);
}
// req: cli/001
// req: robustness/002
// req: robustness/003
// req: performance/001
fn streamRange(file: std.Io.File, io: std.Io, start: u64, end: u64) void {
var read_buffer: [64 * 1024]u8 = undefined;
var reader_file = file.readerStreaming(io, &read_buffer);
var chunk: [64 * 1024]u8 = undefined;
var out_buffer: [64 * 1024]u8 = undefined;
var stdout_file = std.Io.File.stdout().writerStreaming(io, &out_buffer);
var line_no: u64 = 1;
while (line_no <= end) {
const n = reader_file.interface.readSliceShort(&chunk) catch |err| fail(io, "lines: read failed: {s}\n", .{@errorName(err)});
if (n == 0) break;
var pos: usize = 0;
while (pos < n and line_no <= end) {
const rest = chunk[pos..n];
const line_end = if (std.mem.indexOfScalar(u8, rest, '\n')) |newline| pos + newline + 1 else n;
if (line_no >= start) writeStdout(&stdout_file, io, chunk[pos..line_end]);
if (line_end <= n and chunk[line_end - 1] == '\n') line_no += 1;
pos = line_end;
}
}
flushStdout(&stdout_file, io);
}
pub fn main(init: std.process.Init) !void {
const allocator = init.gpa;
var args = try std.process.Args.Iterator.initAllocator(init.minimal.args, allocator);
defer args.deinit();
_ = args.skip();
const path = args.next() orelse {
try usage(std.Io.File.stderr(), init.io);
std.process.exit(2);
};
if (std.mem.eql(u8, path, "--help") or std.mem.eql(u8, path, "-h")) {
try usage(std.Io.File.stdout(), init.io);
return;
}
const start_text = args.next() orelse {
try usage(std.Io.File.stderr(), init.io);
std.process.exit(2);
};
const maybe_end_text = args.next();
if (args.next() != null) {
try usage(std.Io.File.stderr(), init.io);
std.process.exit(2);
}
// req: cli/001
// req: cli/001.e1
// req: cli/003
const start = parseLineNo(start_text, "START", init.io);
const end = if (maybe_end_text) |end_text| parseLineNo(end_text, "END", init.io) else start;
if (end < start) fail(init.io, "lines: END must be >= START\n", .{});
const file = std.Io.Dir.cwd().openFile(init.io, path, .{ .mode = .read_only, .allow_directory = false }) catch |err| fail(init.io, "lines: {s}: {s}\n", .{ path, @errorName(err) });
defer file.close(init.io);
streamRange(file, init.io, start, end);
}