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; }