From b9490c4b5d370760825a3cd14b97f1163e22e6f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Sat, 14 Jun 2025 14:52:51 +0200 Subject: [PATCH] Filter `paragraphbreaks` only needs `core` --- askama/src/filters/alloc.rs | 77 ------------------ askama/src/filters/core.rs | 107 +++++++++++++++++++++----- askama/src/filters/mod.rs | 7 +- askama_derive/src/generator/filter.rs | 3 +- book/src/filters.md | 5 -- testing/tests/filter_block.rs | 8 +- 6 files changed, 97 insertions(+), 110 deletions(-) diff --git a/askama/src/filters/alloc.rs b/askama/src/filters/alloc.rs index bc1f9ce8..2b6bbb15 100644 --- a/askama/src/filters/alloc.rs +++ b/askama/src/filters/alloc.rs @@ -149,65 +149,6 @@ fn flush_linebreaksbr(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Re dest.write_str(&s.replace('\n', "
")) } -/// Replaces only paragraph breaks in plain text with appropriate HTML -/// -/// A new line followed by a blank line becomes a paragraph break `

`. -/// Paragraph tags only wrap content; empty paragraphs are removed. -/// No `
` tags are added. -/// -/// ``` -/// # #[cfg(feature = "code-in-doc")] { -/// # use askama::Template; -/// /// ```jinja -/// /// {{ lines|paragraphbreaks }} -/// /// ``` -/// #[derive(Template)] -/// #[template(ext = "html", in_doc = true)] -/// struct Example<'a> { -/// lines: &'a str, -/// } -/// -/// assert_eq!( -/// Example { lines: "Foo\nBar\n\nBaz" }.to_string(), -/// "

Foo\nBar

Baz

" -/// ); -/// # } -/// ``` -#[inline] -pub fn paragraphbreaks( - source: S, -) -> Result>, Infallible> { - Ok(HtmlSafeOutput(Paragraphbreaks(source))) -} - -pub struct Paragraphbreaks(S); - -impl fmt::Display for Paragraphbreaks { - #[inline] - fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut buffer; - flush_paragraphbreaks(dest, try_to_str!(self.0 => buffer)) - } -} - -impl FastWritable for Paragraphbreaks { - #[inline] - fn write_into( - &self, - dest: &mut W, - values: &dyn crate::Values, - ) -> crate::Result<()> { - let mut buffer = String::new(); - self.0.write_into(&mut buffer, values)?; - Ok(flush_paragraphbreaks(dest, &buffer)?) - } -} - -fn flush_paragraphbreaks(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result { - let linebroken = s.replace("\n\n", "

").replace("

", ""); - write!(dest, "

{linebroken}

") -} - /// Converts to lowercase /// /// ``` @@ -609,24 +550,6 @@ mod tests { ); } - #[test] - fn test_paragraphbreaks() { - assert_eq!( - paragraphbreaks("Foo\nBar Baz").unwrap().to_string(), - "

Foo\nBar Baz

" - ); - assert_eq!( - paragraphbreaks("Foo\nBar\n\nBaz").unwrap().to_string(), - "

Foo\nBar

Baz

" - ); - assert_eq!( - paragraphbreaks("Foo\n\n\n\n\nBar\n\nBaz") - .unwrap() - .to_string(), - "

Foo

\nBar

Baz

" - ); - } - #[test] fn test_lower() { assert_eq!(lower("Foo").unwrap().to_string(), "foo"); diff --git a/askama/src/filters/core.rs b/askama/src/filters/core.rs index 9b17aa09..35d01371 100644 --- a/askama/src/filters/core.rs +++ b/askama/src/filters/core.rs @@ -641,41 +641,94 @@ impl<'a> fmt::Write for WordCountWriter<'a> { /// # } /// ``` #[inline] -pub fn linebreaks(source: S) -> Result>, Infallible> { - Ok(HtmlSafeOutput(Linebreaks(source))) +pub fn linebreaks( + source: S, +) -> Result>, Infallible> { + Ok(HtmlSafeOutput(NewlineCounting { + source, + one: "
", + })) } -pub struct Linebreaks(S); +/// Replaces only paragraph breaks in plain text with appropriate HTML +/// +/// A new line followed by a blank line becomes a paragraph break `

`. +/// Paragraph tags only wrap content; empty paragraphs are removed. +/// No `
` tags are added. +/// +/// ``` +/// # #[cfg(feature = "code-in-doc")] { +/// # use askama::Template; +/// /// ```jinja +/// /// {{ lines|paragraphbreaks }} +/// /// ``` +/// #[derive(Template)] +/// #[template(ext = "html", in_doc = true)] +/// struct Example<'a> { +/// lines: &'a str, +/// } +/// +/// assert_eq!( +/// Example { lines: "Foo\nBar\n\nBaz" }.to_string(), +/// "

Foo\nBar

Baz

" +/// ); +/// # } +/// ``` +#[inline] +pub fn paragraphbreaks( + source: S, +) -> Result>, Infallible> { + Ok(HtmlSafeOutput(NewlineCounting { source, one: "\n" })) +} -impl fmt::Display for Linebreaks { - fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result { - dest.write_str("

")?; - let mut formatter = LinebreakFormatter { dest, counter: -1 }; - write!(formatter, "{}", self.0)?; - formatter.dest.write_str("

") +pub struct NewlineCounting { + source: S, + one: &'static str, +} + +impl NewlineCounting { + #[inline] + fn run<'a, F, W, E>(&self, dest: &'a mut W, inner: F) -> Result<(), E> + where + W: fmt::Write + ?Sized, + F: FnOnce(&mut NewlineCountingFormatter<'a, W>) -> Result<(), E>, + E: From, + { + let mut formatter = NewlineCountingFormatter { + dest, + counter: -1, + one: self.one, + }; + formatter.dest.write_str("

")?; + inner(&mut formatter)?; + formatter.dest.write_str("

")?; + Ok(()) } } -impl FastWritable for Linebreaks { +impl fmt::Display for NewlineCounting { + fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result { + self.run(dest, |f| write!(f, "{}", self.source)) + } +} + +impl FastWritable for NewlineCounting { fn write_into( &self, dest: &mut W, values: &dyn crate::Values, ) -> crate::Result<()> { - dest.write_str("

")?; - let mut formatter = LinebreakFormatter { dest, counter: -1 }; - self.0.write_into(&mut formatter, values)?; - dest.write_str("

")?; - Ok(()) + self.run(dest, |f| self.source.write_into(f, values)) } } -struct LinebreakFormatter<'a, W: ?Sized> { +struct NewlineCountingFormatter<'a, W: ?Sized> { dest: &'a mut W, counter: isize, + one: &'static str, } -impl fmt::Write for LinebreakFormatter<'_, W> { +impl fmt::Write for NewlineCountingFormatter<'_, W> { fn write_str(&mut self, s: &str) -> fmt::Result { if s.is_empty() { return Ok(()); @@ -693,7 +746,7 @@ impl fmt::Write for LinebreakFormatter<'_, W> { if !line.is_empty() { match replace(&mut self.counter, if has_eol { 1 } else { 0 }) { ..=0 => {} - 1 => self.dest.write_str("
")?, + 1 => self.dest.write_str(self.one)?, 2.. => self.dest.write_str("

")?, } self.dest.write_str(line)?; @@ -827,4 +880,22 @@ mod tests { "

Foo
Bar

Baz

" ); } + + #[test] + fn test_paragraphbreaks() { + assert_eq!( + paragraphbreaks("Foo\nBar Baz").unwrap().to_string(), + "

Foo\nBar Baz

" + ); + assert_eq!( + paragraphbreaks("Foo\nBar\n\nBaz").unwrap().to_string(), + "

Foo\nBar

Baz

" + ); + assert_eq!( + paragraphbreaks("Foo\n\n\n\n\nBar\n\nBaz") + .unwrap() + .to_string(), + "

Foo

Bar

Baz

" + ); + } } diff --git a/askama/src/filters/mod.rs b/askama/src/filters/mod.rs index 1c5a515c..af07b62c 100644 --- a/askama/src/filters/mod.rs +++ b/askama/src/filters/mod.rs @@ -25,11 +25,12 @@ mod urlencode; #[cfg(feature = "alloc")] pub use self::alloc::{ - capitalize, fmt, format, linebreaksbr, lower, lowercase, paragraphbreaks, title, titlecase, - trim, upper, uppercase, + capitalize, fmt, format, linebreaksbr, lower, lowercase, title, titlecase, trim, upper, + uppercase, }; pub use self::core::{ - PluralizeCount, center, join, linebreaks, pluralize, reject, reject_with, truncate, wordcount, + PluralizeCount, center, join, linebreaks, paragraphbreaks, pluralize, reject, reject_with, + truncate, wordcount, }; pub use self::escape::{ AutoEscape, AutoEscaper, Escaper, Html, HtmlSafe, HtmlSafeOutput, MaybeSafe, Safe, Text, diff --git a/askama_derive/src/generator/filter.rs b/askama_derive/src/generator/filter.rs index 59b70327..04abc80c 100644 --- a/askama_derive/src/generator/filter.rs +++ b/askama_derive/src/generator/filter.rs @@ -323,9 +323,8 @@ impl<'a> Generator<'a, '_> { ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'a, Expr<'a>>], - node: Span<'_>, + _node: Span<'_>, ) -> Result { - ensure_filter_has_feature_alloc(ctx, "paragraphbreaks", node)?; self.visit_linebreaks_filters(ctx, buf, "paragraphbreaks", args) } diff --git a/book/src/filters.md b/book/src/filters.md index 17c5ae24..90ee58cf 100644 --- a/book/src/filters.md +++ b/book/src/filters.md @@ -346,11 +346,6 @@ hello
world

from
askama ### paragraphbreaks [#paragraphbreaks]: #paragraphbreaks -
-enabled by "alloc"
-enabled by "default" -
- ```jinja {{ text_to_break | paragraphbreaks }} ``` diff --git a/testing/tests/filter_block.rs b/testing/tests/filter_block.rs index c7797d9f..ba0e8927 100644 --- a/testing/tests/filter_block.rs +++ b/testing/tests/filter_block.rs @@ -302,11 +302,9 @@ fn filter_nested_filter_blocks() { }; assert_eq!( template.render().unwrap(), - r"[ -<P>HELLO &#38;</P><P>GOODBYE! -</P> -<P>HELLO &#38;</P><P>GOODBYE! -</P>] + "[ +<P>HELLO &#38;</P><P>GOODBYE!</P> +<P>HELLO &#38;</P><P>GOODBYE!</P>] 2" ); }