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 clear(&mut self) {
228 self.root = TrieNode::default();
229 self.count = 0;
230 }
231}
232
233pub struct Fetcher {
239 client: reqwest::Client,
240}
241
242impl Fetcher {
243 pub fn new() -> Self {
245 #[cfg(not(target_arch = "wasm32"))]
246 let client = reqwest::Client::builder()
247 .timeout(std::time::Duration::from_secs(30))
248 .build()
249 .unwrap_or_else(|_| reqwest::Client::new());
250
251 #[cfg(target_arch = "wasm32")]
252 let client = reqwest::Client::builder()
253 .build()
254 .unwrap_or_else(|_| reqwest::Client::new());
255
256 Self { client }
257 }
258
259 pub async fn fetch_json<T: serde::de::DeserializeOwned>(&self, url: &str) -> Result<T> {
261 let response =
262 self.client.get(url).send().await.map_err(|e| {
263 MetadataError::NetworkError(format!("Failed to fetch {}: {}", url, e))
264 })?;
265
266 let status = response.status();
267 if status == reqwest::StatusCode::NOT_FOUND {
268 return Err(MetadataError::NotFound(format!("URL not found: {}", url)));
269 }
270 if !status.is_success() {
271 return Err(MetadataError::NetworkError(format!(
272 "HTTP {}: {}",
273 status, url
274 )));
275 }
276
277 let text = response.text().await.map_err(|e| {
278 MetadataError::NetworkError(format!("Failed to read response from {}: {}", url, e))
279 })?;
280
281 serde_json::from_str(&text).map_err(|e| {
282 let preview: String = text.chars().take(200).collect();
283 MetadataError::ParseError(format!(
284 "Failed to parse JSON from {}: {}\nJSON preview: {}…",
285 url, e, preview
286 ))
287 })
288 }
289
290 pub async fn fetch_functions(&self, url: &str, extension: String) -> Result<Vec<Function>> {
293 let response =
294 self.client.get(url).send().await.map_err(|e| {
295 MetadataError::NetworkError(format!("Failed to fetch {}: {}", url, e))
296 })?;
297
298 let status = response.status();
299 if status == reqwest::StatusCode::NOT_FOUND {
300 return Err(MetadataError::NotFound(format!("URL not found: {}", url)));
301 }
302 if !status.is_success() {
303 return Err(MetadataError::NetworkError(format!(
304 "HTTP {}: {}",
305 status, url
306 )));
307 }
308
309 let text = response.text().await.map_err(|e| {
310 MetadataError::NetworkError(format!("Failed to read response from {}: {}", url, e))
311 })?;
312
313 let raw_items: Vec<serde_json::Value> = serde_json::from_str(&text).map_err(|e| {
314 let preview: String = text.chars().take(200).collect();
315 MetadataError::ParseError(format!(
316 "Failed to parse JSON array from {}: {}\nJSON preview: {}…",
317 url, e, preview
318 ))
319 })?;
320
321 let mut functions = Vec::with_capacity(raw_items.len());
322 for (i, raw) in raw_items.into_iter().enumerate() {
323 match serde_json::from_value::<Function>(raw) {
324 Ok(mut func) => {
325 func.extension = Some(extension.clone());
326 func.source_url = Some(url.to_string());
327 functions.push(func);
328 }
329 Err(e) => {
330 eprintln!("[forge-kit] Skipping function #{} from {}: {}", i, url, e);
331 }
332 }
333 }
334
335 Ok(functions)
336 }
337
338 pub async fn fetch_enums(&self, url: &str) -> Result<HashMap<String, Vec<String>>> {
340 self.fetch_json(url).await
341 }
342
343 pub async fn fetch_events(&self, url: &str) -> Result<Vec<Event>> {
345 self.fetch_json(url).await
346 }
347}
348
349impl Default for Fetcher {
350 fn default() -> Self {
351 Self::new()
352 }
353}
354
355pub struct MetadataManager {
361 trie: std::sync::RwLock<FunctionTrie>,
362 enums: DashMap<String, Vec<String>>,
363 events: DashMap<String, Event>,
364 sources: std::sync::RwLock<Vec<MetadataSource>>,
365 fetcher: Fetcher,
366}
367
368impl MetadataManager {
369 pub fn new() -> Self {
371 Self {
372 trie: std::sync::RwLock::new(FunctionTrie::new()),
373 enums: DashMap::new(),
374 events: DashMap::new(),
375 sources: std::sync::RwLock::new(Vec::new()),
376 fetcher: Fetcher::new(),
377 }
378 }
379
380 pub fn add_source(&self, source: MetadataSource) {
382 self.sources.write().unwrap().push(source);
383 }
384
385 pub async fn fetch_all(&self) -> Result<FetchStats> {
387 let sources = self.sources.read().unwrap().clone();
388
389 let mut total_functions = 0;
390 let mut total_enums = 0;
391 let mut total_events = 0;
392 let mut errors = Vec::new();
393
394 for source in sources {
395 if let Some(url) = &source.functions_url {
396 match self
397 .fetcher
398 .fetch_functions(url, source.extension.clone())
399 .await
400 {
401 Ok(functions) => {
402 total_functions += functions.len();
403 self.add_functions(functions);
404 }
405 Err(MetadataError::NotFound(_)) => {}
406 Err(e) => {
407 errors.push(format!("Functions from {}: {}", source.extension, e));
408 }
409 }
410 }
411
412 if let Some(url) = &source.enums_url {
413 match self.fetcher.fetch_enums(url).await {
414 Ok(enums) => {
415 total_enums += enums.len();
416 for (name, values) in enums {
417 self.enums.insert(name, values);
418 }
419 }
420 Err(e) => {
421 if !matches!(e, MetadataError::NotFound(_)) {
422 errors.push(format!("Enums from {}: {}", source.extension, e));
423 }
424 }
425 }
426 }
427
428 if let Some(url) = &source.events_url {
429 match self.fetcher.fetch_events(url).await {
430 Ok(events) => {
431 total_events += events.len();
432 for event in events {
433 self.events.insert(event.name.clone(), event);
434 }
435 }
436 Err(e) => {
437 if !matches!(e, MetadataError::NotFound(_)) {
438 errors.push(format!("Events from {}: {}", source.extension, e));
439 }
440 }
441 }
442 }
443 }
444
445 Ok(FetchStats {
446 functions: total_functions,
447 enums: total_enums,
448 events: total_events,
449 errors,
450 })
451 }
452
453 fn add_functions(&self, functions: Vec<Function>) {
454 let mut trie = self.trie.write().unwrap();
455
456 for func in functions {
457 let arc_func = Arc::new(func.clone());
458 trie.insert(&func.name, arc_func.clone());
459
460 if let Some(aliases) = &func.aliases {
461 for alias in aliases {
462 let alias_name = if alias.starts_with('$') {
463 alias.clone()
464 } else {
465 format!("${}", alias)
466 };
467 let mut alias_func = (*arc_func).clone();
468 alias_func.name = alias_name.clone();
469 trie.insert(&alias_name, Arc::new(alias_func));
470 }
471 }
472 }
473 }
474
475 pub fn add_custom_functions_from_json(&self, json: &str) -> Result<usize> {
504 let raw_items: Vec<serde_json::Value> = serde_json::from_str(json).map_err(|e| {
505 MetadataError::ParseError(format!("Invalid custom-functions JSON: {}", e))
506 })?;
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 func.category = func.category.or(Some("custom".to_string()));
519
520 let arc_func = Arc::new(func.clone());
521 trie.insert(&func.name, arc_func.clone());
522 count += 1;
523
524 if let Some(aliases) = &func.aliases {
526 for alias in aliases {
527 let alias_name = if alias.starts_with('$') {
528 alias.clone()
529 } else {
530 format!("${}", alias)
531 };
532 let mut alias_func = (*arc_func).clone();
533 alias_func.name = alias_name.clone();
534 trie.insert(&alias_name, Arc::new(alias_func));
535 }
536 }
537 }
538 Err(e) => {
539 eprintln!("[forge-kit] Skipping custom function #{}: {}", i, e);
540 }
541 }
542 }
543
544 Ok(count)
545 }
546
547 #[cfg(not(target_arch = "wasm32"))]
552 pub fn add_custom_functions_from_json_file(
553 &self,
554 path: impl AsRef<std::path::Path>,
555 ) -> Result<usize> {
556 let path = path.as_ref();
557 let json = std::fs::read_to_string(path).map_err(|e| {
558 MetadataError::CacheError(format!(
559 "Cannot read custom-functions file {}: {}",
560 path.display(),
561 e
562 ))
563 })?;
564 self.add_custom_functions_from_json(&json)
565 }
566
567 #[cfg(not(target_arch = "wasm32"))]
581 pub fn generate_custom_functions_json(
582 &self,
583 folder: impl AsRef<std::path::Path>,
584 ) -> Result<String> {
585 let folder = folder.as_ref();
586 if !folder.exists() || !folder.is_dir() {
587 return Err(MetadataError::InvalidData(format!(
588 "generate_custom_functions_json: {} is not a directory",
589 folder.display()
590 )));
591 }
592
593 let mut functions: Vec<Function> = Vec::new();
594 collect_functions_from_folder(folder, &mut functions)?;
595
596 serde_json::to_string_pretty(&functions).map_err(|e| {
597 MetadataError::ParseError(format!("Failed to serialize custom functions: {}", e))
598 })
599 }
600
601 #[cfg(not(target_arch = "wasm32"))]
606 pub fn generate_custom_functions_json_to_file(
607 &self,
608 folder: impl AsRef<std::path::Path>,
609 output_path: impl AsRef<std::path::Path>,
610 ) -> Result<usize> {
611 let json = self.generate_custom_functions_json(folder)?;
612
613 let entries: Vec<serde_json::Value> =
615 serde_json::from_str(&json).map_err(|e| MetadataError::ParseError(e.to_string()))?;
616 let count = entries.len();
617
618 let output_path = output_path.as_ref();
619 if let Some(parent) = output_path.parent() {
620 std::fs::create_dir_all(parent).map_err(|e| {
621 MetadataError::CacheError(format!("Cannot create directories: {}", e))
622 })?;
623 }
624 std::fs::write(output_path, json).map_err(|e| {
625 MetadataError::CacheError(format!("Cannot write to {}: {}", output_path.display(), e))
626 })?;
627
628 Ok(count)
629 }
630
631 #[inline]
637 pub fn get_exact(&self, name: &str) -> Option<Arc<Function>> {
638 self.trie.read().unwrap().get_exact(name)
639 }
640
641 #[inline]
644 pub fn get_prefix(&self, text: &str) -> Option<(String, Arc<Function>)> {
645 self.trie.read().unwrap().get_prefix(text)
646 }
647
648 pub fn get(&self, name: &str) -> Option<Arc<Function>> {
652 let trie = self.trie.read().unwrap();
653 if let Some(func) = trie.get_exact(name) {
654 return Some(func);
655 }
656 trie.get_prefix(name).map(|(_, func)| func)
657 }
658
659 pub fn get_with_match(&self, name: &str) -> Option<(String, Arc<Function>)> {
661 let trie = self.trie.read().unwrap();
662 if let Some(func) = trie.get_exact(name) {
663 return Some((name.to_string(), func));
664 }
665 trie.get_prefix(name)
666 }
667
668 pub fn get_many(&self, names: &[&str]) -> Vec<Option<Arc<Function>>> {
670 names.iter().map(|name| self.get(name)).collect()
671 }
672
673 #[inline]
675 pub fn get_completions(&self, prefix: &str) -> Vec<Arc<Function>> {
676 self.trie.read().unwrap().get_completions(prefix)
677 }
678
679 #[inline]
681 pub fn all_functions(&self) -> Vec<Arc<Function>> {
682 self.trie.read().unwrap().all_functions()
683 }
684
685 #[inline]
687 pub fn get_enum(&self, name: &str) -> Option<Vec<String>> {
688 self.enums.get(name).map(|v| v.clone())
689 }
690
691 pub fn all_enums(&self) -> HashMap<String, Vec<String>> {
693 self.enums
694 .iter()
695 .map(|e| (e.key().clone(), e.value().clone()))
696 .collect()
697 }
698
699 #[inline]
701 pub fn get_event(&self, name: &str) -> Option<Event> {
702 self.events.get(name).map(|v| v.clone())
703 }
704
705 pub fn all_events(&self) -> Vec<Event> {
707 self.events.iter().map(|e| e.value().clone()).collect()
708 }
709
710 #[inline]
712 pub fn function_count(&self) -> usize {
713 self.trie.read().unwrap().len()
714 }
715
716 #[inline]
718 pub fn enum_count(&self) -> usize {
719 self.enums.len()
720 }
721
722 #[inline]
724 pub fn event_count(&self) -> usize {
725 self.events.len()
726 }
727
728 pub fn clear(&self) {
730 self.trie.write().unwrap().clear();
731 self.enums.clear();
732 self.events.clear();
733 }
734}
735
736impl Default for MetadataManager {
737 fn default() -> Self {
738 Self::new()
739 }
740}
741
742#[cfg(not(target_arch = "wasm32"))]
749fn collect_functions_from_folder(path: &std::path::Path, out: &mut Vec<Function>) -> Result<()> {
750 let entries = std::fs::read_dir(path).map_err(|e| {
751 MetadataError::InvalidData(format!("Cannot read dir {}: {}", path.display(), e))
752 })?;
753
754 for entry in entries {
755 let entry_path = entry
756 .map_err(|e| MetadataError::InvalidData(e.to_string()))?
757 .path();
758
759 if entry_path.is_dir() {
760 collect_functions_from_folder(&entry_path, out)?;
761 } else if entry_path.is_file() {
762 let is_js_ts = entry_path
763 .extension()
764 .and_then(|e| e.to_str())
765 .map(|e| e == "js" || e == "ts")
766 .unwrap_or(false);
767
768 if is_js_ts {
769 let content = std::fs::read_to_string(&entry_path).map_err(|e| {
770 MetadataError::InvalidData(format!(
771 "Cannot read {}: {}",
772 entry_path.display(),
773 e
774 ))
775 })?;
776 out.extend(parse_functions_from_js_ts(
777 &content,
778 entry_path.to_str().unwrap_or_default(),
779 ));
780 }
781 }
782 }
783
784 Ok(())
785}
786
787#[cfg(not(target_arch = "wasm32"))]
792fn parse_functions_from_js_ts(content: &str, file_path: &str) -> Vec<Function> {
793 use regex::Regex;
794 use serde_json::Value as JsonValue;
795
796 let name_re = Regex::new(r#"name:\s*['"]([^'"]+)['"]"#).expect("regex");
798 let params_re = Regex::new(r#"(?:params|args):\s*\["#).expect("regex");
799 let desc_re = Regex::new(
800 r#"(?s)description:\s*(?:'((?:[^'\\]|\\.)*?)'|"((?:[^"\\]|\\.)*?)"|`((?:[^`\\]|\\.)*?)`)"#,
801 )
802 .expect("regex");
803 let brackets_re = Regex::new(r"brackets:\s*(true|false)").expect("regex");
804 let p_name_re = Regex::new(r#"name:\s*['"]([^'"]+)['"]"#).expect("regex");
805 let required_re = Regex::new(r"(?i)required:\s*(true|false)").expect("regex");
806 let rest_re = Regex::new(r"(?i)rest:\s*(true|false)").expect("regex");
807 let type_re = Regex::new(r"type:\s*([^,}\n\s]+)").expect("regex");
808 let output_re = Regex::new(r"output:\s*([^,}\n\s]+)").expect("regex");
809
810 let name_matches: Vec<(usize, usize, String, u32)> = name_re
812 .captures_iter(content)
813 .map(|c: regex::Captures| {
814 let m = c.get(0).unwrap();
815 let start = m.start();
816 let line = content[..start].chars().filter(|&c| c == '\n').count() as u32;
817 (start, m.end(), c[1].to_string(), line)
818 })
819 .collect();
820
821 let mut params_ranges: Vec<std::ops::Range<usize>> = Vec::new();
823 for m in params_re.find_iter(content) {
824 let start = m.start();
825 let mut depth = 0i32;
826 for (i, c) in content[start..].char_indices() {
827 if c == '[' {
828 depth += 1;
829 } else if c == ']' {
830 depth -= 1;
831 if depth == 0 {
832 params_ranges.push(start..start + i);
833 break;
834 }
835 }
836 }
837 }
838
839 let func_names: Vec<_> = name_matches
841 .into_iter()
842 .filter(|m| !params_ranges.iter().any(|r| r.contains(&m.0)))
843 .collect();
844
845 let mut functions = Vec::new();
847
848 for i in 0..func_names.len() {
849 let (_, end_pos, raw_name, line) = &func_names[i];
850 let chunk_end = if i + 1 < func_names.len() {
851 func_names[i + 1].0
852 } else {
853 content.len()
854 };
855 let chunk = &content[*end_pos..chunk_end];
856
857 let name = if raw_name.starts_with('$') {
859 raw_name.clone()
860 } else {
861 format!("${}", raw_name)
862 };
863
864 let description = desc_re
865 .captures(chunk)
866 .and_then(|c: regex::Captures| c.get(1).or(c.get(2)).or(c.get(3)))
867 .map(|m: regex::Match| m.as_str().to_string())
868 .unwrap_or_else(|| "Custom function".to_string());
869
870 let brackets = brackets_re
871 .captures(chunk)
872 .map(|c: regex::Captures| &c[1] == "true");
873
874 let output: Option<Vec<String>> = output_re.captures(chunk).map(|c: regex::Captures| {
875 c[1].split(',')
876 .map(|s: &str| {
877 s.trim()
878 .trim_matches(|c: char| c == '\'' || c == '"')
879 .to_string()
880 })
881 .filter(|s: &String| !s.is_empty())
882 .collect()
883 });
884
885 let args: Option<Vec<crate::types::Arg>> = params_ranges
887 .iter()
888 .find(|r| r.start >= *end_pos && r.start < chunk_end)
889 .and_then(|p_range| {
890 let p_content = &content[p_range.clone()];
891 let mut parsed_args: Vec<crate::types::Arg> = Vec::new();
892 let mut search = 0;
893
894 while let Some(bstart) = p_content[search..].find('{') {
895 let abs = search + bstart;
896 let mut depth = 0i32;
897 for (j, c) in p_content[abs..].char_indices() {
898 if c == '{' {
899 depth += 1;
900 } else if c == '}' {
901 depth -= 1;
902 if depth == 0 {
903 let body = &p_content[abs + 1..abs + j];
904 if let Some(n_cap) = p_name_re.captures(body) {
905 let raw_type = type_re
906 .captures(body)
907 .map(|c: regex::Captures| {
908 let t = c[1]
909 .trim()
910 .trim_matches(|c: char| c == '\'' || c == '"');
911 t.strip_prefix("ArgType.").unwrap_or(t).to_string()
912 })
913 .unwrap_or_else(|| "String".to_string());
914
915 parsed_args.push(crate::types::Arg {
916 name: n_cap[1].to_string(),
917 description: desc_re
918 .captures(body)
919 .and_then(|c: regex::Captures| {
920 c.get(1).or(c.get(2)).or(c.get(3))
921 })
922 .map(|m: regex::Match| m.as_str().to_string())
923 .unwrap_or_default(),
924 rest: rest_re
925 .captures(body)
926 .map(|c: regex::Captures| &c[1] == "true")
927 .unwrap_or(false),
928 required: required_re
929 .captures(body)
930 .map(|c: regex::Captures| &c[1] == "true"),
931 arg_type: JsonValue::String(raw_type),
932 ..Default::default()
933 });
934 }
935 search = abs + j + 1;
936 break;
937 }
938 }
939 }
940 }
941
942 if parsed_args.is_empty() {
943 None
944 } else {
945 Some(parsed_args)
946 }
947 });
948
949 functions.push(Function {
950 name,
951 version: Some(JsonValue::String("1.0.0".to_string())),
952 description,
953 brackets: brackets.or(if args.is_some() { Some(true) } else { None }),
954 unwrap: false,
955 args,
956 output,
957 category: Some("custom".to_string()),
958 local_path: Some(std::path::PathBuf::from(file_path)),
959 line: Some(*line),
960 ..Default::default()
961 });
962 }
963
964 functions
965}
966
967#[derive(Debug, Clone)]
973pub struct FetchStats {
974 pub functions: usize,
975 pub enums: usize,
976 pub events: usize,
977 pub errors: Vec<String>,
978}
979
980impl std::fmt::Display for FetchStats {
981 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
982 write!(
983 f,
984 "Fetched {} functions, {} enums, {} events",
985 self.functions, self.enums, self.events
986 )?;
987 if !self.errors.is_empty() {
988 write!(f, " ({} errors)", self.errors.len())?;
989 }
990 Ok(())
991 }
992}
993
994#[derive(Debug, Serialize, Deserialize)]
1000pub struct MetadataCache {
1001 pub functions: Vec<Function>,
1002 pub enums: HashMap<String, Vec<String>>,
1003 pub events: Vec<Event>,
1004 pub version: u32,
1005}
1006
1007impl MetadataCache {
1008 const VERSION: u32 = 1;
1009
1010 pub fn new(
1011 functions: Vec<Function>,
1012 enums: HashMap<String, Vec<String>>,
1013 events: Vec<Event>,
1014 ) -> Self {
1015 Self {
1016 functions,
1017 enums,
1018 events,
1019 version: Self::VERSION,
1020 }
1021 }
1022}
1023
1024impl MetadataManager {
1025 pub fn export_cache(&self) -> MetadataCache {
1026 MetadataCache::new(
1027 self.all_functions().iter().map(|f| (**f).clone()).collect(),
1028 self.all_enums(),
1029 self.all_events(),
1030 )
1031 }
1032
1033 pub fn import_cache(&self, cache: MetadataCache) -> Result<()> {
1034 if cache.version != MetadataCache::VERSION {
1035 return Err(MetadataError::CacheError(format!(
1036 "Incompatible cache version: expected {}, got {}",
1037 MetadataCache::VERSION,
1038 cache.version
1039 )));
1040 }
1041 self.clear();
1042 self.add_functions(cache.functions);
1043 for (name, values) in cache.enums {
1044 self.enums.insert(name, values);
1045 }
1046 for event in cache.events {
1047 self.events.insert(event.name.clone(), event);
1048 }
1049 Ok(())
1050 }
1051
1052 pub fn cache_to_json(&self) -> Result<String> {
1053 serde_json::to_string(&self.export_cache())
1054 .map_err(|e| MetadataError::CacheError(format!("Serialization failed: {}", e)))
1055 }
1056
1057 pub fn cache_from_json(&self, json: &str) -> Result<()> {
1058 let cache: MetadataCache = serde_json::from_str(json)
1059 .map_err(|e| MetadataError::CacheError(format!("Deserialization failed: {}", e)))?;
1060 self.import_cache(cache)
1061 }
1062}
1063
1064#[cfg(not(target_arch = "wasm32"))]
1065impl MetadataManager {
1066 pub fn save_cache_to_file(&self, path: impl AsRef<std::path::Path>) -> Result<()> {
1067 use std::io::Write;
1068 let json = self.cache_to_json()?;
1069 let mut file = std::fs::File::create(path)
1070 .map_err(|e| MetadataError::CacheError(format!("Failed to create file: {}", e)))?;
1071 file.write_all(json.as_bytes())
1072 .map_err(|e| MetadataError::CacheError(format!("Failed to write file: {}", e)))?;
1073 Ok(())
1074 }
1075
1076 pub fn load_cache_from_file(&self, path: impl AsRef<std::path::Path>) -> Result<()> {
1077 let json = std::fs::read_to_string(path)
1078 .map_err(|e| MetadataError::CacheError(format!("Failed to read file: {}", e)))?;
1079 self.cache_from_json(&json)
1080 }
1081}
1082
1083pub fn github_source(extension: impl Into<String>, repo: &str, branch: &str) -> MetadataSource {
1089 let base = format!("https://raw.githubusercontent.com/{}/{}/", repo, branch);
1090 MetadataSource::new(extension)
1091 .with_functions(format!("{}functions.json", base))
1092 .with_enums(format!("{}enums.json", base))
1093 .with_events(format!("{}events.json", base))
1094}
1095
1096pub fn custom_source(extension: impl Into<String>) -> MetadataSource {
1098 MetadataSource::new(extension)
1099}