1use std::fmt::Write as _;
5
6use crate::flake::{FlakeLisp, FlakeOutput, FlakePackage};
7
8#[must_use]
9pub fn render_flake_nix(f: &FlakeLisp) -> String {
10 let mut out = String::new();
11 out.push_str("# Auto-generated from flake.lisp. Re-run `feira nix` after edits.\n\n");
12 out.push_str("{\n");
13 let _ = writeln!(out, " description = {:?};", f.descricao);
14 out.push('\n');
15
16 out.push_str(" inputs = {\n");
18 for entrada in &f.entradas {
19 if let Some(seg) = &entrada.segue {
20 let _ = writeln!(out, " {}.follows = {seg:?};", entrada.nome);
21 } else {
22 let _ = writeln!(out, " {}.url = {:?};", entrada.nome, entrada.url);
23 }
24 }
25 out.push_str(" };\n\n");
26
27 out.push_str(" outputs = { self, ");
29 let names: Vec<&str> = f.entradas.iter().map(|e| e.nome.as_str()).collect();
30 out.push_str(&names.join(", "));
31 out.push_str(", ... }: let\n");
32 out.push_str(" systems = [ \"aarch64-darwin\" \"x86_64-darwin\" \"aarch64-linux\" \"x86_64-linux\" ];\n");
33 out.push_str(" forAllSystems = f: builtins.listToAttrs (map (s: { name = s; value = f s; }) systems);\n");
34 out.push_str(" in {\n");
35
36 if let Some(s) = &f.saidas {
37 render_outputs(s, &mut out);
38 }
39 out.push_str(" };\n}\n");
40 out
41}
42
43fn render_outputs(o: &FlakeOutput, out: &mut String) {
44 if !o.pacotes.is_empty() {
45 out.push_str(" packages = forAllSystems (system:\n");
46 out.push_str(" let pkgs = import nixpkgs { inherit system; }; in {\n");
47 for p in &o.pacotes {
48 render_package(p, out);
49 }
50 out.push_str(" });\n\n");
51 }
52 if o.dev_shells {
53 out.push_str(" devShells = forAllSystems (system:\n");
54 out.push_str(" let pkgs = import nixpkgs { inherit system; }; in {\n");
55 out.push_str(" default = pkgs.mkShell { packages = [ ]; };\n");
56 out.push_str(" });\n");
57 }
58}
59
60fn render_package(p: &FlakePackage, out: &mut String) {
61 let _ = writeln!(
62 out,
63 " {} = pkgs.stdenvNoCC.mkDerivation {{\n\
64 \tpname = {:?};\n\
65 \tversion = \"0.1.0\";\n\
66 \tsrc = {};\n\
67 \tinstallPhase = \"mkdir -p $out && cp -r . $out/\";\n\
68 }};",
69 p.nome,
70 p.nome,
71 nix_src(&p.src)
72 );
73}
74
75fn nix_src(s: &str) -> String {
76 if s.starts_with('.') || s.starts_with('/') {
77 s.to_string()
78 } else {
79 format!("{s:?}")
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use crate::flake::{FlakeInput, FlakeLisp, FlakeOutput, FlakePackage};
87
88 #[test]
89 fn renders_with_inputs_and_package() {
90 let f = FlakeLisp {
91 descricao: "demo".into(),
92 entradas: vec![FlakeInput {
93 nome: "nixpkgs".into(),
94 url: "github:nixos/nixpkgs".into(),
95 segue: None,
96 }],
97 saidas: Some(FlakeOutput {
98 pacotes: vec![FlakePackage {
99 nome: "default".into(),
100 src: ".".into(),
101 }],
102 modulos: vec![],
103 dev_shells: true,
104 }),
105 };
106 let out = render_flake_nix(&f);
107 assert!(out.contains("description = \"demo\""));
108 assert!(out.contains("nixpkgs.url = \"github:nixos/nixpkgs\""));
109 assert!(out.contains("packages = forAllSystems"));
110 assert!(out.contains("devShells = forAllSystems"));
111 }
112
113 #[test]
114 fn lisp_round_trip_and_render() {
115 let src = r#"
116(defflake
117 :descricao "demo"
118 :entradas ((:nome "nixpkgs" :url "github:nixos/nixpkgs")))
119"#;
120 let f = FlakeLisp::from_lisp(src).unwrap();
121 let nix = render_flake_nix(&f);
122 assert!(nix.contains("description = \"demo\""));
123 }
124}