Files
bun.sh/src/paths/Path.zig
pfg 05d0475c6c Update to zig 0.15.2 (#24204)
Fixes ENG-21287

Build times, from `bun run build && echo '//' >> src/main.zig && time
bun run build`

|Platform|0.14.1|0.15.2|Speedup|
|-|-|-|-|
|macos debug asan|126.90s|106.27s|1.19x|
|macos debug noasan|60.62s|50.85s|1.19x|
|linux debug asan|292.77s|241.45s|1.21x|
|linux debug noasan|146.58s|130.94s|1.12x|
|linux debug use_llvm=false|n/a|78.27s|1.87x|
|windows debug asan|177.13s|142.55s|1.24x|

Runtime performance:

- next build memory usage may have gone up by 5%. Otherwise seems the
same. Some code with writers may have gotten slower, especially one
instance of a counting writer and a few instances of unbuffered writers
that now have vtable overhead.
- File size reduced by 800kb (from 100.2mb to 99.4mb)

Improvements:

- `@export` hack is no longer needed for watch
- native x86_64 backend for linux builds faster. to use it, set use_llvm
false and no_link_obj false. also set `ASAN_OPTIONS=detect_leaks=0`
otherwise it will spam the output with tens of thousands of lines of
debug info errors. may need to use the zig lldb fork for debugging.
- zig test-obj, which we will be able to use for zig unit tests

Still an issue:

- false 'dependency loop' errors remain in watch mode
- watch mode crashes observed

Follow-up:

- [ ] search `comptime Writer: type` and `comptime W: type` and remove
- [ ] remove format_mode in our zig fork
- [ ] remove deprecated.zig autoFormatLabelFallback
- [ ] remove deprecated.zig autoFormatLabel
- [ ] remove deprecated.BufferedWriter and BufferedReader
- [ ] remove override_no_export_cpp_apis as it is no longer needed
- [ ] css Parser(W) -> Parser, and remove all the comptime writer: type
params
- [ ] remove deprecated writer fully

Files that add lines:

```
649     src/deprecated.zig
167     scripts/pack-codegen-for-zig-team.ts
54      scripts/cleartrace-impl.js
46      scripts/cleartrace.ts
43      src/windows.zig
18      src/fs.zig
17      src/bun.js/ConsoleObject.zig
16      src/output.zig
12      src/bun.js/test/debug.zig
12      src/bun.js/node/node_fs.zig
8       src/env_loader.zig
7       src/css/printer.zig
7       src/cli/init_command.zig
7       src/bun.js/node.zig
6       src/string/escapeRegExp.zig
6       src/install/PnpmMatcher.zig
5       src/bun.js/webcore/Blob.zig
4       src/crash_handler.zig
4       src/bun.zig
3       src/install/lockfile/bun.lock.zig
3       src/cli/update_interactive_command.zig
3       src/cli/pack_command.zig
3       build.zig
2       src/Progress.zig
2       src/install/lockfile/lockfile_json_stringify_for_debugging.zig
2       src/css/small_list.zig
2       src/bun.js/webcore/prompt.zig
1       test/internal/ban-words.test.ts
1       test/internal/ban-limits.json
1       src/watcher/WatcherTrace.zig
1       src/transpiler.zig
1       src/shell/builtin/cp.zig
1       src/js_printer.zig
1       src/io/PipeReader.zig
1       src/install/bin.zig
1       src/css/selectors/selector.zig
1       src/cli/run_command.zig
1       src/bun.js/RuntimeTranspilerStore.zig
1       src/bun.js/bindings/JSRef.zig
1       src/bake/DevServer.zig
```

Files that remove lines:

```
-1      src/test/recover.zig
-1      src/sql/postgres/SocketMonitor.zig
-1      src/sql/mysql/MySQLRequestQueue.zig
-1      src/sourcemap/CodeCoverage.zig
-1      src/css/values/color_js.zig
-1      src/compile_target.zig
-1      src/bundler/linker_context/convertStmtsForChunk.zig
-1      src/bundler/bundle_v2.zig
-1      src/bun.js/webcore/blob/read_file.zig
-1      src/ast/base.zig
-2      src/sql/postgres/protocol/ArrayList.zig
-2      src/shell/builtin/mkdir.zig
-2      src/install/PackageManager/patchPackage.zig
-2      src/install/PackageManager/PackageManagerDirectories.zig
-2      src/fmt.zig
-2      src/css/declaration.zig
-2      src/css/css_parser.zig
-2      src/collections/baby_list.zig
-2      src/bun.js/bindings/ZigStackFrame.zig
-2      src/ast/E.zig
-3      src/StandaloneModuleGraph.zig
-3      src/deps/picohttp.zig
-3      src/deps/libuv.zig
-3      src/btjs.zig
-4      src/threading/Futex.zig
-4      src/shell/builtin/touch.zig
-4      src/meta.zig
-4      src/install/lockfile.zig
-4      src/css/selectors/parser.zig
-5      src/shell/interpreter.zig
-5      src/css/error.zig
-5      src/bun.js/web_worker.zig
-5      src/bun.js.zig
-6      src/cli/test_command.zig
-6      src/bun.js/VirtualMachine.zig
-6      src/bun.js/uuid.zig
-6      src/bun.js/bindings/JSValue.zig
-9      src/bun.js/test/pretty_format.zig
-9      src/bun.js/api/BunObject.zig
-14     src/install/install_binding.zig
-14     src/fd.zig
-14     src/bun.js/node/path.zig
-14     scripts/pack-codegen-for-zig-team.sh
-17     src/bun.js/test/diff_format.zig
```

`git diff --numstat origin/main...HEAD | awk '{ print ($1-$2)"\t"$3 }' |
sort -rn`

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: Meghan Denny <meghan@bun.com>
Co-authored-by: tayor.fish <contact@taylor.fish>
2025-11-10 14:38:26 -08:00

974 lines
36 KiB
Zig

const Options = struct {
check_length: CheckLength = .assume_always_less_than_max_path,
sep: PathSeparators = .any,
kind: Kind = .any,
buf_type: BufType = .pool,
unit: Unit = .u8,
const Unit = enum {
u8,
u16,
os,
};
const BufType = enum {
pool,
// stack,
// array_list,
};
const Kind = enum {
abs,
rel,
// not recommended, but useful when you don't know
any,
};
const CheckLength = enum {
assume_always_less_than_max_path,
check_for_greater_than_max_path,
};
const PathSeparators = enum {
any,
auto,
posix,
windows,
pub fn char(comptime sep: @This()) u8 {
return switch (sep) {
.any => @compileError("use the existing slash"),
.auto => std.fs.path.sep,
.posix => std.fs.path.sep_posix,
.windows => std.fs.path.sep_windows,
};
}
};
pub fn pathUnit(comptime opts: @This()) type {
return switch (opts.unit) {
.u8 => u8,
.u16 => u16,
.os => if (Environment.isWindows) u16 else u8,
};
}
pub fn notPathUnit(comptime opts: @This()) type {
return switch (opts.unit) {
.u8 => u16,
.u16 => u8,
.os => if (Environment.isWindows) u8 else u16,
};
}
pub fn maxPathLength(comptime opts: @This()) usize {
switch (comptime opts.check_length) {
.assume_always_less_than_max_path => @compileError("max path length is not needed"),
.check_for_greater_than_max_path => {
return switch (comptime opts.unit) {
.u8 => bun.MAX_PATH_BYTES,
.u16 => bun.PATH_MAX_WIDE,
.os => if (Environment.isWindows) bun.PATH_MAX_WIDE else bun.MAX_PATH_BYTES,
};
},
}
}
pub fn Buf(comptime opts: @This()) type {
return switch (opts.buf_type) {
.pool => struct {
pooled: switch (opts.unit) {
.u8 => *PathBuffer,
.u16 => *WPathBuffer,
.os => if (Environment.isWindows) *WPathBuffer else *PathBuffer,
},
len: usize,
pub fn setLength(this: *@This(), new_len: usize) void {
this.len = new_len;
}
pub fn append(this: *@This(), characters: anytype, add_separator: bool) void {
if (add_separator) {
switch (comptime opts.sep) {
.any, .auto => this.pooled[this.len] = std.fs.path.sep,
.posix => this.pooled[this.len] = std.fs.path.sep_posix,
.windows => this.pooled[this.len] = std.fs.path.sep_windows,
}
this.len += 1;
}
if (opts.inputChildType(@TypeOf(characters)) == opts.pathUnit()) {
switch (comptime opts.sep) {
.any => {
@memcpy(this.pooled[this.len..][0..characters.len], characters);
this.len += characters.len;
},
.auto, .posix, .windows => {
for (characters) |c| {
switch (c) {
'/', '\\' => this.pooled[this.len] = opts.sep.char(),
else => this.pooled[this.len] = c,
}
this.len += 1;
}
},
}
} else {
switch (opts.inputChildType(@TypeOf(characters))) {
u8 => {
const converted = bun.strings.convertUTF8toUTF16InBuffer(this.pooled[this.len..], characters);
if (comptime opts.sep != .any) {
for (this.pooled[this.len..][0..converted.len], 0..) |c, off| {
switch (c) {
'/', '\\' => this.pooled[this.len + off] = opts.sep.char(),
else => {},
}
}
}
this.len += converted.len;
},
u16 => {
const converted = bun.strings.convertUTF16toUTF8InBuffer(this.pooled[this.len..], characters) catch unreachable;
if (comptime opts.sep != .any) {
for (this.pooled[this.len..][0..converted.len], 0..) |c, off| {
switch (c) {
'/', '\\' => this.pooled[this.len + off] = opts.sep.char(),
else => {},
}
}
}
this.len += converted.len;
},
else => @compileError("unexpected character type"),
}
}
// switch (@TypeOf(characters)) {
// []u8, []const u8, [:0]u8, [:0]const u8 => {
// if (opts.unit == .u8) {
// this.appendT()
// }
// }
// }
}
// fn append(this: *@This(), characters: []const opts.pathUnit(), add_separator: bool) void {
// if (add_separator) {}
// switch (comptime opts.sep) {
// .any => {
// @memcpy(this.pooled[this.len..][0..characters.len], characters);
// this.len += characters.len;
// },
// .auto, .posix, .windows => {
// for (characters) |c| {
// switch (c) {
// '/', '\\' => this.pooled[this.len] = opts.sep.char(),
// else => this.pooled[this.len] = c,
// }
// this.len += 1;
// }
// },
// }
// }
fn convertAppend(this: *@This(), characters: []const opts.notPathUnit()) void {
_ = this;
_ = characters;
// switch (comptime opts.sep) {
// .any => {
// switch (opts.notPathUnit()) {
// .u8 => {
// const converted = bun.strings.convertUTF8toUTF16InBuffer(this.pooled[this.len..], characters);
// },
// }
// },
// }
}
},
// .stack => struct {
// buf: PathBuffer,
// len: u16,
// },
// .array_list => struct {
// list: std.array_list.Managed(opts.pathUnit()),
// },
};
}
const Error = error{MaxPathExceeded};
pub fn ResultFn(comptime opts: @This()) fn (comptime T: type) type {
return struct {
pub fn Result(comptime T: type) type {
return switch (opts.check_length) {
.assume_always_less_than_max_path => T,
.check_for_greater_than_max_path => Error!T,
};
}
}.Result;
}
pub fn inputChildType(comptime opts: @This(), comptime InputType: type) type {
_ = opts;
return switch (@typeInfo(std.meta.Child(InputType))) {
// handle string literals
.array => |array| array.child,
else => std.meta.Child(InputType),
};
}
};
pub fn AbsPath(comptime opts: Options) type {
var copy = opts;
copy.kind = .abs;
return Path(copy);
}
pub const AutoAbsPath = Path(.{ .kind = .abs, .sep = .auto });
pub fn RelPath(comptime opts: Options) type {
var copy = opts;
copy.kind = .rel;
return Path(copy);
}
pub const AutoRelPath = Path(.{ .kind = .rel, .sep = .auto });
pub fn Path(comptime opts: Options) type {
const Result = opts.ResultFn();
// if (opts.unit == .u16 and !Environment.isWindows) {
// @compileError("utf16 not supported");
// }
// const log = Output.scoped(.Path, .visible);
return struct {
_buf: opts.Buf(),
pub fn init() @This() {
switch (comptime opts.buf_type) {
.pool => {
return .{
._buf = .{
.pooled = switch (opts.unit) {
.u8 => bun.path_buffer_pool.get(),
.u16 => bun.w_path_buffer_pool.get(),
.os => if (comptime Environment.isWindows)
bun.w_path_buffer_pool.get()
else
bun.path_buffer_pool.get(),
},
.len = 0,
},
};
},
}
}
pub fn deinit(this: *const @This()) void {
switch (comptime opts.buf_type) {
.pool => {
switch (opts.unit) {
.u8 => bun.path_buffer_pool.put(this._buf.pooled),
.u16 => bun.w_path_buffer_pool.put(this._buf.pooled),
.os => if (comptime Environment.isWindows)
bun.w_path_buffer_pool.put(this._buf.pooled)
else
bun.path_buffer_pool.put(this._buf.pooled),
}
},
}
@constCast(this).* = undefined;
}
pub fn move(this: *const @This()) @This() {
const moved = this.*;
@constCast(this).* = undefined;
return moved;
}
pub fn initTopLevelDir() @This() {
bun.debugAssert(bun.fs.FileSystem.instance_loaded);
const top_level_dir = bun.fs.FileSystem.instance.top_level_dir;
const trimmed = switch (comptime opts.kind) {
.abs => trimmed: {
bun.debugAssert(isInputAbsolute(top_level_dir));
break :trimmed trimInput(.abs, top_level_dir);
},
.rel => @compileError("cannot create a relative path from top_level_dir"),
.any => trimInput(.abs, top_level_dir),
};
var this = init();
this._buf.append(trimmed, false);
return this;
}
pub fn initTopLevelDirLongPath() @This() {
bun.debugAssert(bun.fs.FileSystem.instance_loaded);
const top_level_dir = bun.fs.FileSystem.instance.top_level_dir;
const trimmed = switch (comptime opts.kind) {
.abs => trimmed: {
bun.debugAssert(isInputAbsolute(top_level_dir));
break :trimmed trimInput(.abs, top_level_dir);
},
.rel => @compileError("cannot create a relative path from top_level_dir"),
.any => trimInput(.abs, top_level_dir),
};
var this = init();
if (comptime Environment.isWindows) {
switch (comptime opts.unit) {
.u8 => this._buf.append(bun.windows.long_path_prefix_u8, false),
.u16 => this._buf.append(bun.windows.long_path_prefix, false),
.os => if (Environment.isWindows)
this._buf.append(bun.windows.long_path_prefix, false)
else
this._buf.append(bun.windows.long_path_prefix_u8, false),
}
}
this._buf.append(trimmed, false);
return this;
}
pub fn initFdPath(fd: FD) !@This() {
switch (comptime opts.kind) {
.abs => {},
.rel => @compileError("cannot create a relative path from getFdPath"),
.any => {},
}
var this = init();
switch (comptime opts.buf_type) {
.pool => {
const raw = try fd.getFdPath(this._buf.pooled);
const trimmed = trimInput(.abs, raw);
this._buf.len = trimmed.len;
},
}
return this;
}
pub fn fromLongPath(input: anytype) Result(@This()) {
switch (comptime @TypeOf(input)) {
[]u8, []const u8, [:0]u8, [:0]const u8 => {},
[]u16, []const u16, [:0]u16, [:0]const u16 => {},
else => @compileError("unsupported type: " ++ @typeName(@TypeOf(input))),
}
const trimmed = switch (comptime opts.kind) {
.abs => trimmed: {
bun.debugAssert(isInputAbsolute(input));
break :trimmed trimInput(.abs, input);
},
.rel => trimmed: {
bun.debugAssert(!isInputAbsolute(input));
break :trimmed trimInput(.rel, input);
},
.any => trimInput(if (isInputAbsolute(input)) .abs else .rel, input),
};
if (comptime opts.check_length == .check_for_greater_than_max_path) {
if (trimmed.len >= opts.maxPathLength()) {
return error.MaxPathExceeded;
}
}
var this = init();
if (comptime Environment.isWindows) {
switch (comptime opts.unit) {
.u8 => this._buf.append(bun.windows.long_path_prefix_u8, false),
.u16 => this._buf.append(bun.windows.long_path_prefix, false),
.os => if (Environment.isWindows)
this._buf.append(bun.windows.long_path_prefix, false)
else
this._buf.append(bun.windows.long_path_prefix_u8, false),
}
}
this._buf.append(trimmed, false);
return this;
}
pub fn from(input: anytype) Result(@This()) {
const trimmed = switch (comptime opts.kind) {
.abs => trimmed: {
bun.debugAssert(isInputAbsolute(input));
break :trimmed trimInput(.abs, input);
},
.rel => trimmed: {
bun.debugAssert(!isInputAbsolute(input));
break :trimmed trimInput(.rel, input);
},
.any => trimInput(if (isInputAbsolute(input)) .abs else .rel, input),
};
if (comptime opts.check_length == .check_for_greater_than_max_path) {
if (trimmed.len >= opts.maxPathLength()) {
return error.MaxPathExceeded;
}
}
var this = init();
this._buf.append(trimmed, false);
return this;
}
pub fn isAbsolute(this: *const @This()) bool {
return switch (comptime opts.kind) {
.abs => @compileError("already known to be absolute"),
.rel => @compileError("already known to not be absolute"),
.any => isInputAbsolute(this.slice()),
};
}
pub fn basename(this: *const @This()) []const opts.pathUnit() {
return bun.strings.basename(opts.pathUnit(), this.slice());
}
pub fn basenameZ(this: *const @This()) [:0]const opts.pathUnit() {
const full = this.sliceZ();
const base = bun.strings.basename(opts.pathUnit(), full);
return full[full.len - base.len ..][0..base.len :0];
}
pub fn dirname(this: *const @This()) ?[]const opts.pathUnit() {
return bun.Dirname.dirname(opts.pathUnit(), this.slice());
}
pub fn slice(this: *const @This()) []const opts.pathUnit() {
switch (comptime opts.buf_type) {
.pool => return this._buf.pooled[0..this._buf.len],
}
}
pub fn sliceZ(this: *const @This()) [:0]const opts.pathUnit() {
switch (comptime opts.buf_type) {
.pool => {
this._buf.pooled[this._buf.len] = 0;
return this._buf.pooled[0..this._buf.len :0];
},
}
}
pub fn buf(this: *const @This()) []opts.pathUnit() {
switch (comptime opts.buf_type) {
.pool => {
return this._buf.pooled;
},
}
}
pub fn setLength(this: *@This(), new_length: usize) void {
this._buf.setLength(new_length);
const trimmed = switch (comptime opts.kind) {
.abs => trimInput(.abs, this.slice()),
.rel => trimInput(.rel, this.slice()),
.any => trimmed: {
if (this.isAbsolute()) {
break :trimmed trimInput(.abs, this.slice());
}
break :trimmed trimInput(.rel, this.slice());
},
};
this._buf.setLength(trimmed.len);
}
pub fn len(this: *const @This()) usize {
switch (comptime opts.buf_type) {
.pool => {
return this._buf.len;
},
}
}
pub fn clone(this: *const @This()) @This() {
switch (comptime opts.buf_type) {
.pool => {
var cloned = init();
@memcpy(cloned._buf.pooled[0..this._buf.len], this._buf.pooled[0..this._buf.len]);
cloned._buf.len = this._buf.len;
return cloned;
},
}
}
pub fn clear(this: *@This()) void {
this._buf.setLength(0);
}
pub fn rootLen(input: anytype) ?usize {
if (comptime Environment.isWindows) {
if (input.len > 2 and input[1] == ':' and switch (input[2]) {
'/', '\\' => true,
else => false,
}) {
const letter = input[0];
if (('a' <= letter and letter <= 'z') or ('A' <= letter and letter <= 'Z')) {
// C:\
return 3;
}
}
if (input.len > 5 and
switch (input[0]) {
'/', '\\' => true,
else => false,
} and
switch (input[1]) {
'/', '\\' => true,
else => false,
} and
switch (input[2]) {
'\\', '.' => false,
else => true,
})
{
var i: usize = 3;
// \\network\share\
// ^
while (i < input.len and switch (input[i]) {
'/', '\\' => false,
else => true,
}) {
i += 1;
}
i += 1;
// \\network\share\
// ^
const start = i;
while (i < input.len and switch (input[i]) {
'/', '\\' => false,
else => true,
}) {
i += 1;
}
if (start != i and i < input.len and switch (input[i]) {
'/', '\\' => true,
else => false,
}) {
// \\network\share\
// ^
if (i + 1 < input.len) {
return i + 1;
}
return i;
}
}
if (input.len > 0 and switch (input[0]) {
'/', '\\' => true,
else => false,
}) {
// \
return 1;
}
return null;
}
if (input.len > 0 and input[0] == '/') {
// /
return 1;
}
return null;
}
const TrimInputKind = enum {
abs,
rel,
};
fn trimInput(kind: TrimInputKind, input: anytype) []const opts.inputChildType(@TypeOf(input)) {
var trimmed: []const opts.inputChildType(@TypeOf(input)) = input[0..];
if (comptime Environment.isWindows) {
switch (kind) {
.abs => {
const root_len = rootLen(input) orelse 0;
while (trimmed.len > root_len and switch (trimmed[trimmed.len - 1]) {
'/', '\\' => true,
else => false,
}) {
trimmed = trimmed[0 .. trimmed.len - 1];
}
},
.rel => {
if (trimmed.len > 1 and trimmed[0] == '.') {
const c = trimmed[1];
if (c == '/' or c == '\\') {
trimmed = trimmed[2..];
}
}
while (trimmed.len > 0 and switch (trimmed[0]) {
'/', '\\' => true,
else => false,
}) {
trimmed = trimmed[1..];
}
while (trimmed.len > 0 and switch (trimmed[trimmed.len - 1]) {
'/', '\\' => true,
else => false,
}) {
trimmed = trimmed[0 .. trimmed.len - 1];
}
},
}
return trimmed;
}
switch (kind) {
.abs => {
const root_len = rootLen(input) orelse 0;
while (trimmed.len > root_len and trimmed[trimmed.len - 1] == '/') {
trimmed = trimmed[0 .. trimmed.len - 1];
}
},
.rel => {
if (trimmed.len > 1 and trimmed[0] == '.' and trimmed[1] == '/') {
trimmed = trimmed[2..];
}
while (trimmed.len > 0 and trimmed[0] == '/') {
trimmed = trimmed[1..];
}
while (trimmed.len > 0 and trimmed[trimmed.len - 1] == '/') {
trimmed = trimmed[0 .. trimmed.len - 1];
}
},
}
return trimmed;
}
fn isInputAbsolute(input: anytype) bool {
if (input.len == 0) {
return false;
}
if (input[0] == '/') {
return true;
}
if (comptime Environment.isWindows) {
if (input[0] == '\\') {
return true;
}
if (input.len < 3) {
return false;
}
if (input[1] == ':' and switch (input[2]) {
'/', '\\' => true,
else => false,
}) {
return true;
}
}
return false;
}
pub fn append(this: *@This(), input: anytype) Result(void) {
const needs_sep = this.len() > 0 and switch (comptime opts.sep) {
.any => switch (this.slice()[this.len() - 1]) {
'/', '\\' => false,
else => true,
},
else => this.slice()[this.len() - 1] != opts.sep.char(),
};
switch (comptime opts.kind) {
.abs => {
const has_root = this.len() > 0;
if (comptime Environment.isDebug) {
if (has_root) {
bun.debugAssert(!isInputAbsolute(input));
} else {
bun.debugAssert(isInputAbsolute(input));
}
}
const trimmed = trimInput(if (has_root) .rel else .abs, input);
if (trimmed.len == 0) {
return;
}
if (comptime opts.check_length == .check_for_greater_than_max_path) {
if (this.len() + trimmed.len + @intFromBool(needs_sep) >= opts.maxPathLength()) {
return error.MaxPathExceeded;
}
}
this._buf.append(trimmed, needs_sep);
},
.rel => {
bun.debugAssert(!isInputAbsolute(input));
const trimmed = trimInput(.rel, input);
if (trimmed.len == 0) {
return;
}
if (comptime opts.check_length == .check_for_greater_than_max_path) {
if (this.len() + trimmed.len + @intFromBool(needs_sep) >= opts.maxPathLength()) {
return error.MaxPathExceeded;
}
}
this._buf.append(trimmed, needs_sep);
},
.any => {
const input_is_absolute = isInputAbsolute(input);
if (comptime Environment.isDebug) {
if (needs_sep) {
bun.debugAssert(!input_is_absolute);
}
}
const trimmed = trimInput(if (this.len() > 0)
// anything appended to an existing path should be trimmed
// as a relative path
.rel
else if (isInputAbsolute(input))
// path is empty, trim based on input
.abs
else
.rel, input);
if (trimmed.len == 0) {
return;
}
if (comptime opts.check_length == .check_for_greater_than_max_path) {
if (this.len() + trimmed.len + @intFromBool(needs_sep) >= opts.maxPathLength()) {
return error.MaxPathExceeded;
}
}
this._buf.append(trimmed, needs_sep);
},
}
}
pub fn appendFmt(this: *@This(), comptime fmt: []const u8, args: anytype) Result(void) {
// TODO: there's probably a better way to do this. needed for trimming slashes
var temp: Path(.{ .buf_type = .pool }) = .init();
defer temp.deinit();
const input = switch (comptime opts.buf_type) {
.pool => std.fmt.bufPrint(temp._buf.pooled, fmt, args) catch {
if (comptime opts.check_length == .check_for_greater_than_max_path) {
return error.MaxPathExceeded;
}
unreachable;
},
};
return this.append(input);
}
pub fn join(this: *@This(), parts: []const []const opts.pathUnit()) Result(void) {
switch (comptime opts.unit) {
.u8 => {},
.u16 => @compileError("unsupported unit type"),
.os => if (Environment.isWindows) @compileError("unsupported unit type"),
}
switch (comptime opts.kind) {
.abs => {},
.rel => @compileError("cannot join with relative path"),
.any => {
bun.debugAssert(this.isAbsolute());
},
}
const cloned = this.clone();
defer cloned.deinit();
switch (comptime opts.buf_type) {
.pool => {
const joined = bun.path.joinAbsStringBuf(
cloned.slice(),
this._buf.pooled,
parts,
switch (opts.sep) {
.any, .auto => .auto,
.posix => .posix,
.windows => .windows,
},
);
const trimmed = trimInput(.abs, joined);
this._buf.len = trimmed.len;
},
}
}
pub fn appendJoin(this: *@This(), part: anytype) Result(void) {
switch (comptime opts.kind) {
.abs => {},
.rel => @compileError("cannot join with relative path"),
.any => {
bun.debugAssert(this.isAbsolute());
},
}
switch (comptime @TypeOf(part)) {
[]u8, []const u8 => {
switch (comptime opts.pathUnit()) {
u8 => {
const cwd_path_buf = bun.path_buffer_pool.get();
defer bun.path_buffer_pool.put(cwd_path_buf);
const current_slice = this.slice();
const cwd_path = cwd_path_buf[0..current_slice.len];
bun.copy(u8, cwd_path, current_slice);
const joined = bun.path.joinStringBuf(
this._buf.pooled,
&[_][]const u8{ cwd_path, part },
switch (opts.sep) {
.any, .auto => .auto,
.posix => .posix,
.windows => .windows,
},
);
const trimmed = trimInput(.abs, joined);
this._buf.len = trimmed.len;
},
u16 => {
const path_buf = bun.w_path_buffer_pool.get();
defer bun.w_path_buffer_pool.put(path_buf);
const converted = bun.strings.convertUTF8toUTF16InBuffer(path_buf, part);
return this.appendJoin(converted);
},
else => @compileError("unsupported unit type"),
}
},
[]u16, []const u16 => {
switch (comptime opts.pathUnit()) {
u16 => {
const cwd_path_buf = bun.w_path_buffer_pool.get();
defer bun.w_path_buffer_pool.put(cwd_path_buf);
const current_slice = this.slice();
const cwd_path = cwd_path_buf[0..current_slice.len];
bun.copy(u16, cwd_path, current_slice);
const joined = bun.path.joinStringBufW(
this._buf.pooled,
&[_][]const u16{ cwd_path, part },
switch (opts.sep) {
.any, .auto => .auto,
.posix => .posix,
.windows => .windows,
},
);
const trimmed = trimInput(.abs, joined);
this._buf.len = trimmed.len;
},
u8 => {
const path_buf = bun.path_buffer_pool.get();
defer bun.path_buffer_pool.put(path_buf);
const converted = bun.strings.convertUTF16toUTF8InBuffer(path_buf, part) catch {
return .initError(.MaxPathExceeded);
};
return this.appendJoin(converted);
},
else => @compileError("unsupported unit type"),
}
},
else => @compileError("unsupported type: " ++ @typeName(@TypeOf(part))),
}
}
pub fn relative(this: *const @This(), to: anytype) RelPath(opts) {
switch (comptime opts.buf_type) {
.pool => {
var output: RelPath(opts) = .init();
const rel = bun.path.relativeBufZ(output._buf.pooled, this.slice(), to.slice());
const trimmed = trimInput(.rel, rel);
output._buf.len = trimmed.len;
return output;
},
}
}
pub fn undo(this: *@This(), n_components: usize) void {
const min_len = switch (comptime opts.kind) {
.abs => rootLen(this.slice()) orelse 0,
.rel => 0,
.any => min_len: {
if (this.isAbsolute()) {
break :min_len rootLen(this.slice()) orelse 0;
}
break :min_len 0;
},
};
var i: usize = 0;
while (i < n_components) {
const slash = switch (comptime opts.sep) {
.any => std.mem.lastIndexOfAny(opts.pathUnit(), this.slice(), &.{ std.fs.path.sep_posix, std.fs.path.sep_windows }),
.auto => std.mem.lastIndexOfScalar(opts.pathUnit(), this.slice(), std.fs.path.sep),
.posix => std.mem.lastIndexOfScalar(opts.pathUnit(), this.slice(), std.fs.path.sep_posix),
.windows => std.mem.lastIndexOfScalar(opts.pathUnit(), this.slice(), std.fs.path.sep_windows),
} orelse {
this._buf.setLength(min_len);
return;
};
if (slash < min_len) {
this._buf.setLength(min_len);
return;
}
this._buf.setLength(slash);
i += 1;
}
}
const ResetScope = struct {
path: *Path(opts),
saved_len: usize,
pub fn restore(this: *const ResetScope) void {
this.path._buf.setLength(this.saved_len);
}
};
pub fn save(this: *@This()) ResetScope {
return .{ .path = this, .saved_len = this.len() };
}
};
}
const std = @import("std");
const bun = @import("bun");
const Environment = bun.Environment;
const FD = bun.FD;
const Output = bun.Output;
const PathBuffer = bun.PathBuffer;
const WPathBuffer = bun.WPathBuffer;