#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShapePrefix {
NormalTable,
Heatmap,
Transposed,
Delta,
ColorCoded,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum TableType {
Normal, Heatmap, Transposed, }
pub fn classify_shape_name(name: &str) -> Option<ShapePrefix> {
if name.contains("ntbl_") {
Some(ShapePrefix::NormalTable)
} else if name.contains("htmp_") {
Some(ShapePrefix::Heatmap)
} else if name.contains("trns_") {
Some(ShapePrefix::Transposed)
} else if delta_set(name).is_some() {
Some(ShapePrefix::Delta)
} else if name.contains("_ccst") {
Some(ShapePrefix::ColorCoded)
} else {
None
}
}
pub fn prefix_to_table_type(prefix: ShapePrefix) -> Option<TableType> {
match prefix {
ShapePrefix::NormalTable => Some(TableType::Normal),
ShapePrefix::Heatmap => Some(TableType::Heatmap),
ShapePrefix::Transposed => Some(TableType::Transposed),
_ => None,
}
}
pub fn is_exact_token_match(shape_name: &str, linked_name: &str) -> bool {
if linked_name.is_empty() {
return false;
}
let linked_len = linked_name.len();
let name_len = shape_name.len();
let mut pos = 0;
while let Some(found) = shape_name[pos..].find(linked_name) {
let abs_pos = pos + found;
let before_ok = abs_pos == 0 || !shape_name.as_bytes()[abs_pos - 1].is_ascii_alphanumeric();
let end_pos = abs_pos + linked_len;
let after_ok = end_pos >= name_len || !shape_name.as_bytes()[end_pos].is_ascii_alphanumeric();
if before_ok && after_ok {
return true;
}
pos = abs_pos + 1;
if pos >= name_len {
break;
}
}
false
}
pub fn strip_sign_suffix(name: &str) -> &str {
for suffix in &["_pos", "_neg", "_none"] {
if let Some(stripped) = name.strip_suffix(suffix) {
return stripped;
}
}
name
}
pub fn delta_set(name: &str) -> Option<u32> {
let bytes = name.as_bytes();
let mut search = 0;
while let Some(found) = name[search..].find("delt") {
let start = search + found + 4; let digits_end = bytes[start..]
.iter()
.position(|b| !b.is_ascii_digit())
.map_or(bytes.len(), |p| start + p);
if bytes.get(digits_end) == Some(&b'_') {
let digits = &name[start..digits_end];
if digits.is_empty() {
return Some(1);
}
if let Ok(n) = digits.parse::<u32>()
&& n >= 1
{
return Some(n);
}
}
search = search + found + 1;
if search >= bytes.len() {
break;
}
}
None
}
pub fn template_name_for_set(base: &str, set: u32, sign: &str) -> String {
if set <= 1 {
return base.to_string();
}
match base.strip_prefix("tmpl_") {
Some(rest) => format!("tmpl{set}_{rest}"),
None => format!("tmpl{set}_delta_{sign}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_classify_ntbl() {
assert_eq!(classify_shape_name("ntbl_Revenue"), Some(ShapePrefix::NormalTable));
}
#[test]
fn test_classify_htmp() {
assert_eq!(classify_shape_name("htmp_Scores"), Some(ShapePrefix::Heatmap));
}
#[test]
fn test_classify_trns() {
assert_eq!(classify_shape_name("trns_Matrix"), Some(ShapePrefix::Transposed));
}
#[test]
fn test_classify_delt() {
assert_eq!(classify_shape_name("delt_Growth_pos"), Some(ShapePrefix::Delta));
}
#[test]
fn test_classify_ccst() {
assert_eq!(classify_shape_name("table_ccst"), Some(ShapePrefix::ColorCoded));
}
#[test]
fn test_classify_unknown() {
assert_eq!(classify_shape_name("regular_shape"), None);
}
#[test]
fn test_classify_priority_ntbl_over_htmp() {
assert_eq!(classify_shape_name("ntbl_htmp_test"), Some(ShapePrefix::NormalTable));
}
#[test]
fn test_token_match_exact() {
assert!(is_exact_token_match("ntbl_Revenue", "Revenue"));
}
#[test]
fn test_token_match_middle() {
assert!(is_exact_token_match("ntbl_Revenue_Q4", "Revenue"));
}
#[test]
fn test_token_no_match_partial() {
assert!(!is_exact_token_match("ntbl_RevenueTotal", "Revenue"));
}
#[test]
fn test_token_no_match_partial_before() {
assert!(!is_exact_token_match("ntbl_TotalRevenue", "Revenue"));
}
#[test]
fn test_token_match_at_start() {
assert!(is_exact_token_match("Revenue_table", "Revenue"));
}
#[test]
fn test_token_match_at_end() {
assert!(is_exact_token_match("ntbl_Revenue", "Revenue"));
}
#[test]
fn test_token_match_whole_string() {
assert!(is_exact_token_match("Revenue", "Revenue"));
}
#[test]
fn test_token_match_underscore_boundary() {
assert!(is_exact_token_match("data_Revenue_2024", "Revenue"));
}
#[test]
fn test_token_match_hyphen_boundary() {
assert!(is_exact_token_match("data-Revenue-2024", "Revenue"));
}
#[test]
fn test_token_match_space_boundary() {
assert!(is_exact_token_match("data Revenue 2024", "Revenue"));
}
#[test]
fn test_token_no_match_empty_linked() {
assert!(!is_exact_token_match("ntbl_Revenue", ""));
}
#[test]
fn test_token_no_match_not_found() {
assert!(!is_exact_token_match("ntbl_Revenue", "Costs"));
}
#[test]
fn test_strip_pos() {
assert_eq!(strip_sign_suffix("delt_Growth_pos"), "delt_Growth");
}
#[test]
fn test_strip_neg() {
assert_eq!(strip_sign_suffix("delt_Growth_neg"), "delt_Growth");
}
#[test]
fn test_strip_none() {
assert_eq!(strip_sign_suffix("delt_Growth_none"), "delt_Growth");
}
#[test]
fn test_strip_no_suffix() {
assert_eq!(strip_sign_suffix("delt_Growth"), "delt_Growth");
}
#[test]
fn test_strip_numbered_set() {
assert_eq!(strip_sign_suffix("delt2_Growth_neg"), "delt2_Growth");
assert_eq!(strip_sign_suffix("delt12_Growth_none"), "delt12_Growth");
}
#[test]
fn test_delta_set_default() {
assert_eq!(delta_set("delt_Growth_pos"), Some(1));
assert_eq!(delta_set("delt_"), Some(1));
}
#[test]
fn test_delta_set_explicit_one_is_alias() {
assert_eq!(delta_set("delt1_Growth_pos"), Some(1));
}
#[test]
fn test_delta_set_numbered() {
assert_eq!(delta_set("delt2_Growth_pos"), Some(2));
assert_eq!(delta_set("delt9_Growth"), Some(9));
assert_eq!(delta_set("delt12_Growth_none"), Some(12));
assert_eq!(delta_set("delt250_X"), Some(250));
}
#[test]
fn test_delta_set_anywhere_in_name() {
assert_eq!(delta_set("Group delt_Growth"), Some(1));
assert_eq!(delta_set("Xdelt2_Growth"), Some(2));
}
#[test]
fn test_delta_set_zero_rejected() {
assert_eq!(delta_set("delt0_Growth"), None);
}
#[test]
fn test_delta_set_not_a_delta() {
assert_eq!(delta_set("delta_Growth"), None);
assert_eq!(delta_set("delt2Growth"), None);
assert_eq!(delta_set("delt"), None);
assert_eq!(delta_set("ntbl_Growth"), None);
assert_eq!(delta_set(""), None);
}
#[test]
fn test_delta_set_skips_bad_then_finds_good() {
assert_eq!(delta_set("deltX_delt2_Growth"), Some(2));
}
#[test]
fn test_classify_numbered_delt() {
assert_eq!(classify_shape_name("delt2_Growth_pos"), Some(ShapePrefix::Delta));
assert_eq!(classify_shape_name("delt1_Growth_pos"), Some(ShapePrefix::Delta));
assert_eq!(classify_shape_name("delt0_Growth_pos"), None);
}
#[test]
fn test_template_name_set_one_unchanged() {
assert_eq!(template_name_for_set("tmpl_delta_pos", 1, "pos"), "tmpl_delta_pos");
assert_eq!(template_name_for_set("custom_up", 1, "pos"), "custom_up");
}
#[test]
fn test_template_name_numbered() {
assert_eq!(template_name_for_set("tmpl_delta_pos", 2, "pos"), "tmpl2_delta_pos");
assert_eq!(template_name_for_set("tmpl_delta_neg", 2, "neg"), "tmpl2_delta_neg");
assert_eq!(template_name_for_set("tmpl_delta_none", 12, "none"), "tmpl12_delta_none");
}
#[test]
fn test_template_name_custom_base_falls_back() {
assert_eq!(template_name_for_set("custom_up", 2, "pos"), "tmpl2_delta_pos");
}
#[test]
fn test_table_type_priority() {
assert!(TableType::Normal < TableType::Heatmap);
assert!(TableType::Heatmap < TableType::Transposed);
}
}