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();
20        let args = if let Some(args) = args { quote!{#args} }else{ quote!{} };
21        
22        // .exe dir path:
23        if path_str.starts_with("/") || path_str.starts_with("\\") {
24            let path_str = &path_str[1..];
25            let lit_str = syn::LitStr::new(path_str, proc_macro2::Span::call_site());
26            
27            quote! {{
28                let exe_dir = ::std::env::current_exe().expect("Failed to get exe path")
29                    .parent()
30                    .map(::std::path::PathBuf::from)
31                    .expect("Failed to get exe dir");
32                exe_dir.join(&::std::format!(#lit_str #args))
33            }}
34        }
35
36        // user data dir path:
37        else if path_str.starts_with("$/") {
38            let path_str = &path_str["$/".len()..];
39            let lit_str = syn::LitStr::new(path_str, proc_macro2::Span::call_site());
40        
41            quote! {{
42                #[cfg(target_os = "windows")]
43                {
44                    let home_dir = ::std::env::var("APPDATA")
45                        .map(::std::path::PathBuf::from)
46                        .expect("Failed to get user data dir");
47                    home_dir.join(&::std::format!(#lit_str #args))
48                }
49
50                #[cfg(target_os = "macos")]
51                {
52                    let home_dir = ::std::env::var("HOME")
53                        .map(::std::path::PathBuf::from)
54                        .expect("Failed to get user data dir");
55                    let lib_dir = home_dir.join("Library").join("Application Support");
56                    lib_dir.join(&::std::format!(#lit_str #args))
57                }
58
59                #[cfg(all(unix, not(target_os = "macos")))]
60                {
61                    let home_dir = ::std::env::var("XDG_DATA_HOME")
62                        .map(::std::path::PathBuf::from)
63                        .or_else(|_| 
64                            ::std::env::var("HOME")
65                                .map(|p| ::std::path::PathBuf::from(p).join(".local/share"))
66                        )
67                        .expect("Failed to get user data dir");
68                    home_dir.join(&::std::format!(#lit_str #args))
69                }
70            }}
71        }
72        
73        // other:
74        else {
75            quote! {
76                ::std::path::PathBuf::from(&::std::format!(#lit_str #args))
77            }
78        }
79    } else {
80        quote! {
81            ::std::path::PathBuf::from(#expr)
82        }
83    }
84    .into()
85}
86
87/// The string formatter
88struct Format {
89    pub expr: syn::Expr,
90    pub args: Option<TokenStream2>,
91}
92
93impl syn::parse::Parse for Format {
94    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
95        // parse expression:
96        let expr = input.parse()?;
97
98        // parse arguments:
99        let args = if input.peek(syn::token::Comma) {
100            Some(input.parse()?)
101        } else {
102            None
103        };
104        
105        Ok(Self { expr, args })
106    }
107}