Merge pull request #37 from GuillaumeGomez/method-call

Favor methods over closure fields
This commit is contained in:
René Kijewski 2024-07-02 15:45:41 +02:00 committed by GitHub
commit ca85ef6050
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 115 additions and 33 deletions

View File

@ -245,13 +245,11 @@ struct MyTemplate;
### Struct / trait implementations
Finally, we can invoke functions that are implementation methods of our
template struct, by referencing `Self` (note the uppercase `S`) as the path,
before calling our function:
Finally, we can call methods of our template struct:
```rust
#[derive(Template)]
#[template(source = "{{ Self::foo(self, 123) }}", ext = "txt")]
#[template(source = "{{ foo(123) }}", ext = "txt")]
struct MyTemplate {
count: u32,
};
@ -263,9 +261,8 @@ impl MyTemplate {
}
```
If the implemented method requires a reference to the struct itself,
such as is demonstrated in the above example, we can pass `self`
(note the lowercase `s`) as the first argument.
You can also use `self.foo(123)`, or even `Self::foo(self, 123)`, as you see
fit.
Similarly, using the `Self` path, we can also call any method belonging
to a trait that has been implemented for our template struct:
@ -286,6 +283,32 @@ impl Hello for MyTemplate {
}
```
If you want to call a closure which is a field, you'll need to follow Rust's
syntax by surrounding the call with parens:
```rust
#[derive(Template)]
#[template(source = "{{ (closure)(12) }}", ext = "txt")]
struct MyTemplate {
closure: fn(i32) -> i32,
}
```
## Calling functions
If you only provide a function name, `rinja` will assume it's a method. If
you want to call a method, you will need to use a path instead:
```jinja
{# This is the equivalent of `self.method()`. #}
{{ method() }}
{# This is the equivalent of `self::function()`, which will call the
`function` function from the current module. #}
{{ self::function() }}
{# This is the equivalent of `super::b::f()`. #}
{{ super::b::f() }}
```
## Template inheritance
Template inheritance allows you to build a base template with common

View File

@ -1622,7 +1622,7 @@ impl<'a> Generator<'a> {
match sub_left {
Expr::Var(name) => match self.locals.resolve(name) {
Some(resolved) => buf.write(resolved),
None => buf.write(format_args!("(&self.{})", normalize_identifier(name))),
None => buf.write(format_args!("self.{}", normalize_identifier(name))),
},
_ => {
self.visit_expr(ctx, buf, left)?;

View File

@ -1,19 +1,21 @@
use rinja::Template;
#[derive(Template)]
#[template(source = "{{ func(value) }}", ext = "txt")]
#[template(source = "{{ b(value) }}", ext = "txt")]
struct OneFunction {
func: fn(&i32) -> i32,
value: i32,
value: u32,
}
impl OneFunction {
fn b(&self, x: &u32) -> u32 {
self.value + x
}
}
#[test]
fn test_one_func() {
let t = OneFunction {
func: |&i| 2 * i,
value: 123,
};
assert_eq!(t.render().unwrap(), "246");
let t = OneFunction { value: 10 };
assert_eq!(t.render().unwrap(), "20");
}
#[derive(Template)]
@ -127,3 +129,44 @@ fn test_do_not_move_fields() {
};
assert_eq!(x.render().unwrap(), "a");
}
#[derive(Template)]
#[template(source = "{{ (func)(value) }}", ext = "txt")]
struct ClosureField {
func: fn(&i32) -> i32,
value: i32,
}
#[test]
fn test_closure_field() {
let t = ClosureField {
func: |&i| 2 * i,
value: 123,
};
assert_eq!(t.render().unwrap(), "246");
}
fn single() -> &'static str {
"a"
}
mod sub_mod {
pub fn sub_fn(v: i32) -> i32 {
v * 2
}
}
#[derive(Template)]
#[template(
source = "
{{- self::single() -}}
{{- sub_mod::sub_fn(3) -}}
",
ext = "txt"
)]
struct NotMethod;
#[test]
fn test_not_method() {
assert_eq!(NotMethod.render().unwrap(), "a6");
}

View File

@ -304,15 +304,17 @@ fn test_slice_literal() {
#[derive(Template)]
#[template(source = "Hello, {{ world(\"123\", 4) }}!", ext = "txt")]
struct FunctionRefTemplate {
world: fn(s: &str, v: u8) -> String,
struct FunctionRefTemplate;
impl FunctionRefTemplate {
fn world(&self, s: &str, v: u8) -> String {
format!("world({s}, {v})")
}
}
#[test]
fn test_func_ref_call() {
let t = FunctionRefTemplate {
world: |s, r| format!("world({s}, {r})"),
};
let t = FunctionRefTemplate;
assert_eq!(t.render().unwrap(), "Hello, world(123, 4)!");
}

View File

@ -28,14 +28,22 @@ fn test_int_parser() {
#[derive(Template)]
#[template(source = "{{ value()? }}", ext = "txt")]
struct FailFmt {
value: fn() -> Result<&'static str, std::fmt::Error>,
inner: Option<&'static str>,
}
impl FailFmt {
fn value(&self) -> Result<&'static str, std::fmt::Error> {
if let Some(inner) = self.inner {
Ok(inner)
} else {
Err(std::fmt::Error)
}
}
}
#[test]
fn fail_fmt() {
let template = FailFmt {
value: || Err(std::fmt::Error),
};
let template = FailFmt { inner: None };
assert!(matches!(template.render(), Err(rinja::Error::Custom(_))));
assert_eq!(
format!("{}", &template.render().unwrap_err()),
@ -43,7 +51,7 @@ fn fail_fmt() {
);
let template = FailFmt {
value: || Ok("hello world"),
inner: Some("hello world"),
};
assert_eq!(template.render().unwrap(), "hello world");
}
@ -51,19 +59,25 @@ fn fail_fmt() {
#[derive(Template)]
#[template(source = "{{ value()? }}", ext = "txt")]
struct FailStr {
value: fn() -> Result<&'static str, &'static str>,
value: bool,
}
impl FailStr {
fn value(&self) -> Result<&'static str, &'static str> {
if !self.value {
Err("FAIL")
} else {
Ok("hello world")
}
}
}
#[test]
fn fail_str() {
let template = FailStr {
value: || Err("FAIL"),
};
let template = FailStr { value: false };
assert!(matches!(template.render(), Err(rinja::Error::Custom(_))));
assert_eq!(format!("{}", &template.render().unwrap_err()), "FAIL");
let template = FailStr {
value: || Ok("hello world"),
};
let template = FailStr { value: true };
assert_eq!(template.render().unwrap(), "hello world");
}