From dcc45600fd86c9b9bd5f435c755e0021f44fc064 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Fri, 25 Jan 2019 01:18:05 +0100 Subject: [PATCH 1/3] Implement `@whitespace` setter. As suggested in #29. A keyword `@whitespace` can be used to switch whitespace handling between the tree modes `as-is`, `compact`, and `removed`. - [x] Implment the functionality. Set default mode to `compact` and update tests. - [ ] Decide on the exact syntax; should some keyword change? Should `@ws` be allowed as an alias for `@whitespace`? Should the `removed` alternative be called `trim`? Or some other word change? - [ ] Add documentation and more tests / examples. - [ ] Anything else? --- examples/ed2018/src/main.rs | 14 +-- examples/simple/src/main.rs | 29 +++--- examples/static-sass/src/main.rs | 14 +-- examples/statics/src/main.rs | 14 +-- src/template.rs | 6 +- src/templateexpression.rs | 161 ++++++++++++++++++++++++++++++- 6 files changed, 199 insertions(+), 39 deletions(-) diff --git a/examples/ed2018/src/main.rs b/examples/ed2018/src/main.rs index 136d2fa..43756e5 100644 --- a/examples/ed2018/src/main.rs +++ b/examples/ed2018/src/main.rs @@ -16,14 +16,14 @@ fn main() { fn test_page_w_static() { assert_eq!( r2s(|o| page(o)), - "\n \ - \n \ - Example with stylesheet\n \ + "\n\ + \n\ + Example with stylesheet\n\ \n \ - \n \ - \n \ - Hello world!\n \ + type=\"text/css\"/>\n\ + \n\ + \n\ + Hello world!\n\ \n\ \n" ); diff --git a/examples/simple/src/main.rs b/examples/simple/src/main.rs index e2d1028..08e4b3b 100644 --- a/examples/simple/src/main.rs +++ b/examples/simple/src/main.rs @@ -72,7 +72,7 @@ fn test_if_let_destructure() { fn test_list() { assert_eq!( r2s(|o| list(o, &["foo", "bar"])), - "\n\n\n" + "\n\n\n" ); } @@ -85,8 +85,8 @@ fn test_list_empty() { fn test_list_destructure() { assert_eq!( r2s(|o| list_destructure(o, &["foo", "bar"])), - "\n" + "\n" ); } @@ -94,7 +94,7 @@ fn test_list_destructure() { fn test_list_destructure_2() { assert_eq!( r2s(|o| list_destructure_2(o)), - "\n

Rasmus is 44 years old.

\n\n \ + "\n

Rasmus is 44 years old.

\n\n\

Mike is 36 years old.

\n" ); } @@ -104,8 +104,8 @@ fn test_uselist() { assert_eq!( r2s(|o| uselist(o)), "

Two items

\n\n\ - \n\n\n\ + \n\n\n\

No items

\n\n\

No items

\n\n\n" ); @@ -190,8 +190,7 @@ fn test_hello_code() { fn test_for_loop() { assert_eq!( r2s(|o| for_loop(o, &vec!["Hello", "World"])), - "

Looped paragraphs

\n\n \ -

Hello

\n\n

World

\n" + "

Looped paragraphs

\n\n

Hello

\n\n

World

\n" ); } @@ -238,15 +237,15 @@ fn test_page_with_base() { r2s(|o| page::page(o, "World")), "\ \n\ - \n Hello World!\ - \n \ + \nHello World!\ + \n\ \n\ - \n \ - \n

Hello World!

\ - \n \ - \n

This is page content for World

\ + \n\ + \n

Hello World!

\ \n\ - \n \ + \n

This is page content for World

