rustui_merge/core/merge/
merge_impl.rs

1use std::collections::HashSet;
2
3use crate::ast::AstStyle;
4
5use super::{CollisionIdFn, GetCollisionsFn, MergeOptions};
6use crate::core::merge::get_collisions::get_collisions;
7
8/// Merges all the Tailwind classes, resolving conflicts.
9/// Can supply custom options, collision_id_fn and collisions_fn.
10pub fn tw_merge_override(
11    class: &[&str],
12    options: MergeOptions,
13    collision_id_fn: impl CollisionIdFn,
14    collisions_fn: impl GetCollisionsFn,
15) -> String {
16    let styles: Vec<Result<AstStyle, &str>> = crate::ast::parse_tailwind(class, options.into());
17
18    let mut valid_styles: Vec<Result<AstStyle, &str>> = vec![];
19    let mut collision_styles: HashSet<Collision> = HashSet::new();
20
21    for style in styles.into_iter().rev() {
22        let style = match style {
23            Ok(style) => style,
24            Err(s) => {
25                valid_styles.push(Err(s));
26                continue;
27            }
28        };
29
30        let elements = style.elements.as_slice();
31        let result = collision_id_fn
32            .apply(elements, style.arbitrary)
33            .map(Ok)
34            .unwrap_or_else(|| {
35                let arbitrary = style.arbitrary.unwrap_or_default();
36                super::get_collision_id::get_collision_id(elements, arbitrary)
37            });
38
39        match result {
40            Err(error) => match Collision::check_arbitrary(style.clone()) {
41                Some(collision) => {
42                    if collision_styles.contains(&collision) {
43                        continue;
44                    }
45                    collision_styles.insert(collision);
46                }
47                None => {
48                    #[cfg(debug)]
49                    println!("No Instance found: {style:?} {error:?}");
50                    let _ = error;
51                }
52            },
53            Ok(collision_id) => {
54                // hover:md:focus
55                let all_variants: Vec<&str> = style.variants.clone();
56
57                let collision = Collision {
58                    important: style.important,
59                    variants: all_variants.clone(),
60                    collision_id,
61                };
62
63                if collision_styles.contains(&collision) {
64                    continue;
65                }
66
67                // Add the current collision_id.
68                collision_styles.insert(collision);
69
70                let collisions = collisions_fn
71                    .apply(collision_id)
72                    .or_else(|| get_collisions(collision_id));
73
74                if let Some(collisions) = collisions {
75                    collisions.into_iter().for_each(|collision_id| {
76                        let collision = Collision {
77                            important: style.important,
78                            variants: all_variants.clone(),
79                            collision_id,
80                        };
81
82                        collision_styles.insert(collision);
83                    });
84                }
85            }
86        }
87        valid_styles.push(Ok(style));
88    }
89
90    valid_styles.reverse();
91
92    valid_styles
93        .into_iter()
94        .map(|s| match s {
95            Ok(style) => style.source,
96            Err(s) => s,
97        })
98        .collect::<Vec<_>>()
99        .join(" ")
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, Hash)]
103struct Collision<'a> {
104    important: bool,
105    variants: Vec<&'a str>,
106    collision_id: &'a str,
107}
108
109// For [color:blue] => label = "color"
110impl<'a> Collision<'a> {
111    fn check_arbitrary(style: AstStyle<'a>) -> Option<Self> {
112        let arbitrary = style.arbitrary?;
113        let index = arbitrary.find(':')?;
114        let (collision_id, _) = arbitrary.split_at(index);
115        Some(Self {
116            collision_id,
117            important: style.important,
118            variants: style.variants,
119        })
120    }
121}
122
123#[test]
124fn check_arbitrary() {
125    let style = crate::ast::parse_tailwind(&["[color:blue]"], Default::default())
126        .into_iter()
127        .next()
128        .unwrap()
129        .unwrap();
130
131    assert_eq!(
132        Collision::check_arbitrary(style),
133        Some(Collision {
134            important: false,
135            variants: vec![],
136            collision_id: "color"
137        })
138    );
139}