1#![warn(missing_docs)]
38#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]
39
40use proc_macro::{TokenStream, TokenTree};
41
42use core::mem;
43use core::cell::UnsafeCell;
44
45use std::fs;
46use std::io::{self, BufRead};
47use std::collections::{hash_map, HashMap};
48use std::sync::Once;
49
50mod format;
51
52const QUOTE: char = '"';
53
54#[cold]
55#[inline(never)]
56fn compile_error(error: &str) -> TokenStream {
57 format!("compile_error!(\"{error}\")").parse().unwrap()
58}
59
60fn read_envs() -> Result<HashMap<String, String>, TokenStream> {
61 const QUOTES: &[char] = &['"', '\''];
62 let mut envs = HashMap::default();
63
64 match fs::File::open(".env") {
65 Ok(file) => {
66 let file = io::BufReader::new(file);
67 for line in file.lines() {
68 match line {
69 Ok(line) => {
70 let mut split = line.splitn(2, '=');
71 let key = split.next().unwrap();
72 let value = match split.next() {
73 Some(value) => value.trim_matches(QUOTES),
74 None => return Err(compile_error(&format!(".env file has '{key}' without value"))),
75 };
76
77 if envs.insert(key.to_owned(), value.to_owned()).is_some() {
78 return Err(compile_error(&format!(".env file has multiple instances of '{key}'")))
79 }
80 },
81 Err(error) => {
82 let error = format!(".env: Read fail: {error}");
83 return Err(compile_error(&error));
84 }
85 }
86 }
87 }
88 Err(error) => match error.kind() {
89 io::ErrorKind::NotFound => (),
90 _ => {
91 let error = format!(".env: Cannot open: {error}");
92 return Err(compile_error(&error));
93 },
94 }
95 };
96
97 for (key, value) in std::env::vars() {
98 match envs.entry(key) {
99 hash_map::Entry::Vacant(vacant) => {
100 vacant.insert(value);
101 },
102 hash_map::Entry::Occupied(_) => (),
103 }
104 }
105
106 Ok(envs)
107}
108
109type State = UnsafeCell<mem::MaybeUninit<Result<HashMap<String, String>, TokenStream>>>;
111struct Cache(State);
112unsafe impl Sync for Cache {}
113
114fn read_cached_envs() -> &'static Result<HashMap<String, String>, TokenStream> {
117 static STATE: Cache = Cache(State::new(mem::MaybeUninit::uninit()));
118 static LOCK: Once = Once::new();
119
120 LOCK.call_once(|| {
121 unsafe {
122 *STATE.0.get() = mem::MaybeUninit::new(read_envs());
123 }
124 });
125
126 unsafe {
127 &*(STATE.0.get() as *const _)
128 }
129}
130
131struct Args {
132 input: String,
133}
134
135impl Args {
136 pub fn from_tokens(input: TokenStream) -> Result<Self, TokenStream> {
137 const EXPECTED_STRING: &str = "Expected string literal";
138 let mut args = input.into_iter();
139
140 let input = match args.next() {
141 Some(TokenTree::Literal(lit)) => {
142 let quoted = lit.to_string();
143 let result = quoted.trim_matches(QUOTE);
144 if result.len() + 2 != quoted.len() {
145 return Err(compile_error(EXPECTED_STRING));
146 }
147 result.to_owned()
148 },
149 Some(unexpected) => return Err(compile_error(&format!("{EXPECTED_STRING}, got {:?}", unexpected))),
150 None => return Err(compile_error("Missing input string")),
151 };
152
153 Ok(Self {
154 input,
155 })
156 }
157}
158
159#[proc_macro]
160pub fn env(input: TokenStream) -> TokenStream {
162 let args = match Args::from_tokens(input) {
163 Ok(args) => args,
164 Err(error) => return error,
165 };
166 let envs = match read_cached_envs() {
167 Ok(envs) => envs,
168 Err(error) => return error.clone(),
169 };
170
171 let mut output = String::new();
172 let mut formatter = format::Format::new(args.input.as_str(), &envs);
173
174 let mut plain_len = 0;
175 let mut args_len = 0;
176
177 output.push(QUOTE);
178 while let Some(part) = formatter.next() {
179 match part {
180 Ok(part) => match part {
181 format::Part::Plain(plain) => {
182 plain_len += 1;
183 output.push_str(plain);
184 }
185 format::Part::Argument(plain) => {
186 args_len += 1;
187 output.push_str(plain);
188 }
189 },
190 Err(error) => {
191 return compile_error(&format!("Format string error {error}"));
192 }
193 }
194 }
195
196 if args_len == 0 {
197 debug_assert_eq!(plain_len, 1);
198 match std::env::var(&output[1..]) {
199 Ok(value) => {
200 output.clear();
201 output.push(QUOTE);
202 output.push_str(&value);
203 },
204 Err(_) => return compile_error(&format!("env:{}: Cannot fetch env value", &output[1..])),
205 }
206 }
207
208 output.push(QUOTE);
209
210 output.parse().expect("valid literal string syntax")
211}