macron_path/
lib.rs

1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use proc_macro::TokenStream;
4use proc_macro2::TokenStream as TokenStream2;
5use quote::quote;
6
7/// Creates a new instance of PathBuf
8#[proc_macro]
9pub fn path(input: TokenStream) -> TokenStream {
10    // empty string:
11    if input.is_empty() {
12        return quote! { ::std::path::PathBuf::new() }.into();
13    }
14    
15    // path formatting:
16    let Format { expr, args } = syn::parse_macro_input!(input as Format);
17    
18    if let syn::Expr::Lit( syn::ExprLit { lit: syn::Lit::Str(lit_str), .. } ) = expr {
19        let path_str = lit_str.value().replace("\\", "/");
20        let args = if let Some(args) = args { quote!{#args} }else{ quote!{} };
21        
22        // executable file path:
23        if path_str == "$" {
24            quote! {{
25                ::std::env::current_exe()
26                    .expect("Failed to get executable file path")
27            }}
28        }
29        
30        // executable file dir path:
31        else if path_str.starts_with("$/") {
32            let path_str = &path_str[2..];
33            let lit_str = syn::LitStr::new(path_str, proc_macro2::Span::call_site());
34            
35            quote! {{
36                let exe_dir = ::std::env::current_exe().expect("Failed to get executable file path")
37                    .parent()
38                    .map(::std::path::PathBuf::from)
39                    .expect("Failed to get binary file dir");
40                exe_dir.join(&::std::format!(#lit_str #args))
41            }}
42        }
43
44        // home dir path:
45        else if path_str.starts_with("~/") {
46            let path_str = &path_str[2..];
47            let lit_str = syn::LitStr::new(path_str, proc_macro2::Span::call_site());
48        
49            quote! {{
50                #[cfg(target_os = "windows")]
51                {
52                    let home_dir = ::std::env::var("USERPROFILE")
53                        .map(::std::path::PathBuf::from)
54                        .expect("USERPROFILE not found");
55                    home_dir.join(&::std::format!(#lit_str #args))
56                }
57                
58                #[cfg(not(target_os = "windows"))]
59                {
60                    let home_dir = ::std::env::var("HOME")
61                        .map(::std::path::PathBuf::from)
62                        .expect("HOME not found");
63                    home_dir.join(&::std::format!(#lit_str #args))
64                }
65            }}
66        }
67
68        // user data dir path:
69        else if path_str.starts_with("%/") {
70            let path_str = &path_str[2..];
71            let lit_str = syn::LitStr::new(path_str, proc_macro2::Span::call_site());
72        
73            quote! {{
74                #[cfg(target_os = "windows")]
75                {
76                    let home_dir = ::std::env::var("APPDATA")
77                        .map(::std::path::PathBuf::from)
78                        .expect("Failed to get user data dir");
79                    home_dir.join(&::std::format!(#lit_str #args))
80                }
81
82                #[cfg(target_os = "macos")]
83                {
84                    let home_dir = ::std::env::var("HOME")
85                        .map(::std::path::PathBuf::from)
86                        .expect("Failed to get user data dir");
87                    let lib_dir = home_dir.join("Library/Application Support");
88                    lib_dir.join(&::std::format!(#lit_str #args))
89                }
90
91                #[cfg(all(unix, not(target_os = "macos")))]
92                {
93                    let home_dir = ::std::env::var("XDG_DATA_HOME")
94                        .map(::std::path::PathBuf::from)
95                        .or_else(|_| 
96                            ::std::env::var("HOME")
97                                .map(|p| ::std::path::PathBuf::from(p).join(".local/share"))
98                        )
99                        .expect("Failed to get user data dir");
100                    home_dir.join(&::std::format!(#lit_str #args))
101                }
102            }}
103        }
104        
105        // other:
106        else {
107            quote! {
108                ::std::path::PathBuf::from(&::std::format!(#lit_str #args))
109            }
110        }
111    } else {
112        quote! {
113            ::std::path::PathBuf::from(#expr)
114        }
115    }
116    .into()
117}
118
119/// The string formatter
120struct Format {
121    pub expr: syn::Expr,
122    pub args: Option<TokenStream2>,
123}
124
125impl syn::parse::Parse for Format {
126    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
127        // parse expression:
128        let expr = input.parse()?;
129
130        // parse arguments:
131        let args = if input.peek(syn::token::Comma) {
132            Some(input.parse()?)
133        } else {
134            None
135        };
136        
137        Ok(Self { expr, args })
138    }
139}