╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭───────────────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
111 try unused_args.put(arena.allocator(), arg, {}); 112 } else { 113 // if the last key is a value flag and doesn't have a value yet, 114 // set this arg as its value 115 const keys = map_args.keys(); 116 if (keys.len > 0) { 117 const last_key = keys[keys.len - 1]; 118 if (map_args.get(last_key)) |last_val| { 119 if (value_flags.has(last_key) and last_val == null) { 120 try map_args.put(arena.allocator(), last_key, arg); 121 continue; 122 } 123 } 124 } 125 126 // in any other case, just consider it a positional arg 127 try positional_args.append(arena.allocator(), arg); 128 } 129 } 130 131 const args_slice = try positional_args.toOwnedSlice(arena.allocator()); 132 if (args_slice.len == 0) { 133 return .{ 134 .allocator = allocator, 135 .arena = arena, 136 .command_kind = null, 137 .command_name = null, 138 .positional_args = args_slice, 139 .map_args = map_args, 140 .unused_args = unused_args, 141 }; 142 } else { 143 const command_name = args_slice[0]; 144 const extra_args = args_slice[1..]; 145 146 const command_kind: ?CommandKind = inline for (0..@typeInfo(CommandKind).@"enum".field_names.len) |i| { 147 if (std.mem.eql(u8, command_name, commandHelp(@fromBackingInt(@intCast(i))).name)) { 148 break @fromBackingInt(@intCast(i)); 149 } 150 } else null; 151 152 return .{ 153 .allocator = allocator, 154 .arena = arena, 155 .command_kind = command_kind, 156 .command_name = command_name, 157 .positional_args = extra_args, 158 .map_args = map_args, 159 .unused_args = unused_args, 160 }; 161 } 162 } 163 164 pub fn deinit(self: *CommandArgs) void { 165 self.arena.deinit(); 166 self.allocator.destroy(self.arena); 167 } 168 169 pub fn contains(self: *CommandArgs, arg: []const u8) bool { 170 _ = self.unused_args.orderedRemove(arg); 171 return self.map_args.contains(arg); 172 } 173 174 pub fn get(self: *CommandArgs, comptime arg: []const u8) ??[]const u8 { 175 comptime std.debug.assert(value_flags.has(arg)); // can only call `get` with flags included in `value_flags` 176 _ = self.unused_args.orderedRemove(arg); 177 return self.map_args.get(arg); 178 } 179 }; 180 181 /// parses the args into a format that can be directly used by a repo. 182 /// if any additional allocation needs to be done, the arena inside the cmd args will be used. 183 pub const Command = union(CommandKind) { 184 serve: srv.Options, 185 186 pub fn initMaybe(cmd_args: *CommandArgs) !?Command { 187 const command_kind = cmd_args.command_kind orelse return null; 188 switch (command_kind) { 189 .serve => { 190 return .{ .serve = try serveOptions(cmd_args) orelse return null }; 191 }, 192 } 193 } 194 }; 195 196 pub fn parseServeOptions(allocator: std.mem.Allocator, args: []const []const u8) !srv.Options { 197 var cmd_args = try CommandArgs.init(allocator, args); 198 defer cmd_args.deinit(); 199 const options = try serveOptions(&cmd_args) orelse return error.InvalidServeArgument; 200 if (cmd_args.unused_args.count() != 0) return error.InvalidServeArgument; 201 return options; 202 } 203 204 fn serveOptions(cmd_args: *CommandArgs) !?srv.Options { 205 if (cmd_args.positional_args.len != 0) return null; 206 207 var options: srv.Options = .{}; 208 if (cmd_args.get("--web-listen")) |value_maybe| options.web_listen = value_maybe orelse return error.WebListenNeedsValue; 209 if (cmd_args.get("--ssh-listen")) |value_maybe| { 210 const value = value_maybe orelse return error.SshListenNeedsValue; 211 options.ssh_listen = if (std.mem.eql(u8, value, "none")) null else value; 212 } 213 // urls are joined with "/" + a path 214 if (cmd_args.get("--web-url")) |value_maybe| options.web_url = std.mem.trimEnd(u8, value_maybe orelse return error.WebUrlNeedsValue, "/"); 215 if (cmd_args.get("--ssh-url")) |value_maybe| options.ssh_url = std.mem.trimEnd(u8, value_maybe orelse return error.SshUrlNeedsValue, "/"); 216 if (cmd_args.get("--max-repo-size")) |value_maybe| options.max_repo_size = try parseSize(value_maybe orelse return error.MaxRepoSizeNeedsValue); 217 if (cmd_args.get("--data-dir")) |value_maybe| options.data_dir = value_maybe orelse return error.DataDirNeedsValue; 218 return options; 219 } 220 221 // a byte count with an optional K, M or G suffix 222 fn parseSize(value: []const u8) !u64 { 223 if (value.len == 0) return error.InvalidSize; 224 const shift: u6 = switch (value[value.len - 1]) { 225 'K', 'k' => 10, 226 'M', 'm' => 20, 227 'G', 'g' => 30, 228 else => 0, 229 }; 230 const digits = if (shift == 0) value else value[0 .. value.len - 1]; 231 const count = std.fmt.parseInt(u64, digits, 10) catch return error.InvalidSize; 232 return std.math.shlExact(u64, count, shift) catch error.InvalidSize; 233 } 234 235 /// parses the given args into a command if valid, and determines how it should be run 236 /// (via the TUI or CLI). 237 pub const CommandDispatch = union(enum) { 238 invalid: union(enum) { 239 command: []const u8, 240 argument: struct { 241 command: ?CommandKind, 242 value: []const u8, 243 }, 244 }, 245 help: ?CommandKind, 246 local, 247 cli: Command, 248 249 pub fn init(cmd_args: *CommandArgs) !CommandDispatch { 250 const dispatch = try initIgnoreUnused(cmd_args); 251 if (cmd_args.unused_args.count() > 0) { 252 return .{ 253 .invalid = .{ 254 .argument = .{ 255 .command = switch (dispatch) { 256 .invalid => return dispatch, // if there was already an error, return it instead 257 .help => |cmd_kind_maybe| cmd_kind_maybe, 258 .local => null, 259 .cli => |command| command, 260 }, 261 .value = cmd_args.unused_args.keys()[0], 262 }, 263 }, 264 }; 265 } 266 return dispatch; 267 } 268 269 pub fn initIgnoreUnused(cmd_args: *CommandArgs) !CommandDispatch { 270 const show_help = cmd_args.contains("--help"); 271 272 if (cmd_args.command_kind) |command_kind| { 273 if (show_help) { 274 return .{ .help = command_kind }; 275 } else if (try Command.initMaybe(cmd_args)) |cmd| { 276 return .{ .cli = cmd }; 277 } else { 278 return .{ .help = command_kind }; 279 } 280 } else if (cmd_args.command_name) |command_name| { 281 return .{ .invalid = .{ .command = command_name } }; 282 } else if (show_help) { 283 return .{ .help = null }; 284 } else { 285 return .local; 286 } 287 } 288 }; 289