1use std::fmt::{Result, Write};
2
3use crate::VarDynValue;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6enum LineStatus {
7 BlockStart,
8 LineStart,
9 Other,
10}
11
12pub trait CssWriteTarget: Write {
14 fn position(&self) -> usize;
15}
16
17impl CssWriteTarget for String {
18 fn position(&self) -> usize {
19 self.len()
20 }
21}
22
23#[derive(Debug, Clone, Copy, PartialEq)]
25pub enum CssWritePlaceholder {
26 ColorHash(usize),
27 QuoteStr(usize),
28 Num(usize),
29}
30
31impl CssWritePlaceholder {
32 pub fn index(&self) -> usize {
33 match self {
34 CssWritePlaceholder::ColorHash(x) => *x,
35 CssWritePlaceholder::QuoteStr(x) => *x,
36 CssWritePlaceholder::Num(x) => *x,
37 }
38 }
39}
40
41pub struct CssWriter<'a, W: CssWriteTarget> {
43 pub(crate) w: &'a mut W,
44 pub(crate) sc: WriteCssSepCond,
45 pub(crate) debug_mode: bool,
46 placeholders: Vec<CssWritePlaceholder>,
47 tab_count: usize,
48 line_status: LineStatus,
49}
50
51impl<'a, W: CssWriteTarget> CssWriter<'a, W> {
52 pub fn new(w: &'a mut W, debug_mode: bool) -> Self {
53 Self {
54 w,
55 sc: WriteCssSepCond::BlockStart,
56 debug_mode,
57 placeholders: Vec::with_capacity(0),
58 tab_count: 0,
59 line_status: LineStatus::BlockStart,
60 }
61 }
62
63 pub fn target(&self) -> &W {
64 &self.w
65 }
66
67 pub fn placeholders(&self) -> &[CssWritePlaceholder] {
68 &self.placeholders
69 }
70
71 pub fn line_wrap(&mut self) -> Result {
72 if !self.debug_mode {
73 return Ok(());
74 }
75 if self.line_status == LineStatus::Other {
76 self.line_status = LineStatus::LineStart;
77 write!(self.w, "\n")?;
78 }
79 Ok(())
80 }
81
82 fn prepare_write(&mut self) -> Result {
83 if !self.debug_mode {
84 return Ok(());
85 }
86 if self.line_status == LineStatus::BlockStart {
87 self.line_status = LineStatus::LineStart;
88 write!(self.w, "\n")?;
89 }
90 if self.line_status == LineStatus::LineStart {
91 for _ in 0..self.tab_count {
92 write!(self.w, " ")?;
93 }
94 self.line_status = LineStatus::Other;
95 self.sc = WriteCssSepCond::Whitespace;
96 }
97 Ok(())
98 }
99
100 pub(crate) fn custom_write(
101 &mut self,
102 f: impl FnOnce(
103 &mut W,
104 WriteCssSepCond,
105 bool,
106 &mut Vec<CssWritePlaceholder>,
107 ) -> std::result::Result<WriteCssSepCond, std::fmt::Error>,
108 ) -> Result {
109 self.prepare_write()?;
110 let CssWriter {
111 ref mut w,
112 ref mut sc,
113 debug_mode,
114 ref mut placeholders,
115 ..
116 } = self;
117 *sc = f(w, *sc, *debug_mode, placeholders)?;
118 Ok(())
119 }
120
121 pub fn write_ident(&mut self, ident: &str, prefer_sep_before: bool) -> Result {
122 self.prepare_write()?;
123 let CssWriter {
124 ref mut w,
125 ref mut sc,
126 debug_mode,
127 ..
128 } = self;
129 if *debug_mode && prefer_sep_before {
130 match sc {
131 WriteCssSepCond::BlockStart | WriteCssSepCond::Whitespace => {}
132 _ => {
133 write!(w, " ")?;
134 }
135 }
136 } else {
137 match sc {
138 WriteCssSepCond::Ident
139 | WriteCssSepCond::NonIdentAlpha
140 | WriteCssSepCond::Digit
141 | WriteCssSepCond::PlusOrMinus
142 | WriteCssSepCond::At => {
143 write!(w, " ")?;
144 }
145 _ => {}
146 }
147 }
148 write!(w, "{}", ident)?;
149 *sc = WriteCssSepCond::Ident;
150 Ok(())
151 }
152
153 pub fn write_at_keyword(&mut self, ident: &str) -> Result {
154 self.prepare_write()?;
155 let CssWriter {
156 ref mut w,
157 ref mut sc,
158 debug_mode,
159 ..
160 } = self;
161 if *debug_mode {
162 match sc {
163 WriteCssSepCond::BlockStart | WriteCssSepCond::Whitespace => {}
164 _ => {
165 write!(w, " ")?;
166 }
167 }
168 }
169 write!(w, "@{}", ident)?;
170 *sc = WriteCssSepCond::NonIdentAlpha;
171 Ok(())
172 }
173
174 pub fn write_colon(&mut self) -> Result {
175 self.prepare_write()?;
176 let CssWriter {
177 ref mut w,
178 ref mut sc,
179 ..
180 } = self;
181 write!(w, ":")?;
182 *sc = WriteCssSepCond::Other;
183 Ok(())
184 }
185
186 pub fn write_semi(&mut self) -> Result {
187 self.prepare_write()?;
188 let CssWriter {
189 ref mut w,
190 ref mut sc,
191 ..
192 } = self;
193 write!(w, ";")?;
194 *sc = WriteCssSepCond::Other;
195 Ok(())
196 }
197
198 pub fn write_delim(&mut self, s: &str, prefer_sep_before: bool) -> Result {
199 self.prepare_write()?;
200 let CssWriter {
201 ref mut w,
202 ref mut sc,
203 debug_mode,
204 ..
205 } = self;
206 let need_sep = if *debug_mode && prefer_sep_before {
207 match sc {
208 WriteCssSepCond::BlockStart | WriteCssSepCond::Whitespace => false,
209 _ => true,
210 }
211 } else {
212 match sc {
213 WriteCssSepCond::Ident => s == "-",
214 WriteCssSepCond::NonIdentAlpha => s == "-",
215 WriteCssSepCond::Digit => s == "." || s == "-" || s == "%",
216 WriteCssSepCond::At => s == "-",
217 WriteCssSepCond::Equalable => s == "=",
218 WriteCssSepCond::Bar => s == "=" || s == "|" || s == "|=",
219 WriteCssSepCond::Slash => s == "*" || s == "*=",
220 _ => false,
221 }
222 };
223 if need_sep {
224 write!(w, " ")?;
225 }
226 write!(w, "{}", s)?;
227 *sc = match s {
228 "@" => WriteCssSepCond::At,
229 "." => WriteCssSepCond::Dot,
230 "+" | "-" => WriteCssSepCond::PlusOrMinus,
231 "$" | "^" | "~" | "*" => WriteCssSepCond::Equalable,
232 "|" => WriteCssSepCond::Bar,
233 "/" => WriteCssSepCond::Slash,
234 _ => WriteCssSepCond::Other,
235 };
236 Ok(())
237 }
238
239 pub fn write_function_block(
240 &mut self,
241 prefer_sep_before: bool,
242 name: &str,
243 f: impl FnOnce(&mut Self) -> Result,
244 ) -> Result {
245 self.prepare_write()?;
246 {
247 let CssWriter {
248 ref mut w,
249 ref mut sc,
250 debug_mode,
251 ..
252 } = self;
253 if *debug_mode && prefer_sep_before {
254 match sc {
255 WriteCssSepCond::Whitespace => {}
256 _ => {
257 write!(w, " ")?;
258 }
259 }
260 } else {
261 match sc {
262 WriteCssSepCond::Ident
263 | WriteCssSepCond::NonIdentAlpha
264 | WriteCssSepCond::Digit
265 | WriteCssSepCond::At => {
266 write!(w, " ")?;
267 }
268 _ => {}
269 }
270 }
271 write!(w, "{}(", name)?;
272 *sc = WriteCssSepCond::BlockStart;
273 }
274 f(self)?;
275 {
276 let CssWriter {
277 ref mut w,
278 ref mut sc,
279 ..
280 } = self;
281 write!(w, ")")?;
282 *sc = WriteCssSepCond::Other;
283 }
284 Ok(())
285 }
286
287 pub fn write_paren_block(&mut self, f: impl FnOnce(&mut Self) -> Result) -> Result {
288 self.prepare_write()?;
289 {
290 let CssWriter {
291 ref mut w,
292 ref mut sc,
293 debug_mode,
294 ..
295 } = self;
296 if *debug_mode {
297 match sc {
298 WriteCssSepCond::Whitespace => {}
299 _ => {
300 write!(w, " ")?;
301 }
302 }
303 } else {
304 match sc {
305 WriteCssSepCond::Ident => {
306 write!(w, " ")?;
307 }
308 _ => {}
309 }
310 }
311 write!(w, "(")?;
312 *sc = WriteCssSepCond::BlockStart;
313 }
314 f(self)?;
315 {
316 let CssWriter {
317 ref mut w,
318 ref mut sc,
319 ..
320 } = self;
321 write!(w, ")")?;
322 *sc = WriteCssSepCond::Other;
323 }
324 Ok(())
325 }
326
327 pub fn write_bracket_block(&mut self, f: impl FnOnce(&mut Self) -> Result) -> Result {
328 self.prepare_write()?;
329 {
330 let CssWriter {
331 ref mut w,
332 ref mut sc,
333 debug_mode,
334 ..
335 } = self;
336 if *debug_mode {
337 match sc {
338 WriteCssSepCond::Whitespace => {}
339 _ => {
340 write!(w, " ")?;
341 }
342 }
343 }
344 write!(w, "[")?;
345 *sc = WriteCssSepCond::BlockStart;
346 }
347 f(self)?;
348 {
349 let CssWriter {
350 ref mut w,
351 ref mut sc,
352 ..
353 } = self;
354 write!(w, "]")?;
355 *sc = WriteCssSepCond::Other;
356 }
357 Ok(())
358 }
359
360 pub fn write_brace_block(&mut self, f: impl FnOnce(&mut Self) -> Result) -> Result {
361 self.prepare_write()?;
362 {
363 let CssWriter {
364 ref mut w,
365 ref mut sc,
366 debug_mode,
367 ..
368 } = self;
369 if *debug_mode {
370 match sc {
371 WriteCssSepCond::Whitespace => {}
372 _ => {
373 write!(w, " ")?;
374 }
375 }
376 }
377 write!(w, "{{")?;
378 *sc = WriteCssSepCond::BlockStart;
379 }
380 self.tab_count += 1;
381 self.line_wrap()?;
382 f(self)?;
383 self.tab_count -= 1;
384 if self.line_status == LineStatus::BlockStart {
385 self.line_status = LineStatus::LineStart;
386 }
387 self.line_wrap()?;
388 self.prepare_write()?;
389 {
390 let CssWriter {
391 ref mut w,
392 ref mut sc,
393 ..
394 } = self;
395 write!(w, "}}")?;
396 *sc = WriteCssSepCond::Other;
397 }
398 self.line_wrap()?;
399 self.line_status = LineStatus::BlockStart;
400 Ok(())
401 }
402}
403
404pub trait WriteCss {
406 fn write_css_with_args<W: CssWriteTarget>(
408 &self,
409 cssw: &mut CssWriter<W>,
410 var_values: &[VarDynValue],
411 ) -> Result;
412
413 fn write_css<W: CssWriteTarget>(&self, cssw: &mut CssWriter<W>) -> Result {
415 self.write_css_with_args(cssw, &[])
416 }
417}
418
419#[derive(Debug, Clone, Copy, PartialEq)]
421pub enum WriteCssSepCond {
422 Ident,
426 NonIdentAlpha,
430 Digit,
434 At,
438 Dot,
442 PlusOrMinus,
446 Equalable,
450 Bar,
454 Slash,
458 BlockStart,
462 Whitespace,
466 Other,
470}
471
472impl<V: WriteCss> WriteCss for Option<V> {
473 fn write_css_with_args<W: CssWriteTarget>(
474 &self,
475 cssw: &mut CssWriter<W>,
476 values: &[VarDynValue],
477 ) -> std::result::Result<(), std::fmt::Error> {
478 match self {
479 Some(x) => x.write_css_with_args(cssw, values)?,
480 None => {}
481 }
482 Ok(())
483 }
484}