caixa_resolver/
lisp_config.rs1use serde::{Deserialize, Serialize};
14use tatara_lisp::DeriveTataraDomain;
15
16use crate::config::ResolverConfig;
17
18#[derive(DeriveTataraDomain, Serialize, Deserialize, Debug, Clone, PartialEq)]
19#[serde(rename_all = "camelCase")]
20#[tatara(keyword = "defresolver-config")]
21pub struct ResolverConfigLisp {
22 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub default_host: Option<String>,
24
25 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub cache_dir: Option<String>,
27
28 #[serde(default, skip_serializing_if = "Option::is_none")]
29 pub include_dev: Option<bool>,
30
31 #[serde(default)]
32 pub additional_hosts: Vec<String>,
33}
34
35impl ResolverConfigLisp {
36 pub fn from_lisp(src: &str) -> Result<Self, tatara_lisp::LispError> {
38 use tatara_lisp::domain::TataraDomain;
39 let forms = tatara_lisp::read(src)?;
40 let first = forms
41 .first()
42 .ok_or_else(|| tatara_lisp::LispError::Compile {
43 form: "defresolver-config".into(),
44 message: "empty config.lisp".into(),
45 })?;
46 Self::compile_from_sexp(first)
47 }
48
49 pub fn register() -> Result<(), tatara_lisp::KeywordCollision> {
61 tatara_lisp::domain::register::<Self>()
62 }
63
64 #[must_use]
66 pub fn into_runtime(self) -> ResolverConfig {
67 let mut out = ResolverConfig::default();
68 if let Some(h) = self.default_host {
69 out.default_host = h;
70 }
71 if let Some(d) = self.cache_dir {
72 out.cache_dir = Some(std::path::PathBuf::from(d));
73 }
74 if let Some(dev) = self.include_dev {
75 out.include_dev = dev;
76 }
77 out.additional_hosts = self.additional_hosts;
78 out
79 }
80}
81
82impl ResolverConfig {
83 pub fn load_lisp_or_yaml() -> Self {
86 let Some(base) = dirs::config_dir() else {
87 return Self::default();
88 };
89 let dir = base.join("caixa");
90 let lisp = dir.join("config.lisp");
91 if lisp.exists() {
92 if let Ok(src) = std::fs::read_to_string(&lisp) {
93 if let Ok(parsed) = ResolverConfigLisp::from_lisp(&src) {
94 return parsed.into_runtime();
95 }
96 }
97 }
98 let yaml = dir.join("config.yaml");
99 if yaml.exists() {
100 if let Ok(src) = std::fs::read_to_string(&yaml) {
101 if let Ok(cfg) = serde_yaml::from_str::<Self>(&src) {
102 return cfg;
103 }
104 }
105 }
106 Self::default()
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn parses_defresolver_config() {
116 let src = r#"
117(defresolver-config
118 :default-host "codeberg:my-org"
119 :include-dev #t
120 :additional-hosts ("sourcehut:zig-org"))
121"#;
122 let c = ResolverConfigLisp::from_lisp(src).unwrap();
123 assert_eq!(c.default_host.as_deref(), Some("codeberg:my-org"));
124 assert_eq!(c.include_dev, Some(true));
125 assert_eq!(c.additional_hosts, vec!["sourcehut:zig-org".to_string()]);
126 }
127
128 #[test]
129 fn lowers_into_runtime_config() {
130 let c = ResolverConfigLisp {
131 default_host: Some("codeberg:org".into()),
132 cache_dir: None,
133 include_dev: Some(true),
134 additional_hosts: vec![],
135 };
136 let r = c.into_runtime();
137 assert_eq!(r.default_host, "codeberg:org");
138 assert!(r.include_dev);
139 }
140
141 #[test]
142 fn register_populates_registry() {
143 ResolverConfigLisp::register()
144 .expect("first register call in this test process must succeed");
145 assert!(tatara_lisp::domain::registered_keywords().contains(&"defresolver-config"));
146 }
147}