1use 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
13pub type Tokens = crate::Tokens<Nix>;
15
16impl_lang! {
17 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#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
61pub enum Import {
62 Argument(ImportArgument),
64 Inherit(ImportInherit),
66 Variable(ImportVariable),
68 With(ImportWith),
70}
71
72#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
74pub struct ImportArgument(ItemStr);
75
76#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
78pub struct ImportInherit {
79 path: ItemStr,
81 name: ItemStr,
83}
84
85#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
87pub struct ImportVariable {
88 name: ItemStr,
90 value: Tokens,
92}
93
94#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
96pub struct ImportWith {
97 argument: ItemStr,
99 name: ItemStr,
101}
102
103#[derive(Debug, Default)]
105pub struct Format {}
106
107#[derive(Debug, Default)]
109pub struct Config {
110 scoped: bool,
111}
112
113impl Config {
114 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
249pub fn argument<M>(name: M) -> Import
272where
273 M: Into<ItemStr>,
274{
275 Import::Argument(ImportArgument(name.into()))
276}
277
278pub 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
315pub 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
363pub 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}