use crate::css::CallArgs;
use crate::error::Error;
use crate::ordermap::OrderMap;
use crate::output::{Format, Formatted};
use crate::sass::Function;
use crate::value::{Color, ListSeparator, Number, Numeric, Operator, Quotes};
use std::convert::TryFrom;
#[derive(Clone, Debug, Eq, PartialOrd)]
pub enum Value {
Bang(String),
Call(String, CallArgs),
Function(String, Option<Function>),
Literal(String, Quotes),
List(Vec<Value>, ListSeparator, bool),
Numeric(Numeric, bool),
Color(Color, Option<String>),
Null,
True,
False,
BinOp(Box<Value>, bool, Operator, bool, Box<Value>),
UnaryOp(Operator, Box<Value>),
Map(OrderMap<Value, Value>),
UnicodeRange(String),
Paren(Box<Value>),
}
impl Value {
pub fn scalar<T: Into<Number>>(v: T) -> Self {
Value::Numeric(Numeric::scalar(v), true)
}
pub fn type_name(&self) -> &'static str {
match *self {
Value::Color(..) => "color",
Value::Literal(..) => "string",
Value::Map(..) => "map",
Value::Numeric(..) => "number",
Value::List(..) => "list",
Value::Function(..) => "function",
Value::True | Value::False => "bool",
Value::Null => "null",
_ => "unknown",
}
}
pub fn is_calculated(&self) -> bool {
match *self {
Value::Numeric(.., calculated) => calculated,
Value::Color(_, None) => true,
_ => false,
}
}
pub fn into_calculated(self) -> Self {
match self {
Value::Numeric(num, _) => Value::Numeric(num, true),
Value::List(v, sep, bracketed) => Value::List(
v.into_iter().map(|i| i.into_calculated()).collect(),
sep,
bracketed,
),
other => other,
}
}
pub fn is_true(&self) -> bool {
!matches!(self, Value::False | Value::Null)
}
pub fn is_null(&self) -> bool {
match *self {
Value::Null => true,
Value::List(ref list, _, false) => {
list.iter().all(|v| v.is_null())
}
Value::Literal(ref s, Quotes::None) if s.is_empty() => true,
Value::Paren(ref v) => v.is_null(),
_ => false,
}
}
pub fn numeric_value(self) -> Result<Numeric, Self> {
match self {
Value::Numeric(num, ..) => Ok(num),
v => Err(v),
}
}
#[deprecated]
pub fn integer_value(&self) -> Result<i64, Error> {
match self {
&Value::Numeric(ref num, ..) => num
.value
.clone()
.into_integer()
.map_err(|_| Error::bad_value("an integer", self)),
v => Err(Error::bad_value("a number", v)),
}
}
pub fn unquote(self) -> Value {
match self {
Value::Literal(s, Quotes::None) => {
Value::Literal(s, Quotes::None)
}
Value::Literal(s, _) => {
let mut result = String::new();
let mut iter = s.chars().peekable();
while let Some(c) = iter.next() {
if c == '\\' {
let mut val: u32 = 0;
let mut got_num = false;
let nextchar = loop {
match iter.peek() {
Some(&c) if c.is_ascii_hexdigit() => {
val = val * 10 + u32::from(hexvalue(c));
got_num = true;
iter.next();
}
Some(' ') if got_num => {
iter.next();
break (None);
}
Some(_) if !got_num => break (iter.next()),
_ => break (None),
}
};
if got_num {
if let Ok(c) = char::try_from(val) {
result.push(c);
} else {
result.push('\u{fffd}');
}
}
match nextchar {
Some('\n') => {
result.push('\\');
result.push('a');
}
Some(c) => {
result.push(c);
}
None => (),
}
} else {
result.push(c)
}
}
Value::Literal(result, Quotes::None)
}
Value::List(list, s, b) => Value::List(
list.into_iter().map(|v| v.unquote()).collect(),
s,
b,
),
Value::Paren(v) => *v,
v => v,
}
}
pub fn iter_items(self) -> Vec<Value> {
match self {
Value::List(v, _, _) => v,
Value::Map(map) => map
.iter()
.map(|&(ref k, ref v)| {
Value::List(
vec![k.clone(), v.clone()],
ListSeparator::Space,
false,
)
})
.collect(),
Value::Paren(v) => v.iter_items(),
v => vec![v],
}
}
pub fn format(&self, format: Format) -> Formatted<Value> {
Formatted {
value: self,
format,
}
}
}
fn hexvalue(c: char) -> u8 {
if ('0'..='9').contains(&c) {
c as u8 - b'0'
} else if ('a'..='f').contains(&c) {
c as u8 - b'a' + 10
} else if ('A'..='F').contains(&c) {
c as u8 - b'A' + 10
} else {
0
}
}
impl PartialEq for Value {
fn eq(&self, other: &Value) -> bool {
match (&self, other) {
(Value::Bang(a), Value::Bang(b)) => a == b,
(Value::Numeric(a, _), Value::Numeric(b, _)) => a == b,
(Value::Literal(a, aq), Value::Literal(b, bq)) => {
if aq == bq {
a == b
} else {
let a = if aq.is_none() {
a.replace('\\', "\\\\")
} else {
a.clone()
};
let b = if bq.is_none() {
b.replace('\\', "\\\\")
} else {
b.clone()
};
a == b
}
}
(Value::Null, Value::Null) => true,
(Value::True, Value::True) => true,
(Value::False, Value::False) => true,
(Value::Color(a, _), Value::Color(b, _)) => a == b,
(Value::Call(af, aa), Value::Call(bf, ba)) => {
af == bf && aa == ba
}
(Value::Function(a, abody), Value::Function(b, bbody)) => {
a == b && abody == bbody
}
(Value::List(av, asep, ab), Value::List(bv, bsep, bb)) => {
av == bv && asep == bsep && ab == bb
}
(Value::Map(a), Value::Map(b)) => a == b,
(Value::UnaryOp(a, av), Value::UnaryOp(b, bv)) => {
a == b && av == bv
}
(
Value::BinOp(aa, _, ao, _, ab),
Value::BinOp(ba, _, bo, _, bb),
) => ao == bo && aa == ba && ab == bb,
(Value::UnicodeRange(a), Value::UnicodeRange(b)) => a == b,
(Value::Paren(a), Value::Paren(b)) => a == b,
(Value::List(a, ..), Value::Map(b)) => {
a.is_empty() && b.len() == 0
}
(Value::Map(a), Value::List(b, ..)) => {
a.len() == 0 && b.is_empty()
}
_ => false,
}
}
}
impl From<bool> for Value {
fn from(v: bool) -> Value {
match v {
true => Value::True,
false => Value::False,
}
}
}
impl From<&str> for Value {
fn from(s: &str) -> Value {
Value::Literal(s.into(), Quotes::None)
}
}
impl From<String> for Value {
fn from(s: String) -> Value {
Value::Literal(s, Quotes::None)
}
}
impl From<Numeric> for Value {
fn from(v: Numeric) -> Value {
Value::Numeric(v, true)
}
}
impl<C: Into<Color>> From<C> for Value {
fn from(c: C) -> Value {
Value::Color(c.into(), None)
}
}