1use crate::types::{Event, Function};
11use dashmap::DashMap;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::sync::Arc;
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct EventField {
22 pub name: String,
23 #[serde(default)]
24 pub description: String,
25}
26
27#[derive(Debug, Clone)]
29pub struct MetadataSource {
30 pub extension: String,
31 pub functions_url: Option<String>,
32 pub enums_url: Option<String>,
33 pub events_url: Option<String>,
34}
35
36impl MetadataSource {
37 pub fn new(extension: impl Into<String>) -> Self {
39 Self {
40 extension: extension.into(),
41 functions_url: None,
42 enums_url: None,
43 events_url: None,
44 }
45 }
46
47 pub fn with_functions(mut self, url: impl Into<String>) -> Self {
49 self.functions_url = Some(url.into());
50 self
51 }
52
53 pub fn with_enums(mut self, url: impl Into<String>) -> Self {
55 self.enums_url = Some(url.into());
56 self
57 }
58
59 pub fn with_events(mut self, url: impl Into<String>) -> Self {
61 self.events_url = Some(url.into());
62 self
63 }
64}
65
66#[derive(Debug, Clone)]
71pub enum MetadataError {
72 NetworkError(String),
73 ParseError(String),
74 NotFound(String),
75 InvalidData(String),
76 CacheError(String),
77}
78
79impl std::fmt::Display for MetadataError {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 match self {
82 Self::NetworkError(e) => write!(f, "Network error: {}", e),
83 Self::ParseError(e) => write!(f, "Parse error: {}", e),
84 Self::NotFound(e) => write!(f, "Not found: {}", e),
85 Self::InvalidData(e) => write!(f, "Invalid data: {}", e),
86 Self::CacheError(e) => write!(f, "Cache error: {}", e),
87 }
88 }
89}
90
91impl std::error::Error for MetadataError {}
92
93pub type Result<T> = std::result::Result<T, MetadataError>;
94
95#[derive(Default)]
100struct TrieNode {
101 children: HashMap<char, Box<TrieNode>>,
102 value: Option<Arc<Function>>,
103}
104
105#[derive(Default)]
107pub struct FunctionTrie {
108 root: TrieNode,
109 count: usize,
110}
111
112impl FunctionTrie {
113 #[inline]
115 pub fn new() -> Self {
116 Self::default()
117 }
118
119 pub fn insert(&mut self, key: &str, func: Arc<Function>) {
121 let mut node = &mut self.root;
122
123 for ch in key.to_lowercase().chars() {
124 node = node
125 .children
126 .entry(ch)
127 .or_insert_with(|| Box::new(TrieNode::default()));
128 }
129
130 if node.value.is_none() {
131 self.count += 1;
132 }
133 node.value = Some(func);
134 }
135
136 pub fn get_exact(&self, key: &str) -> Option<Arc<Function>> {
138 let mut node = &self.root;
139
140 for ch in key.to_lowercase().chars() {
141 match node.children.get(&ch) {
142 Some(next) => node = next,
143 None => return None,
144 }
145 }
146
147 node.value.clone()
148 }
149
150 pub fn get_prefix(&self, text: &str) -> Option<(String, Arc<Function>)> {
161 let mut node = &self.root;
162 let mut last_match: Option<(String, Arc<Function>)> = None;
163 let mut matched = String::with_capacity(text.len());
164
165 for ch in text.to_lowercase().chars() {
166 match node.children.get(&ch) {
167 Some(next) => {
168 matched.push(ch);
169 node = next;
170 if let Some(func) = &node.value {
171 last_match = Some((matched.clone(), func.clone()));
172 }
173 }
174 None => break,
175 }
176 }
177
178 last_match
179 }
180
181 pub fn get_completions(&self, prefix: &str) -> Vec<Arc<Function>> {
183 let mut node = &self.root;
184
185 for ch in prefix.to_lowercase().chars() {
186 match node.children.get(&ch) {
187 Some(next) => node = next,
188 None => return Vec::new(),
189 }
190 }
191
192 let mut results = Vec::new();
193 self.collect_all(node, &mut results);
194 results
195 }
196
197 fn collect_all(&self, node: &TrieNode, results: &mut Vec<Arc<Function>>) {
198 if let Some(func) = &node.value {
199 results.push(func.clone());
200 }
201
202 for child in node.children.values() {
203 self.collect_all(child, results);
204 }
205 }
206
207 pub fn all_functions(&self) -> Vec<Arc<Function>> {
209 let mut results = Vec::with_capacity(self.count);
210 self.collect_all(&self.root, &mut results);
211 results
212 }
213
214 #[inline]
216 pub fn len(&self) -> usize {
217 self.count
218 }
219
220 #[inline]
222 pub fn is_empty(&self) -> bool {
223 self.count == 0
224 }
225
226 pub fn remove(&mut self, key: &str) -> bool {
228 let mut node = &mut self.root;
229
230 for ch in key.to_lowercase().chars() {
231 match node.children.get_mut(&ch) {
232 Some(next) => node = next,
233 None => return false,
234 }
235 }
236
237 if node.value.is_some() {
238 node.value = None;
239 self.count -= 1;
240 true
241 } else {
242 false
243 }
244 }
245
246 pub fn clear(&mut self) {
248 self.root = TrieNode::default();
249 self.count = 0;
250 }
251}
252
253pub struct Fetcher {
259 client: reqwest::Client,
260}
261
262impl Fetcher {
263 pub fn new() -> Self {
265 #[cfg(not(target_arch = "wasm32"))]
266 let client = reqwest::Client::builder()
267 .timeout(std::time::Duration::from_secs(30))
268 .build()
269 .unwrap_or_else(|_| reqwest::Client::new());
270
271 #[cfg(target_arch = "wasm32")]
272 let client = reqwest::Client::builder()
273 .build()
274 .unwrap_or_else(|_| reqwest::Client::new());
275
276 Self { client }
277 }
278
279 pub async fn fetch_json<T: serde::de::DeserializeOwned>(&self, url: &str) -> Result<T> {
281 let response =
282 self.client.get(url).send().await.map_err(|e| {
283 MetadataError::NetworkError(format!("Failed to fetch {}: {}", url, e))
284 })?;
285
286 let status = response.status();
287 if status == reqwest::StatusCode::NOT_FOUND {
288 return Err(MetadataError::NotFound(format!("URL not found: {}", url)));
289 }
290 if !status.is_success() {
291 return Err(MetadataError::NetworkError(format!(
292 "HTTP {}: {}",
293 status, url
294 )));
295 }
296
297 let text = response.text().await.map_err(|e| {
298 MetadataError::NetworkError(format!("Failed to read response from {}: {}", url, e))
299 })?;
300
301 serde_json::from_str(&text).map_err(|e| {
302 let preview: String = text.chars().take(200).collect();
303 MetadataError::ParseError(format!(
304 "Failed to parse JSON from {}: {}\nJSON preview: {}…",
305 url, e, preview
306 ))
307 })
308 }
309
310 pub async fn fetch_functions(&self, url: &str, extension: String) -> Result<Vec<Function>> {
313 let response =
314 self.client.get(url).send().await.map_err(|e| {
315 MetadataError::NetworkError(format!("Failed to fetch {}: {}", url, e))
316 })?;
317
318 let status = response.status();
319 if status == reqwest::StatusCode::NOT_FOUND {
320 return Err(MetadataError::NotFound(format!("URL not found: {}", url)));
321 }
322 if !status.is_success() {
323 return Err(MetadataError::NetworkError(format!(
324 "HTTP {}: {}",
325 status, url
326 )));
327 }
328
329 let text = response.text().await.map_err(|e| {
330 MetadataError::NetworkError(format!("Failed to read response from {}: {}", url, e))
331 })?;
332
333 let raw_items: Vec<serde_json::Value> = serde_json::from_str(&text).map_err(|e| {
334 let preview: String = text.chars().take(200).collect();
335 MetadataError::ParseError(format!(
336 "Failed to parse JSON array from {}: {}\nJSON preview: {}…",
337 url, e, preview
338 ))
339 })?;
340
341 let mut functions = Vec::with_capacity(raw_items.len());
342 for (i, raw) in raw_items.into_iter().enumerate() {
343 match serde_json::from_value::<Function>(raw) {
344 Ok(mut func) => {
345 func.extension = Some(extension.clone());
346 func.source_url = Some(url.to_string());
347 functions.push(func);
348 }
349 Err(e) => {
350 eprintln!("[forge-kit] Skipping function #{} from {}: {}", i, url, e);
351 }
352 }
353 }
354
355 Ok(functions)
356 }
357
358 pub async fn fetch_enums(&self, url: &str) -> Result<HashMap<String, Vec<String>>> {
360 self.fetch_json(url).await
361 }
362
363 pub async fn fetch_events(&self, url: &str) -> Result<Vec<Event>> {
365 self.fetch_json(url).await
366 }
367}
368
369impl Default for Fetcher {
370 fn default() -> Self {
371 Self::new()
372 }
373}
374
375pub struct MetadataManager {
381 trie: std::sync::RwLock<FunctionTrie>,
382 enums: DashMap<String, Vec<String>>,
383 events: DashMap<String, Event>,
384 sources: std::sync::RwLock<Vec<MetadataSource>>,
385 fetcher: Fetcher,
386 custom_function_names: DashMap<String, ()>,
387}
388
389impl MetadataManager {
390 pub fn new() -> Self {
392 Self {
393 trie: std::sync::RwLock::new(FunctionTrie::new()),
394 enums: DashMap::new(),
395 events: DashMap::new(),
396 sources: std::sync::RwLock::new(Vec::new()),
397 fetcher: Fetcher::new(),
398 custom_function_names: DashMap::new(),
399 }
400 }
401
402 pub fn add_source(&self, source: MetadataSource) {
404 self.sources.write().unwrap().push(source);
405 }
406
407 pub async fn fetch_all(&self) -> Result<FetchStats> {
409 let sources = self.sources.read().unwrap().clone();
410
411 let mut total_functions = 0;
412 let mut total_enums = 0;
413 let mut total_events = 0;
414 let mut errors = Vec::new();
415
416 for source in sources {
417 if let Some(url) = &source.functions_url {
418 match self
419 .fetcher
420 .fetch_functions(url, source.extension.clone())
421 .await
422 {
423 Ok(functions) => {
424 total_functions += functions.len();
425 self.add_functions(functions);
426 }
427 Err(MetadataError::NotFound(_)) => {}
428 Err(e) => {
429 errors.push(format!("Functions from {}: {}", source.extension, e));
430 }
431 }
432 }
433
434 if let Some(url) = &source.enums_url {
435 match self.fetcher.fetch_enums(url).await {
436 Ok(enums) => {
437 total_enums += enums.len();
438 for (name, values) in enums {
439 self.enums.insert(name, values);
440 }
441 }
442 Err(e) => {
443 if !matches!(e, MetadataError::NotFound(_)) {
444 errors.push(format!("Enums from {}: {}", source.extension, e));
445 }
446 }
447 }
448 }
449
450 if let Some(url) = &source.events_url {
451 match self.fetcher.fetch_events(url).await {
452 Ok(events) => {
453 total_events += events.len();
454 for event in events {
455 self.events.insert(event.name.clone(), event);
456 }
457 }
458 Err(e) => {
459 if !matches!(e, MetadataError::NotFound(_)) {
460 errors.push(format!("Events from {}: {}", source.extension, e));
461 }
462 }
463 }
464 }
465 }
466
467 Ok(FetchStats {
468 functions: total_functions,
469 enums: total_enums,
470 events: total_events,
471 errors,
472 })
473 }
474
475 fn add_functions(&self, functions: Vec<Function>) {
476 let mut trie = self.trie.write().unwrap();
477
478 for func in functions {
479 if self.custom_function_names.contains_key(&func.name) {
480 continue;
481 }
482 let arc_func = Arc::new(func.clone());
483 trie.insert(&func.name, arc_func.clone());
484
485 if let Some(aliases) = &func.aliases {
486 for alias in aliases {
487 let alias_name = if alias.starts_with('$') {
488 alias.clone()
489 } else {
490 format!("${}", alias)
491 };
492 let mut alias_func = (*arc_func).clone();
493 alias_func.name = alias_name.clone();
494 trie.insert(&alias_name, Arc::new(alias_func));
495 }
496 }
497 }
498 }
499
500 pub fn add_custom_functions_from_json(&self, json: &str) -> Result<usize> {
501 let raw_items: Vec<serde_json::Value> = serde_json::from_str(json).map_err(|e| {
502 MetadataError::ParseError(format!("Invalid custom-functions JSON: {}", e))
503 })?;
504
505 self.remove_custom_functions();
507
508 let mut count = 0;
509 let mut trie = self.trie.write().unwrap();
510
511 for (i, raw) in raw_items.into_iter().enumerate() {
512 match serde_json::from_value::<Function>(raw) {
513 Ok(mut func) => {
514 if !func.name.starts_with('$') {
516 func.name = format!("${}", func.name);
517 }
518
519 func.category = func.category.or(Some("custom".to_string()));
520
521 let arc_func = Arc::new(func.clone());
522
523 trie.insert(&func.name, arc_func.clone());
525 self.custom_function_names.insert(func.name.clone(), ());
526
527 count += 1;
528
529 if let Some(aliases) = &func.aliases {
531 for alias in aliases {
532 let alias_name = if alias.starts_with('$') {
533 alias.clone()
534 } else {
535 format!("${}", alias)
536 };
537
538 let mut alias_func = (*arc_func).clone();
539 alias_func.name = alias_name.clone();
540
541 trie.insert(&alias_name, Arc::new(alias_func));
542 self.custom_function_names.insert(alias_name.clone(), ());
543 }
544 }
545 }
546 Err(e) => {
547 eprintln!("[forge-kit] Skipping custom function #{}: {}", i, e);
548 }
549 }
550 }
551
552 Ok(count)
553 }
554
555 pub fn remove_custom_functions(&self) {
556 let mut trie = self.trie.write().unwrap();
557
558 for entry in self.custom_function_names.iter() {
559 trie.remove(entry.key());
560 }
561
562 self.custom_function_names.clear();
563 }
564
565 #[cfg(not(target_arch = "wasm32"))]
570 pub fn add_custom_functions_from_json_file(
571 &self,
572 path: impl AsRef<std::path::Path>,
573 ) -> Result<usize> {
574 let path = path.as_ref();
575 let json = std::fs::read_to_string(path).map_err(|e| {
576 MetadataError::CacheError(format!(
577 "Cannot read custom-functions file {}: {}",
578 path.display(),
579 e
580 ))
581 })?;
582 self.add_custom_functions_from_json(&json)
583 }
584
585 #[cfg(not(target_arch = "wasm32"))]
599 pub fn generate_custom_functions_json(
600 &self,
601 folder: impl AsRef<std::path::Path>,
602 ) -> Result<String> {
603 let folder = folder.as_ref();
604 if !folder.exists() || !folder.is_dir() {
605 return Err(MetadataError::InvalidData(format!(
606 "generate_custom_functions_json: {} is not a directory",
607 folder.display()
608 )));
609 }
610
611 let mut functions: Vec<Function> = Vec::new();
612 collect_functions_from_folder(folder, &mut functions)?;
613
614 serde_json::to_string_pretty(&functions).map_err(|e| {
615 MetadataError::ParseError(format!("Failed to serialize custom functions: {}", e))
616 })
617 }
618
619 #[cfg(not(target_arch = "wasm32"))]
624 pub fn generate_custom_functions_json_to_file(
625 &self,
626 folder: impl AsRef<std::path::Path>,
627 output_path: impl AsRef<std::path::Path>,
628 ) -> Result<usize> {
629 let json = self.generate_custom_functions_json(folder)?;
630
631 let entries: Vec<serde_json::Value> =
633 serde_json::from_str(&json).map_err(|e| MetadataError::ParseError(e.to_string()))?;
634 let count = entries.len();
635
636 let output_path = output_path.as_ref();
637 if let Some(parent) = output_path.parent() {
638 std::fs::create_dir_all(parent).map_err(|e| {
639 MetadataError::CacheError(format!("Cannot create directories: {}", e))
640 })?;
641 }
642 std::fs::write(output_path, json).map_err(|e| {
643 MetadataError::CacheError(format!("Cannot write to {}: {}", output_path.display(), e))
644 })?;
645
646 Ok(count)
647 }
648
649 #[inline]
655 pub fn get_exact(&self, name: &str) -> Option<Arc<Function>> {
656 self.trie.read().unwrap().get_exact(name)
657 }
658
659 #[inline]
662 pub fn get_prefix(&self, text: &str) -> Option<(String, Arc<Function>)> {
663 self.trie.read().unwrap().get_prefix(text)
664 }
665
666 pub fn get(&self, name: &str) -> Option<Arc<Function>> {
670 let trie = self.trie.read().unwrap();
671 if let Some(func) = trie.get_exact(name) {
672 return Some(func);
673 }
674 trie.get_prefix(name).map(|(_, func)| func)
675 }
676
677 pub fn get_with_match(&self, name: &str) -> Option<(String, Arc<Function>)> {
679 let trie = self.trie.read().unwrap();
680 if let Some(func) = trie.get_exact(name) {
681 return Some((name.to_string(), func));
682 }
683 trie.get_prefix(name)
684 }
685
686 pub fn get_many(&self, names: &[&str]) -> Vec<Option<Arc<Function>>> {
688 names.iter().map(|name| self.get(name)).collect()
689 }
690
691 #[inline]
693 pub fn get_completions(&self, prefix: &str) -> Vec<Arc<Function>> {
694 self.trie.read().unwrap().get_completions(prefix)
695 }
696
697 #[inline]
699 pub fn all_functions(&self) -> Vec<Arc<Function>> {
700 self.trie.read().unwrap().all_functions()
701 }
702
703 #[inline]
705 pub fn get_enum(&self, name: &str) -> Option<Vec<String>> {
706 self.enums.get(name).map(|v| v.clone())
707 }
708
709 pub fn all_enums(&self) -> HashMap<String, Vec<String>> {
711 self.enums
712 .iter()
713 .map(|e| (e.key().clone(), e.value().clone()))
714 .collect()
715 }
716
717 #[inline]
719 pub fn get_event(&self, name: &str) -> Option<Event> {
720 self.events.get(name).map(|v| v.clone())
721 }
722
723 pub fn all_events(&self) -> Vec<Event> {
725 self.events.iter().map(|e| e.value().clone()).collect()
726 }
727
728 #[inline]
730 pub fn function_count(&self) -> usize {
731 self.trie.read().unwrap().len()
732 }
733
734 #[inline]
736 pub fn enum_count(&self) -> usize {
737 self.enums.len()
738 }
739
740 #[inline]
742 pub fn event_count(&self) -> usize {
743 self.events.len()
744 }
745
746 pub fn clear(&self) {
748 self.trie.write().unwrap().clear();
749 self.enums.clear();
750 self.events.clear();
751 }
752}
753
754impl Default for MetadataManager {
755 fn default() -> Self {
756 Self::new()
757 }
758}
759
760#[cfg(not(target_arch = "wasm32"))]
767fn collect_functions_from_folder(path: &std::path::Path, out: &mut Vec<Function>) -> Result<()> {
768 let entries = std::fs::read_dir(path).map_err(|e| {
769 MetadataError::InvalidData(format!("Cannot read dir {}: {}", path.display(), e))
770 })?;
771
772 for entry in entries {
773 let entry_path = entry
774 .map_err(|e| MetadataError::InvalidData(e.to_string()))?
775 .path();
776
777 if entry_path.is_dir() {
778 collect_functions_from_folder(&entry_path, out)?;
779 } else if entry_path.is_file() {
780 let is_js_ts = entry_path
781 .extension()
782 .and_then(|e| e.to_str())
783 .map(|e| e == "js" || e == "ts")
784 .unwrap_or(false);
785
786 if is_js_ts {
787 let content = std::fs::read_to_string(&entry_path).map_err(|e| {
788 MetadataError::InvalidData(format!(
789 "Cannot read {}: {}",
790 entry_path.display(),
791 e
792 ))
793 })?;
794 out.extend(parse_functions_from_js_ts(
795 &content,
796 entry_path.to_str().unwrap_or_default(),
797 ));
798 }
799 }
800 }
801
802 Ok(())
803}
804
805#[cfg(not(target_arch = "wasm32"))]
810fn parse_functions_from_js_ts(content: &str, file_path: &str) -> Vec<Function> {
811 use regex::Regex;
812 use serde_json::Value as JsonValue;
813
814 let name_re = Regex::new(r#"name:\s*['"]([^'"]+)['"]"#).expect("regex");
816 let params_re = Regex::new(r#"(?:params|args):\s*\["#).expect("regex");
817 let desc_re = Regex::new(
818 r#"(?s)description:\s*(?:'((?:[^'\\]|\\.)*?)'|"((?:[^"\\]|\\.)*?)"|`((?:[^`\\]|\\.)*?)`)"#,
819 )
820 .expect("regex");
821 let brackets_re = Regex::new(r"brackets:\s*(true|false)").expect("regex");
822 let p_name_re = Regex::new(r#"name:\s*['"]([^'"]+)['"]"#).expect("regex");
823 let required_re = Regex::new(r"(?i)required:\s*(true|false)").expect("regex");
824 let rest_re = Regex::new(r"(?i)rest:\s*(true|false)").expect("regex");
825 let type_re = Regex::new(r"type:\s*([^,}\n\s]+)").expect("regex");
826 let output_re = Regex::new(r"output:\s*([^,}\n\s]+)").expect("regex");
827
828 let name_matches: Vec<(usize, usize, String, u32)> = name_re
830 .captures_iter(content)
831 .map(|c: regex::Captures| {
832 let m = c.get(0).unwrap();
833 let start = m.start();
834 let line = content[..start].chars().filter(|&c| c == '\n').count() as u32;
835 (start, m.end(), c[1].to_string(), line)
836 })
837 .collect();
838
839 let mut params_ranges: Vec<std::ops::Range<usize>> = Vec::new();
841 for m in params_re.find_iter(content) {
842 let start = m.start();
843 let mut depth = 0i32;
844 for (i, c) in content[start..].char_indices() {
845 if c == '[' {
846 depth += 1;
847 } else if c == ']' {
848 depth -= 1;
849 if depth == 0 {
850 params_ranges.push(start..start + i);
851 break;
852 }
853 }
854 }
855 }
856
857 let func_names: Vec<_> = name_matches
859 .into_iter()
860 .filter(|m| !params_ranges.iter().any(|r| r.contains(&m.0)))
861 .collect();
862
863 let mut functions = Vec::new();
865
866 for i in 0..func_names.len() {
867 let (_, end_pos, raw_name, line) = &func_names[i];
868 let chunk_end = if i + 1 < func_names.len() {
869 func_names[i + 1].0
870 } else {
871 content.len()
872 };
873 let chunk = &content[*end_pos..chunk_end];
874
875 let name = if raw_name.starts_with('$') {
877 raw_name.clone()
878 } else {
879 format!("${}", raw_name)
880 };
881
882 let description = desc_re
883 .captures(chunk)
884 .and_then(|c: regex::Captures| c.get(1).or(c.get(2)).or(c.get(3)))
885 .map(|m: regex::Match| m.as_str().to_string())
886 .unwrap_or_else(|| "Custom function".to_string());
887
888 let brackets = brackets_re
889 .captures(chunk)
890 .map(|c: regex::Captures| &c[1] == "true");
891
892 let output: Option<Vec<String>> = output_re.captures(chunk).map(|c: regex::Captures| {
893 c[1].split(',')
894 .map(|s: &str| {
895 s.trim()
896 .trim_matches(|c: char| c == '\'' || c == '"')
897 .to_string()
898 })
899 .filter(|s: &String| !s.is_empty())
900 .collect()
901 });
902
903 let args: Option<Vec<crate::types::Arg>> = params_ranges
905 .iter()
906 .find(|r| r.start >= *end_pos && r.start < chunk_end)
907 .and_then(|p_range| {
908 let p_content = &content[p_range.clone()];
909 let mut parsed_args: Vec<crate::types::Arg> = Vec::new();
910 let mut search = 0;
911
912 while let Some(bstart) = p_content[search..].find('{') {
913 let abs = search + bstart;
914 let mut depth = 0i32;
915 for (j, c) in p_content[abs..].char_indices() {
916 if c == '{' {
917 depth += 1;
918 } else if c == '}' {
919 depth -= 1;
920 if depth == 0 {
921 let body = &p_content[abs + 1..abs + j];
922 if let Some(n_cap) = p_name_re.captures(body) {
923 let raw_type = type_re
924 .captures(body)
925 .map(|c: regex::Captures| {
926 let t = c[1]
927 .trim()
928 .trim_matches(|c: char| c == '\'' || c == '"');
929 t.strip_prefix("ArgType.").unwrap_or(t).to_string()
930 })
931 .unwrap_or_else(|| "String".to_string());
932
933 parsed_args.push(crate::types::Arg {
934 name: n_cap[1].to_string(),
935 description: desc_re
936 .captures(body)
937 .and_then(|c: regex::Captures| {
938 c.get(1).or(c.get(2)).or(c.get(3))
939 })
940 .map(|m: regex::Match| m.as_str().to_string())
941 .unwrap_or_default(),
942 rest: rest_re
943 .captures(body)
944 .map(|c: regex::Captures| &c[1] == "true")
945 .unwrap_or(false),
946 required: required_re
947 .captures(body)
948 .map(|c: regex::Captures| &c[1] == "true"),
949 arg_type: JsonValue::String(raw_type),
950 ..Default::default()
951 });
952 }
953 search = abs + j + 1;
954 break;
955 }
956 }
957 }
958 }
959
960 if parsed_args.is_empty() {
961 None
962 } else {
963 Some(parsed_args)
964 }
965 });
966
967 functions.push(Function {
968 name,
969 version: Some(JsonValue::String("1.0.0".to_string())),
970 description,
971 brackets: brackets.or(if args.is_some() { Some(true) } else { None }),
972 unwrap: false,
973 args,
974 output,
975 category: Some("custom".to_string()),
976 local_path: Some(std::path::PathBuf::from(file_path)),
977 line: Some(*line),
978 ..Default::default()
979 });
980 }
981
982 functions
983}
984
985#[derive(Debug, Clone)]
991pub struct FetchStats {
992 pub functions: usize,
993 pub enums: usize,
994 pub events: usize,
995 pub errors: Vec<String>,
996}
997
998impl std::fmt::Display for FetchStats {
999 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1000 write!(
1001 f,
1002 "Fetched {} functions, {} enums, {} events",
1003 self.functions, self.enums, self.events
1004 )?;
1005 if !self.errors.is_empty() {
1006 write!(f, " ({} errors)", self.errors.len())?;
1007 }
1008 Ok(())
1009 }
1010}
1011
1012#[derive(Debug, Serialize, Deserialize)]
1018pub struct MetadataCache {
1019 pub functions: Vec<Function>,
1020 pub enums: HashMap<String, Vec<String>>,
1021 pub events: Vec<Event>,
1022 pub version: u32,
1023}
1024
1025impl MetadataCache {
1026 const VERSION: u32 = 1;
1027
1028 pub fn new(
1029 functions: Vec<Function>,
1030 enums: HashMap<String, Vec<String>>,
1031 events: Vec<Event>,
1032 ) -> Self {
1033 Self {
1034 functions,
1035 enums,
1036 events,
1037 version: Self::VERSION,
1038 }
1039 }
1040}
1041
1042impl MetadataManager {
1043 pub fn export_cache(&self) -> MetadataCache {
1044 let functions = self
1045 .all_functions()
1046 .into_iter()
1047 .filter(|f| !self.custom_function_names.contains_key(&f.name))
1048 .map(|f| (*f).clone())
1049 .collect();
1050
1051 MetadataCache::new(functions, self.all_enums(), self.all_events())
1052 }
1053
1054 pub fn import_cache(&self, cache: MetadataCache) -> Result<()> {
1055 if cache.version != MetadataCache::VERSION {
1056 return Err(MetadataError::CacheError(format!(
1057 "Incompatible cache version: expected {}, got {}",
1058 MetadataCache::VERSION,
1059 cache.version
1060 )));
1061 }
1062 self.clear();
1063 self.add_functions(cache.functions);
1064 for (name, values) in cache.enums {
1065 self.enums.insert(name, values);
1066 }
1067 for event in cache.events {
1068 self.events.insert(event.name.clone(), event);
1069 }
1070 Ok(())
1071 }
1072
1073 pub fn cache_to_json(&self) -> Result<String> {
1074 serde_json::to_string(&self.export_cache())
1075 .map_err(|e| MetadataError::CacheError(format!("Serialization failed: {}", e)))
1076 }
1077
1078 pub fn cache_from_json(&self, json: &str) -> Result<()> {
1079 let cache: MetadataCache = serde_json::from_str(json)
1080 .map_err(|e| MetadataError::CacheError(format!("Deserialization failed: {}", e)))?;
1081 self.import_cache(cache)
1082 }
1083}
1084
1085#[cfg(not(target_arch = "wasm32"))]
1086impl MetadataManager {
1087 pub fn save_cache_to_file(&self, path: impl AsRef<std::path::Path>) -> Result<()> {
1088 use std::io::Write;
1089 let json = self.cache_to_json()?;
1090 let mut file = std::fs::File::create(path)
1091 .map_err(|e| MetadataError::CacheError(format!("Failed to create file: {}", e)))?;
1092 file.write_all(json.as_bytes())
1093 .map_err(|e| MetadataError::CacheError(format!("Failed to write file: {}", e)))?;
1094 Ok(())
1095 }
1096
1097 pub fn load_cache_from_file(&self, path: impl AsRef<std::path::Path>) -> Result<()> {
1098 let json = std::fs::read_to_string(path)
1099 .map_err(|e| MetadataError::CacheError(format!("Failed to read file: {}", e)))?;
1100 self.cache_from_json(&json)
1101 }
1102}
1103
1104pub fn github_source(extension: impl Into<String>, repo: &str, branch: &str) -> MetadataSource {
1110 let base = format!("https://raw.githubusercontent.com/{}/{}/", repo, branch);
1111 MetadataSource::new(extension)
1112 .with_functions(format!("{}functions.json", base))
1113 .with_enums(format!("{}enums.json", base))
1114 .with_events(format!("{}events.json", base))
1115}
1116
1117pub fn custom_source(extension: impl Into<String>) -> MetadataSource {
1119 MetadataSource::new(extension)
1120}