1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
|
const std = @import("std");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const posix = std.posix;
const isFlatpak = @import("flatpak.zig").isFlatpak;
pub const Error = Allocator.Error;
/// Get the environment map.
pub fn getEnvMap(alloc: Allocator) !std.process.EnvMap {
return if (isFlatpak())
std.process.EnvMap.init(alloc)
else
try std.process.getEnvMap(alloc);
}
/// Append a value to an environment variable such as PATH.
/// The returned value is always allocated so it must be freed.
pub fn appendEnv(
alloc: Allocator,
current: []const u8,
value: []const u8,
) Error![]u8 {
// If there is no prior value, we return it as-is
if (current.len == 0) return try alloc.dupe(u8, value);
// Otherwise we must prefix.
return try appendEnvAlways(alloc, current, value);
}
/// Always append value to environment, even when it is empty.
/// This is useful because some env vars (like MANPATH) want there
/// to be an empty prefix to preserve existing values.
///
/// The returned value is always allocated so it must be freed.
pub fn appendEnvAlways(
alloc: Allocator,
current: []const u8,
value: []const u8,
) Error![]u8 {
return try std.fmt.allocPrint(alloc, "{s}{c}{s}", .{
current,
std.fs.path.delimiter,
value,
});
}
/// Prepend a value to an environment variable such as PATH.
/// The returned value is always allocated so it must be freed.
pub fn prependEnv(
alloc: Allocator,
current: []const u8,
value: []const u8,
) Error![]u8 {
// If there is no prior value, we return it as-is
if (current.len == 0) return try alloc.dupe(u8, value);
return try std.fmt.allocPrint(alloc, "{s}{c}{s}", .{
value,
std.fs.path.delimiter,
current,
});
}
/// The result of getenv, with a shared deinit to properly handle allocation
/// on Windows.
pub const GetEnvResult = struct {
value: []const u8,
pub fn deinit(self: GetEnvResult, alloc: Allocator) void {
switch (builtin.os.tag) {
.windows => alloc.free(self.value),
else => {},
}
}
};
/// Gets the value of an environment variable, or null if not found.
/// This will allocate on Windows but not on other platforms. The returned
/// value should have deinit called to do the proper cleanup no matter what
/// platform you are on.
pub fn getenv(alloc: Allocator, key: []const u8) Error!?GetEnvResult {
return switch (builtin.os.tag) {
// Non-Windows doesn't need to allocate
else => if (posix.getenv(key)) |v| .{ .value = v } else null,
// Windows needs to allocate
.windows => if (std.process.getEnvVarOwned(alloc, key)) |v| .{
.value = v,
} else |err| switch (err) {
error.EnvironmentVariableNotFound => null,
error.InvalidWtf8 => null,
else => |e| e,
},
};
}
/// Gets the value of an environment variable. Returns null if not found or the
/// value is empty. This will allocate on Windows but not on other platforms.
/// The returned value should have deinit called to do the proper cleanup no
/// matter what platform you are on.
pub fn getenvNotEmpty(alloc: Allocator, key: []const u8) !?GetEnvResult {
const result_ = try getenv(alloc, key);
if (result_) |result| {
if (result.value.len == 0) {
result.deinit(alloc);
return null;
}
}
return result_;
}
pub fn setenv(key: [:0]const u8, value: [:0]const u8) c_int {
return switch (builtin.os.tag) {
.windows => c._putenv_s(key.ptr, value.ptr),
else => c.setenv(key.ptr, value.ptr, 1),
};
}
pub fn unsetenv(key: [:0]const u8) c_int {
return switch (builtin.os.tag) {
.windows => c._putenv_s(key.ptr, ""),
else => c.unsetenv(key.ptr),
};
}
const c = struct {
// POSIX
extern "c" fn setenv(name: ?[*]const u8, value: ?[*]const u8, overwrite: c_int) c_int;
extern "c" fn unsetenv(name: ?[*]const u8) c_int;
// Windows
extern "c" fn _putenv_s(varname: ?[*]const u8, value_string: ?[*]const u8) c_int;
};
test "appendEnv empty" {
const testing = std.testing;
const alloc = testing.allocator;
const result = try appendEnv(alloc, "", "foo");
defer alloc.free(result);
try testing.expectEqualStrings(result, "foo");
}
test "appendEnv existing" {
const testing = std.testing;
const alloc = testing.allocator;
const result = try appendEnv(alloc, "a:b", "foo");
defer alloc.free(result);
if (builtin.os.tag == .windows) {
try testing.expectEqualStrings(result, "a:b;foo");
} else {
try testing.expectEqualStrings(result, "a:b:foo");
}
}
test "prependEnv empty" {
const testing = std.testing;
const alloc = testing.allocator;
const result = try prependEnv(alloc, "", "foo");
defer alloc.free(result);
try testing.expectEqualStrings(result, "foo");
}
test "prependEnv existing" {
const testing = std.testing;
const alloc = testing.allocator;
const result = try prependEnv(alloc, "a:b", "foo");
defer alloc.free(result);
if (builtin.os.tag == .windows) {
try testing.expectEqualStrings(result, "foo;a:b");
} else {
try testing.expectEqualStrings(result, "foo:a:b");
}
}
|