oden/fine/build.rs

138 lines
4.9 KiB
Rust

use quote::{format_ident, quote};
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
fn generate_test_for_file(path: PathBuf) -> String {
let contents = fs::read_to_string(&path)
.expect("Unable to read input")
.replace("\r\n", "\n");
let display_path = path.display().to_string();
// Start iterating over lines and processing directives....
let mut disabled = quote! {};
let mut assertions = Vec::new();
let mut lines = contents.lines();
while let Some(line) = lines.next() {
let line = match line.strip_prefix("//") {
Some(line) => line,
None => continue,
};
let line = line.trim();
if line == "@disabled" {
disabled = quote! { #[ignore] };
} else if line == "@concrete:" {
let mut concrete = String::new();
while let Some(line) = lines.next() {
let line = match line.strip_prefix("// | ") {
Some(line) => line,
None => break,
};
concrete.push_str(line);
concrete.push_str("\n");
}
assertions.push(quote! {
crate::assert_concrete(&_tree, #concrete, #display_path);
});
} else if line == "@compiles-to:" {
let mut compiled = String::new();
while let Some(line) = lines.next() {
let line = match line.strip_prefix("// | ") {
Some(line) => line,
None => break,
};
compiled.push_str(line);
compiled.push_str("\n");
}
assertions.push(quote! {
crate::assert_compiles_to(&_tree, &_lines, #compiled, #display_path);
});
} else if let Some(line) = line.strip_prefix("@type:") {
let (pos, expected) = line
.trim()
.split_once(' ')
.expect("Mal-formed type expectation");
let pos: usize = pos
.trim()
.parse()
.expect(&format!("Unable to parse position '{pos}'"));
let expected = expected.trim();
assertions.push(quote! {
crate::assert_type_at(&_tree, &_lines, #pos, #expected, #display_path);
});
} else if let Some(line) = line.strip_prefix("@type-error:") {
let (pos, expected) = line
.trim()
.split_once(' ')
.expect("Mal-formed type-error expectation");
let pos: usize = pos
.trim()
.parse()
.expect(&format!("Unable to parse position '{pos}'"));
let expected = expected.trim();
assertions.push(quote! {
crate::assert_type_error_at(&_tree, &_lines, #pos, #expected, #display_path);
});
}
}
let name = format_ident!("{}", path.file_stem().unwrap().to_string_lossy());
let test_method = quote! {
#disabled
fn #name() {
let (_tree, _lines) = fine::parser::parse(#contents);
#(#assertions)*
}
};
let syntax_tree = syn::parse2(test_method).unwrap();
prettyplease::unparse(&syntax_tree)
}
fn process_directory<T>(output: &mut String, path: T)
where
T: AsRef<Path>,
{
let fine_ext: std::ffi::OsString = "fine".into();
let path = path.as_ref();
for entry in std::fs::read_dir(path).expect("Unable to read directory") {
match entry {
Ok(dirent) => {
let file_type = dirent.file_type().unwrap();
if file_type.is_dir() {
let file_name = dirent.file_name();
let file_name = file_name.to_string_lossy().to_owned();
output.push_str(&format!("mod {file_name} {{\n"));
process_directory(output, dirent.path());
output.push_str("}\n\n");
} else if file_type.is_file() {
if dirent.path().extension() == Some(&fine_ext) {
output.push_str(&format!("// {}\n", dirent.path().display()));
output.push_str("#[test]\n");
output.push_str(&generate_test_for_file(dirent.path()));
output.push_str("\n\n");
}
} else {
eprintln!("Skipping symlink: {}", path.display());
}
}
Err(e) => eprintln!("Unable to read directory entry: {:?}", e),
}
}
}
fn main() {
println!("cargo:rerun-if-changed=./tests");
let mut test_source = String::new();
process_directory(&mut test_source, "./tests");
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("generated_tests.rs");
fs::write(dest_path, test_source).unwrap();
}