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

Implemented Revamped Object Model #148

Merged
merged 16 commits into from Nov 19, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 19 additions & 2 deletions examples/dynamic/src/main.rs
Expand Up @@ -2,7 +2,7 @@
use std::fmt;
use std::sync::atomic::{AtomicUsize, Ordering};

use minijinja::value::{from_args, Object, Value};
use minijinja::value::{from_args, Object, SeqObject, Value};
use minijinja::{Environment, Error, State};

#[derive(Debug)]
Expand Down Expand Up @@ -50,17 +50,34 @@ impl Object for Magic {
Ok(Value::from(format!("magic-{}", tag)))
} else {
Err(Error::new(
minijinja::ErrorKind::InvalidOperation,
minijinja::ErrorKind::UnknownMethod,
format!("object has no method named {}", name),
))
}
}
}

struct SimpleDynamicSeq;

impl SeqObject for SimpleDynamicSeq {
fn get_item(&self, idx: usize) -> Option<Value> {
if idx < 3 {
Some(Value::from(idx * 2))
} else {
None
}
}

fn item_count(&self) -> usize {
3
}
}

fn main() {
let mut env = Environment::new();
env.add_function("cycler", make_cycler);
env.add_global("magic", Value::from_object(Magic));
env.add_global("seq", Value::from_seq_object(SimpleDynamicSeq));
env.add_template("template.html", include_str!("template.html"))
.unwrap();

Expand Down
6 changes: 5 additions & 1 deletion examples/dynamic/src/template.html
Expand Up @@ -4,4 +4,8 @@
<li class={{ next_class() }}>{{ char }}</li>
{%- endfor %}
</ul>
{%- endwith %}
{%- endwith %}

{% for item in seq %}
[{{ item }}]
{% endfor %}
10 changes: 9 additions & 1 deletion examples/load-lazy/src/main.rs
Expand Up @@ -4,6 +4,8 @@ use std::fmt;
use std::fs;
use std::sync::Mutex;

use minijinja::value::ObjectKind;
use minijinja::value::StructObject;
use minijinja::value::{Object, Value};
use minijinja::Environment;

Expand All @@ -19,13 +21,19 @@ impl fmt::Display for Site {
}

impl Object for Site {
fn kind(&self) -> ObjectKind<'_> {
ObjectKind::Struct(self)
}
}

