summaryrefslogtreecommitdiffstats
path: root/rust/vendor/proc-macro-crate/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust/vendor/proc-macro-crate/src/lib.rs')
-rw-r--r--rust/vendor/proc-macro-crate/src/lib.rs335
1 files changed, 335 insertions, 0 deletions
diff --git a/rust/vendor/proc-macro-crate/src/lib.rs b/rust/vendor/proc-macro-crate/src/lib.rs
new file mode 100644
index 0000000..09a2dd6
--- /dev/null
+++ b/rust/vendor/proc-macro-crate/src/lib.rs
@@ -0,0 +1,335 @@
+/*!
+
+[![](https://docs.rs/proc-macro-crate/badge.svg)](https://docs.rs/proc-macro-crate/) [![](https://img.shields.io/crates/v/proc-macro-crate.svg)](https://crates.io/crates/proc-macro-crate) [![](https://img.shields.io/crates/d/proc-macro-crate.png)](https://crates.io/crates/proc-macro-crate) [![Build Status](https://travis-ci.org/bkchr/proc-macro-crate.png?branch=master)](https://travis-ci.org/bkchr/proc-macro-crate)
+
+Providing support for `$crate` in procedural macros.
+
+* [Introduction](#introduction)
+* [Example](#example)
+* [License](#license)
+
+## Introduction
+
+In `macro_rules!` `$crate` is used to get the path of the crate where a macro is declared in. In
+procedural macros there is currently no easy way to get this path. A common hack is to import the
+desired crate with a know name and use this. However, with rust edition 2018 and dropping
+`extern crate` declarations from `lib.rs`, people start to rename crates in `Cargo.toml` directly.
+However, this breaks importing the crate, as the proc-macro developer does not know the renamed
+name of the crate that should be imported.
+
+This crate provides a way to get the name of a crate, even if it renamed in `Cargo.toml`. For this
+purpose a single function `crate_name` is provided. This function needs to be called in the context
+of a proc-macro with the name of the desired crate. `CARGO_MANIFEST_DIR` will be used to find the
+current active `Cargo.toml` and this `Cargo.toml` is searched for the desired crate.
+
+## Example
+
+```
+use quote::quote;
+use syn::Ident;
+use proc_macro2::Span;
+use proc_macro_crate::{crate_name, FoundCrate};
+
+fn import_my_crate() {
+ let found_crate = crate_name("my-crate").expect("my-crate is present in `Cargo.toml`");
+
+ match found_crate {
+ FoundCrate::Itself => quote!( crate::Something ),
+ FoundCrate::Name(name) => {
+ let ident = Ident::new(&name, Span::call_site());
+ quote!( #ident::Something )
+ }
+ };
+}
+
+# fn main() {}
+```
+
+## License
+
+Licensed under either of
+
+ * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
+
+ * [MIT license](http://opensource.org/licenses/MIT)
+
+at your option.
+*/
+
+use std::{
+ collections::HashMap,
+ env,
+ fs::File,
+ io::{self, Read},
+ path::{Path, PathBuf},
+};
+
+use toml::{self, value::Table};
+
+type CargoToml = HashMap<String, toml::Value>;
+
+/// Error type used by this crate.
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error("Could not find `Cargo.toml` in manifest dir: `{0}`.")]
+ NotFound(PathBuf),
+ #[error("`CARGO_MANIFEST_DIR` env variable not set.")]
+ CargoManifestDirNotSet,
+ #[error("Could not read `{path}`.")]
+ CouldNotRead { path: PathBuf, source: io::Error },
+ #[error("Invalid toml file.")]
+ InvalidToml { source: toml::de::Error },
+ #[error("Could not find `{crate_name}` in `dependencies` or `dev-dependencies` in `{path}`!")]
+ CrateNotFound { crate_name: String, path: PathBuf },
+}
+
+/// The crate as found by [`crate_name`].
+#[derive(Debug, PartialEq, Clone, Eq)]
+pub enum FoundCrate {
+ /// The searched crate is this crate itself.
+ Itself,
+ /// The searched crate was found with this name.
+ Name(String),
+}
+
+/// Find the crate name for the given `orig_name` in the current `Cargo.toml`.
+///
+/// `orig_name` should be the original name of the searched crate.
+///
+/// The current `Cargo.toml` is determined by taking `CARGO_MANIFEST_DIR/Cargo.toml`.
+///
+/// # Returns
+///
+/// - `Ok(orig_name)` if the crate was found, but not renamed in the `Cargo.toml`.
+/// - `Ok(RENAMED)` if the crate was found, but is renamed in the `Cargo.toml`. `RENAMED` will be
+/// the renamed name.
+/// - `Err` if an error occurred.
+///
+/// The returned crate name is sanitized in such a way that it is a valid rust identifier. Thus,
+/// it is ready to be used in `extern crate` as identifier.
+pub fn crate_name(orig_name: &str) -> Result<FoundCrate, Error> {
+ let manifest_dir =
+ PathBuf::from(env::var("CARGO_MANIFEST_DIR").map_err(|_| Error::CargoManifestDirNotSet)?);
+
+ let cargo_toml_path = manifest_dir.join("Cargo.toml");
+
+ if !cargo_toml_path.exists() {
+ return Err(Error::NotFound(manifest_dir.into()));
+ }
+
+ let cargo_toml = open_cargo_toml(&cargo_toml_path)?;
+
+ extract_crate_name(orig_name, cargo_toml, &cargo_toml_path)
+}
+
+/// Make sure that the given crate name is a valid rust identifier.
+fn sanitize_crate_name<S: AsRef<str>>(name: S) -> String {
+ name.as_ref().replace("-", "_")
+}
+
+/// Open the given `Cargo.toml` and parse it into a hashmap.
+fn open_cargo_toml(path: &Path) -> Result<CargoToml, Error> {
+ let mut content = String::new();
+ File::open(path)
+ .map_err(|e| Error::CouldNotRead {
+ source: e,
+ path: path.into(),
+ })?
+ .read_to_string(&mut content)
+ .map_err(|e| Error::CouldNotRead {
+ source: e,
+ path: path.into(),
+ })?;
+ toml::from_str(&content).map_err(|e| Error::InvalidToml { source: e })
+}
+
+/// Extract the crate name for the given `orig_name` from the given `Cargo.toml` by checking the
+/// `dependencies` and `dev-dependencies`.
+///
+/// Returns `Ok(orig_name)` if the crate is not renamed in the `Cargo.toml` or otherwise
+/// the renamed identifier.
+fn extract_crate_name(
+ orig_name: &str,
+ mut cargo_toml: CargoToml,
+ cargo_toml_path: &Path,
+) -> Result<FoundCrate, Error> {
+ if let Some(toml::Value::Table(t)) = cargo_toml.get("package") {
+ if let Some(toml::Value::String(s)) = t.get("name") {
+ if s == orig_name {
+ if std::env::var_os("CARGO_TARGET_TMPDIR").is_none() {
+ // We're running for a library/binary crate
+ return Ok(FoundCrate::Itself);
+ } else {
+ // We're running for an integration test
+ return Ok(FoundCrate::Name(sanitize_crate_name(orig_name)));
+ }
+ }
+ }
+ }
+
+ if let Some(name) = ["dependencies", "dev-dependencies"]
+ .iter()
+ .find_map(|k| search_crate_at_key(k, orig_name, &mut cargo_toml))
+ {
+ return Ok(FoundCrate::Name(sanitize_crate_name(name)));
+ }
+
+ // Start searching `target.xy.dependencies`
+ if let Some(name) = cargo_toml
+ .remove("target")
+ .and_then(|t| t.try_into::<Table>().ok())
+ .and_then(|t| {
+ t.values()
+ .filter_map(|v| v.as_table())
+ .filter_map(|t| t.get("dependencies").and_then(|t| t.as_table()))
+ .find_map(|t| extract_crate_name_from_deps(orig_name, t.clone()))
+ })
+ {
+ return Ok(FoundCrate::Name(sanitize_crate_name(name)));
+ }
+
+ Err(Error::CrateNotFound {
+ crate_name: orig_name.into(),
+ path: cargo_toml_path.into(),
+ })
+}
+
+/// Search the `orig_name` crate at the given `key` in `cargo_toml`.
+fn search_crate_at_key(key: &str, orig_name: &str, cargo_toml: &mut CargoToml) -> Option<String> {
+ cargo_toml
+ .remove(key)
+ .and_then(|v| v.try_into::<Table>().ok())
+ .and_then(|t| extract_crate_name_from_deps(orig_name, t))
+}
+
+/// Extract the crate name from the given dependencies.
+///
+/// Returns `Some(orig_name)` if the crate is not renamed in the `Cargo.toml` or otherwise
+/// the renamed identifier.
+fn extract_crate_name_from_deps(orig_name: &str, deps: Table) -> Option<String> {
+ for (key, value) in deps.into_iter() {
+ let renamed = value
+ .try_into::<Table>()
+ .ok()
+ .and_then(|t| t.get("package").cloned())
+ .map(|t| t.as_str() == Some(orig_name))
+ .unwrap_or(false);
+
+ if key == orig_name || renamed {
+ return Some(key.clone());
+ }
+ }
+
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ macro_rules! create_test {
+ (
+ $name:ident,
+ $cargo_toml:expr,
+ $( $result:tt )*
+ ) => {
+ #[test]
+ fn $name() {
+ let cargo_toml = toml::from_str($cargo_toml).expect("Parses `Cargo.toml`");
+ let path = PathBuf::from("test-path");
+
+ match extract_crate_name("my_crate", cargo_toml, &path) {
+ $( $result )* => (),
+ o => panic!("Invalid result: {:?}", o),
+ }
+ }
+ };
+ }
+
+ create_test! {
+ deps_with_crate,
+ r#"
+ [dependencies]
+ my_crate = "0.1"
+ "#,
+ Ok(FoundCrate::Name(name)) if name == "my_crate"
+ }
+
+ create_test! {
+ dev_deps_with_crate,
+ r#"
+ [dev-dependencies]
+ my_crate = "0.1"
+ "#,
+ Ok(FoundCrate::Name(name)) if name == "my_crate"
+ }
+
+ create_test! {
+ deps_with_crate_renamed,
+ r#"
+ [dependencies]
+ cool = { package = "my_crate", version = "0.1" }
+ "#,
+ Ok(FoundCrate::Name(name)) if name == "cool"
+ }
+
+ create_test! {
+ deps_with_crate_renamed_second,
+ r#"
+ [dependencies.cool]
+ package = "my_crate"
+ version = "0.1"
+ "#,
+ Ok(FoundCrate::Name(name)) if name == "cool"
+ }
+
+ create_test! {
+ deps_empty,
+ r#"
+ [dependencies]
+ "#,
+ Err(Error::CrateNotFound {
+ crate_name,
+ path,
+ }) if crate_name == "my_crate" && path.display().to_string() == "test-path"
+ }
+
+ create_test! {
+ crate_not_found,
+ r#"
+ [dependencies]
+ serde = "1.0"
+ "#,
+ Err(Error::CrateNotFound {
+ crate_name,
+ path,
+ }) if crate_name == "my_crate" && path.display().to_string() == "test-path"
+ }
+
+ create_test! {
+ target_dependency,
+ r#"
+ [target.'cfg(target_os="android")'.dependencies]
+ my_crate = "0.1"
+ "#,
+ Ok(FoundCrate::Name(name)) if name == "my_crate"
+ }
+
+ create_test! {
+ target_dependency2,
+ r#"
+ [target.x86_64-pc-windows-gnu.dependencies]
+ my_crate = "0.1"
+ "#,
+ Ok(FoundCrate::Name(name)) if name == "my_crate"
+ }
+
+ create_test! {
+ own_crate,
+ r#"
+ [package]
+ name = "my_crate"
+ "#,
+ Ok(FoundCrate::Itself)
+ }
+}