From 748c8a3353df887ee4b01e0e1327aa95c1c7225a Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Fri, 27 Feb 2026 06:16:23 +0800 Subject: Add remote flag to IndexSource and parsing support --- systems/sheet/src/mapping/parse.rs | 189 ++++++++++++++++++++++++ systems/sheet/src/mapping/parse_test.rs | 252 ++++++++++++++++++++++++++++++++ 2 files changed, 441 insertions(+) create mode 100644 systems/sheet/src/mapping/parse.rs create mode 100644 systems/sheet/src/mapping/parse_test.rs (limited to 'systems/sheet/src/mapping') diff --git a/systems/sheet/src/mapping/parse.rs b/systems/sheet/src/mapping/parse.rs new file mode 100644 index 0000000..e203c96 --- /dev/null +++ b/systems/sheet/src/mapping/parse.rs @@ -0,0 +1,189 @@ +use just_fmt::fmt_path::fmt_path_str; + +use crate::{ + index_source::IndexSource, + mapping::{LocalMapping, LocalMappingForward, error::ParseMappingError}, +}; + +impl TryFrom<&str> for LocalMapping { + type Error = ParseMappingError; + + fn try_from(s: &str) -> Result { + // Remove surrounding quotes if present + let s = s.trim_matches('"'); + + // Helper function to remove quotes from a string + fn remove_quotes(s: &str) -> String { + // Simply remove all quotes from the string + s.replace('"', "").trim().to_string() + } + + // Helper function to split by operator, handling both spaced and non-spaced versions + fn split_by_operator<'a>(s: &'a str, operator: &'a str) -> Vec<&'a str> { + let mut result = Vec::new(); + let mut start = 0; + + // Find all occurrences of the operator + let mut search_from = 0; + while let Some(pos) = s[search_from..].find(operator) { + let actual_pos = search_from + pos; + result.push(&s[start..actual_pos]); + start = actual_pos + operator.len(); + search_from = start; + } + + if start < s.len() { + result.push(&s[start..]); + } + + result + } + + // Helper function to find operator position + fn find_operator<'a>(s: &'a str, operator: &'a str) -> Option { + s.find(operator) + } + + // Try to parse "path" => "source" == "version" pattern + if let Some(arrow_pos) = find_operator(s, "=>") { + let after_arrow = &s[arrow_pos + 2..]; + if let Some(equal_pos) = find_operator(after_arrow, "==") { + // Format: "path" => "source" == "version" + let path = remove_quotes(s[..arrow_pos].trim()); + let middle_part = after_arrow[..equal_pos].trim(); + let version_part = after_arrow[equal_pos + 2..].trim(); + + let middle = remove_quotes(middle_part); + let version_part_str = remove_quotes(version_part); + + let val = fmt_path_str(path) + .map_err(|_| ParseMappingError::InvalidMapping)? + .split('/') + .map(|s| s.to_string()) + .collect(); + + let source = IndexSource::try_from(middle.as_str()) + .map_err(|_| ParseMappingError::InvalidMapping)?; + + let version = version_part_str + .parse::() + .map_err(|_| ParseMappingError::InvalidMapping)?; + + return Ok(LocalMapping { + val, + source, + forward: LocalMappingForward::Version { version }, + }); + } + } + + // Split by "=>" to parse the format + let parts = split_by_operator(s, "=>"); + + match parts.len() { + 1 => { + // Check for "==" operator + if let Some(equal_pos) = find_operator(s, "==") { + // Format: "path" == "source" (when mapped_version equals version) + let path_raw = s[..equal_pos].trim(); + let source_part_raw = s[equal_pos + 2..].trim(); + let path = remove_quotes(path_raw); + let source_str = remove_quotes(source_part_raw); + + let val = fmt_path_str(path) + .map_err(|_| ParseMappingError::InvalidMapping)? + .split('/') + .map(|s| s.to_string()) + .collect(); + + let source = IndexSource::try_from(source_str.as_str()) + .map_err(|_| ParseMappingError::InvalidMapping)?; + + let version = source.version(); + + Ok(LocalMapping { + val, + source, + forward: LocalMappingForward::Version { version }, + }) + } else { + Err(ParseMappingError::InvalidMapping) + } + } + 2 => { + // Check if the second part contains "==" + if let Some(equal_pos) = find_operator(parts[1], "==") { + // Format: "path" => "source" == "version" + let path = remove_quotes(parts[0].trim()); + let middle_part = parts[1][..equal_pos].trim(); + let version_part = parts[1][equal_pos + 2..].trim(); + + let middle = remove_quotes(middle_part); + let version_part_str = remove_quotes(version_part); + + let val = fmt_path_str(path) + .map_err(|_| ParseMappingError::InvalidMapping)? + .split('/') + .map(|s| s.to_string()) + .collect(); + + let source = IndexSource::try_from(middle.as_str()) + .map_err(|_| ParseMappingError::InvalidMapping)?; + + let version = version_part_str + .parse::() + .map_err(|_| ParseMappingError::InvalidMapping)?; + + return Ok(LocalMapping { + val, + source, + forward: LocalMappingForward::Version { version }, + }); + } + + // Format: "path" => "source" + let path = remove_quotes(parts[0].trim()); + let source_str = remove_quotes(parts[1].trim()); + + let val = fmt_path_str(path) + .map_err(|_| ParseMappingError::InvalidMapping)? + .split('/') + .map(|s| s.to_string()) + .collect(); + + let source = IndexSource::try_from(source_str.as_str()) + .map_err(|_| ParseMappingError::InvalidMapping)?; + + Ok(LocalMapping { + val, + source, + forward: LocalMappingForward::Latest, + }) + } + 3 => { + // Format: "path" => "source" => "sheet_name" + let path = remove_quotes(parts[0].trim()); + let source_str = remove_quotes(parts[1].trim()); + let sheet_name = remove_quotes(parts[2].trim()); + + let val = fmt_path_str(path) + .map_err(|_| ParseMappingError::InvalidMapping)? + .split('/') + .map(|s| s.to_string()) + .collect(); + + let source = IndexSource::try_from(source_str.as_str()) + .map_err(|_| ParseMappingError::InvalidMapping)?; + + Ok(LocalMapping { + val, + source, + forward: LocalMappingForward::Ref { + sheet_name: sheet_name, + }, + }) + } + _ => Err(ParseMappingError::InvalidMapping), + } + } +} diff --git a/systems/sheet/src/mapping/parse_test.rs b/systems/sheet/src/mapping/parse_test.rs new file mode 100644 index 0000000..a411360 --- /dev/null +++ b/systems/sheet/src/mapping/parse_test.rs @@ -0,0 +1,252 @@ +#[cfg(test)] +mod tests { + use crate::{ + index_source::IndexSource, + mapping::{LocalMapping, LocalMappingForward}, + }; + + /// Helper macro for comparing two LocalMapping instances + /// Checks equality of the mappings themselves, their forward fields, and their index sources + macro_rules! mapping_eq { + ($a:expr, $b:expr) => { + assert_eq!($a, $b); + assert_eq!($a.forward, $b.forward); + assert_eq!($a.index_source(), $b.index_source()); + }; + } + + #[test] + fn test_local_mapping_parse() { + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(true, 1u32, 2u16), + LocalMappingForward::Latest, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\" => \"1/2\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(true, 1u32, 2u16), + LocalMappingForward::Version { version: 2u16 }, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\" == \"1/2\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(true, 1u32, 2u16), + LocalMappingForward::Ref { + sheet_name: "ref".to_string(), + }, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\" => \"1/2\" => \"ref\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(true, 1u32, 2u16), + LocalMappingForward::Version { version: 3u16 }, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\" => \"1/2\" == \"3\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(false, 1u32, 2u16), + LocalMappingForward::Latest, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\" => \"~1/2\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(false, 1u32, 2u16), + LocalMappingForward::Version { version: 2u16 }, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\" == \"~1/2\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(false, 1u32, 2u16), + LocalMappingForward::Ref { + sheet_name: "ref".to_string(), + }, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\" => \"~1/2\" => \"ref\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(false, 1u32, 2u16), + LocalMappingForward::Version { version: 3u16 }, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\" => \"~1/2\" == \"3\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(true, 1u32, 2u16), + LocalMappingForward::Latest, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\"=>\"1/2\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(true, 1u32, 2u16), + LocalMappingForward::Version { version: 2u16 }, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\"==\"1/2\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(true, 1u32, 2u16), + LocalMappingForward::Ref { + sheet_name: "ref".to_string(), + }, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\"=>\"1/2\"=>\"ref\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(true, 1u32, 2u16), + LocalMappingForward::Version { version: 3u16 }, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\"=>\"1/2\"==\"3\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(false, 1u32, 2u16), + LocalMappingForward::Latest, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\"=>\"~1/2\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(false, 1u32, 2u16), + LocalMappingForward::Version { version: 2u16 }, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\"==\"~1/2\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(false, 1u32, 2u16), + LocalMappingForward::Ref { + sheet_name: "ref".to_string(), + }, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\"=>\"~1/2\"=>\"ref\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(false, 1u32, 2u16), + LocalMappingForward::Version { version: 3u16 }, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\"=>\"~1/2\"==\"3\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + // + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(true, 1u32, 2u16), + LocalMappingForward::Latest, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\" =>\"1/2\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(true, 1u32, 2u16), + LocalMappingForward::Version { version: 2u16 }, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\"== \"1/2\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(true, 1u32, 2u16), + LocalMappingForward::Ref { + sheet_name: "ref".to_string(), + }, + ) + .unwrap(); + let local_mapping_gen = + LocalMapping::try_from("\"A.png\" => \"1/2\" =>\"ref\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(true, 1u32, 2u16), + LocalMappingForward::Version { version: 3u16 }, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\"=> \"1/2\" ==\"3\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(false, 1u32, 2u16), + LocalMappingForward::Latest, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from(" \"A.png\"=>\"~1/2\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(false, 1u32, 2u16), + LocalMappingForward::Version { version: 2u16 }, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\" ==\"~1/2\" ").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(false, 1u32, 2u16), + LocalMappingForward::Ref { + sheet_name: "ref".to_string(), + }, + ) + .unwrap(); + let local_mapping_gen = + LocalMapping::try_from("\"A.png\"=> \"~1/2\"=> \"ref\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + + let local_mapping = LocalMapping::new( + vec!["A.png".to_string()], + IndexSource::new(false, 1u32, 2u16), + LocalMappingForward::Version { version: 3u16 }, + ) + .unwrap(); + let local_mapping_gen = LocalMapping::try_from("\"A.png\"=> \"~1/2\" ==\"3\"").unwrap(); + mapping_eq!(local_mapping, local_mapping_gen); + } +} -- cgit