use std::collections::BTreeSet;
use toml_edit::visit::*;
use toml_edit::visit_mut::*;
use toml_edit::{Array, Document, InlineTable, Item, KeyMut, Table, Value};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum VisitState {
Root,
Dependencies,
SubDependencies,
Target,
TargetWithSpec,
Other,
}
impl VisitState {
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,
}
}
}
#[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 {
let old_state = self.state;
self.state = self.state.descend(key);
visit_table_like_kv(self, key, node);
self.state = old_state;
}
}
}
#[derive(Debug)]
struct NormalizeDependencyTablesVisitor {
state: VisitState,
}
impl VisitMut for NormalizeDependencyTablesVisitor {
fn visit_table_mut(&mut self, node: &mut Table) {
visit_table_mut(self, node);
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;
self.state = self.state.descend(key.get());
match self.state {
VisitState::Target | VisitState::TargetWithSpec | VisitState::Dependencies => {
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 => {
if let Item::Table(table) = node {
let table = std::mem::replace(table, Table::new());
let inline_table = table.into_inline_table();
key.fmt();
*node = Item::Value(Value::InlineTable(inline_table));
}
}
_ => {}
}
visit_table_like_kv_mut(self, key, node);
self.state = old_state;
}
fn visit_array_mut(&mut self, node: &mut Array) {
if matches!(
self.state,
VisitState::Dependencies | VisitState::SubDependencies
) {
node.fmt();
}
}
}
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"
"#;
#[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: &Document) -> BTreeSet<&str> {
let mut visitor = DependencyNameVisitor {
state: VisitState::Root,
names: BTreeSet::new(),
};
visitor.visit_document(document);
visitor.names
}
fn visit_mut_example(document: &mut Document) {
let mut visitor = NormalizeDependencyTablesVisitor {
state: VisitState::Root,
};
visitor.visit_document_mut(document);
}
fn main() {
let mut document: Document = 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: Document = 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: Document = INPUT.parse().expect("input is valid TOML");
visit_mut_example(&mut document);
assert_eq!(format!("{}", document), VISIT_MUT_OUTPUT);
}