@@ -300,14 +300,30 @@ fn usage_msg<S: AsRef<str>>(msg: S) -> ! {
300300// Arg parsing
301301
302302/// Parses user arguments into an Args structure.
303- fn parse_args ( args : env :: Args ) -> ( Args , Subcommand ) {
303+ fn parse_args ( args : impl Iterator < Item = String > ) -> ( Args , Subcommand ) {
304304 let mut global_args = Args :: default ( ) ;
305305 let mut subcommand = None ;
306306 let mut subcommand_args = Vec :: new ( ) ;
307307
308308 let mut iter = args. into_iter ( ) . skip ( 1 ) ;
309309 while let Some ( arg) = iter. next ( ) {
310310 match arg. as_ref ( ) {
311+ // Handle separator - only valid after exec subcommand is set
312+ "--" => {
313+ if subcommand. is_none ( ) {
314+ usage_msg ( "'--' separator requires a subcommand to be specified first" ) ;
315+ }
316+ // Only do special handling for exec subcommand
317+ if subcommand. as_deref ( ) == Some ( "exec" ) {
318+ // For exec, collect all remaining args and stop processing
319+ subcommand_args. extend ( iter) ;
320+ break ;
321+ } else {
322+ // For other subcommands, treat -- as a regular argument
323+ subcommand_args. push ( arg) ;
324+ }
325+ }
326+
311327 "-h" | "--help" => usage ( ) ,
312328
313329 // Global args
@@ -1160,3 +1176,127 @@ mod error {
11601176 }
11611177}
11621178type Result < T > = std:: result:: Result < T , error:: Error > ;
1179+ #[ cfg( test) ]
1180+ mod tests {
1181+ use super :: * ;
1182+ use test_case:: test_case;
1183+
1184+ // Helper macro for creating global Args with consistent formatting
1185+ macro_rules! global_args {
1186+ // Create Args with specified log_level and socket_path
1187+ ( $log_level: expr, $socket_path: expr) => {
1188+ Args {
1189+ log_level: $log_level,
1190+ socket_path: $socket_path. to_string( ) ,
1191+ }
1192+ } ;
1193+ // Create Args with just log_level (using default socket_path)
1194+ ( $log_level: expr) => {
1195+ Args {
1196+ log_level: $log_level,
1197+ socket_path: constants:: API_SOCKET . to_string( ) ,
1198+ }
1199+ } ;
1200+ }
1201+
1202+ // Helper macro for creating exec subcommand
1203+ macro_rules! exec_cmd {
1204+ // For Exec subcommand with target, command args, and tty option
1205+ ( $target: expr, [ $( $arg: expr) ,* ] , $tty: expr) => {
1206+ Subcommand :: Exec ( ExecArgs {
1207+ target: $target. to_string( ) ,
1208+ command: vec![ $( $arg. into( ) ) ,* ] ,
1209+ tty: $tty,
1210+ } )
1211+ } ;
1212+ }
1213+
1214+ fn parse_command_line ( cmd_str : & str ) -> ( Args , Subcommand ) {
1215+ let args: Vec < String > = cmd_str. split_whitespace ( ) . map ( |s| s. to_string ( ) ) . collect ( ) ;
1216+ parse_args ( args. into_iter ( ) )
1217+ }
1218+
1219+ #[ test_case( "apiclient exec admin -- sheltie df -h" ,
1220+ global_args!( LevelFilter :: Info ) ,
1221+ exec_cmd!( "admin" , [ "sheltie" , "df" , "-h" ] , None ) ;
1222+ "exec with separator" ) ]
1223+ #[ test_case( "apiclient exec admin sheltie df" ,
1224+ global_args!( LevelFilter :: Info ) ,
1225+ exec_cmd!( "admin" , [ "sheltie" , "df" ] , None ) ;
1226+ "exec without separator" ) ]
1227+ #[ test_case( "apiclient exec -t admin -- sheltie df -h" ,
1228+ global_args!( LevelFilter :: Info ) ,
1229+ exec_cmd!( "admin" , [ "sheltie" , "df" , "-h" ] , Some ( true ) ) ;
1230+ "exec with tty and separator" ) ]
1231+ #[ test_case( "apiclient exec -T admin sheltie df" ,
1232+ global_args!( LevelFilter :: Info ) ,
1233+ exec_cmd!( "admin" , [ "sheltie" , "df" ] , Some ( false ) ) ;
1234+ "exec with no-tty" ) ]
1235+ fn test_exec_parsing ( cmd_str : & str , expected_args : Args , expected_subcommand : Subcommand ) {
1236+ let ( global_args, subcommand) = parse_command_line ( cmd_str) ;
1237+
1238+ // Check the global arguments match what we expect
1239+ assert_eq ! ( global_args. log_level, expected_args. log_level) ;
1240+ assert_eq ! ( global_args. socket_path, expected_args. socket_path) ;
1241+
1242+ // Check the subcommand matches what we expect
1243+ match ( & subcommand, & expected_subcommand) {
1244+ ( Subcommand :: Exec ( actual) , Subcommand :: Exec ( expected) ) => {
1245+ assert_eq ! ( actual. target, expected. target) ;
1246+ assert_eq ! ( actual. tty, expected. tty) ;
1247+ assert_eq ! ( actual. command, expected. command) ;
1248+ }
1249+ _ => panic ! (
1250+ "Expected Exec subcommand: {:?}, got: {:?}" ,
1251+ expected_subcommand, subcommand
1252+ ) ,
1253+ }
1254+ }
1255+
1256+ #[ test_case( "apiclient -v exec admin -- sheltie df -h" ,
1257+ global_args!( LevelFilter :: Debug ) ,
1258+ exec_cmd!( "admin" , [ "sheltie" , "df" , "-h" ] , None ) ;
1259+ "verbose flag with exec as global arg" ) ]
1260+ #[ test_case( "apiclient --log-level error exec admin sheltie df" ,
1261+ global_args!( LevelFilter :: Error ) ,
1262+ exec_cmd!( "admin" , [ "sheltie" , "df" ] , None ) ;
1263+ "log level with exec" ) ]
1264+ #[ test_case( "apiclient exec admin -- sheltie -v df -h" ,
1265+ global_args!( LevelFilter :: Info ) ,
1266+ exec_cmd!( "admin" , [ "sheltie" , "-v" , "df" , "-h" ] , None ) ;
1267+ "verbose flag after separator as command arg" ) ]
1268+ #[ test_case( "apiclient -v exec admin -- sheltie -v df -h" ,
1269+ global_args!( LevelFilter :: Debug ) ,
1270+ exec_cmd!( "admin" , [ "sheltie" , "-v" , "df" , "-h" ] , None ) ;
1271+ "verbose flag in both global and command positions" ) ]
1272+ fn test_args_parsing_with_separator (
1273+ cmd_str : & str ,
1274+ expected_args : Args ,
1275+ expected_subcommand : Subcommand ,
1276+ ) {
1277+ let ( global_args, subcommand) = parse_command_line ( cmd_str) ;
1278+
1279+ // Check the global arguments match what we expect
1280+ assert_eq ! (
1281+ global_args. log_level, expected_args. log_level,
1282+ "Global log level should match"
1283+ ) ;
1284+ assert_eq ! (
1285+ global_args. socket_path, expected_args. socket_path,
1286+ "Socket path should match"
1287+ ) ;
1288+
1289+ // Check the subcommand matches what we expect
1290+ match ( & subcommand, & expected_subcommand) {
1291+ ( Subcommand :: Exec ( actual) , Subcommand :: Exec ( expected) ) => {
1292+ assert_eq ! ( actual. target, expected. target, "Target should match" ) ;
1293+ assert_eq ! ( actual. tty, expected. tty, "TTY setting should match" ) ;
1294+ assert_eq ! ( actual. command, expected. command, "Command should match" ) ;
1295+ }
1296+ _ => panic ! (
1297+ "Expected Exec subcommand: {:?}, got: {:?}" ,
1298+ expected_subcommand, subcommand
1299+ ) ,
1300+ }
1301+ }
1302+ }
0 commit comments