1use std::{collections::HashMap, convert::Infallible};
2
3use cssparser::*;
4use lightningcss::{
5 declaration::DeclarationBlock,
6 error::PrinterError,
7 printer::Printer,
8 properties::custom::{Token, TokenOrValue},
9 rules::{style::StyleRule, CssRule, CssRuleList, Location},
10 selector::{Component, Selector},
11 stylesheet::{ParserOptions, PrinterOptions, StyleSheet},
12 targets::Browsers,
13 traits::{AtRuleParser, ToCss},
14 values::{
15 color::{CssColor, RGBA},
16 length::LengthValue,
17 },
18 vendor_prefix::VendorPrefix,
19 visit_types,
20 visitor::{Visit, VisitTypes, Visitor},
21};
22
23fn main() {
24 let args: Vec<String> = std::env::args().collect();
25 let source = std::fs::read_to_string(&args[1]).unwrap();
26 let opts = ParserOptions {
27 filename: args[1].clone(),
28 ..Default::default()
29 };
30
31 let mut stylesheet = StyleSheet::parse_with(&source, opts, &mut TailwindAtRuleParser).unwrap();
32
33 println!("{:?}", stylesheet);
34
35 let mut style_rules = HashMap::new();
36 stylesheet
37 .visit(&mut StyleRuleCollector {
38 rules: &mut style_rules,
39 })
40 .unwrap();
41 println!("{:?}", style_rules);
42 stylesheet.visit(&mut ApplyVisitor { rules: &style_rules }).unwrap();
43
44 let result = stylesheet
45 .to_css(PrinterOptions {
46 targets: Browsers {
47 chrome: Some(100 << 16),
48 ..Browsers::default()
49 }
50 .into(),
51 ..PrinterOptions::default()
52 })
53 .unwrap();
54 println!("{}", result.code);
55}
56
57#[derive(Debug, Clone)]
59enum TailwindDirective {
60 Base,
61 Components,
62 Utilities,
63 Variants,
64}
65
66enum Prelude {
68 Tailwind(TailwindDirective),
69 Apply(Vec<String>),
70}
71
72#[derive(Debug, Clone)]
74struct TailwindRule {
75 directive: TailwindDirective,
76 loc: SourceLocation,
77}
78
79#[derive(Debug, Clone)]
81struct ApplyRule {
82 names: Vec<String>,
83 loc: SourceLocation,
84}
85
86#[derive(Debug, Clone)]
88enum AtRule {
89 Tailwind(TailwindRule),
90 Apply(ApplyRule),
91}
92
93#[derive(Debug)]
94struct TailwindAtRuleParser;
95impl<'i> AtRuleParser<'i> for TailwindAtRuleParser {
96 type Prelude = Prelude;
97 type Error = Infallible;
98 type AtRule = AtRule;
99
100 fn parse_prelude<'t>(
101 &mut self,
102 name: CowRcStr<'i>,
103 input: &mut Parser<'i, 't>,
104 _options: &ParserOptions<'i>,
105 ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
106 match_ignore_ascii_case! {&*name,
107 "tailwind" => {
108 let location = input.current_source_location();
109 let ident = input.expect_ident()?;
110 let directive = match_ignore_ascii_case! { &*ident,
111 "base" => TailwindDirective::Base,
112 "components" => TailwindDirective::Components,
113 "utilities" => TailwindDirective::Utilities,
114 "variants" => TailwindDirective::Variants,
115 _ => return Err(location.new_unexpected_token_error(
116 cssparser::Token::Ident(ident.clone())
117 ))
118 };
119 Ok(Prelude::Tailwind(directive))
120 },
121 "apply" => {
122 let mut names = Vec::new();
123 loop {
124 if let Ok(name) = input.try_parse(|input| input.expect_ident_cloned()) {
125 names.push(name.as_ref().into());
126 } else {
127 break
128 }
129 }
130
131 Ok(Prelude::Apply(names))
132 },
133 _ => Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name)))
134 }
135 }
136
137 fn rule_without_block(
138 &mut self,
139 prelude: Self::Prelude,
140 start: &ParserState,
141 _options: &ParserOptions<'i>,
142 _is_nested: bool,
143 ) -> Result<Self::AtRule, ()> {
144 let loc = start.source_location();
145 match prelude {
146 Prelude::Tailwind(directive) => Ok(AtRule::Tailwind(TailwindRule { directive, loc })),
147 Prelude::Apply(names) => Ok(AtRule::Apply(ApplyRule { names, loc })),
148 }
149 }
150}
151
152struct StyleRuleCollector<'i, 'a> {
153 rules: &'a mut HashMap<String, DeclarationBlock<'i>>,
154}
155
156impl<'i, 'a> Visitor<'i, AtRule> for StyleRuleCollector<'i, 'a> {
157 type Error = Infallible;
158
159 fn visit_types(&self) -> VisitTypes {
160 VisitTypes::RULES
161 }
162
163 fn visit_rule(&mut self, rule: &mut lightningcss::rules::CssRule<'i, AtRule>) -> Result<(), Self::Error> {
164 match rule {
165 CssRule::Style(rule) => {
166 for selector in rule.selectors.0.iter() {
167 if selector.len() != 1 {
168 continue; }
170 for component in selector.iter_raw_match_order() {
171 match component {
172 Component::Class(name) => {
173 self.rules.insert(name.0.to_string(), rule.declarations.clone());
174 }
175 _ => {}
176 }
177 }
178 }
179 }
180 _ => {}
181 }
182
183 rule.visit_children(self)
184 }
185}
186
187struct ApplyVisitor<'a, 'i> {
188 rules: &'a HashMap<String, DeclarationBlock<'i>>,
189}
190
191impl<'a, 'i> Visitor<'i, AtRule> for ApplyVisitor<'a, 'i> {
192 type Error = Infallible;
193
194 fn visit_types(&self) -> VisitTypes {
195 visit_types!(RULES | COLORS | LENGTHS | DASHED_IDENTS | SELECTORS | TOKENS)
196 }
197
198 fn visit_rule(&mut self, rule: &mut CssRule<'i, AtRule>) -> Result<(), Self::Error> {
199 if let CssRule::Custom(AtRule::Apply(apply)) = rule {
201 let mut declarations = DeclarationBlock::new();
202 for name in &apply.names {
203 let Some(applied) = self.rules.get(name) else {
204 continue;
205 };
206 declarations
207 .important_declarations
208 .extend(applied.important_declarations.iter().cloned());
209 declarations.declarations.extend(applied.declarations.iter().cloned());
210 }
211 *rule = CssRule::Style(StyleRule {
212 selectors: Component::Nesting.into(),
213 vendor_prefix: VendorPrefix::None,
214 declarations,
215 rules: CssRuleList(vec![]),
216 loc: Location {
217 source_index: 0,
218 line: apply.loc.line,
219 column: apply.loc.column,
220 },
221 })
222 }
223
224 rule.visit_children(self)
225 }
226
227 fn visit_url(&mut self, url: &mut lightningcss::values::url::Url<'i>) -> Result<(), Self::Error> {
228 url.url = format!("https://mywebsite.com/{}", url.url).into();
229 Ok(())
230 }
231
232 fn visit_color(&mut self, color: &mut lightningcss::values::color::CssColor) -> Result<(), Self::Error> {
233 *color = color.to_lab().unwrap();
234 Ok(())
235 }
236
237 fn visit_length(&mut self, length: &mut lightningcss::values::length::LengthValue) -> Result<(), Self::Error> {
238 match length {
239 LengthValue::Px(px) => *length = LengthValue::Rem(*px / 16.0),
240 _ => {}
241 }
242
243 Ok(())
244 }
245
246 fn visit_dashed_ident(
247 &mut self,
248 ident: &mut lightningcss::values::ident::DashedIdent,
249 ) -> Result<(), Self::Error> {
250 ident.0 = format!("--tw-{}", &ident.0[2..]).into();
251 Ok(())
252 }
253
254 fn visit_selector(&mut self, selector: &mut Selector<'i>) -> Result<(), Self::Error> {
255 for c in selector.iter_mut_raw_match_order() {
256 match c {
257 Component::Class(c) => {
258 *c = format!("tw-{}", c).into();
259 }
260 _ => {}
261 }
262 }
263
264 Ok(())
265 }
266
267 fn visit_token(&mut self, token: &mut TokenOrValue<'i>) -> Result<(), Self::Error> {
268 match token {
269 TokenOrValue::Function(f) if f.name == "theme" => match f.arguments.0.first() {
270 Some(TokenOrValue::Token(Token::String(s))) => match s.as_ref() {
271 "blue-500" => *token = TokenOrValue::Color(CssColor::RGBA(RGBA::new(0, 0, 255, 1.0))),
272 "red-500" => *token = TokenOrValue::Color(CssColor::RGBA(RGBA::new(255, 0, 0, 1.0))),
273 _ => {}
274 },
275 _ => {}
276 },
277 _ => {}
278 }
279
280 token.visit_children(self)
281 }
282}
283
284#[cfg(feature = "visitor")]
285impl<'i, V: Visitor<'i, AtRule>> Visit<'i, AtRule, V> for AtRule {
286 const CHILD_TYPES: VisitTypes = VisitTypes::empty();
287
288 fn visit_children(&mut self, _: &mut V) -> Result<(), V::Error> {
289 Ok(())
290 }
291}
292
293impl ToCss for AtRule {
294 fn to_css<W: std::fmt::Write>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError> {
295 match self {
296 AtRule::Tailwind(rule) => {
297 let _ = rule.loc; let directive = match rule.directive {
299 TailwindDirective::Base => "TAILWIND BASE HERE",
300 TailwindDirective::Components => "TAILWIND COMPONENTS HERE",
301 TailwindDirective::Utilities => "TAILWIND UTILITIES HERE",
302 TailwindDirective::Variants => "TAILWIND VARIANTS HERE",
303 };
304 dest.write_str(directive)
305 }
306 AtRule::Apply(_) => Ok(()),
307 }
308 }
309}