Compare commits

...

13 commits

Author SHA1 Message Date
Eric Joldasov
0918096301
fix std.Target.isDarwin function replaced with direct version
See https://github.com/ziglang/zig/pull/22589 .

Signed-off-by: Eric Joldasov <bratishkaerik@landless-city.net>
2025-03-03 22:46:51 +05:00
Eric Joldasov
ee1d80da6a
std.fmt.digits now accept u8 instead of usize
See https://github.com/ziglang/zig/pull/22864 .

Signed-off-by: Eric Joldasov <bratishkaerik@landless-city.net>
2025-03-03 22:46:51 +05:00
Eric Joldasov
93a81a3898
ArrayList.pop now returns optional like removed popOrNull
See https://github.com/ziglang/zig/pull/22720 .

Signed-off-by: Eric Joldasov <bratishkaerik@landless-city.net>
2025-03-03 22:46:51 +05:00
Eric Joldasov
cf3a8f3043
update to new allocator interface
See https://github.com/ziglang/zig/pull/20511 .

Signed-off-by: Eric Joldasov <bratishkaerik@landless-city.net>
2025-03-03 22:46:51 +05:00
Eric Joldasov
f7fe61194b
update to new panic interface
See https://github.com/ziglang/zig/pull/22594 .

Signed-off-by: Eric Joldasov <bratishkaerik@landless-city.net>
2025-03-03 22:46:50 +05:00
Eric Joldasov
456cde16df
remove anonymous struct types
See https://github.com/ziglang/zig/pull/21817 .

Signed-off-by: Eric Joldasov <bratishkaerik@landless-city.net>
2025-03-03 22:46:50 +05:00
Eric Joldasov
3c77dc458a
replace deprecated std.Atomic(T).fence with load
See https://github.com/ziglang/zig/pull/21585 .
I went with second option listed in `Conditional Barriers` section.

Signed-off-by: Eric Joldasov <bratishkaerik@landless-city.net>
2025-03-03 22:46:50 +05:00
Eric Joldasov
ce9921846c
update to new names in std.builtin.Type
See https://github.com/ziglang/zig/pull/21225 .

Signed-off-by: Eric Joldasov <bratishkaerik@landless-city.net>
2025-03-03 22:46:49 +05:00
Eric Joldasov
e0ab5d40c7
change deprecated @setCold(true) to @branchHint(.cold)
New `@branchHint` builtin is more expressive than `@setCold`, therefore
latter was removed.
See https://github.com/ziglang/zig/pull/21214 .

Signed-off-by: Eric Joldasov <bratishkaerik@landless-city.net>
2025-03-03 22:46:49 +05:00
Eric Joldasov
607b07a30e
change deprecated timespec.tv_sec to timespec.sec
Part of the reorganization of `std.c` namespace.
See https://github.com/ziglang/zig/pull/20679 .

Signed-off-by: Eric Joldasov <bratishkaerik@landless-city.net>
2025-03-03 22:46:49 +05:00
Eric Joldasov
b4dc9f1d4d
change deprecated std.fs.MAX_PATH_BYTES to max_path_bytes
It was deprecated before, and became hard compile error.
See https://github.com/ziglang/zig/pull/19847 .

Signed-off-by: Eric Joldasov <bratishkaerik@landless-city.net>
2025-03-03 22:46:45 +05:00
Yorhel
2e5c767d4c List all options in --help
Fixes #251. Not as bloated as I had expected, this seems workable.
2025-03-03 13:31:51 +01:00
Yorhel
5d5182ede3 Add support for @-prefix to ignore errors in config file
Forward-ported from the C version: ff830ac2bf
2025-03-01 13:33:29 +01:00
12 changed files with 142 additions and 92 deletions

View file

