1use std::{
6 collections::HashSet,
7 fs::OpenOptions,
8 io::{ErrorKind, Write},
9 path::PathBuf,
10 sync::OnceLock,
11 time::{SystemTime, UNIX_EPOCH},
12};
13
14use proc_macro::TokenStream;
15use quote::quote;
16use serde::Deserialize;
17
18static COMPILE_TIME: OnceLock<u128> = OnceLock::new();
19
20fn state_path() -> PathBuf {
21 let compile_time = COMPILE_TIME.get_or_init(|| {
22 SystemTime::now()
23 .duration_since(UNIX_EPOCH)
24 .expect("your system time is before epoch")
25 .as_nanos()
26 });
27
28 let mut buf = PathBuf::from(env!("VNE_MACRO_STATE_DIR"));
30 buf.push(&format!("macro_state_{}", compile_time));
31 buf
32}
33
34fn get_cargo_workspace() -> PathBuf {
37 #[derive(Deserialize)]
38 struct Manifest {
39 target_directory: String,
40 }
41 let output = std::process::Command::new(env!("CARGO"))
42 .arg("metadata")
43 .arg("--format-version=1")
44 .current_dir(std::env::var("CARGO_MANIFEST_DIR").expect("'CARGO_MANIFEST_DIR' is not set"))
45 .output()
46 .unwrap();
47 let manifest: Manifest = serde_json::from_slice(&output.stdout).unwrap();
49 PathBuf::from(manifest.target_directory)
50}
51
52#[proc_macro]
53pub fn build(_item: TokenStream) -> TokenStream {
54 let crate_ref = quote!(vne);
55
56 let vars = match std::fs::read_to_string(state_path()) {
57 Ok(file) => {
58 let schema_file_path = PathBuf::from(get_cargo_workspace()).join("vne");
59 let pkg_name = std::env::var("CARGO_PKG_NAME").expect("'CARGO_PKG_NAME' is not set");
60 let bin_name = std::env::var("CARGO_BIN_NAME").expect("'CARGO_BIN_NAME' is not set");
61
62 let mut lines = if schema_file_path.exists() {
63 let schema_file = std::fs::read_to_string(&schema_file_path).unwrap();
64 let mut lines = schema_file.split("\n");
65 lines.next().expect("malformed vne schema file");
66 lines
67 .filter(|v| v.starts_with(&format!("{pkg_name}/t{bin_name}")))
68 .collect::<Vec<&str>>()
69 .join("\n")
70 } else {
71 String::new()
72 };
73
74 lines.push_str(&format!(
75 "{pkg_name}\t{bin_name}\t{}",
76 file.replace("\n", "\t")
77 ));
78
79 std::fs::write(
80 schema_file_path,
81 format!("{}\n{}", COMPILE_TIME.get().unwrap(), lines),
82 )
83 .unwrap();
84
85 file.split("\n")
86 .map(|v| v.to_string())
87 .filter(|v| !v.is_empty())
88 .collect()
89 }
90 Err(err) if err.kind() == ErrorKind::NotFound => HashSet::new(),
91 Err(err) => panic!("{err}"),
92 }
93 .into_iter();
94
95 quote! {
96 #crate_ref::internal::construct_env(&[#( #vars ),*])
97 }
98 .into()
99}
100
101#[proc_macro]
102pub fn vne(item: TokenStream) -> TokenStream {
103 let crate_ref = quote!(vne);
104 let item: proc_macro2::TokenStream = item.into();
105
106 let value = item.to_string();
108 let value = value[1..value.len() - 1].to_string();
109
110 let path = state_path();
111 std::fs::create_dir_all(&path.parent().unwrap()).unwrap();
112 let mut file = OpenOptions::new()
113 .write(true)
114 .append(true)
115 .create(true)
116 .open(path)
117 .unwrap(); if let Err(e) = writeln!(file, "{value}") {
122 eprintln!("vne couldn't write to file: {}", e);
123 }
124
125 quote! {
126 #crate_ref::internal::from_env(#item)
127 }
128 .into()
129}