11use proc_macro:: TokenStream ;
22use quote:: quote;
3- use syn:: { Expr , Ident , ItemFn , parse_macro_input, parse_quote} ;
3+ use syn:: {
4+ Expr , Ident , ItemFn , Result ,
5+ parse:: { Parse , ParseStream } ,
6+ parse_macro_input, parse_quote,
7+ } ;
8+
9+ struct CommandeerArgs {
10+ mode : Ident ,
11+ commands : Vec < String > ,
12+ }
13+
14+ const RECORD : & str = "Record" ;
15+ const REPLAY : & str = "Replay" ;
16+
17+ impl Parse for CommandeerArgs {
18+ fn parse ( input : ParseStream ) -> Result < Self > {
19+ let mut commands = vec ! [ ] ;
20+
21+ let ident: Ident = input. parse ( ) ?;
22+
23+ let mode = match ident. to_string ( ) . as_str ( ) {
24+ x if [ RECORD , REPLAY ] . contains ( & x) => Ident :: new ( x, proc_macro2:: Span :: call_site ( ) ) ,
25+ _ => {
26+ return Err ( syn:: Error :: new (
27+ ident. span ( ) ,
28+ format ! ( "Expected '{RECORD}' or '{REPLAY}'" ) ,
29+ ) ) ;
30+ }
31+ } ;
32+
33+ input. parse :: < syn:: Token ![ , ] > ( ) ?;
34+
35+ while !input. is_empty ( ) {
36+ if input. peek ( syn:: LitStr ) {
37+ let lit: syn:: LitStr = input. parse ( ) ?;
38+
39+ commands. push ( lit. value ( ) ) ;
40+ } else {
41+ return Err ( input. error ( "Expected a command string" ) ) ;
42+ }
43+
44+ if input. peek ( syn:: Token ![ , ] ) {
45+ input. parse :: < syn:: Token ![ , ] > ( ) ?;
46+ }
47+ }
48+
49+ if commands. is_empty ( ) {
50+ return Err ( syn:: Error :: new (
51+ input. span ( ) ,
52+ "Expected at least one command string" ,
53+ ) ) ;
54+ }
55+
56+ Ok ( CommandeerArgs { mode, commands } )
57+ }
58+ }
459
560/// Procedural macro for setting up commandeer test environment
661///
@@ -9,35 +64,17 @@ use syn::{Expr, Ident, ItemFn, parse_macro_input, parse_quote};
964/// This expands to code that creates a Commandeer instance and mocks the specified commands
1065#[ proc_macro_attribute]
1166pub fn commandeer ( args : TokenStream , input : TokenStream ) -> TokenStream {
67+ let args = parse_macro_input ! ( args as CommandeerArgs ) ;
1268 let mut input_fn = parse_macro_input ! ( input as ItemFn ) ;
1369
14- // Parse arguments manually since AttributeArgs was removed in syn 2.0
15- let args_str = args. to_string ( ) ;
1670 let fn_name = & input_fn. sig . ident ;
1771
18- // Generate test file name from function name
1972 let test_file_name = format ! ( "cmds_{fn_name}.json" ) ;
2073
21- // Simple parsing - look for Record/Replay and string literals
22- let mut mode_str = "Record" ; // default
23- let mut commands = vec ! [ ] ;
24-
2574 // Split by commas and parse each part
26- for part in args_str. split ( ',' ) {
27- let part = part. trim ( ) ;
28- if part == "Record" || part == "Replay" {
29- mode_str = part;
30- } else if part. starts_with ( '"' ) && part. ends_with ( '"' ) {
31- // Remove quotes and add to commands
32- let cmd = & part[ 1 ..part. len ( ) - 1 ] ;
33- commands. push ( cmd. to_string ( ) ) ;
34- }
35- }
36-
37- let mode_ident = Ident :: new ( mode_str, proc_macro2:: Span :: call_site ( ) ) ;
3875
39- // Generate mock_command calls
40- let mock_commands : Vec < Expr > = commands
76+ let mock_commands : Vec < Expr > = args
77+ . commands
4178 . iter ( )
4279 . map ( |cmd| {
4380 parse_quote ! {
@@ -46,9 +83,11 @@ pub fn commandeer(args: TokenStream, input: TokenStream) -> TokenStream {
4683 } )
4784 . collect ( ) ;
4885
86+ let mode = args. mode ;
87+
4988 // Create the setup statements
5089 let setup_stmts: Vec < syn:: Stmt > = vec ! [ parse_quote! {
51- let commandeer = commandeer_test:: Commandeer :: new( #test_file_name, commandeer_test:: Mode :: #mode_ident ) ;
90+ let commandeer = commandeer_test:: Commandeer :: new( #test_file_name, commandeer_test:: Mode :: #mode ) ;
5291 } ] ;
5392
5493 let mock_stmts: Vec < syn:: Stmt > = mock_commands
0 commit comments