137 lines
4.3 KiB
Zig
137 lines
4.3 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: have [NAME...]
|
|
\\
|
|
\\List executable command names found in PATH, one per line.
|
|
\\With NAME args, print the names that are available and exit 1 if any are missing.
|
|
\\This inspects PATH only; it does not know shell builtins, aliases, functions, or packages.
|
|
\\
|
|
\\stdout:
|
|
\\ one command name per line
|
|
\\
|
|
\\examples:
|
|
\\ have | grep '^zig$'
|
|
\\ have zig git curl
|
|
\\
|
|
);
|
|
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 pathEnv() []const u8 {
|
|
const raw = std.c.getenv("PATH") orelse return "";
|
|
return std.mem.span(raw);
|
|
}
|
|
|
|
fn commandName(name: []const u8) bool {
|
|
return name.len != 0 and std.mem.indexOfScalar(u8, name, '/') == null;
|
|
}
|
|
|
|
fn executableFileInDir(dir: std.Io.Dir, io: std.Io, name: []const u8) bool {
|
|
const stat = dir.statFile(io, name, .{}) catch return false;
|
|
if (stat.kind != .file) return false;
|
|
dir.access(io, name, .{ .execute = true }) catch return false;
|
|
return true;
|
|
}
|
|
|
|
fn haveOne(io: std.Io, name: []const u8) bool {
|
|
if (!commandName(name)) return false;
|
|
|
|
var dirs = std.mem.splitScalar(u8, pathEnv(), ':');
|
|
while (dirs.next()) |dir_path| {
|
|
const actual_dir = if (dir_path.len == 0) "." else dir_path;
|
|
var dir = std.Io.Dir.cwd().openDir(io, actual_dir, .{}) catch continue;
|
|
defer dir.close(io);
|
|
if (executableFileInDir(dir, io, name)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
fn listDir(io: std.Io, writer: *std.Io.Writer, seen: *std.StringHashMap(void), dir_path: []const u8) !void {
|
|
var dir = std.Io.Dir.cwd().openDir(io, dir_path, .{ .iterate = true }) catch return;
|
|
defer dir.close(io);
|
|
|
|
var it = dir.iterate();
|
|
while (try it.next(io)) |entry| {
|
|
if (!commandName(entry.name)) continue;
|
|
if (seen.contains(entry.name)) continue;
|
|
if (!executableFileInDir(dir, io, entry.name)) continue;
|
|
|
|
const owned = try seen.allocator.dupe(u8, entry.name);
|
|
errdefer seen.allocator.free(owned);
|
|
try seen.put(owned, {});
|
|
try writer.print("{s}\n", .{entry.name});
|
|
}
|
|
}
|
|
|
|
fn listAll(allocator: std.mem.Allocator, io: std.Io, writer: *std.Io.Writer) !void {
|
|
var seen = std.StringHashMap(void).init(allocator);
|
|
defer {
|
|
var keys = seen.keyIterator();
|
|
while (keys.next()) |key| allocator.free(key.*);
|
|
seen.deinit();
|
|
}
|
|
|
|
var dirs = std.mem.splitScalar(u8, pathEnv(), ':');
|
|
while (dirs.next()) |dir_path| {
|
|
const actual_dir = if (dir_path.len == 0) "." else dir_path;
|
|
try listDir(io, writer, &seen, actual_dir);
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
var out_buf: [8192]u8 = undefined;
|
|
var out = std.Io.File.stdout().writer(init.io, &out_buf);
|
|
|
|
var names = std.ArrayList([]const u8).empty;
|
|
defer names.deinit(allocator);
|
|
while (args.next()) |arg| {
|
|
if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
|
|
try usage(std.Io.File.stdout(), init.io);
|
|
return;
|
|
}
|
|
try names.append(allocator, arg);
|
|
}
|
|
|
|
if (names.items.len == 0) {
|
|
listAll(allocator, init.io, &out.interface) catch |err| die(init.io, "have: {s}\n", .{@errorName(err)});
|
|
try out.interface.flush();
|
|
return;
|
|
}
|
|
|
|
var missing = false;
|
|
for (names.items) |name| {
|
|
if (haveOne(init.io, name)) {
|
|
try out.interface.print("{s}\n", .{name});
|
|
} else {
|
|
missing = true;
|
|
}
|
|
}
|
|
try out.interface.flush();
|
|
if (missing) std.process.exit(1);
|
|
}
|
|
|
|
test commandName {
|
|
try std.testing.expect(commandName("zig"));
|
|
try std.testing.expect(commandName("."));
|
|
try std.testing.expect(!commandName(""));
|
|
try std.testing.expect(!commandName("./zig"));
|
|
try std.testing.expect(!commandName("bin/zig"));
|
|
}
|