From 94fe52a81d33c2313a5d5efd89d2391d7bb3e408 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 25 Jul 2021 09:53:18 -0400 Subject: [PATCH] support compressed lists and number values --- README.md | 1 + src/args.rs | 4 +- src/builtin/functions/color/hsl.rs | 71 ++++---- src/builtin/functions/color/opacity.rs | 2 +- src/builtin/functions/color/rgb.rs | 93 ++++++----- src/builtin/functions/list.rs | 10 +- src/builtin/functions/macros.rs | 16 +- src/builtin/functions/math.rs | 13 +- src/builtin/functions/string.rs | 10 +- src/color/mod.rs | 9 +- src/common.rs | 7 + src/lib.rs | 4 + src/output.rs | 6 +- src/parse/args.rs | 7 +- src/parse/control_flow.rs | 9 +- src/parse/ident.rs | 16 +- src/parse/keyframes.rs | 6 +- src/parse/media.rs | 11 +- src/parse/mod.rs | 24 ++- src/parse/module.rs | 4 +- src/parse/value/css_function.rs | 19 ++- src/parse/value/eval.rs | 220 ++++++++++++++++++++----- src/parse/value/parse.rs | 19 ++- src/selector/attribute.rs | 2 +- src/style.rs | 2 +- src/value/mod.rs | 46 ++++-- src/value/number/mod.rs | 38 +++-- tests/compressed.rs | 44 ++++- 28 files changed, 521 insertions(+), 192 deletions(-) diff --git a/README.md b/README.md index e62076d..8f9f7ce 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ complex uses of @at-root media queries with @import / as a separator in color functions, e.g. rgba(255, 255, 255 / 0) Infinity and -Infinity +builtin meta function keywords ``` All known missing features and bugs are tracked in [#19](https://github.com/connorskees/grass/issues/19). diff --git a/src/args.rs b/src/args.rs index c9aa636..e14ade4 100644 --- a/src/args.rs +++ b/src/args.rs @@ -64,7 +64,7 @@ impl CallArgs { CallArgs(HashMap::new(), span) } - pub fn to_css_string(self) -> SassResult> { + pub fn to_css_string(self, is_compressed: bool) -> SassResult> { let mut string = String::with_capacity(2 + self.len() * 10); string.push('('); let mut span = self.1; @@ -88,7 +88,7 @@ impl CallArgs { .iter() .map(|a| { span = span.merge(a.span); - a.node.to_css_string(a.span) + a.node.to_css_string(a.span, is_compressed) }) .collect::>>>()? .join(", "), diff --git a/src/builtin/functions/color/hsl.rs b/src/builtin/functions/color/hsl.rs index 89c7693..c3fab2e 100644 --- a/src/builtin/functions/color/hsl.rs +++ b/src/builtin/functions/color/hsl.rs @@ -90,13 +90,17 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas let mut string = format!( "{}({}, {}, {}", name, - v.to_css_string(args.span())?, - saturation.to_css_string(args.span())?, - lightness.to_css_string(args.span())? + v.to_css_string(args.span(), parser.options.is_compressed())?, + saturation.to_css_string(args.span(), parser.options.is_compressed())?, + lightness.to_css_string(args.span(), parser.options.is_compressed())? ); if !args.is_empty() { string.push_str(", "); - string.push_str(&args.get_err(3, "alpha")?.to_css_string(args.span())?); + string.push_str( + &args + .get_err(3, "alpha")? + .to_css_string(args.span(), parser.options.is_compressed())?, + ); } string.push(')'); return Ok(Value::String(string, QuoteKind::None)); @@ -117,13 +121,17 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas let mut string = format!( "{}({}, {}, {}", name, - hue, - v.to_css_string(args.span())?, - lightness.to_css_string(args.span())? + hue.to_string(parser.options.is_compressed()), + v.to_css_string(args.span(), parser.options.is_compressed())?, + lightness.to_css_string(args.span(), parser.options.is_compressed())? ); if !args.is_empty() { string.push_str(", "); - string.push_str(&args.get_err(3, "alpha")?.to_css_string(args.span())?); + string.push_str( + &args + .get_err(3, "alpha")? + .to_css_string(args.span(), parser.options.is_compressed())?, + ); } string.push(')'); return Ok(Value::String(string, QuoteKind::None)); @@ -132,7 +140,7 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas return Err(( format!( "$saturation: {} is not a number.", - v.to_css_string(args.span())? + v.to_css_string(args.span(), parser.options.is_compressed())? ), args.span(), ) @@ -146,13 +154,17 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas let mut string = format!( "{}({}, {}, {}", name, - hue, - saturation, - v.to_css_string(args.span())? + hue.to_string(parser.options.is_compressed()), + saturation.to_string(parser.options.is_compressed()), + v.to_css_string(args.span(), parser.options.is_compressed())? ); if !args.is_empty() { string.push_str(", "); - string.push_str(&args.get_err(3, "alpha")?.to_css_string(args.span())?); + string.push_str( + &args + .get_err(3, "alpha")? + .to_css_string(args.span(), parser.options.is_compressed())?, + ); } string.push(')'); return Ok(Value::String(string, QuoteKind::None)); @@ -161,7 +173,7 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas return Err(( format!( "$lightness: {} is not a number.", - v.to_css_string(args.span())? + v.to_css_string(args.span(), parser.options.is_compressed())? ), args.span(), ) @@ -180,7 +192,7 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas return Err(( format!( "$alpha: Expected {} to have no units or \"%\".", - v.to_css_string(args.span())? + v.to_css_string(args.span(), parser.options.is_compressed())? ), args.span(), ) @@ -191,10 +203,10 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas format!( "{}({}, {}, {}, {})", name, - hue, - saturation, - lightness, - v.to_css_string(args.span())? + hue.to_string(parser.options.is_compressed()), + saturation.to_string(parser.options.is_compressed()), + lightness.to_string(parser.options.is_compressed()), + v.to_css_string(args.span(), parser.options.is_compressed())? ), QuoteKind::None, )); @@ -276,7 +288,7 @@ pub(crate) fn adjust_hue(mut args: CallArgs, parser: &mut Parser) -> SassResult< return Err(( format!( "$degrees: {} is not a number.", - v.to_css_string(args.span())? + v.to_css_string(args.span(), parser.options.is_compressed())? ), args.span(), ) @@ -305,7 +317,7 @@ fn lighten(mut args: CallArgs, parser: &mut Parser) -> SassResult { return Err(( format!( "$amount: {} is not a number.", - v.to_css_string(args.span())? + v.to_css_string(args.span(), false)? ), args.span(), ) @@ -334,7 +346,7 @@ fn darken(mut args: CallArgs, parser: &mut Parser) -> SassResult { return Err(( format!( "$amount: {} is not a number.", - v.to_css_string(args.span())? + v.to_css_string(args.span(), false)? ), args.span(), ) @@ -350,7 +362,8 @@ fn saturate(mut args: CallArgs, parser: &mut Parser) -> SassResult { return Ok(Value::String( format!( "saturate({})", - args.get_err(0, "amount")?.to_css_string(args.span())? + args.get_err(0, "amount")? + .to_css_string(args.span(), false)? ), QuoteKind::None, )); @@ -363,7 +376,7 @@ fn saturate(mut args: CallArgs, parser: &mut Parser) -> SassResult { return Err(( format!( "$amount: {} is not a number.", - v.to_css_string(args.span())? + v.to_css_string(args.span(), false)? ), args.span(), ) @@ -374,7 +387,7 @@ fn saturate(mut args: CallArgs, parser: &mut Parser) -> SassResult { Value::Color(c) => c, Value::Dimension(Some(n), u, _) => { return Ok(Value::String( - format!("saturate({}{})", n, u), + format!("saturate({}{})", n.inspect(), u), QuoteKind::None, )) } @@ -408,7 +421,7 @@ fn desaturate(mut args: CallArgs, parser: &mut Parser) -> SassResult { return Err(( format!( "$amount: {} is not a number.", - v.to_css_string(args.span())? + v.to_css_string(args.span(), parser.options.is_compressed())? ), args.span(), ) @@ -424,7 +437,7 @@ pub(crate) fn grayscale(mut args: CallArgs, parser: &mut Parser) -> SassResult c, Value::Dimension(Some(n), u, _) => { return Ok(Value::String( - format!("grayscale({}{})", n, u), + format!("grayscale({}{})", n.inspect(), u), QuoteKind::None, )) } @@ -471,7 +484,7 @@ pub(crate) fn invert(mut args: CallArgs, parser: &mut Parser) -> SassResult SassResult SassResult Ok(Value::Dimension(Some(c.alpha()), Unit::None, true)), Value::Dimension(Some(num), unit, _) => Ok(Value::String( - format!("opacity({}{})", num, unit), + format!("opacity({}{})", num.inspect(), unit), QuoteKind::None, )), Value::Dimension(None, ..) => todo!(), diff --git a/src/builtin/functions/color/rgb.rs b/src/builtin/functions/color/rgb.rs index fec1711..948d4b3 100644 --- a/src/builtin/functions/color/rgb.rs +++ b/src/builtin/functions/color/rgb.rs @@ -50,9 +50,9 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas format!( "{}({}, {}, {})", name, - red.to_css_string(args.span())?, - green.to_css_string(args.span())?, - v.to_css_string(args.span())? + red.to_css_string(args.span(), parser.options.is_compressed())?, + green.to_css_string(args.span(), parser.options.is_compressed())?, + v.to_css_string(args.span(), parser.options.is_compressed())? ), QuoteKind::None, )); @@ -78,11 +78,16 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas Some(red) => format!( "{}({}, {}, {})", name, - red.to_css_string(args.span())?, - v.to_css_string(args.span())?, - blue + red.to_css_string(args.span(), parser.options.is_compressed())?, + v.to_css_string(args.span(), parser.options.is_compressed())?, + blue.to_string(parser.options.is_compressed()) + ), + None => format!( + "{}({} {})", + name, + v.to_css_string(args.span(), parser.options.is_compressed())?, + blue.to_string(parser.options.is_compressed()) ), - None => format!("{}({} {})", name, v.to_css_string(args.span())?, blue), }; return Ok(Value::String(string, QuoteKind::None)); } @@ -107,9 +112,9 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas format!( "{}({}, {}, {})", name, - v.to_css_string(args.span())?, - green, - blue + v.to_css_string(args.span(), parser.options.is_compressed())?, + green.to_string(parser.options.is_compressed()), + blue.to_string(parser.options.is_compressed()) ), QuoteKind::None, )); @@ -136,8 +141,8 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas format!( "{}({}, {})", name, - v.to_css_string(args.span())?, - alpha.to_css_string(args.span())? + v.to_css_string(args.span(), parser.options.is_compressed())?, + alpha.to_css_string(args.span(), parser.options.is_compressed())? ), QuoteKind::None, )); @@ -158,7 +163,7 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas return Err(( format!( "$alpha: Expected {} to have no units or \"%\".", - v.to_css_string(args.span())? + v.to_css_string(args.span(), parser.options.is_compressed())? ), args.span(), ) @@ -169,10 +174,10 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas format!( "{}({}, {}, {}, {})", name, - color.red(), - color.green(), - color.blue(), - v.to_css_string(args.span())? + color.red().to_string(parser.options.is_compressed()), + color.green().to_string(parser.options.is_compressed()), + color.blue().to_string(parser.options.is_compressed()), + v.to_css_string(args.span(), parser.options.is_compressed())? ), QuoteKind::None, )); @@ -197,7 +202,7 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas return Err(( format!( "$red: Expected {} to have no units or \"%\".", - v.to_css_string(args.span())? + v.to_css_string(args.span(), parser.options.is_compressed())? ), args.span(), ) @@ -209,13 +214,17 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas let mut string = format!( "{}({}, {}, {}", name, - v.to_css_string(args.span())?, - green.to_css_string(args.span())?, - blue.to_css_string(args.span())? + v.to_css_string(args.span(), parser.options.is_compressed())?, + green.to_css_string(args.span(), parser.options.is_compressed())?, + blue.to_css_string(args.span(), parser.options.is_compressed())? ); if !args.is_empty() { string.push_str(", "); - string.push_str(&args.get_err(3, "alpha")?.to_css_string(args.span())?); + string.push_str( + &args + .get_err(3, "alpha")? + .to_css_string(args.span(), parser.options.is_compressed())?, + ); } string.push(')'); return Ok(Value::String(string, QuoteKind::None)); @@ -238,7 +247,7 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas return Err(( format!( "$green: Expected {} to have no units or \"%\".", - v.to_css_string(args.span())? + v.to_css_string(args.span(), parser.options.is_compressed())? ), args.span(), ) @@ -249,13 +258,17 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas let mut string = format!( "{}({}, {}, {}", name, - red, - v.to_css_string(args.span())?, - blue.to_css_string(args.span())? + red.to_string(parser.options.is_compressed()), + v.to_css_string(args.span(), parser.options.is_compressed())?, + blue.to_css_string(args.span(), parser.options.is_compressed())? ); if !args.is_empty() { string.push_str(", "); - string.push_str(&args.get_err(3, "alpha")?.to_css_string(args.span())?); + string.push_str( + &args + .get_err(3, "alpha")? + .to_css_string(args.span(), parser.options.is_compressed())?, + ); } string.push(')'); return Ok(Value::String(string, QuoteKind::None)); @@ -278,7 +291,7 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas return Err(( format!( "$blue: Expected {} to have no units or \"%\".", - v.to_css_string(args.span())? + v.to_css_string(args.span(), parser.options.is_compressed())? ), args.span(), ) @@ -288,13 +301,17 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas let mut string = format!( "{}({}, {}, {}", name, - red, - green, - v.to_css_string(args.span())? + red.to_string(parser.options.is_compressed()), + green.to_string(parser.options.is_compressed()), + v.to_css_string(args.span(), parser.options.is_compressed())? ); if !args.is_empty() { string.push_str(", "); - string.push_str(&args.get_err(3, "alpha")?.to_css_string(args.span())?); + string.push_str( + &args + .get_err(3, "alpha")? + .to_css_string(args.span(), parser.options.is_compressed())?, + ); } string.push(')'); return Ok(Value::String(string, QuoteKind::None)); @@ -319,7 +336,7 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas return Err(( format!( "$alpha: Expected {} to have no units or \"%\".", - v.to_css_string(args.span())? + v.to_css_string(args.span(), parser.options.is_compressed())? ), args.span(), ) @@ -329,10 +346,10 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser) -> Sas let string = format!( "{}({}, {}, {}, {})", name, - red, - green, - blue, - v.to_css_string(args.span())? + red.to_string(parser.options.is_compressed()), + green.to_string(parser.options.is_compressed()), + blue.to_string(parser.options.is_compressed()), + v.to_css_string(args.span(), parser.options.is_compressed())? ); return Ok(Value::String(string, QuoteKind::None)); } @@ -429,7 +446,7 @@ pub(crate) fn mix(mut args: CallArgs, parser: &mut Parser) -> SassResult return Err(( format!( "$weight: {} is not a number.", - v.to_css_string(args.span())? + v.to_css_string(args.span(), parser.options.is_compressed())? ), args.span(), ) diff --git a/src/builtin/functions/list.rs b/src/builtin/functions/list.rs index 470a90b..2d81e88 100644 --- a/src/builtin/functions/list.rs +++ b/src/builtin/functions/list.rs @@ -45,7 +45,7 @@ pub(crate) fn nth(mut args: CallArgs, parser: &mut Parser) -> SassResult return Err(( format!( "$n: Invalid index {}{} for a list with {} elements.", - n, + n.inspect(), unit, list.len() ), @@ -55,7 +55,7 @@ pub(crate) fn nth(mut args: CallArgs, parser: &mut Parser) -> SassResult } if n.is_decimal() { - return Err((format!("$n: {} is not an int.", n), args.span()).into()); + return Err((format!("$n: {} is not an int.", n.inspect()), args.span()).into()); } Ok(list.remove(if n.is_positive() { @@ -114,7 +114,9 @@ pub(crate) fn set_nth(mut args: CallArgs, parser: &mut Parser) -> SassResult SassResult SassResult SassResult n, None => { return Err(( - format!("max must be in range 0 < max \u{2264} 2^32, was {}", limit), + format!( + "max must be in range 0 < max \u{2264} 2^32, was {}", + limit.inspect() + ), args.span(), ) .into()) diff --git a/src/builtin/functions/string.rs b/src/builtin/functions/string.rs index 72ea2d0..3a778b2 100644 --- a/src/builtin/functions/string.rs +++ b/src/builtin/functions/string.rs @@ -100,7 +100,7 @@ pub(crate) fn str_slice(mut args: CallArgs, parser: &mut Parser) -> SassResult { - return Err((format!("{} is not an int.", n), args.span()).into()) + return Err((format!("{} is not an int.", n.inspect()), args.span()).into()) } Value::Dimension(Some(n), Unit::None, _) if n.is_positive() => { n.to_integer().to_usize().unwrap_or(str_len + 1) @@ -133,7 +133,7 @@ pub(crate) fn str_slice(mut args: CallArgs, parser: &mut Parser) -> SassResult { - return Err((format!("{} is not an int.", n), args.span()).into()) + return Err((format!("{} is not an int.", n.inspect()), args.span()).into()) } Value::Dimension(Some(n), Unit::None, _) if n.is_positive() => { n.to_integer().to_usize().unwrap_or(str_len + 1) @@ -240,7 +240,11 @@ pub(crate) fn str_insert(mut args: CallArgs, parser: &mut Parser) -> SassResult< let index = match args.get_err(2, "index")? { Value::Dimension(Some(n), Unit::None, _) if n.is_decimal() => { - return Err((format!("$index: {} is not an int.", n), args.span()).into()) + return Err(( + format!("$index: {} is not an int.", n.inspect()), + args.span(), + ) + .into()) } Value::Dimension(Some(n), Unit::None, _) => n, Value::Dimension(None, Unit::None, ..) => { diff --git a/src/color/mod.rs b/src/color/mod.rs index 218ac4a..d4bc53a 100644 --- a/src/color/mod.rs +++ b/src/color/mod.rs @@ -590,7 +590,14 @@ fn repr(red: &Number, green: &Number, blue: &Number, alpha: &Number) -> String { let blue_u8 = into_u8(blue); if alpha < &Number::one() { - format!("rgba({}, {}, {}, {})", red_u8, green_u8, blue_u8, alpha) + format!( + "rgba({}, {}, {}, {})", + red_u8, + green_u8, + blue_u8, + // todo: is_compressed + alpha.inspect() + ) } else if let Some(c) = NAMED_COLORS.get_by_rgba([red_u8, green_u8, blue_u8]) { (*c).to_owned() } else { diff --git a/src/common.rs b/src/common.rs index 0a3624d..7f5b037 100644 --- a/src/common.rs +++ b/src/common.rs @@ -99,6 +99,13 @@ impl ListSeparator { } } + pub fn as_compressed_str(self) -> &'static str { + match self { + Self::Space => " ", + Self::Comma => ",", + } + } + pub fn name(self) -> &'static str { match self { Self::Space => "space", diff --git a/src/lib.rs b/src/lib.rs index ec90cb1..9914158 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -260,6 +260,10 @@ impl<'a> Options<'a> { self.unicode_error_messages = unicode_error_messages; self } + + pub(crate) fn is_compressed(&self) -> bool { + matches!(self.style, OutputStyle::Compressed) + } } fn raw_to_parse_error(map: &CodeMap, err: Error, unicode: bool) -> Box { diff --git a/src/output.rs b/src/output.rs index caadbba..eec1e57 100644 --- a/src/output.rs +++ b/src/output.rs @@ -511,7 +511,7 @@ impl Formatter for CompressedFormatter { write!(buf, "}}")?; } Toplevel::Style(style) => { - let value = style.value.node.to_css_string(style.value.span)?; + let value = style.value.node.to_css_string(style.value.span, true)?; write!(buf, "{}:{};", style.property, value)?; } } @@ -542,7 +542,7 @@ impl CompressedFormatter { for style in &mut styles { match style { BlockEntry::Style(s) => { - let value = s.value.node.to_css_string(s.value.span)?; + let value = s.value.node.to_css_string(s.value.span, true)?; write!(buf, "{}:{}", s.property, value)?; break; } @@ -554,7 +554,7 @@ impl CompressedFormatter { for style in styles { match style { BlockEntry::Style(s) => { - let value = s.value.node.to_css_string(s.value.span)?; + let value = s.value.node.to_css_string(s.value.span, true)?; write!(buf, ";{}:{}", s.property, value)?; } diff --git a/src/parse/args.rs b/src/parse/args.rs index c939da2..1fa5a2b 100644 --- a/src/parse/args.rs +++ b/src/parse/args.rs @@ -299,8 +299,11 @@ impl<'a, 'b> Parser<'a, 'b> { let value = format!( "{}={}", - left.node.to_css_string(left.span)?, - right.node.to_css_string(right.span)? + left.node + .to_css_string(left.span, self.options.is_compressed())?, + right + .node + .to_css_string(right.span, self.options.is_compressed())? ); args.insert( diff --git a/src/parse/control_flow.rs b/src/parse/control_flow.rs index 03abd01..54f3d84 100644 --- a/src/parse/control_flow.rs +++ b/src/parse/control_flow.rs @@ -211,7 +211,7 @@ impl<'a, 'b> Parser<'a, 'b> { let from = match from_val.node { Value::Dimension(Some(n), ..) => match n.to_i32() { Some(std::i32::MAX) | Some(std::i32::MIN) | None => { - return Err((format!("{} is not an int.", n), from_val.span).into()) + return Err((format!("{} is not an int.", n.inspect()), from_val.span).into()) } Some(v) => v, }, @@ -229,14 +229,17 @@ impl<'a, 'b> Parser<'a, 'b> { let to = match to_val.node { Value::Dimension(Some(n), ..) => match n.to_i32() { Some(std::i32::MAX) | Some(std::i32::MIN) | None => { - return Err((format!("{} is not an int.", n), to_val.span).into()) + return Err((format!("{} is not an int.", n.inspect()), to_val.span).into()) } Some(v) => v, }, Value::Dimension(None, ..) => return Err(("NaN is not an int.", from_val.span).into()), v => { return Err(( - format!("{} is not a number.", v.to_css_string(to_val.span)?), + format!( + "{} is not a number.", + v.to_css_string(to_val.span, self.options.is_compressed())? + ), to_val.span, ) .into()) diff --git a/src/parse/ident.rs b/src/parse/ident.rs index e72f79e..d6cb028 100644 --- a/src/parse/ident.rs +++ b/src/parse/ident.rs @@ -64,7 +64,11 @@ impl<'a, 'b> Parser<'a, 'b> { self.toks.next(); // TODO: if ident, interpolate literally let interpolation = self.parse_interpolation()?; - buf.push_str(&interpolation.node.to_css_string(interpolation.span)?); + buf.push_str( + &interpolation + .node + .to_css_string(interpolation.span, self.options.is_compressed())?, + ); } else { self.toks.reset_cursor(); break; @@ -179,7 +183,10 @@ impl<'a, 'b> Parser<'a, 'b> { self.toks.next(); match self.parse_interpolation()?.node { Value::String(ref s, ..) => text.push_str(s), - v => text.push_str(v.to_css_string(self.span_before)?.borrow()), + v => text.push_str( + v.to_css_string(self.span_before, self.options.is_compressed())? + .borrow(), + ), } } _ => return Err(("Expected identifier.", pos).into()), @@ -268,7 +275,10 @@ impl<'a, 'b> Parser<'a, 'b> { let interpolation = self.parse_interpolation()?; match interpolation.node { Value::String(ref v, ..) => s.push_str(v), - v => s.push_str(v.to_css_string(interpolation.span)?.borrow()), + v => s.push_str( + v.to_css_string(interpolation.span, self.options.is_compressed())? + .borrow(), + ), }; continue; } diff --git a/src/parse/keyframes.rs b/src/parse/keyframes.rs index 57b0110..d4000d3 100644 --- a/src/parse/keyframes.rs +++ b/src/parse/keyframes.rs @@ -126,7 +126,11 @@ impl<'a, 'b> Parser<'a, 'b> { match tok.kind { '#' => { if self.consume_char_if_exists('{') { - string.push_str(&self.parse_interpolation()?.to_css_string(span)?); + string.push_str( + &self + .parse_interpolation()? + .to_css_string(span, self.options.is_compressed())?, + ); } else { string.push('#'); } diff --git a/src/parse/media.rs b/src/parse/media.rs index 70c0bb4..6101cc8 100644 --- a/src/parse/media.rs +++ b/src/parse/media.rs @@ -51,7 +51,10 @@ impl<'a, 'b> Parser<'a, 'b> { _ => false, })?; - value.node.unquote().to_css_string(value.span) + value + .node + .unquote() + .to_css_string(value.span, self.options.is_compressed()) } pub(super) fn parse_media_query_list(&mut self) -> SassResult { @@ -92,7 +95,11 @@ impl<'a, 'b> Parser<'a, 'b> { })?; self.expect_char(')')?; - buf.push_str(&value.node.to_css_string(value.span)?); + buf.push_str( + &value + .node + .to_css_string(value.span, self.options.is_compressed())?, + ); self.whitespace_or_comment(); buf.push(')'); diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 47bf1a3..9a0e702 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -210,7 +210,7 @@ impl<'a, 'b> Parser<'a, 'b> { self.consume_char_if_exists(';'); self.warn(&Spanned { - node: message.to_css_string(span)?, + node: message.to_css_string(span, false)?, span, }); } @@ -407,7 +407,11 @@ impl<'a, 'b> Parser<'a, 'b> { match kind { '#' => { if self.consume_char_if_exists('{') { - string.push_str(&self.parse_interpolation()?.to_css_string(span)?); + string.push_str( + &self + .parse_interpolation()? + .to_css_string(span, self.options.is_compressed())?, + ); } else { string.push('#'); } @@ -513,7 +517,11 @@ impl<'a, 'b> Parser<'a, 'b> { } ('#', Some(Token { kind: '{', .. })) => { self.toks.next(); - comment.push_str(&self.parse_interpolation()?.to_css_string(span)?); + comment.push_str( + &self + .parse_interpolation()? + .to_css_string(span, self.options.is_compressed())?, + ); continue; } (..) => comment.push(tok.kind), @@ -541,7 +549,7 @@ impl<'a, 'b> Parser<'a, 'b> { let interpolation = self.parse_interpolation()?; Ok(match interpolation.node { Value::String(v, ..) => Cow::owned(v), - v => v.to_css_string(interpolation.span)?, + v => v.to_css_string(interpolation.span, self.options.is_compressed())?, }) } @@ -855,7 +863,7 @@ impl<'a, 'b> Parser<'a, 'b> { return Err(( format!( "compound selectors may no longer be extended.\nConsider `@extend {}` instead.\nSee http://bit.ly/ExtendCompound for details.\n", - compound.components.iter().map(|x| x.to_string()).collect::>().join(", ") + compound.components.iter().map(ToString::to_string).collect::>().join(", ") ) , self.span_before).into()); } @@ -922,7 +930,11 @@ impl<'a, 'b> Parser<'a, 'b> { self.toks.next(); self.span_before = pos; let interpolation = self.parse_interpolation()?; - params.push_str(&interpolation.node.to_css_string(interpolation.span)?); + params.push_str( + &interpolation + .node + .to_css_string(interpolation.span, self.options.is_compressed())?, + ); continue; } diff --git a/src/parse/module.rs b/src/parse/module.rs index a404cca..9242c4e 100644 --- a/src/parse/module.rs +++ b/src/parse/module.rs @@ -197,7 +197,9 @@ impl<'a, 'b> Parser<'a, 'b> { }; let Spanned { node: module, span } = self.parse_quoted_string(quote)?; - let module_name = module.unquote().to_css_string(span)?; + let module_name = module + .unquote() + .to_css_string(span, self.options.is_compressed())?; self.whitespace_or_comment(); diff --git a/src/parse/value/css_function.rs b/src/parse/value/css_function.rs index 1a38eca..58168fc 100644 --- a/src/parse/value/css_function.rs +++ b/src/parse/value/css_function.rs @@ -20,7 +20,11 @@ impl<'a, 'b> Parser<'a, 'b> { self.span_before = pos; self.toks.next(); let interpolation = self.parse_interpolation()?; - buf.push_str(&interpolation.node.to_css_string(interpolation.span)?); + buf.push_str( + &interpolation + .node + .to_css_string(interpolation.span, self.options.is_compressed())?, + ); } else { buf.push('#'); } @@ -93,7 +97,10 @@ impl<'a, 'b> Parser<'a, 'b> { let interpolation = self.parse_interpolation()?; match interpolation.node { Value::String(ref s, ..) => buf.push_str(s), - v => buf.push_str(v.to_css_string(interpolation.span)?.borrow()), + v => buf.push_str( + v.to_css_string(interpolation.span, self.options.is_compressed())? + .borrow(), + ), }; } else { buf.push('#'); @@ -142,7 +149,11 @@ impl<'a, 'b> Parser<'a, 'b> { match kind { '+' | '-' | '0'..='9' => { let number = self.parse_dimension(&|_| false)?; - buf.push_str(&number.node.to_css_string(number.span)?); + buf.push_str( + &number + .node + .to_css_string(number.span, self.options.is_compressed())?, + ); } '#' => { self.toks.next(); @@ -316,7 +327,7 @@ impl<'a, 'b> Parser<'a, 'b> { q @ ('"' | '\'') => { self.toks.next(); let s = self.parse_quoted_string(q)?; - buffer.push_str(&s.node.to_css_string(s.span)?); + buffer.push_str(&s.node.to_css_string(s.span, self.options.is_compressed())?); wrote_newline = false; } '/' => { diff --git a/src/parse/value/eval.rs b/src/parse/value/eval.rs index b33104f..e0ab381 100644 --- a/src/parse/value/eval.rs +++ b/src/parse/value/eval.rs @@ -122,14 +122,26 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { Value::Dimension(Some(-n), u, should_divide) } Value::Dimension(None, u, should_divide) => Value::Dimension(None, u, should_divide), - v => Value::String(format!("-{}", v.to_css_string(self.span)?), QuoteKind::None), + v => Value::String( + format!( + "-{}", + v.to_css_string(self.span, self.parser.options.is_compressed())? + ), + QuoteKind::None, + ), }) } fn unary_plus(&self, val: Value) -> SassResult { Ok(match val { v @ Value::Dimension(..) => v, - v => Value::String(format!("+{}", v.to_css_string(self.span)?), QuoteKind::None), + v => Value::String( + format!( + "+{}", + v.to_css_string(self.span, self.parser.options.is_compressed())? + ), + QuoteKind::None, + ), }) } @@ -176,28 +188,36 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { } Value::True | Value::False => match right { Value::String(s, QuoteKind::Quoted) => Value::String( - format!("{}{}", left.to_css_string(self.span)?, s), + format!( + "{}{}", + left.to_css_string(self.span, self.parser.options.is_compressed())?, + s + ), QuoteKind::Quoted, ), _ => Value::String( format!( "{}{}", - left.to_css_string(self.span)?, - right.to_css_string(self.span)? + left.to_css_string(self.span, self.parser.options.is_compressed())?, + right.to_css_string(self.span, self.parser.options.is_compressed())? ), QuoteKind::None, ), }, Value::Important => match right { Value::String(s, ..) => Value::String( - format!("{}{}", left.to_css_string(self.span)?, s), + format!( + "{}{}", + left.to_css_string(self.span, self.parser.options.is_compressed())?, + s + ), QuoteKind::None, ), _ => Value::String( format!( "{}{}", - left.to_css_string(self.span)?, - right.to_css_string(self.span)? + left.to_css_string(self.span, self.parser.options.is_compressed())?, + right.to_css_string(self.span, self.parser.options.is_compressed())? ), QuoteKind::None, ), @@ -205,7 +225,9 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { Value::Null => match right { Value::Null => Value::Null, _ => Value::String( - right.to_css_string(self.span)?.into_owned(), + right + .to_css_string(self.span, self.parser.options.is_compressed())? + .into_owned(), QuoteKind::None, ), }, @@ -230,14 +252,34 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { Value::Dimension(Some(num + num2.convert(&unit2, &unit)), unit, true) } } - Value::String(s, q) => Value::String(format!("{}{}{}", num, unit, s), q), - Value::Null => Value::String(format!("{}{}", num, unit), QuoteKind::None), + Value::String(s, q) => Value::String( + format!( + "{}{}{}", + num.to_string(self.parser.options.is_compressed()), + unit, + s + ), + q, + ), + Value::Null => Value::String( + format!( + "{}{}", + num.to_string(self.parser.options.is_compressed()), + unit + ), + QuoteKind::None, + ), Value::True | Value::False | Value::List(..) | Value::Important | Value::ArgList(..) => Value::String( - format!("{}{}{}", num, unit, right.to_css_string(self.span)?), + format!( + "{}{}{}", + num.to_string(self.parser.options.is_compressed()), + unit, + right.to_css_string(self.span, self.parser.options.is_compressed())? + ), QuoteKind::None, ), Value::Map(..) | Value::FunctionRef(..) => { @@ -251,7 +293,7 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { return Err(( format!( "Undefined operation \"{}{} + {}\".", - num, + num.inspect(), unit, right.inspect(self.span)? ), @@ -264,7 +306,11 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { Value::String(s, q) => Value::String(format!("{}{}", c, s), q), Value::Null => Value::String(c.to_string(), QuoteKind::None), Value::List(..) => Value::String( - format!("{}{}", c, right.to_css_string(self.span)?), + format!( + "{}{}", + c, + right.to_css_string(self.span, self.parser.options.is_compressed())? + ), QuoteKind::None, ), _ => { @@ -281,17 +327,25 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { }, Value::String(text, quotes) => match right { Value::String(text2, ..) => Value::String(text + &text2, quotes), - _ => Value::String(text + &right.to_css_string(self.span)?, quotes), + _ => Value::String( + text + &right.to_css_string(self.span, self.parser.options.is_compressed())?, + quotes, + ), }, Value::List(..) | Value::ArgList(..) => match right { - Value::String(s, q) => { - Value::String(format!("{}{}", left.to_css_string(self.span)?, s), q) - } + Value::String(s, q) => Value::String( + format!( + "{}{}", + left.to_css_string(self.span, self.parser.options.is_compressed())?, + s + ), + q, + ), _ => Value::String( format!( "{}{}", - left.to_css_string(self.span)?, - right.to_css_string(self.span)? + left.to_css_string(self.span, self.parser.options.is_compressed())?, + right.to_css_string(self.span, self.parser.options.is_compressed())? ), QuoteKind::None, ), @@ -314,7 +368,10 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { }; Ok(match left { Value::Null => Value::String( - format!("-{}", right.to_css_string(self.span)?), + format!( + "-{}", + right.to_css_string(self.span, self.parser.options.is_compressed())? + ), QuoteKind::None, ), v @ Value::Dimension(None, ..) => v, @@ -344,7 +401,12 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { | Value::True | Value::False | Value::ArgList(..) => Value::String( - format!("{}{}-{}", num, unit, right.to_css_string(self.span)?), + format!( + "{}{}-{}", + num.to_string(self.parser.options.is_compressed()), + unit, + right.to_css_string(self.span, self.parser.options.is_compressed())? + ), QuoteKind::None, ), Value::Map(..) | Value::FunctionRef(..) => { @@ -358,7 +420,7 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { return Err(( format!( "Undefined operation \"{}{} - {}\".", - num, + num.inspect(), unit, right.inspect(self.span)? ), @@ -366,7 +428,14 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { ) .into()) } - Value::Null => Value::String(format!("{}{}-", num, unit), QuoteKind::None), + Value::Null => Value::String( + format!( + "{}{}-", + num.to_string(self.parser.options.is_compressed()), + unit + ), + QuoteKind::None, + ), }, Value::Color(c) => match right { Value::String(s, q) => { @@ -385,32 +454,45 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { .into()) } _ => Value::String( - format!("{}-{}", c, right.to_css_string(self.span)?), + format!( + "{}-{}", + c, + right.to_css_string(self.span, self.parser.options.is_compressed())? + ), QuoteKind::None, ), }, Value::String(..) => Value::String( format!( "{}-{}", - left.to_css_string(self.span)?, - right.to_css_string(self.span)? + left.to_css_string(self.span, self.parser.options.is_compressed())?, + right.to_css_string(self.span, self.parser.options.is_compressed())? ), QuoteKind::None, ), _ => match right { Value::String(s, q) => Value::String( - format!("{}-{}{}{}", left.to_css_string(self.span)?, q, s, q), + format!( + "{}-{}{}{}", + left.to_css_string(self.span, self.parser.options.is_compressed())?, + q, + s, + q + ), QuoteKind::None, ), Value::Null => Value::String( - format!("{}-", left.to_css_string(self.span)?), + format!( + "{}-", + left.to_css_string(self.span, self.parser.options.is_compressed())? + ), QuoteKind::None, ), _ => Value::String( format!( "{}-{}", - left.to_css_string(self.span)?, - right.to_css_string(self.span)? + left.to_css_string(self.span, self.parser.options.is_compressed())?, + right.to_css_string(self.span, self.parser.options.is_compressed())? ), QuoteKind::None, ), @@ -448,7 +530,7 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { return Err(( format!( "Undefined operation \"{}{} * {}\".", - num, + num.inspect(), unit, right.inspect(self.span)? ), @@ -487,7 +569,10 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { }; Ok(match left { Value::Null => Value::String( - format!("/{}", right.to_css_string(self.span)?), + format!( + "/{}", + right.to_css_string(self.span, self.parser.options.is_compressed())? + ), QuoteKind::None, ), Value::Dimension(None, ..) => todo!(), @@ -537,24 +622,50 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { } } else { Value::String( - format!("{}{}/{}{}", num, unit, num2, unit2), + format!( + "{}{}/{}{}", + num.to_string(self.parser.options.is_compressed()), + unit, + num2.to_string(self.parser.options.is_compressed()), + unit2 + ), QuoteKind::None, ) } } - Value::String(s, q) => { - Value::String(format!("{}{}/{}{}{}", num, unit, q, s, q), QuoteKind::None) - } + Value::String(s, q) => Value::String( + format!( + "{}{}/{}{}{}", + num.to_string(self.parser.options.is_compressed()), + unit, + q, + s, + q + ), + QuoteKind::None, + ), Value::List(..) | Value::True | Value::False | Value::Important | Value::Color(..) | Value::ArgList(..) => Value::String( - format!("{}{}/{}", num, unit, right.to_css_string(self.span)?), + format!( + "{}{}/{}", + num.to_string(self.parser.options.is_compressed()), + unit, + right.to_css_string(self.span, self.parser.options.is_compressed())? + ), + QuoteKind::None, + ), + Value::Null => Value::String( + format!( + "{}{}/", + num.to_string(self.parser.options.is_compressed()), + unit + ), QuoteKind::None, ), - Value::Null => Value::String(format!("{}{}/", num, unit), QuoteKind::None), Value::Map(..) | Value::FunctionRef(..) => { return Err(( format!("{} isn't a valid CSS value.", right.inspect(self.span)?), @@ -580,7 +691,11 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { .into()) } _ => Value::String( - format!("{}/{}", c, right.to_css_string(self.span)?), + format!( + "{}/{}", + c, + right.to_css_string(self.span, self.parser.options.is_compressed())? + ), QuoteKind::None, ), }, @@ -596,7 +711,13 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { | Value::Color(..) | Value::List(..) | Value::ArgList(..) => Value::String( - format!("{}{}{}/{}", q1, s1, q1, right.to_css_string(self.span)?), + format!( + "{}{}{}/{}", + q1, + s1, + q1, + right.to_css_string(self.span, self.parser.options.is_compressed())? + ), QuoteKind::None, ), Value::Null => Value::String(format!("{}{}{}/", q1, s1, q1), QuoteKind::None), @@ -610,18 +731,27 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> { }, _ => match right { Value::String(s, q) => Value::String( - format!("{}/{}{}{}", left.to_css_string(self.span)?, q, s, q), + format!( + "{}/{}{}{}", + left.to_css_string(self.span, self.parser.options.is_compressed())?, + q, + s, + q + ), QuoteKind::None, ), Value::Null => Value::String( - format!("{}/", left.to_css_string(self.span)?), + format!( + "{}/", + left.to_css_string(self.span, self.parser.options.is_compressed())? + ), QuoteKind::None, ), _ => Value::String( format!( "{}/{}", - left.to_css_string(self.span)?, - right.to_css_string(self.span)? + left.to_css_string(self.span, self.parser.options.is_compressed())?, + right.to_css_string(self.span, self.parser.options.is_compressed())? ), QuoteKind::None, ), diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index 536a34a..a420e69 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -272,9 +272,17 @@ impl<'a, 'b> Parser<'a, 'b> { } "url" => match self.try_parse_url()? { Some(val) => s = val, - None => s.push_str(&self.parse_call_args()?.to_css_string()?), + None => s.push_str( + &self + .parse_call_args()? + .to_css_string(self.options.is_compressed())?, + ), }, - _ => s.push_str(&self.parse_call_args()?.to_css_string()?), + _ => s.push_str( + &self + .parse_call_args()? + .to_css_string(self.options.is_compressed())?, + ), } return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal( @@ -1142,7 +1150,10 @@ impl<'a, 'b: 'a, 'c> IntermediateValueIterator<'a, 'b, 'c> { "/{}", ValueVisitor::new(self.parser, right.span) .eval(right.node, false)? - .to_css_string(right.span)? + .to_css_string( + right.span, + self.parser.options.is_compressed() + )? ), QuoteKind::None, )), @@ -1348,7 +1359,7 @@ impl<'a, 'b: 'a, 'c> IntermediateValueIterator<'a, 'b, 'c> { "/{}", ValueVisitor::new(self.parser, val.span) .eval(val.node, false)? - .to_css_string(val.span)? + .to_css_string(val.span, self.parser.options.is_compressed())? ), QuoteKind::None, )), diff --git a/src/selector/attribute.rs b/src/selector/attribute.rs index 604e514..3134563 100644 --- a/src/selector/attribute.rs +++ b/src/selector/attribute.rs @@ -189,7 +189,7 @@ impl Display for Attribute { // (also avoids the clone because we can consume/modify self) f.write_str( &Value::String(self.value.clone(), QuoteKind::Quoted) - .to_css_string(self.span) + .to_css_string(self.span, false) .unwrap(), )?; // todo: this space is not emitted when `compressed` output diff --git a/src/style.rs b/src/style.rs index 4c0e263..bbe91da 100644 --- a/src/style.rs +++ b/src/style.rs @@ -14,7 +14,7 @@ impl Style { Ok(format!( "{}: {};", self.property, - self.value.node.to_css_string(self.value.span)? + self.value.node.to_css_string(self.value.span, false)? )) } } diff --git a/src/value/mod.rs b/src/value/mod.rs index 370ef3e..70a9bfd 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -205,22 +205,28 @@ impl Value { } } - pub fn to_css_string(&self, span: Span) -> SassResult> { + pub fn to_css_string(&self, span: Span, is_compressed: bool) -> SassResult> { Ok(match self { Value::Important => Cow::const_str("!important"), Value::Dimension(num, unit, _) => match unit { Unit::Mul(..) | Unit::Div(..) => { if let Some(num) = num { - return Err( - (format!("{}{} isn't a valid CSS value.", num, unit), span).into() - ); + return Err(( + format!( + "{}{} isn't a valid CSS value.", + num.to_string(is_compressed), + unit + ), + span, + ) + .into()); } return Err((format!("NaN{} isn't a valid CSS value.", unit), span).into()); } _ => { if let Some(num) = num { - Cow::owned(format!("{}{}", num, unit)) + Cow::owned(format!("{}{}", num.to_string(is_compressed), unit)) } else { Cow::owned(format!("NaN{}", unit)) } @@ -237,17 +243,25 @@ impl Value { Brackets::None => Cow::owned( vals.iter() .filter(|x| !x.is_null()) - .map(|x| x.to_css_string(span)) + .map(|x| x.to_css_string(span, is_compressed)) .collect::>>>()? - .join(sep.as_str()), + .join(if is_compressed { + sep.as_compressed_str() + } else { + sep.as_str() + }), ), Brackets::Bracketed => Cow::owned(format!( "[{}]", vals.iter() .filter(|x| !x.is_null()) - .map(|x| x.to_css_string(span)) + .map(|x| x.to_css_string(span, is_compressed)) .collect::>>>()? - .join(sep.as_str()), + .join(if is_compressed { + sep.as_compressed_str() + } else { + sep.as_str() + }), )), }, Value::Color(c) => Cow::owned(c.to_string()), @@ -287,9 +301,13 @@ impl Value { Value::ArgList(args) => Cow::owned( args.iter() .filter(|x| !x.is_null()) - .map(|a| a.node.to_css_string(span)) + .map(|a| a.node.to_css_string(span, is_compressed)) .collect::>>>()? - .join(", "), + .join(if is_compressed { + ListSeparator::Comma.as_compressed_str() + } else { + ListSeparator::Comma.as_str() + }), ), }) } @@ -466,7 +484,9 @@ impl Value { .collect::>>()? .join(", ") )), - Value::Dimension(Some(num), unit, _) => Cow::owned(format!("{}{}", num, unit)), + Value::Dimension(Some(num), unit, _) => { + Cow::owned(format!("{}{}", num.inspect(), unit)) + } Value::Dimension(None, unit, ..) => Cow::owned(format!("NaN{}", unit)), Value::ArgList(args) if args.is_empty() => Cow::const_str("()"), Value::ArgList(args) if args.len() == 1 => Cow::owned(format!( @@ -488,7 +508,7 @@ impl Value { | Value::True | Value::False | Value::Color(..) - | Value::String(..) => self.to_css_string(span)?, + | Value::String(..) => self.to_css_string(span, false)?, }) } diff --git a/src/value/number/mod.rs b/src/value/number/mod.rs index 35aff75..1b89e4c 100644 --- a/src/value/number/mod.rs +++ b/src/value/number/mod.rs @@ -1,8 +1,7 @@ use std::{ cmp::Ordering, convert::{From, TryFrom}, - fmt::{self, Display, Write}, - mem, + fmt, mem, ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign}, }; @@ -313,8 +312,8 @@ from_smaller_integer!(u8); impl fmt::Debug for Number { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Small(..) => write!(f, "Number::Small( {} )", self), - Self::Big(..) => write!(f, "Number::Big( {} )", self), + Self::Small(..) => write!(f, "Number::Small( {} )", self.to_string(false)), + Self::Big(..) => write!(f, "Number::Big( {} )", self.to_string(false)), } } } @@ -355,16 +354,24 @@ impl ToPrimitive for Number { } } -impl Display for Number { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl Number { + pub(crate) fn inspect(&self) -> String { + self.to_string(false) + } + + pub(crate) fn to_string(&self, is_compressed: bool) -> String { let mut whole = self.to_integer().abs(); let has_decimal = self.is_decimal(); let mut frac = self.abs().fract(); let mut dec = String::with_capacity(if has_decimal { PRECISION } else { 0 }); + + let mut buf = String::new(); + if has_decimal { for _ in 0..(PRECISION - 1) { frac *= 10_i64; - write!(dec, "{}", frac.to_integer())?; + dec.push_str(&frac.to_integer().to_string()); + frac = frac.fract(); if frac.is_zero() { break; @@ -398,20 +405,25 @@ impl Display for Number { } } } else { - write!(dec, "{}", end)?; + dec.push_str(&end.to_string()); } } } if self.is_negative() && (!whole.is_zero() || !dec.is_empty()) { - f.write_char('-')?; + buf.push('-'); } - write!(f, "{}", whole)?; + + if !(whole.is_zero() && is_compressed) { + buf.push_str(&whole.to_string()); + } + if !dec.is_empty() { - f.write_char('.')?; - write!(f, "{}", dec)?; + buf.push('.'); + buf.push_str(&dec); } - Ok(()) + + buf } } diff --git a/tests/compressed.rs b/tests/compressed.rs index c87f1c1..e8fc259 100644 --- a/tests/compressed.rs +++ b/tests/compressed.rs @@ -13,13 +13,6 @@ test!( "a{color:red;color:green;color:blue}", grass::Options::default().style(grass::OutputStyle::Compressed) ); -test!( - #[ignore = "we don't support compressed values"] - strips_the_leading_zero, - "a {\n color: 0.5;\n}\n", - "a{color:.5}", - grass::Options::default().style(grass::OutputStyle::Compressed) -); test!( compresses_media_rule, "@media foo {\n a {\n color: red;\n }\n}\n", @@ -86,3 +79,40 @@ test!( "a{color:red}b{color:green}", grass::Options::default().style(grass::OutputStyle::Compressed) ); +test!( + removes_spaces_in_comma_separated_list, + "a {\n color: a, b, c;\n}\n", + "a{color:a,b,c}", + grass::Options::default().style(grass::OutputStyle::Compressed) +); +test!( + removes_leading_zero_in_number_under_1, + "a {\n color: 0.5;\n}\n", + "a{color:.5}", + grass::Options::default().style(grass::OutputStyle::Compressed) +); +test!( + #[ignore = "we do not support compressed colors"] + removes_leading_zero_in_number_under_1_in_rgba_alpha_channel, + "a {\n color: rgba(1, 1, 1, 0.5);\n}\n", + "a{color:rgba(1,1,1,.5)}", + grass::Options::default().style(grass::OutputStyle::Compressed) +); +test!( + retains_leading_zero_in_opacity, + "a {\n color: opacity(0.5);\n}\n", + "a{color:opacity(0.5)}", + grass::Options::default().style(grass::OutputStyle::Compressed) +); +test!( + retains_leading_zero_in_saturate, + "a {\n color: saturate(0.5);\n}\n", + "a{color:saturate(0.5)}", + grass::Options::default().style(grass::OutputStyle::Compressed) +); +test!( + retains_leading_zero_in_grayscale, + "a {\n color: grayscale(0.5);\n}\n", + "a{color:grayscale(0.5)}", + grass::Options::default().style(grass::OutputStyle::Compressed) +);