use std::fmt::{self, Write};
use codemap::Span;
use crate::{ast::CssStmt, error::SassResult, lexer::Lexer, parse::MediaQueryParser};
#[derive(Debug, Clone)]
pub(crate) struct MediaRule {
pub query: Vec<MediaQuery>,
pub body: Vec<CssStmt>,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct MediaQuery {
pub modifier: Option<String>,
pub media_type: Option<String>,
pub conditions: Vec<String>,
pub conjunction: bool,
}
impl MediaQuery {
pub fn matches_all_types(&self) -> bool {
self.media_type.is_none()
|| self
.media_type
.as_ref()
.map_or(false, |v| v.to_ascii_lowercase() == "all")
}
pub fn condition(
conditions: Vec<String>,
conjunction: bool,
) -> Self {
Self {
modifier: None,
media_type: None,
conditions,
conjunction,
}
}
pub fn media_type(
media_type: Option<String>,
modifier: Option<String>,
conditions: Option<Vec<String>>,
) -> Self {
Self {
modifier,
conjunction: true,
media_type,
conditions: conditions.unwrap_or_default(),
}
}
pub fn parse_list(list: &str, span: Span) -> SassResult<Vec<Self>> {
let toks = Lexer::new_from_string(list, span);
MediaQueryParser::new(toks).parse()
}
#[allow(clippy::if_not_else)]
pub(crate) fn merge(&self, other: &Self) -> MediaQueryMergeResult {
if !self.conjunction || !other.conjunction {
return MediaQueryMergeResult::Unrepresentable;
}
let this_modifier = self.modifier.as_ref().map(|m| m.to_ascii_lowercase());
let this_type = self.media_type.as_ref().map(|m| m.to_ascii_lowercase());
let other_modifier = other.modifier.as_ref().map(|m| m.to_ascii_lowercase());
let other_type = other.media_type.as_ref().map(|m| m.to_ascii_lowercase());
if this_type.is_none() && other_type.is_none() {
return MediaQueryMergeResult::Success(Self::condition(
self.conditions
.iter()
.chain(&other.conditions)
.cloned()
.collect(),
true,
));
}
let modifier;
let media_type;
let conditions;
if (this_modifier.as_deref() == Some("not")) != (other_modifier.as_deref() == Some("not")) {
if this_modifier == other_modifier {
let negative_conditions = if this_modifier.as_deref() == Some("not") {
&self.conditions
} else {
&other.conditions
};
let positive_conditions = if this_modifier.as_deref() == Some("not") {
&other.conditions
} else {
&self.conditions
};
if negative_conditions
.iter()
.all(|feat| positive_conditions.contains(feat))
{
return MediaQueryMergeResult::Empty;
}
return MediaQueryMergeResult::Unrepresentable;
} else if self.matches_all_types() || other.matches_all_types() {
return MediaQueryMergeResult::Unrepresentable;
}
if this_modifier.as_deref() == Some("not") {
modifier = &other_modifier;
media_type = &other_type;
conditions = other.conditions.clone();
} else {
modifier = &this_modifier;
media_type = &this_type;
conditions = self.conditions.clone();
}
} else if this_modifier.as_deref() == Some("not") {
debug_assert_eq!(other_modifier.as_deref(), Some("not"));
if this_type != other_type {
return MediaQueryMergeResult::Unrepresentable;
}
let more_conditions = if self.conditions.len() > other.conditions.len() {
&self.conditions
} else {
&other.conditions
};
let fewer_conditions = if self.conditions.len() > other.conditions.len() {
&other.conditions
} else {
&self.conditions
};
if fewer_conditions
.iter()
.all(|feat| more_conditions.contains(feat))
{
modifier = &this_modifier;
media_type = &this_type;
conditions = more_conditions.clone();
} else {
return MediaQueryMergeResult::Unrepresentable;
}
} else if self.matches_all_types() {
modifier = &other_modifier;
media_type = if other.matches_all_types() && this_type.is_none() {
&None
} else {
&other_type
};
conditions = self
.conditions
.iter()
.chain(&other.conditions)
.cloned()
.collect();
} else if other.matches_all_types() {
modifier = &this_modifier;
media_type = &this_type;
conditions = self
.conditions
.iter()
.chain(&other.conditions)
.cloned()
.collect();
} else if this_type != other_type {
return MediaQueryMergeResult::Empty;
} else {
if this_modifier.is_some() {
modifier = &this_modifier;
} else {
modifier = &other_modifier;
}
media_type = &this_type;
conditions = self
.conditions
.iter()
.chain(&other.conditions)
.cloned()
.collect();
}
MediaQueryMergeResult::Success(MediaQuery {
media_type: if media_type == &this_type {
self.media_type.clone()
} else {
other.media_type.clone()
},
modifier: if modifier == &this_modifier {
self.modifier.clone()
} else {
other.modifier.clone()
},
conditions,
conjunction: true,
})
}
}
impl fmt::Display for MediaQuery {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(modifier) = &self.modifier {
f.write_str(modifier)?;
f.write_char(' ')?;
}
if let Some(media_type) = &self.media_type {
f.write_str(media_type)?;
if !&self.conditions.is_empty() {
f.write_str(" and ")?;
}
}
f.write_str(&self.conditions.join(" and "))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub(crate) enum MediaQueryMergeResult {
Empty,
Unrepresentable,
Success(MediaQuery),
}