gdnative_project_utils/
scan.rs1use std::collections::HashSet;
4use std::path::Path;
5
6use proc_macro2::TokenTree;
7
8pub type Classes = HashSet<String>;
10
11pub fn scan_crate(dir: impl AsRef<Path>) -> Result<Classes, ScanError> {
13 let rs_extension = std::ffi::OsString::from("rs");
14 let mut paths = vec![];
15
16 for file in ignore::Walk::new(dir.as_ref()) {
17 let file = file.map_err(ScanError::WalkDir)?;
18
19 let path = file.into_path();
20
21 if path.extension() == Some(&rs_extension) {
22 rerun_if_changed(&path);
23 paths.push(path);
24 }
25 }
26
27 let classes = paths
28 .into_iter()
29 .map(|path| -> Result<_, ScanError> {
30 let contents = std::fs::read_to_string(&path).map_err(ScanError::ReadFile)?;
31
32 let file = syn::parse_file(&contents).map_err(ScanError::Parse)?;
33
34 find_classes(&file).map_err(ScanError::Parse)
35 })
36 .try_fold(
37 HashSet::new(),
38 |mut acc, classes_res| -> Result<_, ScanError> {
39 let classes = classes_res?;
40 acc.extend(classes.into_iter().map(|x| x.to_string()));
41 Ok(acc)
42 },
43 )?;
44
45 Ok(classes)
46}
47
48#[derive(Debug)]
50pub enum ScanError {
51 WalkDir(ignore::Error),
53 ReadFile(std::io::Error),
55 Parse(syn::Error),
57}
58
59impl std::fmt::Display for ScanError {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 match self {
62 ScanError::WalkDir(err) => {
63 f.write_fmt(format_args!("Directory walking error: {}", err))
64 }
65 ScanError::ReadFile(err) => f.write_fmt(format_args!("File reading error: {}", err)),
66 ScanError::Parse(err) => f.write_fmt(format_args!("Parsing error: {}", err)),
67 }
68 }
69}
70
71impl std::error::Error for ScanError {
72 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
73 match self {
74 ScanError::WalkDir(err) => Some(err),
75 ScanError::ReadFile(err) => Some(err),
76 ScanError::Parse(err) => Some(err),
77 }
78 }
79}
80
81fn find_classes(file: &syn::File) -> Result<HashSet<syn::Ident>, syn::Error> {
82 fn derives_nativeclass(attrs: &[syn::Attribute]) -> Result<bool, syn::Error> {
83 let mut res = false;
84
85 for attr in attrs {
86 if !attr.path.is_ident("derive") {
87 continue;
88 }
89
90 for t in attr.tokens.clone() {
91 if let TokenTree::Group(g) = &t {
92 let s = g.stream();
93
94 for tt in s {
95 if let TokenTree::Ident(i) = tt {
96 if i == "NativeClass" {
97 res = true;
98 break;
99 }
100 }
101 }
102 } else {
103 return Err(syn::Error::new(t.span(), "Unexpected #[derive attribute]"));
104 }
105 }
106 }
107
108 Ok(res)
109 }
110
111 struct Visitor {
112 classes: HashSet<syn::Ident>,
113 errors: Vec<syn::Error>,
114 }
115
116 impl<'ast> syn::visit::Visit<'ast> for Visitor {
117 fn visit_item_struct(&mut self, s: &'ast syn::ItemStruct) {
118 match derives_nativeclass(&s.attrs) {
119 Err(err) => {
120 self.errors.push(err);
121 }
122 Ok(true) => {
123 self.classes.insert(s.ident.clone());
124 }
125 Ok(false) => {}
126 }
127 syn::visit::visit_item_struct(self, s)
128 }
129
130 fn visit_item_enum(&mut self, i: &'ast syn::ItemEnum) {
131 match derives_nativeclass(&i.attrs) {
132 Err(err) => {
133 self.errors.push(err);
134 }
135 Ok(true) => {
136 self.classes.insert(i.ident.clone());
137 }
138 Ok(false) => {}
139 }
140 syn::visit::visit_item_enum(self, i)
141 }
142 }
143
144 let mut vis = Visitor {
145 classes: HashSet::new(),
146 errors: vec![],
147 };
148
149 syn::visit::visit_file(&mut vis, file);
150
151 if vis.errors.is_empty() {
152 Ok(vis.classes)
153 } else {
154 let mut err = vis.errors.pop().unwrap();
155
156 for e in vis.errors {
157 err.combine(e);
158 }
159
160 Err(err)
161 }
162}
163
164fn rerun_if_changed(path: &Path) {
165 if cfg!(feature = "build_script") {
166 println!("cargo:rerun-if-changed={}", path.display());
167 }
168}