Skip to main content

esp_generate/
cargo.rs

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        // Parse the manifest string into a mutable TOML document.
16        Ok(Self {
17            manifest: manifest.parse::<DocumentMut>()?,
18        })
19    }
20
21    pub fn is_published(&self) -> bool {
22        // Check if the package is published by looking for the `publish` key
23        // in the manifest.
24        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    /// Calls a callback for each table that contains dependencies.
52    ///
53    /// Callback arguments:
54    /// - `path`: The path to the table (e.g. `dependencies.package`)
55    /// - `dependency_kind`: The kind of dependency (e.g. `dependencies`,
56    ///   `dev-dependencies`)
57    /// - `table`: The table itself
58    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            // Walk through tables recursively so that we can find *all* dependencies.
68            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            // Update dependencies which specify a version:
98            if !table.contains_key(package_name) {
99                return;
100            }
101            match &table[package_name] {
102                Item::Value(Value::String(value)) => {
103                    // package = "version"
104                    dep_version = value.value().to_string();
105                }
106                Item::Table(table) if table.contains_key("version") => {
107                    // [package]
108                    // version = "version"
109                    dep_version = table["version"].as_value().unwrap().to_string();
110                }
111                Item::Value(Value::InlineTable(table)) if table.contains_key("version") => {
112                    // package = { version = "version" }
113                    dep_version = table["version"].as_str().unwrap().to_string();
114                }
115                Item::None => {
116                    // alias = { package = "foo", version = "version" }
117                    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 the actual key of this dependency, e.g.:
122                                    // `procmacros = { package = "esp-hal-procmacros" }`
123                                    //  ^^^^^^^^^^
124                                    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}