css 0.0.22

A crate that uses the cssparser and selectors crates to provide a domain model for CSS Stylesheets. Intended to allow effective minification of CSS and CSS transformations such as autoprefixing and removal by other crates
// This file is part of css. It is subject to the license terms in the COPYRIGHT file found in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/css/master/COPYRIGHT. No part of predicator, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the COPYRIGHT file.
// Copyright © 2017 The developers of css. See the COPYRIGHT file in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/css/master/COPYRIGHT.

/// An @supports condition
///
/// https://drafts.csswg.org/css-conditional-3/#at-supports
#[derive(Debug, Clone)]
pub enum SupportsCondition
{
	/// `not (condition)`
	Not(Box<SupportsCondition>),
	
	/// `(condition)`
	Parenthesized(Box<SupportsCondition>),
	
	/// `(condition) and (condition) and (condition) ..`
	And(Vec<SupportsCondition>),
	
	/// `(condition) or (condition) or (condition) ..`
	Or(Vec<SupportsCondition>),
	
	/// `property-ident: value` (value can be any tokens)
	Declaration(SupportsPropertyDeclaration),
	
	/// `(any tokens)` or `func(any tokens)`
	FutureSyntax(String),
}

impl ToCss for SupportsCondition
{
	fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result
	{
		match *self
		{
			Not(ref condition) =>
			{
				dest.write_str("not ")?;
				condition.to_css(dest)
			}
			
			Parenthesized(ref condition) =>
			{
				dest.write_str("(")?;
				condition.to_css(dest)?;
				dest.write_str(")")
			}
			
			And(ref conditions) =>
			{
				let mut first = true;
				for condition in conditions
				{
					if first
					{
						first = false;
					}
					else
					{
						dest.write_str(" and ")?;
					}
					condition.to_css(dest)?;
				}
				Ok(())
			}
			
			Or(ref conditions) =>
			{
				let mut first = true;
				for condition in conditions
				{
					if first
					{
						first = false;
					}
					else
					{
						dest.write_str(" or ")?;
					}
					condition.to_css(dest)?;
				}
				Ok(())
			}
			
			Declaration(ref declaration) =>
			{
				dest.write_str("(")?;
				declaration.to_css(dest)?;
				dest.write_str(")")
			}
			
			FutureSyntax(ref value) => dest.write_str(&value),
		}
	}
}

impl SupportsCondition
{
	/// Parse a condition
	///
	/// https://drafts.csswg.org/css-conditional/#supports_condition
	pub(crate) fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, CustomParseError<'i>>>
	{
		if let Ok(_) = input.try(|i| i.expect_ident_matching("not"))
		{
			let inner = Self::parse_in_parentheses(input)?;
			return Ok(Not(Box::new(inner)));
		}
		
		let in_parentheses = Self::parse_in_parentheses(input)?;
		
		let (keyword, wrapper) = match input.next()
		{
			// End of input
			Err(_) => return Ok(in_parentheses),
			
			Ok(&Ident(ref ident)) =>
			{
				match_ignore_ascii_case!
				{
					&ident,
                    "and" => ("and", And as fn(_) -> _),
                    "or" => ("or", Or as fn(_) -> _),
                    _ => return Err(ParseError::Custom(CustomParseError::InvalidSupportsCondition(ident.clone())))
                }
			}
			
			Ok(unexpectedToken) => return Err(ParseError::Basic(BasicParseError::UnexpectedToken(unexpectedToken.clone()))),
		};
		
		let mut conditions = Vec::with_capacity(2);
		conditions.push(in_parentheses);
		loop
		{
			conditions.push(Self::parse_in_parentheses(input)?);
			if input.try(|input| input.expect_ident_matching(keyword)).is_err()
			{
				// Did not find the expected keyword.
				// If we found some other token,
				// it will be rejected by `Parser::parse_entirely` somewhere up the stack.
				return Ok(wrapper(conditions))
			}
		}
	}
	
	/// https://drafts.csswg.org/css-conditional-3/#supports_condition_in_parentheses
	fn parse_in_parentheses<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, CustomParseError<'i>>>
	{
		// Whitespace is normally taken care of in `Parser::next` but we want to not include it in `pos` for the SupportsCondition::FutureSyntax cases.
		while input.try(Parser::expect_whitespace).is_ok()
		{
		}
		
		let pos = input.position();
		
		match input.next()?.clone()
		{
			ParenthesisBlock =>
			{
				let nested = input.try(|input|
				{
					input.parse_nested_block(|i| Self::parse_condition_or_declaration(i))
				});
				
				if nested.is_ok()
				{
					return nested;
				}
			}
			
			Function(_) =>
			{
			}
			
			unexpectedToken => return Err(ParseError::Basic(BasicParseError::UnexpectedToken(unexpectedToken))),
		}
		input.parse_nested_block(|i| consume_any_value(i))?;
		Ok(FutureSyntax(input.slice_from(pos).to_owned()))
	}
	
	/// supports_condition | declaration
	/// https://drafts.csswg.org/css-conditional/#dom-css-supports-conditiontext-conditiontext
	fn parse_condition_or_declaration<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, CustomParseError<'i>>>
	{
		if let Ok(condition) = input.try(Self::parse)
		{
			Ok(Parenthesized(Box::new(condition)))
		}
		else
		{
			SupportsPropertyDeclaration::parse(input).map(Declaration)
		}
	}
}