\ + \n\ + \n\ \n\n\n" ); } diff --git a/examples/static-sass/src/main.rs b/examples/static-sass/src/main.rs index 41fe3e5..96fc1c0 100644 --- a/examples/static-sass/src/main.rs +++ b/examples/static-sass/src/main.rs @@ -25,15 +25,15 @@ mod test { fn page_w_static() { assert_eq!( r2s(|o| page(o)), - "\n \ - \n \ - Example with stylesheet\n \ + "\n\ + \n\ + Example with stylesheet\n\ \n \ - \n \ - \n \ - Hello world!\n \ + type=\"text/css\"/>\n\ + \n\ + \n\ + Hello world!\n\ \n\ \n" ); diff --git a/examples/statics/src/main.rs b/examples/statics/src/main.rs index 45f9ce3..724f02a 100644 --- a/examples/statics/src/main.rs +++ b/examples/statics/src/main.rs @@ -16,14 +16,14 @@ fn main() { fn test_page_w_static() { assert_eq!( r2s(|o| page(o)), - "\n \ - \n \ - Example with stylesheet\n \ + "\n\ + \n\ + Example with stylesheet\n\ \n \ - \n \ - \n \ - Hello world!\n \ + type=\"text/css\"/>\n\ + \n\ + \n\ + Hello world!\n\ \n\ \n" ); diff --git a/src/template.rs b/src/template.rs index 819144f..367e6af 100644 --- a/src/template.rs +++ b/src/template.rs @@ -3,7 +3,9 @@ use itertools::Itertools; use nom::types::CompleteByteSlice as Input; use spacelike::spacelike; use std::io::{self, Write}; -use templateexpression::{template_expression, TemplateExpression}; +use templateexpression::{ + apply_spacemode, template_expression, SpaceMode, TemplateExpression, +}; #[derive(Debug, PartialEq, Eq)] pub struct Template { @@ -70,7 +72,7 @@ named!( template_expression), call!(end_of_file)) ), - |((), preamble, args, body)| Template { preamble, args, body: body.0 } + |((), preamble, args, body)| Template { preamble, args, body: apply_spacemode(body.0, SpaceMode::Compact) } ) ); diff --git a/src/templateexpression.rs b/src/templateexpression.rs index 18e1c95..ae8c7dc 100644 --- a/src/templateexpression.rs +++ b/src/templateexpression.rs @@ -29,6 +29,16 @@ pub enum TemplateExpression { name: String, args: Vec, }, + /// The actual mode is allreay applied, this variant is just to ensure + /// that outer space mode changes don't descend into this group. + SpaceMode(Vec), +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SpaceMode { + AsIs, + Compact, + Removed, } #[derive(Debug, PartialEq, Eq)] @@ -37,6 +47,17 @@ pub enum TemplateArgument { Body(Vec), } +impl TemplateArgument { + pub fn apply_spacemode(self, mode: SpaceMode) -> Self { + match self { + TemplateArgument::Rust(s) => TemplateArgument::Rust(s), + TemplateArgument::Body(v) => { + TemplateArgument::Body(apply_spacemode(v, mode)) + } + } + } +} + impl Display for TemplateArgument { fn fmt(&self, out: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { @@ -53,12 +74,64 @@ impl Display for TemplateArgument { } } +pub fn apply_spacemode( + v: Vec, + mode: SpaceMode, +) -> Vec { + v.into_iter().map(|b| b.apply_spacemode(mode)).collect() +} + impl TemplateExpression { pub fn text(text: &str) -> Self { TemplateExpression::Text { text: text.to_string(), } } + pub fn apply_spacemode(self, mode: SpaceMode) -> Self { + match self { + TemplateExpression::Comment => TemplateExpression::Comment, + TemplateExpression::Text { text } => TemplateExpression::Text { + text: match mode { + SpaceMode::AsIs => text, + SpaceMode::Compact => compactify(&text), + SpaceMode::Removed => { + compactify(&text).trim().to_string() + } + }, + }, + TemplateExpression::Expression { expr } => { + TemplateExpression::Expression { expr } + } + TemplateExpression::ForLoop { name, expr, body } => { + TemplateExpression::ForLoop { + name, + expr, + body: apply_spacemode(body, mode), + } + } + TemplateExpression::IfBlock { + expr, + body, + else_body, + } => TemplateExpression::IfBlock { + expr, + body: apply_spacemode(body, mode), + else_body: else_body.map(|eb| apply_spacemode(eb, mode)), + }, + TemplateExpression::CallTemplate { name, args } => { + TemplateExpression::CallTemplate { + name, + args: args + .into_iter() + .map(|b| b.apply_spacemode(mode)) + .collect(), + } + } + TemplateExpression::SpaceMode(body) => { + TemplateExpression::SpaceMode(body) + } + } + } pub fn code(&self) -> String { match *self { TemplateExpression::Comment => String::new(), @@ -104,8 +177,47 @@ impl TemplateExpression { ))), ) } + TemplateExpression::SpaceMode(ref body) => { + body.iter().map(|b| b.code()).format("").to_string() + } + } + } +} + +fn compactify(s: &str) -> String { + let mut space = false; + let mut newline = false; + let mut result = String::new(); + for c in s.chars() { + match c { + '\n' => newline = true, + ' ' => space = true, + c => { + if newline { + result.push('\n'); + } else if space { + result.push(' '); + } + result.push(c); + newline = false; + space = false; + } } } + if newline { + result.push('\n'); + } else if space { + result.push(' '); + } + result +} + +#[test] +fn t_compactify() { + assert_eq!( + compactify(" hello world \n \n This is nice.\n\n\n"), + " hello world\nThis is nice.\n" + ) } named!( @@ -117,7 +229,7 @@ named!( alt!(tag!("*") | tag!(":") | tag!("@") | tag!("{") | tag!("}") | terminated!( - alt!(tag!("if") | tag!("for")), + alt!(tag!("if") | tag!("for") | tag!("whitespace")), tag!(" ")) | value!(Input(&b""[..]))))), Some(Input(b":")) => map!( @@ -168,6 +280,24 @@ named!( expr: expr.to_string(), body, }) | + Some(Input(b"whitespace")) => map!( + tuple!( + delimited!(tag!("("), + alt!( + value!(SpaceMode::AsIs, tag!("as-is")) | + value!(SpaceMode::Compact, tag!("compact")) | + value!(SpaceMode::Removed, tag!("removed")) + ), + terminated!(tag!(")"), spacelike)), + template_block + ), + |(mode, body)| { + eprintln!("TODO: Apply {:?} to {:?}", mode, body); + TemplateExpression::SpaceMode( + body.into_iter().map(|b| b.apply_spacemode(mode)).collect() + ) + } + ) | Some(Input(b"")) => map!( expression, |expr| TemplateExpression::Expression{ expr: expr.to_string() } @@ -489,6 +619,35 @@ mod test { ) } + #[test] + fn skip_ws() { + assert_eq!( + template_expression(Input( + b"@whitespace (removed) {\ + \n @if something {\ + \n construct with\ + \n inner space\ + \n }\ + \n}\ + ", + )), + Ok(( + Input(&b""[..]), + TemplateExpression::SpaceMode(vec![ + TemplateExpression::text(""), + TemplateExpression::IfBlock { + expr: "something".to_string(), + body: vec![TemplateExpression::text( + "construct with\ninner space" + )], + else_body: None, + }, + TemplateExpression::text(""), + ]), + )) + ) + } + #[test] fn for_missing_in() { // TODO The second part of this message isn't really helpful. From 24913fa6dcf3907bf543e89abb306964653ddabd Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Fri, 25 Jan 2019 01:31:57 +0100 Subject: [PATCH 2/3] Tabs are spaces. Only spaces (ascii 32), tabs, newlines and carriage returns are considered whitespace. Other variants (such as no-break space and spaces of specific width) will probably only appear in templates if they are actually wanted in output. --- src/templateexpression.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/templateexpression.rs b/src/templateexpression.rs index ae8c7dc..539f594 100644 --- a/src/templateexpression.rs +++ b/src/templateexpression.rs @@ -190,8 +190,8 @@ fn compactify(s: &str) -> String { let mut result = String::new(); for c in s.chars() { match c { - '\n' => newline = true, - ' ' => space = true, + '\n' | '\r' => newline = true, + ' ' | '\t' => space = true, c => { if newline { result.push('\n'); @@ -219,6 +219,13 @@ fn t_compactify() { " hello world\nThis is nice.\n" ) } +#[test] +fn t_compactify_tabbed() { + assert_eq!( + compactify("\n\thello world\n\t\n\t This \t is nice.\n\t\n \n"), + "\nhello world\nThis is nice.\n" + ) +} named!( pub template_expression, From ebe1b34414d6914fcef1c164dc073c1009cad1f5 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Fri, 25 Jan 2019 22:47:43 +0100 Subject: [PATCH 3/3] Allow `@ws` as an alias for `@whitespace`. --- src/templateexpression.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/templateexpression.rs b/src/templateexpression.rs index 539f594..f90d89c 100644 --- a/src/templateexpression.rs +++ b/src/templateexpression.rs @@ -236,7 +236,8 @@ named!( alt!(tag!("*") | tag!(":") | tag!("@") | tag!("{") | tag!("}") | terminated!( - alt!(tag!("if") | tag!("for") | tag!("whitespace")), + alt!(tag!("if") | tag!("for") | + tag!("whitespace") | tag!("ws")), tag!(" ")) | value!(Input(&b""[..]))))), Some(Input(b":")) => map!( @@ -287,15 +288,16 @@ named!( expr: expr.to_string(), body, }) | - Some(Input(b"whitespace")) => map!( + Some(Input(b"whitespace")) + | Some(Input(b"ws")) => map!( tuple!( - delimited!(tag!("("), + delimited!(delimited!(spacelike, tag!("("), spacelike), alt!( value!(SpaceMode::AsIs, tag!("as-is")) | value!(SpaceMode::Compact, tag!("compact")) | value!(SpaceMode::Removed, tag!("removed")) ), - terminated!(tag!(")"), spacelike)), + delimited!(spacelike, tag!(")"), spacelike)), template_block ), |(mode, body)| {