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(¤t_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}