285 lines
7.8 KiB
Rust
285 lines
7.8 KiB
Rust
//! Example for how to use `VisitMut` to iterate over a table.
|
|
|
|
use std::collections::BTreeSet;
|
|
use toml_edit::visit::{visit_table_like_kv, Visit};
|
|
use toml_edit::visit_mut::{visit_table_like_kv_mut, visit_table_mut, VisitMut};
|
|
use toml_edit::{Array, DocumentMut, InlineTable, Item, KeyMut, Table, Value};
|
|
|
|
/// This models the visit state for dependency keys in a `Cargo.toml`.
|
|
///
|
|
/// Dependencies can be specified as:
|
|
///
|
|
/// ```toml
|
|
/// [dependencies]
|
|
/// dep1 = "0.2"
|
|
///
|
|
/// [build-dependencies]
|
|
/// dep2 = "0.3"
|
|
///
|
|
/// [dev-dependencies]
|
|
/// dep3 = "0.4"
|
|
///
|
|
/// [target.'cfg(windows)'.dependencies]
|
|
/// dep4 = "0.5"
|
|
///
|
|
/// # and target build- and dev-dependencies
|
|
/// ```
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
enum VisitState {
|
|
/// Represents the root of the table.
|
|
Root,
|
|
/// Represents "dependencies", "build-dependencies" or "dev-dependencies", or the target
|
|
/// forms of these.
|
|
Dependencies,
|
|
/// A table within dependencies.
|
|
SubDependencies,
|
|
/// Represents "target".
|
|
Target,
|
|
/// "target.[TARGET]".
|
|
TargetWithSpec,
|
|
/// Represents some other state.
|
|
Other,
|
|
}
|
|
|
|
impl VisitState {
|
|
/// Figures out the next visit state, given the current state and the given key.
|
|
fn descend(self, key: &str) -> Self {
|
|
match (self, key) {
|
|
(
|
|
VisitState::Root | VisitState::TargetWithSpec,
|
|
"dependencies" | "build-dependencies" | "dev-dependencies",
|
|
) => VisitState::Dependencies,
|
|
(VisitState::Root, "target") => VisitState::Target,
|
|
(VisitState::Root | VisitState::TargetWithSpec, _) => VisitState::Other,
|
|
(VisitState::Target, _) => VisitState::TargetWithSpec,
|
|
(VisitState::Dependencies, _) => VisitState::SubDependencies,
|
|
(VisitState::SubDependencies, _) => VisitState::SubDependencies,
|
|
(VisitState::Other, _) => VisitState::Other,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Collect the names of every dependency key.
|
|
#[derive(Debug)]
|
|
struct DependencyNameVisitor<'doc> {
|
|
state: VisitState,
|
|
names: BTreeSet<&'doc str>,
|
|
}
|
|
|
|
impl<'doc> Visit<'doc> for DependencyNameVisitor<'doc> {
|
|
fn visit_table_like_kv(&mut self, key: &'doc str, node: &'doc Item) {
|
|
if self.state == VisitState::Dependencies {
|
|
self.names.insert(key);
|
|
} else {
|
|
// Since we're only interested in collecting the top-level keys right under
|
|
// [dependencies], don't recurse unconditionally.
|
|
|
|
let old_state = self.state;
|
|
|
|
// Figure out the next state given the key.
|
|
self.state = self.state.descend(key);
|
|
|
|
// Recurse further into the document tree.
|
|
visit_table_like_kv(self, key, node);
|
|
|
|
// Restore the old state after it's done.
|
|
self.state = old_state;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Normalize all dependency tables into the format:
|
|
///
|
|
/// ```toml
|
|
/// [dependencies]
|
|
/// dep = { version = "1.0", features = ["foo", "bar"], ... }
|
|
/// ```
|
|
///
|
|
/// leaving other tables untouched.
|
|
#[derive(Debug)]
|
|
struct NormalizeDependencyTablesVisitor {
|
|
state: VisitState,
|
|
}
|
|
|
|
impl VisitMut for NormalizeDependencyTablesVisitor {
|
|
fn visit_table_mut(&mut self, node: &mut Table) {
|
|
visit_table_mut(self, node);
|
|
|
|
// The conversion from regular tables into inline ones might leave some explicit parent
|
|
// tables hanging, so convert them to implicit.
|
|
if matches!(self.state, VisitState::Target | VisitState::TargetWithSpec) {
|
|
node.set_implicit(true);
|
|
}
|
|
}
|
|
|
|
fn visit_table_like_kv_mut(&mut self, mut key: KeyMut<'_>, node: &mut Item) {
|
|
let old_state = self.state;
|
|
|
|
// Figure out the next state given the key.
|
|
self.state = self.state.descend(key.get());
|
|
|
|
match self.state {
|
|
VisitState::Target | VisitState::TargetWithSpec | VisitState::Dependencies => {
|
|
// Top-level dependency row, or above: turn inline tables into regular ones.
|
|
if let Item::Value(Value::InlineTable(inline_table)) = node {
|
|
let inline_table = std::mem::replace(inline_table, InlineTable::new());
|
|
let table = inline_table.into_table();
|
|
key.fmt();
|
|
*node = Item::Table(table);
|
|
}
|
|
}
|
|
VisitState::SubDependencies => {
|
|
// Individual dependency: turn regular tables into inline ones.
|
|
if let Item::Table(table) = node {
|
|
// Turn the table into an inline table.
|
|
let table = std::mem::replace(table, Table::new());
|
|
let inline_table = table.into_inline_table();
|
|
key.fmt();
|
|
*node = Item::Value(Value::InlineTable(inline_table));
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
// Recurse further into the document tree.
|
|
visit_table_like_kv_mut(self, key, node);
|
|
|
|
// Restore the old state after it's done.
|
|
self.state = old_state;
|
|
}
|
|
|
|
fn visit_array_mut(&mut self, node: &mut Array) {
|
|
// Format any arrays within dependencies to be on the same line.
|
|
if matches!(
|
|
self.state,
|
|
VisitState::Dependencies | VisitState::SubDependencies
|
|
) {
|
|
node.fmt();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// This is the input provided to `visit_mut_example`.
|
|
static INPUT: &str = r#"
|
|
[package]
|
|
name = "my-package"
|
|
|
|
[package.metadata.foo]
|
|
bar = 42
|
|
|
|
[dependencies]
|
|
atty = "0.2"
|
|
cargo-platform = { path = "crates/cargo-platform", version = "0.1.2" }
|
|
|
|
[dependencies.pretty_env_logger]
|
|
version = "0.4"
|
|
optional = true
|
|
|
|
[target.'cfg(windows)'.dependencies]
|
|
fwdansi = "1.1.0"
|
|
|
|
[target.'cfg(windows)'.dependencies.winapi]
|
|
version = "0.3"
|
|
features = [
|
|
"handleapi",
|
|
"jobapi",
|
|
]
|
|
|
|
[target.'cfg(unix)']
|
|
dev-dependencies = { miniz_oxide = "0.5" }
|
|
|
|
[dev-dependencies.cargo-test-macro]
|
|
path = "crates/cargo-test-macro"
|
|
|
|
[build-dependencies.flate2]
|
|
version = "0.4"
|
|
"#;
|
|
|
|
/// This is the output produced by `visit_mut_example`.
|
|
#[cfg(test)]
|
|
static VISIT_MUT_OUTPUT: &str = r#"
|
|
[package]
|
|
name = "my-package"
|
|
|
|
[package.metadata.foo]
|
|
bar = 42
|
|
|
|
[dependencies]
|
|
atty = "0.2"
|
|
cargo-platform = { path = "crates/cargo-platform", version = "0.1.2" }
|
|
pretty_env_logger = { version = "0.4", optional = true }
|
|
|
|
[target.'cfg(windows)'.dependencies]
|
|
fwdansi = "1.1.0"
|
|
winapi = { version = "0.3", features = ["handleapi", "jobapi"] }
|
|
|
|
[target.'cfg(unix)'.dev-dependencies]
|
|
miniz_oxide = "0.5"
|
|
|
|
[dev-dependencies]
|
|
cargo-test-macro = { path = "crates/cargo-test-macro" }
|
|
|
|
[build-dependencies]
|
|
flate2 = { version = "0.4" }
|
|
"#;
|
|
|
|
fn visit_example(document: &DocumentMut) -> BTreeSet<&str> {
|
|
let mut visitor = DependencyNameVisitor {
|
|
state: VisitState::Root,
|
|
names: BTreeSet::new(),
|
|
};
|
|
|
|
visitor.visit_document(document);
|
|
|
|
visitor.names
|
|
}
|
|
|
|
fn visit_mut_example(document: &mut DocumentMut) {
|
|
let mut visitor = NormalizeDependencyTablesVisitor {
|
|
state: VisitState::Root,
|
|
};
|
|
|
|
visitor.visit_document_mut(document);
|
|
}
|
|
|
|
fn main() {
|
|
let mut document: DocumentMut = INPUT.parse().expect("input is valid TOML");
|
|
|
|
println!("** visit example");
|
|
println!("{:?}", visit_example(&document));
|
|
|
|
println!("** visit_mut example");
|
|
visit_mut_example(&mut document);
|
|
println!("{document}");
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[test]
|
|
fn visit_correct() {
|
|
let document: DocumentMut = INPUT.parse().expect("input is valid TOML");
|
|
|
|
let names = visit_example(&document);
|
|
let expected = vec![
|
|
"atty",
|
|
"cargo-platform",
|
|
"pretty_env_logger",
|
|
"fwdansi",
|
|
"winapi",
|
|
"miniz_oxide",
|
|
"cargo-test-macro",
|
|
"flate2",
|
|
]
|
|
.into_iter()
|
|
.collect();
|
|
assert_eq!(names, expected);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[test]
|
|
fn visit_mut_correct() {
|
|
let mut document: DocumentMut = INPUT.parse().expect("input is valid TOML");
|
|
|
|
visit_mut_example(&mut document);
|
|
assert_eq!(format!("{document}"), VISIT_MUT_OUTPUT);
|
|
}
|