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#[proc_macro]
9pub fn path(input: TokenStream) -> TokenStream {
10 if input.is_empty() {
12 return quote! { ::std::path::PathBuf::new() }.into();
13 }
14
15 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 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 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 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
87struct 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 let expr = input.parse()?;
97
98 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}