hephae_macros/
lib.rs

1#![allow(internal_features)]
2#![cfg_attr(any(docsrs, docsrs_dep), feature(rustdoc_internals))]
3#![doc = include_str!("../README.md")]
4#![cfg_attr(doc, deny(missing_docs))]
5
6use std::{path::PathBuf, sync::OnceLock};
7
8pub use proc_macro2;
9pub use quote;
10use quote::ToTokens;
11pub use syn;
12use syn::Path;
13use toml_edit::{DocumentMut, Item};
14
15/// Represents `Cargo.toml`, providing functions to resolve library paths under Bevy or similar
16/// library-containing crates.
17pub struct Manifest(DocumentMut);
18impl Manifest {
19    fn new() -> Self {
20        Self(
21            std::env::var_os("CARGO_MANIFEST_DIR")
22                .map(PathBuf::from)
23                .map(|mut path| {
24                    path.push("Cargo.toml");
25                    if !path.exists() {
26                        panic!("No Cargo manifest found for crate. Expected: {}", path.display());
27                    }
28
29                    std::fs::read_to_string(path.clone())
30                        .unwrap_or_else(|e| panic!("Unable to read cargo manifest ({}): {e}", path.display()))
31                        .parse::<DocumentMut>()
32                        .unwrap_or_else(|e| panic!("Failed to parse cargo manifest ({}): {e}", path.display()))
33                })
34                .expect("CARGO_MANIFEST_DIR is not defined."),
35        )
36    }
37
38    /// Gets a lazily-initialized static instance of [`Manifest`].
39    #[inline]
40    pub fn get() -> &'static Self {
41        static INSTANCE: OnceLock<Manifest> = OnceLock::new();
42        INSTANCE.get_or_init(Self::new)
43    }
44
45    /// Resolves `bevy::{sub}`.
46    #[inline]
47    pub fn resolve_bevy(sub: impl AsRef<str>, tokens: impl ToTokens) -> syn::Result<Path> {
48        Self::get().resolve("bevy", sub, tokens)
49    }
50
51    /// Resolves `hephae::{sub}`.
52    #[inline]
53    pub fn resolve_hephae(sub: impl AsRef<str>, tokens: impl ToTokens) -> syn::Result<Path> {
54        Self::get().resolve("hephae", sub, tokens)
55    }
56
57    /// Resolves a sub-crate under the base crate, i.e., `render` under `bevy`.
58    pub fn resolve(&self, base: impl AsRef<str>, sub: impl AsRef<str>, tokens: impl ToTokens) -> syn::Result<Path> {
59        let name = |dep: &Item, name: &str| -> String {
60            if dep.as_str().is_some() {
61                name.into()
62            } else {
63                dep.get("package").and_then(|name| name.as_str()).unwrap_or(name).into()
64            }
65        };
66
67        let base = base.as_ref();
68        let sub = sub.as_ref();
69
70        let find = |deps: &Item| -> Option<syn::Result<syn::Path>> {
71            if let Some(dep) = deps.get(format!("{base}_{sub}")) {
72                Some(syn::parse_str(&format!("{}_{sub}", name(dep, base))))
73            } else if let Some(dep) = deps.get(format!("{base}-{sub}")) {
74                Some(syn::parse_str(&format!("{}_{sub}", name(dep, base))))
75            } else {
76                deps.get(base)
77                    .map(|dep| syn::parse_str(&format!("{}::{sub}", name(dep, base))))
78            }
79        };
80
81        match self
82            .0
83            .get("dependencies")
84            .and_then(find)
85            .or_else(|| self.0.get("dev-dependencies").and_then(find))
86            .ok_or_else(|| {
87                syn::Error::new_spanned(
88                    &tokens,
89                    format!("missing dependency `{base}::{sub}`; have you enabled the `{sub}` feature in `{base}`?"),
90                )
91            }) {
92            Ok(Ok(path)) => Ok(path),
93            Ok(Err(error)) | Err(error) => Err(error),
94        }
95    }
96}