CDSoft / pp

PP - Generic preprocessor (with pandoc in mind) - macros, literate programming, diagrams, scripts...
http://cdelord.fr/pp
GNU General Public License v3.0
252 stars 21 forks source link

Mustache Integration via !mustache Macro #62

Closed tajmone closed 5 years ago

tajmone commented 5 years ago

Would it be possible to integrate [mustache] into PP with a built-in macro like:

!mustache( YAML/JSON_file )
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Text to preprocess with mustache.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

... where the macro will load the YAML (or JSON) file of the 1st parameter and use its variables to preprocess the text inside 2nd parameter.

Usage Example

Based on the examples found at mustache website, suppose we have a YAML file example.yaml:

names: [ {name: chris}, {name: mark}, {name: scott} ]

... and in our source document the macro:

!mustache(example.yaml)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Greetings

{{#names}}
  Hi {{name}}!
{{/names}}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

... PP's output should be:

# Greetings

Hi chris!
Hi mark!
Hi scott!

Haskell Mustache

There is a Haskell implementation of mustache:

So, hopefully it could be integrated into PP natively, without requiring the end user to install any mustache version on his system.

Probably, it might not be possible to integreate into this macro some of mustache's advanced features (like Lambdas, Partials and Set Delimiter tags), for they require a complex interaction with the environment; but if the rest of the basic features could be integrated it would be good enough — and, IMO, well worth it.

Benefits

I think that mustache's logicless templating system would be a great added bonus to PP's powers, for some templating operations are easier (and less verbose) in mustache.

Also, it would allow to incorporate into projects existing mustache/YAML resources. For example, Base16 color schemes could be used via this macro to produce inline CSS in the final HTML document, to define syntax highlighting colors. But the potential uses are quite broad, for this new macro could be used anywhere in a source document, including YAML headers.

...

What do you think of it Crhistophe?

CDSoft commented 5 years ago

Hello,

This could be interesting. But there are a few questions that should be asked:

I think the inputs of mustache must be preprocess by pp, not the output to be consistent with other macros. If the output needs to be preprocessed as well, it can be done with the !pp macro.

tajmone commented 5 years ago

I hadn't thought of this.

Probably in most use-case scenarios the YAML/JSON file wouldn't need to be preprocessed as it's going to be a plain YAML/JSON file.

As for the template, having PP preprocess the input would be an added bonus, and I agree with the consistency approach.

If the output needs to be preprocessed as well, it can be done with the !pp macro.

Do you mean by this !pp macros WRAPPING the whole mustache block, macro included? like this:

!pp
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!mustache(macros.yaml)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{{#macros}}
  !{{macro}}
{{/macros}}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

I'm trying to imagine the pros and cons of PP processing the input of !mustache before it's fed to mustache....

PP Macros Affecting Mustache Tags

This should mean that PP macros could be used to affect the mustache tags BEFORE the template gets processed. Something like:

!mustache(foods.yaml)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{{#!type}}
  {{item}}!
{{/!type}}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

... where the !type custom macro could be used to alter the mustache tag and output different items — assuming foods.yaml:

fruit:  [ {item: apple}, {item: banana}, {item: pear} ]
cheese: [ {item: Cheddar}, {item: Mozzarella}, {item: Roquefort} ]

... we could set !type to fruit or cheese and produce different lists from the same YAML source file.

Mustache Affecting PP Macros

More complex scenarios might envolve the mustache expanded variables being used as PP macros AFTER !mustache has done its job:

!mustache(macros.yaml)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{{#macros}}
  !{{macro}}
{{/macros}}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

With "macros.yaml":

macros: [ {macro: xxx}, {macro: yyy}, {macro: zzz} ]

... where the post-processed (!mustache) block will produce PP macros:

  !xxx
  !yyy
  !zzz

So, in order for these to be preprocessed it would be necessary to wrap the whole thing in !pp, right? As in:

!pp
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!mustache(macros.yaml)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{{#macros}}
  !{{macro}}
{{/macros}}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Am I getting it right? I'm not sure I understand well how PP would handle nested macros in this scenario.

...

Probably these are far-fetched examples, but they exemplify the added extra power to PP usage that mustache would introduce — of course there might be many other ways that interaction between PP and mustache could be exploited to get cool results.

You're right, this has to be though about beforehand.

Lambdas

Another thing I was wondering is if it was possible to also support Lambdas, and how. From mustache documentation:

Lambdas

When the value is a callable object, such as a function or lambda, the object
will be invoked and passed the block of text. The text passed is the literal
block, unrendered. {{tags}} will not have been expanded - the lambda should do
that on its own. In this way you can implement filters or caching.

Template:

{{#wrapped}}
  {{name}} is awesome.
{{/wrapped}}

Hash:

{
  "name": "Willy",
  "wrapped": function() {
    return function(text, render) {
      return "<b>" + render(text) + "</b>"
    }
  }
}

Output:

<b>Willy is awesome.</b>

I think that the above refers to the Ruby implementation, and that different mustache implementation either support it differently or don't support it at all.

How does the Haskell mustache implementation deal with Lambdas? does it support Haskell callable objects/functions?

CDSoft commented 5 years ago

The haskell implementation seems to support lambda but functions must be defined as Haskell expressions which must be compiled. Embedding a Haskell compiler in pp is not desirable.

It may be possible to use pp macros as lambda expressions but it is easier to mix pp macros and mustache templates:

!def(wrapped)(<b>!1</b>)

!mustache
~~~
!wrapped(
  {{name}} is awesome.
)
~~~

I will check if it is possible to write such things to define lambdas as pp macros:

{
  "name": "Willy",
  "wrapped": "<b>!1<b>"
}
tajmone commented 5 years ago

The haskell implementation seems to support lambda but functions must be defined as Haskell expressions which must be compiled. Embedding a Haskell compiler in pp is not desirable.

I feared it might be so, but I was hoping that somehow it was possible to rely on pandoc's built-in support of Haskell (as filters, ecc.); but it was a far-stretched hope.

It may be possible to use pp macros as lambda expressions ...

I thought of that but I wasn't sure about how inline macro definition and passed parameters could be handled, but your example provides a good usage example.

The potential of mustache integration seems promising; overlapping interaction of PP macros and mustache tags enhance the power of both systems as they allow creating complex contexts where the two are intertependent and interweaved. This should allow simple and fine-tuned customization of documents output by tweaking/swapping human-readable YAML configuration files.

Looking forward to it!

CDSoft commented 5 years ago

The problem using pp macros as lambda is that it would require some changes in the Haskell libraries (mustache as well as aeson). I think I'll stick to my first proposal (using pp macros in the mustache template and preprocessing it before calling mustache).

CDSoft commented 5 years ago

pp 2.6 has been released. It provides the mustache macro.

tajmone commented 5 years ago

Thanks Christophe!