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