... или генерируем кучу статического HTML с минимумом копипаста
Программист за два часа может написать программу, которая за две секунды сделает то, что обычный пользователь будет делать полчаса.
Задача
Недавно понадобилось сделать из большого структурированного текста HTML-документы с навигацией.
Решил сделать это с помощью шаблонов и языка разметки Markdown, потому что:
- Можно избежать копипаста однотипной HTML-разметки, так как общая структура у документов одинаковая.
- При изменении структуры, ссылок на стили и т. п. не нужно изменять много файлов.
Подход
Сходив по ссылке Templating в питоновской вики, я остановился на движке для шаблонов Mako. Из документации видно, что шаблоны Mako поддерживают наследование.
Вынесем общий для всех документов код в базовый шаблон, который затем будем наследовать в конкретных документах и переопределять нужные блоки.
Разметку внутри документов делаем с помощью Markdown для сохранения удобочитаемости.
Также для красоты тексты прогонялись через типограф студии Лебедева.
Будем использовать питоновскую библиотеку для генерации HTML из Markdown.
В качестве бонуса будем автоматически обновлять HTML-документы при изменении с помощью inotify-tools
, про которые тут уже была заметка.
На всякий случай скажу, что использую Python
В Arch Linux описанные инструменты находятся в пакетах: python2-markdown
, python2-markdown
и inotify-tools
. Библиотеки для Питона можно устанавливать также из PyPI с помощью pip
или других подобных утилит.
Код
Главный скрипт updateonsave.sh
, который смотрит за изменением файлов в рабочем каталоге и командует другими скриптами:
#!/bin/bash
while true; do
inotifywait -r -e modify -e move -e create -e delete . \
&& ./site.sh
; done
Видно, что при изменении файлов вызывается скрипт site.sh
:
#!/bin/bash
find . -name '*.mako' \! -name base.mako -print0 | sed 's|[.]mako$||g' | xargs -r -0 python2 render_mako.py
Тут ищутся все файлы, оканчивающиеся на .mako
(кроме base.mako
, который является базовым шаблоном и из которого делать HTML не надо), из них вырезается это окончание, и оставшееся имя файла передается скрипту на Питоне render_mako.py
:
from mako.template import Template
from mako.lookup import TemplateLookup
import sys
import os
lookup = TemplateLookup(directories=['.'],
input_encoding='utf-8',
output_encoding='utf-8')
args = sys.argv[1:]
for tpl_URI in args:
with open(''.join([tpl_URI, '.html']), 'w') as outfile:
outfile.write(lookup.get_template(''.join([tpl_URI, '.mako'])).render())
Тут мы создаем вспомогательный объект класса TemplateLookup
, который говорит Mako, где искать шаблоны, и задает дополнительные опции для каждого шаблона (в данном случае только кодировки для ввода и вывода). Затем получаем список аргументов, который был передан предыдущим скриптом, и записываем в файл с таким же именем как шаблон, но с расширением .html
, результат обработки шаблона.
Ниже приведен пример базового шаблона base.mako
, в который вынесены общие части HTML-документов и определены некоторые блоки и функции, которые будут замещены специфическими для каждого документа значениями.
<%!
from markdown import markdown
back = ''
forward = ''
index = '../index.html'
%>
<%def name="make_figure(filename, title='', width='')">
<div class="figure">
<img src="${filename}" ${''.join(('alt="', title, '"')) if title else ''} ${''.join(('width="', width, 'px"')) if width else ''}>
${''.join(('<p>', title, '.</p>')) if title else ''}
</div>
</%def>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title><%block name="title" /></title>
</head>
<body>
<div id="container">
<%block name="nav">
%if self.attr.back or self.attr.index or self.attr.forward:
<div class="nav">
%if self.attr.back:
<a href="${self.attr.back}" class="nav_back">Назад</a>
%endif
%if self.attr.index:
<a href="${self.attr.index}" class="nav_index">Содержание</a>
%endif
%if self.attr.forward:
<a href="${self.attr.forward}" class="nav_forward">Вперед</a>
%endif
</div>
%endif
</%block>
<div id="content">
<h1>${self.title()}</h1>
${markdown(capture(self.body))}
</div>
${self.nav()}
</div>
</body>
</html>
В итоге получится следующая структура документов:
1. Шапка HTML-документа.
2. Навигация - ссылки "вперед", "назад", "содержание",
если хотя бы одна из этих ссылок присутствует.
3. Тело конкретного документа.
4. Дубликат навигации снизу.
5. Конец HTML-документа.
Теперь приведем пример конкретного шаблона document.mako
, наследующего base.mako
и поговорим, что к чему с этими шаблонами:
<%!
back = 'document_before.html'
forward = 'document_after.html'
%>
<%inherit file="../base.mako" />
<%block name="title">Название страницы</%block>
${parent.make_figure(u'01.jpg', u'Рисунок 1')}
Тут идет некотрый текст, в котором можно применять разметку Markdown:
**полужирный**
*курсив*
[Ссылка](http://google.com)
Список:
* Первый пункт
* Второй пункт
и так далее
Пойдем с начала шаблона base.mako
:
<%!
from markdown import markdown
back = ''
forward = ''
index = '../index.html'
%>
Эта часть называется в Mako module level block, это просто кусок кода на Python, который распространяется на весь шаблон. Здесь мы импортируем функцию markdown
из одноименного модуля и объявляем переменные для использования в блоке навигации, которые можно переопределять в дочерних шаблонах.
<%def name="make_figure(filename, title='', width='')">
<div class="figure">
<img src="${filename}" ${''.join(('alt="', title, '"')) if title else ''} ${''.join(('width="', width, 'px"')) if width else ''}>
${''.join(('<p>', title, '.</p>')) if title else ''}
</div>
</%def>
Здесь объявляется «функция» make_figure
, которая служит для удобной вставки изображений. В document.mako
видно, как вызывается эта функция:
${parent.make_figure(u'01.jpg', u'Рисунок 1')}
parent
— обращение к родительскому шаблону. Аргументы с русскими буквами должны передаваться в виде юникодных строк, потому что для обычных строк декодер по умолчанию ascii
, а файл хранится в UTF-8.
Далее по base.mako
:
<title><%block name="title" /></title>
В этой части определяется пустой блок с именем title
, он же используется как заголовок первого уровня у всех документов (ниже в base.mako
):
<h1>${self.title()}</h1>
Дальше определен блок для ссылок с навигацией:
<%block name="nav">
%if self.attr.back or self.attr.index or self.attr.forward:
<div class="nav">
%if self.attr.back:
<a href="${self.attr.back}" class="nav_back">Назад</a>
%endif
%if self.attr.index:
<a href="${self.attr.index}" class="nav_index">Содержание</a>
%endif
%if self.attr.forward:
<a href="${self.attr.forward}" class="nav_forward">Вперед</a>
%endif
</div>
%endif
</%block>
Здесь используются переменные, объявленные выше в module level block и производится проверки, нужно ли отображать навигацию вообще и каждую ссылку в частности.
И наконец, главная часть — тело будущего документа:
${markdown(capture(self.body))}
Здесь функции markdown
, подключенной в module level block, передается переменная self.body
, которой будет соответствовать тело дочернего шаблона (capture
нужна для управления кешированием).
Вернемся опять к document.mako
- шаблону, наследующему базовый шаблон:
<%!
back = 'document_before.html'
forward = 'document_after.html'
%>
В module level block сразу передопределяем ссылки на предыдущую и следующую страницы (ссылка на содержание останется прежней '../index.html'
).
Далее указываем, что наследуем шаблон base.mako
:
<%inherit file="../base.mako" />
Затем переопределяем блок с названием страницы:
<%block name="title">Название страницы</%block>
Весь текст, который располагается ниже, и попадет в переменную self.body
в базовом шаблоне, где произойдет генерация HTML-разметки на основе Markdown, и на выходе получится законченный HTML-документ:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Название страницы</title>
</head>
<body>
<div id="container">
<div class="nav">
<a href="document_before.html" class="nav_back">Назад</a>
<a href="../index.html" class="nav_index">Содержание</a>
<a href="document_after.html" class="nav_forward">Вперед</a>
</div>
<div id="content">
<h1>Название страницы</h1>
<div class="figure">
<img src="01.jpg" alt="Рисунок 1" >
<p>Рисунок 1.</p>
</div>
<p>Тут идет некотрый текст, в котором можно применять разметку Markdown:
<strong>полужирный</strong>
<em>курсив</em>
<a href="http://google.com">Ссылка</a>
Список:
* Первый пункт
* Второй пункт
и так далее</p>
</div>
<div class="nav">
<a href="document_before.html" class="nav_back">Назад</a>
<a href="../index.html" class="nav_index">Содержание</a>
<a href="document_after.html" class="nav_forward">Вперед</a>
</div>
</div>
</body>
</html>
Комментариев нет :
Отправить комментарий