extern crate proc_macro; use proc_macro::TokenStream; use std::{env, path::PathBuf}; use toml_edit::{DocumentMut, Item}; /// The path to the `Cargo.toml` file for the Bevy project. pub struct BevyManifest { manifest: DocumentMut, } impl Default for BevyManifest { fn default() -> Self { Self { manifest: env::var_os("CARGO_MANIFEST_DIR") .map(PathBuf::from) .map(|mut path| { path.push("Cargo.toml"); if !path.exists() { panic!( "No Cargo manifest found for crate. Expected: {}", path.display() ); } let manifest = std::fs::read_to_string(path.clone()).unwrap_or_else(|_| { panic!("Unable to read cargo manifest: {}", path.display()) }); manifest.parse::().unwrap_or_else(|_| { panic!("Failed to parse cargo manifest: {}", path.display()) }) }) .expect("CARGO_MANIFEST_DIR is not defined."), } } } const BEVY: &str = "bevy"; const BEVY_INTERNAL: &str = "bevy_internal"; impl BevyManifest { /// Attempt to retrieve the [path](syn::Path) of a particular package in /// the [manifest](BevyManifest) by [name](str). pub fn maybe_get_path(&self, name: &str) -> Option { fn dep_package(dep: &Item) -> Option<&str> { if dep.as_str().is_some() { None } else { dep.get("package").map(|name| name.as_str().unwrap()) } } let find_in_deps = |deps: &Item| -> Option { let package = if let Some(dep) = deps.get(name) { return Some(Self::parse_str(dep_package(dep).unwrap_or(name))); } else if let Some(dep) = deps.get(BEVY) { dep_package(dep).unwrap_or(BEVY) } else if let Some(dep) = deps.get(BEVY_INTERNAL) { dep_package(dep).unwrap_or(BEVY_INTERNAL) } else { return None; }; let mut path = Self::parse_str::(package); if let Some(module) = name.strip_prefix("bevy_") { path.segments.push(Self::parse_str(module)); } Some(path) }; let deps = self.manifest.get("dependencies"); let deps_dev = self.manifest.get("dev-dependencies"); deps.and_then(find_in_deps) .or_else(|| deps_dev.and_then(find_in_deps)) } /// Returns the path for the crate with the given name. /// /// This is a convenience method for constructing a [manifest] and /// calling the [`get_path`] method. /// /// This method should only be used where you just need the path and can't /// cache the [manifest]. If caching is possible, it's recommended to create /// the [manifest] yourself and use the [`get_path`] method. /// /// [`get_path`]: Self::get_path /// [manifest]: Self pub fn get_path_direct(name: &str) -> syn::Path { Self::default().get_path(name) } /// Returns the path for the crate with the given name. pub fn get_path(&self, name: &str) -> syn::Path { self.maybe_get_path(name) .unwrap_or_else(|| Self::parse_str(name)) } /// Attempt to parse the provided [path](str) as a [syntax tree node](syn::parse::Parse) pub fn try_parse_str(path: &str) -> Option { syn::parse(path.parse::().ok()?).ok() } /// Attempt to parse provided [path](str) as a [syntax tree node](syn::parse::Parse). /// /// # Panics /// /// Will panic if the path is not able to be parsed. For a non-panicking option, see [`try_parse_str`] /// /// [`try_parse_str`]: Self::try_parse_str pub fn parse_str(path: &str) -> T { Self::try_parse_str(path).unwrap() } /// Attempt to get a subcrate [path](syn::Path) under Bevy by [name](str) pub fn get_subcrate(&self, subcrate: &str) -> Option { self.maybe_get_path(BEVY) .map(|bevy_path| { let mut segments = bevy_path.segments; segments.push(BevyManifest::parse_str(subcrate)); syn::Path { leading_colon: None, segments, } }) .or_else(|| self.maybe_get_path(&format!("bevy_{subcrate}"))) } }