less-rs 0.0.0

A Rust implementation of the Less CSS preprocessor.
Documentation
use std::ops::{Deref, DerefMut};

use cssparser::{ParserInput, Token};
use less_allocator::Vec;

use super::variable::VariableReference;
use crate::{
  printer::{Printer, PrinterError},
  properties::resolve_and_evaluate_variable,
  rules::variable::VariableValue,
};

/// A CSS token or less variable reference.
#[derive(Debug, Clone)]
pub enum TokenOrVariable<'i> {
  Token(Token<'i>),
  Variable(VariableReference<'i>),
}

#[derive(Debug)]
pub struct TokenList<'i>(Vec<'i, TokenOrVariable<'i>>);

impl<'i> Deref for TokenList<'i> {
  type Target = Vec<'i, TokenOrVariable<'i>>;

  fn deref(&self) -> &Self::Target {
    &self.0
  }
}

impl<'i> DerefMut for TokenList<'i> {
  fn deref_mut(&mut self) -> &mut Self::Target {
    &mut self.0
  }
}

impl<'i> TokenList<'i> {
  pub fn parse_raw_until_before(
    allocator: &'i less_allocator::Allocator,
    input: &'i str,
    delimiter: cssparser::Delimiters,
  ) -> Result<Self, cssparser::ParseError<'i, crate::error::ParserError<'i>>> {
    let mut input = ParserInput::new(input);
    let mut parser = cssparser::Parser::new(&mut input);
    Self::parse_until_before(allocator, &mut parser, delimiter)
  }

  pub fn parse_until_before(
    allocator: &'i less_allocator::Allocator,
    input: &mut cssparser::Parser<'i, '_>,
    delimiter: cssparser::Delimiters,
  ) -> Result<Self, cssparser::ParseError<'i, crate::error::ParserError<'i>>> {
    let mut tokens = Vec::new_in(allocator);

    input.parse_until_before(delimiter, |input| {
      loop {
        match input.next() {
          Ok(Token::AtKeyword(v)) => {
            tokens.push(TokenOrVariable::Variable(VariableReference(v.clone())))
          }
          Ok(t @ Token::Function(_)) => {
            tokens.push(TokenOrVariable::Token(t.clone()));
            input.parse_nested_block(|input| {
              let token = Self::parse_until_before(allocator, input, delimiter)?;
              tokens.extend(token.0.into_iter());
              Ok(())
            })?;
            // `.next()` will not return a `Token::CloseParenthesis` here,
            // as the parser will consume all the way to the closing parenthesis.
            // So we need to manually push it.
            tokens.push(TokenOrVariable::Token(Token::CloseParenthesis));
          }
          Ok(t) => tokens.push(TokenOrVariable::Token(t.clone())),
          Err(cssparser::BasicParseError {
            kind: cssparser::BasicParseErrorKind::EndOfInput,
            ..
          }) => return Ok(()),
          Err(_) => unreachable!(),
        }
      }
    })?;

    Ok(Self(tokens))
  }

  /// Recursively evaluate the token list, replacing less variables with new tokens.
  pub fn evaluate<W: std::fmt::Write>(
    &self,
    dest: &mut Printer<'_, 'i, W>,
  ) -> Result<EvaluatedTokens<'i>, PrinterError<'i>> {
    let mut evaluated_tokens: Vec<Token<'i>> = Vec::new_in(dest.allocator);
    for token in self.0.iter() {
      match token {
        TokenOrVariable::Token(token) => evaluated_tokens.push(token.clone()),
        TokenOrVariable::Variable(var) => {
          let variable = resolve_and_evaluate_variable(var, dest)?;
          let evaluted = match &*variable.value.borrow() {
            VariableValue::Unparsed(_) => unreachable!(),
            VariableValue::Parsed(token_list) => token_list.evaluate(dest)?,
          };
          variable.evaluating.set(false);
          evaluated_tokens.extend(evaluted.0);
        }
      }
    }
    Ok(EvaluatedTokens(evaluated_tokens))
  }
}

#[derive(Debug)]
pub struct EvaluatedTokens<'i>(Vec<'i, Token<'i>>);

impl<'i> Deref for EvaluatedTokens<'i> {
  type Target = Vec<'i, Token<'i>>;

  fn deref(&self) -> &Self::Target {
    &self.0
  }
}