#
# The high-throughput toolkit (httk)
# Copyright (C) 2012-2018 Rickard Armiento
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Closely inspired from
# https://makina-corpus.com/blog/metier/2016/the-worlds-simplest-python-template-engine
# https://github.com/ebrehault/superformatter
from __future__ import print_function
import string, os, sys, codecs
import inspect
# Retain python2 compatibility without a dependency on httk.core
if sys.version[0] == "2":
# Note:
# The "html" module is not a builtin in Python 2.
# If it happens to be installed, we still do not
# want to use it since it is old (last updated in 2011,
# version 1.16). Use the builtin cgi module to get the
# escape funtion instead.
from cgi import escape
unicode_type=unicode
else:
from html import escape
unicode_type=str
from httk.httkweb.helpers import UnquotedStr
[docs]class TemplateEngineHttk(object):
def __init__(self, template_dir, template_filename, base_template_filename = None):
self.template_dir = template_dir
self.template_filename = template_filename
self.filename = os.path.join(template_dir, template_filename)
self.dependency_filenames = [self.filename]
if base_template_filename is not None:
self.base_filename = os.path.join(self.template_dir, base_template_filename)
self.dependency_filenames += [self.base_filename]
else:
self.base_filename = None
self.httk_tf = HttkTemplateFormatter()
[docs] def apply(self, content = None, data = None, *subcontent):
if data == None:
data = {}
else:
data = dict(data)
self.httk_tf.data = data
with codecs.open(self.filename,encoding='utf-8') as f:
template = f.read()
data['content'] = content
data['subcontent'] = subcontent
output = self.httk_tf.format(template, **data)
if self.base_filename is not None:
with codecs.open(self.base_filename, encoding='utf-8') as f:
base_template = f.read()
data['content'] = UnquotedStr(output)
del data['subcontent']
output = self.httk_tf.format(base_template, **data)
return output
[docs] def get_dependency_filenames(self):
return self.dependency_filenames
# data = dict(self.global_data)
# data['content'] = page._rendered_content
# if hasattr(page,_rendered_subcontent) and page._rendered_subcontent is not None:
# data['subcontent'] = page._rendered_subcontent
# content = self.httk_tf.format(template,data)
# data['content'] = content
# del data['subcontent']
# return self.httk_tf.format(base_template,data)
if __name__ == "__main__":
tf = HttkTemplateFormatter()
# Turn off auto-quoting
tf.quote = False
class ExampleStrRepr(object):
def __str__(self):
return 'str'
def __repr__(self):
return 'repr'
class ExampleAttribute(object):
x = 'tree'
y = [{'i':'one', 'j':'two'},'z']
class ExampleFormatter(object):
def __format__(self, f):
if (f == 'test'):
return "This is an ExampleFormatter test"
return 'I am the ExampleFormatter'
# Many examples adapted from https://pyformat.info/
print("== Simple strings")
print(tf.format("Strings: '{a}' '{b}'",a="one", b="two"))
print(tf.format("Align: '{a:>10}' '{a:10}' '{a:_<10}' '{a:^10}'",a='test'))
print(tf.format("Truncate: '{a:.10}'",a='this string is too long'))
print(tf.format("Truncate and pad: '{a:20.10}'",a='this string is too long'))
print(tf.format("Str or repr: '{0!s}' '{0!r}' '{0}'",ExampleStrRepr()))
from datetime import datetime
print(tf.format("Std class support: '{a:%Y-%m-%d %H:%M}'",a=datetime(2010,1,2,3,4)))
print(tf.format("Own Class support: '{a:test}' '{a}'",a=ExampleFormatter()))
print("== Simple numbers")
print(tf.format("Integers: '{a}' '{a:d}'",a=42))
print(tf.format("Floats: '{a:f}'",a=3.141592653589793))
print(tf.format("Number padding:'{a:06.2f}'",a=3.141592653589793))
print(tf.format("Sign: '{a:+d}' '{b:+d}' '{a:=+5d}'",a=42,b=-42))
print("== Element access")
print(tf.format("Dicts: '{a[one]}' '{a[two]}'",a={'one':'1', 'two':'2'}))
print(tf.format("Lists: '{a[1]}' '{a[2]}'",a=[1,2,3,4,5]))
print(tf.format("Attributes: '{a.x}'",a=ExampleAttribute))
print(tf.format("Combitations: '{a.y[0][i]}'",a=ExampleAttribute))
print("== Parameterization")
print(tf.format("Alignment and width: '{a:{align}{width}}'".format(a='test', align='^', width='10')))
print(tf.format("Precision: '{a:.{prec}} = {b:.{prec}f}'".format(a='Test', b=2.7182, prec=3)))
print(tf.format("Embedded: '{a:{prec}} = {b:{prec}}'".format(a='Long text here', b=2.7182, prec='.3')))
print(tf.format("Width and precision: '{:{width}.{prec}f}'".format(2.7182, width=5, prec=2)))
print(tf.format("Embedded class: '{a:{dfmt} {tfmt}}'".format(a=datetime(2010,1,2,3,4), dfmt='%Y-%m-%d', tfmt='%H:%M')))
print("********** Functionality from superformatter (with a slight format change for repeat) **************")
print(tf.format("Loops: '{chapters:repeat::Chapter {{item}}, }'", chapters=["I", "II", "III", "IV"]))
print(tf.format("Calls: '{name.upper:call}'", name="eric"))
print(tf.format("Tests: '{t:if:hello}' '{f:if:hello}' '{name:if:hello}' '{empty:if:hello}'", name="eric", empty="", t=True, f=False))
print(tf.format("Test and nesting: 'Action: Back / Logout {manager:if:/ Delete {id}}'", manager=True, id=34))
print("********** New functionality here **************")
# Hint: escaped {{ }} are needed to call varibale contents inside nested formatting, which always occur after ::,
# (If used without nesting, then the content is replaced on the level above, which, e.g., does not contain the item from a loop.)
print("== Loops")
print(tf.format("Indexed loops: '{chapters:repeat::Chapter {{index}}={{item}},}'", chapters=["I", "II", "III", "IV"]))
print(tf.format("Nested loops: '{l:repeat::{{k:repeat::{{{{item}}}}({{item}})}}, }'", l=[1,2,3,4],k=["a","b","c","d"]))
print(tf.format("Nested formatting: '{b:repeat:: {{a:.{{item}}f}},}'", a=3.141592653589793, b=["2","4","8","16"]))
print()
print("== Indirection")
print(tf.format("Item access indirection: '{a:getitem:{b}}' '{c:getitem:{d}}'",a={'x':1, 'y':2}, b='x', c=["0","1","2","3"], d=2))
print(tf.format("Attribute access indirection: '{a:getattr:{b}}'",a=ExampleAttribute, b='x'))
print(tf.format("Attribute access indirection with formatting: '{a:getattr:{b}::>10}'",a=ExampleAttribute, b='x'))
print()
print("== Calls")
print(tf.format("Calls with args: '{name.rstrip:call:,four}'", name="one,two,three,four"))
print(tf.format("Calls with formatting: '{name.rstrip:call:,four::>20}'", name="one,two,three,four"))
print(tf.format("Calls with argument indirection: '{name.rstrip:call:{remove}::>20}'", name="one,two,three,four", remove=",four"))
print()
print("== Conditionals")
print(tf.format("With formatting: '{t:if::hello {{name:>10}}}'", name="eric", empty="", t=True, f=False))
print(tf.format("Negation: '{t:if-not::hello}' '{f:if-not::hello}'", empty="", t=True, f=False))
print(tf.format("Unset: '{empty:if::hello}' '{empty:if-unset::hello}' '{empty:if-set::hello}' '{unset:if-unset::hello}'", empty=""))
print()
print("== Advanced")
print(tf.format("Item access indirection inside loops: '{a:repeat:: {{a:getitem:{{{{index}}}}}}}'",a=[5,6,7,8]))
print()
print("== Quoting")
tf.quote = True
print(tf.format("Autoquoting: '{a}'",a="<i need to be quoted, \"indeed\" said the cat's hat>"))
print(tf.format("Avoid quoting: '{a:unquoted}'",a="<i need to be quoted, \"indeed\" said the cat's hat>"))
print(tf.format("Quoting + formatting: '{a:unquoted::>70}'",a="<i need to be quoted, \"indeed\" said the cat's hat>"))
print(tf.format("Unquoted string: '{a} '",a=UnquotedStr("<i need to be quoted, \"indeed\" said the cat's hat>")))
print(tf.format("Overriding unquoted string: '{a:quote}'",a=UnquotedStr("<i need to be quoted, \"indeed\" said the cat's hat>")))