179 lines
5.7 KiB
Zig
179 lines
5.7 KiB
Zig
const std = @import("std");
|
|
|
|
fn usage(file: std.Io.File, io: std.Io) !void {
|
|
var buf: [1024]u8 = undefined;
|
|
var w = file.writer(io, &buf);
|
|
try w.interface.writeAll(
|
|
\\usage: facts [PATH...]
|
|
\\
|
|
\\Print file metadata as TSV. With no PATH args, reads one path per stdin line.
|
|
\\Columns: path kind size mode mtime_ns
|
|
\\
|
|
\\stdout is records only; diagnostics go to stderr.
|
|
\\No filtering, sorting, deletion, exec, expression language, JSON, color, or hidden writes.
|
|
\\
|
|
\\examples:
|
|
\\ files src | facts | pick --header path size
|
|
\\ facts README.md build.zig
|
|
\\
|
|
);
|
|
try w.interface.flush();
|
|
}
|
|
|
|
fn die(io: std.Io, comptime fmt: []const u8, args: anytype) noreturn {
|
|
var buf: [512]u8 = undefined;
|
|
var w = std.Io.File.stderr().writer(io, &buf);
|
|
w.interface.print(fmt, args) catch {};
|
|
w.interface.flush() catch {};
|
|
std.process.exit(1);
|
|
}
|
|
|
|
fn isHelp(arg: []const u8) bool {
|
|
return std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h");
|
|
}
|
|
|
|
fn kindName(kind: std.Io.File.Kind) []const u8 {
|
|
return switch (kind) {
|
|
.file => "file",
|
|
.directory => "dir",
|
|
.sym_link => "symlink",
|
|
.block_device => "block",
|
|
.character_device => "char",
|
|
.named_pipe => "fifo",
|
|
.unix_domain_socket => "socket",
|
|
.whiteout => "whiteout",
|
|
.door => "door",
|
|
.event_port => "eventport",
|
|
.unknown => "unknown",
|
|
};
|
|
}
|
|
|
|
fn modeValue(stat: std.Io.File.Stat) u64 {
|
|
if (@hasDecl(std.Io.File.Permissions, "toMode")) return @intCast(stat.permissions.toMode());
|
|
return 0;
|
|
}
|
|
|
|
fn printError(io: std.Io, path: []const u8, err: anyerror) !void {
|
|
var err_buf: [512]u8 = undefined;
|
|
var errw = std.Io.File.stderr().writer(io, &err_buf);
|
|
try errw.interface.print("facts: {s}: {s}\n", .{ path, @errorName(err) });
|
|
try errw.interface.flush();
|
|
}
|
|
|
|
fn unsupportedPathByte(path: []const u8) ?[]const u8 {
|
|
for (path) |byte| switch (byte) {
|
|
'\t' => return "tab",
|
|
'\n' => return "newline",
|
|
'\r' => return "carriage return",
|
|
else => {},
|
|
};
|
|
return null;
|
|
}
|
|
|
|
fn printUnsupportedPath(io: std.Io, byte_name: []const u8) !void {
|
|
var err_buf: [512]u8 = undefined;
|
|
var errw = std.Io.File.stderr().writer(io, &err_buf);
|
|
try errw.interface.print("facts: unsupported path: contains {s}\n", .{byte_name});
|
|
try errw.interface.flush();
|
|
}
|
|
|
|
fn printFact(io: std.Io, writer: *std.Io.Writer, path: []const u8) !bool {
|
|
if (unsupportedPathByte(path)) |byte_name| {
|
|
try printUnsupportedPath(io, byte_name);
|
|
return false;
|
|
}
|
|
const st = std.Io.Dir.cwd().statFile(io, path, .{}) catch |err| {
|
|
try printError(io, path, err);
|
|
return false;
|
|
};
|
|
try writer.print("{s}\t{s}\t{}\t{o}\t{}\n", .{ path, kindName(st.kind), st.size, modeValue(st), st.mtime.nanoseconds });
|
|
return true;
|
|
}
|
|
|
|
fn printStdinError(io: std.Io, comptime message: []const u8) !void {
|
|
var err_buf: [512]u8 = undefined;
|
|
var errw = std.Io.File.stderr().writer(io, &err_buf);
|
|
try errw.interface.writeAll("facts: stdin: " ++ message ++ "\n");
|
|
try errw.interface.flush();
|
|
}
|
|
|
|
fn printStdinLine(io: std.Io, writer: *std.Io.Writer, raw: []const u8) !bool {
|
|
if (raw.len == 0) return true;
|
|
const ok = try printFact(io, writer, raw);
|
|
try writer.flush();
|
|
return ok;
|
|
}
|
|
|
|
fn readStdinLines(io: std.Io, writer: *std.Io.Writer) !bool {
|
|
var had_error = false;
|
|
var read_buf: [8192]u8 = undefined;
|
|
var line_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
|
|
var line_len: usize = 0;
|
|
var dropping_long_line = false;
|
|
|
|
while (true) {
|
|
const n = try std.posix.read(std.posix.STDIN_FILENO, &read_buf);
|
|
if (n == 0) break;
|
|
for (read_buf[0..n]) |byte| {
|
|
if (byte == '\n') {
|
|
if (!dropping_long_line) {
|
|
if (!try printStdinLine(io, writer, line_buf[0..line_len])) had_error = true;
|
|
}
|
|
line_len = 0;
|
|
dropping_long_line = false;
|
|
continue;
|
|
}
|
|
if (dropping_long_line) continue;
|
|
if (line_len == line_buf.len) {
|
|
try printStdinError(io, "input path exceeds max path length");
|
|
had_error = true;
|
|
line_len = 0;
|
|
dropping_long_line = true;
|
|
continue;
|
|
}
|
|
line_buf[line_len] = byte;
|
|
line_len += 1;
|
|
}
|
|
}
|
|
|
|
if (!dropping_long_line and line_len != 0) {
|
|
if (!try printStdinLine(io, writer, line_buf[0..line_len])) had_error = true;
|
|
}
|
|
return had_error;
|
|
}
|
|
|
|
pub fn main(init: std.process.Init) !void {
|
|
const allocator = init.gpa;
|
|
|
|
var help_args = try std.process.Args.Iterator.initAllocator(init.minimal.args, allocator);
|
|
defer help_args.deinit();
|
|
_ = help_args.skip();
|
|
while (help_args.next()) |arg| {
|
|
if (isHelp(arg)) {
|
|
try usage(std.Io.File.stdout(), init.io);
|
|
return;
|
|
}
|
|
}
|
|
|
|
var args = try std.process.Args.Iterator.initAllocator(init.minimal.args, allocator);
|
|
defer args.deinit();
|
|
_ = args.skip();
|
|
|
|
var out_buf: [8192]u8 = undefined;
|
|
var out = std.Io.File.stdout().writer(init.io, &out_buf);
|
|
try out.interface.writeAll("path\tkind\tsize\tmode\tmtime_ns\n");
|
|
|
|
var saw_arg = false;
|
|
var had_error = false;
|
|
while (args.next()) |arg| {
|
|
saw_arg = true;
|
|
if (!try printFact(init.io, &out.interface, arg)) had_error = true;
|
|
}
|
|
if (!saw_arg) {
|
|
try out.interface.flush();
|
|
had_error = readStdinLines(init.io, &out.interface) catch |err| die(init.io, "facts: stdin: {s}\n", .{@errorName(err)});
|
|
}
|
|
try out.interface.flush();
|
|
if (had_error) std.process.exit(1);
|
|
}
|