find_crate/
lib.rs

1//! Find the crate name from the current `Cargo.toml`.
2//!
3//! When writing declarative macros, `$crate` representing the current crate is
4//! very useful, but procedural macros do not have this. If you know the current
5//! name of the crate you want to use, you can do the same thing as `$crate`.
6//! This crate provides the features to make it easy.
7//!
8//! # Examples
9//!
10//! [`find_crate`] gets the crate name from the current `Cargo.toml`.
11//!
12//! ```rust
13//! use find_crate::find_crate;
14//! use proc_macro2::{Ident, Span, TokenStream};
15//! use quote::quote;
16//!
17//! fn import() -> TokenStream {
18//!     let name = find_crate(|s| s == "foo").unwrap().name;
19//!     let name = Ident::new(&name, Span::call_site());
20//!     // If your proc-macro crate is 2018 edition, use `quote!(use #name as _foo;)` instead.
21//!     quote!(extern crate #name as _foo;)
22//! }
23//! ```
24//!
25//! As in this example, it is easy to handle cases where proc-macro is exported
26//! from multiple crates.
27//!
28//! ```rust
29//! use find_crate::find_crate;
30//! use proc_macro2::{Ident, Span, TokenStream};
31//! use quote::quote;
32//!
33//! fn import() -> TokenStream {
34//!     let name = find_crate(|s| s == "foo" || s == "foo-core").unwrap().name;
35//!     let name = Ident::new(&name, Span::call_site());
36//!     // If your proc-macro crate is 2018 edition, use `quote!(use #name as _foo;)` instead.
37//!     quote!(extern crate #name as _foo;)
38//! }
39//! ```
40//!
41//! Using [`Manifest`] to search for multiple crates. It is much more efficient
42//! than using [`find_crate`] for each crate.
43//!
44//! ```rust
45//! use find_crate::Manifest;
46//! use proc_macro2::{Ident, Span, TokenStream};
47//! use quote::{format_ident, quote};
48//!
49//! const CRATE_NAMES: &[&[&str]] = &[
50//!     &["foo", "foo-core"],
51//!     &["bar", "bar-util", "bar-core"],
52//!     &["baz"],
53//! ];
54//!
55//! fn imports() -> TokenStream {
56//!     let mut tts = TokenStream::new();
57//!     let manifest = Manifest::new().unwrap();
58//!
59//!     for names in CRATE_NAMES {
60//!         let name = manifest.find(|s| names.iter().any(|x| s == *x)).unwrap().name;
61//!         let name = Ident::new(&name, Span::call_site());
62//!         let import_name = format_ident!("_{}", names[0]);
63//!         // If your proc-macro crate is 2018 edition, use `quote!(use #name as #import_name;)` instead.
64//!         tts.extend(quote!(extern crate #name as #import_name;));
65//!     }
66//!     tts
67//! }
68//! ```
69//!
70//! By default it will be searched from `dependencies` and `dev-dependencies`.
71//! Also, [`find_crate`] and [`Manifest::new`] read `Cargo.toml` in
72//! [`CARGO_MANIFEST_DIR`] as manifest.
73//!
74//! # Alternatives
75//!
76//! If you write function-like procedural macros, [you can combine it with
77//! declarative macros to support both crate renaming and macro
78//! re-exporting.][rust-lang/futures-rs#2124]
79//!
80//! This crate is intended to provide more powerful features such as support
81//! for multiple crate names and versions. For general purposes,
82//! [proc-macro-crate], which provides a simpler API, may be easier to use.
83//!
84//! [`CARGO_MANIFEST_DIR`]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
85//! [rust-lang/futures-rs#2124]: https://github.com/rust-lang/futures-rs/pull/2124
86//! [proc-macro-crate]: https://github.com/bkchr/proc-macro-crate
87
88#![doc(test(
89    no_crate_inject,
90    attr(
91        deny(warnings, rust_2018_idioms, single_use_lifetimes),
92        allow(dead_code, unused_variables)
93    )
94))]
95#![forbid(unsafe_code)]
96#![warn(future_incompatible, rust_2018_idioms, single_use_lifetimes, unreachable_pub)]
97#![warn(missing_debug_implementations, missing_docs)]
98#![warn(clippy::all, clippy::default_trait_access)]
99
100mod error;
101
102use std::{
103    env,
104    fs::File,
105    io::Read,
106    path::{Path, PathBuf},
107};
108use toml::value::{Table, Value};
109
110pub use crate::error::Error;
111
112type Result<T, E = Error> = std::result::Result<T, E>;
113
114/// The [`CARGO_MANIFEST_DIR`] environment variable.
115///
116/// [`CARGO_MANIFEST_DIR`]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
117const MANIFEST_DIR: &str = "CARGO_MANIFEST_DIR";
118
119/// Find the crate name from the current `Cargo.toml`.
120///
121/// This function reads `Cargo.toml` in [`CARGO_MANIFEST_DIR`] as manifest.
122///
123/// Note that this function needs to be used in the context of proc-macro.
124///
125/// # Examples
126///
127/// ```rust
128/// use find_crate::find_crate;
129/// use proc_macro2::{Ident, Span, TokenStream};
130/// use quote::quote;
131///
132/// fn import() -> TokenStream {
133///     let name = find_crate(|s| s == "foo" || s == "foo-core").unwrap().name;
134///     let name = Ident::new(&name, Span::call_site());
135///     // If your proc-macro crate is 2018 edition, use `quote!(use #name as _foo;)` instead.
136///     quote!(extern crate #name as _foo;)
137/// }
138/// ```
139///
140/// [`CARGO_MANIFEST_DIR`]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
141pub fn find_crate<P>(predicate: P) -> Result<Package>
142where
143    P: FnMut(&str) -> bool,
144{
145    Manifest::new()?.find(predicate).ok_or(Error::NotFound)
146}
147
148/// The kind of dependencies to be searched.
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub enum Dependencies {
151    /// Search from `dependencies` and `dev-dependencies`.
152    Default,
153    /// Search from `dependencies`.
154    Release,
155    /// Search from `dev-dependencies`.
156    Dev,
157    /// Search from `build-dependencies`.
158    Build,
159    /// Search from `dependencies`, `dev-dependencies` and `build-dependencies`.
160    All,
161}
162
163impl Dependencies {
164    fn as_slice(self) -> &'static [&'static str] {
165        match self {
166            Dependencies::Default => &["dependencies", "dev-dependencies"],
167            Dependencies::Release => &["dependencies"],
168            Dependencies::Dev => &["dev-dependencies"],
169            Dependencies::Build => &["build-dependencies"],
170            Dependencies::All => &["dependencies", "dev-dependencies", "build-dependencies"],
171        }
172    }
173}
174
175impl Default for Dependencies {
176    fn default() -> Self {
177        Dependencies::Default
178    }
179}
180
181/// The package data. This has information on the current package name,
182/// original package name, and specified version.
183#[derive(Debug, Clone, PartialEq, Eq)]
184pub struct Package {
185    /// The key of this dependency in the manifest.
186    key: String,
187
188    // The key or the value of 'package' key.
189    // If this is `None`, the value of `key` field is the original name.
190    package: Option<String>,
191
192    /// The current name of the package. This is always a valid rust identifier.
193    pub name: String,
194
195    /// The version requirement of the package. Returns `*` if no version
196    /// requirement is specified.
197    pub version: String,
198}
199
200impl Package {
201    /// Returns the original package name.
202    pub fn original_name(&self) -> &str {
203        self.package.as_ref().unwrap_or(&self.key)
204    }
205
206    /// Returns `true` if the value of the [`name`] field is the original package
207    /// name.
208    ///
209    /// [`name`]: Package::name
210    pub fn is_original(&self) -> bool {
211        self.package.is_none()
212    }
213}
214
215/// The manifest of cargo.
216///
217/// Note that this function needs to be used in the context of proc-macro.
218#[derive(Debug, Clone)]
219pub struct Manifest {
220    manifest: Table,
221
222    /// The kind of dependencies to be searched.
223    pub dependencies: Dependencies,
224}
225
226impl Manifest {
227    /// Creates a new `Manifest` from the current `Cargo.toml`.
228    ///
229    /// This function reads `Cargo.toml` in [`CARGO_MANIFEST_DIR`] as manifest.
230    ///
231    /// [`CARGO_MANIFEST_DIR`]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
232    pub fn new() -> Result<Self> {
233        Self::from_path(&manifest_path()?)
234    }
235
236    // TODO: Should we support custom manifest paths?
237    //       And what should we do if the file is not found?
238    //       (should we use `CARGO_MANIFEST_DIR`? Or should we return an error?)
239    /// Creates a new `Manifest` from the specified toml file.
240    fn from_path(manifest_path: &Path) -> Result<Self> {
241        let mut bytes = Vec::new();
242        File::open(manifest_path)?.read_to_end(&mut bytes)?;
243        toml::from_slice(&bytes).map_err(Into::into).map(Self::from_toml)
244    }
245
246    /// Creates a new `Manifest` from a toml table.
247    pub fn from_toml(manifest: Table) -> Self {
248        Self { manifest, dependencies: Dependencies::default() }
249    }
250
251    /// Find the crate.
252    ///
253    /// The argument of the closure is the original name of the package.
254    ///
255    /// # Examples
256    ///
257    /// ```rust
258    /// use find_crate::Manifest;
259    /// use proc_macro2::{Ident, Span, TokenStream};
260    /// use quote::quote;
261    ///
262    /// fn import() -> TokenStream {
263    ///     let manifest = Manifest::new().unwrap();
264    ///     let name = manifest.find(|s| s == "foo" || s == "foo-core").unwrap().name;
265    ///     let name = Ident::new(&name, Span::call_site());
266    ///     // If your proc-macro crate is 2018 edition, use `quote!(use #name as _foo;)` instead.
267    ///     quote!(extern crate #name as _foo;)
268    /// }
269    /// ```
270    #[inline]
271    pub fn find<P>(&self, mut predicate: P) -> Option<Package>
272    where
273        P: FnMut(&str) -> bool,
274    {
275        self.find2(|s, _| predicate(s))
276    }
277
278    /// Find the crate.
279    ///
280    /// The first argument of the closure is the original name of the package
281    /// and the second argument is the version of the package.
282    ///
283    /// # Examples
284    ///
285    /// ```rust
286    /// use find_crate::Manifest;
287    /// use proc_macro2::{Ident, Span, TokenStream};
288    /// use quote::quote;
289    /// use semver::{Version, VersionReq};
290    ///
291    /// fn check_version(req: &str, version: &Version) -> bool {
292    ///     VersionReq::parse(req).unwrap().matches(version)
293    /// }
294    ///
295    /// fn import() -> TokenStream {
296    ///     let version = Version::parse("0.3.0").unwrap();
297    ///     let manifest = Manifest::new().unwrap();
298    ///     let name = manifest
299    ///         .find2(|name, req| name == "foo" && check_version(req, &version))
300    ///         .unwrap()
301    ///         .name;
302    ///     let name = Ident::new(&name, Span::call_site());
303    ///     // If your proc-macro crate is 2018 edition, use `quote!(use #name as _foo;)` instead.
304    ///     quote!(extern crate #name as _foo;)
305    /// }
306    /// ```
307    #[inline]
308    pub fn find2<P>(&self, predicate: P) -> Option<Package>
309    where
310        P: FnMut(&str, &str) -> bool,
311    {
312        find(&self.manifest, self.dependencies, predicate)
313    }
314
315    /// The package for the crate that this manifest represents.
316    ///
317    /// # Examples
318    ///
319    /// ```rust
320    /// use find_crate::Manifest;
321    /// use proc_macro2::{Ident, Span, TokenStream};
322    /// use quote::quote;
323    ///
324    /// fn current_crate_name() -> TokenStream {
325    ///     let manifest = Manifest::new().unwrap();
326    ///     let current_crate_package = manifest.crate_package().unwrap();
327    ///     let name = Ident::new(&current_crate_package.name, Span::call_site());
328    ///     quote!(#name)
329    /// }
330    /// ```
331    pub fn crate_package(&self) -> Result<Package> {
332        let package_section = self
333            .manifest
334            .get("package")
335            .ok_or_else(|| Error::InvalidManifest("[package] section is missing".to_string()))?;
336
337        let package_key_value = package_section.get("name").ok_or_else(|| {
338            Error::InvalidManifest("[package] section is missing `name`".to_string())
339        })?;
340
341        let package_key = package_key_value.as_str().ok_or_else(|| {
342            Error::InvalidManifest("`name` in [package] section is not a string".to_string())
343        })?;
344
345        let package_version_value = package_section.get("version").ok_or_else(|| {
346            Error::InvalidManifest("[package] section is missing `version`".to_string())
347        })?;
348
349        let package_version = package_version_value.as_str().ok_or_else(|| {
350            Error::InvalidManifest("`version` in [package] section is not a string".to_string())
351        })?;
352
353        let package = Package {
354            key: package_key.to_string(),
355            package: None,
356            name: package_key.replace("-", "_"),
357            version: package_version.to_string(),
358        };
359
360        Ok(package)
361    }
362}
363
364fn manifest_path() -> Result<PathBuf> {
365    env::var_os(MANIFEST_DIR).ok_or(Error::NotFoundManifestDir).map(PathBuf::from).map(
366        |mut path| {
367            path.push("Cargo.toml");
368            path
369        },
370    )
371}
372
373fn find<P>(manifest: &Table, dependencies: Dependencies, mut predicate: P) -> Option<Package>
374where
375    P: FnMut(&str, &str) -> bool,
376{
377    fn find_inner<P>(table: &Table, dependencies: &str, predicate: P) -> Option<Package>
378    where
379        P: FnMut(&str, &str) -> bool,
380    {
381        find_from_dependencies(table.get(dependencies)?.as_table()?, predicate)
382    }
383
384    dependencies
385        .as_slice()
386        .iter()
387        .find_map(|dependencies| find_inner(manifest, dependencies, &mut predicate))
388        .or_else(|| {
389            dependencies.as_slice().iter().find_map(|dependencies| {
390                manifest
391                    .get("target")?
392                    .as_table()?
393                    .values()
394                    .find_map(|table| find_inner(table.as_table()?, dependencies, &mut predicate))
395            })
396        })
397}
398
399fn find_from_dependencies<P>(table: &Table, mut predicate: P) -> Option<Package>
400where
401    P: FnMut(&str, &str) -> bool,
402{
403    fn package<P>(value: &Value, version: &str, predicate: P) -> Option<String>
404    where
405        P: FnOnce(&str, &str) -> bool,
406    {
407        value
408            .as_table()?
409            .get("package")?
410            .as_str()
411            .and_then(|s| if predicate(s, version) { Some(s.to_string()) } else { None })
412    }
413
414    fn version(value: &Value) -> Option<&str> {
415        value.as_str().or_else(|| value.as_table()?.get("version")?.as_str())
416    }
417
418    table.iter().find_map(|(key, value)| {
419        let version = version(value).unwrap_or("*");
420        let package = package(value, version, &mut predicate);
421        if package.is_some() || predicate(key, version) {
422            Some(Package {
423                key: key.clone(),
424                name: key.replace("-", "_"),
425                version: version.to_string(),
426                package,
427            })
428        } else {
429            None
430        }
431    })
432}