impl StructObject for Site {
/// This loads a file on attribute access. Note that attribute access
/// can neither access the state nor return failures as such it can at
/// max turn into an undefined object.
///
/// If that is necessary, use `call_method()` instead which is able to
/// both access interpreter state and fail.
fn get_attr(&self, name: &str) -> Option<Value> {
fn get_field(&self, name: &str) -> Option<Value> {
let mut cache = self.cache.lock().unwrap();
if let Some(rv) = cache.get(name) {
return Some(rv.clone());
Expand Down
3 changes: 3 additions & 0 deletions minijinja/src/error.rs
Expand Up @@ -118,6 +118,8 @@ pub enum ErrorKind {
UnknownTest,
/// A function is unknown
UnknownFunction,
/// Un unknown method was called
UnknownMethod,
/// A bad escape sequence in a string was encountered.
BadEscape,
/// An operation on an undefined value was attempted.
Expand Down Expand Up @@ -147,6 +149,7 @@ impl ErrorKind {
ErrorKind::UnknownFilter => "unknown filter",
ErrorKind::UnknownFunction => "unknown function",
ErrorKind::UnknownTest => "unknown test",
ErrorKind::UnknownMethod => "unknown method",
ErrorKind::BadEscape => "bad string escape",
ErrorKind::UndefinedError => "undefined value",
ErrorKind::BadSerialization => "could not serialize to internal format",
Expand Down
91 changes: 57 additions & 34 deletions minijinja/src/filters.rs
Expand Up @@ -250,7 +250,7 @@ mod builtins {
use super::*;

use crate::error::ErrorKind;
use crate::value::{ValueKind, ValueRepr};
use crate::value::ValueRepr;
use std::borrow::Cow;
use std::fmt::Write;
use std::mem;
Expand Down Expand Up @@ -404,10 +404,8 @@ mod builtins {
pub fn reverse(v: Value) -> Result<Value, Error> {
if let Some(s) = v.as_str() {
Ok(Value::from(s.chars().rev().collect::<String>()))
} else if matches!(v.kind(), ValueKind::Seq) {
Ok(Value::from(
ok!(v.as_slice()).iter().rev().cloned().collect::<Vec<_>>(),
))
} else if let Some(seq) = v.as_seq() {
Ok(Value::from(seq.iter().rev().collect::<Vec<_>>()))
} else {
Err(Error::new(
ErrorKind::InvalidOperation,
Expand Down Expand Up @@ -446,9 +444,9 @@ mod builtins {
rv.push(c);
}
Ok(rv)
} else if matches!(val.kind(), ValueKind::Seq) {
} else if let Some(seq) = val.as_seq() {
let mut rv = String::new();
for item in ok!(val.as_slice()) {
for item in seq.iter() {
if !rv.is_empty() {
rv.push_str(joiner);
}
Expand Down Expand Up @@ -537,13 +535,15 @@ mod builtins {
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn first(value: Value) -> Result<Value, Error> {
match value.0 {
ValueRepr::String(s, _) => Ok(s.chars().next().map_or(Value::UNDEFINED, Value::from)),
ValueRepr::Seq(ref s) => Ok(s.first().cloned().unwrap_or(Value::UNDEFINED)),
_ => Err(Error::new(
if let Some(s) = value.as_str() {
Ok(s.chars().next().map_or(Value::UNDEFINED, Value::from))
} else if let Some(s) = value.as_seq() {
Ok(s.get_item(0).unwrap_or(Value::UNDEFINED))
mitsuhiko marked this conversation as resolved.
Show resolved Hide resolved
} else {
Err(Error::new(
ErrorKind::InvalidOperation,
"cannot get first item from value",
)),
))
}
}

Expand All @@ -564,15 +564,15 @@ mod builtins {
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn last(value: Value) -> Result<Value, Error> {
match value.0 {
ValueRepr::String(s, _) => {
Ok(s.chars().rev().next().map_or(Value::UNDEFINED, Value::from))
}
ValueRepr::Seq(ref s) => Ok(s.last().cloned().unwrap_or(Value::UNDEFINED)),
_ => Err(Error::new(
if let Some(s) = value.as_str() {
Ok(s.chars().rev().next().map_or(Value::UNDEFINED, Value::from))
} else if let Some(seq) = value.as_seq() {
Ok(seq.iter().last().unwrap_or(Value::UNDEFINED))
} else {
Err(Error::new(
ErrorKind::InvalidOperation,
"cannot get last item from value",
)),
))
}
}

Expand All @@ -584,21 +584,14 @@ mod builtins {
/// an empty list is returned.
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn list(value: Value) -> Result<Value, Error> {
match &value.0 {
ValueRepr::Undefined => Ok(Value::from(Vec::<Value>::new())),
ValueRepr::String(ref s, _) => {
Ok(Value::from(s.chars().map(Value::from).collect::<Vec<_>>()))
}
ValueRepr::Seq(_) => Ok(value.clone()),
ValueRepr::Map(ref m, _) => Ok(Value::from(
m.iter()
.map(|x| Value::from(x.0.clone()))
.collect::<Vec<_>>(),
)),
_ => Err(Error::new(
ErrorKind::InvalidOperation,
"cannot convert value to list",
)),
if let Some(s) = value.as_str() {
Ok(Value::from(s.chars().map(Value::from).collect::<Vec<_>>()))
} else {
let iter = ok!(value.try_iter().map_err(|err| {
Error::new(ErrorKind::InvalidOperation, "cannot convert value to list")
.with_source(err)
}));
Ok(Value::from(iter.collect::<Vec<_>>()))
}
}

Expand Down Expand Up @@ -876,6 +869,36 @@ mod builtins {
);
});
}

#[test]
fn test_values_in_vec() {
fn upper(value: &str) -> String {
value.to_uppercase()
}

fn sum(value: Vec<i64>) -> i64 {
value.into_iter().sum::<i64>()
}

let upper = BoxedFilter::new(upper);
let sum = BoxedFilter::new(sum);

let env = crate::Environment::new();
State::with_dummy(&env, |state| {
assert_eq!(
upper
.apply_to(state, &[Value::from("Hello World!")])
.unwrap(),
Value::from("HELLO WORLD!")
);

assert_eq!(
sum.apply_to(state, &[Value::from(vec![Value::from(1), Value::from(2)])])
.unwrap(),
Value::from(3)
);
});
}
}

#[cfg(feature = "builtins")]
Expand Down