.meta parsing
This commit is contained in:
@@ -6,7 +6,8 @@
|
|||||||
"Bash(cargo test:*)",
|
"Bash(cargo test:*)",
|
||||||
"Bash(cargo run:*)",
|
"Bash(cargo run:*)",
|
||||||
"Bash(cargo tree:*)",
|
"Bash(cargo tree:*)",
|
||||||
"WebFetch(domain:docs.rs)"
|
"WebFetch(domain:docs.rs)",
|
||||||
|
"Bash(findstr:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
68
ROADMAP.md
68
ROADMAP.md
@@ -122,54 +122,54 @@ This roadmap breaks down the development into 5 phases, each building on the pre
|
|||||||
### Tasks
|
### Tasks
|
||||||
|
|
||||||
1. **Reference Types**
|
1. **Reference Types**
|
||||||
- [ ] Implement `FileReference` struct (fileID + optional GUID)
|
- [x] Implement `FileReference` struct (fileID + optional GUID)
|
||||||
- [ ] Implement `LocalReference` (within-file references)
|
- [x] Implement `LocalReference` (within-file references)
|
||||||
- [ ] Implement `ExternalReference` (cross-file GUID references)
|
- [x] Implement `ExternalReference` (cross-file GUID references)
|
||||||
- [ ] Add reference equality and comparison
|
- [x] Add reference equality and comparison
|
||||||
|
|
||||||
2. **Type ID Mapping**
|
2. **Type ID Mapping**
|
||||||
- [ ] Create Unity type ID → class name mapping
|
- [x] Create Unity type ID → class name mapping
|
||||||
- [ ] Common types: GameObject(1), Transform(4), MonoBehaviour(114), etc.
|
- [x] Common types: GameObject(1), Transform(4), MonoBehaviour(114), etc.
|
||||||
- [ ] Load type mappings from data file or hardcode common ones
|
- [x] Load type mappings from data file or hardcode common ones
|
||||||
- [ ] Support unknown type IDs gracefully
|
- [x] Support unknown type IDs gracefully
|
||||||
|
|
||||||
3. **Reference Resolution**
|
3. **Reference Resolution**
|
||||||
- [ ] Implement within-file reference resolution
|
- [x] Implement within-file reference resolution
|
||||||
- [ ] Cache resolved references for performance
|
- [x] Cache resolved references for performance
|
||||||
- [ ] Handle cyclic references safely
|
- [x] Handle cyclic references safely
|
||||||
- [ ] Detect and report broken references
|
- [x] Detect and report broken references
|
||||||
|
|
||||||
4. **UnityProject Multi-File Support**
|
4. **UnityProject Multi-File Support**
|
||||||
- [ ] Implement `UnityProject` struct
|
- [x] Implement `UnityProject` struct
|
||||||
- [ ] Load multiple Unity files into project
|
- [x] Load multiple Unity files into project
|
||||||
- [ ] Build file ID → document index
|
- [x] Build file ID → document index
|
||||||
- [ ] Cross-file reference resolution (GUID-based)
|
- [x] Cross-file reference resolution (GUID-based)
|
||||||
|
|
||||||
5. **Query Helpers**
|
5. **Query Helpers**
|
||||||
- [ ] Find object by file ID
|
- [x] Find object by file ID
|
||||||
- [ ] Find objects by type
|
- [x] Find objects by type
|
||||||
- [ ] Find objects by name
|
- [x] Find objects by name
|
||||||
- [ ] Get component from GameObject
|
- [x] Get component from GameObject
|
||||||
- [ ] Follow reference chains
|
- [x] Follow reference chains
|
||||||
|
|
||||||
6. **Testing**
|
6. **Testing**
|
||||||
- [ ] Test reference resolution within single file
|
- [x] Test reference resolution within single file
|
||||||
- [ ] Test cross-file references (scene → prefab)
|
- [x] Test cross-file references (scene → prefab)
|
||||||
- [ ] Test broken reference handling
|
- [x] Test broken reference handling
|
||||||
- [ ] Test circular reference detection
|
- [x] Test circular reference detection
|
||||||
|
|
||||||
### Deliverables
|
### Deliverables
|
||||||
- [ ] ✓ All references within files resolved correctly
|
- [x] ✓ All references within files resolved correctly
|
||||||
- [ ] ✓ Type ID system working with common Unity types
|
- [x] ✓ Type ID system working with common Unity types
|
||||||
- [ ] ✓ UnityProject can load and query multiple files
|
- [x] ✓ UnityProject can load and query multiple files
|
||||||
- [ ] ✓ Query API functional
|
- [x] ✓ Query API functional
|
||||||
|
|
||||||
### Success Criteria
|
### Success Criteria
|
||||||
- [ ] Load entire PiratePanic/Scenes/ directory
|
- [x] Load entire PiratePanic/Scenes/ directory
|
||||||
- [ ] Resolve all GameObject → Component references
|
- [x] Resolve all GameObject → Component references
|
||||||
- [ ] Resolve prefab references from scenes
|
- [x] Resolve prefab references from scenes
|
||||||
- [ ] Find objects by name across entire project
|
- [x] Find objects by name across entire project
|
||||||
- [ ] Handle missing references gracefully
|
- [x] Handle missing references gracefully
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
121
examples/guid_resolution.rs
Normal file
121
examples/guid_resolution.rs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
//! Example demonstrating GUID resolution with .meta files
|
||||||
|
//!
|
||||||
|
//! This example shows how to:
|
||||||
|
//! - Load Unity files with their .meta files
|
||||||
|
//! - Access GUID to path mappings
|
||||||
|
//! - Resolve cross-file references using GUIDs
|
||||||
|
//!
|
||||||
|
//! Run with: cargo run --example guid_resolution
|
||||||
|
|
||||||
|
use cursebreaker_parser::{UnityProject, MetaFile};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
println!("Unity GUID Resolution Example");
|
||||||
|
println!("==============================\n");
|
||||||
|
|
||||||
|
// Create a new Unity project with LRU cache
|
||||||
|
let mut project = UnityProject::new(1000);
|
||||||
|
|
||||||
|
// Example 1: Parse a .meta file directly
|
||||||
|
println!("Example 1: Parsing a .meta file");
|
||||||
|
println!("---------------------------------");
|
||||||
|
|
||||||
|
let meta_content = r#"
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4ab6bfb0ff54cdf4c8dd38ca244d6f15
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let meta = MetaFile::from_str(meta_content)?;
|
||||||
|
println!("Parsed GUID: {}", meta.guid());
|
||||||
|
println!("File format version: {:?}\n", meta.file_format_version());
|
||||||
|
|
||||||
|
// Example 2: Load Unity files with automatic .meta parsing
|
||||||
|
println!("Example 2: Loading Unity files with .meta files");
|
||||||
|
println!("-------------------------------------------------");
|
||||||
|
|
||||||
|
let test_dir = "data/tests/unity-sampleproject/PiratePanic/Assets/PiratePanic/Prefabs/Menu/Battle/Hand";
|
||||||
|
|
||||||
|
if Path::new(test_dir).exists() {
|
||||||
|
// Load all Unity files in the directory
|
||||||
|
let loaded_files = project.load_directory(test_dir)?;
|
||||||
|
|
||||||
|
println!("Loaded {} Unity files", loaded_files.len());
|
||||||
|
println!("Found {} GUID mappings\n", project.guid_mappings().len());
|
||||||
|
|
||||||
|
// Example 3: Inspect GUID mappings
|
||||||
|
println!("Example 3: GUID to Path Mappings");
|
||||||
|
println!("---------------------------------");
|
||||||
|
|
||||||
|
for (guid, path) in project.guid_mappings().iter().take(5) {
|
||||||
|
println!("GUID: {} -> {:?}", guid, path.file_name().unwrap());
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
// Example 4: Look up a file by GUID
|
||||||
|
println!("Example 4: Looking up files by GUID");
|
||||||
|
println!("------------------------------------");
|
||||||
|
|
||||||
|
if let Some((sample_guid, _)) = project.guid_mappings().iter().next() {
|
||||||
|
if let Some(path) = project.get_path_by_guid(sample_guid) {
|
||||||
|
println!("GUID {} resolves to:", sample_guid);
|
||||||
|
println!(" Path: {:?}", path);
|
||||||
|
|
||||||
|
// Get the file
|
||||||
|
if let Some(file) = project.get_file(path) {
|
||||||
|
println!(" Documents: {}", file.documents.len());
|
||||||
|
|
||||||
|
// Show the first GameObject
|
||||||
|
for doc in &file.documents {
|
||||||
|
if doc.is_game_object() {
|
||||||
|
if let Some(obj) = doc.get("GameObject").and_then(|v| v.as_object()) {
|
||||||
|
if let Some(name) = obj.get("m_Name").and_then(|v| v.as_str()) {
|
||||||
|
println!(" Contains GameObject: {}", name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
// Example 5: Cross-file reference resolution (when available)
|
||||||
|
println!("Example 5: Cross-file Reference Resolution");
|
||||||
|
println!("-------------------------------------------");
|
||||||
|
|
||||||
|
// Find all external references in loaded files
|
||||||
|
let mut external_ref_count = 0;
|
||||||
|
|
||||||
|
for file in project.files().values() {
|
||||||
|
for doc in &file.documents {
|
||||||
|
// Scan properties for external references
|
||||||
|
for value in doc.properties.values() {
|
||||||
|
if let Some(ext_ref) = value.as_external_ref() {
|
||||||
|
external_ref_count += 1;
|
||||||
|
|
||||||
|
// Try to resolve this GUID
|
||||||
|
if let Some(target_path) = project.get_path_by_guid(&ext_ref.guid) {
|
||||||
|
println!("✓ External reference resolved:");
|
||||||
|
println!(" GUID: {}", ext_ref.guid);
|
||||||
|
println!(" Target: {:?}", target_path.file_name().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\nFound {} external references in loaded files", external_ref_count);
|
||||||
|
} else {
|
||||||
|
println!("Test data directory not found: {}", test_dir);
|
||||||
|
println!("This example works best with Unity sample project files.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ pub mod types;
|
|||||||
pub use ecs::{build_world_from_project, GameObjectComponent, WorldBuilder};
|
pub use ecs::{build_world_from_project, GameObjectComponent, WorldBuilder};
|
||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
pub use model::{UnityDocument, UnityFile};
|
pub use model::{UnityDocument, UnityFile};
|
||||||
pub use parser::parse_unity_file;
|
pub use parser::{meta::MetaFile, parse_unity_file};
|
||||||
pub use project::UnityProject;
|
pub use project::UnityProject;
|
||||||
pub use property::PropertyValue;
|
pub use property::PropertyValue;
|
||||||
pub use types::{
|
pub use types::{
|
||||||
|
|||||||
213
src/parser/meta.rs
Normal file
213
src/parser/meta.rs
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
//! Unity .meta file parser
|
||||||
|
//!
|
||||||
|
//! Unity creates .meta files alongside assets to store metadata including
|
||||||
|
//! the unique GUID that identifies each asset.
|
||||||
|
|
||||||
|
use crate::{Error, Result};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// Represents a Unity .meta file
|
||||||
|
///
|
||||||
|
/// .meta files contain metadata about Unity assets, most importantly
|
||||||
|
/// the GUID which uniquely identifies the asset across the project.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct MetaFile {
|
||||||
|
/// The unique GUID for this asset
|
||||||
|
pub guid: String,
|
||||||
|
|
||||||
|
/// The file format version
|
||||||
|
pub file_format_version: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetaFile {
|
||||||
|
/// Parse a Unity .meta file from the given path
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `path` - Path to the .meta file
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use cursebreaker_parser::parser::meta::MetaFile;
|
||||||
|
///
|
||||||
|
/// let meta = MetaFile::from_path("Assets/Prefabs/Player.prefab.meta")?;
|
||||||
|
/// println!("GUID: {}", meta.guid);
|
||||||
|
/// # Ok::<(), cursebreaker_parser::Error>(())
|
||||||
|
/// ```
|
||||||
|
pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let content = std::fs::read_to_string(path)?;
|
||||||
|
Self::from_str(&content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a Unity .meta file from a string
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `content` - The YAML content of the .meta file
|
||||||
|
pub fn from_str(content: &str) -> Result<Self> {
|
||||||
|
// Parse as YAML
|
||||||
|
let yaml: serde_yaml::Value = serde_yaml::from_str(content)?;
|
||||||
|
|
||||||
|
// Extract GUID (required field)
|
||||||
|
let guid = yaml
|
||||||
|
.get("guid")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.ok_or_else(|| Error::invalid_format("Missing 'guid' field in .meta file"))?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
// Extract file format version (optional)
|
||||||
|
let file_format_version = yaml
|
||||||
|
.get("fileFormatVersion")
|
||||||
|
.and_then(|v| v.as_i64());
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
guid,
|
||||||
|
file_format_version,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the GUID from this .meta file
|
||||||
|
pub fn guid(&self) -> &str {
|
||||||
|
&self.guid
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the file format version
|
||||||
|
pub fn file_format_version(&self) -> Option<i64> {
|
||||||
|
self.file_format_version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the .meta file path for a given asset path
|
||||||
|
///
|
||||||
|
/// Unity creates .meta files alongside assets with the .meta extension.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `asset_path` - Path to the Unity asset
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use cursebreaker_parser::parser::meta::get_meta_path;
|
||||||
|
/// use std::path::PathBuf;
|
||||||
|
///
|
||||||
|
/// let asset = PathBuf::from("Assets/Scenes/MainMenu.unity");
|
||||||
|
/// let meta = get_meta_path(&asset);
|
||||||
|
/// assert_eq!(meta, PathBuf::from("Assets/Scenes/MainMenu.unity.meta"));
|
||||||
|
/// ```
|
||||||
|
pub fn get_meta_path(asset_path: &Path) -> std::path::PathBuf {
|
||||||
|
let mut meta_path = asset_path.to_path_buf();
|
||||||
|
let mut filename = meta_path
|
||||||
|
.file_name()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_os_string();
|
||||||
|
filename.push(".meta");
|
||||||
|
meta_path.set_file_name(filename);
|
||||||
|
meta_path
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_meta_file() {
|
||||||
|
let content = r#"
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4ab6bfb0ff54cdf4c8dd38ca244d6f15
|
||||||
|
TextureImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let meta = MetaFile::from_str(content).unwrap();
|
||||||
|
assert_eq!(meta.guid(), "4ab6bfb0ff54cdf4c8dd38ca244d6f15");
|
||||||
|
assert_eq!(meta.file_format_version(), Some(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_meta_file_minimal() {
|
||||||
|
let content = r#"
|
||||||
|
guid: abc123def456
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let meta = MetaFile::from_str(content).unwrap();
|
||||||
|
assert_eq!(meta.guid(), "abc123def456");
|
||||||
|
assert_eq!(meta.file_format_version(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_meta_file_missing_guid() {
|
||||||
|
let content = r#"
|
||||||
|
fileFormatVersion: 2
|
||||||
|
TextureImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = MetaFile::from_str(content);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_meta_path() {
|
||||||
|
let asset = PathBuf::from("Assets/Scenes/MainMenu.unity");
|
||||||
|
let meta = get_meta_path(&asset);
|
||||||
|
assert_eq!(meta, PathBuf::from("Assets/Scenes/MainMenu.unity.meta"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_meta_path_prefab() {
|
||||||
|
let asset = PathBuf::from("Assets/Prefabs/Player.prefab");
|
||||||
|
let meta = get_meta_path(&asset);
|
||||||
|
assert_eq!(meta, PathBuf::from("Assets/Prefabs/Player.prefab.meta"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_meta_path_asset() {
|
||||||
|
let asset = PathBuf::from("Assets/ScriptableObjects/Config.asset");
|
||||||
|
let meta = get_meta_path(&asset);
|
||||||
|
assert_eq!(meta, PathBuf::from("Assets/ScriptableObjects/Config.asset.meta"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_real_meta_file() {
|
||||||
|
// This tests with a realistic Unity .meta file structure
|
||||||
|
let content = r#"
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 06560ff19ed0c0b43918260dee8775dd
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let meta = MetaFile::from_str(content).unwrap();
|
||||||
|
assert_eq!(meta.guid(), "06560ff19ed0c0b43918260dee8775dd");
|
||||||
|
assert_eq!(meta.file_format_version(), Some(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_script_meta_file() {
|
||||||
|
let content = r#"
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b12cf7f429956b944a0d0e4b85516679
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let meta = MetaFile::from_str(content).unwrap();
|
||||||
|
assert_eq!(meta.guid(), "b12cf7f429956b944a0d0e4b85516679");
|
||||||
|
assert_eq!(meta.file_format_version(), Some(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
//! Unity YAML parsing module
|
//! Unity YAML parsing module
|
||||||
|
|
||||||
|
pub mod meta;
|
||||||
mod unity_tag;
|
mod unity_tag;
|
||||||
mod yaml;
|
mod yaml;
|
||||||
|
|
||||||
|
pub use meta::{MetaFile, get_meta_path};
|
||||||
pub use unity_tag::{UnityTag, parse_unity_tag};
|
pub use unity_tag::{UnityTag, parse_unity_tag};
|
||||||
pub use yaml::split_yaml_documents;
|
pub use yaml::split_yaml_documents;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
mod query;
|
mod query;
|
||||||
|
|
||||||
|
use crate::parser::meta::{get_meta_path, MetaFile};
|
||||||
use crate::{FileID, Result, UnityDocument, UnityFile, UnityReference};
|
use crate::{FileID, Result, UnityDocument, UnityFile, UnityReference};
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -102,8 +103,22 @@ impl UnityProject {
|
|||||||
.insert(doc.file_id, (path.clone(), idx));
|
.insert(doc.file_id, (path.clone(), idx));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Extract GUID from .meta file for guid_to_path mapping
|
// Extract GUID from .meta file for guid_to_path mapping
|
||||||
// For now, we skip GUID mapping as it requires .meta file parsing
|
let meta_path = get_meta_path(&path);
|
||||||
|
if meta_path.exists() {
|
||||||
|
match MetaFile::from_path(&meta_path) {
|
||||||
|
Ok(meta_file) => {
|
||||||
|
self.guid_to_path.insert(meta_file.guid, path.clone());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// Log warning but continue (graceful degradation)
|
||||||
|
eprintln!("Warning: Failed to parse .meta file {:?}: {}", meta_path, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Log warning if .meta file doesn't exist
|
||||||
|
eprintln!("Warning: .meta file not found for {:?}", path);
|
||||||
|
}
|
||||||
|
|
||||||
self.files.insert(path, file);
|
self.files.insert(path, file);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -320,6 +335,42 @@ impl UnityProject {
|
|||||||
pub fn cache_limit(&self) -> usize {
|
pub fn cache_limit(&self) -> usize {
|
||||||
self.cache_limit
|
self.cache_limit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the GUID to path mappings
|
||||||
|
///
|
||||||
|
/// Returns a reference to the map of GUIDs to file paths.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use cursebreaker_parser::UnityProject;
|
||||||
|
///
|
||||||
|
/// let project = UnityProject::new(100);
|
||||||
|
/// println!("Project has {} GUID mappings", project.guid_mappings().len());
|
||||||
|
/// ```
|
||||||
|
pub fn guid_mappings(&self) -> &HashMap<String, PathBuf> {
|
||||||
|
&self.guid_to_path
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the path for a specific GUID
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `guid` - The GUID to look up
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use cursebreaker_parser::UnityProject;
|
||||||
|
///
|
||||||
|
/// let project = UnityProject::new(100);
|
||||||
|
/// if let Some(path) = project.get_path_by_guid("4ab6bfb0ff54cdf4c8dd38ca244d6f15") {
|
||||||
|
/// println!("Found asset at: {:?}", path);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn get_path_by_guid(&self, guid: &str) -> Option<&PathBuf> {
|
||||||
|
self.guid_to_path.get(guid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -423,4 +474,39 @@ mod tests {
|
|||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert!(result.unwrap().is_none());
|
assert!(result.unwrap().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_guid_mappings() {
|
||||||
|
let mut project = UnityProject::new(100);
|
||||||
|
let path = "data/tests/unity-sampleproject/PiratePanic/Assets/PiratePanic/Prefabs/Menu/Battle/Hand/CardGrabber.prefab";
|
||||||
|
|
||||||
|
if Path::new(path).exists() {
|
||||||
|
project.load_file(path).unwrap();
|
||||||
|
|
||||||
|
// Check if GUID mappings were loaded (depends on .meta file existence)
|
||||||
|
let guid_count = project.guid_mappings().len();
|
||||||
|
if guid_count > 0 {
|
||||||
|
println!("Found {} GUID mappings", guid_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_path_by_guid() {
|
||||||
|
let mut project = UnityProject::new(100);
|
||||||
|
let dir = "data/tests/unity-sampleproject/PiratePanic/Assets/PiratePanic/Prefabs/Menu/Battle/Hand";
|
||||||
|
|
||||||
|
if Path::new(dir).exists() {
|
||||||
|
let loaded = project.load_directory(dir).unwrap();
|
||||||
|
|
||||||
|
// If we loaded files, check if we can look up by GUID
|
||||||
|
if !loaded.is_empty() && !project.guid_mappings().is_empty() {
|
||||||
|
// Get the first GUID
|
||||||
|
if let Some((guid, expected_path)) = project.guid_mappings().iter().next() {
|
||||||
|
let found_path = project.get_path_by_guid(guid);
|
||||||
|
assert_eq!(found_path, Some(expected_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
83
tests/test_guid_resolution.rs
Normal file
83
tests/test_guid_resolution.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
//! Integration test for GUID resolution with .meta files
|
||||||
|
|
||||||
|
use cursebreaker_parser::parser::meta::{get_meta_path, MetaFile};
|
||||||
|
use cursebreaker_parser::UnityProject;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_meta_file_parsing() {
|
||||||
|
// Test parsing a .meta file directly
|
||||||
|
let meta_content = r#"
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4ab6bfb0ff54cdf4c8dd38ca244d6f15
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let meta = MetaFile::from_str(meta_content).unwrap();
|
||||||
|
assert_eq!(meta.guid(), "4ab6bfb0ff54cdf4c8dd38ca244d6f15");
|
||||||
|
assert_eq!(meta.file_format_version(), Some(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_meta_path_generation() {
|
||||||
|
let asset_path = Path::new("Assets/Prefabs/Player.prefab");
|
||||||
|
let meta_path = get_meta_path(asset_path);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
meta_path,
|
||||||
|
Path::new("Assets/Prefabs/Player.prefab.meta")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_guid_resolution_in_project() {
|
||||||
|
let mut project = UnityProject::new(100);
|
||||||
|
let test_dir = "data/tests/unity-sampleproject/PiratePanic/Assets/PiratePanic/Prefabs/Menu/Battle/Hand";
|
||||||
|
|
||||||
|
if Path::new(test_dir).exists() {
|
||||||
|
// Load all files in the directory
|
||||||
|
let loaded_files = project.load_directory(test_dir).unwrap();
|
||||||
|
|
||||||
|
if !loaded_files.is_empty() {
|
||||||
|
println!("Loaded {} files", loaded_files.len());
|
||||||
|
println!(
|
||||||
|
"Found {} GUID mappings",
|
||||||
|
project.guid_mappings().len()
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we have GUID mappings, test that we can look them up
|
||||||
|
for (guid, path) in project.guid_mappings() {
|
||||||
|
let found_path = project.get_path_by_guid(guid);
|
||||||
|
assert_eq!(found_path, Some(path));
|
||||||
|
println!("GUID {} -> {:?}", guid, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cross_file_reference_resolution() {
|
||||||
|
let mut project = UnityProject::new(100);
|
||||||
|
let test_dir = "data/tests/unity-sampleproject/PiratePanic/Assets/PiratePanic";
|
||||||
|
|
||||||
|
if Path::new(test_dir).exists() {
|
||||||
|
// Load multiple directories to enable cross-file resolution
|
||||||
|
let _ = project.load_directory(test_dir);
|
||||||
|
|
||||||
|
let guid_count = project.guid_mappings().len();
|
||||||
|
let file_count = project.files().len();
|
||||||
|
|
||||||
|
println!("Loaded {} files with {} GUID mappings", file_count, guid_count);
|
||||||
|
|
||||||
|
// Verify we can look up files by GUID
|
||||||
|
if guid_count > 0 {
|
||||||
|
let sample_guid = project.guid_mappings().keys().next().unwrap();
|
||||||
|
let path = project.get_path_by_guid(sample_guid);
|
||||||
|
assert!(path.is_some(), "Should be able to look up GUID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user