mirror of
https://github.com/ziglang/zig.git
synced 2025-12-21 05:33:15 +00:00
243 lines
8.0 KiB
Zig
Vendored
243 lines
8.0 KiB
Zig
Vendored
const std = @import("std");
|
|
const mem = std.mem;
|
|
const builtin = @import("builtin");
|
|
const system_defaults = @import("system_defaults");
|
|
const is_windows = builtin.os.tag == .windows;
|
|
|
|
fn readFileFake(entries: []const Filesystem.Entry, path: []const u8, buf: []u8) ?[]const u8 {
|
|
@setCold(true);
|
|
for (entries) |entry| {
|
|
if (mem.eql(u8, entry.path, path)) {
|
|
const len = @min(entry.contents.len, buf.len);
|
|
@memcpy(buf[0..len], entry.contents[0..len]);
|
|
return buf[0..len];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
fn findProgramByNameFake(entries: []const Filesystem.Entry, name: []const u8, path: ?[]const u8, buf: []u8) ?[]const u8 {
|
|
@setCold(true);
|
|
if (mem.indexOfScalar(u8, name, '/') != null) {
|
|
@memcpy(buf[0..name.len], name);
|
|
return buf[0..name.len];
|
|
}
|
|
const path_env = path orelse return null;
|
|
var fib = std.heap.FixedBufferAllocator.init(buf);
|
|
|
|
var it = mem.tokenizeScalar(u8, path_env, system_defaults.path_sep);
|
|
while (it.next()) |path_dir| {
|
|
defer fib.reset();
|
|
const full_path = std.fs.path.join(fib.allocator(), &.{ path_dir, name }) catch continue;
|
|
if (canExecuteFake(entries, full_path)) return full_path;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
fn canExecuteFake(entries: []const Filesystem.Entry, path: []const u8) bool {
|
|
@setCold(true);
|
|
for (entries) |entry| {
|
|
if (mem.eql(u8, entry.path, path)) {
|
|
return entry.executable;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
fn existsFake(entries: []const Filesystem.Entry, path: []const u8) bool {
|
|
@setCold(true);
|
|
var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
|
var fib = std.heap.FixedBufferAllocator.init(&buf);
|
|
const resolved = std.fs.path.resolvePosix(fib.allocator(), &.{path}) catch return false;
|
|
for (entries) |entry| {
|
|
if (mem.eql(u8, entry.path, resolved)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
fn canExecutePosix(path: []const u8) bool {
|
|
std.os.access(path, std.os.X_OK) catch return false;
|
|
// Todo: ensure path is not a directory
|
|
return true;
|
|
}
|
|
|
|
/// TODO
|
|
fn canExecuteWindows(path: []const u8) bool {
|
|
_ = path;
|
|
return true;
|
|
}
|
|
|
|
/// TODO
|
|
fn findProgramByNameWindows(allocator: std.mem.Allocator, name: []const u8, path: ?[]const u8, buf: []u8) ?[]const u8 {
|
|
_ = path;
|
|
_ = buf;
|
|
_ = name;
|
|
_ = allocator;
|
|
return null;
|
|
}
|
|
|
|
/// TODO: does WASI need special handling?
|
|
fn findProgramByNamePosix(name: []const u8, path: ?[]const u8, buf: []u8) ?[]const u8 {
|
|
if (mem.indexOfScalar(u8, name, '/') != null) {
|
|
@memcpy(buf[0..name.len], name);
|
|
return buf[0..name.len];
|
|
}
|
|
const path_env = path orelse return null;
|
|
var fib = std.heap.FixedBufferAllocator.init(buf);
|
|
|
|
var it = mem.tokenizeScalar(u8, path_env, system_defaults.path_sep);
|
|
while (it.next()) |path_dir| {
|
|
defer fib.reset();
|
|
const full_path = std.fs.path.join(fib.allocator(), &.{ path_dir, name }) catch continue;
|
|
if (canExecutePosix(full_path)) return full_path;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
pub const Filesystem = union(enum) {
|
|
real: void,
|
|
fake: []const Entry,
|
|
|
|
const Entry = struct {
|
|
path: []const u8,
|
|
contents: []const u8 = "",
|
|
executable: bool = false,
|
|
};
|
|
|
|
const FakeDir = struct {
|
|
entries: []const Entry,
|
|
path: []const u8,
|
|
|
|
fn iterate(self: FakeDir) FakeDir.Iterator {
|
|
return .{
|
|
.entries = self.entries,
|
|
.base = self.path,
|
|
};
|
|
}
|
|
|
|
const Iterator = struct {
|
|
entries: []const Entry,
|
|
base: []const u8,
|
|
i: usize = 0,
|
|
|
|
const Self = @This();
|
|
|
|
fn next(self: *@This()) !?std.fs.IterableDir.Entry {
|
|
while (self.i < self.entries.len) {
|
|
const entry = self.entries[self.i];
|
|
self.i += 1;
|
|
if (entry.path.len == self.base.len) continue;
|
|
if (std.mem.startsWith(u8, entry.path, self.base)) {
|
|
const remaining = entry.path[self.base.len + 1 ..];
|
|
if (std.mem.indexOfScalar(u8, remaining, std.fs.path.sep) != null) continue;
|
|
const extension = std.fs.path.extension(remaining);
|
|
const kind: std.fs.IterableDir.Entry.Kind = if (extension.len == 0) .directory else .file;
|
|
return .{ .name = remaining, .kind = kind };
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
};
|
|
|
|
const IterableDir = union(enum) {
|
|
dir: std.fs.IterableDir,
|
|
fake: FakeDir,
|
|
|
|
pub fn iterate(self: IterableDir) Iterator {
|
|
return switch (self) {
|
|
.dir => |dir| .{ .iterator = dir.iterate() },
|
|
.fake => |fake| .{ .fake = fake.iterate() },
|
|
};
|
|
}
|
|
|
|
pub fn close(self: *IterableDir) void {
|
|
switch (self.*) {
|
|
.dir => |*d| d.close(),
|
|
.fake => {},
|
|
}
|
|
}
|
|
};
|
|
|
|
const Iterator = union(enum) {
|
|
iterator: std.fs.IterableDir.Iterator,
|
|
fake: FakeDir.Iterator,
|
|
|
|
pub fn next(self: *Iterator) std.fs.IterableDir.Iterator.Error!?std.fs.IterableDir.Entry {
|
|
return switch (self.*) {
|
|
.iterator => |*it| it.next(),
|
|
.fake => |*it| it.next(),
|
|
};
|
|
}
|
|
};
|
|
|
|
pub fn exists(fs: Filesystem, path: []const u8) bool {
|
|
switch (fs) {
|
|
.real => {
|
|
std.os.access(path, std.os.F_OK) catch return false;
|
|
return true;
|
|
},
|
|
.fake => |paths| return existsFake(paths, path),
|
|
}
|
|
}
|
|
|
|
pub fn joinedExists(fs: Filesystem, parts: []const []const u8) bool {
|
|
var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
|
var fib = std.heap.FixedBufferAllocator.init(&buf);
|
|
const joined = std.fs.path.join(fib.allocator(), parts) catch return false;
|
|
return fs.exists(joined);
|
|
}
|
|
|
|
pub fn canExecute(fs: Filesystem, path: []const u8) bool {
|
|
return switch (fs) {
|
|
.real => if (is_windows) canExecuteWindows(path) else canExecutePosix(path),
|
|
.fake => |entries| canExecuteFake(entries, path),
|
|
};
|
|
}
|
|
|
|
/// Search for an executable named `name` using platform-specific logic
|
|
/// If it's found, write the full path to `buf` and return a slice of it
|
|
/// Otherwise retun null
|
|
pub fn findProgramByName(fs: Filesystem, allocator: std.mem.Allocator, name: []const u8, path: ?[]const u8, buf: []u8) ?[]const u8 {
|
|
std.debug.assert(name.len > 0);
|
|
return switch (fs) {
|
|
.real => if (is_windows) findProgramByNameWindows(allocator, name, path, buf) else findProgramByNamePosix(name, path, buf),
|
|
.fake => |entries| findProgramByNameFake(entries, name, path, buf),
|
|
};
|
|
}
|
|
|
|
/// Read the file at `path` into `buf`.
|
|
/// Returns null if any errors are encountered
|
|
/// Otherwise returns a slice of `buf`. If the file is larger than `buf` partial contents are returned
|
|
pub fn readFile(fs: Filesystem, path: []const u8, buf: []u8) ?[]const u8 {
|
|
return switch (fs) {
|
|
.real => {
|
|
const file = std.fs.cwd().openFile(path, .{}) catch return null;
|
|
defer file.close();
|
|
|
|
const bytes_read = file.readAll(buf) catch return null;
|
|
return buf[0..bytes_read];
|
|
},
|
|
.fake => |entries| readFileFake(entries, path, buf),
|
|
};
|
|
}
|
|
|
|
pub fn openIterableDir(fs: Filesystem, dir_name: []const u8) std.fs.Dir.OpenError!IterableDir {
|
|
return switch (fs) {
|
|
.real => .{ .dir = try std.fs.cwd().openIterableDir(dir_name, .{ .access_sub_paths = false }) },
|
|
.fake => |entries| .{ .fake = .{ .entries = entries, .path = dir_name } },
|
|
};
|
|
}
|
|
};
|
|
|
|
test "Fake filesystem" {
|
|
const fs: Filesystem = .{ .fake = &.{
|
|
.{ .path = "/usr/bin" },
|
|
} };
|
|
try std.testing.expect(fs.exists("/usr/bin"));
|
|
try std.testing.expect(fs.exists("/usr/bin/foo/.."));
|
|
try std.testing.expect(!fs.exists("/usr/bin/bar"));
|
|
}
|