hq_rs/
write.rs

1//! use the [`hcl-edit`][hcl_edit] crate to modify HCL documents
2
3use hcl_edit::{
4    expr::Expression,
5    structure::{Attribute, Body, Structure},
6    visit_mut::VisitMut,
7    Decorate, Decorated, Ident,
8};
9
10use crate::parser::Field;
11
12struct HclEditor<'a> {
13    fields: Vec<Field>,
14    current_index: usize,
15    current: Option<Field>,
16    value: &'a Expression,
17}
18
19impl<'a> HclEditor<'a> {
20    fn new(fields: Vec<Field>, value: &'a Expression) -> Self {
21        let current = fields.first().cloned();
22        HclEditor {
23            fields,
24            current_index: 0,
25            current,
26            value,
27        }
28    }
29
30    fn current_field(&self) -> Option<Field> {
31        self.fields.get(self.current_index).cloned()
32    }
33
34    fn next_field(&mut self) {
35        self.current_index += 1;
36        self.current = self.current_field();
37    }
38
39    fn previous_field(&mut self) {
40        self.current_index -= 1;
41        self.current = self.current_field();
42    }
43
44    fn should_edit(&self) -> bool {
45        self.current_index >= self.fields.len() - 1
46    }
47}
48
49impl VisitMut for HclEditor<'_> {
50    fn visit_body_mut(&mut self, node: &mut Body) {
51        if let Some(current) = self.current.clone() {
52            let mut matching_attr_keys = Vec::new();
53            let mut matching_block_idents = Vec::new();
54            // save this in case we are adding new attributes
55            let mut decor = None;
56            for item in node.iter() {
57                match item {
58                    Structure::Attribute(attr) => {
59                        // copy existing attribute's decor
60                        decor = Some(attr.decor().clone());
61                        if attr.key.as_str() == current.name {
62                            matching_attr_keys.push(attr.key.to_string());
63                        }
64                    }
65                    Structure::Block(block) => {
66                        if block.ident.as_str() == current.name {
67                            if current.labels.is_empty() {
68                                matching_block_idents.push(block.ident.to_string());
69                            } else {
70                                for filter_label in &current.labels {
71                                    for block_label in &block.labels {
72                                        if block_label.as_str() == filter_label {
73                                            matching_block_idents.push(block.ident.to_string());
74                                        }
75                                    }
76                                }
77                            }
78                        }
79                    }
80                }
81            }
82
83            for key in matching_attr_keys {
84                self.next_field();
85                self.visit_attr_mut(node.get_attribute_mut(&key).unwrap());
86                self.previous_field();
87            }
88
89            for ident in matching_block_idents {
90                for block in node.get_blocks_mut(&ident) {
91                    self.next_field();
92                    self.visit_body_mut(&mut block.body);
93                    self.previous_field();
94                }
95            }
96
97            if self.should_edit() {
98                let key = Decorated::new(Ident::new(current.name));
99                // copy existing attribute's decor when creating the new attribute
100                let decor = decor.unwrap_or_default();
101                let attr = Attribute::new(key, self.value.clone()).decorated(decor);
102                node.insert(node.len(), attr);
103            }
104        }
105    }
106
107    fn visit_attr_mut(&mut self, mut node: hcl_edit::structure::AttributeMut) {
108        if self.should_edit() {
109            let value = node.value_mut();
110            *value = self.value.clone();
111        } else {
112            self.next_field();
113            self.visit_expr_mut(node.value_mut());
114            self.previous_field();
115        }
116    }
117}
118
119/// given a vector of [`Field`]s, write `value` to replace the existing
120/// [`Expression`] that matches that filter
121pub fn write(fields: Vec<Field>, body: &mut Body, value: &Expression) {
122    let mut visitor = HclEditor::new(fields, value);
123    visitor.visit_body_mut(body);
124}