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
files/files.zig
T
2026-07-04 20:44:28 +02:00

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