sailfish icon indicating copy to clipboard operation
sailfish copied to clipboard

Error defining template in macro

Open alvra opened this issue 3 years ago • 6 comments

Defining a template in a macro currently fails, while doing the same thing without a macro works as expected.

A minimal example to reproduce this:

use sailfish::TemplateOnce;

macro_rules! template {
    ($name:ident, $path:literal, { $($field:ident: $type:ty),* } ) => {
        #[derive(TemplateOnce)]
        #[template(path=$path)]
        struct $name {
            $($field: $type,)*
        }
    }
}

template!(SimpleTemplate, "index.html", {});

fn main() {
    let template = SimpleTemplate {};
    let result = template.render_once().unwrap();
    println!("{}", result)
}

Error:

error[E0425]: cannot find value `__sf_buf` in this scope
 --> .../target/debug/build/sailfish-compiler-8fa5b2a3e77dde90/out/templates/index-1cd25f105bd4f9f0:2:27
  |
2 |     __sf_rt::render_text!(__sf_buf, "<p>Hello world</p>");
  |                           ^^^^^^^^ not found in this scope

This is the file where the error occurs (templates/index-1cd25f105bd4f9f0):

{
    __sf_rt::render_text!(__sf_buf, "<p>Hello world</p>");
}

I would expect this to give the same result as this:

#[derive(TemplateOnce)]
#[template(path="index.html")]
struct SimpleTemplate {}

alvra avatar May 09 '21 14:05 alvra

I'm running into this issue too. Did you ever get it working?

mpfaff avatar Sep 01 '23 20:09 mpfaff

@mpfaff Nope, but I'm not using sailfish anymore though

alvra avatar Sep 01 '23 20:09 alvra

Darn. The ability to embed full rust expressions in the templates is a killer feature for me. Do you know of any other template engines that come close in terms of keeping the logic and computation in the templates? I've just looked at Askama and Tera, and Tera looks like the more promising of the two.

mpfaff avatar Sep 01 '23 20:09 mpfaff

No experience with Tera, but I'm a happy Askama user. It has match statements (which are pretty essential in Rust IMO) and converts templates to Rust at compile time. It seems that Tera doesn't support either of these, but compiling templates at run-time does make development easier since you don't need to recompile for a template change. That is my main issue with Askama.

I don't have any experience with an engine that allows you to embed actual Rust, but there are some interesting macros if you're only rendering html, like maud and typed-html.

alvra avatar Sep 02 '23 01:09 alvra

Hmmm... I meet this issue as well... Sailfish version: sailfish = "0.8.3" Rust version: 1.74.0 (79e9716c9 2023-11-13)

Refer to the example simple.rs, and the simple.stpl.

Origin:

use sailfish::TemplateOnce;

#[derive(TemplateOnce)]
#[template(path = "simple.stpl")]
struct Simple {
    messages: Vec<String>,
}

fn main() {
    let messages = vec![String::from("Message 1"), String::from("<Message 2>")];
    println!("{}", Simple { messages }.render_once().unwrap());
}

Modified:

use sailfish::TemplateOnce;

macro_rules! template {
    ($name:ident, $path:literal, { $($field:ident: $type:ty),* } ) => {
        #[derive(TemplateOnce)]
        #[template(path=$path)]
        struct $name {
            $($field: $type,)*
        }
    }
}

template!(Simple, "simple.stpl", { messages: Vec<String> });

fn main() {
    let messages = vec![String::from("Message 1"), String::from("<Message 2>")];
    println!("{}", Simple { messages }.render_once().unwrap());
}

With cargo expand, the results between these two are TOTALLY the SAME:

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2018::*;
#[macro_use]
extern crate std;
use sailfish::TemplateOnce;
#[template(path = "simple.stpl")]
struct Simple {
    messages: Vec<String>,
}
impl sailfish::TemplateOnce for Simple {
    fn render_once(self) -> sailfish::RenderResult {
        use sailfish::runtime::{Buffer, SizeHint};
        static SIZE_HINT: SizeHint = SizeHint::new();
        let mut buf = Buffer::with_capacity(SIZE_HINT.get());
        self.render_once_to(&mut buf)?;
        SIZE_HINT.update(buf.len());
        Ok(buf.into_string())
    }
    fn render_once_to(
        self,
        __sf_buf: &mut sailfish::runtime::Buffer,
    ) -> std::result::Result<(), sailfish::runtime::RenderError> {
        b"<!DOCTYPE html>\n<html>\n  <body>\n    <%# This is a comment %>\n    <% for (i, msg) in messages.iter().enumerate() { %>\n      <% if i == 0 { %>\n        <h1>Hello, world!</h1>\n      <% } %>\n      <div><%= *msg %></div>\n    <% } %>\n  </body>\n</html>\n";
        use sailfish::runtime as __sf_rt;
        let Simple { messages } = self;
        {
            __sf_buf.push_str("<!DOCTYPE html>\n<html>\n  <body>\n    \n    ");
            for (i, msg) in messages.iter().enumerate() {
                __sf_buf.push_str("\n      ");
                if i == 0 {
                    __sf_buf.push_str("\n        <h1>Hello, world!</h1>\n      ");
                }
                __sf_buf.push_str("\n      <div>");
                ::sailfish::runtime::Render::render_escaped(&(*msg), __sf_buf)?;
                __sf_buf.push_str("</div>\n    ");
            }
            __sf_buf.push_str("\n  </body>\n</html>");
        };
        Ok(())
    }
}
impl sailfish::private::Sealed for Simple {}
fn main() {
    let messages = <[_]>::into_vec(
        #[rustc_box]
        ::alloc::boxed::Box::new([
            String::from("Message 1"),
            String::from("<Message 2>"),
        ]),
    );
    {
        ::std::io::_print(
            format_args!("{0}\n", Simple { messages }.render_once().unwrap()),
        );
    };
}

Hmmm... Cannot figure why caused the latter one panic.

wangziling avatar Nov 30 '23 08:11 wangziling

I have used the proc_macro_attribute to addsome new fields to a struct which has derived TemplateOnce. Instead of using decl_macro implementation.

It works. But lack of flexibility just yet.

image

image

image

wangziling avatar Nov 30 '23 15:11 wangziling