Skip to content
Closed
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
3 changes: 3 additions & 0 deletions .changes/unreleased/Fixes-20250824-234019.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Fixes
body: Make modules.re methods accept an already compiled pattern
time: 2025-08-24T23:40:19.408475+02:00
4 changes: 2 additions & 2 deletions crates/dbt-jinja/minijinja-contrib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
[package]
name = "minijinja-contrib"
version = "2.5.0"
edition = "2021"
edition = "2024"
license = "Apache-2.0"
authors = ["Armin Ronacher <[email protected]>"]
description = "Extra utilities for MiniJinja"
homepage = "https://github.com/mitsuhiko/minijinja"
repository = "https://github.com/mitsuhiko/minijinja"
keywords = ["jinja", "jinja2", "templates"]
readme = "README.md"
rust-version = "1.70"
rust-version = "1.88.0"

[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "doc-header.html"]
Expand Down
6 changes: 3 additions & 3 deletions crates/dbt-jinja/minijinja-contrib/src/filters/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use std::convert::TryFrom;

use minijinja::value::{Kwargs, Value, ValueKind};
use minijinja::{Error, ErrorKind, State};
use serde::de::value::SeqDeserializer;
use serde::Deserialize;
use serde::de::value::SeqDeserializer;
use time::format_description::well_known::iso8601::Iso8601;
use time::{format_description, Date, OffsetDateTime, PrimitiveDateTime};
use time::{Date, OffsetDateTime, PrimitiveDateTime, format_description};

fn handle_serde_error(err: serde::de::value::Error) -> Error {
Error::new(ErrorKind::InvalidOperation, "not a valid date or timestamp").with_source(err)
Expand Down Expand Up @@ -39,7 +39,7 @@ fn value_to_datetime(
ErrorKind::InvalidOperation,
"not a valid date or timestamp",
)
.with_source(original_err))
.with_source(original_err));
}
},
},
Expand Down
4 changes: 2 additions & 2 deletions crates/dbt-jinja/minijinja-contrib/src/filters/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::convert::TryFrom;

use minijinja::value::{Kwargs, Value, ValueKind};
use minijinja::State;
use minijinja::value::{Kwargs, Value, ValueKind};
use minijinja::{Error, ErrorKind};

#[cfg(feature = "datetime")]
Expand Down Expand Up @@ -262,7 +262,7 @@ pub fn wordcount(value: &Value) -> Result<Value, Error> {
#[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};
use textwrap::{Options as WrapOptions, WordSplitter, wrap};
let s = value.as_str().unwrap_or_default();

let width = kwargs.get::<Option<usize>>("width")?.unwrap_or(79);
Expand Down
8 changes: 4 additions & 4 deletions crates/dbt-jinja/minijinja-contrib/src/globals.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};

use minijinja::listener::RenderingEventListener;
#[allow(unused)]
use minijinja::value::Value;
use minijinja::value::{from_args, Object, ObjectRepr};
use minijinja::value::{Object, ObjectRepr, from_args};
use minijinja::{Error, ErrorKind, State};

