From e530710e9b44f69b2163563ffc255bd16674760c Mon Sep 17 00:00:00 2001 From: Zeeshan Ali Khan Date: Sun, 22 Dec 2024 16:28:38 +0100 Subject: [PATCH] Switch to tomling crate for reduced dependencies `tomling` crate was specifically created to provide a simple TOML parser with fewer dependencies and focus on Cargo.toml parsing. Fixes #37. --- Cargo.toml | 4 +-- src/lib.rs | 104 +++++++++++++++++++++++++++++++++-------------------- 2 files changed, 67 insertions(+), 41 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c7c95a4..40deec5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,7 @@ readme = "./README.md" rust-version = "1.67.0" [dependencies] -toml_edit = { version = "0.22.20", default-features = false, features = [ - "parse", -] } +tomling = { git = "https://github.com/zeenix/tomling.git", rev = "53144c08" } [dev-dependencies] quote = "1.0.37" diff --git a/src/lib.rs b/src/lib.rs index 020d315..3fba7a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,6 +86,7 @@ at your option. */ use std::{ + cmp::Ordering, collections::btree_map::{self, BTreeMap}, env, fmt, fs, io, path::{Path, PathBuf}, @@ -94,7 +95,10 @@ use std::{ time::SystemTime, }; -use toml_edit::{DocumentMut, Item, Table, TomlError}; +use tomling::{ + cargo::{Dependencies, Dependency, Manifest}, + from_str, Error as TomlError, +}; /// Error type used by this crate. pub enum Error { @@ -125,8 +129,9 @@ impl fmt::Debug for Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Error::NotFound(path) => - write!(f, "Could not find `Cargo.toml` in manifest dir: `{}`.", path.display()), + Error::NotFound(path) => { + write!(f, "Could not find `Cargo.toml` in manifest dir: `{}`.", path.display()) + }, Error::CargoManifestDirNotSet => f.write_str("`CARGO_MANIFEST_DIR` env variable not set."), Error::CouldNotRead { path, .. } => write!(f, "Could not read `{}`.", path.display()), @@ -280,10 +285,12 @@ fn read_cargo_toml( manifest_ts: SystemTime, workspace_manifest_ts: SystemTime, ) -> Result { - let manifest = open_cargo_toml(manifest_path)?; + let content = open_cargo_toml(manifest_path)?; + let manifest = parse_cargo_toml(&content)?; let workspace_dependencies = if manifest_path != workspace_manifest_path { - let workspace_manifest = open_cargo_toml(workspace_manifest_path)?; + let content = open_cargo_toml(workspace_manifest_path)?; + let workspace_manifest = parse_cargo_toml(&content)?; extract_workspace_dependencies(&workspace_manifest)? } else { extract_workspace_dependencies(&manifest)? @@ -304,42 +311,41 @@ fn read_cargo_toml( /// Returns a hash map that maps from dep name to the package name. Dep name /// and package name can be the same if there doesn't exist any rename. fn extract_workspace_dependencies( - workspace_toml: &DocumentMut, + workspace_toml: &Manifest, ) -> Result, Error> { - Ok(workspace_dep_tables(&workspace_toml) + Ok(workspace_toml + .workspace() + .and_then(|w| w.dependencies()) + .map(|d| d.iter()) .into_iter() .flatten() - .map(move |(dep_name, dep_value)| { - let pkg_name = dep_value.get("package").and_then(|i| i.as_str()).unwrap_or(dep_name); + .map(move |(dep_name, dep)| { + let pkg_name = dep.package().unwrap_or(dep_name); (dep_name.to_owned(), pkg_name.to_owned()) }) .collect()) } -/// Return an iterator over all `[workspace.dependencies]` -fn workspace_dep_tables(cargo_toml: &DocumentMut) -> Option<&Table> { - cargo_toml - .get("workspace") - .and_then(|w| w.as_table()?.get("dependencies")?.as_table()) -} - /// Make sure that the given crate name is a valid rust identifier. fn sanitize_crate_name>(name: S) -> String { name.as_ref().replace('-', "_") } +/// Open the given `Cargo.toml` file and read it's content as a string. +fn open_cargo_toml(path: &Path) -> Result { + fs::read_to_string(path).map_err(|e| Error::CouldNotRead { source: e, path: path.into() }) +} + /// Open the given `Cargo.toml` and parse it into a hashmap. -fn open_cargo_toml(path: &Path) -> Result { - let content = fs::read_to_string(path) - .map_err(|e| Error::CouldNotRead { source: e, path: path.into() })?; - content.parse::().map_err(|e| Error::InvalidToml { source: e }) +fn parse_cargo_toml(content: &str) -> Result, Error> { + from_str(content).map_err(|e| Error::InvalidToml { source: e }) } /// Extract all crate names from the given `Cargo.toml` by checking the `dependencies` and /// `dev-dependencies`. fn extract_crate_names( - cargo_toml: &DocumentMut, + cargo_toml: &Manifest, workspace_dependencies: BTreeMap, ) -> Result { let package_name = extract_package_name(cargo_toml); @@ -355,16 +361,16 @@ fn extract_crate_names( }); let dep_tables = dep_tables(cargo_toml).chain(target_dep_tables(cargo_toml)); - let dep_pkgs = dep_tables.flatten().filter_map(move |(dep_name, dep_value)| { - let pkg_name = dep_value.get("package").and_then(|i| i.as_str()).unwrap_or(dep_name); + let dep_pkgs = dep_tables.filter_map(move |(dep_name, dep)| { + let pkg_name = dep.package().unwrap_or(dep_name); // We already handle this via `root_pkg` above. if package_name.as_ref().map_or(false, |n| *n == pkg_name) { - return None + return None; } // Check if this is a workspace dependency. - let workspace = dep_value.get("workspace").and_then(|w| w.as_bool()).unwrap_or_default(); + let workspace = dep.workspace().unwrap_or_default(); let pkg_name = workspace .then(|| workspace_dependencies.get(pkg_name).map(|p| p.as_ref())) @@ -379,22 +385,42 @@ fn extract_crate_names( Ok(root_pkg.into_iter().chain(dep_pkgs).collect()) } -fn extract_package_name(cargo_toml: &DocumentMut) -> Option<&str> { - cargo_toml.get("package")?.get("name")?.as_str() +fn extract_package_name<'c>(cargo_toml: &'c Manifest) -> Option<&'c str> { + cargo_toml.package().map(|p| p.name()) } -fn target_dep_tables(cargo_toml: &DocumentMut) -> impl Iterator { - cargo_toml.get("target").into_iter().filter_map(Item::as_table).flat_map(|t| { - t.iter().map(|(_, value)| value).filter_map(Item::as_table).flat_map(dep_tables) +fn target_dep_tables<'c>( + cargo_toml: &'c Manifest, +) -> impl Iterator)> { + cargo_toml.targets().into_iter().flat_map(|t| { + t.iter() + .map(|(_, t)| t) + .flat_map(|t| combined_dep_tables(t.dependencies(), t.dev_dependencies())) }) } -fn dep_tables(table: &Table) -> impl Iterator { - table - .get("dependencies") +fn dep_tables<'c>(cargo_toml: &'c Manifest) -> impl Iterator)> { + combined_dep_tables(cargo_toml.dependencies(), cargo_toml.dev_dependencies()) +} + +fn combined_dep_tables<'c>( + deps: Option<&'c Dependencies<'c>>, + dev_deps: Option<&'c Dependencies<'c>>, +) -> impl Iterator)> { + let mut deps = deps .into_iter() - .chain(table.get("dev-dependencies")) - .filter_map(Item::as_table) + .flat_map(|deps| deps.iter()) + .chain(dev_deps.into_iter().flat_map(|deps| deps.iter())) + .collect::>(); + // Ensure renames (i-e deps with `package` key) are listed the last. + deps.sort_by(|(_, a), (_, b)| match (a.package(), b.package()) { + (Some(a), Some(b)) => a.cmp(b), + (Some(_), None) => Ordering::Greater, + (None, Some(_)) => Ordering::Less, + (None, None) => Ordering::Equal, + }); + + deps.into_iter() } #[cfg(test)] @@ -410,16 +436,18 @@ mod tests { ) => { #[test] fn $name() { - let cargo_toml = $cargo_toml.parse::() + let cargo_toml = from_str($cargo_toml) .expect("Parses `Cargo.toml`"); - let workspace_cargo_toml = $workspace_toml.parse::() + let workspace_cargo_toml = from_str($workspace_toml) .expect("Parses workspace `Cargo.toml`"); let workspace_deps = extract_workspace_dependencies(&workspace_cargo_toml) .expect("Extracts workspace dependencies"); match extract_crate_names(&cargo_toml, workspace_deps) - .map(|mut map| map.remove("my_crate")) + .map(|mut map| { + map.remove("my_crate") + }) { $( $result )* => (), o => panic!("Invalid result: {:?}", o),