Skip to content

Commit a470b62

Browse files
authored
Merge pull request #506 from epage/render
feat: Add render tag
2 parents 35314b6 + 3f46fa4 commit a470b62

File tree

12 files changed

+1035
-9
lines changed

12 files changed

+1035
-9
lines changed

crates/core/src/parser/filter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ pub struct FilterArguments<'a> {
110110
/// }
111111
///
112112
/// impl Filter for AtLeastFilter {
113-
/// fn evaluate(&self, input: &ValueViwe, runtime: &dyn Runtime) -> Result<Value> {
113+
/// fn evaluate(&self, input: &ValueView, runtime: &dyn Runtime) -> Result<Value> {
114114
/// // Evaluate the `FilterParameters`
115115
/// let args = self.args.evaluate(runtime)?;
116116
///

crates/core/src/runtime/stack.rs

Lines changed: 178 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use crate::error::Error;
22
use crate::error::Result;
33
use crate::model::{Object, ObjectView, ScalarCow, Value, ValueCow, ValueView};
44

5+
use super::Registers;
6+
57
/// Layer variables on top of the existing runtime
68
pub struct StackFrame<P, O> {
79
parent: P,
@@ -89,12 +91,14 @@ impl<P: super::Runtime, O: ObjectView> super::Runtime for StackFrame<P, O> {
8991
}
9092
}
9193

