noapi_functions/
struct_extractor.rs

1use regex::Regex;
2use std::{fs, path::PathBuf, process::Command};
3
4use crate::build_functions::rust_to_typescript_type;
5
6fn cargo_doc_path() -> PathBuf {
7    let output = Command::new("cargo")
8        .arg("metadata")
9        .arg("--format-version=1")
10        .output()
11        .expect("Failed to execute cargo metadata");
12
13    let metadata = serde_json::from_slice::<serde_json::Value>(&output.stdout)
14        .expect("Failed to parse cargo metadata");
15
16    let workspace_root = metadata["workspace_root"]
17        .as_str()
18        .expect("Failed to get workspace root");
19
20    PathBuf::from(workspace_root).join("target/doc")
21}
22
23pub fn extract_struct_def(
24    struct_name: String,
25    content: &str,
26    project_name: &str,
27) -> Option<String> {
28    let use_statements = extract_use_statements(&content);
29    let parts: Vec<&str> = struct_name.split("::").collect();
30    let mut use_statement = String::new();
31    let struct_name: String;
32
33    if parts.len() > 1 {
34        struct_name = parts.last().unwrap().to_string();
35        let other_parts = parts[..parts.len() - 1].join("::");
36        use_statements.iter().for_each(|statement| {
37            if statement.contains(parts.first().unwrap()) {
38                use_statement = format!("{}::{}", statement, other_parts);
39            }
40        });
41        if use_statement.is_empty() {
42            use_statement = other_parts;
43        }
44    } else {
45        struct_name = parts.first().unwrap().to_string();
46        use_statements.iter().for_each(|statement| {
47            if statement.contains(parts.first().unwrap()) {
48                use_statement = statement.clone();
49            }
50        });
51    }
52
53    let parts: Vec<&str> = use_statement.split("::").collect();
54    let mut struct_path: String;
55    if parts.len() > 1 {
56        struct_path = parts[..parts.len() - 1].join("/");
57    } else {
58        struct_path = parts.join("/");
59    }
60
61    struct_path = struct_path.replace("crate", project_name);
62
63    let doc_path = cargo_doc_path().join(format!("{}/struct.{}.html", struct_path, struct_name));
64
65    if !doc_path.exists() {
66        println!("Documentation file not found: {:?}", doc_path);
67        return None;
68    }
69
70    let html_content = fs::read_to_string(&doc_path).ok()?;
71
72    let mut ts_content = String::new();
73
74    let struct_regex = Regex::new(r"struct\s+(\w+)\s*\{([^}]*)\}").expect("Invalid struct regex");
75    for cap in struct_regex.captures_iter(&html_content) {
76        let _struct_name = &cap[1];
77        let fields = &cap[2];
78
79        let ts_fields: Vec<String> = fields
80            .lines()
81            .filter(|line| !line.trim().is_empty())
82            .map(|line| {
83                let re = Regex::new(r"<[^>]*>").unwrap();
84                let html_text = re.replace_all(line, "").to_string();
85                let parts: Vec<&str> = html_text.trim().split(':').collect();
86                if parts.len() == 2 {
87                    let field_name = parts[0].trim();
88
89                    let field_type = rust_to_typescript_type(
90                        parts[1].trim().trim_end_matches(','),
91                        &vec![],
92                        content,
93                        project_name,
94                    );
95
96                    format!("   {}: {};", field_name, field_type)
97                } else {
98                    String::new()
99                }
100            })
101            .collect();
102
103        ts_content.push_str(&format!(
104            "{{\n{}\n}}",
105            ts_fields.join("\n").replace("pub", "")
106        ));
107    }
108
109    Some(ts_content)
110}
111
112pub fn extract_use_statements(content: &str) -> Vec<String> {
113    let mut use_statements = Vec::new();
114
115    for line in content.lines() {
116        let trimmed = line.trim();
117        if trimmed.starts_with("use ") {
118            let statement = trimmed.strip_prefix("use ").unwrap().trim_end_matches(';');
119            let expanded_statements = expand_use_statement(statement);
120            use_statements.extend(expanded_statements);
121        }
122    }
123
124    use_statements
125}
126
127fn expand_use_statement(statement: &str) -> Vec<String> {
128    let mut results = Vec::new();
129
130    if let Some((prefix, grouped)) = statement.split_once("::{") {
131        let grouped = grouped.trim_end_matches('}');
132        let imports = split_imports(grouped);
133        for import in imports {
134            results.push(format!("use {}::{};", prefix, import));
135        }
136    } else if let Some((prefix, grouped)) = statement.split_once('{') {
137        let prefix = prefix.trim_end();
138        let grouped = grouped.trim_end_matches('}');
139        let imports = split_imports(grouped);
140        for import in imports {
141            results.push(format!("use {}{};", prefix, import));
142        }
143    } else {
144        results.push(format!("use {};", statement));
145    }
146
147    results
148}
149
150fn split_imports(grouped: &str) -> Vec<String> {
151    grouped.split(',').map(|s| s.trim().to_string()).collect()
152}