1use proc_macro::TokenStream;
2use proc_macro2::{Ident, Span};
3use quote::quote;
4use syn::{parse_macro_input, DeriveInput};
5
6mod derive_opts;
7
8#[proc_macro_derive(OptsBuilder, attributes(builder))]
10pub fn derive_opts_builder(input: TokenStream) -> TokenStream {
11 let input = parse_macro_input!(input as DeriveInput);
12 derive_opts::expand_derive_opts_builder(&input)
13 .unwrap_or_else(syn::Error::into_compile_error)
14 .into()
15}
16
17#[cfg(feature = "module")]
32#[proc_macro_attribute]
33pub fn oxi_module(_attr: TokenStream, item: TokenStream) -> TokenStream {
34 let item = parse_macro_input!(item as syn::ItemFn);
35
36 #[allow(clippy::redundant_clone)]
37 let module_name = item.sig.ident.clone();
38
39 let lua_module =
40 Ident::new(&format!("luaopen_{module_name}"), Span::call_site());
41
42 let module_body = quote! {
43 #item
44
45 #[no_mangle]
46 unsafe extern "C" fn #lua_module(
47 state: *mut ::nvim_oxi::lua::ffi::lua_State,
48 ) -> ::std::ffi::c_int {
49 ::nvim_oxi::entrypoint(state, #module_name)
50 }
51 };
52
53 module_body.into()
54}
55
56#[cfg(feature = "test")]
71#[proc_macro_attribute]
72pub fn oxi_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
73 let item = parse_macro_input!(item as syn::ItemFn);
74
75 let syn::ItemFn { sig, block, .. } = item;
76
77 let test_name = sig.ident;
83 let test_body = block;
84
85 let module_name = Ident::new(&format!("__{test_name}"), Span::call_site());
86
87 quote! {
88 #[test]
89 fn #test_name() {
90 let mut library_filename = String::new();
91 library_filename.push_str(::std::env::consts::DLL_PREFIX);
92 library_filename.push_str(env!("CARGO_CRATE_NAME"));
93 library_filename.push_str(::std::env::consts::DLL_SUFFIX);
94
95 let mut target_filename = String::from("__");
96 target_filename.push_str(stringify!(#test_name));
97
98 #[cfg(not(target_os = "macos"))]
99 target_filename.push_str(::std::env::consts::DLL_SUFFIX);
100
101 #[cfg(target_os = "macos")]
102 target_filename.push_str(".so");
103
104 let manifest_dir = env!("CARGO_MANIFEST_DIR");
105 let target_dir = nvim_oxi::__test::get_target_dir(manifest_dir.as_ref()).join("debug");
106
107 let library_filepath = target_dir.join(library_filename);
108
109 if !library_filepath.exists() {
110 panic!(
111 "Compiled library not found in '{}'. Please run `cargo \
112 build` before running the tests.",
113 library_filepath.display()
114 )
115 }
116
117 let target_filepath =
118 target_dir.join("oxi-test").join("lua").join(target_filename);
119
120 if !target_filepath.parent().unwrap().exists() {
121 if let Err(err) = ::std::fs::create_dir_all(
122 target_filepath.parent().unwrap(),
123 ) {
124 if !matches!(
127 err.kind(),
128 ::std::io::ErrorKind::AlreadyExists
129 ) {
130 panic!("{}", err)
131 }
132 }
133 }
134
135 #[cfg(unix)]
136 let res = ::std::os::unix::fs::symlink(
137 &library_filepath,
138 &target_filepath,
139 );
140
141 #[cfg(windows)]
142 let res = ::std::os::windows::fs::symlink_file(
143 &library_filepath,
144 &target_filepath,
145 );
146
147 if let Err(err) = res {
148 if !matches!(err.kind(), ::std::io::ErrorKind::AlreadyExists) {
149 panic!("{}", err)
150 }
151 }
152
153 let out = ::std::process::Command::new("nvim")
154 .args(["-u", "NONE", "--headless"])
155 .args(["-c", "set noswapfile"])
156 .args([
157 "-c",
158 &format!(
159 "set rtp+={}",
160 target_dir.join("oxi-test").display()
161 ),
162 ])
163 .args([
164 "-c",
165 &format!("lua require('__{}')", stringify!(#test_name)),
166 ])
167 .args(["+quit"])
168 .output()
169 .expect("Couldn't find `nvim` binary in $PATH");
170
171 if out.status.success() {
172 return;
173 }
174
175 let stderr = String::from_utf8_lossy(&out.stderr);
176
177 if !stderr.is_empty() {
178 let stderr = {
180 let lines = stderr.lines().collect::<Vec<_>>();
181 let len = lines.len();
182 lines[..lines.len() - 2].join("\n")
183 };
184
185 let (_, stderr) = stderr.split_at(31);
187
188 panic!("{}", stderr)
189 } else if let Some(code) = out.status.code() {
190 panic!("Neovim exited with non-zero exit code: {}", code);
191 } else {
192 panic!("Neovim segfaulted");
193 }
194 }
195
196 #[::nvim_oxi::module]
197 fn #module_name() -> ::nvim_oxi::Result<()> {
198 let result = ::std::panic::catch_unwind(|| {
199 #test_body
200 });
201
202 ::std::process::exit(match result {
203 Ok(_) => 0,
204
205 Err(err) => {
206 eprintln!("{:?}", err);
207 1
208 },
209 })
210 }
211 }
212 .into()
213}