1use std::error::Error;
2
3use toml_edit::{DocumentMut, Item, Value};
4
5type Result<T> = std::result::Result<T, Box<dyn Error>>;
6
7pub struct CargoToml {
8 pub manifest: toml_edit::DocumentMut,
9}
10
11const DEPENDENCY_KINDS: [&str; 3] = ["dependencies", "dev-dependencies", "build-dependencies"];
12
13impl CargoToml {
14 pub fn load(manifest: &str) -> Result<Self> {
15 Ok(Self {
17 manifest: manifest.parse::<DocumentMut>()?,
18 })
19 }
20
21 pub fn is_published(&self) -> bool {
22 let Item::Table(package) = &self.manifest["package"] else {
25 unreachable!("The package table is missing in the manifest");
26 };
27
28 let Some(publish) = package.get("publish") else {
29 return true;
30 };
31
32 publish.as_bool().unwrap_or(true)
33 }
34
35 pub fn version(&self) -> &str {
36 self.manifest["package"]["version"]
37 .as_str()
38 .unwrap()
39 .trim()
40 .trim_matches('"')
41 }
42
43 pub fn msrv(&self) -> &str {
44 self.manifest["package"]["rust-version"]
45 .as_str()
46 .unwrap()
47 .trim()
48 .trim_matches('"')
49 }
50
51 pub fn visit_dependencies(
59 &self,
60 mut handle_dependencies: impl FnMut(&str, &'static str, &toml_edit::Table),
61 ) {
62 fn recurse_dependencies(
63 path: String,
64 table: &toml_edit::Table,
65 handle_dependencies: &mut impl FnMut(&str, &'static str, &toml_edit::Table),
66 ) {
67 for (key, item) in table.iter() {
69 if let Item::Table(table) = item {
70 let path = if path.is_empty() {
71 key.to_string()
72 } else {
73 format!("{path}.{key}")
74 };
75 recurse_dependencies(path, table, handle_dependencies);
76 }
77 }
78 for dependency_kind in DEPENDENCY_KINDS {
79 let Some(Item::Table(table)) = table.get(dependency_kind) else {
80 continue;
81 };
82
83 handle_dependencies(&path, dependency_kind, table);
84 }
85 }
86
87 recurse_dependencies(
88 String::new(),
89 self.manifest.as_table(),
90 &mut handle_dependencies,
91 );
92 }
93
94 pub fn dependency_version(&self, package_name: &str) -> String {
95 let mut dep_version = String::new();
96 self.visit_dependencies(|_, _, table| {
97 if !table.contains_key(package_name) {
99 return;
100 }
101 match &table[package_name] {
102 Item::Value(Value::String(value)) => {
103 dep_version = value.value().to_string();
105 }
106 Item::Table(table) if table.contains_key("version") => {
107 dep_version = table["version"].as_value().unwrap().to_string();
110 }
111 Item::Value(Value::InlineTable(table)) if table.contains_key("version") => {
112 dep_version = table["version"].as_str().unwrap().to_string();
114 }
115 Item::None => {
116 let update_renamed_dep = table.get_values().iter().find_map(|(k, p)| {
118 if let Value::InlineTable(table) = p {
119 if let Some(Value::String(name)) = &table.get("package") {
120 if name.value() == package_name {
121 return Some(k.last().unwrap().get().to_string());
125 }
126 }
127 }
128
129 None
130 });
131
132 if let Some(dependency_name) = update_renamed_dep {
133 dep_version = table[&dependency_name]["version"]
134 .as_value()
135 .unwrap()
136 .to_string();
137 }
138 }
139 _ => {}
140 }
141 });
142
143 dep_version.trim_start_matches('=').to_string()
144 }
145}