@@ -19,6 +19,42 @@ pub const client = @import("client.zig");
1919pub const server = @import ("server.zig" );
2020pub const session = @import ("session.zig" );
2121
22+ // ─── CLI parsing ──────────────────────────────────────────────────────────────
23+
24+ const ParsedCli = struct {
25+ /// From `-s <name>` (last wins if repeated). Not appended to `filtered`.
26+ session_name : ? []const u8 ,
27+ /// argv with program path stripped, `-s` and its value removed.
28+ filtered : []const []const u8 ,
29+ };
30+
31+ /// Strip the executable path, remove `-s <name>` pairs, and record the session name.
32+ /// Caller must `allocator.free(parsed.filtered)` when done.
33+ fn parseUserArgs (allocator : std.mem.Allocator , args : []const []const u8 ) error { OutOfMemory , MissingSessionNameAfterS }! ParsedCli {
34+ if (args .len == 0 ) return .{ .session_name = null , .filtered = try allocator .alloc ([]const u8 , 0 ) };
35+
36+ var session_name : ? []const u8 = null ;
37+ var list : std .ArrayListUnmanaged ([]const u8 ) = .{};
38+ errdefer list .deinit (allocator );
39+
40+ var i : usize = 1 ;
41+ while (i < args .len ) : (i += 1 ) {
42+ const arg = args [i ];
43+ if (std .mem .eql (u8 , arg , "-s" )) {
44+ if (i + 1 >= args .len ) return error .MissingSessionNameAfterS ;
45+ session_name = args [i + 1 ];
46+ i += 1 ;
47+ continue ;
48+ }
49+ try list .append (allocator , arg );
50+ }
51+
52+ return .{
53+ .session_name = session_name ,
54+ .filtered = try list .toOwnedSlice (allocator ),
55+ };
56+ }
57+
2258// ─── Socket path helpers ──────────────────────────────────────────────────────
2359
2460/// Resolve `socket_dir` from config, replacing `{uid}` with the real UID.
@@ -403,27 +439,23 @@ pub fn main() !void {
403439 var cfg = try config .loadFromFile (allocator );
404440 defer cfg .deinit ();
405441
406- // Parse args.
442+ // Parse args (handles `-s <name>` anywhere; value is not treated as a subcommand arg) .
407443 const args = try std .process .argsAlloc (allocator );
408444 defer std .process .argsFree (allocator , args );
409445
410- // Scan for -s <name> flag anywhere in args.
411- var session_name : ? []const u8 = null ;
412- var filtered_args : std .ArrayListUnmanaged ([]const u8 ) = .{};
413- defer filtered_args .deinit (allocator );
414-
415- for (args , 0.. ) | arg , i | {
416- if (std .mem .eql (u8 , arg , "-s" ) and i + 1 < args .len ) {
417- session_name = args [i + 1 ];
418- } else if (i > 0 and i < args .len ) {
419- // Check if this arg is the value for -s (skip it).
420- if (i >= 2 and std .mem .eql (u8 , args [i - 1 ], "-s" )) continue ;
421- try filtered_args .append (allocator , arg );
422- }
423- }
446+ const parsed = parseUserArgs (allocator , args ) catch | err | switch (err ) {
447+ error .MissingSessionNameAfterS = > {
448+ std .debug .print ("error: -s requires a session name\n " , .{});
449+ return ;
450+ },
451+ else = > | e | return e ,
452+ };
453+ defer allocator .free (parsed .filtered );
454+ const session_name = parsed .session_name ;
455+ const filtered_args = parsed .filtered ;
424456
425- const has_subcmd = filtered_args .items . len > 0 ;
426- const subcmd = if (has_subcmd ) filtered_args . items [0 ] else "" ;
457+ const has_subcmd = filtered_args .len > 0 ;
458+ const subcmd = if (has_subcmd ) filtered_args [0 ] else "" ;
427459
428460 // No subcommand: create a new session.
429461 if (! has_subcmd ) {
@@ -437,14 +469,18 @@ pub fn main() !void {
437469
438470 // ── Internal ──
439471 if (std .mem .eql (u8 , subcmd , "--server" )) {
440- const name = if (filtered_args .items . len > 1 ) filtered_args . items [1 ] else "0" ;
472+ const name = if (filtered_args .len > 1 ) filtered_args [1 ] else "0" ;
441473 return runServer (allocator , & cfg , name );
442474 }
443475
444476 // ── attach / a ──
445477 if (std .mem .eql (u8 , subcmd , "attach" ) or std .mem .eql (u8 , subcmd , "a" )) {
446- if (filtered_args .items .len > 1 ) {
447- return attachSession (allocator , & cfg , filtered_args .items [1 ]);
478+ const name_arg : ? []const u8 = if (filtered_args .len > 1 )
479+ filtered_args [1 ]
480+ else
481+ session_name ;
482+ if (name_arg ) | n | {
483+ return attachSession (allocator , & cfg , n );
448484 }
449485 return attachAuto (allocator , & cfg );
450486 }
@@ -459,8 +495,12 @@ pub fn main() !void {
459495
460496 // ── kill-session / k ──
461497 if (std .mem .eql (u8 , subcmd , "kill-session" ) or std .mem .eql (u8 , subcmd , "k" )) {
462- if (filtered_args .items .len > 1 ) {
463- return killSession (allocator , & cfg , filtered_args .items [1 ]);
498+ const name_arg : ? []const u8 = if (filtered_args .len > 1 )
499+ filtered_args [1 ]
500+ else
501+ session_name ;
502+ if (name_arg ) | n | {
503+ return killSession (allocator , & cfg , n );
464504 }
465505 std .debug .print ("Usage: zplit kill-session <name>\n " , .{});
466506 return ;
@@ -553,3 +593,32 @@ test "findNextSessionName returns 0 when no sockets exist" {
553593
554594 try std .testing .expectEqualStrings ("0" , name );
555595}
596+
597+ test "parseUserArgs strips -s and keeps subcommand args" {
598+ const allocator = std .testing .allocator ;
599+ const argv = [_ ][]const u8 { "/bin/zplit" , "attach" , "-s" , "foo" , "extra" };
600+ const p = try parseUserArgs (allocator , & argv );
601+ defer allocator .free (p .filtered );
602+
603+ try std .testing .expectEqualStrings ("foo" , p .session_name .? );
604+ try std .testing .expectEqual (@as (usize , 2 ), p .filtered .len );
605+ try std .testing .expectEqualStrings ("attach" , p .filtered [0 ]);
606+ try std .testing .expectEqualStrings ("extra" , p .filtered [1 ]);
607+ }
608+
609+ test "parseUserArgs attach -s name has positional name for attach" {
610+ const allocator = std .testing .allocator ;
611+ const argv = [_ ][]const u8 { "zplit" , "attach" , "-s" , "mysession" };
612+ const p = try parseUserArgs (allocator , & argv );
613+ defer allocator .free (p .filtered );
614+
615+ try std .testing .expectEqualStrings ("mysession" , p .session_name .? );
616+ try std .testing .expectEqual (@as (usize , 1 ), p .filtered .len );
617+ try std .testing .expectEqualStrings ("attach" , p .filtered [0 ]);
618+ }
619+
620+ test "parseUserArgs trailing -s without value errors" {
621+ const allocator = std .testing .allocator ;
622+ const argv = [_ ][]const u8 { "zplit" , "list" , "-s" };
623+ try std .testing .expectError (error .MissingSessionNameAfterS , parseUserArgs (allocator , & argv ));
624+ }
0 commit comments