http://habrahabr.ru/post/254567/
Захотелось мне сделать шаблонизатор, чтобы как slim, теги чтобы автоматом закрывались и прочее. Красиво же так:
html
head
title
- yield "Плюшка!" + " Чашка чаю!"
Но и этого мне мало, хочу чтобы не было своего недоязыка, хочу чтобы просто питоновские конструкции. А кто захочет себе в ногу стрельнуть и бизнес логики в шаблоны навалить, то это проблема начинашек, мне зачем мучаться размазывая код вьюх в папки типа utils, template_tags и прочее?
А и еще можно кстати угореть так уж угореть — а пусть шаблоны через новый механизм импорта в python 3 тянутся. И если надо что-то от другого шаблона себе вставить, то тоже пусть также работает.
А еще, еще пусть каждый шаблон это генератор!
Ну сказано сделано, встречайте
github.com/Deepwalker/backslant. Он еще конечно не до конца допилен, но надо получить фидбек.
Итак, попробуем на пять, base.bs:
!doctype/ html
html
head
title
" Page Title
body
h1 {'class': ' '.join(['main', 'content'], 'ng-app': 'Application'}
" Page Header
div.content
- yield from options['content_block']()
div.footer
" Backslant © 2015
Что тут у нас —
doctype
заканчивается на
/
, значит тег закрывать через
</doctype>
не надо.
Строки пока начинаются с
"
, надо допилить грамматику чтобы можно было сразу после тела тега, попозже.
У
h1
аргументы передаются обычным python
dict
, в рамках которого любой код, который можно в объявлении словаря.
Дальше интересное —
yield from
из вызова некоего
content_block
, который лежит в каком-то
options
. Ну что сказать —
options
это
kwargs
, так как объявления параметров шаблона у нас тут нет. Может и зря кстати что нет.
Так вот, про
content_block
— тут мы ожидаем что нам передадут в параметре некий колбек, и считаем что там будет генератор — у нас же все шаблоны генераторы. Вот значит какой-то шаблон захочет использовать наш base.bs, и вызовет его render, и передаст туда колбек.
И это будет index.bs:
- from . import base
:call base.render(*options)
:content_block
- for i in range(10):
p
- yield 'Paragraph {}'.format(i)
:footer_block
p
" Index page
Тут мы используем немножко сахара вместо того чтобы честно объявить просто функцию и передать её.
:call
переберет свои дочерние ноды, проверит что все они объявления функций, и засунет их в параметры. А
:content_block
как раз и объявляет функцию без аргументов с именем
content_block
, и с этим же именем
:call
отправит ее в аргументы.
А потом в питоно коде можем использовать:
import backslant
sys.meta_path.insert(0, backslant.PymlFinder('./templates', hook='backslant_import'))
from backslant_import.home import index
for chunk in index.render(title='The Real Thing'):
print(chunk)
Безумненько. Что добавить по синтаксису — функцию объявить можно, прям
- def func(a=True)
и прочее.
for
,
if
,
elif
,
else
— просто чистый питон. Конечно же можно и нужно использовать
yield
и
yield from
. Можно импортировать всё что угодно и как угодно использовать.
Из неподдержанного —
try: except: ...
. Текущая версия парсера не очень дружит, надо переделать парсинг.
Что дальше — генератор же. А генератор как известно еще и
send
умеет, не только
next
. Правда что из этого можно получить, ну не знаю, можно пофантазировать. Может как-то докармливать данными и отдавать порции на выход.
Скорость — такая же как у jinja2. Можно наверное попробовать как-то разогнать еще, но в основном код состоит из
yield
и
yield from
, компилируется через
ast
, нечего особо оптимизировать.
Так вот. Синтаксис можно еще допилить, внедрить какие-то идеи.
Есть кстати идеи? Давайте обсудим. А пока можно посмотреть на проект, потыркать примеры
github.com/Deepwalker/backslant/tree/master/example