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.pie = pie;
exe.root_module.linkSystemLibrary("ncursesw", .{}); exe.root_module.linkSystemLibrary("ncursesw", .{});
exe.root_module.linkSystemLibrary("libzstd", .{}); exe.root_module.linkSystemLibrary("libzstd", .{});
// https://github.com/ziglang/zig/blob/b52be973dfb7d1408218b8e75800a2da3dc69108/build.zig#L551-L554 // https://github.com/ziglang/zig/blob/faccd79ca5debbe22fe168193b8de54393257604/build.zig#L745-L748
if (target.result.isDarwin()) { if (target.result.os.tag.isDarwin()) {
// useful for package maintainers // useful for package maintainers
exe.headerpad_max_install_names = true; exe.headerpad_max_install_names = true;
} }

4
ncdu.1
View file

@ -392,6 +392,7 @@ is given on the command line.
.Pp .Pp
The configuration file format is simply one command line option per line. The configuration file format is simply one command line option per line.
Lines starting with '#' are ignored. Lines starting with '#' are ignored.
A line can be prefixed with '@' to suppress errors while parsing the option.
Example configuration file: Example configuration file:
.Bd -literal -offset indent .Bd -literal -offset indent
# Always enable extended mode # Always enable extended mode
@ -402,6 +403,9 @@ Example configuration file:
# Exclude .git directories # Exclude .git directories
\-\-exclude .git \-\-exclude .git
# Read excludes from ~/.ncduexcludes, ignore error if the file does not exist
@--exclude-from ~/.ncduexcludes
.Ed .Ed
. .
.Sh KEYS .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. // 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). // 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 }; 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 { fn flush(t: *Thread, expected_len: usize) void {
@setCold(true); @branchHint(.cold);
const block = createBlock(t); const block = createBlock(t);
defer block.deinit(); 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)); } inline fn bigu64(v: [8]u8) u64 { return std.mem.bigToNative(u64, @bitCast(v)); }
fn die() noreturn { fn die() noreturn {
@setCold(true); @branchHint(.cold);
if (global.lastitem) |e| ui.die("Error reading item {x} from file\n", .{e}) if (global.lastitem) |e| ui.die("Error reading item {x} from file\n", .{e})
else ui.die("Error reading from file\n", .{}); 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. // Skips over any fields that don't fit into an ItemKey.
fn next(r: *ItemParser) ?Field { fn next(r: *ItemParser) ?Field {
while (r.key()) |k| { 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), .key = @enumFromInt(k.arg),
.val = r.r.next(), .val = r.r.next(),
} else { } else {

View file

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

View file

@ -83,7 +83,7 @@ const Parser = struct {
} }
fn fill(p: *Parser) void { fn fill(p: *Parser) void {
@setCold(true); @branchHint(.cold);
p.rdoff = 0; 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) { 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... 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 { fn stringContentSlow(p: *Parser, buf: []u8, head: u8, off: usize) []u8 {
@setCold(true); @branchHint(.cold);
var b = head; var b = head;
var n = off; var n = off;
while (true) { while (true) {

View file

@ -41,7 +41,7 @@ test "imports" {
// This allocator never returns an error, it either succeeds or causes ncdu to quit. // 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, // (Which means you'll find a lot of "catch unreachable" sprinkled through the code,
// they look scarier than they are) // 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) { while (true) {
if (std.heap.c_allocator.vtable.alloc(undefined, len, ptr_alignment, return_address)) |r| if (std.heap.c_allocator.vtable.alloc(undefined, len, ptr_alignment, return_address)) |r|
return r return r
@ -56,18 +56,20 @@ pub const allocator = std.mem.Allocator{
.alloc = wrapAlloc, .alloc = wrapAlloc,
// AFAIK, all uses of resize() to grow an allocation will fall back to alloc() on failure. // AFAIK, all uses of resize() to grow an allocation will fall back to alloc() on failure.
.resize = std.heap.c_allocator.vtable.resize, .resize = std.heap.c_allocator.vtable.resize,
.remap = std.heap.c_allocator.vtable.remap,
.free = std.heap.c_allocator.vtable.free, .free = std.heap.c_allocator.vtable.free,
}, },
}; };
// Custom panic impl to reset the terminal before spewing out an error message. // 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 { pub const panic = std.debug.FullPanic(struct {
@setCold(true); pub fn panicFn(msg: []const u8, first_trace_addr: ?usize) noreturn {
ui.deinit(); @branchHint(.cold);
std.debug.panicImpl(error_return_trace, ret_addr orelse @returnAddress(), msg); ui.deinit();
} std.debug.defaultPanic(msg, first_trace_addr);
}
}.panicFn);
pub const config = struct { pub const config = struct {
pub const SortCol = enum { name, blocks, size, items, mtime }; 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> last_arg: ?[:0]const u8 = null, // In the case of --option=<arg>
shortbuf: [2]u8 = undefined, shortbuf: [2]u8 = undefined,
argsep: bool = false, argsep: bool = false,
ignerror: bool = false,
const Self = @This(); const Self = @This();
const Option = struct { const Option = struct {
@ -153,22 +156,27 @@ const Args = struct {
return .{ .opt = true, .val = &self.shortbuf }; 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. /// Return the next option or positional argument.
/// 'opt' indicates whether it's an option or positional argument, /// 'opt' indicates whether it's an option or positional argument,
/// 'val' will be either -x, --something or the argument. /// 'val' will be either -x, --something or the argument.
pub fn next(self: *Self) ?Option { pub fn next(self: *Self) !?Option {
if (self.last_arg != null) ui.die("Option '{s}' does not expect an argument.\n", .{ self.last.? }); 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); if (self.short) |s| return self.shortopt(s);
const val = self.pop() orelse return null; const val = self.pop() orelse return null;
if (self.argsep or val.len == 0 or val[0] != '-') return Option{ .opt = false, .val = val }; 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] == '-') { if (val.len == 2 and val[1] == '-') {
self.argsep = true; self.argsep = true;
return self.next(); return self.next();
} }
if (val[1] == '-') { if (val[1] == '-') {
if (std.mem.indexOfScalar(u8, val, '=')) |sep| { 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_arg = val[sep+1.. :0];
self.last = val[0..sep]; self.last = val[0..sep];
return Option{ .opt = true, .val = self.last.? }; 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. /// 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| { if (self.short) |a| {
defer self.short = null; defer self.short = null;
return a; return a;
@ -190,11 +198,11 @@ const Args = struct {
return a; return a;
} }
if (self.pop()) |o| return o; 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 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("--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 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("--enable-natsort")) config.sort_natural = true
else if (opt.is("--disable-natsort")) config.sort_natural = false else if (opt.is("--disable-natsort")) config.sort_natural = false
else if (opt.is("--graph-style")) { 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 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, "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 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")) { } else if (opt.is("--sort")) {
var val: []const u8 = args.arg(); var val: []const u8 = try args.arg();
var ord: ?config.SortOrder = null; var ord: ?config.SortOrder = null;
if (std.mem.endsWith(u8, val, "-asc")) { if (std.mem.endsWith(u8, val, "-asc")) {
val = val[0..val.len-4]; 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")) { } else if (std.mem.eql(u8, val, "mtime")) {
config.sort_col = .mtime; config.sort_col = .mtime;
config.sort_order = ord orelse .asc; 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")) { } 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 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, "shared")) config.show_shared = .shared
else if (std.mem.eql(u8, val, "unique")) config.show_shared = .unique 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("--apparent-size")) config.show_blocks = false
else if (opt.is("--disk-usage")) config.show_blocks = true else if (opt.is("--disk-usage")) config.show_blocks = true
else if (opt.is("-0")) config.scan_ui = .none 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("-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("--no-follow-symlinks")) config.follow_symlinks = false
else if (opt.is("--exclude")) { 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); defer if (infile) allocator.free(arg);
exclude.addPattern(arg); exclude.addPattern(arg);
} else if (opt.is("-X") or opt.is("--exclude-from")) { } 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); 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("--exclude-caches")) config.exclude_caches = true
else if (opt.is("--include-caches")) config.exclude_caches = false else if (opt.is("--include-caches")) config.exclude_caches = false
else if (opt.is("--exclude-kernfs")) config.exclude_kernfs = true 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("-c") or opt.is("--compress")) config.compress = true
else if (opt.is("--no-compress")) config.compress = false else if (opt.is("--no-compress")) config.compress = false
else if (opt.is("--compress-level")) { else if (opt.is("--compress-level")) {
const val = args.arg(); const val = try args.arg();
config.complevel = std.fmt.parseInt(u8, val, 10) catch ui.die("Invalid number for --compress-level: {s}.\n", .{val}); const num = std.fmt.parseInt(u8, val, 10) catch try args.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}); 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")) { } else if (opt.is("--export-block-size")) {
const val = args.arg(); const val = try args.arg();
const num = std.fmt.parseInt(u14, val, 10) catch ui.die("Invalid number for --export-block-size: {s}.\n", .{val}); 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) ui.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; config.export_block_size = @as(usize, num) * 1024;
} else if (opt.is("--confirm-quit")) config.confirm_quit = true } 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("--no-confirm-quit")) config.confirm_quit = false
else if (opt.is("--confirm-delete")) config.confirm_delete = true 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("--no-confirm-delete")) config.confirm_delete = false
else if (opt.is("--color")) { 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 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")) config.ui_color = .dark
else if (std.mem.eql(u8, val, "dark-bg")) config.ui_color = .darkbg 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")) { } else if (opt.is("-t") or opt.is("--threads")) {
const val = args.arg(); const val = try args.arg();
config.threads = std.fmt.parseInt(u8, val, 10) catch ui.die("Invalid number of --threads: {s}.\n", .{val}); config.threads = std.fmt.parseInt(u8, val, 10) catch try args.die("Invalid number of --threads: {s}.\n", .{val});
} else return false; } else return error.UnknownOption;
return true;
} }
fn tryReadArgsFile(path: [:0]const u8) void { fn tryReadArgsFile(path: [:0]const u8) void {
@ -318,8 +326,6 @@ fn tryReadArgsFile(path: [:0]const u8) void {
}; };
defer f.close(); defer f.close();
var arglist = std.ArrayList([:0]const u8).init(allocator);
var rd_ = std.io.bufferedReader(f.reader()); var rd_ = std.io.bufferedReader(f.reader());
const rd = rd_.reader(); const rd = rd_.reader();
@ -334,22 +340,36 @@ fn tryReadArgsFile(path: [:0]const u8) void {
}; };
const line_ = line_fbs.getWritten(); 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); 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 (line.len == 0 or line[0] == '#') continue;
if (std.mem.indexOfAny(u8, line, " \t=")) |i| { 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); 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); var args = Args.init(arglist[0..argc]);
while (args.next()) |opt| { args.ignerror = ignerror;
if (!argConfig(&args, opt, true)) while (args.next() catch null) |opt| {
ui.die("Unrecognized option in config file '{s}': {s}.\nRun with --ignore-config to skip reading config files.\n", .{path, opt.val}); 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});
}
}
allocator.free(arglist[0]);
if (argc == 2) allocator.free(arglist[1]);
} }
for (arglist.items) |i| allocator.free(i);
arglist.deinit();
} }
fn version() noreturn { fn version() noreturn {
@ -363,28 +383,54 @@ fn help() noreturn {
stdout.writeAll( stdout.writeAll(
\\ncdu <options> <directory> \\ncdu <options> <directory>
\\ \\
\\Options: \\Mode selection:
\\ -h,--help This help message \\ -h, --help This help message
\\ -q Quiet mode, refresh interval 2 seconds \\ -v, -V, --version Print version
\\ -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
\\ -f FILE Import scanned directory from FILE \\ -f FILE Import scanned directory from FILE
\\ -0,-1,-2 UI to use when scanning (0=none,2=full ncurses) \\ -o FILE Export scanned directory to FILE in JSON format
\\ --si Use base 10 (SI) prefixes instead of base 2 \\ -O FILE Export scanned directory to FILE in binary format
\\ --exclude PATTERN Exclude files that match PATTERN \\ -e, --extended Enable extended information
\\ -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)
\\ --ignore-config Don't load config files \\ --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 {}; ) catch {};
std.process.exit(0); std.process.exit(0);
@ -526,8 +572,8 @@ pub fn main() void {
const arglist = std.process.argsAlloc(allocator) catch unreachable; const arglist = std.process.argsAlloc(allocator) catch unreachable;
defer std.process.argsFree(allocator, arglist); defer std.process.argsFree(allocator, arglist);
var args = Args.init(arglist); var args = Args.init(arglist);
_ = args.next(); // program name _ = args.next() catch unreachable; // program name
while (args.next()) |opt| { while (args.next() catch unreachable) |opt| {
if (!opt.opt) { if (!opt.opt) {
// XXX: ncdu 1.x doesn't error, it just silently ignores all but the last argument. // 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", .{}); 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() 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("-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") 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") 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") 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("--ignore-config")) {}
else if (opt.is("--quit-after-scan")) quit_after_scan = true // undocumented feature to help with benchmarking scan/import 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 if (argConfig(&args, opt, false)) |_| {}
else ui.die("Unrecognized option '{s}'.\n", .{opt.val}); 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)) if (config.binreader and (export_json != null or export_bin != null))
bin_reader.import(); bin_reader.import();
} else { } else {
var buf = [_]u8{0} ** (std.fs.MAX_PATH_BYTES+1); var buf: [std.fs.max_path_bytes+1]u8 = @splat(0);
const path = const path =
if (std.posix.realpathZ(scan_dir orelse ".", buf[0..buf.len-1])) |p| buf[0..p.len:0] if (std.posix.realpathZ(scan_dir orelse ".", buf[0..buf.len-1])) |p| buf[0..p.len:0]
else |_| (scan_dir orelse "."); 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 { pub fn run(d: *model.Dir) void {
const sink_threads = sink.createThreads(1); const sink_threads = sink.createThreads(1);
var ctx = .{ var ctx: Ctx = .{
.sink = &sink_threads[0], .sink = &sink_threads[0],
.stat = toStat(&d.entry), .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, .hasgid = true,
.hasmode = 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), .uid = truncate(model.Ext, .uid, stat.uid),
.gid = truncate(model.Ext, .gid, stat.gid), .gid = truncate(model.Ext, .gid, stat.gid),
.mode = truncate(model.Ext, .mode, stat.mode), .mode = truncate(model.Ext, .mode, stat.mode),
@ -276,7 +276,7 @@ const Thread = struct {
if (entry) |e| t.scanOne(d, e.name) if (entry) |e| t.scanOne(d, e.name)
else { else {
t.sink.setDir(null); 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 { pub fn unref(d: *Dir, t: *Thread) void {
if (d.refcnt.fetchSub(1, .release) != 1) return; if (d.refcnt.fetchSub(1, .release) != 1) return;
d.refcnt.fence(.acquire); _ = d.refcnt.load(.acquire);
switch (d.out) { switch (d.out) {
.mem => |*m| m.final(if (d.parent) |p| &p.out.mem else null), .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 // no clue if ncurses will consistently report OOM, but we're not handling that
// right now. // right now.
pub fn oom() void { pub fn oom() void {
@setCold(true); @branchHint(.cold);
if (main_thread == std.Thread.getCurrentId()) { if (main_thread == std.Thread.getCurrentId()) {
const haveui = inited; const haveui = inited;
deinit(); deinit();
@ -288,7 +288,7 @@ pub const Style = lbl: {
}; };
} }
break :lbl @Type(.{ break :lbl @Type(.{
.Enum = .{ .@"enum" = .{
.tag_type = u8, .tag_type = u8,
.fields = &fields, .fields = &fields,
.decls = &[_]std.builtin.Type.Declaration{}, .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. // Cast any integer type to the target type, truncating if necessary.
pub fn castTruncate(comptime T: type, x: anytype) T { pub fn castTruncate(comptime T: type, x: anytype) T {
const Ti = @typeInfo(T).Int; const Ti = @typeInfo(T).int;
const Xi = @typeInfo(@TypeOf(x)).Int; const Xi = @typeInfo(@TypeOf(x)).int;
const nx: std.meta.Int(Ti.signedness, Xi.bits) = @bitCast(x); const nx: std.meta.Int(Ti.signedness, Xi.bits) = @bitCast(x);
return if (Xi.bits > Ti.bits) @truncate(nx) else nx; return if (Xi.bits > Ti.bits) @truncate(nx) else nx;
} }