pub(crate) mod parser;
pub(super) mod trie;
pub(super) mod find_plugin;
use crate::plugins::CustomPlugin;
use std::{borrow::Cow, cmp::Ordering, str::FromStr};
pub(crate) use parser::parse;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum CssType {
Shadow,
AbsoluteSize,
RelativeSize,
Url,
LineWidth,
LineStyle,
Color,
Length,
Number,
Percentage,
Time,
Position,
Angle,
Image,
FontFamilyName,
}
impl FromStr for CssType {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"shadow" => Self::Shadow,
"absolute-size" => Self::AbsoluteSize,
"relative-size" => Self::RelativeSize,
"url" => Self::Url,
"line-width" => Self::LineWidth,
"line-style" => Self::LineStyle,
"color" => Self::Color,
"length" => Self::Length,
"percentage" => Self::Percentage,
"number" => Self::Number,
"position" => Self::Position,
"angle" => Self::Angle,
"image" => Self::Image,
"generic-name" | "family-name" => Self::FontFamilyName,
_ => return Err(()),
})
}
}
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord)]
pub enum Modifier<'a> {
Builtin {
is_negative: bool,
value: &'a str,
},
Arbitrary {
hint: Option<CssType>,
value: Cow<'a, str>,
},
}
#[derive(Debug, Clone, Eq)]
pub struct Variant<'a> {
pub(crate) order: usize,
pub(crate) prefixed: bool,
pub(crate) template: Cow<'a, str>,
}
impl<'a> Variant<'a> {
pub(crate) const fn new_const(counter: &mut usize, template: &'static str) -> Self {
*counter += 1;
Self {
order: *counter - 1,
prefixed: false,
template: Cow::Borrowed(template),
}
}
pub fn new<T: Into<Cow<'a, str>>>(order: usize, template: T) -> Self {
Self {
order,
prefixed: false,
template: template.into(),
}
}
#[must_use]
pub const fn with_prefixed(mut self) -> Self {
self.prefixed = true;
self
}
}
impl PartialEq for Variant<'_> {
fn eq(&self, other: &Self) -> bool {
self.template == other.template
}
}
#[derive(Clone, Debug)]
pub(crate) struct Selector<'a> {
pub(crate) layer: i8,
pub(crate) order: usize,
pub(crate) full: &'a str,
pub(crate) modifier: Modifier<'a>,
pub(crate) variants: Vec<Variant<'a>>,
pub(crate) is_important: bool,
pub(crate) plugin: CustomPlugin,
}
impl PartialEq for Selector<'_> {
fn eq(&self, other: &Self) -> bool {
self.full == other.full
&& self.modifier == other.modifier
&& self.variants == other.variants
&& self.is_important == other.is_important
&& self.layer == other.layer
}
}
impl Eq for Selector<'_> {}
impl PartialOrd for Selector<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Selector<'_> {
fn cmp(&self, other: &Self) -> Ordering {
if self.order == other.order && self == other {
return Ordering::Equal;
}
self.layer.cmp(&other.layer).then_with(|| {
if self.variants.is_empty() && !other.variants.is_empty() {
Ordering::Less
} else if !self.variants.is_empty() && other.variants.is_empty() {
Ordering::Greater
} else if !self.variants.is_empty() && !other.variants.is_empty() {
let mut compared = None;
for variant_i in 0..self.variants.len() {
if variant_i >= other.variants.len() {
compared = Some(Ordering::Greater);
break;
}
let res = self
.variants
.get(variant_i)
.as_ref()
.unwrap()
.order
.cmp(&other.variants.get(variant_i).unwrap().order);
if res != Ordering::Equal {
compared = Some(res);
break;
}
}
compared.unwrap_or(Ordering::Less).then_with(|| {
self.order.cmp(&other.order).then_with(|| {
self.full
.cmp(other.full)
.then_with(|| self.modifier.cmp(&other.modifier))
})
})
} else {
self.order.cmp(&other.order).then_with(|| {
self.full
.cmp(other.full)
.then_with(|| self.modifier.cmp(&other.modifier))
})
}
})
}
}
#[cfg(test)]
mod tests {
use crate::{config::Config, selector::parse};
use std::collections::BTreeSet;
#[test]
fn sorting_test() {
let config = Config::default();
let trie = crate::selector::trie::build_trie(&config);
let selectors1 = parse(
"lg:bg-red-500",
None,
None,
&config,
&config.get_derived_variants(),
&trie,
);
let selectors2 = parse(
"bg-red-500",
None,
None,
&config,
&config.get_derived_variants(),
&trie,
);
let mut selectors = BTreeSet::new();
selectors.insert(selectors1[0].as_ref().unwrap());
selectors.insert(selectors2[0].as_ref().unwrap());
let mut iter = selectors.iter();
assert!(
iter.next().unwrap().full == "bg-red-500"
&& iter.next().unwrap().full == "lg:bg-red-500"
);
}
#[test]
fn layers_test() {
let config = Config::default();
let trie = crate::selector::trie::build_trie(&config);
let selectors1 = parse(
"lg:bg-red-500",
None,
None,
&config,
&config.get_derived_variants(),
&trie,
);
let mut selectors2 = parse(
"bg-red-500",
None,
None,
&config,
&config.get_derived_variants(),
&trie,
);
selectors2[0].as_mut().unwrap().layer = 42;
let mut selectors = BTreeSet::new();
selectors.insert(selectors1[0].as_ref().unwrap());
selectors.insert(selectors2[0].as_ref().unwrap());
let mut iter = selectors.iter();
assert!(
iter.next().unwrap().full == "lg:bg-red-500"
&& iter.next().unwrap().full == "bg-red-500"
);
}
}