165 lines
5.7 KiB
Zig
165 lines
5.7 KiB
Zig
const std = @import("std");
|
|
|
|
fn usage(file: std.Io.File, io: std.Io) !void {
|
|
var buffer: [1536]u8 = undefined;
|
|
var writer = file.writer(io, &buffer);
|
|
try writer.interface.writeAll(
|
|
\\usage: files [PATH...]
|
|
\\
|
|
\\Recursively print file-like paths under each PATH, one path per line.
|
|
\\With no PATH, . is used. Directories are traversed; symlinked directories are not followed.
|
|
\\If PATH is a non-directory file-like path, that path is printed exactly once.
|
|
\\
|
|
\\stdout:
|
|
\\ Paths only: One path per line. No color, progress, prompts, paging, sorting, JSON, or decoration.
|
|
\\
|
|
\\scope:
|
|
\\ Enumerates paths only. No filtering, sorting, metadata.
|
|
\\ No deletion, exec, callbacks, network, hidden writes, config, or expression language.
|
|
\\
|
|
\\stderr:
|
|
\\ Usage errors and runtime diagnostics only.
|
|
\\
|
|
\\exit status:
|
|
\\ 0 success
|
|
\\ 2 usage error
|
|
\\ 1 runtime failure
|
|
\\
|
|
\\side effects:
|
|
\\ None.
|
|
\\
|
|
\\examples:
|
|
\\ files src | grep '\\.[cz]$'
|
|
\\ files . | sort | sed 20q
|
|
\\
|
|
);
|
|
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 stdoutFailure(io: std.Io, err: anyerror) noreturn {
|
|
fail(io, "files: stdout: {s}\n", .{@errorName(err)});
|
|
}
|
|
|
|
fn stdoutWriteError(out: anytype, err: anyerror) anyerror {
|
|
if (err == error.WriteFailed) return out.err orelse err;
|
|
return err;
|
|
}
|
|
|
|
fn writePath(out: anytype, root: []const u8, path: []const u8) !bool {
|
|
if (std.mem.eql(u8, root, ".")) {
|
|
out.interface.print("{s}\n", .{path}) catch |err| {
|
|
const cause = stdoutWriteError(out, err);
|
|
if (cause == error.BrokenPipe) return false;
|
|
return cause;
|
|
};
|
|
} else if (std.mem.endsWith(u8, root, "/")) {
|
|
out.interface.print("{s}{s}\n", .{ root, path }) catch |err| {
|
|
const cause = stdoutWriteError(out, err);
|
|
if (cause == error.BrokenPipe) return false;
|
|
return cause;
|
|
};
|
|
} else {
|
|
out.interface.print("{s}/{s}\n", .{ root, path }) catch |err| {
|
|
const cause = stdoutWriteError(out, err);
|
|
if (cause == error.BrokenPipe) return false;
|
|
return cause;
|
|
};
|
|
}
|
|
return true;
|
|
}
|
|
|
|
fn writeRawPath(out: anytype, path: []const u8) !bool {
|
|
out.interface.print("{s}\n", .{path}) catch |err| {
|
|
const cause = stdoutWriteError(out, err);
|
|
if (cause == error.BrokenPipe) return false;
|
|
return cause;
|
|
};
|
|
return true;
|
|
}
|
|
|
|
fn flushStdout(out: anytype) !bool {
|
|
out.interface.flush() catch |err| {
|
|
const cause = stdoutWriteError(out, err);
|
|
if (cause == error.BrokenPipe) return false;
|
|
return cause;
|
|
};
|
|
return true;
|
|
}
|
|
|
|
fn failAfterStdout(out: anytype, io: std.Io, comptime fmt: []const u8, args: anytype) noreturn {
|
|
const flushed = flushStdout(out) catch |err| stdoutFailure(io, err);
|
|
if (!flushed) std.process.exit(0);
|
|
fail(io, fmt, args);
|
|
}
|
|
|
|
fn emitRoot(root: []const u8, allocator: std.mem.Allocator, io: std.Io, out: anytype) bool {
|
|
if (root.len == 0) failAfterStdout(out, io, "files: empty PATH\n", .{});
|
|
|
|
const root_stat = std.Io.Dir.cwd().statFile(io, root, .{ .follow_symlinks = false }) catch |err| {
|
|
failAfterStdout(out, io, "files: {s}: {s}\n", .{ root, @errorName(err) });
|
|
};
|
|
if (root_stat.kind != .directory) {
|
|
const ok = writeRawPath(out, root) catch |write_err| stdoutFailure(io, write_err);
|
|
if (!ok) return false;
|
|
return true;
|
|
}
|
|
|
|
var dir = std.Io.Dir.cwd().openDir(io, root, .{ .iterate = true }) catch |err| {
|
|
failAfterStdout(out, io, "files: {s}: {s}\n", .{ root, @errorName(err) });
|
|
};
|
|
defer dir.close(io);
|
|
|
|
var walker = dir.walk(allocator) catch |err| failAfterStdout(out, io, "files: {s}: {s}\n", .{ root, @errorName(err) });
|
|
defer walker.deinit();
|
|
while (true) {
|
|
const maybe_entry = walker.next(io) catch |err| failAfterStdout(out, io, "files: {s}: {s}\n", .{ root, @errorName(err) });
|
|
const entry = maybe_entry orelse break;
|
|
if (entry.kind == .directory) continue;
|
|
const ok = writePath(out, root, entry.path) catch |err| stdoutFailure(io, err);
|
|
if (!ok) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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 first_arg = args.next();
|
|
const second_arg = if (first_arg == null) null else args.next();
|
|
if (first_arg) |arg| {
|
|
if (second_arg == null and (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h"))) {
|
|
try usage(std.Io.File.stdout(), init.io);
|
|
return;
|
|
}
|
|
}
|
|
|
|
var out_buffer: [8192]u8 = undefined;
|
|
var out = std.Io.File.stdout().writer(init.io, &out_buffer);
|
|
|
|
if (first_arg) |root| {
|
|
if (!emitRoot(root, allocator, init.io, &out)) return;
|
|
if (second_arg) |next_root| {
|
|
if (!emitRoot(next_root, allocator, init.io, &out)) return;
|
|
}
|
|
while (args.next()) |next_root| {
|
|
if (!emitRoot(next_root, allocator, init.io, &out)) return;
|
|
}
|
|
} else {
|
|
if (!emitRoot(".", allocator, init.io, &out)) return;
|
|
}
|
|
|
|
const flushed = flushStdout(&out) catch |err| stdoutFailure(init.io, err);
|
|
if (!flushed) return;
|
|
}
|