1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use std::error::Error;
use toml_edit::{DocumentMut, Item, Value};
type Result<T> = std::result::Result<T, Box<dyn Error>>;
pub struct CargoToml {
pub manifest: toml_edit::DocumentMut,
}
const DEPENDENCY_KINDS: [&str; 3] = ["dependencies", "dev-dependencies", "build-dependencies"];
impl CargoToml {
pub fn load(manifest: &str) -> Result<Self> {
// Parse the manifest string into a mutable TOML document.
Ok(Self {
manifest: manifest.parse::<DocumentMut>()?,
})
}
pub fn is_published(&self) -> bool {
// Check if the package is published by looking for the `publish` key
// in the manifest.
let Item::Table(package) = &self.manifest["package"] else {
unreachable!("The package table is missing in the manifest");
};
let Some(publish) = package.get("publish") else {
return true;
};
publish.as_bool().unwrap_or(true)
}
pub fn version(&self) -> &str {
self.manifest["package"]["version"]
.as_str()
.unwrap()
.trim()
.trim_matches('"')
}
pub fn msrv(&self) -> &str {
self.manifest["package"]["rust-version"]
.as_str()
.unwrap()
.trim()
.trim_matches('"')
}
/// Calls a callback for each table that contains dependencies.
///
/// Callback arguments:
/// - `path`: The path to the table (e.g. `dependencies.package`)
/// - `dependency_kind`: The kind of dependency (e.g. `dependencies`,
/// `dev-dependencies`)
/// - `table`: The table itself
pub fn visit_dependencies(
&self,
mut handle_dependencies: impl FnMut(&str, &'static str, &toml_edit::Table),
) {
fn recurse_dependencies(
path: String,
table: &toml_edit::Table,
handle_dependencies: &mut impl FnMut(&str, &'static str, &toml_edit::Table),
) {
// Walk through tables recursively so that we can find *all* dependencies.
for (key, item) in table.iter() {
if let Item::Table(table) = item {
let path = if path.is_empty() {
key.to_string()
} else {
format!("{path}.{key}")
};
recurse_dependencies(path, table, handle_dependencies);
}
}
for dependency_kind in DEPENDENCY_KINDS {
let Some(Item::Table(table)) = table.get(dependency_kind) else {
continue;
};
handle_dependencies(&path, dependency_kind, table);
}
}
recurse_dependencies(
String::new(),
self.manifest.as_table(),
&mut handle_dependencies,
);
}
pub fn dependency_version(&self, package_name: &str) -> String {
let mut dep_version = String::new();
self.visit_dependencies(|_, _, table| {
// Update dependencies which specify a version:
if !table.contains_key(package_name) {
return;
}
match &table[package_name] {
Item::Value(Value::String(value)) => {
// package = "version"
dep_version = value.value().to_string();
}
Item::Table(table) if table.contains_key("version") => {
// [package]
// version = "version"
dep_version = table["version"].as_value().unwrap().to_string();
}
Item::Value(Value::InlineTable(table)) if table.contains_key("version") => {
// package = { version = "version" }
dep_version = table["version"].as_str().unwrap().to_string();
}
Item::None => {
// alias = { package = "foo", version = "version" }
let update_renamed_dep = table.get_values().iter().find_map(|(k, p)| {
if let Value::InlineTable(table) = p {
if let Some(Value::String(name)) = &table.get("package") {
if name.value() == package_name {
// Return the actual key of this dependency, e.g.:
// `procmacros = { package = "esp-hal-procmacros" }`
// ^^^^^^^^^^
return Some(k.last().unwrap().get().to_string());
}
}
}
None
});
if let Some(dependency_name) = update_renamed_dep {
dep_version = table[&dependency_name]["version"]
.as_value()
.unwrap()
.to_string();
}
}
_ => {}
}
});
dep_version.trim_start_matches('=').to_string()
}
}