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
15pub 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 #[inline]
40 pub fn get() -> &'static Self {
41 static INSTANCE: OnceLock<Manifest> = OnceLock::new();
42 INSTANCE.get_or_init(Self::new)
43 }
44
45 #[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 #[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 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}