Hastache — реализация Mustache для Haskell
Довел до ума и выложил в open source свою реализация шаблонизатора Mustache, на которой, в частности, крутится сайт с которого вы это сейчас читаете.
Взять можно либо на GitHub, либо из HackageDB:
|
|
Про Mustache вообще я уже написал, повторятся не буду, сразу перейду к Хаскелю.
Простейший пример использования
|
|
Результат:
|
|
О форматах представления строк
В Hastache используются ByteString. Причем и обычные и Lazy. По обычным удобно искать, поэтому ими представлена исходная строка шаблона. Результатом работы является Lazy ByteString.
Внутри hastache для построения результата использует библиотека blaze-builder. Она обеспечивает эффективное построение Lazy ByteString из большого количество коротких фрагментов. Blaze-builder позволяет как быстро строить ByteString, так и быстро передавать получившийся результат по сети (или записывать в файл). Это достигается за счет того, что библиотека следит за минимальным размером каждого фрагмента ByteString и не позволяет им быть слишком короткими. С помощью функций hastacheStrBuilder и hastacheFileBuilder можно непосредственно получить объект Builder библиотеки blaze-builder и использовать его нужным вам образом (например изменить минимальный размер фрагмента ByteString).
Hastache предполагает, что все ByteString закодированы в Utf-8 и предоставляет функции кодирования и декодирования String в ByteString Utf-8 и обратно.
Контекст
Можно условно выделить два пути передачи контекста в шаблонизатор. Первый — «собрать» все данные в некий пакет (например в хеш-таблицу) и отдать его шаблонизатору чтоб он с ним разбирался. Это отлично подходит для динамических языков типа JavaScript или Python, однако в Haskell какого-то общепринятого метода собрать разные данные в кучку нет (методов, конечно, полно, но для разных применений будут удобны разные методы). Поэтому, немного подумав, я решил пойти по второму пути — пусть контекстом будет функция, которая на вход будет получать имя переменной, а на выходе у неё будет значение (строка например) с которой шаблонизатор может что-то сделать (скажем, вывести в результирующий документ). В результате, способ хранения переменных контекста никак не ограничивается и программист может сам выбрать самый удобные для себя способ.
В Hastache контекстом является функция типа:
|
|
У MuType есть следующие конструкторы:
- MuVar a => MuVariable a — Любая переменная, для которой определен класс MuVar (в библиотеке hastache этот класс определен для типов String, Int, Double и т.д., полный список в документации).
- MuList [MuContext m] — Список контекстов для отображения секции шаблона для каждого элемента этого списка.
- MuBool Bool — Булево значение, используется для показа или скрытия секции.
- MuVar a => MuLambda (ByteString -> a) — Функция, получающая на вход содержимое секции в виде ByteString и возвращающая его после какого-либо преобразования.
- MuVar a => MuLambdaM (ByteString -> m a) — Как предыдущий конструктор, но функция выполняется в той монаде, из которой вызвали Hastache.
- MuNothing — Знак того, что переменная с таким именем в контексте не найдена. Встретив такое значение, Hastache попытается повторить поиск в родительском контексте (например из контекста внутри MuList [..] будет произведен поиск в родительском для этого MuList [..] контексте). Если переменная не будет найдена, то вместо соответствующего тега будет подставлена пустая строка.
Немного примеров.
Работа со списком:
|
|
Результат:
|
|
Также к элементам списка можно обратится по их номеру (например heroes.0.name). Смотрите пример как это делается.
Вызов функции:
|
|
Результат:
|
|
В этом примере используется функция decodeStr (ByteString -> String), она нужна для преобразования ByteString в String с корректной обработкой Utf-8 (есть обратная ей функция encodeStr).
Вызов монадической функции:
|
|
Результат:
|
|
В этом примере Hastache вызывается из монады WriterT и в процессе работы в эту монаду собираются данные из исходного шаблона.
Конструктор MuLambdaM полезен, если необходимо вызвать IO-функцию над содержимым секции шаблона, или как-то накопить данные из шаблона, или когда преобразование зависит от каких-либо внешних условий, и так далее для всех возможных применений монад.
Использование переменных из родительского контекста:
|
|
Результат:
|
|
Если вы используете вложенные контексты, может возникнуть ситуация когда нужно получить доступ к какому-либо родительскому контексту из вложенного. К примеру, это может быть некий глобальный флаг. Для этого существует конструктор MuNothing, который ваши функции-контексты должны возвращать встретив неизвестную переменную. Увидев MuNothing, hastache попытается найти эту же переменную в контексте уровнем выше.
Автоматическое создание контекста из типа с указанными именами полей
Один из очевидных методов собирания данных вместе в Haskell — это создание нового типа данных. При этом если в конструкторе этого типа используются именованные поля, то можно воспользоваться функцией-хелпером mkGenericContext которая автоматически создаст контекст для этих данных.
|
|
Результат:
|
|
Если одним из полей является другой тип с указанными именами полей, то к элементам этого типа можно обратится двумя путями: разделив поля точкой (строка 47), либо создав отдельную секцию (сроки 49-52).
Для элементов списка из простых типов генерируется контекст, в котором к элементу можно обратится как {{.}} (строка 59).
Для создания контекста можно использовать типы данных с полями-функциями String -> String, ByteString -> ByteString, String -> m String и т.д. (m — монада из которой вызывается hastache, полный список поддерживаемых полей-функций есть в документации к Text.Hastache.Context):
|
|
Результат:
|
|
Автоматически созданные контексты также поддерживают поиск в родительских контекстах.
Конфигурация
Hastache можно конфигурировать с помощью типа MuConfig, имеющего следующие поля:
- muEscapeFunc :: ByteString -> ByteString — escape функция для преобразования управляющих символов. В стандартной конфигурации работает с HTML символами. Можно написать свою для других языков, либо вообще отключить, указав emptyEscape.
- muTemplateFileDir :: Maybe FilePath — путь для поиска файлов, включенных в шаблон (через {{> имя_файла}}). Если Nothing, то текущая директория. В стандартной конфигурации — Nothing.
- muTemplateFileExt :: Maybe String — расширение, добавляемое к именам включаемых файлов. В стандартной конфигурации — Nothing.
Ограничения
Из-за особенностей парсинга, не поддерживаются вложенные секции с одинаковыми именами у вложенной (любого уровня вложенности) и у родительской секции. Обойти это ограничение можно, выделив внутреннюю секцию в отдельный файл.
Заключение
Описал практически все возможности Hastache, за дополнительной информацией обращайтесь к документации (да, я знаю что она может быть лучше) и смотрите исходники.
Я обещал выложить части из которых состоит мой блог, и на мой взгляд, сейчас выложил самую важную. Поэтому, если кто-то хочет сделать себе небольшой сайтик на Haskell, может уже приступать, остальное все просто :) .
За поддержкой и с багрепортами обращайтесь по email, в комментарии к этой записи, или на GitHub.