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

macros or block with parameters #5

Closed
ghost opened this issue Sep 26, 2014 · 11 comments
Closed

macros or block with parameters #5

ghost opened this issue Sep 26, 2014 · 11 comments
Assignees

Comments

@ghost
Copy link

ghost commented Sep 26, 2014

Hi,

Very good template language, but I miss macros or block with parameters.
What I mean by this:

--- my_template.html
<html>
<some><code>
  {* blocks.my_macro(a1, table) *}
</html>

{- my_macro(arg1, arg2) -}
    <div>
      {% if arg1 the %}
           <some>arg2.param1<code>
      {% end %}
    </div>
{-my_macro-}

I tried to call the function lua by {* *} and define the another rendering in lua code, but it does not work properly. eg:

--- my_template.html
<html>
<some><code>
  {* my_macro(a1, table) *}
</html>

my_macro passed in the context as:

template.compile(macro_code)(macro_args)

It seems that the function call template.compile recursively OpenResty is not well tested and does not work properly.

@bungle
Copy link
Owner

bungle commented Sep 26, 2014

Hi, thanks!

Let's think about this a moment.

This looks a lot like this:
https://github.com/bungle/lua-resty-template#template-including

E.g.

{(macros/my_macro.html, { arg1 = "test", arg2 = "test"})}

And inside macros/my_macro.html:

<ul>
  <li>{{arg1}}</li>
  <li>{{arg2}}</li>
</ul>

And all it does is actually (something that I think you tried in your second example):

template.compile("macros/my_macro.html"){ arg1 = "test", arg2 = "test"}

Or are you looking something different?

Also I do not understand what you mean by:

It seems that the function call template.compile recursively OpenResty is not well tested and does not work properly.

I do not see any problems in this, but maybe you can show a more complete example where things go wrong that I can test too?

Thanks you for your comments, and suggestions, let's see what we can do to this.

Inline macros (inside templates) can be created like this:

{%
local function my_macro(arg1, arg2)
    -- ...
end
%}

@ghost
Copy link
Author

ghost commented Sep 29, 2014

template-including - It is a great functionality, often I use it.
Would have been nice if I could load functions of the module and do not have to keep each macro in a separate HTML file. eg:

{(my_module.my_function, { arg1 = "test", arg2 = "test"})}

And inside my_module.lua:

local my_function
my_function = function(arg1, arg2)
    return "html code to render"
end

local my_function2
my_function2 = function(arg1, arg2)
    return "another html code to render"
end

Of course my_module.my_function should be provided in context.
It should be easy to do, yet I many times benefited of that.

We're talking here about extending the functionality.
First, however, I care about correcting the following error:
openresty/1.7.2.1
Lua 5.1
lua-resty-template 1.1-1
caching - default enabled

lua code:

local template = require("template")
local items = {
  a = 'AAA',
  b = 'BBB'
}
local fn
fn = function(arg)
  return template.compile('{{' .. arg .. '}}')({
    items = items
  })
end
return ngx.print(template.compile("[html before]{* fn('items.a') *}[html after] <br> [html before]{* fn('items.b') *}[html after]")({
  fn = fn
}))

error log:

[error] 10411#0: *17 lua entry thread aborted: runtime error: [string "context=... or {}..."]:6: attempt to call global 'fn' (a nil value)
stack traceback:
coroutine 0:
    [string "context=... or {}..."]: in main chunk
    ...template_test/test.lua:12: in function <.../template_test/test.lua:1>

In the code you can see a trick I used, which prevents me from using just template-Including.

