manifest_dir_macros/
lib.rs

1/*!
2# Manifest Dir Macros
3
4This crate provides function-like macros to check or operate paths relative to **CARGO_MANIFEST_DIR** at compile time.
5
6## Examples
7
8```rust
9#[macro_use] extern crate manifest_dir_macros;
10
11println!(path!("Cargo.toml"));
12println!(path!("src/lib.rs"));
13println!(path!("src", "lib.rs"));
14println!(path!("src", "lib.rs", "/bin"));
15println!(path!("/usr"));
16
17println!(exist_path!("Cargo.toml"));
18println!(directory_path!("src"));
19println!(not_directory_path!("Cargo.toml"));
20println!(file_path!("Cargo.toml"));
21
22println!(relative_path!("Cargo.toml"));
23println!(directory_relative_path!("src"));
24println!(not_directory_relative_path!("Cargo.toml"));
25println!(file_relative_path!("Cargo.toml"));
26
27println!(get_file_name!("src/lib.rs"));
28println!(get_file_name!(default = "main.rs", "/"));
29println!(get_file_stem!("src/lib.rs"));
30println!(get_file_stem!(default = "lib", "/"));
31println!(get_extension!("src/lib.rs"));
32println!(get_extension!(default = "rs", "src/lib"));
33println!(get_parent!("src/lib.rs"));
34println!(get_parent!(default = "/home", "/"));
35
36#[cfg(feature = "mime_guess")]
37{
38    println!(mime_guess!("src/lib.rs"));
39    println!(mime_guess!(default = "application/octet-stream", "Cargo.lock"));
40}
41
42// The `tuple` feature lets these macros above support to input nested literal string tuples, which is useful when you want to use these macros inside a `macro_rule!` macro and concatenate with other literal strings.
43// `$x:expr` matchers can be used in these macros thus.
44#[cfg(feature = "tuple")]
45{
46    println!(path!(("foo",)));
47    println!(path!(("foo", "bar")));
48    println!(path!("a", ("foo", "bar")));
49    println!(path!(("foo", "bar"), "a"));
50    println!(path!(("foo", "bar"), ("a", "b")));
51    println!(path!(("foo", "bar", ("a", "b")), ("c", "d")));
52}
53```
54*/
55
56mod functions;
57mod join_builder;
58
59use std::{env, path::PathBuf};
60
61use functions::*;
62use join_builder::*;
63use once_cell::sync::Lazy;
64use proc_macro::TokenStream;
65#[cfg(feature = "mime_guess")]
66use quote::quote;
67use syn::parse_macro_input;
68
69static MANIFEST_DIR: Lazy<PathBuf> = Lazy::new(|| {
70    let s = env::var_os("CARGO_MANIFEST_DIR").expect("we need CARGO_MANIFEST_DIR");
71
72    #[cfg(all(windows, feature = "replace-separator"))]
73    let s = beautify_windows_path_os(s).expect("a UTF8-encodable CARGO_MANIFEST_DIR");
74
75    PathBuf::from(s)
76});
77
78/// Allows input an absolute path, or a relative path. If a relative path is input, it will be relative to the CARGO_MANIFEST_DIR (a directory where your `Cargo.toml` located). Returns an absolute path.
79///
80/// Multiple components can be input by using commas to separate them.
81#[proc_macro]
82pub fn path(input: TokenStream) -> TokenStream {
83    let original_path: PathBuf = syn::parse_macro_input!(input as JoinBuilder).into();
84
85    let p =
86        if original_path.is_absolute() { original_path } else { MANIFEST_DIR.join(original_path) };
87
88    output_path(p)
89}
90
91/// Allows input an absolute path, or a relative path. (multiple components are supported) If a relative path is input, it will be relative to the CARGO_MANIFEST_DIR (a directory where your `Cargo.toml` located). Returns an absolute path, and it must exist.
92///
93/// Multiple components can be input by using commas to separate them.
94#[proc_macro]
95pub fn exist_path(input: TokenStream) -> TokenStream {
96    let original_path: PathBuf = parse_macro_input!(input as JoinBuilder).into();
97
98    let p =
99        if original_path.is_absolute() { original_path } else { MANIFEST_DIR.join(original_path) };
100
101    if p.exists() {
102        output_path(p)
103    } else {
104        compile_error_not_exist(p)
105    }
106}
107
108/// Allows input an absolute path, or a relative path. If a relative path is input, it will be relative to the CARGO_MANIFEST_DIR (a directory where your `Cargo.toml` located). Returns an absolute path, and it must be an existing directory.
109///
110/// Multiple components can be input by using commas to separate them.
111#[proc_macro]
112pub fn directory_path(input: TokenStream) -> TokenStream {
113    let original_path: PathBuf = parse_macro_input!(input as JoinBuilder).into();
114
115    let p =
116        if original_path.is_absolute() { original_path } else { MANIFEST_DIR.join(original_path) };
117
118    if p.is_dir() {
119        output_path(p)
120    } else {
121        compile_error_not_directory(p)
122    }
123}
124
125/// Allows input an absolute path, or a relative path. If a relative path is input, it will be relative to the CARGO_MANIFEST_DIR (a directory where your `Cargo.toml` located). Returns an absolute path, and it must not be an existing directory.
126///
127/// Multiple components can be input by using commas to separate them.
128#[proc_macro]
129pub fn not_directory_path(input: TokenStream) -> TokenStream {
130    let original_path: PathBuf = parse_macro_input!(input as JoinBuilder).into();
131
132    let p =
133        if original_path.is_absolute() { original_path } else { MANIFEST_DIR.join(original_path) };
134
135    if p.metadata().map(|m| !m.is_dir()).unwrap_or(false) {
136        output_path(p)
137    } else {
138        compile_error_directory(p)
139    }
140}
141
142/// Allows input an absolute path, or a relative path. If a relative path is input, it will be relative to the CARGO_MANIFEST_DIR (a directory where your `Cargo.toml` located). Returns an absolute path, and it must be an existing file.
143///
144/// Multiple components can be input by using commas to separate them.
145#[proc_macro]
146pub fn file_path(input: TokenStream) -> TokenStream {
147    let original_path: PathBuf = parse_macro_input!(input as JoinBuilder).into();
148
149    let p =
150        if original_path.is_absolute() { original_path } else { MANIFEST_DIR.join(original_path) };
151
152    if p.is_file() {
153        output_path(p)
154    } else {
155        compile_error_not_file(p)
156    }
157}
158
159/// Allows input a relative path. It will be relative to the CARGO_MANIFEST_DIR (a directory where your `Cargo.toml` located). Returns an absolute path.
160///
161/// Multiple components can be input by using commas to separate them.
162#[proc_macro]
163pub fn relative_path(input: TokenStream) -> TokenStream {
164    let original_path: PathBuf = parse_macro_input!(input as JoinBuilder).into();
165
166    if original_path.is_relative() {
167        output_path(MANIFEST_DIR.join(original_path))
168    } else {
169        compile_error_not_relative(original_path)
170    }
171}
172
173/// Allows input a relative path. It will be relative to the CARGO_MANIFEST_DIR (a directory where your `Cargo.toml` located). Returns an absolute path, and it must exist.
174///
175/// Multiple components can be input by using commas to separate them.
176#[proc_macro]
177pub fn exist_relative_path(input: TokenStream) -> TokenStream {
178    let original_path: PathBuf = parse_macro_input!(input as JoinBuilder).into();
179
180    if original_path.is_relative() {
181        let p = MANIFEST_DIR.join(original_path);
182
183        if p.exists() {
184            output_path(p)
185        } else {
186            compile_error_not_exist(p)
187        }
188    } else {
189        compile_error_not_relative(original_path)
190    }
191}
192
193/// Allows input a relative path. It will be relative to the CARGO_MANIFEST_DIR (a directory where your `Cargo.toml` located). Returns an absolute path, and it must be a directory.
194///
195/// Multiple components can be input by using commas to separate them.
196#[proc_macro]
197pub fn directory_relative_path(input: TokenStream) -> TokenStream {
198    let original_path: PathBuf = parse_macro_input!(input as JoinBuilder).into();
199
200    if original_path.is_relative() {
201        let p = MANIFEST_DIR.join(original_path);
202
203        if p.is_dir() {
204            output_path(p)
205        } else {
206            compile_error_not_directory(p)
207        }
208    } else {
209        compile_error_not_relative(original_path)
210    }
211}
212
213/// Allows input a relative path. It will be relative to the CARGO_MANIFEST_DIR (a directory where your `Cargo.toml` located). Returns an absolute path, and it must not be a directory.
214///
215/// Multiple components can be input by using commas to separate them.
216#[proc_macro]
217pub fn not_directory_relative_path(input: TokenStream) -> TokenStream {
218    let original_path: PathBuf = parse_macro_input!(input as JoinBuilder).into();
219
220    if original_path.is_relative() {
221        let p = MANIFEST_DIR.join(original_path);
222
223        if p.metadata().map(|m| !m.is_dir()).unwrap_or(false) {
224            output_path(p)
225        } else {
226            compile_error_directory(p)
227        }
228    } else {
229        compile_error_not_relative(original_path)
230    }
231}
232
233/// Allows input a relative path. It will be relative to the CARGO_MANIFEST_DIR (a directory where your `Cargo.toml` located). Returns an absolute path, and it must be a file.
234///
235/// Multiple components can be input by using commas to separate them.
236#[proc_macro]
237pub fn file_relative_path(input: TokenStream) -> TokenStream {
238    let original_path: PathBuf = parse_macro_input!(input as JoinBuilder).into();
239
240    if original_path.is_relative() {
241        let p = MANIFEST_DIR.join(original_path);
242
243        if p.is_file() {
244            output_path(p)
245        } else {
246            compile_error_not_file(p)
247        }
248    } else {
249        compile_error_not_relative(original_path)
250    }
251}
252
253/// Allows input a absolute path. Checks and returns the absolute path.
254///
255/// Multiple components can be input by using commas to separate them.
256#[proc_macro]
257pub fn absolute_path(input: TokenStream) -> TokenStream {
258    let original_path: PathBuf = parse_macro_input!(input as JoinBuilder).into();
259
260    if original_path.is_absolute() {
261        output_path(original_path)
262    } else {
263        compile_error_not_absolute(original_path)
264    }
265}
266
267/// Allows input a absolute path. Checks whether it exists and returns the absolute path.
268///
269/// Multiple components can be input by using commas to separate them.
270#[proc_macro]
271pub fn exist_absolute_path(input: TokenStream) -> TokenStream {
272    let original_path: PathBuf = parse_macro_input!(input as JoinBuilder).into();
273
274    if original_path.is_absolute() {
275        if original_path.exists() {
276            output_path(original_path)
277        } else {
278            compile_error_not_exist(original_path)
279        }
280    } else {
281        compile_error_not_absolute(original_path)
282    }
283}
284
285/// Allows input a absolute path. Checks whether it is a directory and returns the absolute path.
286///
287/// Multiple components can be input by using commas to separate them.
288#[proc_macro]
289pub fn directory_absolute_path(input: TokenStream) -> TokenStream {
290    let original_path: PathBuf = parse_macro_input!(input as JoinBuilder).into();
291
292    if original_path.is_absolute() {
293        if original_path.is_dir() {
294            output_path(original_path)
295        } else {
296            compile_error_not_directory(original_path)
297        }
298    } else {
299        compile_error_not_absolute(original_path)
300    }
301}
302
303/// Allows input a absolute path. Checks whether it is not a directory and returns the absolute path.
304///
305/// Multiple components can be input by using commas to separate them.
306#[proc_macro]
307pub fn not_directory_absolute_path(input: TokenStream) -> TokenStream {
308    let original_path: PathBuf = parse_macro_input!(input as JoinBuilder).into();
309
310    if original_path.is_absolute() {
311        if original_path.metadata().map(|m| !m.is_dir()).unwrap_or(false) {
312            output_path(original_path)
313        } else {
314            compile_error_directory(original_path)
315        }
316    } else {
317        compile_error_not_absolute(original_path)
318    }
319}
320
321/// Allows input a absolute path. Checks whether it is a file and returns the absolute path.
322///
323/// Multiple components can be input by using commas to separate them.
324#[proc_macro]
325pub fn file_absolute_path(input: TokenStream) -> TokenStream {
326    let original_path: PathBuf = parse_macro_input!(input as JoinBuilder).into();
327
328    if original_path.is_absolute() {
329        if original_path.is_file() {
330            output_path(original_path)
331        } else {
332            compile_error_not_file(original_path)
333        }
334    } else {
335        compile_error_not_absolute(original_path)
336    }
337}
338
339/// Gets the file name for other purposes. If there is no file name, the default value will be used, or a compile error will be shown.
340///
341/// Multiple components can be input by using commas to separate them.
342#[proc_macro]
343pub fn get_file_name(input: TokenStream) -> TokenStream {
344    let jb = parse_macro_input!(input as JoinBuilderNoBeautifyWithDefaultValue);
345
346    match jb.0.file_name() {
347        Some(file_name) => output_os_str(file_name),
348        None => match jb.1 {
349            Some(expr) => output_expr(&expr),
350            None => compile_error(format!("The path {:?} has no file name", jb.0)),
351        },
352    }
353}
354
355/// Gets the file stem for other purposes. If there is no file stem, the default value will be used, or a compile error will be shown.
356///
357/// Multiple components can be input by using commas to separate them.
358#[proc_macro]
359pub fn get_file_stem(input: TokenStream) -> TokenStream {
360    let jb = parse_macro_input!(input as JoinBuilderNoBeautifyWithDefaultValue);
361
362    match jb.0.file_stem() {
363        Some(file_stem) => output_os_str(file_stem),
364        None => match jb.1 {
365            Some(expr) => output_expr(&expr),
366            None => compile_error(format!("The path {:?} has no file stem", jb.0)),
367        },
368    }
369}
370
371/// Gets the file extension for other purposes. If there is no file extension, the default value will be used, or a compile error will be shown.
372///
373/// Multiple components can be input by using commas to separate them.
374#[proc_macro]
375pub fn get_extension(input: TokenStream) -> TokenStream {
376    let jb = parse_macro_input!(input as JoinBuilderNoBeautifyWithDefaultValue);
377
378    match jb.0.extension() {
379        Some(extension) => output_os_str(extension),
380        None => match jb.1 {
381            Some(expr) => output_expr(&expr),
382            None => compile_error(format!("The path {:?} has no file extension", jb.0)),
383        },
384    }
385}
386
387/// Gets the parent for other purposes. If there is no parent, the default value will be used, or a compile error will be shown.
388///
389/// Multiple components can be input by using commas to separate them.
390#[proc_macro]
391pub fn get_parent(input: TokenStream) -> TokenStream {
392    let jb = parse_macro_input!(input as JoinBuilderWithDefaultValue);
393
394    match jb.0.parent() {
395        Some(parent) => output_path(parent),
396        None => match jb.1 {
397            Some(expr) => output_expr(&expr),
398            None => compile_error(format!("The path {:?} has no parent", jb.0)),
399        },
400    }
401}
402
403#[cfg(feature = "mime_guess")]
404/// Guesses the mime type by the path. If the guess fails, the default value will be used, or a compile error will be shown.
405///
406/// Multiple components can be input by using commas to separate them.
407#[proc_macro]
408pub fn mime_guess(input: TokenStream) -> TokenStream {
409    let jb = parse_macro_input!(input as JoinBuilderNoBeautifyWithDefaultValue);
410
411    match jb
412        .0
413        .extension()
414        .and_then(|ext| ext.to_str())
415        .and_then(|ext| mime_guess::from_ext(ext).first())
416        .map(|mime| mime.to_string())
417    {
418        Some(mime) => {
419            let code = quote! {
420                #mime
421            };
422
423            code.into()
424        },
425        None => match jb.1 {
426            Some(expr) => output_expr(&expr),
427            None => {
428                compile_error(format!("The path {:?} can not be guessed for its mime type", jb.0))
429            },
430        },
431    }
432}