genco/lang/
nix.rs

1//! Nix
2
3use core::fmt::Write as _;
4
5use alloc::collections::BTreeSet;
6use alloc::string::ToString;
7
8use crate as genco;
9use crate::fmt;
10use crate::quote_in;
11use crate::tokens::ItemStr;
12
13/// Tokens
14pub type Tokens = crate::Tokens<Nix>;
15
16impl_lang! {
17    /// Nix
18    pub Nix {
19        type Config = Config;
20        type Format = Format;
21        type Item = Import;
22
23        fn write_quoted(out: &mut fmt::Formatter<'_>, input: &str) -> fmt::Result {
24            super::c_family_write_quoted(out, input)
25        }
26
27        fn format_file(
28            tokens: &Tokens,
29            out: &mut fmt::Formatter<'_>,
30            config: &Self::Config,
31        ) -> fmt::Result {
32            let mut header = Tokens::new();
33
34            if !config.scoped {
35                Self::arguments(&mut header, tokens);
36            }
37            Self::withs(&mut header, tokens);
38            Self::imports(&mut header, tokens);
39            let format = Format::default();
40            header.format(out, config, &format)?;
41            tokens.format(out, config, &format)?;
42            Ok(())
43        }
44    }
45
46    Import {
47        fn format(&self, out: &mut fmt::Formatter<'_>, _: &Config, _: &Format) -> fmt::Result {
48            match self {
49                Import::Argument(import) => out.write_str(&import.0)?,
50                Import::Inherit(import) => out.write_str(&import.name)?,
51                Import::Variable(import) => out.write_str(&import.name)?,
52                Import::With(import) => out.write_str(&import.name)?,
53            }
54            Ok(())
55        }
56    }
57}
58
59/// Import
60#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
61pub enum Import {
62    /// Argument
63    Argument(ImportArgument),
64    /// Inherit
65    Inherit(ImportInherit),
66    /// Variable
67    Variable(ImportVariable),
68    /// With
69    With(ImportWith),
70}
71
72/// ImportArgument
73#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
74pub struct ImportArgument(ItemStr);
75
76/// ImportInherit
77#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
78pub struct ImportInherit {
79    /// Path
80    path: ItemStr,
81    /// Name
82    name: ItemStr,
83}
84
85/// ImportVariable
86#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
87pub struct ImportVariable {
88    /// Name
89    name: ItemStr,
90    /// Value
91    value: Tokens,
92}
93
94/// ImportWith
95#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
96pub struct ImportWith {
97    /// Argument
98    argument: ItemStr,
99    /// Name
100    name: ItemStr,
101}
102
103/// Format
104#[derive(Debug, Default)]
105pub struct Format {}
106
107/// Nix formatting configuration.
108#[derive(Debug, Default)]
109pub struct Config {
110    scoped: bool,
111}
112
113impl Config {
114    /// With scoped
115    pub fn with_scoped(self, scoped: bool) -> Self {
116        Self { scoped }
117    }
118}
119
120impl Nix {
121    fn arguments(out: &mut Tokens, tokens: &Tokens) {
122        let mut arguments = BTreeSet::new();
123
124        for imports in tokens.walk_imports() {
125            match imports {
126                Import::Argument(argument) => {
127                    arguments.insert(argument.0.to_string());
128                }
129                Import::Inherit(inherit) => {
130                    let argument = inherit.path.split('.').next();
131                    if let Some(a) = argument {
132                        arguments.insert(a.to_string());
133                    }
134                }
135                Import::Variable(variable) => {
136                    let value = &variable.value;
137                    for import in value.walk_imports() {
138                        match import {
139                            Import::Inherit(inherit) => {
140                                let argument = inherit.path.split('.').next();
141                                if let Some(a) = argument {
142                                    arguments.insert(a.to_string());
143                                }
144                            }
145                            Import::Argument(argument) => {
146                                arguments.insert(argument.0.to_string());
147                            }
148                            _ => (),
149                        }
150                    }
151                }
152                Import::With(with) => {
153                    let argument = with.argument.split('.').next();
154                    if let Some(a) = argument {
155                        arguments.insert(a.to_string());
156                    }
157                }
158            }
159        }
160
161        out.append("{");
162        out.push();
163        out.indent();
164
165        for argument in arguments {
166            quote_in!(*out => $argument,);
167            out.push();
168        }
169
170        out.append("...");
171        out.push();
172
173        out.unindent();
174        out.append("}:");
175        out.push();
176
177        out.line();
178    }
179
180    fn withs(out: &mut Tokens, tokens: &Tokens) {
181        let mut withs = BTreeSet::new();
182
183        for imports in tokens.walk_imports() {
184            if let Import::With(with) = imports {
185                withs.insert(&with.argument);
186            }
187        }
188
189        if withs.is_empty() {
190            return;
191        }
192
193        for name in withs {
194            quote_in!(*out => with $name;);
195            out.push();
196        }
197
198        out.line();
199    }
200
201    fn imports(out: &mut Tokens, tokens: &Tokens) {
202        let mut inherits = BTreeSet::new();
203        let mut variables = BTreeSet::new();
204
205        for imports in tokens.walk_imports() {
206            match imports {
207                Import::Inherit(inherit) => {
208                    inherits.insert((&inherit.path, &inherit.name));
209                }
210                Import::Variable(variable) => {
211                    let value = &variable.value;
212                    for import in value.walk_imports() {
213                        if let Import::Inherit(inherit) = import {
214                            inherits.insert((&inherit.path, &inherit.name));
215                        }
216                    }
217                    variables.insert((&variable.name, &variable.value));
218                }
219                _ => (),
220            }
221        }
222
223        if inherits.is_empty() && variables.is_empty() {
224            return;
225        }
226
227        out.append("let");
228        out.push();
229        out.indent();
230
231        for (path, name) in inherits {
232            quote_in!(*out => inherit ($path) $name;);
233            out.push();
234        }
235
236        for (name, value) in variables {
237            quote_in!(*out => $name = $value;);
238            out.push();
239        }
240
241        out.unindent();
242        out.append("in");
243        out.push();
244
245        out.line();
246    }
247}
248
249/// ```
250/// use genco::prelude::*;
251///
252/// let cell = nix::argument("cell");
253///
254/// let toks = quote! {
255///     $cell
256/// };
257///
258/// assert_eq!(
259///     vec![
260///         "{",
261///         "    cell,",
262///         "    ...",
263///         "}:",
264///         "",
265///         "cell",
266///     ],
267///     toks.to_file_vec()?
268/// );
269/// # Ok::<_, genco::fmt::Error>(())
270/// ```
271pub fn argument<M>(name: M) -> Import
272where
273    M: Into<ItemStr>,
274{
275    Import::Argument(ImportArgument(name.into()))
276}
277
278/// ```
279/// use genco::prelude::*;
280///
281/// let nixpkgs = nix::inherit("inputs", "nixpkgs");
282///
283/// let toks = quote! {
284///     $nixpkgs
285/// };
286///
287/// assert_eq!(
288///     vec![
289///         "{",
290///         "    inputs,",
291///         "    ...",
292///         "}:",
293///         "",
294///         "let",
295///         "    inherit (inputs) nixpkgs;",
296///         "in",
297///         "",
298///         "nixpkgs",
299///     ],
300///     toks.to_file_vec()?
301/// );
302/// # Ok::<_, genco::fmt::Error>(())
303/// ```
304pub fn inherit<M, N>(path: M, name: N) -> Import
305where
306    M: Into<ItemStr>,
307    N: Into<ItemStr>,
308{
309    Import::Inherit(ImportInherit {
310        path: path.into(),
311        name: name.into(),
312    })
313}
314
315/// ```
316/// use genco::prelude::*;
317///
318/// let nixpkgs = &nix::inherit("inputs", "nixpkgs");
319///
320/// let pkgs = nix::variable("pkgs", quote! {
321///     import $nixpkgs {
322///         inherit ($nixpkgs) system;
323///         config.allowUnfree = true;
324///     }
325/// });
326///
327/// let toks = quote! {
328///     $pkgs
329/// };
330///
331/// assert_eq!(
332///     vec![
333///         "{",
334///         "    inputs,",
335///         "    ...",
336///         "}:",
337///         "",
338///         "let",
339///         "    inherit (inputs) nixpkgs;",
340///         "    pkgs = import nixpkgs {",
341///         "        inherit (nixpkgs) system;",
342///         "        config.allowUnfree = true;",
343///         "    };",
344///         "in",
345///         "",
346///         "pkgs"
347///     ],
348///     toks.to_file_vec()?
349/// );
350/// # Ok::<_, genco::fmt::Error>(())
351/// ```
352pub fn variable<M, N>(name: M, value: N) -> Import
353where
354    M: Into<ItemStr>,
355    N: Into<Tokens>,
356{
357    Import::Variable(ImportVariable {
358        name: name.into(),
359        value: value.into(),
360    })
361}
362
363/// ```
364/// use genco::prelude::*;
365///
366/// let concat_map = nix::with("inputs.nixpkgs.lib", "concatMap");
367/// let list_to_attrs = nix::with("inputs.nixpkgs.lib", "listToAttrs");
368///
369/// let toks = quote! {
370///     $list_to_attrs $concat_map
371/// };
372///
373/// assert_eq!(
374///     vec![
375///         "{",
376///         "    inputs,",
377///         "    ...",
378///         "}:",
379///         "",
380///         "with inputs.nixpkgs.lib;",
381///         "",
382///         "listToAttrs concatMap",
383///     ],
384///     toks.to_file_vec()?
385/// );
386/// # Ok::<_, genco::fmt::Error>(())
387/// ```
388pub fn with<M, N>(argument: M, name: N) -> Import
389where
390    M: Into<ItemStr>,
391    N: Into<ItemStr>,
392{
393    Import::With(ImportWith {
394        argument: argument.into(),
395        name: name.into(),
396    })
397}