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}