╭ main.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 const std = @import("std"); 2 const xit = @import("xit"); 3 const rp = xit.repo; 4 const cmd = @import("./command.zig"); 5 const srv = @import("./serve.zig"); 6 const ui = @import("./ui.zig"); 7 8 // cook the terminal before a panic/segfault trace is printed, so the trace 9 // isn't mangled by raw mode and the alternate buffer 10 pub const std_options_debug_io = xit.xitui.terminal.crash_debug_io; 11 12 pub const RunOpts = struct { 13 out: *std.Io.Writer, 14 err: *std.Io.Writer, 15 }; 16 17 pub fn main(init: std.process.Init) !u8 { 18 const allocator = std.heap.smp_allocator; 19 20 var threaded = std.Io.Threaded.init(allocator, .{ 21 .environ = init.minimal.environ, 22 }); 23 defer threaded.deinit(); 24 const io = threaded.io(); 25 26 var args: std.ArrayList([]const u8) = .empty; 27 defer args.deinit(allocator); 28 29 var arg_it = try init.minimal.args.iterateAllocator(allocator); 30 defer arg_it.deinit(); 31 _ = arg_it.skip(); 32 while (arg_it.next()) |arg| { 33 try args.append(allocator, arg); 34 } 35 36 var stdout_writer = std.Io.File.stdout().writer(io, &.{}); 37 var stderr_writer = std.Io.File.stderr().writer(io, &.{}); 38 const run_opts = RunOpts{ .out = &stdout_writer.interface, .err = &stderr_writer.interface }; 39 40 const cwd_path = try std.process.currentPathAlloc(io, allocator); 41 defer allocator.free(cwd_path); 42 43 run(.xit, .{}, io, allocator, args.items, cwd_path, run_opts) catch |err| switch (err) { 44 error.HandledError => return 1, 45 else => |e| return e, 46 }; 47 48 return 0; 49 } 50 51 pub fn run( 52 comptime repo_kind: rp.RepoKind, 53 comptime any_repo_opts: rp.AnyRepoOpts(repo_kind), 54 io: std.Io, 55 allocator: std.mem.Allocator, 56 args: []const []const u8, 57 cwd_path: []const u8, 58 run_opts: RunOpts, 59 ) !void { 60 var cmd_args = try cmd.CommandArgs.init(allocator, args); 61 defer cmd_args.deinit(); 62 63 switch (try cmd.CommandDispatch.init(&cmd_args)) { 64 .invalid => |invalid| switch (invalid) { 65 .command => |command| { 66 try run_opts.err.print("\"{s}\" is not a valid command\n\n", .{command}); 67 try cmd.printHelp(null, run_opts.err); 68 return error.HandledError; 69 }, 70 .argument => |argument| { 71 try run_opts.err.print("\"{s}\" is not a valid argument\n\n", .{argument.value}); 72 try cmd.printHelp(argument.command, run_opts.err); 73 return error.HandledError; 74 }, 75 }, 76 .help => |cmd_kind_maybe| try cmd.printHelp(cmd_kind_maybe, run_opts.out), 77 .local => { 78 // session-lifetime allocations (the repo path, session state) 79 var session_arena = std.heap.ArenaAllocator.init(allocator); 80 defer session_arena.deinit(); 81 82 // probe for a git repo first, then a xit repo. only the work path 83 // and backend kind are kept; pages re-open the repo on each build. 84 const local: ui.RepoSource = blk: { 85 if (rp.AnyRepo(.git, .{}).open(io, allocator, .{ .path = cwd_path })) |repo| { 86 var git_repo = repo; 87 defer git_repo.deinit(io, allocator); 88 const work_path = switch (git_repo) { 89 inline else => |*r| r.core.work_path, 90 }; 91 break :blk .{ .path = try session_arena.allocator().dupe(u8, work_path), .repo_kind = .git }; 92 } else |err| switch (err) { 93 error.RepoNotFound => {}, 94 else => |e| return e, 95 } 96 var xit_repo = rp.AnyRepo(.xit, .{}).open(io, allocator, .{ .path = cwd_path }) catch |err| switch (err) { 97 error.RepoNotFound => { 98 try run_opts.err.print("no git or xit repo found in the current directory\n", .{}); 99 return error.HandledError; 100 }, 101 else => |e| return e, 102 }; 103 defer xit_repo.deinit(io, allocator); 104 const work_path = switch (xit_repo) { 105 inline else => |*r| r.core.work_path, 106 }; 107 break :blk .{ .path = try session_arena.allocator().dupe(u8, work_path), .repo_kind = .xit }; 108 }; 109 110 // every local route internally carries this synthetic 111 // "owner/name"; urls elide it. 112 const identity = try std.fmt.allocPrint(session_arena.allocator(), "local/{s}", .{std.fs.path.basename(local.path)}); 113 const route = ui.RoutablePage.repoFilesRoute(identity, null, "", "", 0) orelse { 114 try run_opts.err.print("repo directory name is too long\n", .{}); 115 return error.HandledError; 116 }; 117 118 var session = ui.Session{ 119 .arena = &session_arena, 120 .page_arena = &session_arena, 121 .io = io, 122 .local = local, 123 .is_terminal = true, 124 .data = .{ .current_page = route }, 125 }; 126 127 try ui.run(io, allocator, &session, null); 128 }, 129 .cli => |cli_cmd| switch (cli_cmd) { 130 .serve => |options| try srv.run(repo_kind, any_repo_opts, io, allocator, cwd_path, options, run_opts.err, {}), 131 }, 132 } 133 } 134