unity_asset_core/
document.rs1use crate::error::Result;
7use crate::unity_class::UnityClass;
8use std::path::{Path, PathBuf};
9
10#[cfg(feature = "async")]
11use async_trait::async_trait;
12#[cfg(feature = "async")]
13use futures::Stream;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum DocumentFormat {
18 Yaml,
19 Binary,
20}
21
22pub trait UnityDocument {
24 fn entry(&self) -> Option<&UnityClass>;
26
27 fn entry_mut(&mut self) -> Option<&mut UnityClass>;
29
30 fn entries(&self) -> &[UnityClass];
32
33 fn entries_mut(&mut self) -> &mut Vec<UnityClass>;
35
36 fn add_entry(&mut self, entry: UnityClass);
38
39 fn filter_by_class(&self, class_name: &str) -> Vec<&UnityClass> {
41 self.entries()
42 .iter()
43 .filter(|entry| entry.class_name == class_name)
44 .collect()
45 }
46
47 fn filter_by_classes(&self, class_names: &[&str]) -> Vec<&UnityClass> {
49 self.entries()
50 .iter()
51 .filter(|entry| class_names.contains(&entry.class_name.as_str()))
52 .collect()
53 }
54
55 fn filter<F>(&self, predicate: F) -> Vec<&UnityClass>
57 where
58 F: Fn(&UnityClass) -> bool,
59 {
60 self.entries()
61 .iter()
62 .filter(|entry| predicate(entry))
63 .collect()
64 }
65
66 fn find_by_class_and_property(&self, class_name: &str, property: &str) -> Option<&UnityClass> {
68 self.entries()
69 .iter()
70 .find(|entry| entry.class_name == class_name && entry.has_property(property))
71 }
72
73 fn file_path(&self) -> Option<&Path>;
75
76 fn is_empty(&self) -> bool {
78 self.entries().is_empty()
79 }
80
81 fn len(&self) -> usize {
83 self.entries().len()
84 }
85
86 fn save(&self) -> Result<()>;
88
89 fn save_to<P: AsRef<Path>>(&self, path: P) -> Result<()>;
91
92 fn format(&self) -> DocumentFormat;
94}
95
96#[derive(Debug, Clone)]
98pub struct DocumentMetadata {
99 pub file_path: Option<PathBuf>,
101 pub format: DocumentFormat,
103 pub version: Option<String>,
105 pub metadata: std::collections::HashMap<String, String>,
107}
108
109impl DocumentMetadata {
110 pub fn new(format: DocumentFormat) -> Self {
112 Self {
113 file_path: None,
114 format,
115 version: None,
116 metadata: std::collections::HashMap::new(),
117 }
118 }
119
120 pub fn with_file_path<P: AsRef<Path>>(mut self, path: P) -> Self {
122 self.file_path = Some(path.as_ref().to_path_buf());
123 self
124 }
125
126 pub fn with_version<S: Into<String>>(mut self, version: S) -> Self {
128 self.version = Some(version.into());
129 self
130 }
131
132 pub fn with_metadata<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
134 self.metadata.insert(key.into(), value.into());
135 self
136 }
137}
138
139#[cfg(feature = "async")]
141#[async_trait]
142pub trait AsyncUnityDocument: Send + Sync {
143 async fn load_from_path_async<P: AsRef<Path> + Send>(path: P) -> Result<Self>
145 where
146 Self: Sized;
147
148 async fn save_to_path_async<P: AsRef<Path> + Send>(&self, path: P) -> Result<()>;
150
151 fn entries(&self) -> &[UnityClass];
153
154 fn entry(&self) -> Option<&UnityClass> {
156 self.entries().first()
157 }
158
159 fn file_path(&self) -> Option<&Path>;
161
162 fn entries_stream(&self) -> impl Stream<Item = &UnityClass> + Send {
164 futures::stream::iter(self.entries())
165 }
166
167 async fn process_entries<F, Fut>(&self, mut processor: F) -> Result<()>
169 where
170 F: FnMut(&UnityClass) -> Fut + Send,
171 Fut: std::future::Future<Output = Result<()>> + Send,
172 {
173 for entry in self.entries() {
174 processor(entry).await?;
175 }
176 Ok(())
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use crate::unity_class::UnityClass;
184
185 struct MockDocument {
187 entries: Vec<UnityClass>,
188 metadata: DocumentMetadata,
189 }
190
191 impl UnityDocument for MockDocument {
192 fn entry(&self) -> Option<&UnityClass> {
193 self.entries.first()
194 }
195
196 fn entry_mut(&mut self) -> Option<&mut UnityClass> {
197 self.entries.first_mut()
198 }
199
200 fn entries(&self) -> &[UnityClass] {
201 &self.entries
202 }
203
204 fn entries_mut(&mut self) -> &mut Vec<UnityClass> {
205 &mut self.entries
206 }
207
208 fn add_entry(&mut self, entry: UnityClass) {
209 self.entries.push(entry);
210 }
211
212 fn file_path(&self) -> Option<&Path> {
213 self.metadata.file_path.as_deref()
214 }
215
216 fn save(&self) -> Result<()> {
217 Ok(()) }
219
220 fn save_to<P: AsRef<Path>>(&self, _path: P) -> Result<()> {
221 Ok(()) }
223
224 fn format(&self) -> DocumentFormat {
225 self.metadata.format
226 }
227 }
228
229 #[test]
230 fn test_document_trait() {
231 let mut doc = MockDocument {
232 entries: Vec::new(),
233 metadata: DocumentMetadata::new(DocumentFormat::Yaml),
234 };
235
236 assert!(doc.is_empty());
237 assert_eq!(doc.len(), 0);
238
239 let class = UnityClass::new(1, "GameObject".to_string(), "123".to_string());
240 doc.add_entry(class);
241
242 assert!(!doc.is_empty());
243 assert_eq!(doc.len(), 1);
244 assert_eq!(doc.format(), DocumentFormat::Yaml);
245 }
246
247 #[test]
248 fn test_document_filtering() {
249 let mut doc = MockDocument {
250 entries: Vec::new(),
251 metadata: DocumentMetadata::new(DocumentFormat::Yaml),
252 };
253
254 let game_object = UnityClass::new(1, "GameObject".to_string(), "123".to_string());
255 let behaviour = UnityClass::new(114, "MonoBehaviour".to_string(), "456".to_string());
256
257 doc.add_entry(game_object);
258 doc.add_entry(behaviour);
259
260 let game_objects = doc.filter_by_class("GameObject");
261 assert_eq!(game_objects.len(), 1);
262
263 let behaviours = doc.filter_by_class("MonoBehaviour");
264 assert_eq!(behaviours.len(), 1);
265 }
266}