1 //! Example for how to use `VisitMut` to iterate over a table.
2 
3 use std::collections::BTreeSet;
4 use toml_edit::visit::*;
5 use toml_edit::visit_mut::*;
6 use toml_edit::{Array, Document, InlineTable, Item, KeyMut, Table, Value};
7 
8 /// This models the visit state for dependency keys in a `Cargo.toml`.
9 ///
10 /// Dependencies can be specified as:
11 ///
12 /// ```toml
13 /// [dependencies]
14 /// dep1 = "0.2"
15 ///
16 /// [build-dependencies]
17 /// dep2 = "0.3"
18 ///
19 /// [dev-dependencies]
20 /// dep3 = "0.4"
21 ///
22 /// [target.'cfg(windows)'.dependencies]
23 /// dep4 = "0.5"
24 ///
25 /// # and target build- and dev-dependencies
26 /// ```
27 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
28 enum VisitState {
29     /// Represents the root of the table.
30     Root,
31     /// Represents "dependencies", "build-dependencies" or "dev-dependencies", or the target
32     /// forms of these.
33     Dependencies,
34     /// A table within dependencies.
35     SubDependencies,
36     /// Represents "target".
37     Target,
38     /// "target.[TARGET]".
39     TargetWithSpec,
40     /// Represents some other state.
41     Other,
42 }
43 
44 impl VisitState {
45     /// Figures out the next visit state, given the current state and the given key.
descend(self, key: &str) -> Self46     fn descend(self, key: &str) -> Self {
47         match (self, key) {
48             (
49                 VisitState::Root | VisitState::TargetWithSpec,
50                 "dependencies" | "build-dependencies" | "dev-dependencies",
51             ) => VisitState::Dependencies,
52             (VisitState::Root, "target") => VisitState::Target,
53             (VisitState::Root | VisitState::TargetWithSpec, _) => VisitState::Other,
54             (VisitState::Target, _) => VisitState::TargetWithSpec,
55             (VisitState::Dependencies, _) => VisitState::SubDependencies,
56             (VisitState::SubDependencies, _) => VisitState::SubDependencies,
57             (VisitState::Other, _) => VisitState::Other,
58         }
59     }
60 }
61 
62 /// Collect the names of every dependency key.
63 #[derive(Debug)]
64 struct DependencyNameVisitor<'doc> {
65     state: VisitState,
66     names: BTreeSet<&'doc str>,
67 }
68 
69 impl<'doc> Visit<'doc> for DependencyNameVisitor<'doc> {
visit_table_like_kv(&mut self, key: &'doc str, node: &'doc Item)70     fn visit_table_like_kv(&mut self, key: &'doc str, node: &'doc Item) {
71         if self.state == VisitState::Dependencies {
72             self.names.insert(key);
73         } else {
74             // Since we're only interested in collecting the top-level keys right under
75             // [dependencies], don't recurse unconditionally.
76 
77             let old_state = self.state;
78 
79             // Figure out the next state given the key.
80             self.state = self.state.descend(key);
81 
82             // Recurse further into the document tree.
83             visit_table_like_kv(self, key, node);
84 
85             // Restore the old state after it's done.
86             self.state = old_state;
87         }
88     }
89 }
90 
91 /// Normalize all dependency tables into the format:
92 ///
93 /// ```toml
94 /// [dependencies]
95 /// dep = { version = "1.0", features = ["foo", "bar"], ... }
96 /// ```
97 ///
98 /// leaving other tables untouched.
99 #[derive(Debug)]
100 struct NormalizeDependencyTablesVisitor {
101     state: VisitState,
102 }
103 
104 impl VisitMut for NormalizeDependencyTablesVisitor {
visit_table_mut(&mut self, node: &mut Table)105     fn visit_table_mut(&mut self, node: &mut Table) {
106         visit_table_mut(self, node);
107 
108         // The conversion from regular tables into inline ones might leave some explicit parent
109         // tables hanging, so convert them to implicit.
110         if matches!(self.state, VisitState::Target | VisitState::TargetWithSpec) {
111             node.set_implicit(true);
112         }
113     }
114 
visit_table_like_kv_mut(&mut self, mut key: KeyMut<'_>, node: &mut Item)115     fn visit_table_like_kv_mut(&mut self, mut key: KeyMut<'_>, node: &mut Item) {
116         let old_state = self.state;
117 
118         // Figure out the next state given the key.
119         self.state = self.state.descend(key.get());
120 
121         match self.state {
122             VisitState::Target | VisitState::TargetWithSpec | VisitState::Dependencies => {
123                 // Top-level dependency row, or above: turn inline tables into regular ones.
124                 if let Item::Value(Value::InlineTable(inline_table)) = node {
125                     let inline_table = std::mem::replace(inline_table, InlineTable::new());
126                     let table = inline_table.into_table();
127                     key.fmt();
128                     *node = Item::Table(table);
129                 }
130             }
131             VisitState::SubDependencies => {
132                 // Individual dependency: turn regular tables into inline ones.
133                 if let Item::Table(table) = node {
134                     // Turn the table into an inline table.
135                     let table = std::mem::replace(table, Table::new());
136                     let inline_table = table.into_inline_table();
137                     key.fmt();
138                     *node = Item::Value(Value::InlineTable(inline_table));
139                 }
140             }
141             _ => {}
142         }
143 
144         // Recurse further into the document tree.
145         visit_table_like_kv_mut(self, key, node);
146 
147         // Restore the old state after it's done.
148         self.state = old_state;
149     }
150 
visit_array_mut(&mut self, node: &mut Array)151     fn visit_array_mut(&mut self, node: &mut Array) {
152         // Format any arrays within dependencies to be on the same line.
153         if matches!(
154             self.state,
155             VisitState::Dependencies | VisitState::SubDependencies
156         ) {
157             node.fmt();
158         }
159     }
160 }
161 
162 /// This is the input provided to visit_mut_example.
163 static INPUT: &str = r#"
164 [package]
165 name = "my-package"
166 
167 [package.metadata.foo]
168 bar = 42
169 
170 [dependencies]
171 atty = "0.2"
172 cargo-platform = { path = "crates/cargo-platform", version = "0.1.2" }
173 
174 [dependencies.pretty_env_logger]
175 version = "0.4"
176 optional = true
177 
178 [target.'cfg(windows)'.dependencies]
179 fwdansi = "1.1.0"
180 
181 [target.'cfg(windows)'.dependencies.winapi]
182 version = "0.3"
183 features = [
184 "handleapi",
185 "jobapi",
186 ]
187 
188 [target.'cfg(unix)']
189 dev-dependencies = { miniz_oxide = "0.5" }
190 
191 [dev-dependencies.cargo-test-macro]
192 path = "crates/cargo-test-macro"
193 
194 [build-dependencies.flate2]
195 version = "0.4"
196 "#;
197 
198 /// This is the output produced by visit_mut_example.
199 #[cfg(test)]
200 static VISIT_MUT_OUTPUT: &str = r#"
201 [package]
202 name = "my-package"
203 
204 [package.metadata.foo]
205 bar = 42
206 
207 [dependencies]
208 atty = "0.2"
209 cargo-platform = { path = "crates/cargo-platform", version = "0.1.2" }
210 pretty_env_logger = { version = "0.4", optional = true }
211 
212 [target.'cfg(windows)'.dependencies]
213 fwdansi = "1.1.0"
214 winapi = { version = "0.3", features = ["handleapi", "jobapi"] }
215 
216 [target.'cfg(unix)'.dev-dependencies]
217 miniz_oxide = "0.5"
218 
219 [dev-dependencies]
220 cargo-test-macro = { path = "crates/cargo-test-macro" }
221 
222 [build-dependencies]
223 flate2 = { version = "0.4" }
224 "#;
225 
visit_example(document: &Document) -> BTreeSet<&str>226 fn visit_example(document: &Document) -> BTreeSet<&str> {
227     let mut visitor = DependencyNameVisitor {
228         state: VisitState::Root,
229         names: BTreeSet::new(),
230     };
231 
232     visitor.visit_document(document);
233 
234     visitor.names
235 }
236 
visit_mut_example(document: &mut Document)237 fn visit_mut_example(document: &mut Document) {
238     let mut visitor = NormalizeDependencyTablesVisitor {
239         state: VisitState::Root,
240     };
241 
242     visitor.visit_document_mut(document);
243 }
244 
main()245 fn main() {
246     let mut document: Document = INPUT.parse().expect("input is valid TOML");
247 
248     println!("** visit example");
249     println!("{:?}", visit_example(&document));
250 
251     println!("** visit_mut example");
252     visit_mut_example(&mut document);
253     println!("{}", document);
254 }
255 
256 #[cfg(test)]
257 #[test]
visit_correct()258 fn visit_correct() {
259     let document: Document = INPUT.parse().expect("input is valid TOML");
260 
261     let names = visit_example(&document);
262     let expected = vec![
263         "atty",
264         "cargo-platform",
265         "pretty_env_logger",
266         "fwdansi",
267         "winapi",
268         "miniz_oxide",
269         "cargo-test-macro",
270         "flate2",
271     ]
272     .into_iter()
273     .collect();
274     assert_eq!(names, expected);
275 }
276 
277 #[cfg(test)]
278 #[test]
visit_mut_correct()279 fn visit_mut_correct() {
280     let mut document: Document = INPUT.parse().expect("input is valid TOML");
281 
282     visit_mut_example(&mut document);
283     assert_eq!(format!("{}", document), VISIT_MUT_OUTPUT);
284 }
285