1//! Functions for querying the cache.
2
3use pyo3::{exceptions::PyIOError, prelude::*};
4
5use analyzer::data_model::{self as analyze_model};
6
7use crate::data_model::{Crate, Enum, Function, Module, Struct};
8
9fn read_file(path: &std::path::Path) -> PyResult<String> {
10 match std::fs::read_to_string(path) {
11 Ok(contents) => Ok(contents),
12 Err(err) => Err(PyIOError::new_err(format!(
13 "Could not read file: {}: {}",
14 path.to_string_lossy(),
15 err
16 ))),
17 }
18}
19
20/// Deserialize an object from a string.
21fn deserialize_object<'a, T>(name: &str, content: &'a str) -> PyResult<T>
22where
23 T: serde::Deserialize<'a>,
24{
25 let obj: T = match serde_json::from_str(content) {
26 Ok(crate_) => crate_,
27 Err(err) => {
28 return Err(PyIOError::new_err(format!(
29 "Could not deserialize {}: {}",
30 name, err
31 )))
32 }
33 };
34 Ok(obj)
35}
36
37#[pyfunction]
38/// load a crate from the cache, if it exists
39pub fn load_crate(cache_path: &str, name: &str) -> PyResult<Option<Crate>> {
40 let path = std::path::Path::new(cache_path)
41 .join("crates")
42 .join(format!("{}.json", name));
43 if !path.exists() {
44 return Ok(None);
45 }
46 let contents = read_file(&path)?;
47 let crate_: analyze_model::Crate = deserialize_object(name, &contents)?;
48 Ok(Some(crate_.into()))
49}
50
51#[pyfunction]
52/// load a module from the cache, if it exists
53pub fn load_module(cache_path: &str, full_name: &str) -> PyResult<Option<Module>> {
54 let path = std::path::Path::new(cache_path)
55 .join("modules")
56 .join(format!("{}.json", full_name));
57 if !path.exists() {
58 return Ok(None);
59 }
60 let contents = read_file(&path)?;
61 let mod_: analyze_model::Module = deserialize_object(full_name, &contents)?;
62 Ok(Some(mod_.into()))
63}
64
65#[pyfunction]
66/// load a struct from the cache, if it exists
67pub fn load_struct(cache_path: &str, full_name: &str) -> PyResult<Option<Struct>> {
68 let path = std::path::Path::new(cache_path)
69 .join("structs")
70 .join(format!("{}.json", full_name));
71 if !path.exists() {
72 return Ok(None);
73 }
74 let contents = read_file(&path)?;
75 let struct_: analyze_model::Struct = deserialize_object(full_name, &contents)?;
76 Ok(Some(struct_.into()))
77}
78
79#[pyfunction]
80/// load an enum from the cache, if it exists
81pub fn load_enum(cache_path: &str, full_name: &str) -> PyResult<Option<Enum>> {
82 let path = std::path::Path::new(cache_path)
83 .join("enums")
84 .join(format!("{}.json", full_name));
85 if !path.exists() {
86 return Ok(None);
87 }
88 let contents = read_file(&path)?;
89 let enum_: analyze_model::Enum = deserialize_object(full_name, &contents)?;
90 Ok(Some(enum_.into()))
91}
92
93#[pyfunction]
94/// load a function from the cache, if it exists
95pub fn load_function(cache_path: &str, full_name: &str) -> PyResult<Option<Function>> {
96 let path = std::path::Path::new(cache_path)
97 .join("functions")
98 .join(format!("{}.json", full_name));
99 if !path.exists() {
100 return Ok(None);
101 }
102 let contents = read_file(&path)?;
103 let func: analyze_model::Function = deserialize_object(full_name, &contents)?;
104 Ok(Some(func.into()))
105}
106
107/// Check if a path is a child of a given parent, and return the fully qualified name of the child.
108fn is_child(path: &std::path::PathBuf, parent: &Vec<String>) -> Option<String> {
109 let name = match path.file_stem() {
110 Some(name) => name,
111 None => return None,
112 };
113 let name = match name.to_str() {
114 Some(name) => name,
115 None => return None,
116 };
117 let name_path = name.split("::").collect::<Vec<_>>();
118 if name_path.len() != parent.len() + 1 {
119 return None;
120 }
121 for (a, b) in parent.iter().zip(name_path.iter().take(parent.len())) {
122 if a != b {
123 return None;
124 }
125 }
126 Some(name.to_string())
127}
128
129#[pyfunction]
130/// load all modules from the cache that are children of the given parent
131pub fn load_child_modules(cache_path: &str, parent: Vec<String>) -> PyResult<Vec<Module>> {
132 let path = std::path::Path::new(cache_path).join("modules");
133 if !path.exists() {
134 return Ok(vec![]);
135 }
136 let mut modules = vec![];
137 for entry in std::fs::read_dir(path)? {
138 let entry = entry?;
139 let path = entry.path();
140 if path.is_file() {
141 if let Some(name) = is_child(&path, &parent) {
142 let contents = read_file(&path)?;
143 let mod_: analyze_model::Module = deserialize_object(&name, &contents)?;
144 modules.push(mod_.into());
145 }
146 }
147 }
148 Ok(modules)
149}
150
151#[pyfunction]
152/// load all structs from the cache that are children of the given parent
153pub fn load_child_structs(cache_path: &str, parent: Vec<String>) -> PyResult<Vec<Struct>> {
154 let path = std::path::Path::new(cache_path).join("structs");
155 if !path.exists() {
156 return Ok(vec![]);
157 }
158 let mut structs = vec![];
159 for entry in std::fs::read_dir(path)? {
160 let entry = entry?;
161 let path = entry.path();
162 if path.is_file() {
163 if let Some(name) = is_child(&path, &parent) {
164 let contents = read_file(&path)?;
165 let struct_: analyze_model::Struct = deserialize_object(&name, &contents)?;
166 structs.push(struct_.into());
167 }
168 }
169 }
170 Ok(structs)
171}
172
173#[pyfunction]
174/// load all enums from the cache that are children of the given parent
175pub fn load_child_enums(cache_path: &str, parent: Vec<String>) -> PyResult<Vec<Enum>> {
176 let path = std::path::Path::new(cache_path).join("enums");
177 if !path.exists() {
178 return Ok(vec![]);
179 }
180 let mut enums = vec![];
181 for entry in std::fs::read_dir(path)? {
182 let entry = entry?;
183 let path = entry.path();
184 if path.is_file() {
185 if let Some(name) = is_child(&path, &parent) {
186 let contents = read_file(&path)?;
187 let enum_: analyze_model::Enum = deserialize_object(&name, &contents)?;
188 enums.push(enum_.into());
189 }
190 }
191 }
192 Ok(enums)
193}
194
195#[pyfunction]
196/// load all function from the cache that are children of the given parent
197pub fn load_child_functions(cache_path: &str, parent: Vec<String>) -> PyResult<Vec<Function>> {
198 let path = std::path::Path::new(cache_path).join("functions");
199 if !path.exists() {
200 return Ok(vec![]);
201 }
202 let mut funcs = vec![];
203 for entry in std::fs::read_dir(path)? {
204 let entry = entry?;
205 let path = entry.path();
206 if path.is_file() {
207 if let Some(name) = is_child(&path, &parent) {
208 let contents = read_file(&path)?;
209 let func: analyze_model::Function = deserialize_object(&name, &contents)?;
210 funcs.push(func.into());
211 }
212 }
213 }
214 Ok(funcs)
215}
216
217/// Check if a path is an ancestor of a given parent, and return the fully qualified name of the child.
218fn is_ancestor(
219 path: &std::path::PathBuf,
220 parent: &Vec<String>,
221 include_self: bool,
222) -> Option<String> {
223 let name = path.file_stem()?.to_str()?;
224 let name_path = name.split("::").collect::<Vec<_>>();
225 if include_self && name_path == parent.iter().map(|s| s.as_str()).collect::<Vec<_>>() {
226 return Some(name.to_string());
227 }
228 if name_path.len() <= parent.len() {
229 return None;
230 }
231 for (a, b) in parent.iter().zip(name_path.iter().take(parent.len())) {
232 if a != b {
233 return None;
234 }
235 }
236 Some(name.to_string())
237}
238
239#[pyfunction]
240/// load all modules from the cache that have a common descendant
241pub fn load_descendant_modules(
242 cache_path: &str,
243 ancestor: Vec<String>,
244 include_self: bool,
245) -> PyResult<Vec<Module>> {
246 let path = std::path::Path::new(cache_path).join("modules");
247 if !path.exists() {
248 return Ok(vec![]);
249 }
250 let mut modules = vec![];
251 for entry in std::fs::read_dir(path)? {
252 let entry = entry?;
253 let path = entry.path();
254 if path.is_file() {
255 if let Some(name) = is_ancestor(&path, &ancestor, include_self) {
256 let contents = read_file(&path)?;
257 let mod_: analyze_model::Module = deserialize_object(&name, &contents)?;
258 modules.push(mod_.into());
259 }
260 }
261 }
262 Ok(modules)
263}
264
265#[pyfunction]
266/// load all structs from the cache that have a common ancestor
267pub fn load_descendant_structs(cache_path: &str, ancestor: Vec<String>) -> PyResult<Vec<Struct>> {
268 let path = std::path::Path::new(cache_path).join("structs");
269 if !path.exists() {
270 return Ok(vec![]);
271 }
272 let mut structs = vec![];
273 for entry in std::fs::read_dir(path)? {
274 let entry = entry?;
275 let path = entry.path();
276 if path.is_file() {
277 if let Some(name) = is_ancestor(&path, &ancestor, false) {
278 let contents = read_file(&path)?;
279 let struct_: analyze_model::Struct = deserialize_object(&name, &contents)?;
280 structs.push(struct_.into());
281 }
282 }
283 }
284 Ok(structs)
285}
286
287#[pyfunction]
288/// load all enums from the cache that that have a common ancestor
289pub fn load_descendant_enums(cache_path: &str, ancestor: Vec<String>) -> PyResult<Vec<Enum>> {
290 let path = std::path::Path::new(cache_path).join("enums");
291 if !path.exists() {
292 return Ok(vec![]);
293 }
294 let mut enums = vec![];
295 for entry in std::fs::read_dir(path)? {
296 let entry = entry?;
297 let path = entry.path();
298 if path.is_file() {
299 if let Some(name) = is_ancestor(&path, &ancestor, false) {
300 let contents = read_file(&path)?;
301 let enum_: analyze_model::Enum = deserialize_object(&name, &contents)?;
302 enums.push(enum_.into());
303 }
304 }
305 }
306 Ok(enums)
307}