use std::collections::HashSet;
use std::path::Path;
use proc_macro2::TokenTree;
pub type Classes = HashSet<String>;
pub fn scan_crate(dir: impl AsRef<Path>) -> Result<Classes, ScanError> {
let rs_extension = std::ffi::OsString::from("rs");
let mut paths = vec![];
for file in ignore::Walk::new(dir.as_ref()) {
let file = file.map_err(ScanError::WalkDir)?;
let path = file.into_path();
if path.extension() == Some(&rs_extension) {
rerun_if_changed(&path);
paths.push(path);
}
}
let classes = paths
.into_iter()
.map(|path| -> Result<_, ScanError> {
let contents = std::fs::read_to_string(&path).map_err(ScanError::ReadFile)?;
let file = syn::parse_file(&contents).map_err(ScanError::Parse)?;
find_classes(&file).map_err(ScanError::Parse)
})
.try_fold(
HashSet::new(),
|mut acc, classes_res| -> Result<_, ScanError> {
let classes = classes_res?;
acc.extend(classes.into_iter().map(|x| x.to_string()));
Ok(acc)
},
)?;
Ok(classes)
}
#[derive(Debug)]
pub enum ScanError {
WalkDir(ignore::Error),
ReadFile(std::io::Error),
Parse(syn::Error),
}
impl std::fmt::Display for ScanError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ScanError::WalkDir(err) => {
f.write_fmt(format_args!("Directory walking error: {}", err))
}
ScanError::ReadFile(err) => f.write_fmt(format_args!("File reading error: {}", err)),
ScanError::Parse(err) => f.write_fmt(format_args!("Parsing error: {}", err)),
}
}
}
impl std::error::Error for ScanError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ScanError::WalkDir(err) => Some(err),
ScanError::ReadFile(err) => Some(err),
ScanError::Parse(err) => Some(err),
}
}
}
fn find_classes(file: &syn::File) -> Result<HashSet<syn::Ident>, syn::Error> {
fn derives_nativeclass(attrs: &[syn::Attribute]) -> Result<bool, syn::Error> {
let mut res = false;
for attr in attrs {
if !attr.path.is_ident("derive") {
continue;
}
for t in attr.tokens.clone() {
if let TokenTree::Group(g) = &t {
let s = g.stream();
for tt in s {
if let TokenTree::Ident(i) = tt {
if i == "NativeClass" {
res = true;
break;
}
}
}
} else {
return Err(syn::Error::new(t.span(), "Unexpected #[derive attribute]"));
}
}
}
Ok(res)
}
struct Visitor {
classes: HashSet<syn::Ident>,
errors: Vec<syn::Error>,
}
impl<'ast> syn::visit::Visit<'ast> for Visitor {
fn visit_item_struct(&mut self, s: &'ast syn::ItemStruct) {
match derives_nativeclass(&s.attrs) {
Err(err) => {
self.errors.push(err);
}
Ok(true) => {
self.classes.insert(s.ident.clone());
}
Ok(false) => {}
}
syn::visit::visit_item_struct(self, s)
}
fn visit_item_enum(&mut self, i: &'ast syn::ItemEnum) {
match derives_nativeclass(&i.attrs) {
Err(err) => {
self.errors.push(err);
}
Ok(true) => {
self.classes.insert(i.ident.clone());
}
Ok(false) => {}
}
syn::visit::visit_item_enum(self, i)
}
}
let mut vis = Visitor {
classes: HashSet::new(),
errors: vec![],
};
syn::visit::visit_file(&mut vis, file);
if vis.errors.is_empty() {
Ok(vis.classes)
} else {
let mut err = vis.errors.pop().unwrap();
for e in vis.errors {
err.combine(e);
}
Err(err)
}
}
fn rerun_if_changed(path: &Path) {
if cfg!(feature = "build_script") {
println!("cargo:rerun-if-changed={}", path.display());
}
}