Guise Mesh Template Expression Evaluation Engine

Description

It's time to finally create the foundation and first implementation of an engine to evaluation expressions, interpolate values, and manipulate the DOM. This epic is to provide documentation for the design and initial implementation.

Other static site generators provide templating systems:

Guise will create its own templating engine inspired by Thymeleaf. See Using Thymeleaf. It might have been preferable to use Thymeleaf itself, but Thymeleaf seems to operate only on the text form of the document (i.e. as long a string), not on its DOM representation. It would be inefficient and inelegant to be required to serialize each document to a string as we are processing it, perform Thymeleaf operations, then parse it back for more processing, and then finally serialize it again.

There are several parts to Guise expression interpolation:

  • The templating engine itself will be named Guise Mesh and be a separate subproject. The word "mesh" brings to mind combining things, which is what interpolation is. It also recalls the Portuguese word "mexer", which means "to move". Apparently the etymology derives from Latin miscēre, "to mix", which again fits perfectly.

  • The expression language will be called MEXL, or "Mummy Expression Language". Observe that the "mex" part could be pronounced "mesh" as well, and also recalls "mexer" as mentioned. Rather than writing an expression evaluation engine from scratch, several libraries exist already; a promising one is Apache Commons JEXL.

  • The Guise Mesh template (X)HTML prefix (keeping it short and simple, as does Thymeleaf (th) will be mx. The Guise Mesh namespace will be https://guise.io/name/mesh/.

The following variables will be exposed in the Guise Mummy context for Guise Mesh:

  • plan: MummyPlan ()

  • artifact: current Artifact

  • page: current page resource description

This epic will cover the following Guise Mesh features:

  • mx:each ()

  • mx:text ()

  • ^{…} ()

  • <!--/*…*/--> ()

Environment

None

Activity

Show:
Garret Wilson
December 26, 2020, 11:05 PM
Edited

I'm not sure why we would want to have both artifact and page context variable alias names. Now that I think about it, meshing only occurs for pages anyway. I suppose that if we did some sort of processing for images using metadata, we would need some sort of expression language and thus some variable for the artifact. But I don't know why it would be helpful or even relevant whether a name such as artifact was the same across the different artifact types. I don't know that image processing would interact with page processing or vice versa.

However I did realize that the Artifact instance is not what contains the URF metadata such as title; instead there is an Artifact.getResourceDescription() (so named as to distinguish it from the "description" of artifact, which is a property of the resource description) that returns the actual UrfResourceDescription providing access to the properties. Moreover Artifact itself has some useful property getters that could conceivably be used in MEXL, such as getTargetPath().

This presents an elegant solution: we will store the Artifact instance itself under the mesh context name artifact, and store the URF description under the mesh context name page, allowing easy access to e.g. page.title as originally desired. And if in the future we somehow have some expression evaluation across artifact types needing some general access to metadata without knowing the specific artifact type, we can always use artifact.resourceDescription, which will provide the same thing that is in the page mesh context variable for pages.

Garret Wilson
December 26, 2020, 11:17 PM
Edited

Once we start adding mx: attribute support (e.g. mx:text in ), we'll need to determine precedence. See Thymeleaf Attribute Precedence for comparison.

Garret Wilson
December 29, 2020, 10:00 PM

I'm not sure I like the Thymeleaf approach to setting attributes, which provides convenience attributes for almost every attribute imaginable, such as th:href to th:for. These Thymeleaf namespaced attributes provide values to override HTML attributes (without a namespace). Sure, these are convenient, but there is a huge risk of clashing (or confusion) with the Thymelaf attributes themselves. For example if we were to decide to use mx:for for iteration (), then that would clash with using mx:for for setting the for attribute.

This can be discussed more on a separate ticket for setting attributes, but one idea is to use a attr- prefix, such as mx:attr-for or mx:set-attr-for. The latter seems too verbose. We also have to think about prepend/append, although adding things such as CSS class values should really have something more elegant, recognized as tokens rather than an opaque string.

Finally all this relates with whether and which values are considered expressions and which allow interpolation. The current thinking is that all mx: attributes are expressions by default (requiring another level of quoting for actual strings). One option is to require some flag such as mx:inter to turn on interpolation (in attributes, text content, and children), maybe with an option (e.g. mx:interpolate="attr") to choose which things it is applied to.

Garret Wilson
January 9, 2021, 10:32 PM

I'm thinking and thinking about how to interpolate values, and I like the unintrusive approach of Thymeleaf for specifying other attributes that override existing attributes. That allows developers to use dummy data to see how a page would look, without even any interpolation variables, and have the Thymeleaf specific attributes replace them (similar to how we've already implemented with mx:text in ).

But not only would creating a set of parallel Guise Mesh attributes be a lot of work, they would still by default use MEXL expressions (e.g. attribute="'foo'+bar"), not interpolation (attribute="foo^{bar}"), which can be powerful but aren't as concise as interpolation.

I suppose we could create yet another set of parallel Guise Mesh attributes in a different namespace, such as mxi:attribute, which would set attribute but using Guise Mesh interpolation rather than a Guise Mesh expression.

Garret Wilson
January 18, 2021, 4:55 PM

I'm starting to do some real-life tests of Guise Mesh, and it's working great, but something just struck me: should we be meshing before or after the DOM has been relocated from the source tree to the target tree? For some reason I had just assumed we would do it in the source tree, before we regenerate navigation and such. But why?

I just realized that if we have different "aspects" of resources generated (e.g. an image "preview" aspect) and we have a way to reference that aspect via the plan such as ^{plan.reference(artifact, someImage, "preview")}, that would generate something like some-image-preview.jpg which would not be found during relocation (because it hadn't been generated yet).

Why are we meshing in the source tree anyway? Should we wait until after relocation? But it seems that we need to mesh before processing navigation regeneration and widgets, because we might want to use Mesh expressions in the widgets. But aren't widgets performed in terms of the source tree? At least navigation regeneration is (currently).

But as I write this it also occurred to me that even if we mesh after relocation to the target tree, there is still no guarantee that an "aspect" such as some-image-preview.jpg will have been generated, because that would depend on whether the page or the image got mummified first.

The relocation warning for a missing referenced artifact is based on finding the artifact in the plan, anyway. So maybe the real solution is to make sure some-image-preview.jpg is found in the plan. That can be considered in more detail in the ticket for creating aspects such as image previews.

Assignee

Garret Wilson

Reporter

Garret Wilson

Labels

None

Components

Fix versions

Priority

Major

Epic Name

Guise Mesh
Configure