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

Adds more string methods to pycompat #522

Merged
merged 3 commits into from
Jun 11, 2024
Merged
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable changes to MiniJinja are documented here.

## 2.0.3

- Added new methods to pycompat: `str.endswith`, `str.rfind`,
`str.isalnum`, `str.isalpha`, `str.isascii`, `str.isdigit`,
`str.isnumeric`, `str.join`, `str.startswith`. #522

## 2.0.2

- Implemented sequence (+ some iterator) and string repeating with the `*`
Expand Down
104 changes: 104 additions & 0 deletions minijinja-contrib/src/pycompat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,24 @@ use minijinja::{Error, ErrorKind, State, Value};
/// * `list.count`
/// * `str.capitalize`
/// * `str.count`
/// * `str.endswith`
/// * `str.find`
/// * `str.isalnum`
/// * `str.isalpha`
/// * `str.isascii`
/// * `str.isdigit`
/// * `str.islower`
/// * `str.isnumeric`
/// * `str.isupper`
/// * `str.join`
/// * `str.lower`
/// * `str.lstrip`
/// * `str.replace`
/// * `str.rfind`
/// * `str.rstrip`
/// * `str.split`
/// * `str.splitlines`
/// * `str.startswith`
/// * `str.strip`
/// * `str.title`
/// * `str.upper`
Expand Down Expand Up @@ -77,6 +86,24 @@ fn string_methods(value: &Value, method: &str, args: &[Value]) -> Result<Value,
from_args(args)?;
Ok(Value::from(s.chars().all(|x| x.is_whitespace())))
}
"isdigit" | "isnumeric" => {
// this is not a perfect mapping to what Python does, but
// close enough for most uses in templates.
from_args(args)?;
Ok(Value::from(s.chars().all(|x| x.is_numeric())))
}
"isalnum" => {
from_args(args)?;
Ok(Value::from(s.chars().all(|x| x.is_alphanumeric())))
}
"isalpha" => {
from_args(args)?;
Ok(Value::from(s.chars().all(|x| x.is_alphabetic())))
}
"isascii" => {
from_args(args)?;
Ok(Value::from(s.chars().all(|x| x.is_ascii())))
}
"strip" => {
let (chars,): (Option<&str>,) = from_args(args)?;
Ok(Value::from(if let Some(chars) = chars {
Expand Down Expand Up @@ -164,6 +191,83 @@ fn string_methods(value: &Value, method: &str, args: &[Value]) -> Result<Value,
None => -1,
}))
}
"rfind" => {
let (what,): (&str,) = from_args(args)?;
Ok(Value::from(match s.rfind(what) {
Some(x) => x as i64,
None => -1,
}))
}
"startswith" => {
let (prefix,): (&Value,) = from_args(args)?;
if let Some(prefix) = prefix.as_str() {
Ok(Value::from(s.starts_with(prefix)))
} else if matches!(prefix.kind(), ValueKind::Iterable | ValueKind::Seq) {
for prefix in prefix.try_iter()? {
if s.starts_with(prefix.as_str().ok_or_else(|| {
Error::new(
ErrorKind::InvalidOperation,
format!(
"tuple for startswith must contain only strings, not {}",
prefix.kind()
),
)
})?) {
return Ok(Value::from(true));
}
}
Ok(Value::from(false))
} else {
Err(Error::new(
ErrorKind::InvalidOperation,
format!(
"startswith argument must be string or a tuple of strings, not {}",
prefix.kind()
),
))
}
}
"endswith" => {
let (suffix,): (&Value,) = from_args(args)?;
if let Some(suffix) = suffix.as_str() {
Ok(Value::from(s.ends_with(suffix)))
} else if matches!(suffix.kind(), ValueKind::Iterable | ValueKind::Seq) {
for suffix in suffix.try_iter()? {
if s.ends_with(suffix.as_str().ok_or_else(|| {
Error::new(
ErrorKind::InvalidOperation,
format!(
"tuple for endswith must contain only strings, not {}",
suffix.kind()
),
)
})?) {
return Ok(Value::from(true));
}
}
Ok(Value::from(false))
} else {
Err(Error::new(
ErrorKind::InvalidOperation,
format!(
"endswith argument must be string or a tuple of strings, not {}",
suffix.kind()
),
))
}
}
"join" => {
use std::fmt::Write;
let (values,): (&Value,) = from_args(args)?;
let mut rv = String::new();
for (idx, value) in values.try_iter()?.enumerate() {
if idx > 0 {
rv.push_str(s);
}
write!(rv, "{}", value).ok();
}
Ok(Value::from(rv))
}
_ => Err(Error::from(ErrorKind::UnknownMethod)),
}
}
Expand Down
11 changes: 11 additions & 0 deletions minijinja-contrib/tests/pycompat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ fn test_string_methods() {
assert!(eval_expr("'foobar'.islower()").is_true());
assert!(eval_expr("'FOOBAR'.isupper()").is_true());
assert!(eval_expr("' \\n'.isspace()").is_true());
assert!(eval_expr("'abc'.isalpha()").is_true());
assert!(eval_expr("'abc123'.isalnum()").is_true());
assert!(eval_expr("'abc%@#'.isascii()").is_true());
assert_eq!(
eval_expr("'foobar'.replace('o', 'x')").as_str(),
Some("fxxbar")
Expand All @@ -41,12 +44,20 @@ fn test_string_methods() {
);
assert_eq!(eval_expr("'foo barooo'.count('oo')").as_usize(), Some(2));
assert_eq!(eval_expr("'foo barooo'.find('oo')").as_usize(), Some(1));
assert_eq!(eval_expr("'foo barooo'.rfind('oo')").as_usize(), Some(8));
assert!(eval_expr("'a b c'.split() == ['a', 'b', 'c']").is_true());
assert!(eval_expr("'a b c'.split() == ['a', 'b', 'c']").is_true());
assert!(eval_expr("'a b c'.split(none, 1) == ['a', 'b c']").is_true());
assert!(eval_expr("'abcbd'.split('b', 1) == ['a', 'cbd']").is_true());
assert!(eval_expr("'a\\nb\\r\\nc'.splitlines() == ['a', 'b', 'c']").is_true());
assert!(eval_expr("'a\\nb\\r\\nc'.splitlines(true) == ['a\\n', 'b\\r\\n', 'c']").is_true());
assert!(eval_expr("'foobarbaz'.startswith('foo')").is_true());
assert!(eval_expr("'foobarbaz'.startswith(('foo', 'bar'))").is_true());
assert!(!eval_expr("'barfoobaz'.startswith(('foo', 'baz'))").is_true());
assert!(eval_expr("'foobarbaz'.endswith('baz')").is_true());
assert!(eval_expr("'foobarbaz'.endswith(('baz', 'bar'))").is_true());
assert!(!eval_expr("'foobarbazblah'.endswith(('baz', 'bar'))").is_true());
assert_eq!(eval_expr("'|'.join([1, 2, 3])").as_str(), Some("1|2|3"));
}

#[test]
Expand Down