One-liner для компиляции шаблонов на Lua

    Синтаксис Lua позволяет реализовать шаблоны в стиле PHP буквально несколькими регулярными выражениями.
    Для начала посмотрим, что из этого выйдет.

    Подстановка переменных


    <a href="<%url%>"><%label%></a>
    

    Логические конструкции


    Будет
    <? if 1 > 2 then ?>
    лучше
    <? else ?>
    хуже
    <? end ?>
    

    Циклы


    <ul>
    <? for i = 1, 9999 do ?>
      <li>ФЗ №<%i%></li>
    <? end ?> 
    </ul>
    


    Подключение других шаблонов


    <html>
      <script><? require "scripts" ?></script>
      <style><? require "styles" ?></style>
      ...
    

          <? require 'tracking' ?>

    И любые другие конструкции на Lua


    <? function warn() ?>
    <b>Вы совершаете уголовно наказуемое деяние!</b>
    <? end ?>
    ...
    <? warn() ?>
    ...
    <? --[[ ?>
    Больше нечего скрывать
    <? --]] ?>
    

    И это все собирается одной командой в обычный модуль Lua:
    (echo 'return function(_)_[=['; sed -e 's/[][]=[][]/]=]_"\0"_[=[/g; s/<%/]=]_(/g; s/%>/)_[=[/g; s/<[?]/]=] /g; s/[?]>/ _[=[/g'; echo ']=] end') < template.tpl > template.lua
    

    На самом деле, для запуска потребуется написать еще одну короткую функцию:
    function template.print(data, args, callback)
      local callback = callback or print
      local function exec(data)
        if type(data) == "function" then
          local args = args or {}
          setmetatable(args, { __index = _G })
          setfenv(data, args)
          data(exec)
        else
          callback(tostring(data))
        end
      end
      exec(data)
    end
    

    Установка


    Маленькая библиотека из трех функций доступна в Moon Rocks:
    luarocks install template

    luarocks, в свою очередь, имеется в репозиториях Ubuntu:
    sudo apt-get install luarocks

    Проект на GitHub.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 20
    • +4
      Ничего хорошего из этого не выйдет :-)
      • +4
        А теперь предположим, что в html встречается последовательность символов [=[ или ]=]. И абстракция потекла…
        Всё-таки для шаблонов нужен нормальный парсер.
        • 0
          Да, есть несколько неучтенных моментов, которые я в ближайшие пару дней поправлю и задокументирую.
          • 0
            Для более сложного можно использовать мой вариант. Он подстраивается под начинку. Но да, не «одной строкой».
            • 0
              Исправлено.
            • +4
              Причем тут спагетти-код? Исходно термин «спагетти-код», он же «макаронный код», означал код с запутанным потоком управления — многочисленными операторами GOTO (или избыточным использованием исключений). Также спагетти-кодом называют код со слишком обширными связями между модулями, когда формально независимые модули невозможно использовать раздельно.

              Шаблоны в стиле php — не самая удачная конструкция исключительно из-за похожести символов <? и ?> на тэги HTML. Других недостатков у таких этих шаблонов нет, и называть их «спагетти-кодом» некорректно.
              • +1
                Есть, они смешивают логику и верстку из-за чего и верстать не удобно и за логикой сложно уследить.
                • +1
                  А что, кто-то заставляет их смешивать? Можно же их и разнести: либо просто логику в начало файла передвинуть, либо даже по разным файлам — язык-то позволяет. Хоть убейте, не вижу никакой принципиальной разницы между
                  <? if ($foo) { ?>
                      <div><?=$foo?></div>
                  <? } ?>
                  
                  и
                  @if (foo != null) {
                      <div>@foo</div>
                  }
                  
                  кроме удобочитаемости.
                  • –1
                    Да, такой формат. Ты просто не сможешь ничего отрендерить, не применив циклы, ветвления и функции.
                    Оба примера содержат смешение логики и верстки. Правильный пример мог бы выглядеть так:

                    myViewList = {
                    	get content(){
                    		if( this.items ) {
                    			return this.items.map( item => new myViewItem( item ) )
                    		} else {
                    			return new myViewEmpty
                    		}
                    	}
                    }
                    


                    <div type="myViewList">{content}</div>
                    
                    <div type="myViewItem">
                    	<div type="myViewItem_title">{title}</div>
                    	<div type="myViewItem_count">{count}</div>
                    </div>
                    
                    <div type="myViewEmpty">
                    	List is empty. Try to reduce filters.
                    </div>
                    
                    • +5
                      Вот извините, но именно ваш пример и является спагетти-кодом (точнее, спагетти-версткой). О том, что myViewItem и myViewEmpty являются альтернативными вариантами разрешения {content} в myViewList, не сказано нигде — и догадаться об этом, читая верстку, невозможно.

                      Так зачем нужна «чистая от логики» верстка, если понять ее можно только глядя на логику?
                      • –1
                        {content} может разрешаться во всё, что угодно и на этапе вёрстки совершенно не важно во что. Задача верстальщика — предоставить кубики лего из которых программист может строить приложение любой сложности просто стыкуя их в разных комбинациях. Прежде чем оголтело кидаться минусами, я бы советовал вам попробовать — это реально гораздо удобней.
                        А пока подумайте над следующими проблемами так называемых «традиционных шаблонов» и над способами их решения:
                        1. рендеринг блоков разных типов одним списком (например, лента новостей)
                        2. перестановка блоков местами в зависимости от набора параметров (например, полей много, но заполнены только частично и нужно выводить их в компактном виде)
                        3. ленивая загрузка минимума данных, необходимого для рендеринга, а не всего подряд (в запросе надо указать какие поля моделей надо прогрузить)
                        4. возможность пользователя конфигурировать раскладку блоков на странице и переключать их видимость (например, дашборд)
                        5. реиспользование верстки (ссылка на пользователя, ссылка на метку, кнопка и тп)
                        • 0
                          Задача верстальщика — сверстать страницу, а не заставлять программиста играть в кубики. Тем более, что верстальщику гораздо проще напрямую сверстать страницу, чем сначала верстать ее, а потом нарезать на кубики.

                          Не говорю уже о том, что в вашей модели верстальщик и программист должны работать параллельно, потому что ни один из них не увидит результата своей работы до завершения чужой. В традиционной модели такого ограничения нет.

                          И даже если в команде считается, что задача верстальщика — только HTML-верстка, а шаблон должен готовить программист — то «нарезание» готовой верстки на циклы и условные операторы не сложнее ее нарезания на блоки.

                          Прежде чем оголтело кидаться минусами, я бы советовал вам попробовать — это реально гораздо удобней.
                          В разное время мне случалось редактировать шаблоны веб-страниц на движке Razor (который в ASP.NET MVC) и на укозе (там используются блоки, как вы предлагаете). Так вот — первая задача была выполнена за 15 минут, а вторую я бросил. Поэтому я возвращаю вам ваш совет: попробуйте делать так, как делают все нормальные люди — это реально гораздо удобней.

                          1. Для того, чтобы рендерить разные типы блоков одним списком, надо их получить одним списком. Рендеринг — наименьшая из проблем, которая решается оператором цикла и несколькими условными операторами.

                          2,4. Перестановка блоков местами и возможность конфигурировать раскладку блоков — да, они требуют выделения части верстки в отдельную сущность «блок». «Традиционные» шаблоны легко позволяют это сделать. Но, заметьте, выделять в автономный блок следует лишь то, что и правда должно быть выделено — при этом как общий макет страницы, так и внутренняя верстка блока могут быть сделаны так, как удобно верстальщику, а не так, как его ограничили вы.

                          3. Ленивая загрузка данных — это когда выполняется 100500 уточняющих запросов к БД при каждом формировании страницы? Спасибо, не надо! Лучше я по-старинке, одним-двумя запросами все необходимое вытащу.

                          5. Те блоки верстки, которые должны быть переиспользуемы, легко становятся функциями. Важно, что решение сделать их повторно используемыми может быть принято верстальщиком единолично, без согласования с программистом или архитектором проекта.
                          • –1
                            Если речь о лендингах, то да, сверстал страницу и свободен. Если речь о полноценном сайте или даже веб-приложении, то важным становится реиспользуемость кода, легкость поддержки, выдержанность единого дизайна (как внешнего, так и внутреннего). Программисту проще писать без тестов, но поддерживать результат потом невозможно. Работа верстальщика не заканчивается на первичной верстке. Он должен иметь возможность править верстку без ковыряния в куче логики. Путь «верстальщик делает статический макет, программист разрезает его и натягивает на шаблонизатор, потом верстальщик поправил макет, программист заново его нарезает» — порочен.

                            В «традиционной модели» шаблон зависит от формата данных, которые должен сформировать программист. Никакого разделения не получится. В «непривычной модели» зависимости нет и верстальщик может компоновать блоки как угодно, причем выводить их сразу во всех возможных состояниях, а не заниматься ерундой вида «поправил код. перезагрузил страницу, подождал пока пройдет вся инициализация, накликал нужное состояние, увидел косяк и по новой».

                            Ещё раз — проблема не в «нарезании» — это однократная операция. Проблема в поддержке, которая нужна постоянно. И верстальщик, не имея должной квалификации и желания разбираться просто сделает копипасту сложного условия вместо того, чтобы рефакторить логику.

                            Можно пример укозового шаблона? В любом случае тезис похож на «я привык работать молотком, поэтому забиваю шурупы быстрее чем вкручиваю». За 8 лет, а перепробовал всё, что только можно, от говнокода на smarty до крупных проектов на xslt :-)

                            1. Получить одним списком это проблема только в строготипизированных языках. Но вывод слишком поверхностный. Там еще вынести все эти шаблоны придется по любому, или этот мегашаблон, который умеет рендерить всё, разрастётся до неприличных размеров.

                            2, 4. В современных динамичных приложениях такие блоки — далеко не единичные случаи. Даже «общего макета страницы» может не быть.

                            3. Нет, это когда клиент выгружает с сервера не все 100500 полей на каждый чих, а только те, что нужно показать. Но тем не менее, много простых запросов куда лучше кластеризуются, кешируются, параллелизуются, чем один сложный.

                            5. Продвигаемая мной идея как раз и позволяет делать это легко и просто. О чём спор? :-)
                            • 0
                              В современных динамичных приложениях такие блоки — далеко не единичные случаи. Даже «общего макета страницы» может не быть.
                              Приведите пример сайта, у которого нечего вынести в общий макет (кроме одностраничных сайтов, где само слово «общий» теряет смысл). Я пока что из таких видел только одну CMS, работающую по принципу «создай все сам, редактируя БД через тот же интерфейс, который ты сейчас создаешь». Спасибо, но такая CMS должна отправиться в то же место, куда и мегаблочные спагетти-шаблоны.

                              В любом случае тезис похож на «я привык работать молотком, поэтому забиваю шурупы быстрее чем вкручиваю».
                              Оба раза я видел шаблонизатор впервые.

                              Ещё раз — проблема не в «нарезании» — это однократная операция. Проблема в поддержке, которая нужна постоянно.
                              Вот мне надо исправить положение имени пользователя на странице. Допустим, я как веб-разработчик уже имею опыт, но на проекте впервые, так что ничего еще в нем не знаю (сравнивать поведение человека в знакомом проекте смысла нет, потому что в обоих случаях он найдет нужное место сразу же).
                              В традиционном шаблоне достаточно найти элемент в инспекторе (встроенная возможность браузера, 2 клика), запомнить его окружение — и найти похожий блок кода в шаблоне. Поскольку структура шаблона и результата совпадает — это несложно.
                              Теперь берем ваш шаблонизатор. Где искать блок, выводящий имя пользователя? Искать по файлу строку «username»? А если она называется «user_name» или «login»? Ну хорошо, блок с именем пользователя нашли — но проблема оказалась не в нем, а во внешнем блоке. Как найти внешний блок, не заглядывая в логику? А если в эту логику заглянуть — то зачем мы ее с такой кровью отделяли?
                              • 0
                                Да тот же google.ru (морда и результаты поиска)
                                Правда там смена раскладки сделана на css — гораздо менее гибким муторным способом.

                                Вы так и не привели примера укозного шаблона.

                                «запомнить его окружение»
                                «и найти похожий блок кода в шаблон»
                                Как-то всё это похоже на гадание на кофейной гуще.

                                «структура шаблона и результата совпадает»
                                Опять же только в лендингах она совпадает. В остальных же случаях — частично пересекается, не более.

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

                                image

                                Тут подробней описано как оно работает
                                • 0
                                  На том же google.ru можно дофига чего вынести в общий макет — наличие нескольких выбивающихся из макета страниц этому ничуть не мешает.

                                  Вы так и не привели примера укозного шаблона.

                                  Ну где я вам сейчас возьму укозный шаблон? 10 лет назад я тот сайт редактировал. Создайте там сайт да посмотрите на этот ужас.

                                  С идентификаторами элементов — ладно, выкрутились :) В таком виде действительно можно быстро найти нужное место.
                                  • 0
                                    Но и ничуть не способствует. Зачем искусственно вводить какой-то общий макет для совершенно разных по структуре страниц?

                                    Ладно, не важно, не думаю, что в этом кривом сервисе было что-то вменяемое :-)
              • +1
                В луашной библиотеке Lapis тоже есть шаблоны с возможностью экранировать теги HTML.
                Про Lapis по-русски: habrahabr.ru/post/240217/
                • +1
                  Более того, в moonscript есть возможность делать так
                  Скрытый текст
                  [list_users: "/users"]: =>
                      users = Users\select! -- `select` all the users
                      @html ->
                        ul ->
                          for user in *users
                            li ->
                              a href: @url_for("user", user.id), user.name
                  


                  Я задавался задачей сделать что-то аналогичное etlua максимально просто.
                  • 0
                    MoonScript для генерации HTML хорош, уж по крайней мере не забудешь закрыть тег. Раздражает отсутствие некоторых тегов, из-за чего приходится писать так:

                    element 'table', ->
                      element 'tr', ->
                        element 'td', ->
                          text 'Тут наконец содержимое первой ячейки'
                    

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