地圖 (Jido) is a lightweight Unix TUI file explorer designed for speed and simplicity.
at main 5.9 kB view raw
1const std = @import("std"); 2const builtin = @import("builtin"); 3const options = @import("options"); 4const App = @import("app.zig"); 5const FileLogger = @import("file_logger.zig"); 6const vaxis = @import("vaxis"); 7const config = &@import("./config.zig").config; 8const resolvePath = @import("./commands.zig").resolvePath; 9 10pub const panic = vaxis.panic_handler; 11const help_menu = 12 \\Usage: jido 13 \\ 14 \\a lightweight Unix TUI file explorer 15 \\ 16 \\Flags: 17 \\ -h, --help Show help information and exit. 18 \\ -v, --version Print version information and exit. 19 \\ --entry-dir=PATH Open jido at chosen dir. 20 \\ --choose-dir Makes jido act like a directory chooser. When jido 21 \\ quits, it will write the name of the last visited 22 \\ directory to STDOUT. 23 \\ 24; 25 26pub const std_options: std.Options = .{ 27 .log_scope_levels = &.{ 28 .{ .scope = .vaxis, .level = .warn }, 29 .{ .scope = .vaxis_parser, .level = .warn }, 30 }, 31}; 32 33const Options = struct { 34 help: bool = false, 35 version: bool = false, 36 @"choose-dir": bool = false, 37 @"entry-path": []const u8 = ".", 38 39 fn optKind(a: []const u8) enum { short, long, positional } { 40 if (std.mem.startsWith(u8, a, "--")) return .long; 41 if (std.mem.startsWith(u8, a, "-")) return .short; 42 return .positional; 43 } 44}; 45 46pub fn main() !void { 47 var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 48 defer { 49 const deinit_status = gpa.deinit(); 50 if (deinit_status == .leak) { 51 std.log.err("memory leak", .{}); 52 } 53 } 54 const alloc = gpa.allocator(); 55 56 var last_dir: ?[]const u8 = null; 57 var entry_path_buf: [std.fs.max_path_bytes]u8 = undefined; 58 59 var opts = Options{}; 60 var args = std.process.args(); 61 _ = args.skip(); 62 while (args.next()) |arg| { 63 switch (Options.optKind(arg)) { 64 .short => { 65 const str = arg[1..]; 66 for (str) |b| { 67 switch (b) { 68 'v' => opts.version = true, 69 'h' => opts.help = true, 70 else => { 71 std.log.err("Invalid opt: '{c}'", .{b}); 72 std.process.exit(1); 73 }, 74 } 75 } 76 }, 77 .long => { 78 var split = std.mem.splitScalar(u8, arg[2..], '='); 79 const opt = split.first(); 80 const val = split.rest(); 81 if (std.mem.eql(u8, opt, "version")) { 82 opts.version = true; 83 } else if (std.mem.eql(u8, opt, "help")) { 84 opts.help = true; 85 } else if (std.mem.eql(u8, opt, "choose-dir")) { 86 opts.@"choose-dir" = true; 87 } else if (std.mem.eql(u8, opt, "entry-dir")) { 88 const path = if (std.mem.eql(u8, val, "")) "." else val; 89 var dir = try std.fs.cwd().openDir(".", .{ .iterate = true }); 90 defer dir.close(); 91 opts.@"entry-path" = resolvePath(&entry_path_buf, path, dir); 92 } 93 }, 94 .positional => { 95 std.log.err("Invalid opt: '{s}'. Jido does not take positional arguments.", .{arg}); 96 std.process.exit(1); 97 }, 98 } 99 } 100 101 if (opts.help) { 102 std.debug.print(help_menu, .{}); 103 return; 104 } 105 106 if (opts.version) { 107 std.debug.print("jido v{f}\n", .{options.version}); 108 return; 109 } 110 111 { 112 var app = App.init(alloc, opts.@"entry-path") catch { 113 vaxis.recover(); 114 std.process.exit(1); 115 }; 116 defer app.deinit(); 117 118 config.parse(alloc, &app) catch |err| switch (err) { 119 error.SyntaxError => { 120 app.notification.write("Encountered a syntax error while parsing the config file.", .err) catch { 121 std.log.err("Encountered a syntax error while parsing the config file.", .{}); 122 }; 123 }, 124 error.InvalidCharacter => { 125 app.notification.write("One or more overriden keybinds are invalid.", .err) catch { 126 std.log.err("One or more overriden keybinds are invalid.", .{}); 127 }; 128 }, 129 error.DuplicateKeybind => { 130 // Error logged in function 131 }, 132 else => { 133 const message = try std.fmt.allocPrint(alloc, "Encountend an unknown error while parsing the config file - {}", .{err}); 134 defer alloc.free(message); 135 136 app.notification.write(message, .err) catch { 137 std.log.err("Encountend an unknown error while parsing the config file - {}", .{err}); 138 }; 139 }, 140 }; 141 142 app.file_logger = if (config.config_dir) |dir| FileLogger.init(dir) else logger: { 143 std.log.err("Failed to initialise file logger - no config directory found", .{}); 144 break :logger null; 145 }; 146 app.notification.loop = &app.loop; 147 148 try app.run(); 149 150 if (opts.@"choose-dir") { 151 last_dir = alloc.dupe(u8, try app.directories.fullPath(".")) catch null; 152 } 153 } 154 155 // Must be printed after app has deinit as part of that process clears 156 // the screen. 157 if (last_dir) |path| { 158 var stdout_buffer: [std.fs.max_path_bytes]u8 = undefined; 159 var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); 160 const stdout = &stdout_writer.interface; 161 stdout.print("{s}\n", .{path}) catch {}; 162 stdout.flush() catch {}; 163 164 alloc.free(path); 165 } 166}