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

    Захотелось мне сделать шаблонизатор, чтобы как 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
    Метки:
    • +17
    • 5,6k
    • 4
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 4
    • +5
      Вас случайно не Jade вдохновил?
      • 0
        Нет, там в заголовке прям написано, что именно меня вдохновляло.
        Интересная штука — главное интересно зачем они компилят в django, jinja2 etc.
      • 0
        > Скорость — такая же как у jinja2.

        Но почему?..
        Кстати, какая именно скорость? Если у Jinja2 отключено кеширование байткода, то при рендеринге она каждый раз файл эээ… перекомпилирует заново, что снижает производительность — зачастую довольно серьёзно.

        А можно в цифрах?
        • 0
          Ну как — техника одна и та же. И backslant и jinja2 компилируются в байткод. И backslant и jinja2 каждый раз на диск за ним потом не ходят. Потому и скорость практически одинакова.
          В цифрах есть в папке example на фласке накиданный пример — там оба движка подключены. ab или wrk выдают примерно одинаковые цифры. Небольшая потеря на backslant, на уровне погрешности, видимо из-за генератора. Хотя я точно код жинжи не смотрел, может там тоже генераторы, точно не скажу.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.