1use crate::{index, objects, reply};
2use serde::de::DeserializeOwned;
3use std::ffi::OsStr;
4use std::path::{Path, PathBuf};
5use std::{fs, io};
6
7#[non_exhaustive]
9#[derive(Debug, thiserror::Error)]
10pub enum ReaderError {
11 #[error("IO error: {0}")]
12 IO(io::Error),
13
14 #[error("Failed to deserialize reply: {0}")]
15 Parse(serde_json::Error),
16
17 #[error("cmake-file-api is not generated for this build directory")]
18 FileApiNotGenerated,
19
20 #[error("failed to find object")]
21 ObjectNotFound,
22}
23
24impl From<io::Error> for ReaderError {
25 fn from(err: io::Error) -> Self {
26 ReaderError::IO(err)
27 }
28}
29
30impl From<serde_json::Error> for ReaderError {
31 fn from(err: serde_json::Error) -> Self {
32 ReaderError::Parse(err)
33 }
34}
35
36pub struct Reader {
50 build_dir: PathBuf,
52
53 index: index::Index,
55}
56
57impl Reader {
58 pub fn from_build_dir<P: AsRef<Path>>(build_dir: P) -> Result<Self, ReaderError> {
66 let index_file = index_file(build_dir.as_ref()).ok_or(ReaderError::FileApiNotGenerated)?;
67 let index = Reader::parse_reply(index_file)?;
68 Ok(Reader {
69 build_dir: build_dir.as_ref().to_path_buf(),
70 index,
71 })
72 }
73
74 #[must_use]
75 pub fn build_dir(&self) -> &Path {
76 &self.build_dir
77 }
78
79 #[must_use]
80 pub fn index(&self) -> &index::Index {
81 &self.index
82 }
83
84 #[must_use]
85 pub fn has_object<T: objects::Object>(&self) -> bool {
86 self.find_object(T::kind(), T::major()).is_some()
87 }
88
89 pub fn read_object<T: objects::Object + DeserializeOwned>(&self) -> Result<T, ReaderError> {
97 let reply_reference = self
98 .find_object(T::kind(), T::major())
99 .ok_or(ReaderError::ObjectNotFound)?;
100 let reply_file = reply::dir(&self.build_dir).join(&reply_reference.json_file);
101 let mut object: T = Reader::parse_reply(reply_file)?;
102
103 object.resolve_references(self)?;
104
105 Ok(object)
106 }
107
108 pub(crate) fn parse_reply<P: AsRef<Path>, Object: DeserializeOwned>(
110 reply_file: P,
111 ) -> Result<Object, ReaderError> {
112 let content = fs::read_to_string(&reply_file)?;
113
114 let object = serde_json::from_str(content.as_str())?;
115
116 Ok(object)
117 }
118
119 fn find_object(
121 &self,
122 kind: objects::ObjectKind,
123 major: u32,
124 ) -> Option<&index::ReplyFileReference> {
125 self.index
126 .objects
127 .iter()
128 .find(|obj| obj.kind == kind && obj.version.major == major)
129 }
130}
131
132pub fn dir<P: AsRef<Path>>(build_dir: P) -> PathBuf {
134 Path::new(build_dir.as_ref())
135 .join(".cmake")
136 .join("api")
137 .join("v1")
138 .join("reply")
139}
140
141pub fn index_file<P: AsRef<Path>>(build_dir: P) -> Option<PathBuf> {
143 let reply_dir = dir(build_dir);
144
145 if !reply_dir.exists() {
146 return None;
147 }
148
149 fs::read_dir(&reply_dir).ok()?.find_map(|entry| {
151 let path = entry.ok()?.path();
152 if path.is_file() {
153 if let Some(file_name) = path.file_name().and_then(OsStr::to_str) {
154 if file_name.starts_with("index-")
155 && path
156 .extension()
157 .map_or(false, |ext| ext.eq_ignore_ascii_case("json"))
158 {
159 return Some(path);
160 }
161 }
162 }
163 None
164 })
165}
166
167pub fn is_available<P: AsRef<Path>>(build_dir: P) -> bool {
169 index_file(build_dir).is_some()
170}