92-
pub(crate) struct GlobalFrame<P> {
94+
/// A stack frame that only provides a sandboxed set of globals
95+
pub struct GlobalFrame<P> {
9396
parent: P,
9497
data: std::cell::RefCell<Object>,
9598
}
9699

97100
impl<P: super::Runtime> GlobalFrame<P> {
101+
/// Override globals for `parent`
98102
pub fn new(parent: P) -> Self {
99103
Self {
100104
parent,
@@ -238,3 +242,176 @@ impl<P: super::Runtime> super::Runtime for IndexFrame<P> {
238242
self.parent.registers()
239243
}
240244
}
245+
246+
/// A [`StackFrame`] where variables are not recursively searched for,
247+
/// However, you can still access the parent's partials.
248+
pub struct SandboxedStackFrame<P, O> {
249+
parent: P,
250+
name: Option<crate::model::KString>,
251+
data: O,
252+
registers: Registers,
253+
}
254+
255+
impl<P: super::Runtime, O: ObjectView> SandboxedStackFrame<P, O> {
256+
/// Create a new [`SandboxedStackFrame`] from a parent and some data
257+
pub fn new(parent: P, data: O) -> Self {
258+
Self {
259+
parent,
260+
name: None,
261+
data,
262+
registers: Default::default(),
263+
}
264+
}
265+
266+
/// Name the current context
267+
pub fn with_name<S: Into<crate::model::KString>>(mut self, name: S) -> Self {
268+
self.name = Some(name.into());
269+
self
270+
}
271+
}
272+
273+
impl<P: super::Runtime, O: ObjectView> super::Runtime for SandboxedStackFrame<P, O> {
274+
fn partials(&self) -> &dyn super::PartialStore {
275+
self.parent.partials()
276+
}
277+
278+
fn name(&self) -> Option<crate::model::KStringRef<'_>> {
279+
self.name
280+
.as_ref()
281+
.map(|n| n.as_ref())
282+
.or_else(|| self.parent.name())
283+
}
284+
285+
fn roots(&self) -> std::collections::BTreeSet<crate::model::KStringCow<'_>> {
286+
let mut roots = std::collections::BTreeSet::new();
287+
roots.extend(self.data.keys());
288+
roots
289+
}
290+
291+
fn try_get(&self, path: &[ScalarCow<'_>]) -> Option<ValueCow<'_>> {
292+
let key = path.first()?;
293+
let key = key.to_kstr();
294+
let data = &self.data;
295+
data.get(key.as_str())
296+
.and_then(|_| crate::model::try_find(data.as_value(), path))
297+
}
298+
299+
fn get(&self, path: &[ScalarCow<'_>]) -> Result<ValueCow<'_>> {
300+
let key = path.first().ok_or_else(|| {
301+
Error::with_msg("Unknown variable").context("requested variable", "nil")
302+
})?;
303+
let key = key.to_kstr();
304+
let data = &self.data;
305+
data.get(key.as_str())
306+
.and_then(|_| crate::model::try_find(data.as_value(), path))
307+
.map(|v| v.into_owned().into())
308+
.ok_or_else(|| Error::with_msg("Unknown variable").context("requested variable", key))
309+
}
310+
311+
fn set_global(
312+
&self,
313+
name: crate::model::KString,
314+
val: crate::model::Value,
315+
) -> Option<crate::model::Value> {
316+
self.parent.set_global(name, val)
317+
}
318+
319+
fn set_index(&self, name: crate::model::KString, val: Value) -> Option<Value> {
320+
self.parent.set_index(name, val)
321+
}
322+
323+
fn get_index<'a>(&'a self, name: &str) -> Option<ValueCow<'a>> {
324+
self.parent.get_index(name)
325+
}
326+
327+
fn registers(&self) -> &super::Registers {
328+
&self.registers
329+
}
330+
}
331+
332+
#[cfg(test)]
333+
mod tests {
334+
use crate::{runtime::RuntimeBuilder, Runtime};
335+
336+
use super::*;
337+
338+
#[test]
339+
fn test_opaque_stack_frame_try_get() {
340+
let globals = {
341+
let mut o = Object::new();
342+
o.insert("a".into(), Value::Scalar(1i64.into()));
343+
o
344+
};
345+
let runtime = RuntimeBuilder::new().set_globals(&globals).build();
346+
let opaque_stack_frame = SandboxedStackFrame::new(&runtime, {
347+
let mut o = Object::new();
348+
o.insert("b".into(), Value::Scalar(2i64.into()));
349+
o
350+
});
351+
352+
// Testing that you can access variables in the current frame, but not the parent
353+
assert!(opaque_stack_frame.try_get(&["a".into()]).is_none());
354+
assert!(opaque_stack_frame.try_get(&["b".into()]).is_some());
355+
356+
let stack_frame = StackFrame::new(opaque_stack_frame, {
357+
let mut o = Object::new();
358+
o.insert("c".into(), Value::Scalar(1i64.into()));
359+
o
360+
});
361+
362+
// Testing that a child of a OpaqueStackFrame can access access OpaqueStackFrame's variables but not the parent
363+
assert!(stack_frame.try_get(&["a".into()]).is_none());
364+
assert!(stack_frame.try_get(&["b".into()]).is_some());
365+
assert!(stack_frame.try_get(&["c".into()]).is_some());
366+
}
367+
368+
#[test]
369+
fn test_opaque_stack_frame_get() {
370+
let globals = {
371+
let mut o = Object::new();
372+
o.insert("a".into(), Value::Scalar(1i64.into()));
373+
o
374+
};
375+
let runtime = RuntimeBuilder::new().set_globals(&globals).build();
376+
let opaque_stack_frame = SandboxedStackFrame::new(&runtime, {
377+
let mut o = Object::new();
378+
o.insert("b".into(), Value::Scalar(2i64.into()));
379+
o
380+
});
381+
382+
// Testing that you can access variables in the current frame, but not the parent
383+
assert!(opaque_stack_frame.get(&["a".into()]).is_err());
384+
assert!(opaque_stack_frame.get(&["b".into()]).is_ok());
385+
386+
let stack_frame = StackFrame::new(opaque_stack_frame, {
387+
let mut o = Object::new();
388+
o.insert("c".into(), Value::Scalar(1i64.into()));
389+
o
390+
});
391+
392+
// Testing that a child of a OpaqueStackFrame can access access OpaqueStackFrame's variables but not the parent
393+
assert!(stack_frame.get(&["a".into()]).is_err());
394+
assert!(stack_frame.get(&["b".into()]).is_ok());
395+
assert!(stack_frame.get(&["c".into()]).is_ok());
396+
}
397+
398+
#[test]
399+
fn test_opaque_stack_frame_roots() {
400+
let globals = {
401+
let mut o = Object::new();
402+
o.insert("a".into(), Value::Scalar(1i64.into()));
403+
o
404+
};
405+
let runtime = RuntimeBuilder::new().set_globals(&globals).build();
406+
let opaque_stack_frame = SandboxedStackFrame::new(&runtime, {
407+
let mut o = Object::new();
408+
o.insert("b".into(), Value::Scalar(2i64.into()));
409+
o
410+
});
411+
let roots = opaque_stack_frame.roots();
412+
413+
// Testing that the roots are not copied from the parent
414+
assert!(!roots.contains("a"));
415+
assert!(roots.contains("b"));
416+
}
417+
}

crates/derive/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ use proc_macro::TokenStream;
4545
///
4646
/// If you want to only accept a certain type of `Value` for a given argument,
4747
/// you may mark its type with `arg_type = "..."`. This will take away the burden
48-
/// and boilerplate of handling errors and unwraping the liquid `Value` into a rust
48+
/// and boilerplate of handling errors and unwrapping the liquid `Value` into a rust
4949
/// type.
5050
///
5151
/// Right now, there is a default `arg_type`, "any", that accepts any value, as well

crates/lib/src/stdlib/blocks/for_block.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ impl Renderable for For {
226226
}
227227

228228
#[derive(Debug, Clone, ValueView, ObjectView)]
229-
struct ForloopObject<'p> {
229+
pub struct ForloopObject<'p> {
230230
length: i64,
231231
parentloop: Option<&'p dyn ValueView>,
232232
index0: i64,
@@ -238,7 +238,7 @@ struct ForloopObject<'p> {
238238
}
239239

240240
impl<'p> ForloopObject<'p> {
241-
fn new(i: usize, len: usize) -> Self {
241+
pub fn new(i: usize, len: usize) -> Self {
242242
let i = i as i64;
243243
let len = len as i64;
244244
let first = i == 0;
@@ -520,7 +520,7 @@ fn evaluate_attr(attr: &Option<Expression>, runtime: &dyn Runtime) -> Result<Opt
520520
}
521521

522522
#[derive(Clone, Debug)]
523-
enum RangeExpression {
523+
pub enum RangeExpression {
524524
Array(Expression),
525525
Counted(Expression, Expression),
526526
}
@@ -554,7 +554,7 @@ impl fmt::Display for RangeExpression {
554554
}
555555

556556
#[derive(Clone, Debug)]
557-
enum Range<'r> {
557+
pub enum Range<'r> {
558558
Array(ValueCow<'r>),
559559
Counted(i64, i64),
560560
}

crates/lib/src/stdlib/blocks/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ pub use self::if_block::IfBlock;
1515
pub use self::if_block::UnlessBlock;
1616
pub use self::ifchanged_block::IfChangedBlock;
1717
pub use self::raw_block::RawBlock;
18+
19+
pub use self::for_block::ForloopObject;
20+
pub use self::for_block::Range;
21+
pub use self::for_block::RangeExpression;

crates/lib/src/stdlib/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ mod tags;
55
pub use blocks::*;
66
pub use filters::*;
77
pub use tags::*;
8+
9+
pub use blocks::ForloopObject;
10+
pub use blocks::Range;
11+
pub use blocks::RangeExpression;

crates/lib/src/stdlib/tags/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod cycle_tag;
33
mod include_tag;
44
mod increment_tags;
55
mod interrupt_tags;
6+
mod render_tag;
67

78
pub use self::assign_tag::AssignTag;
89
pub use self::cycle_tag::CycleTag;
@@ -11,3 +12,4 @@ pub use self::increment_tags::DecrementTag;
1112
pub use self::increment_tags::IncrementTag;
1213
pub use self::interrupt_tags::BreakTag;
1314
pub use self::interrupt_tags::ContinueTag;
15+
pub use self::render_tag::RenderTag;

0 commit comments

Comments
 (0)