return template.compile('{{' .. arg .. '}}')({

I dynamically generate HTML code, which is then further rendered.
If there is only one {**} in the code, it all works fine, but if there are two, and the first calls template.compile, it disappears correct context.

@bungle
Copy link
Owner

bungle commented Sep 29, 2014

Great examples. I will look forward to this.

bungle added a commit that referenced this issue Sep 29, 2014
@bungle
Copy link
Owner

bungle commented Sep 29, 2014

Hi again @DDarko,

That was a really nice catch! I think that I have fixed this recursion/context corrupting issue now. Check the release 1.2.

@bungle
Copy link
Owner

bungle commented Sep 29, 2014

{(my_module.my_function, { arg1 = "test", arg2 = "test"})}

I have been thinkg about this as well. The problem is that this looks like a feature that may break backward compability. I don't think there is an easyway to check if my_module.my_function is a function or a string. The most straightforward way to fix this is to change include syntax to take lua expressions e.g.:

{("list.html", { arg1 = "test", arg2 = "test"})}

and

{(my_function, { arg1 = "test", arg2 = "test"})}

or even something like this:

{(my_function("test", "test"), items)}

But that is a breaking change. I have to think this more. Is it worth it to break backward compability, is it worth adding more syntax to template for this to work or should we try to find other ways. Please let me know what you think, and continue posting these great ideas.

@ghost
Copy link
Author

ghost commented Sep 30, 2014

Great job, the issue has been fixed.

IMHO it does not matter if the syntax is {(my_function(items))} or {(my_function, items)}.
I leave that decision to you. It can also be a completely new syntax. If only could be done.

@bungle
Copy link
Owner

bungle commented Sep 30, 2014

Does this work for your use case?

Using template.compile inside templates:

{% local my_macro = [[
<div>{{item}}</div>
]] %}

{* template.compile(my_macro){ item = "test" } *}

or

{% local my_macro = function(var)
    return "<div>{{" .. var .. "}}</div>"
end %}

{* template.compile(my_macro('item')){ item = "test" } *}

or

{% local my_macro = function(var)
    return template.compile("<div>{{" .. var .. "}}</div>")
end %}

{* my_macro('item'){ item = "test" } *}
{* my_macro('item')(context) *}

or

{% local my_macro = function(var, ctx)
    return template.compile("<div>{{" .. var .. "}}</div>")(ctx or context)
end %}

{* my_macro('item', { item = "test" }) *}
{* my_macro('item') *}

You may even omit the local there if you want to. I think all these examples work currently, but I have not yet tested them. local my_macro = function(var) ... end can alternatively be defined like local function my_macro(var) ... end, or just function my_macro(var) ... end.

The obvious problem in this is that you cannot have %} inside your my_macro (and you have to define your macro before using it, but that is just how Lua works). Well you can have %}, but you have to do it with concatenation "%" .. "}". Maybe you could use somekind of var blocks (not supported in template) (here we don't have %} problem), but the var should still be defined before using it (and obviously you should not nest var blocks):

{$my_macro$}
<div>{{item}}</div>
{$my_macro$}

{* partial(my_macro){ item = "test" } *}

{# or #}

{* template.compile(my_macro){ item = "test" } *}

But let's discuss more about this, maybe we can find a better solution.

@ghost
Copy link
Author

ghost commented Sep 30, 2014

Lack of {% in macro code exclude this option.

(Update, @bungle accidentally edited this comment, sorry about that)

@bungle
Copy link
Owner

bungle commented Sep 30, 2014

Lack of {% in macro code exclude this option.

Hi, I just updated the comment. You can have {% but you cannot have %}, but you can have "%" .. "}". Or just define %} (and optionally {%) as vars:

{% local my_macro = function(var)
    local cs, ce = "{%", "%" .. "}"
    return "<div>" .. cs .. local test = "test" .. ce .. var .. "</div>"
end %}

Only thing you cannot have is %} as it will then end the thing:

{% local my_macro = function(var)
    return "<div>%}" .. var .. "</div>"
end %}

In this case the code block will be this:

{% local my_macro = function(var)
    return "<div>%}

This will give you an error. But you may use everything else, like {{, }}, {*, *}, {( etc. just as is.

But this is just fine:

{% local my_macro = function(var)
    return "<div>%" .. "}" .. var .. "</div>"
end %}

This same happens in templates as well in general.... whenever you start a tag with something, the template will just look at the first end tag it can find. That's why you should not nest same tags and you should have them balanced. It is top-to-bottom parsing and it doesn't care what's inside as long as it can find the start tag and corresponding end tag (and in case it doesn't it just thinks these are just ordinary strings and are outputted as is). I don't see this as a problem. It has somewhat same rules as programming languages in general.

@bungle
Copy link
Owner

bungle commented Sep 30, 2014

I'm sorry, I somehow managed to edit and not reply your comment. Sorry about that. But let me know if this issue can now be closed or is there any other problems/bugs you are having?

@bungle bungle self-assigned this Sep 30, 2014
@ghost ghost closed this as completed Sep 30, 2014
@bungle
Copy link
Owner

bungle commented Sep 30, 2014

I added some macro documentation here:
https://github.com/bungle/lua-resty-template#macros

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant