141 lines
4.1 KiB
Rust
141 lines
4.1 KiB
Rust
//! Unity YAML parsing module
|
|
|
|
pub mod meta;
|
|
mod unity_tag;
|
|
mod yaml;
|
|
|
|
pub use meta::{MetaFile, get_meta_path};
|
|
pub use unity_tag::{UnityTag, parse_unity_tag};
|
|
pub use yaml::split_yaml_documents;
|
|
|
|
use crate::property::convert_yaml_value;
|
|
use crate::{Error, Result, UnityDocument, UnityFile};
|
|
use std::path::Path;
|
|
|
|
/// Parse a Unity file from the given path
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```no_run
|
|
/// use cursebreaker_parser::parser::parse_unity_file;
|
|
/// use std::path::Path;
|
|
///
|
|
/// let file = parse_unity_file(Path::new("Scene.unity"))?;
|
|
/// println!("Found {} documents", file.documents.len());
|
|
/// # Ok::<(), cursebreaker_parser::Error>(())
|
|
/// ```
|
|
pub fn parse_unity_file(path: &Path) -> Result<UnityFile> {
|
|
// Read the file
|
|
let content = std::fs::read_to_string(path)?;
|
|
|
|
// Validate Unity header
|
|
validate_unity_header(&content, path)?;
|
|
|
|
// Split into individual YAML documents
|
|
let raw_documents = split_yaml_documents(&content)?;
|
|
|
|
// Parse each document
|
|
let mut documents = Vec::new();
|
|
for raw_doc in raw_documents {
|
|
if let Some(doc) = parse_document(&raw_doc)? {
|
|
documents.push(doc);
|
|
}
|
|
}
|
|
|
|
Ok(UnityFile {
|
|
path: path.to_path_buf(),
|
|
documents,
|
|
})
|
|
}
|
|
|
|
/// Validate that the file has a proper Unity YAML header
|
|
fn validate_unity_header(content: &str, path: &Path) -> Result<()> {
|
|
let has_yaml_header = content.starts_with("%YAML");
|
|
let has_unity_tag = content.contains("%TAG !u! tag:unity3d.com");
|
|
|
|
if !has_yaml_header || !has_unity_tag {
|
|
return Err(Error::MissingHeader(path.to_path_buf()));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Parse a single YAML document into a UnityDocument
|
|
fn parse_document(raw_doc: &str) -> Result<Option<UnityDocument>> {
|
|
// Parse the Unity tag line (e.g., "--- !u!1 &12345")
|
|
let tag = match parse_unity_tag(raw_doc) {
|
|
Some(tag) => tag,
|
|
None => return Ok(None), // Skip documents without Unity tags
|
|
};
|
|
|
|
// Extract the YAML content (everything after the tag line)
|
|
let yaml_content = extract_yaml_content(raw_doc);
|
|
|
|
// Parse the YAML content
|
|
let properties = if yaml_content.trim().is_empty() {
|
|
indexmap::IndexMap::new()
|
|
} else {
|
|
match serde_yaml::from_str::<serde_yaml::Value>(yaml_content) {
|
|
Ok(serde_yaml::Value::Mapping(map)) => {
|
|
// Convert to IndexMap with PropertyValue
|
|
map.into_iter()
|
|
.filter_map(|(k, v)| {
|
|
k.as_str().and_then(|s| {
|
|
convert_yaml_value(&v)
|
|
.ok()
|
|
.map(|pv| (s.to_string(), pv))
|
|
})
|
|
})
|
|
.collect()
|
|
}
|
|
Ok(_) => indexmap::IndexMap::new(),
|
|
Err(e) => return Err(Error::Yaml(e)),
|
|
}
|
|
};
|
|
|
|
// Get class name from the first key in properties or use "Unknown"
|
|
let class_name = properties
|
|
.keys()
|
|
.next()
|
|
.map(|s| s.to_string())
|
|
.unwrap_or_else(|| format!("UnityType{}", tag.type_id));
|
|
|
|
Ok(Some(UnityDocument {
|
|
type_id: tag.type_id,
|
|
file_id: crate::types::FileID::from_i64(tag.file_id),
|
|
class_name,
|
|
properties,
|
|
}))
|
|
}
|
|
|
|
/// Extract the YAML content from a raw document (skip the Unity tag line)
|
|
fn extract_yaml_content(raw_doc: &str) -> &str {
|
|
// Find the first newline after the "--- !u!" tag
|
|
if let Some(first_line_end) = raw_doc.find('\n') {
|
|
&raw_doc[first_line_end + 1..]
|
|
} else {
|
|
""
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_validate_unity_header() {
|
|
let valid_content = "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n";
|
|
assert!(validate_unity_header(valid_content, Path::new("test.unity")).is_ok());
|
|
|
|
let invalid_content = "Not a Unity file";
|
|
assert!(validate_unity_header(invalid_content, Path::new("test.unity")).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_yaml_content() {
|
|
let raw_doc = "--- !u!1 &12345\nGameObject:\n m_Name: Test";
|
|
let content = extract_yaml_content(raw_doc);
|
|
assert_eq!(content, "GameObject:\n m_Name: Test");
|
|
}
|
|
}
|