1use std::borrow::Cow;
2
3pub enum Token {
9 UnwrapLiteral(String),
10}
11
12pub struct TokenWithId {
14 pub token: Token,
15 id: String,
16}
17
18pub struct Interpolation {
20 pub tokens: Vec<TokenWithId>,
21}
22impl Interpolation {
23 pub fn unwrap_literals(&self, source: &str) -> String {
24 let mut result = String::from(source);
25 for token in &self.tokens {
26 match &token.token {
27 Token::UnwrapLiteral(s) => result = result.replace(&token.id, s),
28 }
29 }
30 result
31 }
32}
33
34pub fn handle_interpolate(mut source: &str) -> (Interpolation, Cow<'_, str>) {
38 let mut state = Interpolation { tokens: vec![] };
39
40 let mut result = String::new();
41 let mut last_id = 0;
42 while let Some(start) = source[..].find("${") {
43 let end = source[start..].find("}").unwrap_or_else(|| {
44 panic!(
45 "No closing bracket found for interpolation token at position {}",
46 start
47 )
48 }) + start;
49
50 let token = &source[start + 2..end].trim_start();
51 let token_id = format!("__RCSS__TOKEN_{}", last_id);
52 last_id += 1;
53
54 if token.starts_with("\"") {
55 let token = token.trim_matches(|c: char| c.is_whitespace() || c == '"');
56 state.tokens.push(TokenWithId {
57 token: Token::UnwrapLiteral(token.to_string()),
58 id: token_id.clone(),
59 });
60 } else {
61 panic!(
62 "Only string literals are supported in interpolation \"#{{..}}\", got=\"{}\"",
63 token
64 );
65 }
66
67 result.push_str(&source[..start]);
68 result.push_str(&token_id);
69 source = &source[end + 1..];
70 }
71 if state.tokens.is_empty() {
72 return (state, Cow::Borrowed(source));
73 }
74 result.push_str(&source);
75 (state, result.into())
76}
77
78#[cfg(test)]
79mod test {
80 use super::*;
81
82 #[test]
83 fn test_handle_interpolate() {
84 let source = "background-color: red; left: ${\"0em\"};";
85 let (interpolation, result) = handle_interpolate(source);
86 assert_eq!(interpolation.tokens.len(), 1);
87 assert_eq!(result, "background-color: red; left: __RCSS__TOKEN_0;");
88 match &interpolation.tokens[0].token {
89 Token::UnwrapLiteral(s) => assert_eq!(s, "0em"),
90 }
91 }
92
93 #[test]
94 fn test_handle_unwrap() {
95 let source = "background-color: red; left: ${\"0em\"}; color: #${\"ff0000\"};";
96 let (interpolation, result) = handle_interpolate(source);
97 assert_eq!(
98 result,
99 "background-color: red; left: __RCSS__TOKEN_0; color: #__RCSS__TOKEN_1;"
100 );
101 assert_eq!(interpolation.tokens.len(), 2);
102 let result = interpolation.unwrap_literals(&result);
103 assert_eq!(result, "background-color: red; left: 0em; color: #ff0000;");
104 }
105}