/// Returns the current time in UTC as unix timestamp.
Expand Down Expand Up @@ -137,8 +137,8 @@ pub fn joiner(sep: Option<Value>) -> Value {
/// Returns the rng for the state
#[cfg(feature = "rand")]
pub(crate) fn get_rng(state: &State) -> rand::rngs::SmallRng {
use rand::rngs::SmallRng;
use rand::SeedableRng;
use rand::rngs::SmallRng;

if let Some(seed) = state
.lookup("RAND_SEED")
Expand Down Expand Up @@ -189,8 +189,8 @@ pub fn lipsum(
n: Option<usize>,
kwargs: minijinja::value::Kwargs,
) -> Result<Value, Error> {
use rand::seq::SliceRandom;
use rand::Rng;
use rand::seq::SliceRandom;

#[rustfmt::skip]
const LIPSUM_WORDS: &[&str] = &[
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use chrono::{Datelike, Local, NaiveDate, NaiveDateTime, TimeZone};
use minijinja::arg_utils::ArgParser;
use minijinja::{value::Object, Error, ErrorKind, Value};
use minijinja::{Error, ErrorKind, Value, value::Object};
use std::fmt;
use std::sync::Arc;

Expand Down Expand Up @@ -336,9 +336,9 @@ impl Object for PyDate {
mod tests {
use super::*;
use crate::modules::py_datetime::timedelta::PyTimeDelta;
use minijinja::Environment;
use minijinja::args;
use minijinja::context;
use minijinja::Environment;

#[test]
fn test_date_strftime() {
Expand All @@ -361,9 +361,11 @@ mod tests {

// Test error case - missing format argument
let error = date_arc.strftime(&[]).unwrap_err();
assert!(error
.to_string()
.contains("strftime requires one string argument"));
assert!(
error
.to_string()
.contains("strftime requires one string argument")
);
}

#[test]
Expand Down Expand Up @@ -546,9 +548,11 @@ mod tests {
// Test invalid date
let result = date_arc.replace(args!(month => 13));
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Invalid date: year=2023, month=13, day=15"));
assert!(
result
.unwrap_err()
.to_string()
.contains("Invalid date: year=2023, month=13, day=15")
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::sync::Arc;

use chrono::{DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike, Utc};
use chrono_tz::Tz;
use minijinja::{arg_utils::ArgParser, value::Object, Error, ErrorKind, Value};
use minijinja::{Error, ErrorKind, Value, arg_utils::ArgParser, value::Object};

use crate::modules::py_datetime::date::PyDate; // your date
use crate::modules::py_datetime::time::PyTime;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use chrono::{Local, NaiveDate, NaiveTime, Timelike};
use minijinja::{arg_utils::ArgParser, value::Object, Error, ErrorKind, Value};
use minijinja::{Error, ErrorKind, Value, arg_utils::ArgParser, value::Object};
use std::fmt;
use std::sync::Arc;

Expand Down Expand Up @@ -92,7 +92,7 @@ impl PyTimeClass {
return Err(Error::new(
ErrorKind::InvalidArgument,
format!("Invalid iso time format: {iso_str}: {e}"),
))
));
}
};

Expand Down Expand Up @@ -318,8 +318,8 @@ impl Object for PyTime {
mod tests {
use super::*;
use crate::modules::py_datetime::timedelta::PyTimeDelta;
use minijinja::context;
use minijinja::Environment;
use minijinja::context;

#[test]
fn test_time_strftime() {
Expand All @@ -342,9 +342,11 @@ mod tests {

// Test error case - missing format argument
let error = time_arc.strftime(&[]).unwrap_err();
assert!(error
.to_string()
.contains("strftime requires one string argument"));
assert!(
error
.to_string()
.contains("strftime requires one string argument")
);
}

#[test]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use chrono::Duration;
use minijinja::{arg_utils::ArgParser, value::Object, Error, ErrorKind, Value};
use minijinja::{Error, ErrorKind, Value, arg_utils::ArgParser, value::Object};
use std::fmt;
use std::sync::Arc;

Expand Down Expand Up @@ -263,9 +263,9 @@ impl Object for PyTimeDelta {
#[cfg(test)]
mod tests {
use super::*;
use minijinja::args;
use minijinja::Environment;
use minijinja::Value;
use minijinja::args;

#[test]
fn test_timedelta_creation() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use minijinja::{value::Object, Error, ErrorKind, Value};
use minijinja::{Error, ErrorKind, Value, value::Object};
use std::fmt;
use std::rc::Rc;
use std::sync::Arc;
Expand Down
36 changes: 28 additions & 8 deletions crates/dbt-jinja/minijinja-contrib/src/modules/re.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//! pattern-oriented usage consistent with MiniJinja's function/value approach.

use fancy_regex::{Captures, Expander, Regex}; // like python regex, fancy_regex supports lookadheds/lookbehinds
use minijinja::{value::Object, Error, ErrorKind, Value};
use minijinja::{Error, ErrorKind, Value, value::Object};
use std::{collections::BTreeMap, iter, sync::Arc};

/// Create a namespace with `re`-like functions for pattern matching.
Expand Down Expand Up @@ -295,13 +295,19 @@ fn get_or_compile_regex_and_text(args: &[Value]) -> Result<(Box<Regex>, &str), E
}

// First arg: either compiled or raw pattern
let pattern = args[0].to_string();
let compiled = Box::new(Regex::new(&pattern).map_err(|e| {
Error::new(
ErrorKind::InvalidOperation,
format!("Failed to compile regex: {e}"),
)
})?);
let compiled = if let Some(object) = args[0].as_object()
&& let Some(pattern) = object.downcast_ref::<Pattern>()
{
Box::new(pattern._compiled.clone())
} else {
let pattern = args[0].to_string();
Box::new(Regex::new(&pattern).map_err(|e| {
Error::new(
ErrorKind::InvalidOperation,
format!("Failed to compile regex: {e}"),
)
})?)
};

// Second arg: the text to match against
let text = args[1].to_string();
Expand Down Expand Up @@ -413,4 +419,18 @@ mod tests {
.unwrap();
assert!(!result.is_true());
}

#[test]
fn test_re_search() {
let result = re_search(&[
Value::from(".*".to_string()),
Value::from("xyz".to_string()),
])
.unwrap();
assert!(result.is_true());

let compiled_pattern = re_compile(&[Value::from(".*".to_string())]).unwrap();
let result = re_search(&[compiled_pattern, Value::from("xyz".to_string())]).unwrap();
assert!(result.is_true());
}
}
2 changes: 1 addition & 1 deletion crates/dbt-jinja/minijinja-contrib/src/pycompat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::BTreeSet;

use minijinja::tuple;
use minijinja::value::mutable_vec::MutableVec;
use minijinja::value::{from_args, ValueKind};
use minijinja::value::{ValueKind, from_args};
use minijinja::{Error, ErrorKind, State, Value};
use regex::Regex;

Expand Down
2 changes: 1 addition & 1 deletion crates/dbt-jinja/minijinja-contrib/tests/filters.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use minijinja::{context, Environment};
use minijinja::{Environment, context};
use minijinja_contrib::filters::pluralize;
use similar_asserts::assert_eq;

Expand Down
2 changes: 1 addition & 1 deletion crates/dbt-jinja/minijinja-contrib/tests/globals.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use insta::assert_snapshot;
use minijinja::{render, Environment};
use minijinja::{Environment, render};
use minijinja_contrib::globals::{cycler, joiner};

#[test]
Expand Down
64 changes: 38 additions & 26 deletions crates/dbt-jinja/minijinja-contrib/tests/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,50 @@ fn test_validation() {
let mut env = Environment::new();
env.add_global("validation", create_validation_namespace());

assert!(eval_expr(
&env,
"validation.any['compound', 'interleaved']('compound')"
)
.unwrap()
.is_true());
assert!(eval_expr(
&env,
"validation.any['compound', 'interleaved']('interleaved')"
)
.unwrap()
.is_true());
assert!(eval_expr(
&env,
"validation.any['compound', 'interleaved']('something_else')"
)
.is_err());

assert!(eval_expr(&env, "validation.any['compound']('compound')")
assert!(
eval_expr(
&env,
"validation.any['compound', 'interleaved']('compound')"
)
.unwrap()
.is_true()
);
assert!(
eval_expr(
&env,
"validation.any['compound', 'interleaved']('interleaved')"
)
.unwrap()
.is_true());
.is_true()
);
assert!(
eval_expr(
&env,
"validation.any['compound', 'interleaved']('something_else')"
)
.is_err()
);

assert!(
eval_expr(&env, "validation.any['compound']('compound')")
.unwrap()
.is_true()
);
assert!(eval_expr(&env, "validation.any['compound']('something_else')").is_err());
// compound here is a type, we should always return true when type is used
assert!(
eval_expr(&env, "validation.any[anytype, 'compound']('test')")
.unwrap()
.is_true()
);
assert!(eval_expr(&env, "validation.any[anytype](1)")
.unwrap()
.is_true());
assert!(eval_expr(&env, "validation.any['test', anytype](1)")
.unwrap()
.is_true());
assert!(
eval_expr(&env, "validation.any[anytype](1)")
.unwrap()
.is_true()
);
assert!(
eval_expr(&env, "validation.any['test', anytype](1)")
.unwrap()
.is_true()
);
}