bevy_macro_utils/
bevy_manifest.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use std::{env, path::PathBuf};
5use toml_edit::{DocumentMut, Item};
6
7/// The path to the `Cargo.toml` file for the Bevy project.
8pub struct BevyManifest {
9    manifest: DocumentMut,
10}
11
12impl 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::<DocumentMut>().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}
36const BEVY: &str = "bevy";
37const BEVY_INTERNAL: &str = "bevy_internal";
38
39impl 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-panicking 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}