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() {
51 tatara_lisp::domain::register::<Self>();
52 }
53
54 #[must_use]
56 pub fn into_runtime(self) -> ResolverConfig {
57 let mut out = ResolverConfig::default();
58 if let Some(h) = self.default_host {
59 out.default_host = h;
60 }
61 if let Some(d) = self.cache_dir {
62 out.cache_dir = Some(std::path::PathBuf::from(d));
63 }
64 if let Some(dev) = self.include_dev {
65 out.include_dev = dev;
66 }
67 out.additional_hosts = self.additional_hosts;
68 out
69 }
70}
71
72impl ResolverConfig {
73 pub fn load_lisp_or_yaml() -> Self {
76 let Some(base) = dirs::config_dir() else {
77 return Self::default();
78 };
79 let dir = base.join("caixa");
80 let lisp = dir.join("config.lisp");
81 if lisp.exists() {
82 if let Ok(src) = std::fs::read_to_string(&lisp) {
83 if let Ok(parsed) = ResolverConfigLisp::from_lisp(&src) {
84 return parsed.into_runtime();
85 }
86 }
87 }
88 let yaml = dir.join("config.yaml");
89 if yaml.exists() {
90 if let Ok(src) = std::fs::read_to_string(&yaml) {
91 if let Ok(cfg) = serde_yaml::from_str::<Self>(&src) {
92 return cfg;
93 }
94 }
95 }
96 Self::default()
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn parses_defresolver_config() {
106 let src = r#"
107(defresolver-config
108 :default-host "codeberg:my-org"
109 :include-dev #t
110 :additional-hosts ("sourcehut:zig-org"))
111"#;
112 let c = ResolverConfigLisp::from_lisp(src).unwrap();
113 assert_eq!(c.default_host.as_deref(), Some("codeberg:my-org"));
114 assert_eq!(c.include_dev, Some(true));
115 assert_eq!(c.additional_hosts, vec!["sourcehut:zig-org".to_string()]);
116 }
117
118 #[test]
119 fn lowers_into_runtime_config() {
120 let c = ResolverConfigLisp {
121 default_host: Some("codeberg:org".into()),
122 cache_dir: None,
123 include_dev: Some(true),
124 additional_hosts: vec![],
125 };
126 let r = c.into_runtime();
127 assert_eq!(r.default_host, "codeberg:org");
128 assert!(r.include_dev);
129 }
130
131 #[test]
132 fn register_populates_registry() {
133 ResolverConfigLisp::register();
134 assert!(tatara_lisp::domain::registered_keywords().contains(&"defresolver-config"));
135 }
136}