noapi_functions/
struct_extractor.rs1use 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}