Skip to main content

caixa_flake/
flake.rs

1use serde::{Deserialize, Serialize};
2use tatara_lisp::DeriveTataraDomain;
3
4/// A whole `flake.lisp` — parsed as a TataraDomain via `defflake`.
5#[derive(DeriveTataraDomain, Serialize, Deserialize, Debug, Clone, PartialEq)]
6#[serde(rename_all = "camelCase")]
7#[tatara(keyword = "defflake")]
8pub struct FlakeLisp {
9    pub descricao: String,
10    #[serde(default)]
11    pub entradas: Vec<FlakeInput>,
12    #[serde(default)]
13    pub saidas: Option<FlakeOutput>,
14}
15
16#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
17#[serde(rename_all = "camelCase")]
18pub struct FlakeInput {
19    pub nome: String,
20    pub url: String,
21    /// When true, emit `inputs.<nome>.follows = "<segue>"` instead of `.url`.
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub segue: Option<String>,
24}
25
26#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
27#[serde(rename_all = "camelCase")]
28pub struct FlakeOutput {
29    #[serde(default)]
30    pub pacotes: Vec<FlakePackage>,
31    #[serde(default)]
32    pub modulos: Vec<FlakeModule>,
33    #[serde(default)]
34    pub dev_shells: bool,
35}
36
37#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
38#[serde(rename_all = "camelCase")]
39pub struct FlakePackage {
40    pub nome: String,
41    pub src: String,
42}
43
44#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
45#[serde(rename_all = "camelCase")]
46pub struct FlakeModule {
47    pub nome: String,
48    pub caminho: String,
49}
50
51impl FlakeLisp {
52    pub fn from_lisp(src: &str) -> Result<Self, tatara_lisp::LispError> {
53        use tatara_lisp::domain::TataraDomain;
54        let forms = tatara_lisp::read(src)?;
55        let first = forms
56            .first()
57            .ok_or_else(|| tatara_lisp::LispError::Compile {
58                form: "defflake".into(),
59                message: "empty flake.lisp".into(),
60            })?;
61        Self::compile_from_sexp(first)
62    }
63
64    /// Register `FlakeLisp` with the global tatara-lisp domain registry
65    /// so `defflake` is dispatchable from any tatara-lisp binary that
66    /// seeds the registry.
67    ///
68    /// # Errors
69    ///
70    /// [`tatara_lisp::KeywordCollision`] when a peer type has already
71    /// claimed the `defflake` keyword in this process. Peer of the
72    /// sibling [`caixa_core::Caixa::register`] and the other per-crate
73    /// entry points documented at
74    /// `caixa-core/src/manifest.rs::Caixa::register` — every substrate
75    /// crate that owns a tatara-lisp keyword now propagates the same
76    /// typed error verbatim.
77    pub fn register() -> Result<(), tatara_lisp::KeywordCollision> {
78        tatara_lisp::domain::register::<Self>()
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn parses_minimal_flake() {
88        let src = r#"
89(defflake
90  :descricao "my caixa"
91  :entradas ((:nome "nixpkgs" :url "github:nixos/nixpkgs?ref=nixos-unstable")
92             (:nome "substrate" :url "github:pleme-io/substrate")))
93"#;
94        let f = FlakeLisp::from_lisp(src).unwrap();
95        assert_eq!(f.descricao, "my caixa");
96        assert_eq!(f.entradas.len(), 2);
97        assert_eq!(f.entradas[0].nome, "nixpkgs");
98    }
99
100    #[test]
101    fn register_populates_registry() {
102        FlakeLisp::register().expect("first register call in this test process must succeed");
103        assert!(tatara_lisp::domain::registered_keywords().contains(&"defflake"));
104    }
105}