Как стать автором
Обновить

Универсальный шаблонный движок за 1 минуту из подручных материалов

Время на прочтение3 мин
Количество просмотров1.8K
Шаблон

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

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

В раздумьях, как бы это сделать побыстрее и подешевле, не следует обманывать себя (в основном, на раннем этапе) мыслями типа «да у меня статический текст, только надо приклеить шапку», «тут нужен только счётчик», «мне надо только вывести дату» и т.д.
Сначала — шапка и счётчик, дальше будет условная вставка блоков (if), потом понадобится генерация таблиц (тут уже нужны циклы и массивы), далее потянутся строковые операции и все остальные конструкции нормального языка программирования.

Поиск решения


Поскольку свой язык программирования я изобретать не собирался, а решение хотелось быстро и дёшево, взял я для начала perl с его regexp-ами и начал генерить генерить вывод по тэгам вида <?=value?> и <? code ?>.
Код получался вида
print "text";print $value; code

С другой стороны, посмотрев, как происходит подстановка в двойные кавычки, я понял, что занимаюсь тем, что уже сделано до меня: компилятор perl при разборе текста сам заменяет строки вида
"abc $var cde"
на
"abc ".$var." cde"

Ну и прекрасно, можно вставлять значения и через $var, а результат получать eval-ом.
Это уже половина дела: переменные и элементы массивов в шаблон мы уже вставлять можем.
Осталось решить, что делать с кодом.

Тут приходит на помощь интерполяция массивов:
print "5*5 = @{[5*5]}"
выведет
5*5 = 25

Возможно, этот хак требует пояснения.
При интерполяции @ означает, что сейчас мы будем выводить массив. Далее, {} отделяет сам массив от окружающего текста, а [ ] говорит о том, что вставлять мы будем массив по ссылке.
Однако, содержимое массива мы уже генерим кодом, который там подставлен. И об этом необходимо помнить в дальнейшем: у всего, что мы делаем тут в квадрантых скобках — контекст массива.

То есть, механизм работы: создание массива (часто, с одним элементом), создание ссылки на него и потом вывод этого массива по ссылке. Неоптимально, за то — работает.

Итак, код в строку вставлять тоже умеем.
Ещё немного — и…

Решение


#!/usr/bin/perl -w
use strict;
{ local $/ = undef; $txt = <>; }
print eval qq{<< "__END_OF_TEMPLATE"\n$txt\n__END_OF_TEMPLATE\n};
die $@ if $@;

Всё.
Это и есть шаблонный движок.
При желании он даже помещается в одну строку в perl -e.

Что это и зачем


Это действительно самый короткий и простой template engine, который я когда-либо видел. Запустить его можно везде, где есть perl и при этом ничего не надо скачивать, собирать, инсталлировать — в некоторых случаях это достаточно большая проблема.

Тут есть подстановка значений через $, выполнение кода на perl через @{[ ]}, есть правильный вывод ошибок с правильным указанием номера строки. Тут даже есть модульность: важные, часто используемые и длинные функции отделяются в отдельный модуль или просто исходник, который из самого шаблона потом подключается через use или do соответственно.
Практически ничего не сделав, мы получили почти всё что надо!

Шаблоны имеют такой вид:
Привет!
Дата на сервере: @{[''.localtime]}.
PID: $$
\@INC path: @INC
Environment:
@{[join "\n", map {".... $_ => $ENV{$_}"} sort keys %ENV]}
@{[do{use IO::File; IO::File->new("<tail.inc")->getlines()}]}

Практически в неизменном виде я использовал его уже в нескольких задачах:
• в специфичном самописном http-сервере страницы содержались в hash (по пути) и через этот же механизм результат вычислялся и отдавался клиенту;
• генерил код на C++ со сложными структурами и зависимостями, данные описывались в глубокой разветвлённой perl-структуре — там был аналог XSLT на хешах и массивах;
• делал заменитель autotools для генерации Makefile.
Не говоря уже о множестве мелких применений.
Универсальность тут состоит в том, что нигде нет вообще никакой привязки к герерируемому контенту, а специфичных функций можно наделать для любого применения.

В общем, если у вас ничего нет и вам срочно нужно что-то подставить в шаблон, этот способ — для вас.

Если хочется принимать код от клиентов, можно посмотреть в сторону таких модулей как Safe или ops, но сразу хочу сказать, что готового решения тут я не предлагаю.

Где это использовать НЕ надо:
• Не надо это пытаться отдавать дизайнерам и не надо от них требовать шаблонов в этом формате. Помните, что реально это — программа на PERL, хоть и выглядящая как HTML (или что-то другое).
• Без серьёзной доработки не стоит напрямую совать туда контент от пользователей, скорее всего это приведёт к локальному выполнению кода и взлому.
• Бинарные данные генерить можно, но надо, как минимум, подумать, как поступить с последним переводом строки (наверно, просто оторвать).
• Если у вас уже есть «правильный» шаблонизатор, заточенный под вашу задачу, этот костыль вам тоже не нужен.

В областях, где этот способ шаблонизации использовал я, метод показал себя прекрасно и кое-где работает до сих пор.

Отвечу на все вопросы, если они возникнут.
Теги:
Хабы:
Всего голосов 12: ↑7 и ↓5+2
Комментарии13

Публикации