@ -22,8 +22,8 @@ pub fn build(b: *std.Build) void {
exe.pie = pie;
exe.root_module.linkSystemLibrary("ncursesw", .{});
exe.root_module.linkSystemLibrary("libzstd", .{});
// https://github.com/ziglang/zig/blob/b52be973dfb7d1408218b8e75800a2da3dc69108/build.zig#L551-L554
if (target.result.isDarwin()) {
// https://github.com/ziglang/zig/blob/faccd79ca5debbe22fe168193b8de54393257604/build.zig#L745-L748
if (target.result.os.tag.isDarwin()) {
// useful for package maintainers
exe.headerpad_max_install_names = true;
}

4
ncdu.1
View file

@ -392,6 +392,7 @@ is given on the command line.
.Pp
The configuration file format is simply one command line option per line.
Lines starting with '#' are ignored.
A line can be prefixed with '@' to suppress errors while parsing the option.
Example configuration file:
.Bd -literal -offset indent
# Always enable extended mode
@ -402,6 +403,9 @@ Example configuration file:
# Exclude .git directories
\-\-exclude .git
# Read excludes from ~/.ncduexcludes, ignore error if the file does not exist
@--exclude-from ~/.ncduexcludes
.Ed
.
.Sh KEYS

View file

@ -51,7 +51,7 @@ pub const ItemKey = enum(u5) {
// Pessimistic upper bound on the encoded size of an item, excluding the name field.
// 2 bytes for map start/end, 11 per field (2 for the key, 9 for a full u64).
const MAX_ITEM_LEN = 2 + 11 * @typeInfo(ItemKey).Enum.fields.len;
const MAX_ITEM_LEN = 2 + 11 * @typeInfo(ItemKey).@"enum".fields.len;
pub const CborMajor = enum(u3) { pos, neg, bytes, text, array, map, tag, simple };
@ -118,7 +118,7 @@ pub const Thread = struct {
}
fn flush(t: *Thread, expected_len: usize) void {
@setCold(true);
@branchHint(.cold);
const block = createBlock(t);
defer block.deinit();

View file

@ -63,7 +63,7 @@ inline fn bigu32(v: [4]u8) u32 { return std.mem.bigToNative(u32, @bitCast(v)); }
inline fn bigu64(v: [8]u8) u64 { return std.mem.bigToNative(u64, @bitCast(v)); }
fn die() noreturn {
@setCold(true);
@branchHint(.cold);
if (global.lastitem) |e| ui.die("Error reading item {x} from file\n", .{e})
else ui.die("Error reading from file\n", .{});
}
@ -338,7 +338,7 @@ const ItemParser = struct {
// Skips over any fields that don't fit into an ItemKey.
fn next(r: *ItemParser) ?Field {
while (r.key()) |k| {
if (k.major == .pos and k.arg <= std.math.maxInt(@typeInfo(ItemKey).Enum.tag_type)) return .{
if (k.major == .pos and k.arg <= std.math.maxInt(@typeInfo(ItemKey).@"enum".tag_type)) return .{
.key = @enumFromInt(k.arg),
.val = r.r.next(),
} else {

View file

@ -73,7 +73,7 @@ pub const Writer = struct {
dir_entry_open: bool = false,
fn flush(ctx: *Writer, bytes: usize) void {
@setCold(true);
@branchHint(.cold);
// This can only really happen when the root path exceeds PATH_MAX,
// in which case we would probably have error'ed out earlier anyway.
if (bytes > ctx.buf.len) ui.die("Error writing JSON export: path too long.\n", .{});
@ -126,14 +126,14 @@ pub const Writer = struct {
var index: usize = buf.len;
while (a >= 100) : (a = @divTrunc(a, 100)) {
index -= 2;
buf[index..][0..2].* = std.fmt.digits2(@as(usize, @intCast(a % 100)));
buf[index..][0..2].* = std.fmt.digits2(@as(u8, @intCast(a % 100)));
}
if (a < 10) {
index -= 1;
buf[index] = '0' + @as(u8, @intCast(a));
} else {
index -= 2;
buf[index..][0..2].* = std.fmt.digits2(@as(usize, @intCast(a)));
buf[index..][0..2].* = std.fmt.digits2(@as(u8, @intCast(a)));
}
ctx.write(buf[index..]);
}

View file

@ -83,7 +83,7 @@ const Parser = struct {
}
fn fill(p: *Parser) void {
@setCold(true);
@branchHint(.cold);
p.rdoff = 0;
p.rdsize = (if (p.zstd) |z| z.read(p.rd, &p.buf) else p.rd.read(&p.buf)) catch |e| switch (e) {
error.IsDir => p.die("not a file"), // should be detected at open() time, but no flag for that...
@ -133,7 +133,7 @@ const Parser = struct {
}
fn stringContentSlow(p: *Parser, buf: []u8, head: u8, off: usize) []u8 {
@setCold(true);
@branchHint(.cold);
var b = head;
var n = off;
while (true) {

View file

@ -41,7 +41,7 @@ test "imports" {
// This allocator never returns an error, it either succeeds or causes ncdu to quit.
// (Which means you'll find a lot of "catch unreachable" sprinkled through the code,
// they look scarier than they are)
fn wrapAlloc(_: *anyopaque, len: usize, ptr_alignment: u8, return_address: usize) ?[*]u8 {
fn wrapAlloc(_: *anyopaque, len: usize, ptr_alignment: std.mem.Alignment, return_address: usize) ?[*]u8 {
while (true) {
if (std.heap.c_allocator.vtable.alloc(undefined, len, ptr_alignment, return_address)) |r|
return r
@ -56,18 +56,20 @@ pub const allocator = std.mem.Allocator{
.alloc = wrapAlloc,
// AFAIK, all uses of resize() to grow an allocation will fall back to alloc() on failure.
.resize = std.heap.c_allocator.vtable.resize,
.remap = std.heap.c_allocator.vtable.remap,
.free = std.heap.c_allocator.vtable.free,
},
};
// Custom panic impl to reset the terminal before spewing out an error message.
pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
@setCold(true);
pub const panic = std.debug.FullPanic(struct {
pub fn panicFn(msg: []const u8, first_trace_addr: ?usize) noreturn {
@branchHint(.cold);
ui.deinit();
std.debug.panicImpl(error_return_trace, ret_addr orelse @returnAddress(), msg);
}
std.debug.defaultPanic(msg, first_trace_addr);
}
}.panicFn);
pub const config = struct {
pub const SortCol = enum { name, blocks, size, items, mtime };
@ -124,6 +126,7 @@ const Args = struct {
last_arg: ?[:0]const u8 = null, // In the case of --option=<arg>
shortbuf: [2]u8 = undefined,
argsep: bool = false,
ignerror: bool = false,
const Self = @This();
const Option = struct {
@ -153,22 +156,27 @@ const Args = struct {
return .{ .opt = true, .val = &self.shortbuf };
}
pub fn die(self: *const Self, comptime msg: []const u8, args: anytype) !noreturn {
if (self.ignerror) return error.InvalidArg;
ui.die(msg, args);
}
/// Return the next option or positional argument.
/// 'opt' indicates whether it's an option or positional argument,
/// 'val' will be either -x, --something or the argument.
pub fn next(self: *Self) ?Option {
if (self.last_arg != null) ui.die("Option '{s}' does not expect an argument.\n", .{ self.last.? });
pub fn next(self: *Self) !?Option {
if (self.last_arg != null) try self.die("Option '{s}' does not expect an argument.\n", .{ self.last.? });
if (self.short) |s| return self.shortopt(s);
const val = self.pop() orelse return null;
if (self.argsep or val.len == 0 or val[0] != '-') return Option{ .opt = false, .val = val };
if (val.len == 1) ui.die("Invalid option '-'.\n", .{});
if (val.len == 1) try self.die("Invalid option '-'.\n", .{});
if (val.len == 2 and val[1] == '-') {
self.argsep = true;
return self.next();
}
if (val[1] == '-') {
if (std.mem.indexOfScalar(u8, val, '=')) |sep| {
if (sep == 2) ui.die("Invalid option '{s}'.\n", .{val});
if (sep == 2) try self.die("Invalid option '{s}'.\n", .{val});
self.last_arg = val[sep+1.. :0];
self.last = val[0..sep];
return Option{ .opt = true, .val = self.last.? };
@ -180,7 +188,7 @@ const Args = struct {
}
/// Returns the argument given to the last returned option. Dies with an error if no argument is provided.
pub fn arg(self: *Self) [:0]const u8 {
pub fn arg(self: *Self) ![:0]const u8 {
if (self.short) |a| {
defer self.short = null;
return a;
@ -190,11 +198,11 @@ const Args = struct {
return a;
}
if (self.pop()) |o| return o;
ui.die("Option '{s}' requires an argument.\n", .{ self.last.? });
try self.die("Option '{s}' requires an argument.\n", .{ self.last.? });
}
};
fn argConfig(args: *Args, opt: Args.Option, infile: bool) bool {
fn argConfig(args: *Args, opt: Args.Option, infile: bool) !void {
if (opt.is("-q") or opt.is("--slow-ui-updates")) config.update_delay = 2*std.time.ns_per_s
else if (opt.is("--fast-ui-updates")) config.update_delay = 100*std.time.ns_per_ms
else if (opt.is("-x") or opt.is("--one-file-system")) config.same_fs = true
@ -224,13 +232,13 @@ fn argConfig(args: *Args, opt: Args.Option, infile: bool) bool {
else if (opt.is("--enable-natsort")) config.sort_natural = true
else if (opt.is("--disable-natsort")) config.sort_natural = false
else if (opt.is("--graph-style")) {
const val = args.arg();
const val = try args.arg();
if (std.mem.eql(u8, val, "hash")) config.graph_style = .hash
else if (std.mem.eql(u8, val, "half-block")) config.graph_style = .half
else if (std.mem.eql(u8, val, "eighth-block") or std.mem.eql(u8, val, "eigth-block")) config.graph_style = .eighth
else ui.die("Unknown --graph-style option: {s}.\n", .{val});
else try args.die("Unknown --graph-style option: {s}.\n", .{val});
} else if (opt.is("--sort")) {
var val: []const u8 = args.arg();
var val: []const u8 = try args.arg();
var ord: ?config.SortOrder = null;
if (std.mem.endsWith(u8, val, "-asc")) {
val = val[0..val.len-4];
@ -254,13 +262,13 @@ fn argConfig(args: *Args, opt: Args.Option, infile: bool) bool {
} else if (std.mem.eql(u8, val, "mtime")) {
config.sort_col = .mtime;
config.sort_order = ord orelse .asc;
} else ui.die("Unknown --sort option: {s}.\n", .{val});
} else try args.die("Unknown --sort option: {s}.\n", .{val});
} else if (opt.is("--shared-column")) {
const val = args.arg();
const val = try args.arg();
if (std.mem.eql(u8, val, "off")) config.show_shared = .off
else if (std.mem.eql(u8, val, "shared")) config.show_shared = .shared
else if (std.mem.eql(u8, val, "unique")) config.show_shared = .unique
else ui.die("Unknown --shared-column option: {s}.\n", .{val});
else try args.die("Unknown --shared-column option: {s}.\n", .{val});
} else if (opt.is("--apparent-size")) config.show_blocks = false
else if (opt.is("--disk-usage")) config.show_blocks = true
else if (opt.is("-0")) config.scan_ui = .none
@ -271,13 +279,13 @@ fn argConfig(args: *Args, opt: Args.Option, infile: bool) bool {
else if (opt.is("-L") or opt.is("--follow-symlinks")) config.follow_symlinks = true
else if (opt.is("--no-follow-symlinks")) config.follow_symlinks = false
else if (opt.is("--exclude")) {
const arg = if (infile) (util.expanduser(args.arg(), allocator) catch unreachable) else args.arg();
const arg = if (infile) (util.expanduser(try args.arg(), allocator) catch unreachable) else try args.arg();
defer if (infile) allocator.free(arg);
exclude.addPattern(arg);
} else if (opt.is("-X") or opt.is("--exclude-from")) {
const arg = if (infile) (util.expanduser(args.arg(), allocator) catch unreachable) else args.arg();
const arg = if (infile) (util.expanduser(try args.arg(), allocator) catch unreachable) else try args.arg();
defer if (infile) allocator.free(arg);
readExcludeFile(arg) catch |e| ui.die("Error reading excludes from {s}: {s}.\n", .{ arg, ui.errorString(e) });
readExcludeFile(arg) catch |e| try args.die("Error reading excludes from {s}: {s}.\n", .{ arg, ui.errorString(e) });
} else if (opt.is("--exclude-caches")) config.exclude_caches = true
else if (opt.is("--include-caches")) config.exclude_caches = false
else if (opt.is("--exclude-kernfs")) config.exclude_kernfs = true
@ -285,29 +293,29 @@ fn argConfig(args: *Args, opt: Args.Option, infile: bool) bool {
else if (opt.is("-c") or opt.is("--compress")) config.compress = true
else if (opt.is("--no-compress")) config.compress = false
else if (opt.is("--compress-level")) {
const val = args.arg();
config.complevel = std.fmt.parseInt(u8, val, 10) catch ui.die("Invalid number for --compress-level: {s}.\n", .{val});
if (config.complevel <= 0 or config.complevel > 20) ui.die("Invalid number for --compress-level: {s}.\n", .{val});
const val = try args.arg();
const num = std.fmt.parseInt(u8, val, 10) catch try args.die("Invalid number for --compress-level: {s}.\n", .{val});
if (num <= 0 or num > 20) try args.die("Invalid number for --compress-level: {s}.\n", .{val});
config.complevel = num;
} else if (opt.is("--export-block-size")) {
const val = args.arg();
const num = std.fmt.parseInt(u14, val, 10) catch ui.die("Invalid number for --export-block-size: {s}.\n", .{val});
if (num < 4 or num > 16000) ui.die("Invalid number for --export-block-size: {s}.\n", .{val});
const val = try args.arg();
const num = std.fmt.parseInt(u14, val, 10) catch try args.die("Invalid number for --export-block-size: {s}.\n", .{val});
if (num < 4 or num > 16000) try args.die("Invalid number for --export-block-size: {s}.\n", .{val});
config.export_block_size = @as(usize, num) * 1024;
} else if (opt.is("--confirm-quit")) config.confirm_quit = true
else if (opt.is("--no-confirm-quit")) config.confirm_quit = false
else if (opt.is("--confirm-delete")) config.confirm_delete = true
else if (opt.is("--no-confirm-delete")) config.confirm_delete = false
else if (opt.is("--color")) {
const val = args.arg();
const val = try args.arg();
if (std.mem.eql(u8, val, "off")) config.ui_color = .off
else if (std.mem.eql(u8, val, "dark")) config.ui_color = .dark
else if (std.mem.eql(u8, val, "dark-bg")) config.ui_color = .darkbg
else ui.die("Unknown --color option: {s}.\n", .{val});
else try args.die("Unknown --color option: {s}.\n", .{val});
} else if (opt.is("-t") or opt.is("--threads")) {
const val = args.arg();
config.threads = std.fmt.parseInt(u8, val, 10) catch ui.die("Invalid number of --threads: {s}.\n", .{val});
} else return false;
return true;
const val = try args.arg();
config.threads = std.fmt.parseInt(u8, val, 10) catch try args.die("Invalid number of --threads: {s}.\n", .{val});
} else return error.UnknownOption;
}
fn tryReadArgsFile(path: [:0]const u8) void {
@ -318,8 +326,6 @@ fn tryReadArgsFile(path: [:0]const u8) void {
};
defer f.close();
var arglist = std.ArrayList([:0]const u8).init(allocator);
var rd_ = std.io.bufferedReader(f.reader());
const rd = rd_.reader();
@ -334,22 +340,36 @@ fn tryReadArgsFile(path: [:0]const u8) void {
};
const line_ = line_fbs.getWritten();
var argc: usize = 0;
var ignerror = false;
var arglist: [2][:0]const u8 = .{ "", "" };
var line = std.mem.trim(u8, line_, &std.ascii.whitespace);
if (line.len > 0 and line[0] == '@') {
ignerror = true;
line = line[1..];
}
if (line.len == 0 or line[0] == '#') continue;
if (std.mem.indexOfAny(u8, line, " \t=")) |i| {
arglist.append(allocator.dupeZ(u8, line[0..i]) catch unreachable) catch unreachable;
arglist[argc] = allocator.dupeZ(u8, line[0..i]) catch unreachable;
argc += 1;
line = std.mem.trimLeft(u8, line[i+1..], &std.ascii.whitespace);
}
arglist.append(allocator.dupeZ(u8, line) catch unreachable) catch unreachable;
}
arglist[argc] = allocator.dupeZ(u8, line) catch unreachable;
argc += 1;
var args = Args.init(arglist.items);
while (args.next()) |opt| {
if (!argConfig(&args, opt, true))
var args = Args.init(arglist[0..argc]);
args.ignerror = ignerror;
while (args.next() catch null) |opt| {
if (argConfig(&args, opt, true)) |_| {}
else |_| {
if (ignerror) break;
ui.die("Unrecognized option in config file '{s}': {s}.\nRun with --ignore-config to skip reading config files.\n", .{path, opt.val});
}
for (arglist.items) |i| allocator.free(i);
arglist.deinit();
}
allocator.free(arglist[0]);
if (argc == 2) allocator.free(arglist[1]);
}
}
fn version() noreturn {
@ -363,28 +383,54 @@ fn help() noreturn {
stdout.writeAll(
\\ncdu <options> <directory>
\\
\\Options:
\\ -h,--help This help message
\\ -q Quiet mode, refresh interval 2 seconds
\\ -v,-V,--version Print version
\\ -x Same filesystem
\\ -e Enable extended information
\\ -t NUM Number of threads to use
\\ -r Read only
\\ -o FILE Export scanned directory to FILE
\\Mode selection:
\\ -h, --help This help message
\\ -v, -V, --version Print version
\\ -f FILE Import scanned directory from FILE
\\ -0,-1,-2 UI to use when scanning (0=none,2=full ncurses)
\\ --si Use base 10 (SI) prefixes instead of base 2
\\ --exclude PATTERN Exclude files that match PATTERN
\\ -X, --exclude-from FILE Exclude files that match any pattern in FILE
\\ -L, --follow-symlinks Follow symbolic links (excluding directories)
\\ --exclude-caches Exclude directories containing CACHEDIR.TAG
\\ --exclude-kernfs Exclude Linux pseudo filesystems (procfs,sysfs,cgroup,...)
\\ --confirm-quit Confirm quitting ncdu
\\ --color SCHEME Set color scheme (off/dark/dark-bg)
\\ -o FILE Export scanned directory to FILE in JSON format
\\ -O FILE Export scanned directory to FILE in binary format
\\ -e, --extended Enable extended information
\\ --ignore-config Don't load config files
\\
\\Refer to `man ncdu` for the full list of options.
\\Scan options:
\\ -x, --one-file-system Stay on the same filesystem
\\ --exclude PATTERN Exclude files that match PATTERN
\\ -X, --exclude-from FILE Exclude files that match any pattern in FILE
\\ --exclude-caches Exclude directories containing CACHEDIR.TAG
\\ -L, --follow-symlinks Follow symbolic links (excluding directories)
\\ --exclude-kernfs Exclude Linux pseudo filesystems (procfs,sysfs,cgroup,...)
\\ -t NUM Scan with NUM threads
\\
\\Export options:
\\ -c, --compress Use Zstandard compression with `-o`
\\ --compress-level NUM Set compression level
\\ --export-block-size KIB Set export block size with `-O`
\\
\\Interface options:
\\ -0, -1, -2 UI to use when scanning (0=none,2=full ncurses)
\\ -q, --slow-ui-updates "Quiet" mode, refresh interval 2 seconds
\\ --enable-shell Enable/disable shell spawning feature
\\ --enable-delete Enable/disable file deletion feature
\\ --enable-refresh Enable/disable directory refresh feature
\\ -r Read only (--disable-delete)
\\ -rr Read only++ (--disable-delete & --disable-shell)
\\ --si Use base 10 (SI) prefixes instead of base 2
\\ --apparent-size Show apparent size instead of disk usage by default
\\ --hide-hidden Hide "hidden" or excluded files by default
\\ --show-itemcount Show item count column by default
\\ --show-mtime Show mtime column by default (requires `-e`)
\\ --show-graph Show graph column by default
\\ --show-percent Show percent column by default
\\ --graph-style STYLE hash / half-block / eighth-block
\\ --shared-column off / shared / unique
\\ --sort COLUMN-(asc/desc) disk-usage / name / apparent-size / itemcount / mtime
\\ --enable-natsort Use natural order when sorting by name
\\ --group-directories-first Sort directories before files
\\ --confirm-quit Ask confirmation before quitting ncdu
\\ --no-confirm-delete Don't ask confirmation before deletion
\\ --color SCHEME off / dark / dark-bg
\\
\\Refer to `man ncdu` for more information.
\\
) catch {};
std.process.exit(0);
@ -526,8 +572,8 @@ pub fn main() void {
const arglist = std.process.argsAlloc(allocator) catch unreachable;
defer std.process.argsFree(allocator, arglist);
var args = Args.init(arglist);
_ = args.next(); // program name
while (args.next()) |opt| {
_ = args.next() catch unreachable; // program name
while (args.next() catch unreachable) |opt| {
if (!opt.opt) {
// XXX: ncdu 1.x doesn't error, it just silently ignores all but the last argument.
if (scan_dir != null) ui.die("Multiple directories given, see ncdu -h for help.\n", .{});
@ -537,15 +583,15 @@ pub fn main() void {
if (opt.is("-h") or opt.is("-?") or opt.is("--help")) help()
else if (opt.is("-v") or opt.is("-V") or opt.is("--version")) version()
else if (opt.is("-o") and (export_json != null or export_bin != null)) ui.die("The -o flag can only be given once.\n", .{})
else if (opt.is("-o")) export_json = allocator.dupeZ(u8, args.arg()) catch unreachable
else if (opt.is("-o")) export_json = allocator.dupeZ(u8, args.arg() catch unreachable) catch unreachable
else if (opt.is("-O") and (export_json != null or export_bin != null)) ui.die("The -O flag can only be given once.\n", .{})
else if (opt.is("-O")) export_bin = allocator.dupeZ(u8, args.arg()) catch unreachable
else if (opt.is("-O")) export_bin = allocator.dupeZ(u8, args.arg() catch unreachable) catch unreachable
else if (opt.is("-f") and import_file != null) ui.die("The -f flag can only be given once.\n", .{})
else if (opt.is("-f")) import_file = allocator.dupeZ(u8, args.arg()) catch unreachable
else if (opt.is("-f")) import_file = allocator.dupeZ(u8, args.arg() catch unreachable) catch unreachable
else if (opt.is("--ignore-config")) {}
else if (opt.is("--quit-after-scan")) quit_after_scan = true // undocumented feature to help with benchmarking scan/import
else if (argConfig(&args, opt, false)) {}
else ui.die("Unrecognized option '{s}'.\n", .{opt.val});
else if (argConfig(&args, opt, false)) |_| {}
else |_| ui.die("Unrecognized option '{s}'.\n", .{opt.val});
}
}
@ -593,7 +639,7 @@ pub fn main() void {
if (config.binreader and (export_json != null or export_bin != null))
bin_reader.import();
} else {
var buf = [_]u8{0} ** (std.fs.MAX_PATH_BYTES+1);
var buf: [std.fs.max_path_bytes+1]u8 = @splat(0);
const path =
if (std.posix.realpathZ(scan_dir orelse ".", buf[0..buf.len-1])) |p| buf[0..p.len:0]
else |_| (scan_dir orelse ".");

View file

@ -56,7 +56,7 @@ fn rec(ctx: *Ctx, dir: *sink.Dir, entry: *model.Entry) void {
pub fn run(d: *model.Dir) void {
const sink_threads = sink.createThreads(1);
var ctx = .{
var ctx: Ctx = .{
.sink = &sink_threads[0],
.stat = toStat(&d.entry),
};

View file

@ -67,7 +67,7 @@ fn statAt(parent: std.fs.Dir, name: [:0]const u8, follow: bool, symlink: *bool)
.hasgid = true,
.hasmode = true,
},
.mtime = clamp(model.Ext, .mtime, stat.mtime().tv_sec),
.mtime = clamp(model.Ext, .mtime, stat.mtime().sec),
.uid = truncate(model.Ext, .uid, stat.uid),
.gid = truncate(model.Ext, .gid, stat.gid),
.mode = truncate(model.Ext, .mode, stat.mode),
@ -276,7 +276,7 @@ const Thread = struct {
if (entry) |e| t.scanOne(d, e.name)
else {
t.sink.setDir(null);
t.stack.pop().destroy(t);
t.stack.pop().?.destroy(t);
}
}
}

View file

@ -162,7 +162,7 @@ pub const Dir = struct {
pub fn unref(d: *Dir, t: *Thread) void {
if (d.refcnt.fetchSub(1, .release) != 1) return;
d.refcnt.fence(.acquire);
_ = d.refcnt.load(.acquire);
switch (d.out) {
.mem => |*m| m.final(if (d.parent) |p| &p.out.mem else null),

View file

@ -37,7 +37,7 @@ pub fn quit() noreturn {
// no clue if ncurses will consistently report OOM, but we're not handling that
// right now.
pub fn oom() void {
@setCold(true);
@branchHint(.cold);
if (main_thread == std.Thread.getCurrentId()) {
const haveui = inited;
deinit();
@ -288,7 +288,7 @@ pub const Style = lbl: {
};
}
break :lbl @Type(.{
.Enum = .{
.@"enum" = .{
.tag_type = u8,
.fields = &fields,
.decls = &[_]std.builtin.Type.Declaration{},

View file

@ -18,8 +18,8 @@ pub fn castClamp(comptime T: type, x: anytype) T {
// Cast any integer type to the target type, truncating if necessary.
pub fn castTruncate(comptime T: type, x: anytype) T {
const Ti = @typeInfo(T).Int;
const Xi = @typeInfo(@TypeOf(x)).Int;
const Ti = @typeInfo(T).int;
const Xi = @typeInfo(@TypeOf(x)).int;
const nx: std.meta.Int(Ti.signedness, Xi.bits) = @bitCast(x);
return if (Xi.bits > Ti.bits) @truncate(nx) else nx;
}