A better Rust ATProto crate
1use super::LexiconSource;
2use crate::fetch::sources::parse_from_index_or_lexicon_file;
3use jacquard_common::IntoStatic;
4use jacquard_lexicon::lexicon::LexiconDoc;
5use miette::{IntoDiagnostic, Result, miette};
6use std::collections::HashMap;
7use tempfile::TempDir;
8use tokio::process::Command;
9
10#[derive(Debug, Clone)]
11pub struct GitSource {
12 pub repo: String,
13 pub git_ref: Option<String>,
14 pub pattern: String,
15}
16
17impl LexiconSource for GitSource {
18 async fn fetch(&self) -> Result<HashMap<String, LexiconDoc<'_>>> {
19 // Create temp directory for clone
20 let temp_dir = TempDir::new().into_diagnostic()?;
21 let clone_path = temp_dir.path();
22
23 // Shallow clone
24 let mut clone_cmd = Command::new("git");
25 clone_cmd.arg("clone").arg("--depth").arg("1");
26
27 if let Some(ref git_ref) = self.git_ref {
28 clone_cmd.arg("--branch").arg(git_ref);
29 }
30
31 clone_cmd.arg(&self.repo).arg(clone_path);
32
33 let output = clone_cmd.output().await.into_diagnostic()?;
34
35 if !output.status.success() {
36 let stderr = String::from_utf8_lossy(&output.stderr);
37 return Err(miette!("Git clone failed: {}", stderr));
38 }
39
40 // Find lexicon files matching pattern
41 let mut lexicons = HashMap::new();
42
43 for entry in glob::glob(&format!("{}/{}", clone_path.display(), self.pattern))
44 .into_diagnostic()?
45 .filter_map(|e| e.ok())
46 {
47 if !entry.is_file() {
48 continue;
49 }
50
51 // Try to parse as lexicon
52 let content = tokio::fs::read_to_string(&entry).await.into_diagnostic()?;
53
54 match parse_from_index_or_lexicon_file(&content) {
55 Ok((nsid, doc)) => {
56 let doc = doc.into_static();
57 lexicons.insert(nsid, doc);
58 }
59 Err(e) => {
60 if self.repo.contains("weaver") {
61 println!("Failed to parse lexicon file: {}\n{}", content, e);
62 }
63 // Not a lexicon, skip
64 continue;
65 }
66 }
67 }
68
69 Ok(lexicons)
70 }
71}