Initial import
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user