#[derive(Eq, PartialEq, Debug)]
enum ParsingRowState {
Left,
Right,
RightBracketOpen(u16),
}
pub fn css_split_rows(css: &str) -> Vec<&str> {
let mut out: Vec<&str> = Vec::new();
let mut state = ParsingRowState::Left;
let mut start = 0;
for (index, char) in css.char_indices() {
if char == '{' {
if state == ParsingRowState::Right {
state = ParsingRowState::RightBracketOpen(1);
} else if let ParsingRowState::RightBracketOpen(counter) = &mut state {
*counter += 1;
} else {
panic!("unsupported use case");
}
}
if char == '}' {
let should_close = if let ParsingRowState::RightBracketOpen(counter) = &mut state {
if *counter > 1 {
*counter -= 1;
false
} else {
true
}
} else {
panic!("unsupported use case");
};
if should_close {
state = ParsingRowState::Right;
}
}
if char == ':' && state == ParsingRowState::Left {
state = ParsingRowState::Right;
}
if char == ';' && state == ParsingRowState::Right {
out.push(css[start..index].trim());
start = index + 1;
state = ParsingRowState::Left;
}
}
out.push(css[start..css.len()].trim());
out.into_iter()
.filter(|item| item.trim() != "")
.collect()
}
pub fn css_row_split_to_pair(row: &str) -> Option<(&str, String)> {
let chunks: Vec<&str> = row.split(':').collect();
if chunks.len() < 2 {
return None;
}
let key = chunks[0].trim();
let attr: String = chunks[1..].join(":").trim().into();
Some((key, attr))
}
pub fn find_brackets(line: &str) -> Option<(&str, &str, &str)> {
let mut start: Option<usize> = None;
let mut end: Option<usize> = None;
let chars: Vec<char> = line.chars().collect();
for (index, char) in chars.iter().enumerate() {
if *char == '{' {
start = Some(index);
break;
}
}
for (index, char) in chars.iter().enumerate().rev() {
if *char == '}' {
end = Some(index);
break;
}
}
if let (Some(start), Some(end)) = (start, end) {
let start_word = &line[0..start];
let central_word = &line[start + 1..end];
let end_word = &line[end + 1..];
return Some((start_word.trim(), central_word.trim(), end_word.trim()));
}
None
}
#[cfg(test)]
mod tests {
use super::{css_split_rows, css_row_split_to_pair, find_brackets};
#[test]
fn test_css_split_rows_simple() {
let css = "border-right: 3px solid #666;
border-bottom: 4px solid #444;";
let rows = css_split_rows(css);
assert_eq!(rows.len(), 2);
assert_eq!(rows[0], "border-right: 3px solid #666");
assert_eq!(rows[1], "border-bottom: 4px solid #444");
}
#[test]
fn test_css_split_rows_hover() {
let css = "transition: all .2s ease-in-out;
:hover {
transform: scale(1.2);
box-shadow: 54px 54px 14px rgba(0, 0, 0, 0.3), 58px 58px 14px rgba(0, 0, 0, 0.2), 62px 62px 14px rgba(0, 0, 0, 0.1);
};
";
let rows = css_split_rows(css);
assert_eq!(rows.len(), 2);
assert_eq!(rows[0], "transition: all .2s ease-in-out");
assert_eq!(rows[1], ":hover {
transform: scale(1.2);
box-shadow: 54px 54px 14px rgba(0, 0, 0, 0.3), 58px 58px 14px rgba(0, 0, 0, 0.2), 62px 62px 14px rgba(0, 0, 0, 0.1);
}");
}
#[test]
fn test_css_split_rows_media_query_1() {
let css = ":hover {
transform: scale(1.2);
};
@media screen and (min-width: 600px) {
:hover {
transform: scale(1.5);
}
};
";
let rows = css_split_rows(css);
assert_eq!(rows.len(), 2);
assert_eq!(rows[0], ":hover {
transform: scale(1.2);
}");
assert_eq!(rows[1], "@media screen and (min-width: 600px) {
:hover {
transform: scale(1.5);
}
}");
}
#[test]
fn test_css_split_rows_media_query_2() {
let css = "color: black;
:hover {
color: white;
};
@media screen and (min-width: 600px) {
color: red;
:hover {
green: green;
}
};
@media screen and (min-width: 1200px) {
color: blue;
:hover {
green: cyan;
}
};
";
let rows = css_split_rows(css);
assert_eq!(rows.len(), 4);
assert_eq!(rows[0], "color: black");
assert_eq!(rows[1], ":hover {
color: white;
}");
assert_eq!(rows[2], "@media screen and (min-width: 600px) {
color: red;
:hover {
green: green;
}
}");
assert_eq!(rows[3], "@media screen and (min-width: 1200px) {
color: blue;
:hover {
green: cyan;
}
}");
}
fn css_split_rows_pair(css: &str) -> Vec<(&str, String)> {
let mut out = Vec::new();
for row in css_split_rows(css) {
let pair = css_row_split_to_pair(row);
if let Some(pair) = pair {
out.push(pair);
} else {
log::error!("Problem with parsing this css line: {}", row);
}
}
out
}
#[test]
fn test_css_split_rows1() {
let css = "cursor: pointer;";
assert_eq!(
css_split_rows(css),
vec!("cursor: pointer")
);
let rows_pairs = css_split_rows_pair(css);
assert_eq!(
rows_pairs,
vec!(("cursor", "pointer".into()),)
);
}
#[test]
fn test_css_split_rows2() {
let css = "border: 1px solid black; padding: 10px; background-color: #e0e0e0; margin-bottom: 10px ; ";
assert_eq!(
css_split_rows(css),
vec!(
"border: 1px solid black",
"padding: 10px",
"background-color: #e0e0e0",
"margin-bottom: 10px"
)
);
let rows_pairs = css_split_rows_pair(css);
assert_eq!(
rows_pairs,
vec!(
("border", "1px solid black".into()),
("padding", "10px".into()),
("background-color", "#e0e0e0".into()),
("margin-bottom", "10px".into())
)
)
}
#[test]
fn test_css_split_rows3() {
let css = "border: 1px solid black; padding: 10px; background-color: #e0e0e0; margin-bottom: 10px ";
let rows = css_split_rows(css);
assert_eq!(
&rows,
&vec!(
"border: 1px solid black",
"padding: 10px",
"background-color: #e0e0e0",
"margin-bottom: 10px"
)
);
let rows_pairs = css_split_rows_pair(css);
assert_eq!(
rows_pairs,
vec!(
("border", "1px solid black".into()),
("padding", "10px".into()),
("background-color", "#e0e0e0".into()),
("margin-bottom", "10px".into())
)
)
}
#[test]
fn test_find_brackets() {
let css = "";
assert_eq!(find_brackets(css), None);
let css = "1.0s infinite ease-in-out";
assert_eq!(find_brackets(css), None);
let css = "1.0s infinite ease-in-out { dsd }";
assert_eq!(find_brackets(css), Some(("1.0s infinite ease-in-out", "dsd", "")));
let css = "1.0s infinite ease-in-out { dsd } fff";
assert_eq!(find_brackets(css), Some(("1.0s infinite ease-in-out", "dsd", "fff")));
let css = "@media screen { color: white }";
assert_eq!(find_brackets(css), Some(("@media screen", "color: white", "")));
}
}