flake_edit/walk.rs
1use std::collections::HashMap;
2
3use rnix::{Root, SyntaxKind, SyntaxNode};
4
5use crate::{
6 change::Change,
7 edit::{OutputChange, Outputs},
8 input::Input,
9};
10
11#[derive(Debug, Clone)]
12pub struct Walker {
13 pub root: SyntaxNode,
14 pub inputs: HashMap<String, Input>,
15 pub add_toplevel: bool,
16}
17
18#[derive(Debug, Clone)]
19/// A helper for the [`Walker`], in order to hold context while traversing the tree.
20pub struct Context {
21 level: Vec<String>,
22}
23
24impl Context {
25 fn new(level: Vec<String>) -> Self {
26 Self { level }
27 }
28
29 pub fn level(&self) -> Vec<String> {
30 self.level.clone()
31 }
32}
33
34impl<'a> Walker {
35 pub fn new(stream: &'a str) -> Self {
36 let root = Root::parse(stream).syntax();
37 Self {
38 root,
39 inputs: HashMap::new(),
40 add_toplevel: false,
41 }
42 }
43 /// Traverse the toplevel `flake.nix` file.
44 /// It should consist of three attribute keys:
45 /// - description
46 /// - inputs
47 /// - outputs
48 pub fn walk(&mut self, change: &Change) -> Option<SyntaxNode> {
49 let cst = &self.root;
50 if cst.kind() != SyntaxKind::NODE_ROOT {
51 // TODO: handle this as an error
52 panic!("Should be a topevel node.")
53 } else {
54 self.walk_toplevel(cst.clone(), None, change)
55 }
56 }
57 /// Insert a new Input node at the correct position
58 /// or update it with new information.
59 fn insert_with_ctx(&mut self, id: String, input: Input, ctx: &Option<Context>) {
60 tracing::debug!("Inserting id: {id}, input: {input:?} with: ctx: {ctx:?}");
61
62 if let Some(ctx) = ctx {
63 // TODO: add more nesting
64 if let Some(follows) = ctx.level.first() {
65 if let Some(node) = self.inputs.get_mut(follows) {
66 // TODO: only indirect follows is handled
67 node.follows
68 .push(crate::input::Follows::Indirect(id, input.url));
69 // TODO: this should not be necessary
70 node.follows.sort();
71 node.follows.dedup();
72 } else {
73 // In case the Input is not fully constructed
74 let mut stub = Input::new(follows.to_string());
75 stub.follows
76 .push(crate::input::Follows::Indirect(id, input.url));
77 self.inputs.insert(follows.to_string(), stub);
78 }
79 }
80 } else {
81 // Update the input, in case there was already a stub present.
82 if let Some(node) = self.inputs.get_mut(&id) {
83 if !input.url.to_string().is_empty() {
84 node.url = input.url;
85 }
86 if !input.flake {
87 node.flake = input.flake;
88 }
89 } else {
90 self.inputs.insert(id, input);
91 }
92 }
93 tracing::debug!("Self Inputs: {:#?}", self.inputs);
94 }
95
96 fn remove_child_with_whitespace(
97 parent: &SyntaxNode,
98 node: &SyntaxNode,
99 index: usize,
100 ) -> SyntaxNode {
101 let mut green = parent.green().remove_child(index);
102 if let Some(prev) = node.prev_sibling_or_token() {
103 if let SyntaxKind::TOKEN_WHITESPACE = prev.kind() {
104 green = green.remove_child(prev.index());
105 }
106 } else if let Some(next) = node.next_sibling_or_token() {
107 if let SyntaxKind::TOKEN_WHITESPACE = next.kind() {
108 green = green.remove_child(next.index());
109 }
110 }
111 Root::parse(green.to_string().as_str()).syntax()
112 }
113
114 /// Only walk the outputs attribute
115 pub(crate) fn list_outputs(&mut self) -> Outputs {
116 let mut outputs: Vec<String> = vec![];
117 let mut any = false;
118 tracing::debug!("Walking outputs.");
119 let cst = &self.root;
120 if cst.kind() != SyntaxKind::NODE_ROOT {
121 // TODO: handle this as an error
122 panic!("Should be a topevel node.")
123 }
124
125 for toplevel in cst.first_child().unwrap().children() {
126 if toplevel.kind() == SyntaxKind::NODE_ATTRPATH_VALUE {
127 {
128 if let Some(outputs_node) = toplevel
129 .children()
130 .find(|child| child.to_string() == "outputs")
131 {
132 assert!(outputs_node.kind() == SyntaxKind::NODE_ATTRPATH);
133
134 if let Some(outputs_lambda) = outputs_node.next_sibling() {
135 assert!(outputs_lambda.kind() == SyntaxKind::NODE_LAMBDA);
136 if let Some(output) = outputs_lambda
137 .children()
138 .find(|n| n.kind() == SyntaxKind::NODE_PATTERN)
139 {
140 // We need to iterate over tokens, because ellipsis ...
141 // is not a valid node itself.
142 for child in output.children_with_tokens() {
143 if child.kind() == SyntaxKind::NODE_PAT_ENTRY {
144 outputs.push(child.to_string());
145 }
146 if child.kind() == SyntaxKind::TOKEN_ELLIPSIS {
147 any = true;
148 }
149 }
150 }
151 }
152 }
153 }
154 }
155 }
156 if outputs.is_empty() {
157 Outputs::None
158 } else if any {
159 Outputs::Any(outputs)
160 } else {
161 Outputs::Multiple(outputs)
162 }
163 }
164 /// Only change the outputs attribute
165 pub(crate) fn change_outputs(&mut self, change: OutputChange) -> Option<SyntaxNode> {
166 tracing::debug!("Changing outputs.");
167 let cst = &self.root;
168 if cst.kind() != SyntaxKind::NODE_ROOT {
169 // TODO: handle this as an error
170 panic!("Should be a toplevel node.")
171 }
172
173 for toplevel in cst.first_child().unwrap().children() {
174 if toplevel.kind() == SyntaxKind::NODE_ATTRPATH_VALUE {
175 {
176 if let Some(outputs_node) = toplevel
177 .children()
178 .find(|child| child.to_string() == "outputs")
179 {
180 assert!(outputs_node.kind() == SyntaxKind::NODE_ATTRPATH);
181
182 if let Some(outputs_lambda) = outputs_node.next_sibling() {
183 assert!(outputs_lambda.kind() == SyntaxKind::NODE_LAMBDA);
184 for output in outputs_lambda.children() {
185 if SyntaxKind::NODE_PATTERN == output.kind() {
186 if let OutputChange::Add(ref add) = change {
187 let token_count = output.children_with_tokens().count();
188 let count = output.children().count();
189 let last_node = token_count - 2;
190
191 // Adjust the addition for trailing slasheks
192 let addition = if let Some(SyntaxKind::TOKEN_COMMA) = output
193 .children()
194 .last()
195 .and_then(|last| last.next_sibling_or_token())
196 .map(|last_token| last_token.kind())
197 {
198 Root::parse(&format!("{add},")).syntax()
199 } else {
200 Root::parse(&format!(", {add}")).syntax()
201 };
202
203 let mut green = output
204 .green()
205 .insert_child(last_node, addition.green().into());
206 if let Some(prev) = output
207 .children()
208 .nth(count - 1)
209 .unwrap()
210 .prev_sibling_or_token()
211 {
212 if let SyntaxKind::TOKEN_WHITESPACE = prev.kind() {
213 let whitespace = Root::parse(
214 prev.as_token().unwrap().green().text(),
215 )
216 .syntax();
217 green = green.insert_child(
218 last_node,
219 whitespace.green().into(),
220 );
221 }
222 }
223 let changed_outputs_lambda = outputs_lambda
224 .green()
225 .replace_child(output.index(), green.into());
226 let changed_toplevel = toplevel.green().replace_child(
227 outputs_lambda.index(),
228 changed_outputs_lambda.into(),
229 );
230 let result =
231 cst.first_child().unwrap().green().replace_child(
232 toplevel.index(),
233 changed_toplevel.into(),
234 );
235 return Some(Root::parse(&result.to_string()).syntax());
236 }
237
238 for child in output.children() {
239 if child.kind() == SyntaxKind::NODE_PAT_ENTRY {
240 if let OutputChange::Remove(ref id) = change {
241 if child.to_string() == *id {
242 let mut green =
243 output.green().remove_child(child.index());
244 if let Some(prev) =
245 child.prev_sibling_or_token()
246 {
247 if let SyntaxKind::TOKEN_WHITESPACE =
248 prev.kind()
249 {
250 green =
251 green.remove_child(prev.index());
252 green = green
253 .remove_child(prev.index() - 1);
254 }
255 } else if let Some(next) =
256 child.next_sibling_or_token()
257 {
258 if let SyntaxKind::TOKEN_WHITESPACE =
259 next.kind()
260 {
261 green =
262 green.remove_child(next.index());
263 }
264 }
265 let changed_outputs_lambda =
266 outputs_lambda.green().replace_child(
267 output.index(),
268 green.into(),
269 );
270 let changed_toplevel =
271 toplevel.green().replace_child(
272 outputs_lambda.index(),
273 changed_outputs_lambda.into(),
274 );
275 let result = cst
276 .first_child()
277 .unwrap()
278 .green()
279 .replace_child(
280 toplevel.index(),
281 changed_toplevel.into(),
282 );
283 return Some(
284 Root::parse(&result.to_string()).syntax(),
285 );
286 }
287 }
288 }
289 }
290 }
291 }
292 }
293 }
294 }
295 }
296 }
297 None
298 }
299 /// Traverse the toplevel `flake.nix` file.
300 /// It should consist of three attribute keys:
301 /// - description
302 /// - inputs
303 /// - outputs
304 #[allow(clippy::option_map_unit_fn)]
305 fn walk_toplevel(
306 &mut self,
307 node: SyntaxNode,
308 ctx: Option<Context>,
309 change: &Change,
310 ) -> Option<SyntaxNode> {
311 for root in node.children() {
312 // Because it is the node root this is the toplevel attribute
313 for toplevel in root.children() {
314 // Match attr_sets inputs, and outputs
315 tracing::debug!("Toplevel: {}", toplevel);
316 tracing::debug!("Kind: {:?}", toplevel.kind());
317 if toplevel.kind() == SyntaxKind::NODE_ATTRPATH_VALUE {
318 for child in toplevel.children() {
319 tracing::debug!("Toplevel Child: {child}");
320 tracing::debug!("Toplevel Child Kind: {:?}", child.kind());
321 tracing::debug!("Toplevel Child Index: {:?}", child.index());
322 tracing::debug!("Toplevel Index: {:?}", toplevel.index());
323 if let Some(parent) = child.parent() {
324 tracing::debug!("Toplevel Child Parent: {}", parent);
325 tracing::debug!("Toplevel Child Parent Kind: {:?}", parent.kind());
326 tracing::debug!("Toplevel Child Parent Index: {:?}", parent.index());
327 }
328 if child.to_string() == "description" {
329 // We are not interested in the description
330 break;
331 }
332 if child.to_string() == "inputs" {
333 if let Some(replacement) =
334 self.walk_inputs(child.next_sibling().unwrap(), &ctx, change)
335 {
336 tracing::debug!("Replacement Node: {replacement}");
337 let green = toplevel.green().replace_child(
338 child.next_sibling().unwrap().index(),
339 replacement.green().into(),
340 );
341 let green = toplevel.replace_with(green);
342 let node = Root::parse(green.to_string().as_str()).syntax();
343 return Some(node);
344 }
345 } else if child.to_string().starts_with("inputs") {
346 // This is a toplevel node, of the form:
347 // input.id ...
348 // If the node should be empty,
349 // it's toplevel should be empty too.
350 if let Some(replacement) = self.walk_inputs(child.clone(), &ctx, change)
351 {
352 if replacement.to_string().is_empty() {
353 let node = Self::remove_child_with_whitespace(
354 &root,
355 &toplevel,
356 toplevel.index(),
357 );
358 return Some(node);
359 } else {
360 tracing::debug!("Replacement Node: {replacement}");
361 let green = toplevel.green().replace_child(
362 child.next_sibling().unwrap().index(),
363 replacement.green().into(),
364 );
365 let green = toplevel.replace_with(green);
366 let node = Root::parse(green.to_string().as_str()).syntax();
367 return Some(node);
368 }
369 }
370 };
371 // If we already see outputs, but have no inputs
372 // we need to create a toplevel inputs attribute set.
373 if child.to_string() == "outputs" && self.add_toplevel {
374 if let Change::Add { id, uri, flake } = change {
375 let addition = Root::parse(&format!(
376 "inputs.{}.url = \"{}\";",
377 id.clone().unwrap(),
378 uri.clone().unwrap()
379 ))
380 .syntax();
381 // TODO Guard against indices that would be out of range here.
382 if toplevel.index() > 0 {
383 let mut node = root.green().insert_child(
384 toplevel.index() - 1,
385 addition.green().into(),
386 );
387 root.children()
388 .find(|c| c.index() == toplevel.index() - 2)
389 .map(|c| {
390 if let Some(prev) = c.prev_sibling_or_token() {
391 if prev.kind() == SyntaxKind::TOKEN_WHITESPACE {
392 let whitespace = Root::parse(
393 prev.as_token().unwrap().green().text(),
394 )
395 .syntax();
396 node = node.insert_child(
397 toplevel.index() - 1,
398 whitespace.green().into(),
399 );
400 }
401 } else if let Some(next) = c.next_sibling_or_token() {
402 if next.kind() == SyntaxKind::TOKEN_WHITESPACE {
403 let whitespace = Root::parse(
404 next.as_token().unwrap().green().text(),
405 )
406 .syntax();
407 node = node.insert_child(
408 toplevel.index() - 1,
409 whitespace.green().into(),
410 );
411 }
412 }
413 });
414 if !flake {
415 let no_flake = Root::parse(&format!(
416 "inputs.{}.flake = false;",
417 id.clone().unwrap(),
418 ))
419 .syntax();
420 node = node.insert_child(
421 toplevel.index() + 1,
422 no_flake.green().into(),
423 );
424 root.children()
425 .find(|c| c.index() == toplevel.index() - 2)
426 .map(|c| {
427 if let Some(prev) = c.prev_sibling_or_token() {
428 if prev.kind() == SyntaxKind::TOKEN_WHITESPACE {
429 let whitespace = Root::parse(
430 prev.as_token().unwrap().green().text(),
431 )
432 .syntax();
433 node = node.insert_child(
434 toplevel.index() + 1,
435 whitespace.green().into(),
436 );
437 }
438 } else if let Some(next) = c.next_sibling_or_token()
439 {
440 if next.kind() == SyntaxKind::TOKEN_WHITESPACE {
441 let whitespace = Root::parse(
442 next.as_token().unwrap().green().text(),
443 )
444 .syntax();
445 node = node.insert_child(
446 toplevel.index() + 1,
447 whitespace.green().into(),
448 );
449 }
450 }
451 });
452 }
453 if let Some(prev) = child.next_sibling_or_token() {
454 if prev.kind() == SyntaxKind::TOKEN_WHITESPACE {
455 let whitespace = Root::parse(
456 prev.as_token().unwrap().green().text(),
457 )
458 .syntax();
459 node = node.insert_child(
460 child.index() + 1,
461 whitespace.green().into(),
462 );
463 }
464 }
465 return Some(Root::parse(&node.to_string()).syntax());
466 }
467 }
468 }
469 }
470 } else {
471 // TODO: proper error handling.
472 panic!("Should be a NODE_ATTRPATH_VALUE");
473 }
474 }
475 }
476 None
477 }
478 fn walk_inputs(
479 &mut self,
480 node: SyntaxNode,
481 ctx: &Option<Context>,
482 change: &Change,
483 ) -> Option<SyntaxNode> {
484 tracing::debug!("WalkInputs: \n{node}\n with ctx: {ctx:?}");
485 tracing::debug!("WalkInputsKind: {:?}", node.kind());
486 match node.kind() {
487 SyntaxKind::NODE_ATTR_SET => {}
488 SyntaxKind::NODE_ATTRPATH_VALUE => {}
489 SyntaxKind::NODE_IDENT => {}
490 SyntaxKind::NODE_ATTRPATH => {
491 let maybe_follows_id = node
492 .children()
493 .find(|child| child.to_string() == "follows")
494 .and_then(|input_child| input_child.prev_sibling());
495 if let Some(follows_id) = &maybe_follows_id {
496 let maybe_input_id = node
497 .children()
498 .find(|child| child.to_string() == "inputs")
499 .and_then(|input_child| input_child.next_sibling());
500 let ctx = maybe_input_id
501 .clone()
502 .map(|id| Context::new(vec![id.to_string()]));
503 let mut input = Input::new(follows_id.to_string());
504 input.url = node.next_sibling().unwrap().to_string();
505 let text_range = node.next_sibling().unwrap().text_range();
506 input.range = crate::input::Range::from_text_range(text_range);
507 self.insert_with_ctx(follows_id.to_string(), input, &ctx);
508
509 // Remove a toplevel follows node
510 if let Some(input_id) = maybe_input_id {
511 if change.is_remove() {
512 if let Some(id) = change.id() {
513 let maybe_follows = maybe_follows_id.map(|id| id.to_string());
514 if id.matches_with_follows(&input_id.to_string(), maybe_follows) {
515 let replacement = Root::parse("").syntax();
516 return Some(replacement);
517 }
518 }
519 }
520 }
521 }
522 }
523 _ => {}
524 }
525 for child in node.children_with_tokens() {
526 tracing::debug!("Inputs Child Kind: {:?}", child.kind());
527 tracing::debug!("Inputs Child: {child}");
528 tracing::debug!("Inputs Child Len: {}", child.to_string().len());
529 match child.kind() {
530 SyntaxKind::NODE_ATTRPATH_VALUE => {
531 // TODO: Append to context, instead of creating a new one.
532 let ctx = if ctx.is_none() {
533 let maybe_input_id = child.as_node().unwrap().children().find_map(|c| {
534 c.children()
535 .find(|child| child.to_string() == "inputs")
536 .and_then(|input_child| input_child.prev_sibling())
537 });
538 maybe_input_id.map(|id| Context::new(vec![id.to_string()]))
539 } else {
540 ctx.clone()
541 };
542 if let Some(replacement) =
543 self.walk_input(child.as_node().unwrap(), &ctx, change)
544 {
545 tracing::debug!("Child Id: {}", child.index());
546 tracing::debug!("Input replacement node: {}", node);
547 let mut green = node
548 .green()
549 .replace_child(child.index(), replacement.green().into());
550 if replacement.text().is_empty() {
551 let prev = child.prev_sibling_or_token();
552 if let Some(prev) = prev {
553 if let SyntaxKind::TOKEN_WHITESPACE = prev.kind() {
554 green = green.remove_child(prev.index());
555 }
556 } else if let Some(next) = child.next_sibling_or_token() {
557 if let SyntaxKind::TOKEN_WHITESPACE = next.kind() {
558 green = green.remove_child(next.index());
559 }
560 }
561 }
562 let node = Root::parse(green.to_string().as_str()).syntax();
563 return Some(node);
564 } else if change.is_some() && change.id().is_some() && ctx.is_none() {
565 if let Change::Add { id, uri, flake } = change {
566 let uri = Root::parse(&format!(
567 "{}.url = \"{}\";",
568 id.clone().unwrap(),
569 uri.clone().unwrap(),
570 ))
571 .syntax();
572 let mut green =
573 node.green().insert_child(child.index(), uri.green().into());
574 let prev = child.prev_sibling_or_token().unwrap();
575 tracing::debug!("Token:{}", prev);
576 tracing::debug!("Token Kind: {:?}", prev.kind());
577 if prev.kind() == SyntaxKind::TOKEN_WHITESPACE {
578 let whitespace =
579 Root::parse(prev.as_token().unwrap().green().text()).syntax();
580 green = green
581 .insert_child(child.index() + 1, whitespace.green().into());
582 }
583 tracing::debug!("green: {}", green);
584 tracing::debug!("node: {}", node);
585 tracing::debug!("node kind: {:?}", node.kind());
586 if !flake {
587 let no_flake = Root::parse(&format!(
588 "{}.flake = false;",
589 id.clone().unwrap(),
590 ))
591 .syntax();
592 green =
593 green.insert_child(child.index() + 2, no_flake.green().into());
594 if prev.kind() == SyntaxKind::TOKEN_WHITESPACE {
595 let whitespace =
596 Root::parse(prev.as_token().unwrap().green().text())
597 .syntax();
598 green = green
599 .insert_child(child.index() + 3, whitespace.green().into());
600 }
601 }
602 let node = Root::parse(green.to_string().as_str()).syntax();
603 return Some(node);
604 }
605 }
606 }
607 SyntaxKind::NODE_IDENT => {
608 tracing::debug!("Node PATH: {}", child);
609 if child.to_string() == "inputs" {
610 if let Some(next_sibling) = child.as_node().unwrap().next_sibling() {
611 match next_sibling.kind() {
612 SyntaxKind::NODE_IDENT => {
613 tracing::debug!("NODE_IDENT input: {}", next_sibling);
614 tracing::debug!("NODE_IDENT input: {}", next_sibling);
615 if let Some(url_id) = next_sibling.next_sibling() {
616 match url_id.kind() {
617 SyntaxKind::NODE_IDENT => {
618 if url_id.to_string() == "url" {
619 if let Some(url) = child
620 .as_node()
621 .unwrap()
622 .parent()
623 .unwrap()
624 .next_sibling()
625 {
626 tracing::debug!(
627 "This is an url from {} - {}",
628 next_sibling,
629 url
630 );
631 let mut input =
632 Input::new(next_sibling.to_string());
633 input.url = url.to_string();
634 let text_range = url.text_range();
635 input.range =
636 crate::input::Range::from_text_range(
637 text_range,
638 );
639 self.insert_with_ctx(
640 next_sibling.to_string(),
641 input,
642 ctx,
643 );
644 if change.is_some() && change.is_remove() {
645 if let Some(id) = change.id() {
646 if id.to_string()
647 == next_sibling.to_string()
648 {
649 let replacement =
650 Root::parse("").syntax();
651 tracing::debug!("Node: {node}");
652 return Some(replacement);
653 }
654 }
655 if let Some(ctx) = ctx {
656 if *ctx.level.first().unwrap()
657 == next_sibling.to_string()
658 {
659 let replacement =
660 Root::parse("").syntax();
661 return Some(replacement);
662 }
663 }
664 }
665 }
666 } else if url_id.to_string() == "flake" {
667 if let Some(is_flake) = child
668 .as_node()
669 .unwrap()
670 .parent()
671 .unwrap()
672 .next_sibling()
673 {
674 tracing::debug!(
675 "This id {} is a flake: {}",
676 next_sibling,
677 is_flake
678 );
679 // let mut input =
680 // Input::new(next_sibling.to_string());
681 // input.flake =
682 // is_flake.to_string().parse().unwrap();
683 // self.insert_with_ctx(
684 // next_sibling.to_string(),
685 // input,
686 // ctx,
687 // );
688 if change.is_some() && change.is_remove() {
689 if let Some(id) = change.id() {
690 if id.to_string()
691 == next_sibling.to_string()
692 {
693 let replacement =
694 Root::parse("").syntax();
695 tracing::debug!("Node: {node}");
696 return Some(replacement);
697 }
698 }
699 if let Some(ctx) = ctx {
700 if *ctx.level.first().unwrap()
701 == next_sibling.to_string()
702 {
703 let replacement =
704 Root::parse("").syntax();
705 return Some(replacement);
706 }
707 }
708 }
709 }
710 } else {
711 tracing::debug!(
712 "Unhandled input: {}",
713 next_sibling
714 );
715 }
716 }
717 _ => {
718 tracing::debug!(
719 "Unhandled input: {}",
720 next_sibling
721 );
722 }
723 }
724 } else {
725 tracing::debug!("Unhandled input: {}", next_sibling);
726 if let Some(nested_attr) = child
727 .as_node()
728 .unwrap()
729 .parent()
730 .unwrap()
731 .next_sibling()
732 {
733 tracing::debug!("Nested input: {}", nested_attr);
734 for attr in nested_attr.children() {
735 tracing::debug!(
736 "Nested input attr: {}, from: {}",
737 attr,
738 next_sibling
739 );
740
741 for binding in attr.children() {
742 if binding.to_string() == "url" {
743 let url = binding.next_sibling().unwrap();
744 tracing::debug!(
745 "This is an url: {} - {}",
746 next_sibling,
747 url
748 );
749 let mut input =
750 Input::new(next_sibling.to_string());
751 input.url = url.to_string();
752 let text_range = next_sibling.text_range();
753 input.range =
754 crate::input::Range::from_text_range(
755 text_range,
756 );
757 self.insert_with_ctx(
758 next_sibling.to_string(),
759 input,
760 ctx,
761 );
762 }
763 if change.is_remove() {
764 if let Some(id) = change.id() {
765 if id.to_string()
766 == next_sibling.to_string()
767 {
768 let replacement =
769 Root::parse("").syntax();
770 tracing::debug!("Node: {node}");
771 return Some(replacement);
772 }
773 }
774 }
775 tracing::debug!(
776 "Nested input attr binding: {}",
777 binding
778 );
779 }
780 let context =
781 Context::new(vec![next_sibling.to_string()]);
782 tracing::debug!(
783 "Walking inputs with: {attr}, context: {context:?}"
784 );
785 if let Some(change) =
786 self.walk_input(&attr, &Some(context), change)
787 {
788 tracing::debug!("Adjusted change: {change}");
789 tracing::debug!(
790 "Adjusted change is_empty: {}",
791 change.to_string().is_empty()
792 );
793 tracing::debug!(
794 "Child index: {}",
795 child.index()
796 );
797 tracing::debug!(
798 "Child node: {}",
799 child.as_node().unwrap()
800 );
801 tracing::debug!("Nested Attr: {}", nested_attr);
802 tracing::debug!("Node: {}", node);
803 tracing::debug!("Attr: {}", attr);
804 // TODO: adjust node correctly if the change is
805 // not empty
806 let replacement =
807 Self::remove_child_with_whitespace(
808 &nested_attr,
809 &attr,
810 attr.index(),
811 );
812 tracing::debug!("Replacement: {}", replacement);
813 return Some(replacement);
814 }
815 }
816 }
817 }
818 }
819 SyntaxKind::NODE_ATTR_SET => {}
820 _ => {
821 tracing::debug!(
822 "Unhandled input kind: {:?}",
823 next_sibling.kind()
824 );
825 tracing::debug!("Unhandled input: {}", next_sibling);
826 }
827 }
828 }
829 }
830 // TODO: flat tree attributes
831 if child.to_string().starts_with("inputs") {
832 let child_node = child.as_node().unwrap();
833 let id = child_node.next_sibling().unwrap();
834 let context = Context::new(vec![id.to_string()]);
835 tracing::debug!("Walking inputs with: {child}, context: {context:?}");
836 if let Some(_replacement) =
837 self.walk_inputs(child_node.clone(), &Some(context), change)
838 {
839 panic!("Not yet implemented");
840 }
841 }
842 if let Some(parent) = child.parent() {
843 tracing::debug!("Children Parent Child: {}", child);
844 tracing::debug!("Children Parent Child Kind: {:?}", child.kind());
845 tracing::debug!("Children Parent Kind: {:?}", parent.kind());
846 tracing::debug!("Children Parent: {}", parent);
847 tracing::debug!("Children Parent Context: {:?}", ctx);
848 if let Some(sibling) = parent.next_sibling() {
849 tracing::debug!("Children Sibling: {}", sibling);
850 }
851 for child in parent.children() {
852 tracing::debug!("Children Sibling --: {}", child);
853 }
854 }
855 }
856 _ => {
857 tracing::debug!("UNMATCHED KIND: {:?}", child.kind());
858 tracing::debug!("UNMATCHED PATH: {}", child);
859 }
860 }
861 }
862 None
863 }
864 /// Walk a single input field.
865 /// Example:
866 /// ```nix
867 /// flake-utils.url = "github:numtide/flake-utils";
868 /// ```
869 /// or
870 /// ```nix
871 /// rust-overlay = {
872 /// url = "github:oxalica/rust-overlay";
873 /// inputs.nixpkgs.follows = "nixpkgs";
874 /// inputs.flake-utils.follows = "flake-utils";
875 /// };
876 /// ```
877 fn walk_input(
878 &mut self,
879 node: &SyntaxNode,
880 ctx: &Option<Context>,
881 change: &Change,
882 ) -> Option<SyntaxNode> {
883 tracing::debug!("\nInput: {node}\n with ctx: {ctx:?}");
884 for (i, child) in node.children().enumerate() {
885 tracing::debug!("Kind #:{i} {:?}", child.kind());
886 tracing::debug!("Kind #:{i} {}", child);
887 if child.kind() == SyntaxKind::NODE_ATTRPATH {
888 for attr in child.children() {
889 tracing::debug!("Child of ATTRPATH #:{i} {}", child);
890 tracing::debug!("Child of ATTR #:{i} {}", attr);
891 if attr.to_string() == "url" {
892 if let Some(prev_id) = attr.prev_sibling() {
893 if let Change::Remove { id } = change {
894 if id.to_string() == prev_id.to_string() {
895 tracing::debug!("Removing: {id}");
896 let empty = Root::parse("").syntax();
897 return Some(empty);
898 }
899 }
900 if let Some(sibling) = child.next_sibling() {
901 tracing::debug!("This is an url from {} - {}", prev_id, sibling);
902 let mut input = Input::new(prev_id.to_string());
903 input.url = sibling.to_string();
904 let text_range = sibling.text_range();
905 input.range = crate::input::Range::from_text_range(text_range);
906 self.insert_with_ctx(prev_id.to_string(), input, ctx);
907 }
908 }
909 tracing::debug!("This is the parent: {}", child.parent().unwrap());
910 tracing::debug!(
911 "This is the next_sibling: {}",
912 child.next_sibling().unwrap()
913 );
914 if let Some(parent) = child.parent() {
915 if let Some(sibling) = parent.next_sibling() {
916 //TODO: this is only matched, when url is the first child
917 // TODO: Is this correct?
918 tracing::debug!(
919 "This is a possible follows attribute:{} {}",
920 attr,
921 sibling
922 );
923 if let Some(child) = sibling.first_child() {
924 if child.to_string() == "inputs" {
925 if let Some(attr_set) = child.next_sibling() {
926 if SyntaxKind::NODE_ATTR_SET == attr_set.kind() {
927 for attr in attr_set.children() {
928 let is_follows = attr
929 .first_child()
930 .unwrap()
931 .first_child()
932 .unwrap()
933 .next_sibling()
934 .unwrap();
935
936 if is_follows.to_string() == "follows" {
937 let id = is_follows.prev_sibling().unwrap();
938 let follows = attr
939 .first_child()
940 .unwrap()
941 .next_sibling()
942 .unwrap();
943 tracing::debug!(
944 "The following attribute follows: {id}:{follows} is nested inside the attr: {ctx:?}"
945 );
946 let mut input = Input::new(id.to_string());
947 input.url = follows.to_string();
948 let text_range = follows.text_range();
949 input.range =
950 crate::input::Range::from_text_range(
951 text_range,
952 );
953 self.insert_with_ctx(
954 id.to_string(),
955 input,
956 ctx,
957 );
958 if change.is_remove() {
959 if let Some(id) = change.id() {
960 if id.matches_with_ctx(
961 &follows.to_string(),
962 ctx.clone(),
963 ) {
964 let replacement =
965 Root::parse("").syntax();
966 return Some(replacement);
967 }
968 }
969 }
970 }
971 }
972 }
973 }
974 }
975 }
976 }
977 }
978 }
979 if attr.to_string() == "flake" {
980 if let Some(input_id) = attr.prev_sibling() {
981 if let Some(is_flake) = attr.parent().unwrap().next_sibling() {
982 tracing::debug!(
983 "The following attribute is a flake: {input_id}:{is_flake} is nested inside the context: {ctx:?}"
984 );
985 let mut input = Input::new(input_id.to_string());
986 input.flake = is_flake.to_string().parse().unwrap();
987 let text_range = input_id.text_range();
988 input.range = crate::input::Range::from_text_range(text_range);
989 self.insert_with_ctx(input_id.to_string(), input, ctx);
990 if change.is_remove() {
991 if let Some(id) = change.id() {
992 if id.matches_with_ctx(&input_id.to_string(), ctx.clone()) {
993 let replacement = Root::parse("").syntax();
994 return Some(replacement);
995 }
996 }
997 }
998 }
999 } else {
1000 // TODO: handle this.
1001 // This happens, when there is a nested node.
1002 tracing::info!("Nested: This is not handled yet.");
1003 }
1004 }
1005 if attr.to_string() == "follows" {
1006 // Construct the follows attribute
1007 // TODO:
1008 // - check for possible removal / change
1009 let id = attr.prev_sibling().unwrap();
1010 let follows = attr.parent().unwrap().next_sibling().unwrap();
1011 tracing::debug!(
1012 "The following attribute follows: {id}:{follows} is nested inside the attr: {ctx:?}"
1013 );
1014 // TODO: Construct follows attribute if not yet ready.
1015 // For now assume that the url is the first attribute.
1016 // This assumption doesn't generally hold true.
1017 let mut input = Input::new(id.to_string());
1018 input.url = follows.to_string();
1019 let text_range = follows.text_range();
1020 input.range = crate::input::Range::from_text_range(text_range);
1021 self.insert_with_ctx(id.to_string(), input.clone(), ctx);
1022 if let Some(id) = change.id() {
1023 if let Some(ctx) = ctx {
1024 if id.matches_with_ctx(input.id(), Some(ctx.clone()))
1025 && change.is_remove()
1026 {
1027 let replacement = Root::parse("").syntax();
1028 return Some(replacement);
1029 }
1030 }
1031 }
1032 }
1033 }
1034 }
1035 if child.kind() == SyntaxKind::NODE_ATTR_SET {
1036 for attr in child.children() {
1037 tracing::debug!("Child of ATTRSET KIND #:{i} {:?}", attr.kind());
1038 tracing::debug!("Child of ATTRSET #:{i} {}", attr);
1039 for leaf in attr.children() {
1040 tracing::debug!("LEAF of ATTRSET KIND #:{i} {:?}", leaf.kind());
1041 tracing::debug!("LEAF of ATTRSET #:{i} {}", leaf);
1042 if leaf.to_string() == "url" {
1043 let id = child.prev_sibling().unwrap();
1044 let uri = leaf.next_sibling().unwrap();
1045 tracing::debug!("This is an url from {} - {}", id, uri,);
1046 let mut input = Input::new(id.to_string());
1047 input.url = uri.to_string();
1048 let text_range = uri.text_range();
1049 input.range = crate::input::Range::from_text_range(text_range);
1050 self.insert_with_ctx(id.to_string(), input, ctx);
1051
1052 // Remove matched node.
1053 if let Change::Remove { id: candidate } = change {
1054 if candidate.to_string() == id.to_string() {
1055 tracing::debug!("Removing: {id}");
1056 let empty = Root::parse("").syntax();
1057 return Some(empty);
1058 }
1059 }
1060 }
1061 if leaf.to_string().starts_with("inputs") {
1062 let id = child.prev_sibling().unwrap();
1063 let context = Context::new(vec![id.to_string()]);
1064 tracing::debug!("Walking inputs with: {attr}, context: {context:?}");
1065 // panic!("Walking inputs with: {attr}, context: {context:?}");
1066 if let Some(replacement) =
1067 self.walk_inputs(child.clone(), &Some(context), change)
1068 {
1069 // TODO: adjustment of whitespace, if node is empty
1070 // TODO: if it leaves an empty attr, then remove whole?
1071 let tree = node
1072 .green()
1073 .replace_child(child.index(), replacement.green().into());
1074 let replacement = Root::parse(&tree.to_string()).syntax();
1075 return Some(replacement);
1076 }
1077 tracing::debug!("Child of ATTRSET KIND #:{i} {:?}", leaf.kind());
1078 tracing::debug!("Child of ATTRSET CHILD #:{i} {}", leaf);
1079 }
1080 }
1081 }
1082 }
1083 tracing::debug!("Child #:{i} {}", child);
1084 }
1085 None
1086 }
1087}