+1
build.zig
+1
build.zig
+7
-7
build.zig.zon
+7
-7
build.zig.zon
···
3
3
.version = "1.0.0",
4
4
.fingerprint = 0x495d173f6002e86, // Changing this has security and trust implications.
5
5
6
-
.minimum_zig_version = "0.14.0",
6
+
.minimum_zig_version = "0.15.1",
7
7
8
8
.dependencies = .{
9
9
.ourio = .{
10
-
.url = "git+https://github.com/rockorager/ourio#ed8a67650e5dbb0a6dca811c9d769187e306ad94",
11
-
.hash = "ourio-0.0.0-_s-z0dAWAgD3XNod2pTh0H8X-a3CjtpAwduh7jcgBz0G",
10
+
.url = "git+https://github.com/rockorager/ourio#07bf94db87a9aea70d6e1a1dd99cac6fb9d38b35",
11
+
.hash = "ourio-0.0.0-_s-z0Z0XAgBU_BFjdY8QjGhJ8vcdIONPSErlYRwLoxfg",
12
12
},
13
13
.zeit = .{
14
-
.url = "git+https://github.com/rockorager/zeit#4496d1c40b2223c22a1341e175fc2ecd94cc0de9",
15
-
.hash = "zeit-0.6.0-5I6bk1J1AgA13rteb6E0steXiOUKBYTzJZMMIuK9oEmb",
14
+
.url = "git+https://github.com/rockorager/zeit#74be5a2afb346b2a6a6349abbb609e89ec7e65a6",
15
+
.hash = "zeit-0.6.0-5I6bk4t8AgCP0UGGHVF_khlmWZkAF5XtfQWEKCyLoptU",
16
16
},
17
17
.zzdoc = .{
18
-
.url = "git+https://github.com/rockorager/zzdoc#57e86eb4e621bc4a96fbe0dd89ad0986db6d0483",
19
-
.hash = "zzdoc-0.0.0-tzT1PuPZAACr1jIJxjTrdOsLbfXS6idWFGfTq0gwxJiv",
18
+
.url = "git+https://github.com/rockorager/zzdoc#a54223bdc13a80839ccf9f473edf3a171e777946",
19
+
.hash = "zzdoc-0.0.0-tzT1Ph7cAAC5YmXQXiBJHAg41_A5AUAC5VOm7ShnUxlz",
20
20
},
21
21
},
22
22
.paths = .{
+1
-1
flake.nix
+1
-1
flake.nix
+2
-2
nix/cache.nix
+2
-2
nix/cache.nix
···
2
2
3
3
pkgs.stdenv.mkDerivation {
4
4
pname = "lsr-cache";
5
-
version = "0.2.0";
5
+
version = "1.0.0";
6
6
doCheck = false;
7
7
src = ../.;
8
8
···
17
17
mv $ZIG_GLOBAL_CACHE_DIR/p $out
18
18
'';
19
19
20
-
outputHash = "sha256-lnOow40km0mcj21i2mTQiDGXLhcSxQ2kJoAgUhkQiEg=";
20
+
outputHash = "sha256-bfc2dlQa1VGq9S6OBeQawAJuvfxU4kgFtQ13fuKhdZc=";
21
21
outputHashMode = "recursive";
22
22
outputHashAlgo = "sha256";
23
23
}
+304
-96
src/main.zig
+304
-96
src/main.zig
···
4
4
const zeit = @import("zeit");
5
5
const natord = @import("natord.zig");
6
6
const build_options = @import("build_options");
7
+
const grp = @cImport({
8
+
@cInclude("grp.h");
9
+
});
7
10
8
11
const posix = std.posix;
9
12
10
13
const usage =
11
-
\\Usage:
12
-
\\ lsr [options] [path]
14
+
\\Usage:
15
+
\\ lsr [options] [path...]
13
16
\\
14
17
\\ --help Print this message and exit
15
18
\\ --version Print the version string
···
26
29
\\ -l, --long Display extended file metadata
27
30
\\ -r, --reverse Reverse the sort order
28
31
\\ -t, --time Sort the entries by modification time, most recent first
32
+
\\ --tree[=DEPTH] Display entries in a tree format (optional limit depth)
29
33
\\
30
34
;
31
35
···
42
46
long: bool = false,
43
47
sort_by_mod_time: bool = false,
44
48
reverse_sort: bool = false,
49
+
tree: bool = false,
50
+
tree_depth: ?usize = null,
45
51
46
-
directory: [:0]const u8 = ".",
52
+
directories: std.ArrayListUnmanaged([:0]const u8) = .empty,
47
53
file: ?[]const u8 = null,
48
54
49
55
winsize: ?posix.winsize = null,
···
146
152
147
153
var cmd: Command = .{ .arena = allocator };
148
154
149
-
cmd.opts.winsize = getWinsize(std.io.getStdOut().handle);
155
+
cmd.opts.winsize = getWinsize(std.fs.File.stdout().handle);
150
156
151
157
cmd.opts.shortview = if (cmd.opts.isatty()) .columns else .oneline;
152
158
153
-
const stdout = std.io.getStdOut().writer();
154
-
const stderr = std.io.getStdErr().writer();
155
-
var bw = std.io.bufferedWriter(stdout);
159
+
var stdout_buf: [4096]u8 = undefined;
160
+
var stderr_buf: [4096]u8 = undefined;
161
+
var stdout_writer = std.fs.File.stdout().writer(&stdout_buf);
162
+
var stderr_writer = std.fs.File.stderr().writer(&stderr_buf);
163
+
var stdout = &stdout_writer.interface;
164
+
var stderr = &stderr_writer.interface;
156
165
157
166
var args = std.process.args();
158
167
// skip binary
···
172
181
'r' => cmd.opts.reverse_sort = true,
173
182
't' => cmd.opts.sort_by_mod_time = true,
174
183
else => {
175
-
try stderr.print("Invalid opt: '{c}'", .{b});
184
+
try stderr.print("Invalid opt: '{c}'\n", .{b});
176
185
std.process.exit(1);
177
186
},
178
187
}
···
184
193
const val = split.rest();
185
194
if (eql(opt, "all")) {
186
195
cmd.opts.all = parseArgBool(val) orelse {
187
-
try stderr.print("Invalid boolean: '{s}'", .{val});
196
+
try stderr.print("Invalid boolean: '{s}'\n", .{val});
188
197
std.process.exit(1);
189
198
};
190
199
} else if (eql(opt, "long")) {
191
200
cmd.opts.long = parseArgBool(val) orelse {
192
-
try stderr.print("Invalid boolean: '{s}'", .{val});
201
+
try stderr.print("Invalid boolean: '{s}'\n", .{val});
193
202
std.process.exit(1);
194
203
};
195
204
} else if (eql(opt, "almost-all")) {
196
205
cmd.opts.@"almost-all" = parseArgBool(val) orelse {
197
-
try stderr.print("Invalid boolean: '{s}'", .{val});
206
+
try stderr.print("Invalid boolean: '{s}'\n", .{val});
198
207
std.process.exit(1);
199
208
};
200
209
} else if (eql(opt, "group-directories-first")) {
201
210
cmd.opts.@"group-directories-first" = parseArgBool(val) orelse {
202
-
try stderr.print("Invalid boolean: '{s}'", .{val});
211
+
try stderr.print("Invalid boolean: '{s}'\n", .{val});
203
212
std.process.exit(1);
204
213
};
205
214
} else if (eql(opt, "color")) {
206
215
cmd.opts.color = std.meta.stringToEnum(Options.When, val) orelse {
207
-
try stderr.print("Invalid color option: '{s}'", .{val});
216
+
try stderr.print("Invalid color option: '{s}'\n", .{val});
208
217
std.process.exit(1);
209
218
};
210
219
} else if (eql(opt, "human-readable")) {
211
220
// no-op: present for compatibility
212
221
} else if (eql(opt, "hyperlinks")) {
213
222
cmd.opts.hyperlinks = std.meta.stringToEnum(Options.When, val) orelse {
214
-
try stderr.print("Invalid hyperlinks option: '{s}'", .{val});
223
+
try stderr.print("Invalid hyperlinks option: '{s}'\n", .{val});
215
224
std.process.exit(1);
216
225
};
217
226
} else if (eql(opt, "icons")) {
218
227
cmd.opts.icons = std.meta.stringToEnum(Options.When, val) orelse {
219
-
try stderr.print("Invalid color option: '{s}'", .{val});
228
+
try stderr.print("Invalid color option: '{s}'\n", .{val});
220
229
std.process.exit(1);
221
230
};
222
231
} else if (eql(opt, "columns")) {
223
232
const c = parseArgBool(val) orelse {
224
-
try stderr.print("Invalid columns option: '{s}'", .{val});
233
+
try stderr.print("Invalid columns option: '{s}'\n", .{val});
225
234
std.process.exit(1);
226
235
};
227
236
cmd.opts.shortview = if (c) .columns else .oneline;
228
237
} else if (eql(opt, "oneline")) {
229
238
const o = parseArgBool(val) orelse {
230
-
try stderr.print("Invalid oneline option: '{s}'", .{val});
239
+
try stderr.print("Invalid oneline option: '{s}'\n", .{val});
231
240
std.process.exit(1);
232
241
};
233
242
cmd.opts.shortview = if (o) .oneline else .columns;
234
243
} else if (eql(opt, "time")) {
235
244
cmd.opts.sort_by_mod_time = parseArgBool(val) orelse {
236
-
try stderr.print("Invalid boolean: '{s}'", .{val});
245
+
try stderr.print("Invalid boolean: '{s}'\n", .{val});
237
246
std.process.exit(1);
238
247
};
239
248
} else if (eql(opt, "reverse")) {
240
249
cmd.opts.reverse_sort = parseArgBool(val) orelse {
241
-
try stderr.print("Invalid boolean: '{s}'", .{val});
250
+
try stderr.print("Invalid boolean: '{s}'\n", .{val});
242
251
std.process.exit(1);
243
252
};
253
+
} else if (eql(opt, "tree")) {
254
+
if (val.len == 0) {
255
+
cmd.opts.tree = true;
256
+
cmd.opts.tree_depth = null; // unlimited depth
257
+
} else {
258
+
cmd.opts.tree = true;
259
+
cmd.opts.tree_depth = std.fmt.parseInt(usize, val, 10) catch {
260
+
try stderr.print("Invalid tree depth: '{s}'\n", .{val});
261
+
std.process.exit(1);
262
+
};
263
+
}
244
264
} else if (eql(opt, "help")) {
245
265
return stderr.writeAll(usage);
246
266
} else if (eql(opt, "version")) {
247
-
try bw.writer().print("lsr {s}\r\n", .{build_options.version});
248
-
try bw.flush();
267
+
try stdout.print("lsr {s}\r\n", .{build_options.version});
268
+
try stdout.flush();
249
269
return;
250
270
} else {
251
-
try stderr.print("Invalid opt: '{s}'", .{opt});
271
+
try stderr.print("Invalid opt: '{s}'\n", .{opt});
252
272
std.process.exit(1);
253
273
}
254
274
},
255
275
.positional => {
256
-
cmd.opts.directory = arg;
276
+
try cmd.opts.directories.append(allocator, arg);
257
277
},
258
278
}
259
279
}
···
262
282
cmd.opts.colors = .default;
263
283
}
264
284
265
-
var ring: ourio.Ring = try .init(allocator, queue_size);
266
-
defer ring.deinit();
285
+
if (cmd.opts.directories.items.len == 0) {
286
+
try cmd.opts.directories.append(allocator, ".");
287
+
}
267
288
268
-
_ = try ring.open(cmd.opts.directory, .{ .DIRECTORY = true, .CLOEXEC = true }, 0, .{
269
-
.ptr = &cmd,
270
-
.cb = onCompletion,
271
-
.msg = @intFromEnum(Msg.cwd),
272
-
});
289
+
const multiple_dirs = cmd.opts.directories.items.len > 1;
290
+
291
+
for (cmd.opts.directories.items, 0..) |directory, dir_idx| {
292
+
cmd.entries = &.{};
293
+
cmd.entry_idx = 0;
294
+
cmd.symlinks.clearRetainingCapacity();
295
+
cmd.groups.clearRetainingCapacity();
296
+
cmd.users.clearRetainingCapacity();
297
+
cmd.tz = null;
298
+
cmd.opts.file = null;
299
+
cmd.current_directory = directory;
273
300
274
-
if (cmd.opts.long) {
275
-
_ = try ring.open("/etc/localtime", .{ .CLOEXEC = true }, 0, .{
276
-
.ptr = &cmd,
277
-
.cb = onCompletion,
278
-
.msg = @intFromEnum(Msg.localtime),
279
-
});
280
-
_ = try ring.open("/etc/passwd", .{ .CLOEXEC = true }, 0, .{
281
-
.ptr = &cmd,
282
-
.cb = onCompletion,
283
-
.msg = @intFromEnum(Msg.passwd),
284
-
});
285
-
_ = try ring.open("/etc/group", .{ .CLOEXEC = true }, 0, .{
301
+
var ring: ourio.Ring = try .init(allocator, queue_size);
302
+
defer ring.deinit();
303
+
304
+
_ = try ring.open(directory, .{ .DIRECTORY = true, .CLOEXEC = true }, 0, .{
286
305
.ptr = &cmd,
287
306
.cb = onCompletion,
288
-
.msg = @intFromEnum(Msg.group),
307
+
.msg = @intFromEnum(Msg.cwd),
289
308
});
290
-
}
291
309
292
-
try ring.run(.until_done);
310
+
if (cmd.opts.long) {
311
+
_ = try ring.open("/etc/localtime", .{ .CLOEXEC = true }, 0, .{
312
+
.ptr = &cmd,
313
+
.cb = onCompletion,
314
+
.msg = @intFromEnum(Msg.localtime),
315
+
});
316
+
_ = try ring.open("/etc/passwd", .{ .CLOEXEC = true }, 0, .{
317
+
.ptr = &cmd,
318
+
.cb = onCompletion,
319
+
.msg = @intFromEnum(Msg.passwd),
320
+
});
321
+
_ = try ring.open("/etc/group", .{ .CLOEXEC = true }, 0, .{
322
+
.ptr = &cmd,
323
+
.cb = onCompletion,
324
+
.msg = @intFromEnum(Msg.group),
325
+
});
326
+
}
293
327
294
-
if (cmd.entries.len == 0) return;
328
+
try ring.run(.until_done);
295
329
296
-
std.sort.pdq(Entry, cmd.entries, cmd.opts, Entry.lessThan);
330
+
if (cmd.entries.len == 0) {
331
+
if (multiple_dirs and dir_idx < cmd.opts.directories.items.len - 1) {
332
+
try stdout.writeAll("\r\n");
333
+
}
334
+
continue;
335
+
}
336
+
337
+
std.sort.pdq(Entry, cmd.entries, cmd.opts, Entry.lessThan);
297
338
298
-
if (cmd.opts.reverse_sort) {
299
-
std.mem.reverse(Entry, cmd.entries);
300
-
}
339
+
if (cmd.opts.reverse_sort) {
340
+
std.mem.reverse(Entry, cmd.entries);
341
+
}
301
342
302
-
if (cmd.opts.long) {
303
-
try printLong(cmd, bw.writer());
304
-
} else switch (cmd.opts.shortview) {
305
-
.columns => try printShortColumns(cmd, bw.writer()),
306
-
.oneline => try printShortOnePerLine(cmd, bw.writer()),
343
+
if (multiple_dirs and !cmd.opts.tree) {
344
+
if (dir_idx > 0) try stdout.writeAll("\r\n");
345
+
try stdout.print("{s}:\r\n", .{directory});
346
+
}
347
+
348
+
if (cmd.opts.tree) {
349
+
if (multiple_dirs and dir_idx > 0) try stdout.writeAll("\r\n");
350
+
try printTree(cmd, stdout);
351
+
} else if (cmd.opts.long) {
352
+
try printLong(&cmd, stdout);
353
+
} else switch (cmd.opts.shortview) {
354
+
.columns => try printShortColumns(cmd, stdout),
355
+
.oneline => try printShortOnePerLine(cmd, stdout),
356
+
}
307
357
}
308
-
try bw.flush();
358
+
try stdout.flush();
309
359
}
310
360
311
361
fn printShortColumns(cmd: Command, writer: anytype) !void {
···
382
432
383
433
if (i < columns.items.len - 1) {
384
434
const spaces = column.width - (icon_width + entry.name.len);
385
-
try writer.writeByteNTimes(' ', spaces);
435
+
var space_buf = [_][]const u8{" "};
436
+
try writer.writeSplatAll(&space_buf, spaces);
386
437
}
387
438
}
388
439
try writer.writeAll("\r\n");
···
397
448
const opts = cmd.opts;
398
449
const colors = opts.colors;
399
450
if (opts.useIcons()) {
400
-
const icon = Icon.get(entry, opts);
451
+
const icon = Icon.get(entry);
401
452
402
453
if (opts.useColor()) {
403
454
try writer.writeAll(icon.color);
···
420
471
}
421
472
422
473
if (opts.useHyperlinks()) {
423
-
const path = try std.fs.path.join(cmd.arena, &.{ opts.directory, entry.name });
474
+
const path = try std.fs.path.join(cmd.arena, &.{ cmd.current_directory, entry.name });
424
475
try writer.print("\x1b]8;;file://{s}\x1b\\", .{path});
425
476
try writer.writeAll(entry.name);
426
477
try writer.writeAll("\x1b]8;;\x1b\\");
···
446
497
}
447
498
}
448
499
449
-
fn printLong(cmd: Command, writer: anytype) !void {
500
+
fn drawTreePrefix(writer: anytype, prefix_list: []const bool, is_last: bool) !void {
501
+
for (prefix_list) |is_last_at_level| {
502
+
if (is_last_at_level) {
503
+
try writer.writeAll(" ");
504
+
} else {
505
+
try writer.writeAll("โ ");
506
+
}
507
+
}
508
+
509
+
if (is_last) {
510
+
try writer.writeAll("โโโ ");
511
+
} else {
512
+
try writer.writeAll("โโโ ");
513
+
}
514
+
}
515
+
516
+
fn printTree(cmd: Command, writer: anytype) !void {
517
+
const dir_name = if (std.mem.eql(u8, cmd.current_directory, ".")) blk: {
518
+
var buf: [std.fs.max_path_bytes]u8 = undefined;
519
+
const cwd = try std.process.getCwd(&buf);
520
+
break :blk std.fs.path.basename(cwd);
521
+
} else std.fs.path.basename(cmd.current_directory);
522
+
523
+
try writer.print("{s}\n", .{dir_name});
524
+
525
+
const max_depth = cmd.opts.tree_depth orelse std.math.maxInt(usize);
526
+
var prefix_list: std.ArrayList(bool) = .{};
527
+
528
+
for (cmd.entries, 0..) |entry, i| {
529
+
const is_last = i == cmd.entries.len - 1;
530
+
531
+
try drawTreePrefix(writer, prefix_list.items, is_last);
532
+
try printShortEntry(entry, cmd, writer);
533
+
try writer.writeAll("\r\n");
534
+
535
+
if (entry.kind == .directory and max_depth > 0) {
536
+
const full_path = try std.fs.path.joinZ(cmd.arena, &.{ cmd.current_directory, entry.name });
537
+
538
+
try prefix_list.append(cmd.arena, is_last);
539
+
try recurseTree(cmd, writer, full_path, &prefix_list, 1, max_depth);
540
+
541
+
_ = prefix_list.pop();
542
+
}
543
+
}
544
+
}
545
+
546
+
fn recurseTree(cmd: Command, writer: anytype, dir_path: [:0]const u8, prefix_list: *std.ArrayList(bool), depth: usize, max_depth: usize) !void {
547
+
var dir = std.fs.cwd().openDir(dir_path, .{ .iterate = true }) catch {
548
+
return;
549
+
};
550
+
defer dir.close();
551
+
552
+
var entries: std.ArrayList(Entry) = .{};
553
+
var iter = dir.iterate();
554
+
555
+
while (try iter.next()) |dirent| {
556
+
if (!cmd.opts.showDotfiles() and std.mem.startsWith(u8, dirent.name, ".")) continue;
557
+
558
+
const nameZ = try cmd.arena.dupeZ(u8, dirent.name);
559
+
try entries.append(cmd.arena, .{
560
+
.name = nameZ,
561
+
.kind = dirent.kind,
562
+
.statx = undefined,
563
+
});
564
+
}
565
+
566
+
std.sort.pdq(Entry, entries.items, cmd.opts, Entry.lessThan);
567
+
568
+
if (cmd.opts.reverse_sort) {
569
+
std.mem.reverse(Entry, entries.items);
570
+
}
571
+
572
+
for (entries.items, 0..) |entry, i| {
573
+
const is_last = i == entries.items.len - 1;
574
+
575
+
try drawTreePrefix(writer, prefix_list.items, is_last);
576
+
try printTreeEntry(entry, cmd, writer, dir_path);
577
+
try writer.writeAll("\r\n");
578
+
579
+
if (entry.kind == .directory and depth < max_depth) {
580
+
const full_path = try std.fs.path.joinZ(cmd.arena, &.{ dir_path, entry.name });
581
+
582
+
try prefix_list.append(cmd.arena, is_last);
583
+
try recurseTree(cmd, writer, full_path, prefix_list, depth + 1, max_depth);
584
+
585
+
_ = prefix_list.pop();
586
+
}
587
+
}
588
+
}
589
+
590
+
fn printTreeEntry(entry: Entry, cmd: Command, writer: anytype, dir_path: [:0]const u8) !void {
591
+
const opts = cmd.opts;
592
+
const colors = opts.colors;
593
+
594
+
if (opts.useIcons()) {
595
+
const icon = Icon.get(entry);
596
+
597
+
if (opts.useColor()) {
598
+
try writer.writeAll(icon.color);
599
+
try writer.writeAll(icon.icon);
600
+
try writer.writeAll(colors.reset);
601
+
} else {
602
+
try writer.writeAll(icon.icon);
603
+
}
604
+
605
+
try writer.writeByte(' ');
606
+
}
607
+
608
+
switch (entry.kind) {
609
+
.directory => try writer.writeAll(colors.dir),
610
+
.sym_link => try writer.writeAll(colors.symlink),
611
+
else => {
612
+
const full_path = try std.fs.path.join(cmd.arena, &.{ dir_path, entry.name });
613
+
const stat_result = std.fs.cwd().statFile(full_path) catch null;
614
+
if (stat_result) |stat| {
615
+
if (stat.mode & (std.posix.S.IXUSR | std.posix.S.IXGRP | std.posix.S.IXOTH) != 0) {
616
+
try writer.writeAll(colors.executable);
617
+
}
618
+
}
619
+
},
620
+
}
621
+
622
+
if (opts.useHyperlinks()) {
623
+
const path = try std.fs.path.join(cmd.arena, &.{ dir_path, entry.name });
624
+
try writer.print("\x1b]8;;file://{s}\x1b\\", .{path});
625
+
try writer.writeAll(entry.name);
626
+
try writer.writeAll("\x1b]8;;\x1b\\");
627
+
try writer.writeAll(colors.reset);
628
+
} else {
629
+
try writer.writeAll(entry.name);
630
+
try writer.writeAll(colors.reset);
631
+
}
632
+
}
633
+
634
+
fn printLong(cmd: *Command, writer: anytype) !void {
450
635
const tz = cmd.tz.?;
451
636
const now = zeit.instant(.{}) catch unreachable;
452
637
const one_year_ago = try now.subtract(.{ .days = 365 });
···
458
643
var n_size: usize = 0;
459
644
var n_suff: usize = 0;
460
645
for (cmd.entries) |entry| {
461
-
const group = cmd.getGroup(entry.statx.gid);
462
-
const user = cmd.getUser(entry.statx.uid);
646
+
const group = try cmd.getGroup(entry.statx.gid);
647
+
const user = try cmd.getUser(entry.statx.uid);
463
648
464
649
var buf: [16]u8 = undefined;
465
650
const size = try entry.humanReadableSize(&buf);
···
490
675
};
491
676
492
677
for (cmd.entries) |entry| {
493
-
const user: User = cmd.getUser(entry.statx.uid) orelse
678
+
const user: User = try cmd.getUser(entry.statx.uid) orelse
494
679
.{
495
680
.uid = entry.statx.uid,
496
681
.name = try std.fmt.allocPrint(cmd.arena, "{d}", .{entry.statx.uid}),
497
682
};
498
-
const group: Group = cmd.getGroup(entry.statx.gid) orelse
683
+
const group: Group = try cmd.getGroup(entry.statx.gid) orelse
499
684
.{
500
685
.gid = entry.statx.gid,
501
686
.name = try std.fmt.allocPrint(cmd.arena, "{d}", .{entry.statx.gid}),
···
509
694
try writer.writeAll(&mode);
510
695
try writer.writeByte(' ');
511
696
try writer.writeAll(user.name);
512
-
try writer.writeByteNTimes(' ', longest_user - user.name.len);
697
+
var space_buf1 = [_][]const u8{" "};
698
+
try writer.writeSplatAll(&space_buf1, longest_user - user.name.len);
513
699
try writer.writeByte(' ');
514
700
try writer.writeAll(group.name);
515
-
try writer.writeByteNTimes(' ', longest_group - group.name.len);
701
+
var space_buf2 = [_][]const u8{" "};
702
+
try writer.writeSplatAll(&space_buf2, longest_group - group.name.len);
516
703
try writer.writeByte(' ');
517
704
518
705
var size_buf: [16]u8 = undefined;
519
706
const size = try entry.humanReadableSize(&size_buf);
520
707
const suffix = entry.humanReadableSuffix();
521
708
522
-
try writer.writeByteNTimes(' ', longest_size - size.len);
709
+
var space_buf3 = [_][]const u8{" "};
710
+
try writer.writeSplatAll(&space_buf3, longest_size - size.len);
523
711
try writer.writeAll(size);
524
712
try writer.writeByte(' ');
525
713
try writer.writeAll(suffix);
526
-
try writer.writeByteNTimes(' ', longest_suffix - suffix.len);
714
+
var space_buf4 = [_][]const u8{" "};
715
+
try writer.writeSplatAll(&space_buf4, longest_suffix - suffix.len);
527
716
try writer.writeByte(' ');
528
717
529
718
try writer.print("{d: >2} {s} ", .{
···
538
727
}
539
728
540
729
if (cmd.opts.useIcons()) {
541
-
const icon = Icon.get(entry, cmd.opts);
730
+
const icon = Icon.get(entry);
542
731
543
732
if (cmd.opts.useColor()) {
544
733
try writer.writeAll(icon.color);
···
562
751
}
563
752
564
753
if (cmd.opts.useHyperlinks()) {
565
-
const path = try std.fs.path.join(cmd.arena, &.{ cmd.opts.directory, entry.name });
754
+
const path = try std.fs.path.join(cmd.arena, &.{ cmd.current_directory, entry.name });
566
755
try writer.print("\x1b]8;;file://{s}\x1b\\", .{path});
567
756
try writer.writeAll(entry.name);
568
757
try writer.writeAll("\x1b]8;;\x1b\\");
···
606
795
entries: []Entry = &.{},
607
796
entry_idx: usize = 0,
608
797
symlinks: std.StringHashMapUnmanaged(Symlink) = .empty,
798
+
current_directory: [:0]const u8 = ".",
609
799
610
800
tz: ?zeit.TimeZone = null,
611
801
groups: std.ArrayListUnmanaged(Group) = .empty,
612
802
users: std.ArrayListUnmanaged(User) = .empty,
613
803
614
-
fn getUser(self: Command, uid: posix.uid_t) ?User {
804
+
fn getUser(self: *Command, uid: posix.uid_t) !?User {
615
805
for (self.users.items) |user| {
616
806
if (user.uid == uid) return user;
617
807
}
808
+
if (std.c.getpwuid(uid)) |user| {
809
+
if (user.name) |name| {
810
+
const new_user = User{
811
+
.uid = uid,
812
+
.name = std.mem.span(name),
813
+
};
814
+
try self.users.append(self.arena, new_user);
815
+
return new_user;
816
+
}
817
+
}
618
818
return null;
619
819
}
620
820
621
-
fn getGroup(self: Command, gid: posix.gid_t) ?Group {
821
+
fn getGroup(self: *Command, gid: posix.gid_t) !?Group {
622
822
for (self.groups.items) |group| {
623
823
if (group.gid == gid) return group;
624
824
}
825
+
if (grp.getgrgid(gid)) |group| {
826
+
const new_group = Group{
827
+
.gid = gid,
828
+
.name = std.mem.span(group.*.gr_name),
829
+
};
830
+
try self.groups.append(self.arena, new_group);
831
+
return new_group;
832
+
}
625
833
return null;
626
834
}
627
835
};
···
683
891
statx: ourio.Statx,
684
892
685
893
fn lessThan(opts: Options, lhs: Entry, rhs: Entry) bool {
686
-
if (opts.@"group-directories-first" and
687
-
lhs.kind != rhs.kind and
688
-
(lhs.kind == .directory or rhs.kind == .directory))
689
-
{
690
-
return lhs.kind == .directory;
894
+
if (opts.@"group-directories-first") {
895
+
const lhs_is_dir = posix.S.ISDIR(lhs.statx.mode);
896
+
const rhs_is_dir = posix.S.ISDIR(rhs.statx.mode);
897
+
898
+
if (lhs_is_dir != rhs_is_dir) return lhs_is_dir;
691
899
}
692
900
693
901
if (opts.sort_by_mod_time) {
···
786
994
787
995
// if the user specified a file (or something that couldn't be opened as a
788
996
// directory), then we open it's parent and apply a filter
789
-
const dirname = std.fs.path.dirname(cmd.opts.directory) orelse ".";
790
-
cmd.opts.file = std.fs.path.basename(cmd.opts.directory);
791
-
cmd.opts.directory = try cmd.arena.dupeZ(u8, dirname);
997
+
const dirname = std.fs.path.dirname(cmd.current_directory) orelse ".";
998
+
cmd.opts.file = std.fs.path.basename(cmd.current_directory);
999
+
cmd.current_directory = try cmd.arena.dupeZ(u8, dirname);
792
1000
_ = try io.open(
793
-
cmd.opts.directory,
1001
+
cmd.current_directory,
794
1002
.{ .DIRECTORY = true, .CLOEXEC = true },
795
1003
0,
796
1004
.{
···
811
1019
if (cmd.opts.useHyperlinks()) {
812
1020
var buf: [std.fs.max_path_bytes]u8 = undefined;
813
1021
const cwd = try std.os.getFdPath(fd, &buf);
814
-
cmd.opts.directory = try cmd.arena.dupeZ(u8, cwd);
1022
+
cmd.current_directory = try cmd.arena.dupeZ(u8, cwd);
815
1023
}
816
1024
817
1025
var temp_results: std.ArrayListUnmanaged(MinimalEntry) = .empty;
···
873
1081
}
874
1082
const path = try std.fs.path.joinZ(
875
1083
cmd.arena,
876
-
&.{ cmd.opts.directory, entry.name },
1084
+
&.{ cmd.current_directory, entry.name },
877
1085
);
878
1086
879
1087
if (entry.kind == .sym_link) {
···
909
1117
const n = try result.read;
910
1118
_ = try io.close(task.req.read.fd, .{});
911
1119
const bytes = task.req.read.buffer[0..n];
912
-
var fbs = std.io.fixedBufferStream(bytes);
913
-
const tz = try zeit.timezone.TZInfo.parse(cmd.arena, fbs.reader());
1120
+
var reader = std.Io.Reader.fixed(bytes);
1121
+
const tz = try zeit.timezone.TZInfo.parse(cmd.arena, &reader);
914
1122
cmd.tz = .{ .tzinfo = tz };
915
1123
},
916
1124
···
1040
1248
cmd.entry_idx += 1;
1041
1249
const path = try std.fs.path.joinZ(
1042
1250
cmd.arena,
1043
-
&.{ cmd.opts.directory, entry.name },
1251
+
&.{ cmd.current_directory, entry.name },
1044
1252
);
1045
1253
1046
1254
if (entry.kind == .sym_link) {
···
1128
1336
.{ "zon", Icon.zig },
1129
1337
});
1130
1338
1131
-
fn get(entry: Entry, opts: Options) Icon {
1339
+
fn get(entry: Entry) Icon {
1132
1340
// 1. By name
1133
-
// 2. By extension
1134
-
// 3. By type
1341
+
// 2. By type
1342
+
// 3. By extension
1135
1343
if (by_name.get(entry.name)) |icon| return icon;
1136
1344
1137
-
const ext = std.fs.path.extension(entry.name);
1138
-
if (ext.len > 0) {
1139
-
const ft = ext[1..];
1140
-
if (by_extension.get(ft)) |icon| return icon;
1141
-
}
1142
-
1143
1345
switch (entry.kind) {
1144
1346
.block_device => return drive,
1145
1347
.character_device => return drive,
1146
1348
.directory => return directory,
1147
1349
.file => {
1350
+
const ext = std.fs.path.extension(entry.name);
1351
+
if (ext.len > 0) {
1352
+
const ft = ext[1..];
1353
+
if (by_extension.get(ft)) |icon| return icon;
1354
+
}
1355
+
1148
1356
if (entry.isExecutable()) {
1149
1357
return executable;
1150
1358
}
···
1152
1360
},
1153
1361
.named_pipe => return pipe,
1154
1362
.sym_link => {
1155
-
if (opts.long and posix.S.ISDIR(entry.statx.mode)) {
1363
+
if (posix.S.ISDIR(entry.statx.mode)) {
1156
1364
return symlink_dir;
1157
1365
}
1158
1366
return symlink;