Skip to content

Instantly share code, notes, and snippets.

@Deepwalker
Last active August 29, 2015 14:18
Show Gist options
  • Save Deepwalker/17859c042154302736bc to your computer and use it in GitHub Desktop.
Save Deepwalker/17859c042154302736bc to your computer and use it in GitHub Desktop.

Захотелось мне сделать шаблонизатор, чтобы как slim, теги чтобы автоматом закрывались и прочее. Красиво же так:

html
  head
    title
        - yield "Плюшка!" + " Чашка чаю!"

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

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

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

Ну сказано сделано, встречайте https://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, нечего особо оптимизировать.

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

Есть кстати идеи? Давайте обсудим.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment