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

Remove mandatory serde dependency #539

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
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: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"rust-analyzer.cargo.allFeatures": true,
"rust-analyzer.checkOnSave.command": "clippy",
"python.formatting.provider": "black"
"python.formatting.provider": "black",
"rust-analyzer.showUnlinkedFileNotification": false
}
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ MiniJinja is a powerful but minimal dependency template engine for Rust which
is based on the syntax and behavior of the
[Jinja2](https://jinja.palletsprojects.com/) template engine for Python.

It's implemented on top of `serde` and only has it as a single required
dependency. It supports [a range of features from Jinja2](https://github.com/mitsuhiko/minijinja/blob/main/COMPATIBILITY.md)
It can be used entirely without dependencies though by default it uses
`serde`'s `Serialize` trait for value conversions. It supports
[a range of features from Jinja2](https://github.com/mitsuhiko/minijinja/blob/main/COMPATIBILITY.md)
including inheritance, filters and more. The goal is that it should be possible
to use some templates in Rust programs without the fear of pulling in complex
dependencies for a small problem. Additionally it tries not to re-invent
Expand All @@ -24,9 +25,8 @@ ecosystem of editor integrations.

```
$ cargo tree
minimal v0.1.0 (examples/minimal)
└── minijinja v2.5.0 (minijinja)
└── serde v1.0.144
minimal v0.1.0 (/Users/mitsuhiko/Development/minijinja/examples/minimal)
└── minijinja v2.5.0 (/Users/mitsuhiko/Development/minijinja/minijinja)
```

Additionally minijinja is also available as an (optionally pre-compiled) command line executable
Expand Down
8 changes: 4 additions & 4 deletions examples/build-script/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ fn main() {
include_str!("src/example.rs.jinja"),
struct_name => "Point",
points => vec![
(1.0, 2.0),
(2.0, 2.5),
(4.0, 1.0),
vec![1.0, 2.0],
vec![2.0, 2.5],
vec![4.0, 1.0],
],
build_cwd => env::current_dir().unwrap()
build_cwd => env::current_dir().unwrap().to_string_lossy().to_string(),
),
)
.unwrap();
Expand Down
2 changes: 1 addition & 1 deletion examples/deserialize/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ fn main() {
template
.render(context! {
path => std::env::current_dir().unwrap(),
point => point,
point => &point,
})
.unwrap()
);
Expand Down
2 changes: 1 addition & 1 deletion examples/invalid-value/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use minijinja::{context, Environment};
use serde::Serialize;

/// This struct makes no sense, and serde will fail serializing it.
#[derive(Serialize, Clone)]
#[derive(Serialize, Copy, Clone)]
pub struct BadStruct {
a: i32,
#[serde(flatten)]
Expand Down
2 changes: 1 addition & 1 deletion examples/minimal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
minijinja = { path = "../../minijinja", features = ["serde"], default-features = false }
minijinja = { path = "../../minijinja", default-features = false }
3 changes: 2 additions & 1 deletion examples/minimal/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ fn main() {
let tmpl = env.get_template("hello.txt").unwrap();
println!(
"{}",
tmpl.render(context!(names => ["John", "Peter"])).unwrap()
tmpl.render(context!(names => vec!["John", "Peter"]))
.unwrap()
);
}
11 changes: 6 additions & 5 deletions minijinja/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ default = [
"builtins",
"debug",
"deserialization",
"serde",
"macros",
"multi_template",
"adjacent_loop_items",
Expand All @@ -30,13 +31,13 @@ default = [

# API features
preserve_order = ["indexmap"]
deserialization = []
deserialization = ["serde"]
debug = []
loader = ["self_cell", "memo-map"]
unicode = ["unicode-ident", "unicase"]
custom_syntax = ["dep:aho-corasick"]
std_collections = []
serde = []
serde = ["dep:serde"]

# Speedups
key_interning = []
Expand All @@ -51,17 +52,17 @@ loop_controls = []
fuel = []

# Extra Filters
json = ["serde_json"]
json = ["serde", "serde_json"]
urlencode = ["percent-encoding"]

# Internal Features that should not be used
internal_debug = []
unstable_machinery = ["internal_debug"]
unstable_machinery_serde = ["unstable_machinery", "serde/derive"]
unstable_machinery_serde = ["serde", "unstable_machinery", "serde/derive"]

[dependencies]
aho-corasick = { version = "1.0", default-features = false, optional = true }
serde = "1.0.130"
serde = { version = "1.0.130", optional = true }
v_htmlescape = { version = "0.15.8", optional = true }
self_cell = { version = "1.0.4", optional = true }
serde_json = { version = "1.0.68", optional = true }
Expand Down
4 changes: 1 addition & 3 deletions minijinja/src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ use std::collections::BTreeMap;
use std::fmt;
use std::sync::Arc;

use serde::Serialize;

use crate::compiler::codegen::CodeGenerator;
use crate::compiler::instructions::Instructions;
use crate::compiler::parser::parse_expr;
Expand All @@ -13,7 +11,7 @@ use crate::expression::Expression;
use crate::output::Output;
use crate::template::{CompiledTemplate, CompiledTemplateRef, Template, TemplateConfig};
use crate::utils::{AutoEscape, BTreeMapKeysDebug, UndefinedBehavior};
use crate::value::{FunctionArgs, FunctionResult, Value};
use crate::value::{FunctionArgs, FunctionResult, Serialize, Value};
use crate::vm::State;
use crate::{defaults, filters, functions, tests};

Expand Down
6 changes: 2 additions & 4 deletions minijinja/src/expression.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
use std::collections::{BTreeMap, HashSet};
use std::fmt;

use serde::Serialize;

use crate::compiler::ast;
use crate::compiler::instructions::Instructions;
use crate::compiler::meta::find_undeclared;
use crate::compiler::parser::parse_expr;
use crate::environment::Environment;
use crate::error::Error;
use crate::output::Output;
use crate::value::Value;
use crate::value::{Serialize, Value};
use crate::vm::Vm;

/// A handle to a compiled expression.
Expand Down Expand Up @@ -88,7 +86,7 @@ impl<'env, 'source> Expression<'env, 'source> {
pub fn eval<S: Serialize>(&self, ctx: S) -> Result<Value, Error> {
// reduce total amount of code faling under mono morphization into
// this function, and share the rest in _eval.
self._eval(Value::from_serialize(&ctx))
self._eval(Value::from_serialize(ctx))
}

/// Returns a set of all undeclared variables in the expression.
Expand Down
27 changes: 7 additions & 20 deletions minijinja/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
//! MiniJinja is a powerful but minimal dependency template engine for Rust which
//! is based on the syntax and behavior of the
//! [Jinja2](https://jinja.palletsprojects.com/) template engine for Python. It's
//! implemented on top of [`serde`]. The goal is to be able to render a large
//! chunk of the Jinja2 template ecosystem from Rust with a minimal engine and to
//! leverage an already existing ecosystem of editor integrations.
//! implemented on top of `serde` though that dependency can be disabled. The goal
//! is to be able to render a large chunk of the Jinja2 template ecosystem from
//! Rust with a minimal engine and to leverage an already existing ecosystem of
//! editor integrations.
//!
//! ```jinja
//! {% for user in users %}
Expand Down Expand Up @@ -156,6 +157,9 @@
//!
//! - **Rust Functionality:**
//!
//! - `serde`: if this feature is removed the `serde` dependency is not used at all.
//! This restricts some value conversions that would otherwise be possible though
//! a large set of the engine stays functional.
//! - `debug`: if this feature is removed some debug functionality of the engine is
//! removed as well. This mainly affects the quality of error reporting.
//! - `deserialization`: when removed this disables deserialization support for
Expand Down Expand Up @@ -250,23 +254,6 @@ pub use self::value::Value;
pub use self::macros::__context;
pub use self::vm::State;

// forwards compatibility
#[cfg(not(feature = "serde"))]
const _: () = {
#[deprecated(
since = "2.0.4",
note = "Future versions of MiniJinja will require enabling \
the 'serde' feature to use serde types. To silence this warning \
add 'serde' to the least of features of minijinja."
)]
#[allow(unused)]
fn enable_implicit_serde_support() {}

fn trigger_warning() {
enable_implicit_serde_support();
}
};

/// This module gives access to the low level machinery.
///
/// This module is only provided by the `unstable_machinery` feature and does not
Expand Down
58 changes: 43 additions & 15 deletions minijinja/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ macro_rules! some {
#[doc(hidden)]
pub mod __context {
pub use crate::value::merge_object::MergeObject;
use crate::value::{Value, ValueMap};
use crate::value::{Serialize, Value, ValueMap};
use crate::Environment;
use std::rc::Rc;

Expand Down Expand Up @@ -54,6 +54,26 @@ pub mod __context {
}
ENV.with(|x| x.clone())
}

pub struct Convert<T>(pub T);

pub trait ConvertToValue<T> {
fn convert_to_minijinja_value(self) -> Value;
}

impl<T: Serialize> ConvertToValue<T> for &Convert<T> {
#[inline(always)]
fn convert_to_minijinja_value(self) -> Value {
Value::from_serialize(&self.0)
}
}

impl<T: Into<Value>> ConvertToValue<T> for Convert<T> {
#[inline(always)]
fn convert_to_minijinja_value(self) -> Value {
self.0.into()
}
}
}

/// Creates a template context from keys and values or merging in another value.
Expand Down Expand Up @@ -120,14 +140,11 @@ pub mod __context {
///
/// # Note on Conversions
///
/// This macro uses [`Value::from_serialize`](crate::Value::from_serialize)
/// for conversions.
///
/// This macro currently does not move passed values. Future versions of
/// MiniJinja are going to change the move behavior and it's recommended to not
/// depend on this implicit reference behavior. You should thus pass values
/// with `&value` if you intend on still being able to reference them
/// after the macro invocation.
/// The `context!` macro is special in that it supports two forms of value
/// conversions. It first attempts to convert a value with `Into<Value>`
/// and if that fails, it falls back to [`Value::from_serialize`]. This
/// means that if you have conflicting implementations, `From<YourType> for Value`
/// will be used first.
#[macro_export]
macro_rules! context {
() => {
Expand Down Expand Up @@ -176,7 +193,7 @@ macro_rules! __context_pair {
$crate::__context::add(
&mut $ctx,
stringify!($key),
$crate::value::Value::from_serialize(&$value),
$crate::__make_value!($value),
);
};
}
Expand All @@ -203,13 +220,24 @@ macro_rules! __context_pair {
/// ```
///
/// Note that this like [`context!`](crate::context) goes through
/// [`Value::from_serialize`](crate::value::Value::from_serialize).
/// `Into<Value>` and [`Value::from_serialize`](crate::value::Value::from_serialize)
/// for conversions.
#[macro_export]
macro_rules! args {
() => { &[][..] as &[$crate::value::Value] };
($($arg:tt)*) => { $crate::__args_helper!(branch [[$($arg)*]], [$($arg)*]) };
}

/// Converts an object into a value
#[macro_export]
#[doc(hidden)]
macro_rules! __make_value {
($expr:expr) => {{
use $crate::__context::ConvertToValue;
$crate::__context::Convert($expr).convert_to_minijinja_value()
}};
}

/// Utility macro for `args!`
#[macro_export]
#[doc(hidden)]
Expand Down Expand Up @@ -245,17 +273,17 @@ macro_rules! __args_helper {
// `$args` or `$kwargs` depending on type.
(peel $args:ident, $kwargs:ident, $has_kwargs:ident, []) => {};
(peel $args:ident, $kwargs:ident, $has_kwargs:ident, [$name:ident => $expr:expr]) => {
$kwargs.push((stringify!($name), $crate::value::Value::from_serialize(&$expr)));
$kwargs.push((stringify!($name), $crate::__make_value!($expr)));
};
(peel $args:ident, $kwargs:ident, $has_kwargs:ident, [$name:ident => $expr:expr, $($rest:tt)*]) => {
$kwargs.push((stringify!($name), $crate::value::Value::from_serialize(&$expr)));
$kwargs.push((stringify!($name), $crate::__make_value!($expr)));
$crate::__args_helper!(peel $args, $kwargs, true, [$($rest)*]);
};
(peel $args:ident, $kwargs:ident, false, [$expr:expr]) => {
$args.push($crate::value::Value::from_serialize(&$expr));
$args.push($crate::__make_value!($expr));
};
(peel $args:ident, $kwargs:ident, false, [$expr:expr, $($rest:tt)*]) => {
$args.push($crate::value::Value::from_serialize(&$expr));
$args.push($crate::__make_value!($expr));
$crate::__args_helper!(peel $args, $kwargs, false, [$($rest)*]);
};
}
Expand Down
14 changes: 6 additions & 8 deletions minijinja/src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ use std::ops::Deref;
use std::sync::Arc;
use std::{fmt, io};

use serde::Serialize;

use crate::compiler::codegen::CodeGenerator;
use crate::compiler::instructions::Instructions;
use crate::compiler::lexer::WhitespaceConfig;
Expand All @@ -15,7 +13,7 @@ use crate::error::{attach_basic_debug_info, Error};
use crate::output::{Output, WriteWrapper};
use crate::syntax::SyntaxConfig;
use crate::utils::AutoEscape;
use crate::value::{self, Value};
use crate::value::{self, Serialize, Value};
use crate::vm::{prepare_blocks, Context, State, Vm};

/// Callback for auto escape determination
Expand Down Expand Up @@ -90,7 +88,7 @@ impl<'env, 'source> Template<'env, 'source> {
/// Renders the template into a string.
///
/// The provided value is used as the initial context for the template. It
/// can be any object that implements [`Serialize`](serde::Serialize). You
/// can be any object that implements [`Serialize`](crate::value::Serialize). You
/// can either create your own struct and derive `Serialize` for it or the
/// [`context!`](crate::context) macro can be used to create an ad-hoc context.
///
Expand All @@ -115,7 +113,7 @@ impl<'env, 'source> Template<'env, 'source> {
pub fn render<S: Serialize>(&self, ctx: S) -> Result<String, Error> {
// reduce total amount of code faling under mono morphization into
// this function, and share the rest in _render.
self._render(Value::from_serialize(&ctx)).map(|x| x.0)
self._render(Value::from_serialize(ctx)).map(|x| x.0)
}

/// Like [`render`](Self::render) but also return the evaluated [`State`].
Expand All @@ -141,7 +139,7 @@ impl<'env, 'source> Template<'env, 'source> {
) -> Result<(String, State<'_, 'env>), Error> {
// reduce total amount of code faling under mono morphization into
// this function, and share the rest in _render.
self._render(Value::from_serialize(&ctx))
self._render(Value::from_serialize(ctx))
}

fn _render(&self, root: Value) -> Result<(String, State<'_, 'env>), Error> {
Expand Down Expand Up @@ -175,7 +173,7 @@ impl<'env, 'source> Template<'env, 'source> {
) -> Result<State<'_, 'env>, Error> {
let mut wrapper = WriteWrapper { w, err: None };
self._eval(
Value::from_serialize(&ctx),
Value::from_serialize(ctx),
&mut Output::with_write(&mut wrapper),
)
.map(|(_, state)| state)
Expand Down Expand Up @@ -204,7 +202,7 @@ impl<'env, 'source> Template<'env, 'source> {
///
/// For more information see [`State`].
pub fn eval_to_state<S: Serialize>(&self, ctx: S) -> Result<State<'_, 'env>, Error> {
let root = Value::from_serialize(&ctx);
let root = Value::from_serialize(ctx);
let mut out = Output::null();
let vm = Vm::new(self.env);
let state = ok!(vm.eval(
Expand Down
Loading