include_preprocessor_macro/
lib.rs

1#![feature(proc_macro_span, track_path)]
2
3//! A companion crate to the [include-preprocessor][1] crate that provides an [include_str_ipp]
4//! macro to conveniently invoke the preprocessor for basic use-cases.
5//!
6//! [1]: https://docs.rs/include-preprocessor/latest/include_preprocessor/
7
8use std::env;
9use std::path::Path;
10
11use include_preprocessor::{SearchPaths, SourceTracker, preprocess_with_source_tracker};
12use proc_macro::tracked_path;
13use proc_macro::{Literal, Span, TokenStream, TokenTree};
14use syn::{LitStr, parse_macro_input};
15
16/// Includes a UTF-8 encoded file as a string after processing C-preprocessor style `#include`
17/// directives to inline other UTF-8 encoded files into the string.
18///
19/// The macro takes a single string literal argument that represents the path of the file to be
20/// included:
21///
22/// ```ignore
23/// use include_preprocessor_macro::include_str_ipp;
24///
25/// let s = include_str_ipp!("my_file.txt")
26/// ```
27///
28/// The path is resolved relative to the parent directory of the Rust file that invokes the macro.
29///
30/// The macro uses the [include-preprocessor][1] crate to parse and resolve C-preprocessor style
31/// `#include` directives. For details on how `#include` directives are parsed and resolved, refer
32/// to the [The `#include` Directive][2] and [File Resolution][3] sections of the
33/// [include-preprocessor][1] documentation. For file resolution, this macro uses a [SearchPaths]
34/// object to which the value of the `CARGO_MANIFEST_DIR` environment variable (set by Cargo when
35/// running Cargo command like `cargo build`; the directory containing the manifest of your package)
36/// is added as a "base path".
37///
38/// Also supports the `#pragma once` directive, which prevents files from being included multiple
39/// times. Refer to the [The `#pragma once` Directive][4] section of the [include-preprocessor][1]
40/// documentation for details.
41///
42/// This macro will yield an expression of type `&'static str` which is the result of running the
43/// include-preprocessor using the macro's argument as the entry-point.
44///
45/// [1]: https://docs.rs/include-preprocessor/latest/include_preprocessor/
46/// [2]: https://docs.rs/include-preprocessor/latest/include_preprocessor/#the-include-directive
47/// [3]: https://docs.rs/include-preprocessor/latest/include_preprocessor/#file-resolution
48/// [4]: https://docs.rs/include-preprocessor/latest/include_preprocessor/#the-pragma-once-directive
49#[proc_macro]
50pub fn include_str_ipp(input: TokenStream) -> TokenStream {
51    let path = parse_macro_input!(input as LitStr);
52
53    let span = Span::call_site();
54    let source_path = span.local_file().unwrap();
55    let source_dir = source_path.parent().unwrap();
56
57    let mut search_paths = SearchPaths::new();
58    let cargo_manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
59
60    search_paths.push_base_path(cargo_manifest_dir);
61
62    let source_join = source_dir.join(path.value());
63
64    let output = if source_join.is_file() {
65        let buffer = String::new();
66
67        preprocess_with_source_tracker(source_join, search_paths, buffer, &mut ProcMacroPathTracker)
68            .unwrap()
69    } else {
70        panic!("Entry (`{:?}`) point is not a file!", source_join);
71    };
72
73    let token = Literal::string(&output);
74
75    let tree: TokenTree = token.into();
76
77    tree.into()
78}
79
80struct ProcMacroPathTracker;
81
82impl SourceTracker for ProcMacroPathTracker {
83    fn track(&mut self, path: &Path, _source: &str) {
84        tracked_path::path(path.to_str().expect("cannot track non-unicode path"));
85    }
86}