A better Rust ATProto crate
1use jacquard_lexicon::codegen::CodeGenerator; 2use jacquard_lexicon::corpus::LexiconCorpus; 3use prettyplease; 4use std::collections::BTreeMap; 5use std::fs; 6use std::path::Path; 7 8fn main() -> Result<(), Box<dyn std::error::Error>> { 9 let lexicons_path = "lexicons/atproto"; 10 let output_path = "crates/jacquard-api/src"; 11 let root_module = "crate"; 12 13 println!("Loading lexicons from {}...", lexicons_path); 14 let corpus = LexiconCorpus::load_from_dir(lexicons_path)?; 15 println!("Loaded {} lexicons", corpus.len()); 16 17 println!("Generating code..."); 18 let generator = CodeGenerator::new(&corpus, root_module); 19 20 // Group by module 21 let mut modules: BTreeMap<String, Vec<(String, String)>> = BTreeMap::new(); 22 23 for (nsid, doc) in corpus.iter() { 24 let nsid_str = nsid.as_str(); 25 26 // Get module path: app.bsky.feed.post -> app_bsky/feed 27 let parts: Vec<&str> = nsid_str.split('.').collect(); 28 let module_path = if parts.len() >= 3 { 29 let first_two = format!("{}_{}", parts[0], parts[1]); 30 if parts.len() > 3 { 31 let middle: Vec<&str> = parts[2..parts.len() - 1].iter().copied().collect(); 32 format!("{}/{}", first_two, middle.join("/")) 33 } else { 34 first_two 35 } 36 } else { 37 parts.join("_") 38 }; 39 40 let file_name = parts.last().unwrap().to_string(); 41 42 for (def_name, def) in &doc.defs { 43 match generator.generate_def(nsid_str, def_name, def) { 44 Ok(tokens) => { 45 let code = prettyplease::unparse(&syn::parse_file(&tokens.to_string())?); 46 modules 47 .entry(format!("{}/{}.rs", module_path, file_name)) 48 .or_default() 49 .push((def_name.to_string(), code)); 50 } 51 Err(e) => { 52 eprintln!("Error generating {}.{}: {:?}", nsid_str, def_name, e); 53 } 54 } 55 } 56 } 57 58 // Write files 59 for (file_path, defs) in modules { 60 let full_path = Path::new(output_path).join(&file_path); 61 62 // Create parent directory 63 if let Some(parent) = full_path.parent() { 64 fs::create_dir_all(parent)?; 65 } 66 67 let content = defs.iter().map(|(_, code)| code.as_str()).collect::<Vec<_>>().join("\n"); 68 fs::write(&full_path, content)?; 69 println!("Wrote {}", file_path); 70 } 71 72 // Generate mod.rs files 73 println!("Generating mod.rs files..."); 74 generate_mod_files(Path::new(output_path))?; 75 76 println!("Done!"); 77 Ok(()) 78} 79 80fn generate_mod_files(root: &Path) -> Result<(), Box<dyn std::error::Error>> { 81 // Find all directories 82 for entry in fs::read_dir(root)? { 83 let entry = entry?; 84 let path = entry.path(); 85 86 if path.is_dir() { 87 let dir_name = path.file_name().unwrap().to_str().unwrap(); 88 89 // Recursively generate for subdirectories 90 generate_mod_files(&path)?; 91 92 // Generate mod.rs for this directory 93 let mut mods = Vec::new(); 94 for sub_entry in fs::read_dir(&path)? { 95 let sub_entry = sub_entry?; 96 let sub_path = sub_entry.path(); 97 98 if sub_path.is_file() { 99 if let Some(name) = sub_path.file_stem() { 100 let name_str = name.to_str().unwrap(); 101 if name_str != "mod" { 102 mods.push(format!("pub mod {};", name_str)); 103 } 104 } 105 } else if sub_path.is_dir() { 106 if let Some(name) = sub_path.file_name() { 107 mods.push(format!("pub mod {};", name.to_str().unwrap())); 108 } 109 } 110 } 111 112 if !mods.is_empty() { 113 let mod_content = mods.join("\n") + "\n"; 114 fs::write(path.join("mod.rs"), mod_content)?; 115 } 116 } 117 } 118 119 Ok(()) 120}