Skip to content

Commit 914115c

Browse files
committed
Add skip attribute for filtering codegen
1 parent d37f361 commit 914115c

File tree

1 file changed

+155
-31
lines changed

1 file changed

+155
-31
lines changed

rust/bufferfish/src/compiler.rs

Lines changed: 155 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ use syn::{
1818
/// Requires Rust types to be annotated with `#[derive(Encode)]` and/or
1919
/// `#[derive(Decode]` macros.
2020
pub 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)]
691766
mod 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

Comments
 (0)