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
Allow partial evaluation of templates #282
Conversation
Codecov Report
@@ Coverage Diff @@
## master #282 +/- ##
============================================
- Coverage 72.35% 72.35% -0.01%
- Complexity 1483 1503 +20
============================================
Files 231 234 +3
Lines 4597 4662 +65
Branches 737 743 +6
============================================
+ Hits 3326 3373 +47
- Misses 1016 1034 +18
Partials 255 255
Continue to review full report at Codecov.
|
7802685
to
95471b6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pretty neat!
builder.append(n.getMaster().getImage()); | ||
} | ||
|
||
builder.append("{% ").append(getEndName()). append(" %}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you use/create constants for {%
and %}
? People have requested to make these configurable and I'd like to consolidate them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure
@@ -84,4 +86,17 @@ public Tag getTag() { | |||
return tag; | |||
} | |||
|
|||
|
|||
private String reconstructImage() { | |||
StringBuilder builder = new StringBuilder().append(master.getImage()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we could store a reference to the original template string and an index value of the start and end n the node object. I think this would solve the raw case and preserve all the whitespace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll try to get that working in a separate PR. If I can get it to work, I'll loop back and update this PR to use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it looks like it might require a larger version bump because I will need to mess around with Tag / Node etc constructors. I'll go ahead with this for now and come back to that as I refine this.
This PR implements partial evaluation of templates, or, deferring the evaluation of parts of a template, however you want to think about it.
The aim here is to support a use case for HubSpot's email infrastructure. We want to evaluate as much as possible of an email's template, except for the parts which are specific to an individual contact. This will allow us to minimise the work which needs to be repeated per-contact for large email sends to 100,000s of contacts. In this case the CPU time for evaluating the template isn't really a concern, but many Tags perform I/O, and this is the part we really don't want to repeat more than necessary.
How it works:
DeferredValue
.context.put("contact, DeferredValue.instance())
, because we want to defer the per-contact properties which will appear in the email.ExpressionResolver
encounters one of these objects, for example when encountering the expression{{contact.firstname}}
while rendering a template, it will throw aDeferredValueException
.DeferredValueException
is caught in a few specific places. When caught the engine attempts to just echo the Node / Tag / Expression which caused the exception, leaving it untouched.Concerns:
{% raw %}
wrapped content will be lost in the deferred evaluation scenario, because they will be evaluated twice. I could add a new mode to the tag which rewrapped its output in another raw wrapper?TagNode
. It seems to work, but will definitely need some more work.resolvedExpressions
collection and throwing if we see something being added which we don't like. Maybe this would be better? I think the exception approach is more flexible, because we can do things like making functions throwDeferredValueException
under certain circumstances, like if the function returned the current time and we are in deferred evaluation mode.