use crate::error::Result;
use crate::responsive::Breakpoint;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct EnhancedVariantParser {
variants: HashMap<String, VariantDefinition>,
breakpoints: HashMap<String, Breakpoint>,
custom_variants: HashMap<String, CustomVariant>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VariantDefinition {
pub name: String,
pub variant_type: VariantType,
pub selector_pattern: String,
pub media_query: Option<String>,
pub specificity: u32,
pub combinable: bool,
pub dependencies: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum VariantType {
State,
Responsive,
DarkMode,
Group,
Peer,
PseudoElement,
Container,
Layer,
Custom,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CustomVariant {
pub name: String,
pub selector_pattern: String,
pub media_query: Option<String>,
pub specificity: u32,
pub combinable: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VariantCombination {
pub variants: Vec<ParsedVariant>,
pub base_class: String,
pub selector: String,
pub media_query: Option<String>,
pub specificity: u32,
pub is_valid: bool,
pub errors: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ParsedVariant {
pub name: String,
pub variant_type: VariantType,
pub selector_fragment: String,
pub specificity: u32,
pub position: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VariantParseResult {
pub combination: VariantCombination,
pub metadata: VariantMetadata,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VariantMetadata {
pub processing_time: std::time::Duration,
pub variant_count: usize,
pub combination_count: usize,
pub memory_usage: usize,
pub cache_hits: usize,
pub cache_misses: usize,
}
impl EnhancedVariantParser {
pub fn new() -> Self {
let mut parser = Self {
variants: HashMap::new(),
breakpoints: HashMap::new(),
custom_variants: HashMap::new(),
};
parser.initialize_default_variants();
parser.initialize_breakpoints();
parser
}
fn initialize_default_variants(&mut self) {
self.add_variant(VariantDefinition {
name: "hover".to_string(),
variant_type: VariantType::State,
selector_pattern: ":hover".to_string(),
media_query: None,
specificity: 10,
combinable: true,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "focus".to_string(),
variant_type: VariantType::State,
selector_pattern: ":focus".to_string(),
media_query: None,
specificity: 10,
combinable: true,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "active".to_string(),
variant_type: VariantType::State,
selector_pattern: ":active".to_string(),
media_query: None,
specificity: 10,
combinable: true,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "visited".to_string(),
variant_type: VariantType::State,
selector_pattern: ":visited".to_string(),
media_query: None,
specificity: 10,
combinable: true,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "disabled".to_string(),
variant_type: VariantType::State,
selector_pattern: ":disabled".to_string(),
media_query: None,
specificity: 10,
combinable: true,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "dark".to_string(),
variant_type: VariantType::DarkMode,
selector_pattern: ".dark".to_string(),
media_query: None,
specificity: 20,
combinable: true,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "group-hover".to_string(),
variant_type: VariantType::Group,
selector_pattern: ".group:hover".to_string(),
media_query: None,
specificity: 15,
combinable: true,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "group-focus".to_string(),
variant_type: VariantType::Group,
selector_pattern: ".group:focus".to_string(),
media_query: None,
specificity: 15,
combinable: true,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "peer-hover".to_string(),
variant_type: VariantType::Peer,
selector_pattern: ".peer:hover".to_string(),
media_query: None,
specificity: 15,
combinable: true,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "peer-focus".to_string(),
variant_type: VariantType::Peer,
selector_pattern: ".peer:focus".to_string(),
media_query: None,
specificity: 15,
combinable: true,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "before".to_string(),
variant_type: VariantType::PseudoElement,
selector_pattern: "::before".to_string(),
media_query: None,
specificity: 10,
combinable: true,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "after".to_string(),
variant_type: VariantType::PseudoElement,
selector_pattern: "::after".to_string(),
media_query: None,
specificity: 10,
combinable: true,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "container".to_string(),
variant_type: VariantType::Container,
selector_pattern: "@container".to_string(),
media_query: None,
specificity: 25,
combinable: true,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "layer".to_string(),
variant_type: VariantType::Layer,
selector_pattern: "@layer".to_string(),
media_query: None,
specificity: 30,
combinable: true,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "sm".to_string(),
variant_type: VariantType::Responsive,
selector_pattern: "".to_string(),
media_query: Some("(min-width: 640px)".to_string()),
specificity: 20,
combinable: false,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "md".to_string(),
variant_type: VariantType::Responsive,
selector_pattern: "".to_string(),
media_query: Some("(min-width: 768px)".to_string()),
specificity: 20,
combinable: false,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "lg".to_string(),
variant_type: VariantType::Responsive,
selector_pattern: "".to_string(),
media_query: Some("(min-width: 1024px)".to_string()),
specificity: 20,
combinable: false,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "xl".to_string(),
variant_type: VariantType::Responsive,
selector_pattern: "".to_string(),
media_query: Some("(min-width: 1280px)".to_string()),
specificity: 20,
combinable: false,
dependencies: Vec::new(),
});
self.add_variant(VariantDefinition {
name: "2xl".to_string(),
variant_type: VariantType::Responsive,
selector_pattern: "".to_string(),
media_query: Some("(min-width: 1536px)".to_string()),
specificity: 20,
combinable: false,
dependencies: Vec::new(),
});
}
fn initialize_breakpoints(&mut self) {
self.breakpoints.insert("sm".to_string(), Breakpoint::Sm);
self.breakpoints.insert("md".to_string(), Breakpoint::Md);
self.breakpoints.insert("lg".to_string(), Breakpoint::Lg);
self.breakpoints.insert("xl".to_string(), Breakpoint::Xl);
self.breakpoints.insert("2xl".to_string(), Breakpoint::Xl);
}
pub fn add_variant(&mut self, variant: VariantDefinition) {
self.variants.insert(variant.name.clone(), variant);
}
pub fn add_custom_variant(&mut self, variant: CustomVariant) {
self.custom_variants.insert(variant.name.clone(), variant);
}
pub fn parse_class(&self, class: &str) -> Result<VariantParseResult> {
let start_time = std::time::Instant::now();
let (variants, base_class) = self.parse_variants_advanced(class);
let variant_count = variants.len();
let combination = self.generate_variant_combination(variants, base_class)?;
let processing_time = start_time.elapsed();
Ok(VariantParseResult {
combination,
metadata: VariantMetadata {
processing_time,
variant_count,
combination_count: 1,
memory_usage: 0, cache_hits: 0, cache_misses: 1, },
})
}
fn parse_variants_advanced(&self, class: &str) -> (Vec<String>, String) {
let mut variants = Vec::new();
let mut remaining = class.to_string();
let variant_patterns = [
("dark:", "dark"),
("group-hover:", "group-hover"),
("group-focus:", "group-focus"),
("group-active:", "group-active"),
("group-disabled:", "group-disabled"),
("peer-hover:", "peer-hover"),
("peer-focus:", "peer-focus"),
("peer-active:", "peer-active"),
("peer-disabled:", "peer-disabled"),
("hover:", "hover"),
("focus:", "focus"),
("active:", "active"),
("visited:", "visited"),
("disabled:", "disabled"),
("before:", "before"),
("after:", "after"),
("container:", "container"),
("layer:", "layer"),
("sm:", "sm"),
("md:", "md"),
("lg:", "lg"),
("xl:", "xl"),
("2xl:", "2xl"),
];
loop {
let mut found = false;
for (prefix, variant) in &variant_patterns {
if remaining.starts_with(prefix) {
variants.push(variant.to_string());
remaining = remaining
.strip_prefix(prefix)
.unwrap_or(&remaining)
.to_string();
found = true;
break;
}
}
if !found {
break;
}
}
(variants, remaining)
}
fn generate_variant_combination(
&self,
variants: Vec<String>,
base_class: String,
) -> Result<VariantCombination> {
let mut parsed_variants = Vec::new();
let mut selector_parts = Vec::new();
let mut media_query = None;
let mut total_specificity = 10; let mut errors = Vec::new();
for (i, variant_name) in variants.iter().enumerate() {
if let Some(variant_def) = self.variants.get(variant_name) {
let parsed_variant = ParsedVariant {
name: variant_name.clone(),
variant_type: variant_def.variant_type.clone(),
selector_fragment: variant_def.selector_pattern.clone(),
specificity: variant_def.specificity,
position: i,
};
parsed_variants.push(parsed_variant.clone());
selector_parts.push(variant_def.selector_pattern.clone());
total_specificity += variant_def.specificity;
if let Some(mq) = &variant_def.media_query {
media_query = Some(mq.clone());
}
} else if let Some(custom_variant) = self.custom_variants.get(variant_name) {
let parsed_variant = ParsedVariant {
name: variant_name.clone(),
variant_type: VariantType::Custom,
selector_fragment: custom_variant.selector_pattern.clone(),
specificity: custom_variant.specificity,
position: i,
};
parsed_variants.push(parsed_variant.clone());
selector_parts.push(custom_variant.selector_pattern.clone());
total_specificity += custom_variant.specificity;
if let Some(mq) = &custom_variant.media_query {
media_query = Some(mq.clone());
}
} else {
errors.push(format!("Unknown variant: {}", variant_name));
}
}
let selector = self.generate_selector(selector_parts, &base_class);
let is_valid = self.validate_combination(&parsed_variants, &errors);
Ok(VariantCombination {
variants: parsed_variants,
base_class,
selector,
media_query,
specificity: total_specificity,
is_valid,
errors,
})
}
fn generate_selector(&self, parts: Vec<String>, base_class: &str) -> String {
let mut selector = String::new();
for part in parts {
if part.starts_with('.') {
selector.push_str(&part);
selector.push(' ');
} else if part.starts_with(':') {
selector.push_str(&part);
} else if part.starts_with('@') {
selector.push_str(&part);
selector.push(' ');
}
}
selector.push_str(&format!(".{}", base_class));
selector
}
fn validate_combination(&self, variants: &[ParsedVariant], errors: &[String]) -> bool {
if !errors.is_empty() {
return false;
}
let mut variant_types = std::collections::HashSet::new();
for variant in variants {
if !variant_types.insert(&variant.variant_type) {
match variant.variant_type {
VariantType::State => {
}
VariantType::Responsive => {
return false;
}
VariantType::DarkMode => {
return false;
}
_ => {
}
}
}
}
true
}
pub fn get_variant(&self, name: &str) -> Option<&VariantDefinition> {
self.variants.get(name)
}
pub fn get_all_variants(&self) -> &HashMap<String, VariantDefinition> {
&self.variants
}
pub fn get_custom_variants(&self) -> &HashMap<String, CustomVariant> {
&self.custom_variants
}
pub fn remove_variant(&mut self, name: &str) -> Option<VariantDefinition> {
self.variants.remove(name)
}
pub fn remove_custom_variant(&mut self, name: &str) -> Option<CustomVariant> {
self.custom_variants.remove(name)
}
pub fn clear_variants(&mut self) {
self.variants.clear();
self.custom_variants.clear();
}
pub fn reset_to_defaults(&mut self) {
self.clear_variants();
self.initialize_default_variants();
}
}
impl Default for EnhancedVariantParser {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_enhanced_variant_parser_creation() {
let parser = EnhancedVariantParser::new();
assert!(!parser.variants.is_empty());
assert!(!parser.breakpoints.is_empty());
}
#[test]
fn test_variant_definition_creation() {
let variant = VariantDefinition {
name: "hover".to_string(),
variant_type: VariantType::State,
selector_pattern: ":hover".to_string(),
media_query: None,
specificity: 10,
combinable: true,
dependencies: Vec::new(),
};
assert_eq!(variant.name, "hover");
assert_eq!(variant.variant_type, VariantType::State);
assert_eq!(variant.specificity, 10);
}
#[test]
fn test_parse_simple_class() {
let parser = EnhancedVariantParser::new();
let result = parser.parse_class("p-4");
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.combination.base_class, "p-4");
assert!(result.combination.variants.is_empty());
assert!(result.combination.is_valid);
}
#[test]
fn test_parse_single_variant() {
let parser = EnhancedVariantParser::new();
let result = parser.parse_class("hover:bg-blue-500");
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.combination.base_class, "bg-blue-500");
assert_eq!(result.combination.variants.len(), 1);
assert_eq!(result.combination.variants[0].name, "hover");
assert!(result.combination.is_valid);
}
#[test]
fn test_parse_multiple_variants() {
let parser = EnhancedVariantParser::new();
let result = parser.parse_class("dark:hover:bg-blue-500");
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.combination.base_class, "bg-blue-500");
assert_eq!(result.combination.variants.len(), 2);
assert!(result.combination.is_valid);
}
#[test]
fn test_parse_responsive_variant() {
let parser = EnhancedVariantParser::new();
let result = parser.parse_class("md:p-4");
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.combination.base_class, "p-4");
assert_eq!(result.combination.variants.len(), 1);
assert_eq!(result.combination.variants[0].name, "md");
assert!(result.combination.is_valid);
}
#[test]
fn test_parse_complex_combination() {
let parser = EnhancedVariantParser::new();
let result = parser.parse_class("dark:group-hover:focus:bg-blue-500");
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.combination.base_class, "bg-blue-500");
assert_eq!(result.combination.variants.len(), 3);
assert!(result.combination.is_valid);
}
#[test]
fn test_custom_variant_addition() {
let mut parser = EnhancedVariantParser::new();
let custom_variant = CustomVariant {
name: "custom".to_string(),
selector_pattern: ".custom".to_string(),
media_query: None,
specificity: 15,
combinable: true,
};
parser.add_custom_variant(custom_variant);
assert!(parser.custom_variants.contains_key("custom"));
}
#[test]
fn test_variant_removal() {
let mut parser = EnhancedVariantParser::new();
let removed = parser.remove_variant("hover");
assert!(removed.is_some());
assert!(!parser.variants.contains_key("hover"));
}
#[test]
fn test_parser_reset() {
let mut parser = EnhancedVariantParser::new();
parser.add_custom_variant(CustomVariant {
name: "test".to_string(),
selector_pattern: ".test".to_string(),
media_query: None,
specificity: 10,
combinable: true,
});
assert!(parser.custom_variants.contains_key("test"));
parser.reset_to_defaults();
assert!(!parser.custom_variants.contains_key("test"));
assert!(parser.variants.contains_key("hover"));
}
}