use super::{extract_attr, has_attr, BlockInfo};
use crate::types::{ArtMetadata, ArtParseError, ArtStatus};
use memchr::{memchr, memmem};
use vize_carton::Bump;
#[inline]
pub(crate) fn find_art_block<'a>(
bytes: &[u8],
source: &'a str,
) -> Result<BlockInfo<'a>, ArtParseError> {
let art_finder = memmem::Finder::new(b"<art");
let Some(art_start) = art_finder.find(bytes) else {
return Err(ArtParseError::NoArtBlock);
};
let after_art = art_start + 4;
if after_art < bytes.len() {
let next_char = bytes[after_art];
if next_char != b' ' && next_char != b'>' && next_char != b'\n' && next_char != b'\t' {
return Err(ArtParseError::NoArtBlock);
}
}
let Some(tag_close_offset) = memchr(b'>', &bytes[art_start..]) else {
return Err(ArtParseError::NoArtBlock);
};
let tag_end = art_start + tag_close_offset;
let attrs_start = art_start + 4;
let attrs_str = source[attrs_start..tag_end].trim();
let content_start = tag_end + 1;
let close_finder = memmem::Finder::new(b"</art>");
let Some(close_offset) = close_finder.find(&bytes[content_start..]) else {
return Err(ArtParseError::NoArtBlock);
};
let close_pos = content_start + close_offset;
let content = &source[content_start..close_pos];
Ok(BlockInfo {
attrs_str,
content,
content_start,
})
}
#[inline]
pub(crate) fn parse_metadata<'a>(
allocator: &'a Bump,
block: &BlockInfo<'a>,
) -> Result<ArtMetadata<'a>, ArtParseError> {
let attrs = block.attrs_str;
let title = extract_attr(attrs, "title").ok_or(ArtParseError::MissingTitle)?;
let description = extract_attr(attrs, "description");
let component = extract_attr(attrs, "component");
let category = extract_attr(attrs, "category");
let mut tags = vize_carton::Vec::new_in(allocator);
if let Some(tags_str) = extract_attr(attrs, "tags") {
for tag in tags_str.split(',') {
let trimmed = tag.trim();
if !trimmed.is_empty() {
tags.push(trimmed);
}
}
}
let status = parse_status(attrs);
let order = extract_attr(attrs, "order").and_then(|s| s.parse::<u32>().ok());
Ok(ArtMetadata {
title,
description,
component,
category,
tags,
status,
order,
})
}
#[inline]
fn parse_status(attrs: &str) -> ArtStatus {
if let Some(status_str) = extract_attr(attrs, "status") {
let bytes = status_str.as_bytes();
if bytes.eq_ignore_ascii_case(b"draft") {
ArtStatus::Draft
} else if bytes.eq_ignore_ascii_case(b"deprecated") {
ArtStatus::Deprecated
} else {
ArtStatus::Ready
}
} else if has_attr(attrs, "draft") {
ArtStatus::Draft
} else if has_attr(attrs, "deprecated") {
ArtStatus::Deprecated
} else {
ArtStatus::Ready
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_find_art_block() {
let source = r#"<art title="Test"><variant name="A"></variant></art>"#;
let result = find_art_block(source.as_bytes(), source);
assert!(result.is_ok());
let block = result.unwrap();
assert!(block.attrs_str.contains("title"));
assert!(block.content.contains("variant"));
}
#[test]
fn test_parse_metadata_minimal() {
let allocator = Bump::new();
let source = r#"<art title="Button"></art>"#;
let block = find_art_block(source.as_bytes(), source).unwrap();
let metadata = parse_metadata(&allocator, &block).unwrap();
assert_eq!(metadata.title, "Button");
assert_eq!(metadata.description, None);
assert_eq!(metadata.status, ArtStatus::Ready);
}
#[test]
fn test_parse_metadata_full() {
let allocator = Bump::new();
let source = r#"<art title="Button" description="A button" category="atoms" tags="ui,input" status="draft"></art>"#;
let block = find_art_block(source.as_bytes(), source).unwrap();
let metadata = parse_metadata(&allocator, &block).unwrap();
assert_eq!(metadata.title, "Button");
assert_eq!(metadata.description, Some("A button"));
assert_eq!(metadata.category, Some("atoms"));
assert_eq!(metadata.tags.len(), 2);
assert_eq!(metadata.tags[0], "ui");
assert_eq!(metadata.tags[1], "input");
assert_eq!(metadata.status, ArtStatus::Draft);
}
#[test]
fn test_parse_status() {
assert_eq!(parse_status(r#"status="draft""#), ArtStatus::Draft);
assert_eq!(parse_status(r#"status="ready""#), ArtStatus::Ready);
assert_eq!(
parse_status(r#"status="deprecated""#),
ArtStatus::Deprecated
);
assert_eq!(parse_status(r#"draft"#), ArtStatus::Draft);
assert_eq!(parse_status(r#"deprecated"#), ArtStatus::Deprecated);
assert_eq!(parse_status(r#""#), ArtStatus::Ready);
}
}