#![allow(clippy::redundant_pub_crate)]
use plumb_config::{CssPropertyScrape, ScrapedValue};
use plumb_core::Config;
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct PerFileStats {
pub colors: usize,
pub dimensions: usize,
pub other: usize,
}
impl PerFileStats {
pub(crate) fn increment(&mut self, value: &ScrapedValue) {
match value {
ScrapedValue::Color(_) => self.colors += 1,
ScrapedValue::Px(_) => self.dimensions += 1,
_ => self.other += 1,
}
}
}
pub(crate) fn classify_css_scrapes(scrapes: &[CssPropertyScrape], config: &mut Config) {
for scrape in scrapes {
let stem = scrape
.name
.strip_prefix("--")
.unwrap_or(scrape.name.as_str());
let lower = stem.to_ascii_lowercase();
match &scrape.value {
ScrapedValue::Color(hex) => {
if is_color_name(&lower) {
config
.color
.tokens
.entry(stem.to_owned())
.or_insert_with(|| hex.clone());
}
}
ScrapedValue::Px(px) => {
if is_radius_name(&lower) {
config.radius.scale.push(*px);
} else if is_spacing_name(&lower) {
config.spacing.scale.push(*px);
config.spacing.tokens.entry(stem.to_owned()).or_insert(*px);
} else if is_type_size_name(&lower) {
config.type_scale.scale.push(*px);
config
.type_scale
.tokens
.entry(stem.to_owned())
.or_insert(*px);
}
}
ScrapedValue::Rem(_) | ScrapedValue::Em(_) | ScrapedValue::Other(_) => {}
}
}
}
fn is_color_name(name: &str) -> bool {
static PREFIXES: &[&str] = &[
"color-", "bg-", "fg-", "accent-", "surface-", "text-", "border-", "ring-",
];
PREFIXES.iter().any(|p| name.starts_with(p))
}
fn is_spacing_name(name: &str) -> bool {
static PREFIXES: &[&str] = &["space-", "spacing-", "gap-", "padding-", "margin-", "size-"];
PREFIXES.iter().any(|p| name.starts_with(p))
}
fn is_radius_name(name: &str) -> bool {
static PREFIXES: &[&str] = &["radius-", "rounded-", "corner-"];
PREFIXES.iter().any(|p| name.starts_with(p))
}
fn is_type_size_name(name: &str) -> bool {
static PREFIXES: &[&str] = &["font-size-", "type-", "leading-"];
if PREFIXES.iter().any(|p| name.starts_with(p)) {
return true;
}
name.starts_with("text-")
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use std::path::PathBuf;
fn scrape(name: &str, value: ScrapedValue) -> CssPropertyScrape {
CssPropertyScrape {
source: PathBuf::from("test.css"),
at_rule: None,
name: name.to_owned(),
raw_value: String::new(),
value,
}
}
#[test]
fn classifies_color_tokens() {
let mut config = Config::default();
let scrapes = vec![
scrape("--color-bg", ScrapedValue::Color("#ffffff".into())),
scrape("--accent-brand", ScrapedValue::Color("#0b7285".into())),
scrape("--unknown", ScrapedValue::Color("#deadbe".into())),
];
classify_css_scrapes(&scrapes, &mut config);
assert_eq!(config.color.tokens.len(), 2);
assert_eq!(
config.color.tokens.get("color-bg"),
Some(&"#ffffff".to_owned())
);
}
#[test]
fn classifies_spacing_tokens() {
let mut config = Config::default();
let scrapes = vec![
scrape("--space-xs", ScrapedValue::Px(4)),
scrape("--space-sm", ScrapedValue::Px(8)),
scrape("--gap-md", ScrapedValue::Px(16)),
];
classify_css_scrapes(&scrapes, &mut config);
assert_eq!(config.spacing.scale, vec![4, 8, 16]);
assert_eq!(config.spacing.tokens.len(), 3);
}
#[test]
fn classifies_radius_tokens() {
let mut config = Config::default();
let scrapes = vec![
scrape("--radius-sm", ScrapedValue::Px(2)),
scrape("--radius-md", ScrapedValue::Px(8)),
scrape("--rounded-full", ScrapedValue::Px(9999)),
];
classify_css_scrapes(&scrapes, &mut config);
assert_eq!(config.radius.scale, vec![2, 8, 9999]);
}
#[test]
fn classifies_text_color_vs_text_size_by_value_kind() {
let mut config = Config::default();
let scrapes = vec![
scrape("--text-base", ScrapedValue::Px(16)),
scrape("--text-primary", ScrapedValue::Color("#0b0b0b".into())),
];
classify_css_scrapes(&scrapes, &mut config);
assert_eq!(config.type_scale.scale, vec![16]);
assert_eq!(config.color.tokens.len(), 1);
assert!(config.color.tokens.contains_key("text-primary"));
}
#[test]
fn rem_and_em_skip_classification() {
let mut config = Config::default();
let scrapes = vec![
scrape("--space-md", ScrapedValue::Rem(1.0)),
scrape("--space-lg", ScrapedValue::Em(1.5)),
];
classify_css_scrapes(&scrapes, &mut config);
assert!(config.spacing.scale.is_empty());
}
}