Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ pub fn getPath(
## Installation

> [!NOTE]
> The default branch requires Zig `0.16.0-dev.1859+212968c57` or later.
> The default branch requires Zig `0.16.0-dev.1976+8e091047b` or later.

Initialize a `zig build` project if you haven't already.

Expand Down
2 changes: 1 addition & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.{
.name = .known_folders,
.version = "0.0.0",
.minimum_zig_version = "0.16.0-dev.1859+212968c57",
.minimum_zig_version = "0.16.0-dev.1976+8e091047b",
.dependencies = .{},
.paths = .{
"build.zig",
Expand Down
70 changes: 30 additions & 40 deletions known-folders.zig
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ pub const KnownFolder = enum {
executable_dir,
};

// Explicitly define possible errors to make it clearer what callers need to handle
pub const Error = error{ ParseError, OutOfMemory };
/// Most errors will not be reported here but instead cause null to be returned.
pub const Error = std.mem.Allocator.Error || std.Io.Cancelable;

pub const KnownFolderConfig = struct {
xdg_force_default: bool = false,
Expand All @@ -120,10 +120,11 @@ pub const KnownFolderConfig = struct {
pub fn open(
io: std.Io,
allocator: std.mem.Allocator,
environ: std.process.Environ.Map,
folder: KnownFolder,
args: std.Io.Dir.OpenOptions,
) (std.Io.Dir.OpenError || Error)!?std.Io.Dir {
const path = try getPath(io, allocator, folder) orelse return null;
const path = try getPath(io, allocator, environ, folder) orelse return null;
defer allocator.free(path);
return try std.Io.Dir.cwd().openDir(io, path, args);
}
Expand All @@ -132,9 +133,10 @@ pub fn open(
pub fn getPath(
io: std.Io,
allocator: std.mem.Allocator,
environ: std.process.Environ.Map,
folder: KnownFolder,
) Error!?[]const u8 {
var system: DefaultSystem = .{};
var system: DefaultSystem = .{ .environ = environ };
defer system.deinit();
return getPathInner(DefaultSystem, &system, io, allocator, folder);
}
Expand All @@ -150,7 +152,7 @@ fn getPathInner(
if (folder == .executable_dir) {
if (builtin.os.tag == .wasi) return null;
return std.process.executableDirPathAlloc(io, allocator) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.OutOfMemory, error.Canceled => |e| return e,
else => null,
};
}
Expand Down Expand Up @@ -191,17 +193,12 @@ fn getPathInner(
}
},
.by_env => |env_path| {
const env_var = std.process.getEnvVarOwned(allocator, env_path.env_var) catch |err| switch (err) {
error.EnvironmentVariableNotFound => return null,
error.InvalidWtf8 => return null,
error.OutOfMemory => |e| return e,
};
const env_var = system.getenv(env_path.env_var) orelse return null;

if (env_path.subdir) |sub_dir| {
defer allocator.free(env_var);
return try std.fs.path.join(allocator, &[_][]const u8{ env_var, sub_dir });
} else {
return env_var;
return try allocator.dupe(u8, env_var);
}
},
}
Expand All @@ -214,7 +211,7 @@ fn getPathInner(
return try allocator.dupe(u8, comptime getMacFolderSpec(.global_configuration));
}

const home_dir = try system.getenv(allocator, "HOME") orelse return null;
const home_dir = system.getenv("HOME") orelse return null;

if (folder == .home) {
return try allocator.dupe(u8, home_dir);
Expand Down Expand Up @@ -244,7 +241,7 @@ fn getPathXdg(
break :fallback;

const env: []const u8, var env_owned: bool = env_opt: {
if (try system.getenv(allocator, folder_spec.env.name)) |env_opt|
if (system.getenv(folder_spec.env.name)) |env_opt|
break :env_opt .{ env_opt, false };

if (system.config.xdg_force_default)
Expand All @@ -254,7 +251,7 @@ fn getPathXdg(
break :fallback;

const env = xdgUserDirLookup(System, system, io, allocator, folder_spec.env.name) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.OutOfMemory, error.Canceled => |e| return e,
else => break :fallback,
} orelse break :fallback;

Expand Down Expand Up @@ -283,7 +280,7 @@ fn getPathXdg(

const default = folder_spec.default orelse return null;
if (default[0] == '~') {
const home = try system.getenv(allocator, "HOME") orelse return null;
const home = system.getenv("HOME") orelse return null;
return try std.fs.path.join(allocator, &.{ home, default[1..] });
} else {
return try allocator.dupe(u8, default);
Expand All @@ -293,23 +290,15 @@ fn getPathXdg(
/// Encapsulates all operating system interactions
const DefaultSystem = struct {
comptime config: KnownFolderConfig = if (@hasDecl(root, "known_folders_config")) root.known_folders_config else .{},
envmap: if (builtin.os.tag == .wasi and !builtin.link_libc) ?std.process.EnvMap else ?void = null,
environ: std.process.Environ.Map,

pub fn deinit(system: *DefaultSystem) void {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
if (system.envmap) |*envmap| envmap.deinit();
}
_ = system;
}

/// Caller does **not** owns the returned memory.
pub fn getenv(system: *DefaultSystem, allocator: std.mem.Allocator, key: []const u8) std.mem.Allocator.Error!?[]const u8 {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
if (system.envmap == null) {
system.envmap = std.process.getEnvMap(allocator) catch return error.OutOfMemory;
}
return system.envmap.?.get(key);
}
return std.posix.getenv(key);
pub fn getenv(system: *DefaultSystem, key: []const u8) ?[]const u8 {
return system.environ.get(key);
}

pub fn openFile(_: *DefaultSystem, io: std.Io, dir_path: []const u8, sub_path: []const u8) std.Io.File.OpenError!std.Io.File {
Expand Down Expand Up @@ -345,12 +334,7 @@ const TestingSystem = struct {
/// Asserts that the environment variable is specified in `TestingSystem.env_map`.
///
/// Caller does **not** owns the returned memory.
pub fn getenv(system: *TestingSystem, allocator: std.mem.Allocator, key: []const u8) std.mem.Allocator.Error!?[]const u8 {
{
// This allocation will simulate the possibility of allocation failure using `std.testing.checkAllAllocationFailures`
allocator.free(try allocator.alloc(u8, 1));
}

pub fn getenv(system: *TestingSystem, key: []const u8) ?[]const u8 {
for (system.env_map) |kv| {
if (std.mem.eql(u8, key, kv.key)) return kv.value;
}
Expand Down Expand Up @@ -398,7 +382,7 @@ const TestingSystem = struct {
const UserDirLookupError =
std.mem.Allocator.Error ||
std.Io.File.OpenError ||
std.posix.ReadError;
std.Io.File.Reader.Error;

const xdg_user_dir_lookup_line_buffer_size: usize = 511;

Expand Down Expand Up @@ -442,8 +426,8 @@ fn xdgUserDirLookup(
std.mem.eql(u8, folder_name, "VIDEOS"));
}

const home_dir: []const u8 = try system.getenv(allocator, "HOME") orelse return null;
const maybe_config_home: ?[]const u8 = if (try system.getenv(allocator, "XDG_CONFIG_HOME")) |value|
const home_dir: []const u8 = system.getenv("HOME") orelse return null;
const maybe_config_home: ?[]const u8 = if (system.getenv("XDG_CONFIG_HOME")) |value|
if (value.len != 0) value else null
else
null;
Expand Down Expand Up @@ -553,7 +537,7 @@ const LineIterator = struct {
};
}

fn next(it: *LineIterator, buffer: []u8) std.posix.ReadError!?[:'\n']const u8 {
fn next(it: *LineIterator, buffer: []u8) std.Io.File.Reader.Error!?[:'\n']const u8 {
if (!it.keep_reading) return null;

const reader = &it.file_reader.interface;
Expand Down Expand Up @@ -1220,8 +1204,11 @@ test "getPath - .global_configuration with xdg_on_mac=false" {
}

test "query each known folders" {
var environ = try std.testing.io_instance.environ.process_environ.createMap(std.testing.allocator);
defer environ.deinit();

for (std.meta.tags(KnownFolder)) |folder| {
const path_or_null = try getPath(std.testing.io, std.testing.allocator, folder);
const path_or_null = try getPath(std.testing.io, std.testing.allocator, environ, folder);
defer if (path_or_null) |path| std.testing.allocator.free(path);
std.debug.print("{s} => {?s}\n", .{ @tagName(folder), path_or_null });
}
Expand All @@ -1230,8 +1217,11 @@ test "query each known folders" {
test "open each known folders" {
if (builtin.os.tag == .wasi) return error.SkipZigTest;

var environ = try std.testing.io_instance.environ.process_environ.createMap(std.testing.allocator);
defer environ.deinit();

for (std.meta.tags(KnownFolder)) |folder| {
var dir_or_null = open(std.testing.io, std.testing.allocator, folder, .{}) catch |e| switch (e) {
var dir_or_null = open(std.testing.io, std.testing.allocator, environ, folder, .{}) catch |e| switch (e) {
error.FileNotFound => return,
else => return e,
};
Expand Down