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().replace("\\", "/");
20 let args = if let Some(args) = args { quote!{#args} }else{ quote!{} };
21
22 if path_str == "$" {
24 quote! {{
25 ::std::env::current_exe()
26 .expect("Failed to get executable file path")
27 }}
28 }
29
30 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 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 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 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
119struct 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 let expr = input.parse()?;
129
130 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}