Backslant – шаблонизатор в стиле slim

Backslant – шаблонизатор в стиле slim

440
ПОДЕЛИТЬСЯ

Захотелось мне сделать шаблонизатор, чтоб как slim , теги чтоб автоматом закрывались и прочее. Прекрасно же так:
html
head
title
— yield "Плюшка!" + " Чашечка чаю!"

Но и этого мне не достаточно, желаю чтоб не было собственного недоязыка, желаю чтоб просто питоновские конструкции. А кто захотит для себя в ногу стрельнуть и бизнес логики в шаблоны навалить, то это неувязка начинашек, мне для чего страдать размазывая код вьюх в папки типа utils, template_tags и прочее?

А и еще можно кстати угореть так уж угореть — а пусть шаблоны через новейший механизм импорта в python 3 тянутся. И раз нужно что-то от другого шаблона для себя вставить, то тоже пусть также работает.

А еще, еще пусть каждый шаблон это генератор!

Ну сказано изготовлено, встречайте github.com/Deepwalker/backslant. Он еще естественно не до конца допилен, но нужно получить фидбек.

Итак, попробуем на 5, 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, в рамках которого хоть какой код, который можно в объявлении словаря.

Ну что огласить — options это kwargs, так как объявления характеристик шаблона у нас здесь нет. Может и напрасно кстати что нет. Далее увлекательное — yield from из вызова некоего content_block, который лежит в каком-то options.

Вот означает какой-то шаблон захотит применять наш base.bs, и вызовет его render, и передаст туда колбек. Так вот, про content_block — здесь мы ожидаем что нам передадут в параметре некоторый колбек, и считаем что там будет генератор — у нас же все шаблоны генераторы.

И это будет index.bs:

import base
:call base.render(*options)
:content_block
— for i in range(10):
p
— yield ‘Paragraph {}’.format(i)
:footer_block
p
" Index page — from.

А :content_block как раз и заявляет функцию без аргументов с именованием content_block, и с сиим же именованием :call вышлет ее в аргументы. Здесь мы используем чуть-чуть сахара заместо того чтоб честно объявить просто функцию и передать её. :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)

Можно импортировать всё что угодно и как угодно применять. Естественно же можно и необходимо применять yield и yield from. Безумненько. Что добавить по синтаксису — функцию объявить можно, прям — def func(a=True) и прочее. for, if, elif, else — просто незапятнанный питон.
Из неподдержанного — try: except: …. Текущая версия парсера не чрезвычайно дружит, нужно переработать парсинг.

А генератор как понятно еще и send умеет, не лишь next. Что далее — генератор же. Может как-то докармливать данными и отдавать порции на выход. Правда что из этого можно получить, ну не знаю, можно пофантазировать.

Скорость — таковая же как у jinja2. Можно наверняка испытать как-то разогнать еще, но в основном код состоит из yield и yield from, компилируется через ast, нечего особо улучшить.

Синтаксис можно еще допилить, ввести какие-то идеи. Так вот.

А пока можно поглядеть на проект, потыркать примеры github.com/Deepwalker/backslant/tree/master/example habrahabr.ru Есть кстати идеи? Давайте обсудим.