@@ -18,6 +18,18 @@ use syn::{
1818/// Requires Rust types to be annotated with `#[derive(Encode)]` and/or
1919/// `#[derive(Decode]` macros.
2020pub fn generate ( src_path : & str , output_dst : & str ) -> io:: Result < ( ) > {
21+ generate_with_target ( src_path, output_dst, None )
22+ }
23+
24+ /// Generate a TypeScript file at `output_dst` from a directory of Rust source
25+ /// files at `src_path`, filtering by target (e.g., "client" or "server").
26+ ///
27+ /// Items with `skip = "target"` will be excluded from generation.
28+ pub fn generate_with_target (
29+ src_path : & str ,
30+ output_dst : & str ,
31+ target : Option < & str > ,
32+ ) -> io:: Result < ( ) > {
2133 let mut files = Vec :: new ( ) ;
2234
2335 fn visit_dirs ( dir : & std:: path:: Path , files : & mut Vec < String > ) -> io:: Result < ( ) > {
@@ -38,7 +50,7 @@ pub fn generate(src_path: &str, output_dst: &str) -> io::Result<()> {
3850 visit_dirs ( std:: path:: Path :: new ( src_path) , & mut files) ?;
3951
4052 let mut output = String :: new ( ) ;
41- generate_output_string ( files, & mut output) ?;
53+ generate_output_string_with_target ( files, & mut output, target ) ?;
4254 write_typescript_file ( output_dst, & output) ?;
4355
4456 Ok ( ( ) )
@@ -134,7 +146,11 @@ fn get_items_implementing_encode(items: Vec<Item>) -> (Vec<ItemStruct>, Vec<Item
134146 ( structs, enums)
135147}
136148
137- fn generate_output_string ( input : Vec < String > , output : & mut String ) -> Result < ( ) , std:: io:: Error > {
149+ fn generate_output_string_with_target (
150+ input : Vec < String > ,
151+ output : & mut String ,
152+ target : Option < & str > ,
153+ ) -> Result < ( ) , std:: io:: Error > {
138154 output. push_str ( "/* AUTOGENERATED BUFFERFISH FILE, DO NOT EDIT */\n " ) ;
139155 output. push_str ( "import { Bufferfish } from 'bufferfish'\n " ) ;
140156
@@ -148,16 +164,38 @@ fn generate_output_string(input: Vec<String>, output: &mut String) -> Result<(),
148164 let ( structs, enums) = get_items_implementing_encode ( items) ;
149165
150166 for item in & structs {
151- if let Some ( packet_id) = get_packet_id ( & item. attrs )
167+ let ( packet_id, skip_attr) = get_packet_attributes ( & item. attrs ) ;
168+ if should_skip_for_target ( & skip_attr, target) {
169+ continue ;
170+ }
171+
172+ if let Some ( packet_id) = packet_id
152173 && let Some ( enum_name) = extract_enum_name_from_packet_id ( & packet_id)
153174 && !packet_id_enum_names. contains ( & enum_name)
154175 {
155176 packet_id_enum_names. push ( enum_name) ;
156177 }
157178 }
158179
159- all_structs. extend ( structs) ;
160- all_enums. extend ( enums) ;
180+ // Filter structs and enums based on target
181+ let filtered_structs: Vec < _ > = structs
182+ . into_iter ( )
183+ . filter ( |item| {
184+ let ( _, skip_attr) = get_packet_attributes ( & item. attrs ) ;
185+ !should_skip_for_target ( & skip_attr, target)
186+ } )
187+ . collect ( ) ;
188+
189+ let filtered_enums: Vec < _ > = enums
190+ . into_iter ( )
191+ . filter ( |item| {
192+ let skip_attr = get_enum_skip_attribute ( & item. attrs ) ;
193+ !should_skip_for_target ( & skip_attr, target)
194+ } )
195+ . collect ( ) ;
196+
197+ all_structs. extend ( filtered_structs) ;
198+ all_enums. extend ( filtered_enums) ;
161199 }
162200
163201 for item in & all_enums {
@@ -184,7 +222,44 @@ fn generate_output_string(input: Vec<String>, output: &mut String) -> Result<(),
184222 Ok ( ( ) )
185223}
186224
187- fn generate_typescript_enum_encoders ( item : ItemEnum , output : & mut String ) {
225+ fn get_packet_attributes ( attrs : & [ Attribute ] ) -> ( Option < String > , Option < String > ) {
226+ for attr in attrs {
227+ if attr. path ( ) . is_ident ( "bufferfish" )
228+ && let Meta :: List ( list) = & attr. meta
229+ {
230+ let tokens = list. tokens . to_string ( ) ;
231+ let parts: Vec < & str > = tokens. split ( ',' ) . map ( |s| s. trim ( ) ) . collect ( ) ;
232+
233+ let mut packet_id = None ;
234+ let mut skip_target = None ;
235+
236+ for part in parts {
237+ if part. contains ( "skip" ) && part. contains ( '=' ) {
238+ // Parse skip = "target"
239+ if let Some ( target) = part. split ( '=' ) . nth ( 1 ) {
240+ skip_target = Some ( target. trim ( ) . trim_matches ( '"' ) . to_string ( ) ) ;
241+ }
242+ } else if !part. contains ( '=' ) {
243+ // This is the packet ID
244+ packet_id = Some ( part. replace ( " " , "" ) . replace ( "::" , "." ) ) ;
245+ }
246+ }
247+
248+ return ( packet_id, skip_target) ;
249+ }
250+ }
251+ ( None , None )
252+ }
253+
254+ fn should_skip_for_target ( skip_attr : & Option < String > , target : Option < & str > ) -> bool {
255+ if let ( Some ( skip_target) , Some ( current_target) ) = ( skip_attr, target) {
256+ skip_target == current_target
257+ } else {
258+ false
259+ }
260+ }
261+
262+ fn generate_typescript_packet_id_encoder ( item : ItemEnum , output : & mut String ) {
188263 let enum_name = item. ident . to_string ( ) ;
189264 let repr_type = get_repr_type ( & item. attrs ) . unwrap_or ( "u8" . to_string ( ) ) ;
190265
@@ -212,29 +287,7 @@ fn generate_typescript_enum_encoders(item: ItemEnum, output: &mut String) {
212287 output. push_str ( "}\n " ) ;
213288}
214289
215- /// Extract the PacketID from struct attributes and format it for TypeScript
216- fn get_packet_id ( attrs : & [ Attribute ] ) -> Option < String > {
217- for attr in attrs {
218- if attr. path ( ) . is_ident ( "bufferfish" )
219- && let Meta :: List ( list) = & attr. meta
220- {
221- let tokens = list. tokens . to_string ( ) ;
222- let cleaned = tokens. trim ( ) . replace ( " " , "" ) . replace ( "::" , "." ) ;
223-
224- return Some ( cleaned) ;
225- }
226- }
227- None
228- }
229-
230- /// Extract the enum name from a packet ID reference like "EnumName.Variant"
231- fn extract_enum_name_from_packet_id ( id : & str ) -> Option < String > {
232- id. split ( '.' ) . next ( ) . map ( |s| s. to_string ( ) )
233- }
234-
235- /// Generate a TypeScript encoder function for a specific packet ID enum
236- /// This is used specifically for enum types that are used as packet IDs
237- fn generate_typescript_packet_id_encoder ( item : ItemEnum , output : & mut String ) {
290+ fn generate_typescript_enum_encoders ( item : ItemEnum , output : & mut String ) {
238291 let enum_name = item. ident . to_string ( ) ;
239292 let repr_type = get_repr_type ( & item. attrs ) . unwrap_or ( "u8" . to_string ( ) ) ;
240293
@@ -268,7 +321,7 @@ fn generate_typescript_struct_encoders(
268321 packet_id_enums : & [ String ] ,
269322) {
270323 let struct_name = item. ident . to_string ( ) ;
271- let packet_id = get_packet_id ( & item. attrs ) ;
324+ let ( packet_id, _ ) = get_packet_attributes ( & item. attrs ) ;
272325
273326 match & item. fields {
274327 Fields :: Named ( fields_named) => {
@@ -687,6 +740,28 @@ fn get_repr_type(attrs: &[Attribute]) -> Option<String> {
687740 None
688741}
689742
743+ /// Extract the enum name from a packet ID reference like "EnumName.Variant"
744+ fn extract_enum_name_from_packet_id ( id : & str ) -> Option < String > {
745+ id. split ( '.' ) . next ( ) . map ( |s| s. to_string ( ) )
746+ }
747+
748+ fn get_enum_skip_attribute ( attrs : & [ Attribute ] ) -> Option < String > {
749+ for attr in attrs {
750+ if attr. path ( ) . is_ident ( "bufferfish" )
751+ && let Meta :: List ( list) = & attr. meta
752+ {
753+ let tokens = list. tokens . to_string ( ) ;
754+ if tokens. contains ( "skip" )
755+ && tokens. contains ( '=' )
756+ && let Some ( target) = tokens. split ( '=' ) . nth ( 1 )
757+ {
758+ return Some ( target. trim ( ) . trim_matches ( '"' ) . to_string ( ) ) ;
759+ }
760+ }
761+ }
762+ None
763+ }
764+
690765#[ cfg( test) ]
691766mod tests {
692767 use super :: * ;
@@ -784,7 +859,8 @@ export function encodeUnknownPacket(bf: Bufferfish, value: UnknownPacket): void
784859 let mut packet_id_enum_names = Vec :: new ( ) ;
785860
786861 for item in & structs {
787- if let Some ( packet_id) = get_packet_id ( & item. attrs )
862+ let ( packet_id, _) = get_packet_attributes ( & item. attrs ) ;
863+ if let Some ( packet_id) = packet_id
788864 && let Some ( enum_name) = extract_enum_name_from_packet_id ( & packet_id)
789865 && !packet_id_enum_names. contains ( & enum_name)
790866 {
@@ -820,4 +896,52 @@ export function encodeUnknownPacket(bf: Bufferfish, value: UnknownPacket): void
820896 panic ! ( "Output does not match expected output" ) ;
821897 }
822898 }
899+
900+ #[ test]
901+ fn test_skip_attribute_parsing ( ) {
902+ let test_file = r#"
903+ #[derive(Encode)]
904+ #[bufferfish(PacketId::Join, skip = "client")]
905+ pub struct ClientOnlyPacket {
906+ pub data: String,
907+ }
908+
909+ #[derive(Encode)]
910+ #[bufferfish(PacketId::Leave, skip = "server")]
911+ pub struct ServerOnlyPacket {
912+ pub data: String,
913+ }
914+
915+ #[derive(Encode)]
916+ #[bufferfish(PacketId::Both)]
917+ pub struct BothPacket {
918+ pub data: String,
919+ }
920+ "# ;
921+
922+ let items = syn:: parse_file ( test_file)
923+ . unwrap_or_else ( |e| panic ! ( "Failed to parse: {e}" ) )
924+ . items ;
925+ let ( structs, _) = get_items_implementing_encode ( items) ;
926+
927+ let client_structs: Vec < _ > = structs
928+ . iter ( )
929+ . filter ( |item| {
930+ let ( _, skip_attr) = get_packet_attributes ( & item. attrs ) ;
931+ !should_skip_for_target ( & skip_attr, Some ( "client" ) )
932+ } )
933+ . collect ( ) ;
934+
935+ assert_eq ! ( client_structs. len( ) , 2 ) ;
936+
937+ let server_structs: Vec < _ > = structs
938+ . iter ( )
939+ . filter ( |item| {
940+ let ( _, skip_attr) = get_packet_attributes ( & item. attrs ) ;
941+ !should_skip_for_target ( & skip_attr, Some ( "server" ) )
942+ } )
943+ . collect ( ) ;
944+
945+ assert_eq ! ( server_structs. len( ) , 2 ) ;
946+ }
823947}
0 commit comments