Skip to content

Commit

Permalink
Implemented Revamped Object Model (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Nov 19, 2022
1 parent 72a4c73 commit f6cc6a4
Show file tree
Hide file tree
Showing 16 changed files with 927 additions and 306 deletions.
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))
} 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

0 comments on commit f6cc6a4

Please sign in to comment.