|
| 1 | +extern crate proc_macro; |
| 2 | + |
| 3 | +use proc_macro::TokenStream; |
| 4 | +use std::{env, path::PathBuf}; |
| 5 | +use toml_edit::{Document, Item}; |
| 6 | + |
| 7 | +/// The path to the `Cargo.toml` file for the Bevy project. |
| 8 | +pub struct BevyManifest { |
| 9 | + manifest: Document, |
| 10 | +} |
| 11 | + |
| 12 | +impl Default for BevyManifest { |
| 13 | + fn default() -> Self { |
| 14 | + Self { |
| 15 | + manifest: env::var_os("CARGO_MANIFEST_DIR") |
| 16 | + .map(PathBuf::from) |
| 17 | + .map(|mut path| { |
| 18 | + path.push("Cargo.toml"); |
| 19 | + if !path.exists() { |
| 20 | + panic!( |
| 21 | + "No Cargo manifest found for crate. Expected: {}", |
| 22 | + path.display() |
| 23 | + ); |
| 24 | + } |
| 25 | + let manifest = std::fs::read_to_string(path.clone()).unwrap_or_else(|_| { |
| 26 | + panic!("Unable to read cargo manifest: {}", path.display()) |
| 27 | + }); |
| 28 | + manifest.parse::<Document>().unwrap_or_else(|_| { |
| 29 | + panic!("Failed to parse cargo manifest: {}", path.display()) |
| 30 | + }) |
| 31 | + }) |
| 32 | + .expect("CARGO_MANIFEST_DIR is not defined."), |
| 33 | + } |
| 34 | + } |
| 35 | +} |
| 36 | +const BEVY: &str = "bevy"; |
| 37 | +const BEVY_INTERNAL: &str = "bevy_internal"; |
| 38 | + |
| 39 | +impl BevyManifest { |
| 40 | + /// Attempt to retrieve the [path](syn::Path) of a particular package in |
| 41 | + /// the [manifest](BevyManifest) by [name](str). |
| 42 | + pub fn maybe_get_path(&self, name: &str) -> Option<syn::Path> { |
| 43 | + fn dep_package(dep: &Item) -> Option<&str> { |
| 44 | + if dep.as_str().is_some() { |
| 45 | + None |
| 46 | + } else { |
| 47 | + dep.get("package").map(|name| name.as_str().unwrap()) |
| 48 | + } |
| 49 | + } |
| 50 | + |
| 51 | + let find_in_deps = |deps: &Item| -> Option<syn::Path> { |
| 52 | + let package = if let Some(dep) = deps.get(name) { |
| 53 | + return Some(Self::parse_str(dep_package(dep).unwrap_or(name))); |
| 54 | + } else if let Some(dep) = deps.get(BEVY) { |
| 55 | + dep_package(dep).unwrap_or(BEVY) |
| 56 | + } else if let Some(dep) = deps.get(BEVY_INTERNAL) { |
| 57 | + dep_package(dep).unwrap_or(BEVY_INTERNAL) |
| 58 | + } else { |
| 59 | + return None; |
| 60 | + }; |
| 61 | + |
| 62 | + let mut path = Self::parse_str::<syn::Path>(package); |
| 63 | + if let Some(module) = name.strip_prefix("bevy_") { |
| 64 | + path.segments.push(Self::parse_str(module)); |
| 65 | + } |
| 66 | + Some(path) |
| 67 | + }; |
| 68 | + |
| 69 | + let deps = self.manifest.get("dependencies"); |
| 70 | + let deps_dev = self.manifest.get("dev-dependencies"); |
| 71 | + |
| 72 | + deps.and_then(find_in_deps) |
| 73 | + .or_else(|| deps_dev.and_then(find_in_deps)) |
| 74 | + } |
| 75 | + |
| 76 | + /// Returns the path for the crate with the given name. |
| 77 | + /// |
| 78 | + /// This is a convenience method for constructing a [manifest] and |
| 79 | + /// calling the [`get_path`] method. |
| 80 | + /// |
| 81 | + /// This method should only be used where you just need the path and can't |
| 82 | + /// cache the [manifest]. If caching is possible, it's recommended to create |
| 83 | + /// the [manifest] yourself and use the [`get_path`] method. |
| 84 | + /// |
| 85 | + /// [`get_path`]: Self::get_path |
| 86 | + /// [manifest]: Self |
| 87 | + pub fn get_path_direct(name: &str) -> syn::Path { |
| 88 | + Self::default().get_path(name) |
| 89 | + } |
| 90 | + |
| 91 | + /// Returns the path for the crate with the given name. |
| 92 | + pub fn get_path(&self, name: &str) -> syn::Path { |
| 93 | + self.maybe_get_path(name) |
| 94 | + .unwrap_or_else(|| Self::parse_str(name)) |
| 95 | + } |
| 96 | + |
| 97 | + /// Attempt to parse the provided [path](str) as a [syntax tree node](syn::parse::Parse) |
| 98 | + pub fn try_parse_str<T: syn::parse::Parse>(path: &str) -> Option<T> { |
| 99 | + syn::parse(path.parse::<TokenStream>().ok()?).ok() |
| 100 | + } |
| 101 | + |
| 102 | + /// Attempt to parse provided [path](str) as a [syntax tree node](syn::parse::Parse). |
| 103 | + /// |
| 104 | + /// # Panics |
| 105 | + /// |
| 106 | + /// Will panic if the path is not able to be parsed. For a non-panicing option, see [`try_parse_str`] |
| 107 | + /// |
| 108 | + /// [`try_parse_str`]: Self::try_parse_str |
| 109 | + pub fn parse_str<T: syn::parse::Parse>(path: &str) -> T { |
| 110 | + Self::try_parse_str(path).unwrap() |
| 111 | + } |
| 112 | + |
| 113 | + /// Attempt to get a subcrate [path](syn::Path) under Bevy by [name](str) |
| 114 | + pub fn get_subcrate(&self, subcrate: &str) -> Option<syn::Path> { |
| 115 | + self.maybe_get_path(BEVY) |
| 116 | + .map(|bevy_path| { |
| 117 | + let mut segments = bevy_path.segments; |
| 118 | + segments.push(BevyManifest::parse_str(subcrate)); |
| 119 | + syn::Path { |
| 120 | + leading_colon: None, |
| 121 | + segments, |
| 122 | + } |
| 123 | + }) |
| 124 | + .or_else(|| self.maybe_get_path(&format!("bevy_{subcrate}"))) |
| 125 | + } |
| 126 | +} |
0 commit comments