1use super::functions::*;
6use std::collections::{HashMap, HashSet, VecDeque};
7
8#[allow(dead_code)]
10pub struct NixModuleValidator;
11impl NixModuleValidator {
12 pub fn validate(module: &NixModule) -> NixModuleValidationReport {
17 let mut report = NixModuleValidationReport::default();
18 match &module.top_level {
19 NixExpr::Lambda(func) => match &*func.body {
20 NixExpr::AttrSet(attrs) => {
21 let has_options = attrs.iter().any(|a| a.name == "options");
22 let has_config = attrs.iter().any(|a| a.name == "config");
23 if !has_options && !has_config {
24 report
25 .warnings
26 .push("Module body has neither 'options' nor 'config' keys".into());
27 }
28 }
29 _ => {
30 report
31 .errors
32 .push("NixOS module body should be an attribute set".into());
33 }
34 },
35 _ => {
36 if module.is_module_file {
37 report
38 .errors
39 .push("NixOS module file should start with a lambda".into());
40 }
41 }
42 }
43 report
44 }
45}
46#[allow(dead_code)]
48pub struct NixPkgsHelper;
49impl NixPkgsHelper {
50 pub fn fetch_url(url: &str, sha256: &str) -> NixExpr {
52 nix_apply(
53 nix_select(nix_var("pkgs"), "fetchurl"),
54 nix_set(vec![("url", nix_str(url)), ("sha256", nix_str(sha256))]),
55 )
56 }
57 pub fn fetch_from_github(owner: &str, repo: &str, rev: &str, sha256: &str) -> NixExpr {
59 nix_apply(
60 nix_select(nix_var("pkgs"), "fetchFromGitHub"),
61 nix_set(vec![
62 ("owner", nix_str(owner)),
63 ("repo", nix_str(repo)),
64 ("rev", nix_str(rev)),
65 ("sha256", nix_str(sha256)),
66 ]),
67 )
68 }
69 pub fn fetch_git(url: &str, rev: &str) -> NixExpr {
71 nix_apply(
72 nix_select(nix_var("pkgs"), "fetchGit"),
73 nix_set(vec![("url", nix_str(url)), ("rev", nix_str(rev))]),
74 )
75 }
76 pub fn call_package(path: &str) -> NixExpr {
78 nix_apply(
79 nix_apply(nix_select(nix_var("pkgs"), "callPackage"), nix_path(path)),
80 nix_set(vec![]),
81 )
82 }
83 pub fn write_shell_script_bin(name: &str, content: &str) -> NixExpr {
85 nix_apply(
86 nix_apply(
87 nix_select(nix_var("pkgs"), "writeShellScriptBin"),
88 nix_str(name),
89 ),
90 NixExpr::Multiline(content.to_string()),
91 )
92 }
93 pub fn write_text(name: &str, content: &str) -> NixExpr {
95 nix_apply(
96 nix_apply(nix_select(nix_var("pkgs"), "writeText"), nix_str(name)),
97 nix_str(content),
98 )
99 }
100 pub fn symlink_join(name: &str, paths: Vec<NixExpr>) -> NixExpr {
102 nix_apply(
103 nix_select(nix_var("pkgs"), "symlinkJoin"),
104 nix_set(vec![("name", nix_str(name)), ("paths", nix_list(paths))]),
105 )
106 }
107}
108#[allow(dead_code)]
110pub struct NixFormatter {
111 pub indent: usize,
113 pub max_line_len: usize,
115 pub sort_attrs: bool,
117}
118impl NixFormatter {
119 pub fn new() -> Self {
121 NixFormatter {
122 indent: 2,
123 max_line_len: 80,
124 sort_attrs: true,
125 }
126 }
127 pub fn format(&self, expr: &NixExpr) -> String {
129 if self.sort_attrs {
130 let sorted = self.sort_attrset(expr);
131 sorted.emit(0)
132 } else {
133 expr.emit(0)
134 }
135 }
136 pub(super) fn sort_attrset(&self, expr: &NixExpr) -> NixExpr {
138 match expr {
139 NixExpr::AttrSet(attrs) => {
140 let mut sorted = attrs
141 .iter()
142 .map(|a| NixAttr {
143 name: a.name.clone(),
144 value: self.sort_attrset(&a.value),
145 })
146 .collect::<Vec<_>>();
147 sorted.sort_by(|a, b| a.name.cmp(&b.name));
148 NixExpr::AttrSet(sorted)
149 }
150 NixExpr::Rec(attrs) => {
151 let mut sorted = attrs
152 .iter()
153 .map(|a| NixAttr {
154 name: a.name.clone(),
155 value: self.sort_attrset(&a.value),
156 })
157 .collect::<Vec<_>>();
158 sorted.sort_by(|a, b| a.name.cmp(&b.name));
159 NixExpr::Rec(sorted)
160 }
161 other => other.clone(),
162 }
163 }
164 pub fn estimate_width(expr: &NixExpr) -> usize {
166 expr.emit(0).lines().map(|l| l.len()).max().unwrap_or(0)
167 }
168}
169#[allow(dead_code)]
170#[derive(Debug, Clone, PartialEq)]
171pub enum NixPassPhase {
172 Analysis,
173 Transformation,
174 Verification,
175 Cleanup,
176}
177impl NixPassPhase {
178 #[allow(dead_code)]
179 pub fn name(&self) -> &str {
180 match self {
181 NixPassPhase::Analysis => "analysis",
182 NixPassPhase::Transformation => "transformation",
183 NixPassPhase::Verification => "verification",
184 NixPassPhase::Cleanup => "cleanup",
185 }
186 }
187 #[allow(dead_code)]
188 pub fn is_modifying(&self) -> bool {
189 matches!(self, NixPassPhase::Transformation | NixPassPhase::Cleanup)
190 }
191}
192#[allow(dead_code)]
196pub struct NixTypeChecker;
197impl NixTypeChecker {
198 pub fn infer(expr: &NixExpr) -> NixType {
200 match expr {
201 NixExpr::Int(_) => NixType::Int,
202 NixExpr::Float(_) => NixType::Float,
203 NixExpr::Bool(_) => NixType::Bool,
204 NixExpr::Str(_) | NixExpr::Antiquote(_, _, _) | NixExpr::Multiline(_) => {
205 NixType::String
206 }
207 NixExpr::Path(_) => NixType::Path,
208 NixExpr::Null => NixType::NullType,
209 NixExpr::List(items) => {
210 let elem_ty = items.first().map(Self::infer).unwrap_or(NixType::NullType);
211 NixType::List(Box::new(elem_ty))
212 }
213 NixExpr::AttrSet(attrs) | NixExpr::Rec(attrs) => NixType::AttrSet(
214 attrs
215 .iter()
216 .map(|a| (a.name.clone(), Self::infer(&a.value)))
217 .collect(),
218 ),
219 NixExpr::Lambda(_) => {
220 NixType::Function(Box::new(NixType::NullType), Box::new(NixType::NullType))
221 }
222 NixExpr::If(_, t, f) => {
223 let ty_t = Self::infer(t);
224 let _ty_f = Self::infer(f);
225 ty_t
226 }
227 NixExpr::Let(_, body) => Self::infer(body),
228 NixExpr::With(_, body) => Self::infer(body),
229 NixExpr::Apply(_, _) => NixType::NullType,
230 NixExpr::Select(_, _, _) => NixType::NullType,
231 NixExpr::Import(_) => NixType::AttrSet(vec![]),
232 NixExpr::Var(_) => NixType::NullType,
233 NixExpr::UnOp(op, e) => {
234 if op == "!" {
235 NixType::Bool
236 } else {
237 Self::infer(e)
238 }
239 }
240 NixExpr::BinOp(op, lhs, _) => match op.as_str() {
241 "==" | "!=" | "<" | ">" | "<=" | ">=" | "&&" | "||" => NixType::Bool,
242 "++" => NixType::List(Box::new(NixType::NullType)),
243 "//" => NixType::AttrSet(vec![]),
244 _ => Self::infer(lhs),
245 },
246 _ => NixType::NullType,
247 }
248 }
249}
250#[allow(dead_code)]
251pub struct NixConstantFoldingHelper;
252impl NixConstantFoldingHelper {
253 #[allow(dead_code)]
254 pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
255 a.checked_add(b)
256 }
257 #[allow(dead_code)]
258 pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
259 a.checked_sub(b)
260 }
261 #[allow(dead_code)]
262 pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
263 a.checked_mul(b)
264 }
265 #[allow(dead_code)]
266 pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
267 if b == 0 {
268 None
269 } else {
270 a.checked_div(b)
271 }
272 }
273 #[allow(dead_code)]
274 pub fn fold_add_f64(a: f64, b: f64) -> f64 {
275 a + b
276 }
277 #[allow(dead_code)]
278 pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
279 a * b
280 }
281 #[allow(dead_code)]
282 pub fn fold_neg_i64(a: i64) -> Option<i64> {
283 a.checked_neg()
284 }
285 #[allow(dead_code)]
286 pub fn fold_not_bool(a: bool) -> bool {
287 !a
288 }
289 #[allow(dead_code)]
290 pub fn fold_and_bool(a: bool, b: bool) -> bool {
291 a && b
292 }
293 #[allow(dead_code)]
294 pub fn fold_or_bool(a: bool, b: bool) -> bool {
295 a || b
296 }
297 #[allow(dead_code)]
298 pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
299 a.checked_shl(b)
300 }
301 #[allow(dead_code)]
302 pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
303 a.checked_shr(b)
304 }
305 #[allow(dead_code)]
306 pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
307 if b == 0 {
308 None
309 } else {
310 Some(a % b)
311 }
312 }
313 #[allow(dead_code)]
314 pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
315 a & b
316 }
317 #[allow(dead_code)]
318 pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
319 a | b
320 }
321 #[allow(dead_code)]
322 pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
323 a ^ b
324 }
325 #[allow(dead_code)]
326 pub fn fold_bitnot_i64(a: i64) -> i64 {
327 !a
328 }
329}
330#[allow(dead_code)]
331#[derive(Debug, Clone)]
332pub struct NixWorklist {
333 pub(super) items: std::collections::VecDeque<u32>,
334 pub(super) in_worklist: std::collections::HashSet<u32>,
335}
336impl NixWorklist {
337 #[allow(dead_code)]
338 pub fn new() -> Self {
339 NixWorklist {
340 items: std::collections::VecDeque::new(),
341 in_worklist: std::collections::HashSet::new(),
342 }
343 }
344 #[allow(dead_code)]
345 pub fn push(&mut self, item: u32) -> bool {
346 if self.in_worklist.insert(item) {
347 self.items.push_back(item);
348 true
349 } else {
350 false
351 }
352 }
353 #[allow(dead_code)]
354 pub fn pop(&mut self) -> Option<u32> {
355 let item = self.items.pop_front()?;
356 self.in_worklist.remove(&item);
357 Some(item)
358 }
359 #[allow(dead_code)]
360 pub fn is_empty(&self) -> bool {
361 self.items.is_empty()
362 }
363 #[allow(dead_code)]
364 pub fn len(&self) -> usize {
365 self.items.len()
366 }
367 #[allow(dead_code)]
368 pub fn contains(&self, item: u32) -> bool {
369 self.in_worklist.contains(&item)
370 }
371}
372#[allow(dead_code)]
373pub struct NixPassRegistry {
374 pub(super) configs: Vec<NixPassConfig>,
375 pub(super) stats: std::collections::HashMap<String, NixPassStats>,
376}
377impl NixPassRegistry {
378 #[allow(dead_code)]
379 pub fn new() -> Self {
380 NixPassRegistry {
381 configs: Vec::new(),
382 stats: std::collections::HashMap::new(),
383 }
384 }
385 #[allow(dead_code)]
386 pub fn register(&mut self, config: NixPassConfig) {
387 self.stats
388 .insert(config.pass_name.clone(), NixPassStats::new());
389 self.configs.push(config);
390 }
391 #[allow(dead_code)]
392 pub fn enabled_passes(&self) -> Vec<&NixPassConfig> {
393 self.configs.iter().filter(|c| c.enabled).collect()
394 }
395 #[allow(dead_code)]
396 pub fn get_stats(&self, name: &str) -> Option<&NixPassStats> {
397 self.stats.get(name)
398 }
399 #[allow(dead_code)]
400 pub fn total_passes(&self) -> usize {
401 self.configs.len()
402 }
403 #[allow(dead_code)]
404 pub fn enabled_count(&self) -> usize {
405 self.enabled_passes().len()
406 }
407 #[allow(dead_code)]
408 pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
409 if let Some(stats) = self.stats.get_mut(name) {
410 stats.record_run(changes, time_ms, iter);
411 }
412 }
413}
414#[allow(dead_code)]
415#[derive(Debug, Clone)]
416pub struct NixCacheEntry {
417 pub key: String,
418 pub data: Vec<u8>,
419 pub timestamp: u64,
420 pub valid: bool,
421}
422#[allow(dead_code)]
423#[derive(Debug, Clone)]
424pub struct NixPassConfig {
425 pub phase: NixPassPhase,
426 pub enabled: bool,
427 pub max_iterations: u32,
428 pub debug_output: bool,
429 pub pass_name: String,
430}
431impl NixPassConfig {
432 #[allow(dead_code)]
433 pub fn new(name: impl Into<String>, phase: NixPassPhase) -> Self {
434 NixPassConfig {
435 phase,
436 enabled: true,
437 max_iterations: 10,
438 debug_output: false,
439 pass_name: name.into(),
440 }
441 }
442 #[allow(dead_code)]
443 pub fn disabled(mut self) -> Self {
444 self.enabled = false;
445 self
446 }
447 #[allow(dead_code)]
448 pub fn with_debug(mut self) -> Self {
449 self.debug_output = true;
450 self
451 }
452 #[allow(dead_code)]
453 pub fn max_iter(mut self, n: u32) -> Self {
454 self.max_iterations = n;
455 self
456 }
457}
458pub struct NixBackend {
462 pub indent_width: usize,
464}
465impl NixBackend {
466 pub fn new() -> Self {
468 NixBackend { indent_width: 2 }
469 }
470 pub fn emit_expr(&self, expr: &NixExpr, indent: usize) -> String {
472 expr.emit(indent)
473 }
474 pub fn emit_module(&self, module: &NixModule) -> String {
476 module.emit()
477 }
478 pub fn emit_attr(&self, attr: &NixAttr, indent: usize) -> String {
480 attr.emit(indent)
481 }
482 pub fn emit_function(&self, func: &NixFunction, indent: usize) -> String {
484 func.emit(indent)
485 }
486 #[allow(clippy::too_many_arguments)]
488 pub fn make_derivation(
489 &self,
490 name: &str,
491 version: &str,
492 src: NixExpr,
493 build_inputs: Vec<NixExpr>,
494 build_phase: Option<&str>,
495 install_phase: Option<&str>,
496 extra_attrs: Vec<NixAttr>,
497 ) -> NixExpr {
498 let mut attrs = vec![
499 NixAttr::new("name", NixExpr::Str(format!("{}-{}", name, version))),
500 NixAttr::new("version", NixExpr::Str(version.to_string())),
501 NixAttr::new("src", src),
502 NixAttr::new("buildInputs", NixExpr::List(build_inputs)),
503 ];
504 if let Some(bp) = build_phase {
505 attrs.push(NixAttr::new(
506 "buildPhase",
507 NixExpr::Multiline(bp.to_string()),
508 ));
509 }
510 if let Some(ip) = install_phase {
511 attrs.push(NixAttr::new(
512 "installPhase",
513 NixExpr::Multiline(ip.to_string()),
514 ));
515 }
516 attrs.extend(extra_attrs);
517 NixExpr::Apply(
518 Box::new(NixExpr::Select(
519 Box::new(NixExpr::Var("pkgs".into())),
520 "stdenv.mkDerivation".into(),
521 None,
522 )),
523 Box::new(NixExpr::AttrSet(attrs)),
524 )
525 }
526 pub fn make_nixos_module(
528 &self,
529 module_args: Vec<(String, Option<NixExpr>)>,
530 options: Vec<NixAttr>,
531 config: Vec<NixAttr>,
532 ) -> NixModule {
533 let body = NixExpr::AttrSet(vec![
534 NixAttr::new("options", NixExpr::AttrSet(options)),
535 NixAttr::new("config", NixExpr::AttrSet(config)),
536 ]);
537 let top = NixExpr::Lambda(Box::new(NixFunction {
538 pattern: NixPattern::AttrPattern {
539 attrs: module_args,
540 ellipsis: true,
541 },
542 body: Box::new(body),
543 }));
544 NixModule::nixos_module(top)
545 }
546 pub fn make_overlay(&self, attrs: Vec<NixAttr>) -> NixExpr {
548 NixExpr::Lambda(Box::new(NixFunction {
549 pattern: NixPattern::Ident("final".into()),
550 body: Box::new(NixExpr::Lambda(Box::new(NixFunction {
551 pattern: NixPattern::Ident("prev".into()),
552 body: Box::new(NixExpr::AttrSet(attrs)),
553 }))),
554 }))
555 }
556 pub fn make_flake(&self, description: &str, outputs_attrs: Vec<NixAttr>) -> NixExpr {
558 NixExpr::AttrSet(vec![
559 NixAttr::new("description", NixExpr::Str(description.to_string())),
560 NixAttr::new(
561 "outputs",
562 NixExpr::Lambda(Box::new(NixFunction {
563 pattern: NixPattern::AttrPattern {
564 attrs: vec![("self".into(), None), ("nixpkgs".into(), None)],
565 ellipsis: true,
566 },
567 body: Box::new(NixExpr::AttrSet(outputs_attrs)),
568 })),
569 ),
570 ])
571 }
572}
573#[allow(dead_code)]
575pub struct NixFlakeHelper;
576impl NixFlakeHelper {
577 pub fn input(name: &str, url: &str) -> NixAttr {
579 NixAttr::new(name, nix_set(vec![("url", nix_str(url))]))
580 }
581 pub fn non_flake_input(name: &str, url: &str) -> NixAttr {
583 NixAttr::new(
584 name,
585 nix_set(vec![("url", nix_str(url)), ("flake", nix_bool(false))]),
586 )
587 }
588 pub fn follows(input_name: &str, follows_from: &str) -> NixAttr {
590 NixAttr::new(
591 &format!("inputs.{}.follows", input_name),
592 nix_str(follows_from),
593 )
594 }
595 pub fn full_flake(
597 description: &str,
598 inputs: Vec<NixAttr>,
599 output_body: NixExpr,
600 input_args: Vec<(String, Option<NixExpr>)>,
601 ) -> NixExpr {
602 NixExpr::AttrSet(vec![
603 NixAttr::new("description", nix_str(description)),
604 NixAttr::new("inputs", NixExpr::AttrSet(inputs)),
605 NixAttr::new(
606 "outputs",
607 NixExpr::Lambda(Box::new(NixFunction {
608 pattern: NixPattern::AttrPattern {
609 attrs: input_args,
610 ellipsis: true,
611 },
612 body: Box::new(output_body),
613 })),
614 ),
615 ])
616 }
617 pub fn per_system_let(body: NixExpr) -> NixExpr {
619 NixExpr::Lambda(Box::new(NixFunction {
620 pattern: NixPattern::Ident("system".into()),
621 body: Box::new(nix_let(
622 vec![(
623 "pkgs",
624 NixExpr::Select(
625 Box::new(NixExpr::Select(
626 Box::new(nix_var("nixpkgs")),
627 "legacyPackages".into(),
628 None,
629 )),
630 "system".into(),
631 None,
632 ),
633 )],
634 body,
635 )),
636 }))
637 }
638}
639#[allow(dead_code)]
640#[derive(Debug, Clone)]
641pub struct NixAnalysisCache {
642 pub(super) entries: std::collections::HashMap<String, NixCacheEntry>,
643 pub(super) max_size: usize,
644 pub(super) hits: u64,
645 pub(super) misses: u64,
646}
647impl NixAnalysisCache {
648 #[allow(dead_code)]
649 pub fn new(max_size: usize) -> Self {
650 NixAnalysisCache {
651 entries: std::collections::HashMap::new(),
652 max_size,
653 hits: 0,
654 misses: 0,
655 }
656 }
657 #[allow(dead_code)]
658 pub fn get(&mut self, key: &str) -> Option<&NixCacheEntry> {
659 if self.entries.contains_key(key) {
660 self.hits += 1;
661 self.entries.get(key)
662 } else {
663 self.misses += 1;
664 None
665 }
666 }
667 #[allow(dead_code)]
668 pub fn insert(&mut self, key: String, data: Vec<u8>) {
669 if self.entries.len() >= self.max_size {
670 if let Some(oldest) = self.entries.keys().next().cloned() {
671 self.entries.remove(&oldest);
672 }
673 }
674 self.entries.insert(
675 key.clone(),
676 NixCacheEntry {
677 key,
678 data,
679 timestamp: 0,
680 valid: true,
681 },
682 );
683 }
684 #[allow(dead_code)]
685 pub fn invalidate(&mut self, key: &str) {
686 if let Some(entry) = self.entries.get_mut(key) {
687 entry.valid = false;
688 }
689 }
690 #[allow(dead_code)]
691 pub fn clear(&mut self) {
692 self.entries.clear();
693 }
694 #[allow(dead_code)]
695 pub fn hit_rate(&self) -> f64 {
696 let total = self.hits + self.misses;
697 if total == 0 {
698 return 0.0;
699 }
700 self.hits as f64 / total as f64
701 }
702 #[allow(dead_code)]
703 pub fn size(&self) -> usize {
704 self.entries.len()
705 }
706}
707#[derive(Debug, Clone, PartialEq)]
709pub struct NixFunction {
710 pub pattern: NixPattern,
712 pub body: Box<NixExpr>,
714}
715impl NixFunction {
716 pub fn new(arg: impl Into<String>, body: NixExpr) -> Self {
718 NixFunction {
719 pattern: NixPattern::Ident(arg.into()),
720 body: Box::new(body),
721 }
722 }
723 pub fn with_attr_pattern(
725 attrs: Vec<(String, Option<NixExpr>)>,
726 ellipsis: bool,
727 body: NixExpr,
728 ) -> Self {
729 NixFunction {
730 pattern: NixPattern::AttrPattern { attrs, ellipsis },
731 body: Box::new(body),
732 }
733 }
734 pub(super) fn emit(&self, indent: usize) -> String {
735 format!("{}: {}", self.pattern.emit(), self.body.emit(indent))
736 }
737}
738#[derive(Debug, Clone, PartialEq)]
740pub struct NixLetBinding {
741 pub name: String,
743 pub value: NixExpr,
745}
746impl NixLetBinding {
747 pub fn new(name: impl Into<String>, value: NixExpr) -> Self {
748 NixLetBinding {
749 name: name.into(),
750 value,
751 }
752 }
753}
754#[allow(dead_code)]
755#[derive(Debug, Clone)]
756pub struct NixDominatorTree {
757 pub idom: Vec<Option<u32>>,
758 pub dom_children: Vec<Vec<u32>>,
759 pub dom_depth: Vec<u32>,
760}
761impl NixDominatorTree {
762 #[allow(dead_code)]
763 pub fn new(size: usize) -> Self {
764 NixDominatorTree {
765 idom: vec![None; size],
766 dom_children: vec![Vec::new(); size],
767 dom_depth: vec![0; size],
768 }
769 }
770 #[allow(dead_code)]
771 pub fn set_idom(&mut self, node: usize, idom: u32) {
772 self.idom[node] = Some(idom);
773 }
774 #[allow(dead_code)]
775 pub fn dominates(&self, a: usize, b: usize) -> bool {
776 if a == b {
777 return true;
778 }
779 let mut cur = b;
780 loop {
781 match self.idom[cur] {
782 Some(parent) if parent as usize == a => return true,
783 Some(parent) if parent as usize == cur => return false,
784 Some(parent) => cur = parent as usize,
785 None => return false,
786 }
787 }
788 }
789 #[allow(dead_code)]
790 pub fn depth(&self, node: usize) -> u32 {
791 self.dom_depth.get(node).copied().unwrap_or(0)
792 }
793}
794#[allow(dead_code)]
796pub struct NixOptionalHelper;
797impl NixOptionalHelper {
798 pub fn map_nullable(value: NixExpr, default: NixExpr, f: NixExpr) -> NixExpr {
800 nix_if(
801 NixExpr::BinOp(
802 "==".into(),
803 Box::new(value.clone()),
804 Box::new(NixExpr::Null),
805 ),
806 default,
807 nix_apply(f, value),
808 )
809 }
810 pub fn fmap_nullable(value: NixExpr, f: NixExpr) -> NixExpr {
812 Self::map_nullable(value.clone(), NixExpr::Null, f)
813 }
814 pub fn with_default(base: NixExpr, attr: &str, default: NixExpr) -> NixExpr {
816 NixExpr::Select(Box::new(base), attr.to_string(), Some(Box::new(default)))
817 }
818 pub fn optionals(cond: NixExpr, list: NixExpr) -> NixExpr {
820 nix_apply(
821 nix_apply(nix_select(nix_var("lib"), "optionals"), cond),
822 list,
823 )
824 }
825 pub fn optional(cond: NixExpr, value: NixExpr) -> NixExpr {
827 nix_apply(
828 nix_apply(nix_select(nix_var("lib"), "optional"), cond),
829 value,
830 )
831 }
832}
833#[allow(dead_code)]
835#[derive(Debug, Clone)]
836pub enum NixStringPart {
837 Literal(String),
839 Interp(NixExpr),
841}
842#[derive(Debug, Clone, PartialEq, Eq)]
844pub enum NixType {
845 Int,
847 Float,
849 Bool,
851 String,
853 Path,
855 NullType,
857 List(Box<NixType>),
859 AttrSet(Vec<(String, NixType)>),
861 Function(Box<NixType>, Box<NixType>),
863 Derivation,
865}
866#[derive(Debug, Clone, PartialEq)]
868pub enum NixPattern {
869 Ident(String),
871 AttrPattern {
873 attrs: Vec<(String, Option<NixExpr>)>,
875 ellipsis: bool,
877 },
878 AtPattern {
881 ident: String,
883 ident_first: bool,
885 attrs: Vec<(String, Option<NixExpr>)>,
887 ellipsis: bool,
889 },
890}
891impl NixPattern {
892 pub(super) fn emit(&self) -> String {
893 match self {
894 NixPattern::Ident(x) => x.clone(),
895 NixPattern::AttrPattern { attrs, ellipsis } => {
896 let mut parts: Vec<String> = attrs
897 .iter()
898 .map(|(name, default)| match default {
899 None => name.clone(),
900 Some(d) => format!("{} ? {}", name, d.emit(0)),
901 })
902 .collect();
903 if *ellipsis {
904 parts.push("...".into());
905 }
906 format!("{{ {} }}", parts.join(", "))
907 }
908 NixPattern::AtPattern {
909 ident,
910 ident_first,
911 attrs,
912 ellipsis,
913 } => {
914 let mut parts: Vec<String> = attrs
915 .iter()
916 .map(|(name, default)| match default {
917 None => name.clone(),
918 Some(d) => format!("{} ? {}", name, d.emit(0)),
919 })
920 .collect();
921 if *ellipsis {
922 parts.push("...".into());
923 }
924 let set_pat = format!("{{ {} }}", parts.join(", "));
925 if *ident_first {
926 format!("{} @ {}", ident, set_pat)
927 } else {
928 format!("{} @ {}", set_pat, ident)
929 }
930 }
931 }
932 }
933}
934#[allow(dead_code)]
936pub struct NixSystemdHelper;
937impl NixSystemdHelper {
938 #[allow(clippy::too_many_arguments)]
940 pub fn make_service(
941 description: &str,
942 exec_start: &str,
943 after: Vec<&str>,
944 wants: Vec<&str>,
945 restart: &str,
946 user: Option<&str>,
947 extra_attrs: Vec<NixAttr>,
948 ) -> NixExpr {
949 let mut attrs = vec![
950 NixAttr::new("description", nix_str(description)),
951 NixAttr::new("after", nix_list(after.into_iter().map(nix_str).collect())),
952 NixAttr::new("wants", nix_list(wants.into_iter().map(nix_str).collect())),
953 NixAttr::new(
954 "serviceConfig",
955 NixExpr::AttrSet(vec![
956 NixAttr::new("ExecStart", nix_str(exec_start)),
957 NixAttr::new("Restart", nix_str(restart)),
958 ]),
959 ),
960 ];
961 if let Some(u) = user {
962 if let NixExpr::AttrSet(ref mut service_attrs) = attrs[3].value {
963 service_attrs.push(NixAttr::new("User", nix_str(u)));
964 }
965 }
966 attrs.extend(extra_attrs);
967 NixExpr::AttrSet(attrs)
968 }
969 pub fn make_timer(on_calendar: &str, description: &str) -> NixExpr {
971 nix_set(vec![
972 ("description", nix_str(description)),
973 (
974 "timerConfig",
975 nix_set(vec![
976 ("OnCalendar", nix_str(on_calendar)),
977 ("Persistent", nix_bool(true)),
978 ]),
979 ),
980 ])
981 }
982}
983#[derive(Debug, Clone, PartialEq)]
985pub enum NixExpr {
986 Int(i64),
988 Float(f64),
990 Bool(bool),
992 Str(String),
994 Path(String),
996 Null,
998 List(Vec<NixExpr>),
1000 AttrSet(Vec<NixAttr>),
1002 Rec(Vec<NixAttr>),
1004 With(Box<NixExpr>, Box<NixExpr>),
1006 Let(Vec<NixLetBinding>, Box<NixExpr>),
1008 If(Box<NixExpr>, Box<NixExpr>, Box<NixExpr>),
1010 Lambda(Box<NixFunction>),
1012 Apply(Box<NixExpr>, Box<NixExpr>),
1014 Select(Box<NixExpr>, String, Option<Box<NixExpr>>),
1016 Assert(Box<NixExpr>, Box<NixExpr>),
1018 Var(String),
1020 Inherit(Option<Box<NixExpr>>, Vec<String>),
1022 Antiquote(String, Box<NixExpr>, String),
1024 Multiline(String),
1026 UnOp(String, Box<NixExpr>),
1028 BinOp(String, Box<NixExpr>, Box<NixExpr>),
1030 Import(Box<NixExpr>),
1032}
1033impl NixExpr {
1034 pub fn emit(&self, indent: usize) -> String {
1036 let ind = " ".repeat(indent);
1037 let ind2 = " ".repeat(indent + 2);
1038 match self {
1039 NixExpr::Int(n) => n.to_string(),
1040 NixExpr::Float(f) => {
1041 let s = format!("{}", f);
1042 if s.contains('.') || s.contains('e') {
1043 s
1044 } else {
1045 format!("{}.0", s)
1046 }
1047 }
1048 NixExpr::Bool(b) => if *b { "true" } else { "false" }.into(),
1049 NixExpr::Str(s) => format!("\"{}\"", escape_nix_string(s)),
1050 NixExpr::Path(p) => p.clone(),
1051 NixExpr::Null => "null".into(),
1052 NixExpr::List(elems) => {
1053 if elems.is_empty() {
1054 "[ ]".into()
1055 } else {
1056 let items: Vec<String> = elems.iter().map(|e| e.emit(indent + 2)).collect();
1057 format!(
1058 "[\n{}{}\n{}]",
1059 ind2,
1060 items.join(format!("\n{}", ind2).as_str()),
1061 ind
1062 )
1063 }
1064 }
1065 NixExpr::AttrSet(attrs) => {
1066 if attrs.is_empty() {
1067 "{ }".into()
1068 } else {
1069 let lines: Vec<String> = attrs.iter().map(|a| a.emit(indent + 2)).collect();
1070 format!("{{\n{}\n{}}}", lines.join("\n"), ind)
1071 }
1072 }
1073 NixExpr::Rec(attrs) => {
1074 if attrs.is_empty() {
1075 "rec { }".into()
1076 } else {
1077 let lines: Vec<String> = attrs.iter().map(|a| a.emit(indent + 2)).collect();
1078 format!("rec {{\n{}\n{}}}", lines.join("\n"), ind)
1079 }
1080 }
1081 NixExpr::With(src, body) => {
1082 format!("with {};\n{}{}", src.emit(indent), ind, body.emit(indent))
1083 }
1084 NixExpr::Let(bindings, body) => {
1085 let mut out = "let\n".to_string();
1086 for b in bindings {
1087 out.push_str(&format!(
1088 "{} {} = {};\n",
1089 ind,
1090 b.name,
1091 b.value.emit(indent + 2)
1092 ));
1093 }
1094 out.push_str(&format!("{}in\n{}{}", ind, ind2, body.emit(indent + 2)));
1095 out
1096 }
1097 NixExpr::If(cond, then_e, else_e) => {
1098 format!(
1099 "if {}\nthen {}{}\nelse {}{}",
1100 cond.emit(indent),
1101 ind2,
1102 then_e.emit(indent + 2),
1103 ind2,
1104 else_e.emit(indent + 2),
1105 )
1106 }
1107 NixExpr::Lambda(func) => func.emit(indent),
1108 NixExpr::Apply(func, arg) => {
1109 let fs = func.emit(indent);
1110 let as_ = arg_needs_parens(arg);
1111 if as_ {
1112 format!("{} ({})", fs, arg.emit(indent))
1113 } else {
1114 format!("{} {}", fs, arg.emit(indent))
1115 }
1116 }
1117 NixExpr::Select(expr, attr, default) => {
1118 let base = format!("{}.{}", expr.emit(indent), attr);
1119 match default {
1120 None => base,
1121 Some(d) => format!("{} or {}", base, d.emit(indent)),
1122 }
1123 }
1124 NixExpr::Assert(cond, body) => {
1125 format!(
1126 "assert {};\n{}{}",
1127 cond.emit(indent),
1128 ind,
1129 body.emit(indent)
1130 )
1131 }
1132 NixExpr::Var(name) => name.clone(),
1133 NixExpr::Inherit(src, names) => {
1134 let src_s = match src {
1135 None => String::new(),
1136 Some(s) => format!(" ({})", s.emit(indent)),
1137 };
1138 format!("inherit{} {};", src_s, names.join(" "))
1139 }
1140 NixExpr::Antiquote(prefix, expr, suffix) => {
1141 format!(
1142 "\"{}${{{}}}{}\"",
1143 escape_nix_string(prefix),
1144 expr.emit(indent),
1145 escape_nix_string(suffix)
1146 )
1147 }
1148 NixExpr::Multiline(content) => format!("''\n{}\n{}''", content, ind),
1149 NixExpr::UnOp(op, e) => format!("{}{}", op, e.emit(indent)),
1150 NixExpr::BinOp(op, lhs, rhs) => {
1151 format!("({} {} {})", lhs.emit(indent), op, rhs.emit(indent))
1152 }
1153 NixExpr::Import(path) => format!("import {}", path.emit(indent)),
1154 }
1155 }
1156}
1157#[allow(dead_code)]
1159#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1160pub enum NixBuiltin {
1161 IsInt,
1162 IsFloat,
1163 IsBool,
1164 IsString,
1165 IsPath,
1166 IsNull,
1167 IsList,
1168 IsAttrs,
1169 IsFunction,
1170 StringLength,
1171 SubString,
1172 Concat,
1173 ConcatStringsSep,
1174 ToString,
1175 ParseInt,
1176 ParseFloat,
1177 ToLower,
1178 ToUpper,
1179 HasSuffix,
1180 HasPrefix,
1181 StringSplit,
1182 ReplaceStrings,
1183 Length,
1184 Head,
1185 Tail,
1186 Filter,
1187 Map,
1188 FoldLeft,
1189 FoldRight,
1190 Concatmap,
1191 Elem,
1192 ElemAt,
1193 Flatten,
1194 Sort,
1195 Partition,
1196 GroupBy,
1197 ZipAttrsWith,
1198 Unique,
1199 Reversal,
1200 Intersect,
1201 SubtractLists,
1202 ListToAttrs,
1203 AttrNames,
1204 AttrValues,
1205 HasAttr,
1206 GetAttr,
1207 Intersect2,
1208 RemoveAttrs,
1209 MapAttrs,
1210 FilterAttrs,
1211 Foldl2,
1212 ToJSON,
1213 FromJSON,
1214 ToTOML,
1215 ReadFile,
1216 ReadDir,
1217 PathExists,
1218 BaseName,
1219 DirOf,
1220 ToPath,
1221 StorePath,
1222 DerivationStrict,
1223 PlaceholderOf,
1224 HashString,
1225 HashFile,
1226 TypeOf,
1227 Seq,
1228 DeepSeq,
1229 Trace,
1230 Abort,
1231 Throw,
1232 CurrentSystem,
1233 CurrentTime,
1234 NixVersion,
1235}
1236#[derive(Debug, Clone, PartialEq)]
1249pub struct NixModule {
1250 pub top_level: NixExpr,
1252 pub is_module_file: bool,
1254}
1255impl NixModule {
1256 pub fn new(top_level: NixExpr) -> Self {
1258 NixModule {
1259 top_level,
1260 is_module_file: false,
1261 }
1262 }
1263 pub fn nixos_module(top_level: NixExpr) -> Self {
1265 NixModule {
1266 top_level,
1267 is_module_file: true,
1268 }
1269 }
1270 pub fn emit(&self) -> String {
1272 let mut out = String::new();
1273 if self.is_module_file {
1274 out.push_str("# NixOS module generated by OxiLean\n");
1275 } else {
1276 out.push_str("# Nix expression generated by OxiLean\n");
1277 }
1278 out.push_str(&self.top_level.emit(0));
1279 out.push('\n');
1280 out
1281 }
1282}
1283#[allow(dead_code)]
1285pub struct NixStringInterpolator {
1286 pub(super) parts: Vec<NixStringPart>,
1287}
1288impl NixStringInterpolator {
1289 pub fn new() -> Self {
1291 NixStringInterpolator { parts: Vec::new() }
1292 }
1293 pub fn lit(mut self, s: &str) -> Self {
1295 self.parts.push(NixStringPart::Literal(s.to_string()));
1296 self
1297 }
1298 pub fn interp(mut self, expr: NixExpr) -> Self {
1300 self.parts.push(NixStringPart::Interp(expr));
1301 self
1302 }
1303 pub fn build(self) -> NixExpr {
1308 if self.parts.is_empty() {
1309 return NixExpr::Str(String::new());
1310 }
1311 let mut collapsed: Vec<NixStringPart> = Vec::new();
1312 for part in self.parts {
1313 match (&mut collapsed.last_mut(), &part) {
1314 (Some(NixStringPart::Literal(prev)), NixStringPart::Literal(next)) => {
1315 prev.push_str(next);
1316 }
1317 _ => collapsed.push(part),
1318 }
1319 }
1320 if collapsed.len() == 1 {
1321 if let NixStringPart::Literal(s) = &collapsed[0] {
1322 return NixExpr::Str(s.clone());
1323 }
1324 }
1325 if collapsed.len() == 3 {
1326 if let (
1327 NixStringPart::Literal(pre),
1328 NixStringPart::Interp(expr),
1329 NixStringPart::Literal(post),
1330 ) = (&collapsed[0], &collapsed[1], &collapsed[2])
1331 {
1332 return NixExpr::Antiquote(pre.clone(), Box::new(expr.clone()), post.clone());
1333 }
1334 }
1335 let mut result: NixExpr = match &collapsed[0] {
1336 NixStringPart::Literal(s) => NixExpr::Str(s.clone()),
1337 NixStringPart::Interp(e) => e.clone(),
1338 };
1339 for part in &collapsed[1..] {
1340 let part_expr = match part {
1341 NixStringPart::Literal(s) => NixExpr::Str(s.clone()),
1342 NixStringPart::Interp(e) => e.clone(),
1343 };
1344 result = NixExpr::BinOp("++".into(), Box::new(result), Box::new(part_expr));
1345 }
1346 result
1347 }
1348}
1349#[allow(dead_code)]
1351#[derive(Debug, Clone, Default)]
1352pub struct NixExprStats {
1353 pub node_count: u64,
1355 pub lambdas: u64,
1357 pub applications: u64,
1359 pub let_bindings: u64,
1361 pub attr_sets: u64,
1363 pub conditionals: u64,
1365 pub var_refs: u64,
1367 pub literals: u64,
1369 pub max_depth: u64,
1371}
1372impl NixExprStats {
1373 pub fn collect(expr: &NixExpr) -> Self {
1375 let mut stats = NixExprStats::default();
1376 stats.visit(expr, 0);
1377 stats
1378 }
1379 pub(super) fn visit(&mut self, expr: &NixExpr, depth: u64) {
1380 self.node_count += 1;
1381 if depth > self.max_depth {
1382 self.max_depth = depth;
1383 }
1384 match expr {
1385 NixExpr::Int(_)
1386 | NixExpr::Float(_)
1387 | NixExpr::Bool(_)
1388 | NixExpr::Str(_)
1389 | NixExpr::Path(_)
1390 | NixExpr::Null => {
1391 self.literals += 1;
1392 }
1393 NixExpr::Var(_) => {
1394 self.var_refs += 1;
1395 }
1396 NixExpr::Lambda(f) => {
1397 self.lambdas += 1;
1398 self.visit(&f.body, depth + 1);
1399 }
1400 NixExpr::Apply(f, arg) => {
1401 self.applications += 1;
1402 self.visit(f, depth + 1);
1403 self.visit(arg, depth + 1);
1404 }
1405 NixExpr::Let(bindings, body) => {
1406 self.let_bindings += bindings.len() as u64;
1407 for b in bindings {
1408 self.visit(&b.value, depth + 1);
1409 }
1410 self.visit(body, depth + 1);
1411 }
1412 NixExpr::AttrSet(attrs) | NixExpr::Rec(attrs) => {
1413 self.attr_sets += 1;
1414 for a in attrs {
1415 self.visit(&a.value, depth + 1);
1416 }
1417 }
1418 NixExpr::If(cond, t, f) => {
1419 self.conditionals += 1;
1420 self.visit(cond, depth + 1);
1421 self.visit(t, depth + 1);
1422 self.visit(f, depth + 1);
1423 }
1424 NixExpr::With(src, body) | NixExpr::Assert(src, body) => {
1425 self.visit(src, depth + 1);
1426 self.visit(body, depth + 1);
1427 }
1428 NixExpr::BinOp(_, lhs, rhs) => {
1429 self.visit(lhs, depth + 1);
1430 self.visit(rhs, depth + 1);
1431 }
1432 NixExpr::UnOp(_, e) => {
1433 self.visit(e, depth + 1);
1434 }
1435 NixExpr::Select(e, _, default) => {
1436 self.visit(e, depth + 1);
1437 if let Some(d) = default {
1438 self.visit(d, depth + 1);
1439 }
1440 }
1441 NixExpr::List(items) => {
1442 for item in items {
1443 self.visit(item, depth + 1);
1444 }
1445 }
1446 _ => {}
1447 }
1448 }
1449}
1450#[allow(dead_code)]
1451#[derive(Debug, Clone)]
1452pub struct NixDepGraph {
1453 pub(super) nodes: Vec<u32>,
1454 pub(super) edges: Vec<(u32, u32)>,
1455}
1456impl NixDepGraph {
1457 #[allow(dead_code)]
1458 pub fn new() -> Self {
1459 NixDepGraph {
1460 nodes: Vec::new(),
1461 edges: Vec::new(),
1462 }
1463 }
1464 #[allow(dead_code)]
1465 pub fn add_node(&mut self, id: u32) {
1466 if !self.nodes.contains(&id) {
1467 self.nodes.push(id);
1468 }
1469 }
1470 #[allow(dead_code)]
1471 pub fn add_dep(&mut self, dep: u32, dependent: u32) {
1472 self.add_node(dep);
1473 self.add_node(dependent);
1474 self.edges.push((dep, dependent));
1475 }
1476 #[allow(dead_code)]
1477 pub fn dependents_of(&self, node: u32) -> Vec<u32> {
1478 self.edges
1479 .iter()
1480 .filter(|(d, _)| *d == node)
1481 .map(|(_, dep)| *dep)
1482 .collect()
1483 }
1484 #[allow(dead_code)]
1485 pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
1486 self.edges
1487 .iter()
1488 .filter(|(_, dep)| *dep == node)
1489 .map(|(d, _)| *d)
1490 .collect()
1491 }
1492 #[allow(dead_code)]
1493 pub fn topological_sort(&self) -> Vec<u32> {
1494 let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
1495 for &n in &self.nodes {
1496 in_degree.insert(n, 0);
1497 }
1498 for (_, dep) in &self.edges {
1499 *in_degree.entry(*dep).or_insert(0) += 1;
1500 }
1501 let mut queue: std::collections::VecDeque<u32> = self
1502 .nodes
1503 .iter()
1504 .filter(|&&n| in_degree[&n] == 0)
1505 .copied()
1506 .collect();
1507 let mut result = Vec::new();
1508 while let Some(node) = queue.pop_front() {
1509 result.push(node);
1510 for dep in self.dependents_of(node) {
1511 let cnt = in_degree.entry(dep).or_insert(0);
1512 *cnt = cnt.saturating_sub(1);
1513 if *cnt == 0 {
1514 queue.push_back(dep);
1515 }
1516 }
1517 }
1518 result
1519 }
1520 #[allow(dead_code)]
1521 pub fn has_cycle(&self) -> bool {
1522 self.topological_sort().len() < self.nodes.len()
1523 }
1524}
1525#[allow(dead_code)]
1526#[derive(Debug, Clone)]
1527pub struct NixLivenessInfo {
1528 pub live_in: Vec<std::collections::HashSet<u32>>,
1529 pub live_out: Vec<std::collections::HashSet<u32>>,
1530 pub defs: Vec<std::collections::HashSet<u32>>,
1531 pub uses: Vec<std::collections::HashSet<u32>>,
1532}
1533impl NixLivenessInfo {
1534 #[allow(dead_code)]
1535 pub fn new(block_count: usize) -> Self {
1536 NixLivenessInfo {
1537 live_in: vec![std::collections::HashSet::new(); block_count],
1538 live_out: vec![std::collections::HashSet::new(); block_count],
1539 defs: vec![std::collections::HashSet::new(); block_count],
1540 uses: vec![std::collections::HashSet::new(); block_count],
1541 }
1542 }
1543 #[allow(dead_code)]
1544 pub fn add_def(&mut self, block: usize, var: u32) {
1545 if block < self.defs.len() {
1546 self.defs[block].insert(var);
1547 }
1548 }
1549 #[allow(dead_code)]
1550 pub fn add_use(&mut self, block: usize, var: u32) {
1551 if block < self.uses.len() {
1552 self.uses[block].insert(var);
1553 }
1554 }
1555 #[allow(dead_code)]
1556 pub fn is_live_in(&self, block: usize, var: u32) -> bool {
1557 self.live_in
1558 .get(block)
1559 .map(|s| s.contains(&var))
1560 .unwrap_or(false)
1561 }
1562 #[allow(dead_code)]
1563 pub fn is_live_out(&self, block: usize, var: u32) -> bool {
1564 self.live_out
1565 .get(block)
1566 .map(|s| s.contains(&var))
1567 .unwrap_or(false)
1568 }
1569}
1570#[allow(dead_code)]
1571#[derive(Debug, Clone, Default)]
1572pub struct NixPassStats {
1573 pub total_runs: u32,
1574 pub successful_runs: u32,
1575 pub total_changes: u64,
1576 pub time_ms: u64,
1577 pub iterations_used: u32,
1578}
1579impl NixPassStats {
1580 #[allow(dead_code)]
1581 pub fn new() -> Self {
1582 Self::default()
1583 }
1584 #[allow(dead_code)]
1585 pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
1586 self.total_runs += 1;
1587 self.successful_runs += 1;
1588 self.total_changes += changes;
1589 self.time_ms += time_ms;
1590 self.iterations_used = iterations;
1591 }
1592 #[allow(dead_code)]
1593 pub fn average_changes_per_run(&self) -> f64 {
1594 if self.total_runs == 0 {
1595 return 0.0;
1596 }
1597 self.total_changes as f64 / self.total_runs as f64
1598 }
1599 #[allow(dead_code)]
1600 pub fn success_rate(&self) -> f64 {
1601 if self.total_runs == 0 {
1602 return 0.0;
1603 }
1604 self.successful_runs as f64 / self.total_runs as f64
1605 }
1606 #[allow(dead_code)]
1607 pub fn format_summary(&self) -> String {
1608 format!(
1609 "Runs: {}/{}, Changes: {}, Time: {}ms",
1610 self.successful_runs, self.total_runs, self.total_changes, self.time_ms
1611 )
1612 }
1613}
1614#[derive(Debug, Clone, PartialEq)]
1616pub struct NixAttr {
1617 pub name: String,
1619 pub value: NixExpr,
1621}
1622impl NixAttr {
1623 pub fn new(name: impl Into<String>, value: NixExpr) -> Self {
1625 NixAttr {
1626 name: name.into(),
1627 value,
1628 }
1629 }
1630 pub(super) fn emit(&self, indent: usize) -> String {
1631 format!(
1632 "{}{} = {};",
1633 " ".repeat(indent),
1634 self.name,
1635 self.value.emit(indent)
1636 )
1637 }
1638}
1639#[allow(dead_code)]
1641#[derive(Debug, Clone, Default)]
1642pub struct NixModuleValidationReport {
1643 pub errors: Vec<String>,
1645 pub warnings: Vec<String>,
1647}
1648impl NixModuleValidationReport {
1649 pub fn is_valid(&self) -> bool {
1651 self.errors.is_empty()
1652 }
1653}
1654#[allow(dead_code)]
1656#[derive(Debug, Clone, PartialEq)]
1657pub enum NixValue {
1658 Int(i64),
1660 Float(f64),
1662 Bool(bool),
1664 Str(String),
1666 Path(String),
1668 Null,
1670 List(Vec<NixValue>),
1672 AttrSet(Vec<(String, NixValue)>),
1674 Closure(NixPattern, Box<NixExpr>),
1676 Thunk(Box<NixExpr>),
1678 Derivation(Box<NixValue>),
1680}
1681impl NixValue {
1682 pub fn type_name(&self) -> &'static str {
1684 match self {
1685 NixValue::Int(_) => "int",
1686 NixValue::Float(_) => "float",
1687 NixValue::Bool(_) => "bool",
1688 NixValue::Str(_) => "string",
1689 NixValue::Path(_) => "path",
1690 NixValue::Null => "null",
1691 NixValue::List(_) => "list",
1692 NixValue::AttrSet(_) => "set",
1693 NixValue::Closure(_, _) => "lambda",
1694 NixValue::Thunk(_) => "thunk",
1695 NixValue::Derivation(_) => "derivation",
1696 }
1697 }
1698 pub fn is_truthy(&self) -> bool {
1700 match self {
1701 NixValue::Bool(b) => *b,
1702 NixValue::Int(n) => *n != 0,
1703 NixValue::Null => false,
1704 NixValue::Str(s) => !s.is_empty(),
1705 NixValue::List(l) => !l.is_empty(),
1706 NixValue::AttrSet(a) => !a.is_empty(),
1707 _ => true,
1708 }
1709 }
1710 pub fn get_attr(&self, key: &str) -> Option<&NixValue> {
1712 if let NixValue::AttrSet(attrs) = self {
1713 attrs.iter().find(|(k, _)| k == key).map(|(_, v)| v)
1714 } else {
1715 None
1716 }
1717 }
1718 pub fn to_expr(&self) -> NixExpr {
1720 match self {
1721 NixValue::Int(n) => NixExpr::Int(*n),
1722 NixValue::Float(f) => NixExpr::Float(*f),
1723 NixValue::Bool(b) => NixExpr::Bool(*b),
1724 NixValue::Str(s) => NixExpr::Str(s.clone()),
1725 NixValue::Path(p) => NixExpr::Path(p.clone()),
1726 NixValue::Null => NixExpr::Null,
1727 NixValue::List(items) => NixExpr::List(items.iter().map(|v| v.to_expr()).collect()),
1728 NixValue::AttrSet(attrs) => NixExpr::AttrSet(
1729 attrs
1730 .iter()
1731 .map(|(k, v)| NixAttr::new(k.clone(), v.to_expr()))
1732 .collect(),
1733 ),
1734 NixValue::Closure(pat, body) => NixExpr::Lambda(Box::new(NixFunction {
1735 pattern: pat.clone(),
1736 body: body.clone(),
1737 })),
1738 NixValue::Thunk(expr) => *expr.clone(),
1739 NixValue::Derivation(inner) => inner.to_expr(),
1740 }
1741 }
1742}
1743#[allow(dead_code)]
1745pub struct NixLibHelper;
1746impl NixLibHelper {
1747 pub fn mk_option(ty: NixExpr, default: Option<NixExpr>, description: Option<&str>) -> NixExpr {
1749 let mut attrs = vec![("type", ty)];
1750 if let Some(d) = default {
1751 attrs.push(("default", d));
1752 }
1753 if let Some(desc) = description {
1754 attrs.push(("description", nix_str(desc)));
1755 }
1756 nix_apply(nix_select(nix_var("lib"), "mkOption"), nix_set(attrs))
1757 }
1758 pub fn mk_if(cond: NixExpr, value: NixExpr) -> NixExpr {
1760 nix_apply(nix_apply(nix_select(nix_var("lib"), "mkIf"), cond), value)
1761 }
1762 pub fn mk_default(value: NixExpr) -> NixExpr {
1764 nix_apply(nix_select(nix_var("lib"), "mkDefault"), value)
1765 }
1766 pub fn mk_force(value: NixExpr) -> NixExpr {
1768 nix_apply(nix_select(nix_var("lib"), "mkForce"), value)
1769 }
1770 pub fn mk_merge(items: Vec<NixExpr>) -> NixExpr {
1772 nix_apply(nix_select(nix_var("lib"), "mkMerge"), nix_list(items))
1773 }
1774 pub fn type_str() -> NixExpr {
1776 NixExpr::Select(
1777 Box::new(NixExpr::Select(
1778 Box::new(nix_var("lib")),
1779 "types".into(),
1780 None,
1781 )),
1782 "str".into(),
1783 None,
1784 )
1785 }
1786 pub fn type_int() -> NixExpr {
1788 NixExpr::Select(
1789 Box::new(NixExpr::Select(
1790 Box::new(nix_var("lib")),
1791 "types".into(),
1792 None,
1793 )),
1794 "int".into(),
1795 None,
1796 )
1797 }
1798 pub fn type_bool() -> NixExpr {
1800 NixExpr::Select(
1801 Box::new(NixExpr::Select(
1802 Box::new(nix_var("lib")),
1803 "types".into(),
1804 None,
1805 )),
1806 "bool".into(),
1807 None,
1808 )
1809 }
1810 pub fn type_list_of(ty: NixExpr) -> NixExpr {
1812 nix_apply(
1813 NixExpr::Select(
1814 Box::new(NixExpr::Select(
1815 Box::new(nix_var("lib")),
1816 "types".into(),
1817 None,
1818 )),
1819 "listOf".into(),
1820 None,
1821 ),
1822 ty,
1823 )
1824 }
1825 pub fn type_attrs_of(ty: NixExpr) -> NixExpr {
1827 nix_apply(
1828 NixExpr::Select(
1829 Box::new(NixExpr::Select(
1830 Box::new(nix_var("lib")),
1831 "types".into(),
1832 None,
1833 )),
1834 "attrsOf".into(),
1835 None,
1836 ),
1837 ty,
1838 )
1839 }
1840 pub fn concat_map_strings(f: NixExpr, list: NixExpr) -> NixExpr {
1842 nix_apply(
1843 nix_apply(nix_select(nix_var("lib"), "concatMapStrings"), f),
1844 list,
1845 )
1846 }
1847 pub fn concat_strings_sep(sep: &str, list: NixExpr) -> NixExpr {
1849 nix_apply(
1850 nix_apply(nix_select(nix_var("lib"), "concatStringsSep"), nix_str(sep)),
1851 list,
1852 )
1853 }
1854 pub fn map_attrs_to_list(f: NixExpr, attrs: NixExpr) -> NixExpr {
1856 nix_apply(
1857 nix_apply(nix_select(nix_var("lib"), "mapAttrsToList"), f),
1858 attrs,
1859 )
1860 }
1861 pub fn attr_by_path(path: Vec<&str>, default: NixExpr, attrs: NixExpr) -> NixExpr {
1863 nix_apply(
1864 nix_apply(
1865 nix_apply(
1866 nix_select(nix_var("lib"), "attrByPath"),
1867 nix_list(path.into_iter().map(nix_str).collect()),
1868 ),
1869 default,
1870 ),
1871 attrs,
1872 )
1873 }
1874}