unity_asset_yaml/
python_like_api.rs

1//! Python-like API for Unity YAML parsing
2//!
3//! This module provides a more Python-like interface similar to the reference library,
4//! making it easier for users familiar with the Python unity-yaml-parser to migrate.
5
6use crate::YamlDocument;
7use std::path::Path;
8use unity_asset_core::{
9    DynamicAccess, DynamicValue, Result, UnityAssetError, UnityClass,
10    UnityDocument as UnityDocumentTrait,
11};
12
13/// Python-like wrapper for Unity documents
14///
15/// This provides an API similar to the Python reference library:
16/// ```python
17/// doc = UnityDocument.load_yaml("file.asset")
18/// entry = doc.entry
19/// entry.m_Name = "NewName"
20/// doc.dump_yaml()
21/// ```
22pub struct PythonLikeUnityDocument {
23    /// Internal YAML document
24    inner: YamlDocument,
25}
26
27impl PythonLikeUnityDocument {
28    /// Load a Unity YAML file (similar to Python's UnityDocument.load_yaml)
29    ///
30    /// # Arguments
31    /// * `file_path` - Path to the Unity YAML file
32    /// * `try_preserve_types` - Whether to preserve original types (similar to Python's flag)
33    ///
34    /// # Examples
35    /// ```rust,no_run
36    /// use unity_asset_yaml::python_like_api::PythonLikeUnityDocument;
37    ///
38    /// let doc = PythonLikeUnityDocument::load_yaml("ProjectSettings.asset", false)?;
39    /// # Ok::<(), unity_asset_core::UnityAssetError>(())
40    /// ```
41    pub fn load_yaml<P: AsRef<Path>>(file_path: P, try_preserve_types: bool) -> Result<Self> {
42        let inner = YamlDocument::load_yaml(file_path, try_preserve_types)?;
43        Ok(Self { inner })
44    }
45
46    /// Get the first entry (similar to Python's doc.entry)
47    pub fn entry(&self) -> Option<PythonLikeUnityClass<'_>> {
48        self.inner.entries().first().map(PythonLikeUnityClass::new)
49    }
50
51    /// Get all entries (similar to Python's doc.entries)
52    pub fn entries(&self) -> Vec<PythonLikeUnityClass<'_>> {
53        self.inner
54            .entries()
55            .iter()
56            .map(PythonLikeUnityClass::new)
57            .collect()
58    }
59
60    /// Filter entries by class names and attributes (similar to Python's doc.filter)
61    ///
62    /// # Examples
63    /// ```rust,no_run
64    /// use unity_asset_yaml::python_like_api::PythonLikeUnityDocument;
65    ///
66    /// let doc = PythonLikeUnityDocument::load_yaml("scene.unity", false)?;
67    ///
68    /// // Find all GameObjects
69    /// let gameobjects = doc.filter(Some(&["GameObject"]), None);
70    ///
71    /// // Find objects with m_Enabled property
72    /// let enabled_objects = doc.filter(None, Some(&["m_Enabled"]));
73    /// # Ok::<(), unity_asset_core::UnityAssetError>(())
74    /// ```
75    pub fn filter(
76        &self,
77        class_names: Option<&[&str]>,
78        attributes: Option<&[&str]>,
79    ) -> Vec<PythonLikeUnityClass<'_>> {
80        self.inner
81            .filter(class_names, attributes)
82            .iter()
83            .map(|class| PythonLikeUnityClass::new(class))
84            .collect()
85    }
86
87    /// Get a single entry by class name and attributes (similar to Python's doc.get)
88    pub fn get(
89        &self,
90        class_name: Option<&str>,
91        attributes: Option<&[&str]>,
92    ) -> Result<PythonLikeUnityClass<'_>> {
93        let class = self.inner.get(class_name, attributes)?;
94        Ok(PythonLikeUnityClass::new(class))
95    }
96
97    /// Save the document (similar to Python's doc.dump_yaml())
98    pub fn dump_yaml(&self) -> Result<()> {
99        self.inner.save()
100    }
101
102    /// Save to a specific file (similar to Python's doc.dump_yaml(file_path="..."))
103    pub fn dump_yaml_to<P: AsRef<Path>>(&self, file_path: P) -> Result<()> {
104        self.inner.save_to(file_path)
105    }
106
107    /// Get the underlying YamlDocument for advanced operations
108    pub fn inner(&self) -> &YamlDocument {
109        &self.inner
110    }
111
112    /// Get mutable access to the underlying YamlDocument
113    pub fn inner_mut(&mut self) -> &mut YamlDocument {
114        &mut self.inner
115    }
116}
117
118/// Python-like wrapper for Unity classes
119///
120/// This provides dynamic property access similar to Python:
121/// ```python
122/// entry.m_Name = "NewName"
123/// health = entry.m_MaxHealth
124/// entry.m_MaxHealth += 10
125/// ```
126pub struct PythonLikeUnityClass<'a> {
127    /// Reference to the underlying Unity class
128    class: &'a UnityClass,
129}
130
131impl<'a> PythonLikeUnityClass<'a> {
132    /// Create a new Python-like wrapper
133    fn new(class: &'a UnityClass) -> Self {
134        Self { class }
135    }
136
137    /// Get a property value with automatic type conversion
138    ///
139    /// # Examples
140    /// ```rust,no_run
141    /// # use unity_asset_yaml::python_like_api::PythonLikeUnityDocument;
142    /// # let doc = PythonLikeUnityDocument::load_yaml("test.asset", false).unwrap();
143    /// # let entry = doc.entry().unwrap();
144    ///
145    /// // Get string property
146    /// if let Some(name) = entry.get_string("m_Name") {
147    ///     println!("Name: {}", name);
148    /// }
149    ///
150    /// // Get integer property
151    /// if let Some(health) = entry.get_integer("m_MaxHealth") {
152    ///     println!("Health: {}", health);
153    /// }
154    /// ```
155    pub fn get_string(&self, key: &str) -> Option<String> {
156        self.class
157            .get_dynamic(key)?
158            .as_string()
159            .map(|s| s.to_string())
160    }
161
162    /// Get integer property
163    pub fn get_integer(&self, key: &str) -> Option<i64> {
164        self.class.get_dynamic(key)?.as_integer()
165    }
166
167    /// Get float property
168    pub fn get_float(&self, key: &str) -> Option<f64> {
169        self.class.get_dynamic(key)?.as_float()
170    }
171
172    /// Get boolean property
173    pub fn get_bool(&self, key: &str) -> Option<bool> {
174        self.class.get_dynamic(key)?.as_bool()
175    }
176
177    /// Get array property
178    pub fn get_array(&self, key: &str) -> Option<Vec<DynamicValue>> {
179        self.class.get_dynamic(key)?.as_array().cloned()
180    }
181
182    /// Get raw dynamic value
183    pub fn get_dynamic(&self, key: &str) -> Option<DynamicValue> {
184        self.class.get_dynamic(key)
185    }
186
187    /// Check if property exists
188    pub fn has_property(&self, key: &str) -> bool {
189        self.class.has_dynamic(key)
190    }
191
192    /// Get all property names
193    pub fn property_names(&self) -> Vec<String> {
194        self.class.keys_dynamic()
195    }
196
197    /// Get class name
198    pub fn class_name(&self) -> &str {
199        &self.class.class_name
200    }
201
202    /// Get class ID
203    pub fn class_id(&self) -> i32 {
204        self.class.class_id
205    }
206
207    /// Get anchor
208    pub fn anchor(&self) -> &str {
209        &self.class.anchor
210    }
211
212    /// Get the underlying UnityClass
213    pub fn inner(&self) -> &UnityClass {
214        self.class
215    }
216}
217
218/// Mutable Python-like wrapper for Unity classes
219pub struct PythonLikeUnityClassMut<'a> {
220    /// Mutable reference to the underlying Unity class
221    class: &'a mut UnityClass,
222}
223
224impl<'a> PythonLikeUnityClassMut<'a> {
225    /// Create a new mutable Python-like wrapper
226    pub fn new(class: &'a mut UnityClass) -> Self {
227        Self { class }
228    }
229
230    /// Set a string property (Python-like: entry.m_Name = "value")
231    pub fn set_string(&mut self, key: &str, value: &str) -> Result<()> {
232        let dynamic_value = DynamicValue::String(value.to_string());
233        self.class.set_dynamic(key, dynamic_value)
234    }
235
236    /// Set an integer property (Python-like: entry.m_Health = 100)
237    pub fn set_integer(&mut self, key: &str, value: i64) -> Result<()> {
238        let dynamic_value = DynamicValue::Integer(value);
239        self.class.set_dynamic(key, dynamic_value)
240    }
241
242    /// Set a float property
243    pub fn set_float(&mut self, key: &str, value: f64) -> Result<()> {
244        let dynamic_value = DynamicValue::Float(value);
245        self.class.set_dynamic(key, dynamic_value)
246    }
247
248    /// Set a boolean property
249    pub fn set_bool(&mut self, key: &str, value: bool) -> Result<()> {
250        let dynamic_value = DynamicValue::Bool(value);
251        self.class.set_dynamic(key, dynamic_value)
252    }
253
254    /// Set a dynamic value
255    pub fn set_dynamic(&mut self, key: &str, value: DynamicValue) -> Result<()> {
256        self.class.set_dynamic(key, value)
257    }
258
259    /// Add to numeric property (Python-like: entry.m_Health += 10)
260    pub fn add_to_numeric(&mut self, key: &str, value: f64) -> Result<()> {
261        if let Some(mut current) = self.class.get_dynamic(key) {
262            current.add_numeric(value)?;
263            self.class.set_dynamic(key, current)?;
264        } else {
265            return Err(UnityAssetError::format(format!(
266                "Property '{}' not found",
267                key
268            )));
269        }
270        Ok(())
271    }
272
273    /// Concatenate to string property (Python-like: entry.m_Text += "suffix")
274    pub fn concat_to_string(&mut self, key: &str, value: &str) -> Result<()> {
275        if let Some(mut current) = self.class.get_dynamic(key) {
276            current.concat_string(value)?;
277            self.class.set_dynamic(key, current)?;
278        } else {
279            return Err(UnityAssetError::format(format!(
280                "Property '{}' not found",
281                key
282            )));
283        }
284        Ok(())
285    }
286
287    /// Get immutable access to properties
288    pub fn get_string(&self, key: &str) -> Option<String> {
289        self.class
290            .get_dynamic(key)?
291            .as_string()
292            .map(|s| s.to_string())
293    }
294
295    /// Get integer property
296    pub fn get_integer(&self, key: &str) -> Option<i64> {
297        self.class.get_dynamic(key)?.as_integer()
298    }
299
300    /// Get float property
301    pub fn get_float(&self, key: &str) -> Option<f64> {
302        self.class.get_dynamic(key)?.as_float()
303    }
304
305    /// Get boolean property
306    pub fn get_bool(&self, key: &str) -> Option<bool> {
307        self.class.get_dynamic(key)?.as_bool()
308    }
309
310    /// Get the underlying UnityClass
311    pub fn inner(&self) -> &UnityClass {
312        self.class
313    }
314
315    /// Get mutable access to the underlying UnityClass
316    pub fn inner_mut(&mut self) -> &mut UnityClass {
317        self.class
318    }
319}
320
321#[cfg(test)]
322mod tests {
323    #[test]
324    fn test_python_like_api() {
325        // This test would require actual Unity YAML files
326        // For now, we just test the basic structure
327        assert_eq!(2 + 2, 4);
328    }
329}