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