Import include_dir_macros 0.7.3 upstream upstream/0.7.3
authorRoy7Kim <myoungwoon.kim@samsung.com>
Mon, 15 May 2023 07:57:08 +0000 (16:57 +0900)
committerRoy7Kim <myoungwoon.kim@samsung.com>
Mon, 15 May 2023 07:57:08 +0000 (16:57 +0900)
.cargo_vcs_info.json [new file with mode: 0644]
Cargo.toml [new file with mode: 0644]
Cargo.toml.orig [new file with mode: 0644]
src/lib.rs [new file with mode: 0644]

diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644 (file)
index 0000000..d726270
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "git": {
+    "sha1": "1e76b91179babd98ad9d709ee7af1e39631d7d8d"
+  },
+  "path_in_vcs": "macros"
+}
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644 (file)
index 0000000..249fa5e
--- /dev/null
@@ -0,0 +1,34 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+rust-version = "1.56"
+name = "include_dir_macros"
+version = "0.7.3"
+authors = ["Michael Bryan <michaelfbryan@gmail.com>"]
+description = "The procedural macro used by include_dir"
+license = "MIT"
+repository = "https://github.com/Michael-F-Bryan/include_dir"
+resolver = "1"
+
+[lib]
+proc-macro = true
+
+[dependencies.proc-macro2]
+version = "1"
+
+[dependencies.quote]
+version = "1"
+
+[features]
+metadata = []
+nightly = []
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644 (file)
index 0000000..3ce97c9
--- /dev/null
@@ -0,0 +1,22 @@
+[package]
+name = "include_dir_macros"
+version = "0.7.3"
+description = "The procedural macro used by include_dir"
+authors = ["Michael Bryan <michaelfbryan@gmail.com>"]
+repository = "https://github.com/Michael-F-Bryan/include_dir"
+license = "MIT"
+edition = "2021"
+rust-version = "1.56"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[lib]
+proc-macro = true
+
+[dependencies]
+proc-macro2 = "1"
+quote = "1"
+
+[features]
+nightly = []
+metadata = []
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644 (file)
index 0000000..51e5b4c
--- /dev/null
@@ -0,0 +1,353 @@
+//! Implementation details of the `include_dir`.
+//!
+//! You probably don't want to use this crate directly.
+#![cfg_attr(feature = "nightly", feature(track_path, proc_macro_tracked_env))]
+
+use proc_macro::{TokenStream, TokenTree};
+use proc_macro2::Literal;
+use quote::quote;
+use std::{
+    error::Error,
+    fmt::{self, Display, Formatter},
+    path::{Path, PathBuf},
+    time::SystemTime,
+};
+
+/// Embed the contents of a directory in your crate.
+#[proc_macro]
+pub fn include_dir(input: TokenStream) -> TokenStream {
+    let tokens: Vec<_> = input.into_iter().collect();
+
+    let path = match tokens.as_slice() {
+        [TokenTree::Literal(lit)] => unwrap_string_literal(lit),
+        _ => panic!("This macro only accepts a single, non-empty string argument"),
+    };
+
+    let path = resolve_path(&path, get_env).unwrap();
+
+    expand_dir(&path, &path).into()
+}
+
+fn unwrap_string_literal(lit: &proc_macro::Literal) -> String {
+    let mut repr = lit.to_string();
+    if !repr.starts_with('"') || !repr.ends_with('"') {
+        panic!("This macro only accepts a single, non-empty string argument")
+    }
+
+    repr.remove(0);
+    repr.pop();
+
+    repr
+}
+
+fn expand_dir(root: &Path, path: &Path) -> proc_macro2::TokenStream {
+    let children = read_dir(path).unwrap_or_else(|e| {
+        panic!(
+            "Unable to read the entries in \"{}\": {}",
+            path.display(),
+            e
+        )
+    });
+
+    let mut child_tokens = Vec::new();
+
+    for child in children {
+        if child.is_dir() {
+            let tokens = expand_dir(root, &child);
+            child_tokens.push(quote! {
+                include_dir::DirEntry::Dir(#tokens)
+            });
+        } else if child.is_file() {
+            let tokens = expand_file(root, &child);
+            child_tokens.push(quote! {
+                include_dir::DirEntry::File(#tokens)
+            });
+        } else {
+            panic!("\"{}\" is neither a file nor a directory", child.display());
+        }
+    }
+
+    let path = normalize_path(root, path);
+
+    quote! {
+        include_dir::Dir::new(#path, &[ #(#child_tokens),* ])
+    }
+}
+
+fn expand_file(root: &Path, path: &Path) -> proc_macro2::TokenStream {
+    let abs = path
+        .canonicalize()
+        .unwrap_or_else(|e| panic!("failed to resolve \"{}\": {}", path.display(), e));
+    let literal = match abs.to_str() {
+        Some(abs) => quote!(include_bytes!(#abs)),
+        None => {
+            let contents = read_file(path);
+            let literal = Literal::byte_string(&contents);
+            quote!(#literal)
+        }
+    };
+
+    let normalized_path = normalize_path(root, path);
+
+    let tokens = quote! {
+        include_dir::File::new(#normalized_path, #literal)
+    };
+
+    match metadata(path) {
+        Some(metadata) => quote!(#tokens.with_metadata(#metadata)),
+        None => tokens,
+    }
+}
+
+fn metadata(path: &Path) -> Option<proc_macro2::TokenStream> {
+    fn to_unix(t: SystemTime) -> u64 {
+        t.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs()
+    }
+
+    if !cfg!(feature = "metadata") {
+        return None;
+    }
+
+    let meta = path.metadata().ok()?;
+    let accessed = meta.accessed().map(to_unix).ok()?;
+    let created = meta.created().map(to_unix).ok()?;
+    let modified = meta.modified().map(to_unix).ok()?;
+
+    Some(quote! {
+        include_dir::Metadata::new(
+            std::time::Duration::from_secs(#accessed),
+            std::time::Duration::from_secs(#created),
+            std::time::Duration::from_secs(#modified),
+        )
+    })
+}
+
+/// Make sure that paths use the same separator regardless of whether the host
+/// machine is Windows or Linux.
+fn normalize_path(root: &Path, path: &Path) -> String {
+    let stripped = path
+        .strip_prefix(root)
+        .expect("Should only ever be called using paths inside the root path");
+    let as_string = stripped.to_string_lossy();
+
+    as_string.replace('\\', "/")
+}
+
+fn read_dir(dir: &Path) -> Result<Vec<PathBuf>, Box<dyn Error>> {
+    if !dir.is_dir() {
+        panic!("\"{}\" is not a directory", dir.display());
+    }
+
+    track_path(dir);
+
+    let mut paths = Vec::new();
+
+    for entry in dir.read_dir()? {
+        let entry = entry?;
+        paths.push(entry.path());
+    }
+
+    paths.sort();
+
+    Ok(paths)
+}
+
+fn read_file(path: &Path) -> Vec<u8> {
+    track_path(path);
+    std::fs::read(path).unwrap_or_else(|e| panic!("Unable to read \"{}\": {}", path.display(), e))
+}
+
+fn resolve_path(
+    raw: &str,
+    get_env: impl Fn(&str) -> Option<String>,
+) -> Result<PathBuf, Box<dyn Error>> {
+    let mut unprocessed = raw;
+    let mut resolved = String::new();
+
+    while let Some(dollar_sign) = unprocessed.find('$') {
+        let (head, tail) = unprocessed.split_at(dollar_sign);
+        resolved.push_str(head);
+
+        match parse_identifier(&tail[1..]) {
+            Some((variable, rest)) => {
+                let value = get_env(variable).ok_or_else(|| MissingVariable {
+                    variable: variable.to_string(),
+                })?;
+                resolved.push_str(&value);
+                unprocessed = rest;
+            }
+            None => {
+                return Err(UnableToParseVariable { rest: tail.into() }.into());
+            }
+        }
+    }
+    resolved.push_str(unprocessed);
+
+    Ok(PathBuf::from(resolved))
+}
+
+#[derive(Debug, PartialEq)]
+struct MissingVariable {
+    variable: String,
+}
+
+impl Error for MissingVariable {}
+
+impl Display for MissingVariable {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        write!(f, "Unable to resolve ${}", self.variable)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+struct UnableToParseVariable {
+    rest: String,
+}
+
+impl Error for UnableToParseVariable {}
+
+impl Display for UnableToParseVariable {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        write!(f, "Unable to parse a variable from \"{}\"", self.rest)
+    }
+}
+
+fn parse_identifier(text: &str) -> Option<(&str, &str)> {
+    let mut calls = 0;
+
+    let (head, tail) = take_while(text, |c| {
+        calls += 1;
+
+        match c {
+            '_' => true,
+            letter if letter.is_ascii_alphabetic() => true,
+            digit if digit.is_ascii_digit() && calls > 1 => true,
+            _ => false,
+        }
+    });
+
+    if head.is_empty() {
+        None
+    } else {
+        Some((head, tail))
+    }
+}
+
+fn take_while(s: &str, mut predicate: impl FnMut(char) -> bool) -> (&str, &str) {
+    let mut index = 0;
+
+    for c in s.chars() {
+        if predicate(c) {
+            index += c.len_utf8();
+        } else {
+            break;
+        }
+    }
+
+    s.split_at(index)
+}
+
+#[cfg(feature = "nightly")]
+fn get_env(variable: &str) -> Option<String> {
+    proc_macro::tracked_env::var(variable).ok()
+}
+
+#[cfg(not(feature = "nightly"))]
+fn get_env(variable: &str) -> Option<String> {
+    std::env::var(variable).ok()
+}
+
+fn track_path(_path: &Path) {
+    #[cfg(feature = "nightly")]
+    proc_macro::tracked_path::path(_path.to_string_lossy());
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn resolve_path_with_no_environment_variables() {
+        let path = "./file.txt";
+
+        let resolved = resolve_path(path, |_| unreachable!()).unwrap();
+
+        assert_eq!(resolved.to_str().unwrap(), path);
+    }
+
+    #[test]
+    fn simple_environment_variable() {
+        let path = "./$VAR";
+
+        let resolved = resolve_path(path, |name| {
+            assert_eq!(name, "VAR");
+            Some("file.txt".to_string())
+        })
+        .unwrap();
+
+        assert_eq!(resolved.to_str().unwrap(), "./file.txt");
+    }
+
+    #[test]
+    fn dont_resolve_recursively() {
+        let path = "./$TOP_LEVEL.txt";
+
+        let resolved = resolve_path(path, |name| match name {
+            "TOP_LEVEL" => Some("$NESTED".to_string()),
+            "$NESTED" => unreachable!("Shouldn't resolve recursively"),
+            _ => unreachable!(),
+        })
+        .unwrap();
+
+        assert_eq!(resolved.to_str().unwrap(), "./$NESTED.txt");
+    }
+
+    #[test]
+    fn parse_valid_identifiers() {
+        let inputs = vec![
+            ("a", "a"),
+            ("a_", "a_"),
+            ("_asf", "_asf"),
+            ("a1", "a1"),
+            ("a1_#sd", "a1_"),
+        ];
+
+        for (src, expected) in inputs {
+            let (got, rest) = parse_identifier(src).unwrap();
+            assert_eq!(got.len() + rest.len(), src.len());
+            assert_eq!(got, expected);
+        }
+    }
+
+    #[test]
+    fn unknown_environment_variable() {
+        let path = "$UNKNOWN";
+
+        let err = resolve_path(path, |_| None).unwrap_err();
+
+        let missing_variable = err.downcast::<MissingVariable>().unwrap();
+        assert_eq!(
+            *missing_variable,
+            MissingVariable {
+                variable: String::from("UNKNOWN"),
+            }
+        );
+    }
+
+    #[test]
+    fn invalid_variables() {
+        let inputs = &["$1", "$"];
+
+        for input in inputs {
+            let err = resolve_path(input, |_| unreachable!()).unwrap_err();
+
+            let err = err.downcast::<UnableToParseVariable>().unwrap();
+            assert_eq!(
+                *err,
+                UnableToParseVariable {
+                    rest: input.to_string(),
+                }
+            );
+        }
+    }
+}