YATplT - Yet Another Template Thing
notAdvanced template rendering thing
Template rendering
template_string = """
{{# Create page with static rendered title and dynamic content #}}
{1{!
# One time block
import random
titles = [
'foo',
'bar',
'baz'
]
page_title = random.choice(titles)
!}1}
{{# Set title #}}
<title>{1{%
# One time expression
page_title
%}1}</title>
{{# Peek heavy data #}}
{{!
# Render time block
import time
lots_of_userdata = []
# Simulate heavy work
for i in range(100):
lots_of_userdata.append(i)
time.sleep(0.1)
!}}
{{# Fill with content #}}
{{%
# Render time expression
'<br>'.join([ f'<h1>{user}</h1>' for user in lots_of_userdata ])
%}}
"""
# Load
template = Template.from_string(source=template_string, context={})
# Init
await template.init(init_ok=True, strip_string=True, none_ok=True, wrap_scope=False)
# Render
result = await tempalte.render_string(scope={}, strip_string=True, none_ok=True, wrap_scope=False)
# Do anything
print(result)
This template rendering thing is based on pure python expressions and code inside files. This allows using multiple different block types inside each file: comment blocks, statement blocks, expression blocks and one-time blocks. Statement blocks never return value, while expression blocks always return value and support None check.
Additional feature is automatic identation that helps used to write code more simple and formatted and do not cate about excessive identation.
With autoident, this code:
def myfun():
return 42
a = 37
Is turned into:
def myfun():
return 42
a = 37
Default comment block tags are: {{#
and #}}
. All code and text placed inside comment blocks is ignored and removed:
{{#
exit(0)
#}}
This type of blocks is the same as regular comments in your code /* comment */
.
This type of blocks is a special one that allows user to execute some code when Template is initialized. by default Template is uninitialized if it has at least one code block that is one-time callable.
Following code defines global function that can be used in any code block later. This block is a statement without return value:
{1{!
global myfun
def myfun(x):
return x + 37
!}1}
Second approach is to use wrap_scope
option that allows sharing variables between blocks:
{1{!
def myfun(x):
return x + 37
!}1}
Following code defines one-time expression that is evaluated during .init()
call. After evaluation, result of this expression is inserted instead of this code block
<h1>
{1{%
myfun(42)
%}1}
</h1>
Will result in:
<h1>
79
</h1>
This type of blocks and expressions is different from one-time init blocks because these blocks are evaluated each time on .render()
call.
This type of blocks also supports statement blocks that can define global variables too:
{{!
# Use global keyword or set wrap_scope=False
global gettimestamp
# Enclosure local variable
import time
timestamp = time.time()
def gettimestamp(x):
return timestamps
!}}
Later this code can be used to evaluate expression blocks:
<h1>
{{%
myfun(gettimestamp())
%}}
</h1>
Will result in whatever your time is + 37:
<h1>
1291238534263
</h1>
Despite to one-time blocks, these blocks are rendered on each call to .render()
.
After dealing with basic block types, let's render something
This library supports different source of tempalte input and all of them are slow.
# By file name
template = yatplt.Template.from_file('myfile.thtml')
# By file object
template = yatplt.Template.from_file(open('myfile.thtml', 'r'))
# From file myfile.thtml
template_string = """
<title>{{% username %}}</title>
<h1>{{% action %}}</h1>
"""
# Direct constructor
template = yatplt.Template(template_string)
# Indirect
template = yatplt.Template.from_string(template_string)
# Same as above
# ExpressionTemplateFragment accepts python source code as input
fragments = [
yatplt.StringTemplateFragment('<title>'),
yatplt.ExpressionTemplateFragment('username'),
yatplt.StringTemplateFragment('</title>\n<h1>')
yatplt.ExpressionTemplateFragment('action'),
yatplt.StringTemplateFragment('</h1>')
]
# Indirect
template = yatplt.Template.from_fragments(fragments)
Template can be constructed with additional options that define future behavior of the template.
From example above
# From file myfile.thtml
template_string = """
<title>{{% username %}}</title>
<h1>{{% action %}}</h1>
<<<
this will disappear
>>>
"""
# Define global variables for this template
context = {
'username': 'anonymous',
'action': 'nothing'
}
# Custom parser where comment block is defined with <<< and >>>
template_parser = TemplateParser(comment_block_start='<<<', comment_block_end='>>>')
# Indirect
template = yatplt.Template.from_string(template_string, template_parser=template_parser, context=context)
Rendering operation supports different variants of render. Basic rendering enforces support for async expressions in python code snippets and each .render()
call requires await.
Before render, initialize your template:
# This dict defines variables that will be copied as locals() for this .init() call
scope = {
'key': 'value'
}
# Set init_ok to True to ignore already initialized error
# Set none_ok to True to ignore None value error in expressions
# Set strip_string to True to strip output of the expression fragments
# Set wrap_scope to True to wrap scope for each fragment render
template.init(scope=scope, init_ok=True, none_ok=True, strip_string=True)
scope = {
'username': 'Pegasko',
'action': 'Merp'
}
async for fragment in template.render_generator(scope=scope, init_ok=True, none_ok=True, strip_string=True, wrap_scope=False):
print(fragment)
scope = {
'username': 'bitrate16',
'action': '"DROP TABLE *;--'
}
print(await template.render_string(scope=scope, init_ok=True, none_ok=True, strip_string=True, wrap_scope=False))
scope = {
'username': 'odmin',
'action': 'hak for mani'
}
print(await template.render_file('output.html', scope=scope, init_ok=True, none_ok=True, strip_string=True, wrap_scope=False))
This type of templates is a simple wrapper for template class that automatically updates template from dist on change. Function .update()
is called before each render to fetch actual template based on last file update time.
tmpl = yatplt.FileWatcherTemplate(
filename='mytemplate.txt',
context={
'useful': lambda x: x ** 2
},
init_none_ok=True,
init_scope={
'hello': 'world'
},
init_wrap_scope=False
)
# This call automatically loads tempalte from file,
# calls .init() with init_* parameters of constructor and performs render
# This method has the same signature as regular Tempalte method except auto_reload argument
tmpl.render_string(auto_reload=True)
# Manually flush template
await tmpl.update()
Oh no, my PyHP colletion!
Copyright 2022 bitrate16
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
MIT License
Copyright (c) 2022 bitrate16
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.