Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Truncate string but not cut off mid-word #955

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions docs/content/docs/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ tera.autoescape_on(vec![".php.html"]);
tera.autoescape_on(vec![]);
```

Tera does not perform contextual auto-escaping, e.g. by parsing the template to know whether to escape JS, CSS or HTML (see
Tera does not perform contextual auto-escaping, e.g. by parsing the template to know whether to escape JS, CSS or HTML (see
<https://rawgit.com/mikesamuel/sanitized-jquery-templates/trunk/safetemplate.html> for more details on that).

## Advanced usage
Expand Down Expand Up @@ -200,7 +200,7 @@ would be rendered as `Hello {{ name }}`.
### Whitespace control

Tera comes with easy to use whitespace control: use `{%-` if you want to remove all whitespace
before a statement and `-%}` if you want to remove all whitespace after. This behavior also
before a statement and `-%}` if you want to remove all whitespace after. This behavior also
works with expressions, using `{{-` and `-}}`, and with comments, using `{#-` and `-#}`.

For example, let's look at the following template:
Expand Down Expand Up @@ -854,6 +854,9 @@ By default, the filter will add an ellipsis at the end if the text was truncated
change the string appended by setting the `end` argument.
For example, `{{ value | truncate(length=10, end="") }}` will not append anything.

In addition, to truncate but not cut off mid-word, you can set `killwords` argument to `true`.
For example, `{{ value | truncate(length=10, killwords=true) }}` will truncate the string and discard the last word.

#### linebreaksbr
Replaces line breaks (`\n` or `\r\n`) with HTML line breaks (`<br>`).

Expand Down
37 changes: 36 additions & 1 deletion src/builtins/filters/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ pub fn trim_end_matches(value: &Value, args: &HashMap<String, Value>) -> Result<
/// returned untouched. The default value is 255.
/// * `end` - The ellipsis string to be used if the given string is
/// truncated. The default value is "…".
/// * `killwords` - If true, the string will be truncated by discarding the
/// last word. The default value is false.
///
/// # Remarks
///
Expand All @@ -164,6 +166,10 @@ pub fn truncate(value: &Value, args: &HashMap<String, Value>) -> Result<Value> {
Some(l) => try_get_value!("truncate", "end", String, l),
None => "…".to_string(),
};
let killwords = match args.get("killwords") {
Some(l) => try_get_value!("truncate", "killwords", bool, l),
None => false,
};

let graphemes = GraphemeIndices::new(&s).collect::<Vec<(usize, &str)>>();

Expand All @@ -172,7 +178,13 @@ pub fn truncate(value: &Value, args: &HashMap<String, Value>) -> Result<Value> {
return Ok(to_value(&s).unwrap());
}

let result = s[..graphemes[length].0].to_string() + &end;
let mut result = s[..graphemes[length].0].to_string();
if killwords {
let last_word = result.rfind(' ').unwrap_or(result.len());
result.truncate(last_word);
}
result = result + &end;

Ok(to_value(result).unwrap())
}

Expand Down Expand Up @@ -557,6 +569,29 @@ mod tests {
assert_eq!(result.unwrap(), to_value("👨‍👩‍👧‍👦 fam…").unwrap());
}

#[test]
fn test_truncate_killwords() {
let mut args = HashMap::new();
args.insert("length".to_string(), to_value(24).unwrap());
args.insert("killwords".to_string(), to_value(true).unwrap());
let result = truncate(
&to_value("Lorem ipsum dolor sit amet, consectetur adipiscing elit.").unwrap(),
&args,
);
assert!(result.is_ok());
assert_eq!(result.unwrap(), to_value("Lorem ipsum dolor sit…").unwrap());
}

#[test]
fn test_truncate_killwords_but_shorter_than_length() {
let mut args = HashMap::new();
args.insert("length".to_string(), to_value(12).unwrap());
args.insert("killwords".to_string(), to_value(true).unwrap());
let result = truncate(&to_value("Lorem ipsum").unwrap(), &args);
assert!(result.is_ok());
assert_eq!(result.unwrap(), to_value("Lorem ipsum").unwrap());
}

#[test]
fn test_lower() {
let result = lower(&to_value("HELLO").unwrap(), &HashMap::new());
Expand Down