Skip to content

Commit

Permalink
Refactor and feature gate filter
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Dec 1, 2024
1 parent e829c30 commit 5c65cae
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 30 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to MiniJinja are documented here.
- Added `sum` filter. #648
- Added `truncate` filter to `minijinja-contrib`. #647
- Added `wordcount` filter to `minijinja-contrib`. #649
- Added `wordwrap` filter to `minijinja-contrib`. #651

## 2.5.0

Expand Down
2 changes: 1 addition & 1 deletion minijinja-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ minijinja = { version = "=2.5.0", path = "../minijinja", features = [
"custom_syntax",
"loop_controls"
] }
minijinja-contrib = { version = "=2.5.0", optional = true, path = "../minijinja-contrib", features = ["pycompat", "datetime", "timezone", "rand"] }
minijinja-contrib = { version = "=2.5.0", optional = true, path = "../minijinja-contrib", features = ["pycompat", "datetime", "timezone", "rand", "unicode_wordwrap"] }
rustyline = { version = "14.0.0", optional = true }
serde = { version = "1.0.183", features = ["derive", "rc"] }
serde_json = "1.0.105"
Expand Down
4 changes: 3 additions & 1 deletion minijinja-contrib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ pycompat = ["minijinja/builtins"]
datetime = ["time"]
timezone = ["time-tz"]
rand = ["dep:rand"]
wordwrap = ["dep:textwrap"]
unicode_wordwrap = ["wordwrap", "textwrap/unicode-linebreak", "textwrap/unicode-width"]

[dependencies]
minijinja = { version = "2.5.0", path = "../minijinja", default-features = false }
rand = { version = "0.8.5", optional = true, default-features = false, features = ["std", "std_rng", "small_rng"] }
serde = "1.0.164"
textwrap = "0.16.1"
textwrap = { version = "0.16.1", optional = true, default-features = false, features = ["smawk"] }
time = { version = "0.3.35", optional = true, features = ["serde", "formatting", "parsing"] }
time-tz = { version = "1.0.3", features = ["db"], optional = true }

Expand Down
61 changes: 34 additions & 27 deletions minijinja-contrib/src/filters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use std::convert::TryFrom;
use minijinja::value::{Kwargs, Value, ValueKind};
use minijinja::State;
use minijinja::{Error, ErrorKind};
use textwrap::{wrap, Options as WrapOptions, WordSplitter};

#[cfg(feature = "datetime")]
mod datetime;
Expand Down Expand Up @@ -242,16 +241,22 @@ pub fn wordcount(value: Value) -> Result<Value, Error> {

/// Wrap a string to the given width.
///
/// Parameters:
/// - s: Original text to wrap
/// - width: Maximum length of wrapped lines (default: 79)
/// - break_long_words: If a word is longer than width, break it across lines (default: true)
/// - break_on_hyphens: If a word contains hyphens, it may be split across lines (default: true)
/// - wrapstring: String to join each wrapped line (default: newline)
/// By default this filter is not unicode aware (feature = `wordwrap`) but when the unicode
/// feature is enabled (`unicode_wordwrap`) then it becomes so. It's implemented on top of
/// the `textwrap` crate.
///
/// **Keyword arguments:**
///
/// - `width`: Maximum length of wrapped lines (default: 79)
/// - `break_long_words`: If a word is longer than width, break it across lines (default: true)
/// - `break_on_hyphens`: If a word contains hyphens, it may be split across lines (default: true)
/// - `wrapstring`: String to join each wrapped line (default: newline)
#[cfg(feature = "wordwrap")]
#[cfg_attr(docsrs, doc(any(cfg(feature = "wordwrap"), cfg = "unicode_wordwrap")))]
pub fn wordwrap(value: Value, kwargs: Kwargs) -> Result<Value, Error> {
use textwrap::{wrap, Options as WrapOptions, WordSplitter};
let s = value.as_str().unwrap_or_default();

// Extract kwargs with defaults
let width = kwargs.get::<Option<usize>>("width")?.unwrap_or(79);
let break_long_words = kwargs
.get::<Option<bool>>("break_long_words")?
Expand All @@ -260,6 +265,7 @@ pub fn wordwrap(value: Value, kwargs: Kwargs) -> Result<Value, Error> {
.get::<Option<bool>>("break_on_hyphens")?
.unwrap_or(true);
let wrapstring = kwargs.get::<Option<&str>>("wrapstring")?.unwrap_or("\n");
kwargs.assert_all_used()?;

let mut options = WrapOptions::new(width).break_words(break_long_words);

Expand All @@ -269,26 +275,27 @@ pub fn wordwrap(value: Value, kwargs: Kwargs) -> Result<Value, Error> {

// Handle empty/whitespace-only input
if s.trim().is_empty() {
return Ok(Value::from(s));
return Ok(Value::from(""));
}

// Split input into paragraphs on existing newlines
let paragraphs: Vec<&str> = s.split('\n').collect();

// Wrap each paragraph separately
let wrapped: Vec<String> = paragraphs
.iter()
.map(|&p| {
if p.trim().is_empty() {
// Preserve empty lines
String::new()
} else {
// Wrap the paragraph
wrap(p, &options).join(wrapstring)
// Process paragraphs sequentially into final string
Ok(Value::from(s.lines().enumerate().fold(
String::new(),
|mut acc, (i, p)| {
if i > 0 {
acc.push_str(wrapstring);
}
})
.collect();

// Join paragraphs with newlines
Ok(Value::from(wrapped.join("\n")))
if !p.trim().is_empty() {
// Wrap the paragraph and join with wrapstring
let wrapped = wrap(p, &options);
for (j, line) in wrapped.iter().enumerate() {
if j > 0 {
acc.push_str(wrapstring);
}
acc.push_str(line);
}
}
acc
},
)))
}
4 changes: 4 additions & 0 deletions minijinja-contrib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ pub fn add_to_environment(env: &mut Environment) {
env.add_filter("filesizeformat", filters::filesizeformat);
env.add_filter("truncate", filters::truncate);
env.add_filter("wordcount", filters::wordcount);
#[cfg(feature = "wordwrap")]
{
env.add_filter("wordwrap", filters::wordwrap);
}
#[cfg(feature = "datetime")]
{
env.add_filter("datetimeformat", filters::datetimeformat);
Expand Down
5 changes: 4 additions & 1 deletion minijinja-contrib/tests/filters.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use minijinja::{context, Environment};
use minijinja_contrib::filters::{pluralize, wordcount, wordwrap};
use minijinja_contrib::filters::{pluralize, wordcount};
use similar_asserts::assert_eq;

#[test]
Expand Down Expand Up @@ -269,7 +269,10 @@ fn test_wordcount() {
}

#[test]
#[cfg(feature = "wordwrap")]
fn test_wordwrap() {
use minijinja_contrib::filters::wordwrap;

let mut env = minijinja::Environment::new();
env.add_filter("wordwrap", wordwrap);

Expand Down

0 comments on commit 5c65cae

Please sign in to comment.