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

Add borrow support for filter arguments #115

Merged
merged 10 commits into from Sep 14, 2022
38 changes: 20 additions & 18 deletions .github/workflows/tests.yml
Expand Up @@ -17,32 +17,34 @@ jobs:
- name: Test
run: make test

build-old-stable:
name: Build on 1.45.0
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.45.0
profile: minimal
override: true
- name: Build
run: cargo build --features=unstable_machinery,builtins,source,json,urlencode,debug,internal_debug
working-directory: ./minijinja
env:
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
CARGO_HTTP_MULTIPLEXING: "false"
## Bugs in older Rust versions for some of our borrowing logic for filters
## No longer makes supporting this Rust version possible
# build-old-stable:
# name: Build on 1.45.0
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v1
# - uses: actions-rs/toolchain@v1
# with:
# toolchain: 1.45.0
# profile: minimal
# override: true
# - name: Build
# run: cargo build --features=unstable_machinery,builtins,source,json,urlencode,debug,internal_debug
# working-directory: ./minijinja
# env:
# CARGO_NET_GIT_FETCH_WITH_CLI: "true"
# CARGO_HTTP_MULTIPLEXING: "false"

test-stable:
name: Test on 1.56.1
name: Test on 1.61.0
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.56.1
toolchain: 1.61.0
profile: minimal
override: true
- name: Test
Expand Down
11 changes: 7 additions & 4 deletions README.md
Expand Up @@ -5,7 +5,7 @@
[![Build Status](https://github.com/mitsuhiko/minijinja/workflows/Tests/badge.svg?branch=main)](https://github.com/mitsuhiko/minijinja/actions?query=workflow%3ATests)
[![License](https://img.shields.io/github/license/mitsuhiko/minijinja)](https://github.com/mitsuhiko/minijinja/blob/main/LICENSE)
[![Crates.io](https://img.shields.io/crates/d/minijinja.svg)](https://crates.io/crates/minijinja)
[![rustc 1.45.0](https://img.shields.io/badge/rust-1.45%2B-orange.svg)](https://img.shields.io/badge/rust-1.45%2B-orange.svg)
[![rustc 1.61.0](https://img.shields.io/badge/rust-1.61%2B-orange.svg)](https://img.shields.io/badge/rust-1.61%2B-orange.svg)
[![Documentation](https://docs.rs/minijinja/badge.svg)](https://docs.rs/minijinja)

</div>
Expand Down Expand Up @@ -63,9 +63,12 @@ fn main() {

## Minimum Rust Version

MiniJinja supports Rust versions down to 1.45 at the moment. For the order
preservation feature Rust 1.49 is required as it uses the indexmap dependency
which no longer supports older Rust versions.
MiniJinja's development version requires Rust 1.61 due to limitations with
HRTBs in older Rust versions.

MiniJinja 0.20 supports Rust versions down to 1.45. It is possible to write
code that is compatible with both 0.20 and newer versions of MiniJinja which
should make it possible to defer the upgrade to later.

## Sponsor

Expand Down
8 changes: 5 additions & 3 deletions examples/dynamic/src/main.rs
Expand Up @@ -2,7 +2,7 @@
use std::fmt;
use std::sync::atomic::{AtomicUsize, Ordering};

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

#[derive(Debug)]
Expand All @@ -19,7 +19,8 @@ impl fmt::Display for Cycler {

impl Object for Cycler {
fn call(&self, _state: &State, args: &[Value]) -> Result<Value, Error> {
let _: () = FunctionArgs::from_values(args)?;
// we don't want any args
from_args(args)?;
let idx = self.idx.fetch_add(1, Ordering::Relaxed);
Ok(self.values[idx % self.values.len()].clone())
}
Expand All @@ -44,7 +45,8 @@ impl fmt::Display for Magic {
impl Object for Magic {
fn call_method(&self, _state: &State, name: &str, args: &[Value]) -> Result<Value, Error> {
if name == "make_class" {
let (tag,): (String,) = FunctionArgs::from_values(args)?;
// single string argument
let (tag,): (&str,) = from_args(args)?;
Ok(Value::from(format!("magic-{}", tag)))
} else {
Err(Error::new(
Expand Down
2 changes: 1 addition & 1 deletion minijinja/src/compiler/codegen.rs
Expand Up @@ -412,7 +412,7 @@ impl<'source> CodeGenerator<'source> {
for arg in &f.args {
self.compile_expr(arg)?;
}
self.add(Instruction::BuildList(f.args.len()));
self.add(Instruction::BuildList(f.args.len() + 1));
self.add(Instruction::ApplyFilter(f.name));
}
ast::Expr::Test(f) => {
Expand Down
2 changes: 2 additions & 0 deletions minijinja/src/defaults.rs
Expand Up @@ -118,6 +118,7 @@ pub(crate) fn get_builtin_filters() -> BTreeMap<&'static str, filters::BoxedFilt
rv.insert("urlencode", BoxedFilter::new(filters::urlencode));
}
}

rv
}

Expand Down Expand Up @@ -151,5 +152,6 @@ pub(crate) fn get_globals() -> BTreeMap<&'static str, Value> {
rv.insert("dict", BoxedFunction::new(functions::dict).to_value());
rv.insert("debug", BoxedFunction::new(functions::debug).to_value());
}

rv
}
21 changes: 14 additions & 7 deletions minijinja/src/environment.rs
Expand Up @@ -361,10 +361,11 @@ impl<'source> Environment<'source> {
/// Filter functions are functions that can be applied to values in
/// templates. For details about filters have a look at
/// [`Filter`](crate::filters::Filter).
pub fn add_filter<F, V, Rv, Args>(&mut self, name: &'source str, f: F)
pub fn add_filter<F, Rv, Args>(&mut self, name: &'source str, f: F)
where
F: filters::Filter<V, Rv, Args>,
V: for<'a> ArgType<'a>,
// the crazy bounds here exist to enable borrowing in closures
F: filters::Filter<Rv, Args>
+ for<'a> filters::Filter<Rv, <Args as FunctionArgs<'a>>::Output>,
Rv: FunctionResult,
Args: for<'a> FunctionArgs<'a>,
{
Expand All @@ -381,11 +382,13 @@ impl<'source> Environment<'source> {
/// Test functions are similar to filters but perform a check on a value
/// where the return value is always true or false. For details about tests
/// have a look at [`Test`](crate::tests::Test).
pub fn add_test<F, V, Rv, Args>(&mut self, name: &'source str, f: F)
pub fn add_test<F, Rv, V, Args>(&mut self, name: &'source str, f: F)
where
V: for<'a> ArgType<'a>,
// the crazy bounds here exist to enable borrowing in closures
F: tests::Test<Rv, V, Args>
+ for<'a> tests::Test<Rv, <V as ArgType<'a>>::Output, <Args as FunctionArgs<'a>>::Output>,
Rv: tests::TestResult,
F: tests::Test<V, Rv, Args>,
V: for<'a> ArgType<'a>,
Args: for<'a> FunctionArgs<'a>,
{
self.tests.insert(name, tests::BoxedTest::new(f));
Expand All @@ -400,10 +403,14 @@ impl<'source> Environment<'source> {
///
/// For details about functions have a look at [`functions`]. Note that
/// functions and other global variables share the same namespace.
/// For more details about functions have a look at
/// [`Function`](crate::functions::Function).
pub fn add_function<F, Rv, Args>(&mut self, name: &'source str, f: F)
where
// the crazy bounds here exist to enable borrowing in closures
F: functions::Function<Rv, Args>
+ for<'a> functions::Function<Rv, <Args as FunctionArgs<'a>>::Output>,
Rv: FunctionResult,
F: functions::Function<Rv, Args>,
Args: for<'a> FunctionArgs<'a>,
{
self.add_global(name, functions::BoxedFunction::new(f).to_value());
Expand Down