Здравствуй уважаемый %username%.
Сегодня я хотел бы поделиться с тобой личным опытом в создании Web сайта на CppCMS (библиотека-шаблонизатор на с++). Можно назвать это «помощью начинающему программисту на CppCMS».
Доводы за и против такого решения могут быть весьма разнообразны и, что бы не спровоцировать войну «языковых школ», я проведу аналогию с автомобилями: «Я купил этот. Нравится. Езжу. Продавать не хочу!».
Из дополнительных аргументов будет то, что данный язык является профильным для моего рабочего места.
Перед написанием сайта — придется сначала оное (CppCMS) поставить на рабочую машину. Библиотека самым наглым образом требует для своей работы Boost c++, pcre, crypt, python, icu и, несмотря на кросплатформенность, гораздо приятнее ставится под *nix системами.
Само же построение сводится к банальным:
Проблем возникнуть не должно, все строится в автоматическом режиме, и ни разу еще меня не огорчало.
Забегая вперед хотелось бы сказать что для комфортной работы желательно что бы среда разработки умела выполнять пользовательские шаги построения, а так же имела удобноредактируемый «синтаксический анализатор», мною используется QtCreator.
Все дальнейшие шаги я буду описывать применительно к вышеобозначенной среде, так как построение с использованием командной строки хорошо рассмотрено на сайте самой библиотеки. Так же хочу отметить что некоторые действия сборки будут автоматизированы bash скриптами( хотя было бы достаточно прописания «пользовательского шага» на этапе сборки )
Перед началом работы желательно добавить подсветку синтаксиса для QtCreator, что бы он распознавал особые файлы *.tmpl, которые используются в качестве шаблонов. Данный файл tmpl.xml ( слегка модифицированная подсветка HTML ) должен лежать в папке конфигов «qtcreator/generic-highlighter/tmpl.xml»:
В зависимости построения следует добавить:
Создаем файл main.cpp и наполняем его следующим содержимым:
Если вы уже попытались это запустить, то скорее всего ничего не получилось, так как для полноценной работы не хватает конфигурационного файла для сервера, его местоположение нужно передать бинарнику при запуске
Конфиг может быть следующего содержания:
Этого должно быть достаточно.
Так же хорошим вариантом будет прописать параметра запуска в «конфиги среды программирования»
Итак, дописываем и запускаем и открываем.
Впечатляет? Нет?
Оно и верно, так как для данного примера мы не использовали механизм шаблонов, а ограничились банальным выводом строки. Однако пример позволяет нам убедиться что все работает.
Давайте напишем первый шаблон, который будет «превращен» шаблонизатором библиотеки в файл *.cpp.
Итак, первым делом добавим заголовочный файл, содержащий структуру динамических данных ( данных шаблона ). По умолчанию будут помещаются в папку data внутри проекта.
data/tmpl_master.h
Содержимое данного файла как правило не отличается каким-либо умным кодом и по сути это всего лишь контейнеры для описания переменных, используемых в шаблонах.
Приступим к написанию собственно шаблона, создадим папку templates, в ней файл master.tmpl, следующего содержания:
Что же тут понаписано?
В самой первой строчке
Строка
Строка
Строки
определяют дефолтные значения, которые будут выведены пользователю в случае если мы их не переопределим( то есть являются
Давайте попробуем собрать. Ясное дело что компилятор с++ не проглотит файл tmpl. Поэтому на помощь должна прийти утилита, собравшаяся вместе с библиотекой, которая переработает шаблон до нужного состояния.
Для этого создадим внутри проекта файл «make_templates.sh», внутрь которого поместим нужные нам операции( Данный файл легко можно заменить или ручным вызовом данной утилиты или прописанием ее в «исполняемую часть» вашей среды ):
Теперь в настройках проекта QtCreator необходимо добавить «пользовательский шаг»
Команда: "./make_templates.sh"
Рабочая директория: "%{sourceDir}"
Аргументы команды: "-i %{sourceDir} -o %{buildDir}"
Не забудьте добавить файлу «исполняемость».
Если сборка прошла удачно, то в директории построения появится кроме прочего библиотека libcpp_defskin.so.
Важно отметить что собрать библиотеку можно статически или динамически. У меня сделано вторым способом, крайне вам не советую использовать первый, так как файлы TMPL приходится менять частенько, а перекомпилировать из-за этого весь проект — весьма неблагодарное занятие.
Так же что бы шаблоны были привязаны к проекту — необходимо дополнить файл config.json
И внести соответствующие изменения в main.cpp:
Теперь при запуске проекта мы должны увидеть вывод шаблона. Но постойте, совсем забыл про css и изображения, сейчас исправлюсь.
Добавляем в config.json еще один пункт
Здесь определенно нужно пояснить, что данным пунктом мы разрешаем бинарнику смотреть в файловую систему. А правила по которым он это делает описаны в секции http
то есть любой запрос, начинающийся с /media/ следует перенаправлять «файловому серверу».
Создадим в папке проекта папку media, а так же добавим соответствующий пункт в make_templates.sh:
Внутри папки media ( в директории исходников проекта ) создадим подпапку css, а в ней файл style.css
Пробуем собрать еще раз.
Теперь, когда сайт похож на первую работу начинающего мастера — можно перейти к самому главному.
Механизм наследования шаблонов предельно прост. Определяем от какого шаблона наследуемся и дописываем переопределение функции вывода контента.
Создадим файл файл tmpl_news.h в папке data
Так же добавим файл news.tmpl в папку templates
Добавим путь до файлика в скрипт сборки ( должно выглядеть как-то так):
Изменяем файл main.cpp
Основные изменения в файле произошли в конструкторе, где мы указали какая функция за вывод какой страницы отвечает.
Теперь эти страницы выводят различные шаблоны.
Особо важный момент — порядок подачи списка файлов шаблонизатору ( файлы-потомки должны следовать после родителей, иначе будут сыпаться ошибки ).
На этом я планирую закончить первую часть.
Сегодня я хотел бы поделиться с тобой личным опытом в создании Web сайта на CppCMS (библиотека-шаблонизатор на с++). Можно назвать это «помощью начинающему программисту на CppCMS».
Зачем писать сайт на с++
Доводы за и против такого решения могут быть весьма разнообразны и, что бы не спровоцировать войну «языковых школ», я проведу аналогию с автомобилями: «Я купил этот. Нравится. Езжу. Продавать не хочу!».
Из дополнительных аргументов будет то, что данный язык является профильным для моего рабочего места.
Давайте уже что-нибудь напишем
Однако прежде
Перед написанием сайта — придется сначала оное (CppCMS) поставить на рабочую машину. Библиотека самым наглым образом требует для своей работы Boost c++, pcre, crypt, python, icu и, несмотря на кросплатформенность, гораздо приятнее ставится под *nix системами.
Само же построение сводится к банальным:
mkdir build
cd build
cmake ..
make
make install
Проблем возникнуть не должно, все строится в автоматическом режиме, и ни разу еще меня не огорчало.
Забегая вперед хотелось бы сказать что для комфортной работы желательно что бы среда разработки умела выполнять пользовательские шаги построения, а так же имела удобноредактируемый «синтаксический анализатор», мною используется QtCreator.
Все дальнейшие шаги я буду описывать применительно к вышеобозначенной среде, так как построение с использованием командной строки хорошо рассмотрено на сайте самой библиотеки. Так же хочу отметить что некоторые действия сборки будут автоматизированы bash скриптами( хотя было бы достаточно прописания «пользовательского шага» на этапе сборки )
Перед началом работы желательно добавить подсветку синтаксиса для QtCreator, что бы он распознавал особые файлы *.tmpl, которые используются в качестве шаблонов. Данный файл tmpl.xml ( слегка модифицированная подсветка HTML ) должен лежать в папке конфигов «qtcreator/generic-highlighter/tmpl.xml»:
Полный текст файла
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE language SYSTEM "language.dtd"
[
<!ENTITY name "[A-Za-z_:][\w.:_-]*">
<!ENTITY entref "&(#[0-9]+|#[xX][0-9A-Fa-f]+|&name;);">
]>
<language name="TMPL" version="1" kateversion="2.4" section="Markup" extensions="*.tmpl" mimetype="text/tmpl" author="Wilbert Berendsen ( original HTML author)(wilbert@kde.nl)" license="LGPL" priority="10">
<highlighting>
<contexts>
<context name="Start" attribute="Normal Text" lineEndContext="#stay">
<IncludeRules context="FindHTML" />
</context>
<context name="FindHTML" attribute="Normal Text" lineEndContext="#stay">
<DetectSpaces/>
<DetectIdentifier/>
<StringDetect attribute="Comment" context="Comment" String="<!--" beginRegion="comment" />
<StringDetect attribute="Commenttmpl" context="Commenttmpl" String="<%" beginRegion="commenttmpl" />
<StringDetect attribute="CDATA" context="CDATA" String="<![CDATA[" beginRegion="cdata" />
<RegExpr attribute="Doctype" context="Doctype" String="<!DOCTYPE\s+" beginRegion="doctype" />
<RegExpr attribute="Processing Instruction" context="PI" String="<\?[\w:-]*" beginRegion="pi" />
<RegExpr attribute="Element" context="CSS" String="<style\b" insensitive="TRUE" beginRegion="style" />
<RegExpr attribute="Element" context="JS" String="<script\b" insensitive="TRUE" beginRegion="script" />
<RegExpr attribute="Element" context="El Open" String="<pre\b" insensitive="TRUE" beginRegion="pre" />
<RegExpr attribute="Element" context="El Open" String="<div\b" insensitive="TRUE" beginRegion="div" />
<RegExpr attribute="Element" context="El Open" String="<table\b" insensitive="TRUE" beginRegion="table" />
<RegExpr attribute="Element" context="El Open" String="<ul\b" insensitive="TRUE" beginRegion="ul" />
<RegExpr attribute="Element" context="El Open" String="<ol\b" insensitive="TRUE" beginRegion="ol" />
<RegExpr attribute="Element" context="El Open" String="<dl\b" insensitive="TRUE" beginRegion="dl" />
<RegExpr attribute="Element" context="El Open" String="<&name;" />
<RegExpr attribute="Element" context="El Close" String="</pre\b" insensitive="TRUE" endRegion="pre" />
<RegExpr attribute="Element" context="El Close" String="</div\b" insensitive="TRUE" endRegion="div" />
<RegExpr attribute="Element" context="El Close" String="</table\b" insensitive="TRUE" endRegion="table" />
<RegExpr attribute="Element" context="El Close" String="</ul\b" insensitive="TRUE" endRegion="ul" />
<RegExpr attribute="Element" context="El Close" String="</ol\b" insensitive="TRUE" endRegion="ol" />
<RegExpr attribute="Element" context="El Close" String="</dl\b" insensitive="TRUE" endRegion="dl" />
<RegExpr attribute="Element" context="El Close" String="</&name;" />
<!-- as long as kde gives DTDs the text/html mimetype--><IncludeRules context="FindDTDRules" />
<IncludeRules context="FindEntityRefs" />
</context>
<context name="FindEntityRefs" attribute="Other Text" lineEndContext="#stay">
<StringDetect attribute="Commenttmpl" context="Commenttmpl" String="<%" beginRegion="commenttmpl" />
<RegExpr attribute="EntityRef" context="#stay" String="&entref;" />
<AnyChar attribute="Error" context="#stay" String="&<" />
</context>
<context name="FindPEntityRefs" attribute="Other Text" lineEndContext="#stay">
<RegExpr attribute="EntityRef" context="#stay" String="&entref;" />
<RegExpr attribute="PEntityRef" context="#stay" String="%&name;;" />
<AnyChar attribute="Error" context="#stay" String="&%" />
</context>
<context name="FindAttributes" attribute="Other Text" lineEndContext="#stay">
<RegExpr attribute="Attribute" context="#stay" String="&name;" column="0"/>
<RegExpr attribute="Attribute" context="#stay" String="\s+&name;" />
<DetectChar attribute="Attribute" context="Value" char="=" />
</context>
<context name="FindDTDRules" attribute="Other Text" lineEndContext="#stay">
<RegExpr attribute="Doctype" context="Doctype Markupdecl" String="<!(ELEMENT|ENTITY|ATTLIST|NOTATION)\b" />
</context>
<context name="Comment" attribute="Comment" lineEndContext="#stay">
<DetectSpaces/>
<IncludeRules context="##Alerts" />
<DetectIdentifier/>
<StringDetect attribute="Comment" context="#pop" String="-->" endRegion="comment" />
<RegExpr attribute="Error" context="#stay" String="-(-(?!->))+" />
</context>
<context name="Commenttmpl" attribute="Commenttmpl" lineEndContext="#stay">
<DetectSpaces/>
<DetectIdentifier/>
<StringDetect attribute="Commenttmpl" context="#pop" String="%>" endRegion="commenttmpl" />
</context>
<context name="CDATA" attribute="Other Text" lineEndContext="#stay">
<DetectSpaces/>
<DetectIdentifier/>
<StringDetect attribute="CDATA" context="#pop" String="]]>" endRegion="cdata" />
<StringDetect attribute="EntityRef" context="#stay" String="]]>" />
</context>
<context name="PI" attribute="Other Text" lineEndContext="#stay">
<Detect2Chars attribute="Processing Instruction" context="#pop" char="?" char1=">" endRegion="pi" />
</context>
<context name="Doctype" attribute="Other Text" lineEndContext="#stay">
<DetectChar attribute="Doctype" context="#pop" char=">" endRegion="doctype" />
<DetectChar attribute="Doctype" context="Doctype Internal Subset" char="[" beginRegion="int_subset" />
</context>
<context name="Doctype Internal Subset" attribute="Other Text" lineEndContext="#stay">
<DetectChar attribute="Doctype" context="#pop" char="]" endRegion="int_subset" />
<IncludeRules context="FindDTDRules" />
<StringDetect attribute="Comment" context="Comment" String="<!--" beginRegion="comment" />
<RegExpr attribute="Processing Instruction" context="PI" String="<\?[\w:-]*" beginRegion="pi" />
<IncludeRules context="FindPEntityRefs" />
</context>
<context name="Doctype Markupdecl" attribute="Other Text" lineEndContext="#stay">
<DetectChar attribute="Doctype" context="#pop" char=">" />
<DetectChar attribute="Value" context="Doctype Markupdecl DQ" char=""" />
<DetectChar attribute="Value" context="Doctype Markupdecl SQ" char="'" />
</context>
<context name="Doctype Markupdecl DQ" attribute="Value" lineEndContext="#stay">
<DetectChar attribute="Value" context="#pop" char=""" />
<IncludeRules context="FindPEntityRefs" />
</context>
<context name="Doctype Markupdecl SQ" attribute="Value" lineEndContext="#stay">
<DetectChar attribute="Value" context="#pop" char="'" />
<IncludeRules context="FindPEntityRefs" />
</context>
<context name="El Open" attribute="Other Text" lineEndContext="#stay">
<Detect2Chars attribute="Element" context="#pop" char="/" char1=">" />
<DetectChar attribute="Element" context="#pop" char=">" />
<IncludeRules context="FindAttributes" />
<RegExpr attribute="Error" context="#stay" String="\S" />
</context>
<context name="El Close" attribute="Other Text" lineEndContext="#stay">
<DetectChar attribute="Element" context="#pop" char=">" />
<RegExpr attribute="Error" context="#stay" String="\S" />
</context>
<context name="El Close 2" attribute="Other Text" lineEndContext="#stay">
<DetectChar attribute="Element" context="#pop#pop#pop" char=">" />
<RegExpr attribute="Error" context="#stay" String="\S" />
</context>
<context name="El Close 3" attribute="Other Text" lineEndContext="#stay">
<DetectChar attribute="Element" context="#pop#pop#pop#pop" char=">" />
<RegExpr attribute="Error" context="#stay" String="\S" />
</context>
<context name="CSS" attribute="Other Text" lineEndContext="#stay">
<Detect2Chars attribute="Element" context="#pop" char="/" char1=">" endRegion="style" />
<DetectChar attribute="Element" context="CSS content" char=">" />
<IncludeRules context="FindAttributes" />
<RegExpr attribute="Error" context="#stay" String="\S" />
</context>
<context name="CSS content" attribute="Other Text" lineEndContext="#stay">
<RegExpr attribute="Element" context="El Close 2" String="</style\b" insensitive="TRUE" endRegion="style" />
<IncludeRules context="##CSS" includeAttrib="true"/>
</context>
<context name="JS" attribute="Other Text" lineEndContext="#stay">
<Detect2Chars attribute="Element" context="#pop" char="/" char1=">" endRegion="script" />
<DetectChar attribute="Element" context="JS content" char=">" />
<IncludeRules context="FindAttributes" />
<RegExpr attribute="Error" context="#stay" String="\S" />
</context>
<context name="JS content" attribute="Other Text" lineEndContext="#stay">
<RegExpr attribute="Element" context="El Close 2" String="</script\b" insensitive="TRUE" endRegion="script" />
<RegExpr attribute="Comment" context="JS comment close" String="//(?=.*</script\b)" insensitive="TRUE" />
<IncludeRules context="##JavaScript" includeAttrib="true"/>
</context>
<context name="JS comment close" attribute="Comment" lineEndContext="#pop">
<RegExpr attribute="Element" context="El Close 3" String="</script\b" insensitive="TRUE" endRegion="script" />
<IncludeRules context="##Alerts" />
</context>
<context name="Value" attribute="Other Text" lineEndContext="#stay" fallthrough="true" fallthroughContext="Value NQ">
<DetectChar attribute="Value" context="Value DQ" char=""" />
<DetectChar attribute="Value" context="Value SQ" char="'" />
<DetectSpaces />
</context>
<context name="Value NQ" attribute="Other Text" lineEndContext="#pop#pop" fallthrough="true" fallthroughContext="#pop#pop">
<IncludeRules context="FindEntityRefs" />
<RegExpr attribute="Value" context="#stay" String="/(?!>)" />
<RegExpr attribute="Value" context="#stay" String="[^/><"'\s]" />
</context>
<context name="Value DQ" attribute="Value" lineEndContext="#stay">
<DetectChar attribute="Value" context="#pop#pop" char=""" />
<IncludeRules context="FindEntityRefs" />
</context>
<context name="Value SQ" attribute="Value" lineEndContext="#stay">
<DetectChar attribute="Value" context="#pop#pop" char="'" />
<IncludeRules context="FindEntityRefs" />
</context>
</contexts>
<itemDatas>
<itemData name="Normal Text" defStyleNum="dsNormal" />
<itemData name="Other Text" defStyleNum="dsNormal" spellChecking="false" />
<itemData name="Comment" defStyleNum="dsComment" />
<itemData name="Commenttmpl" defStyleNum="dsComment" color="#66f" />
<itemData name="CDATA" defStyleNum="dsBaseN" bold="1" spellChecking="false" />
<itemData name="Processing Instruction" defStyleNum="dsKeyword" spellChecking="false" />
<itemData name="Doctype" defStyleNum="dsDataType" bold="1" spellChecking="false" />
<itemData name="Element" defStyleNum="dsKeyword" spellChecking="false" />
<itemData name="Attribute" defStyleNum="dsOthers" spellChecking="false" />
<itemData name="Value" defStyleNum="dsString" color="#a00" spellChecking="false" />
<itemData name="EntityRef" defStyleNum="dsDecVal" spellChecking="false" />
<itemData name="PEntityRef" defStyleNum="dsDecVal" spellChecking="false" />
<itemData name="Error" defStyleNum="dsError" spellChecking="false" />
</itemDatas>
</highlighting>
<general>
<comments>
<comment name="multiLine" start="<!--" end="-->" />
</comments>
</general>
</language>
Теперь приступим
В зависимости построения следует добавить:
LIBS += -L/usr/local/lib/ -lbooster -lcppcms
INCLUDEPATH += /usr/local/include
DEPENDPATH += /usr/local/include
Создаем файл main.cpp и наполняем его следующим содержимым:
#include <cppcms/applications_pool.h>
#include <cppcms/url_dispatcher.h>
#include <cppcms/http_response.h>
#include <cppcms/application.h>
#include <cppcms/url_mapper.h>
#include <cppcms/service.h>
//-------------------------------------------------------------------------------------
// Dsc: Наш класс отрисовки страниц, при запросе некоторого адреса пользователем
// В первую очередь он попадет сюда
//-------------------------------------------------------------------------------------
class WebSite : public cppcms::application{
public:
//-------------------------------------------------------------------------------------
// Dsc: Конструктор, который будет запушен во время старта программы
//-------------------------------------------------------------------------------------
WebSite(cppcms::service &s) : cppcms::application(s)
{}
//-------------------------------------------------------------------------------------
// Dsc: Функция в которую мы попадем, если иного не указано в конструкторе
// ( об этом позже )
//-------------------------------------------------------------------------------------
virtual void main(std::string path)
{
response().out() << "Hello!";
}
};
//-------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------
int main(int argc,char **argv)
{
try {
// создаем сервис
cppcms::service srv(argc,argv);
// задаем корень
srv.applications_pool().mount(cppcms::applications_factory<WebSite>());
// запускаем
srv.run();
}
catch(std::exception const &e) {
std::cerr << "Failed: " << e.what() << std::endl;
std::cerr << booster::trace(e) << std::endl;
return 1;
}
return 0;
}
Если вы уже попытались это запустить, то скорее всего ничего не получилось, так как для полноценной работы не хватает конфигурационного файла для сервера, его местоположение нужно передать бинарнику при запуске
WebApp.bin -c config.json
Конфиг может быть следующего содержания:
{
"WebSite" : {
"root" : "",
"host" : "localhost:8080",
"locdomain" : "localhost",
},
"service" : {
"ip" : "0.0.0.0",
"api" : "http",
"port" : 8080
},
"http" : {
"script" : "/mb.fcgi" ,
"rewrite" : [
{ "regex" : ".*" , "pattern" : "/mb.fcgi$0" }
],
}
}
Этого должно быть достаточно.
Так же хорошим вариантом будет прописать параметра запуска в «конфиги среды программирования»
Итак, дописываем и запускаем и открываем.
Впечатляет? Нет?
Оно и верно, так как для данного примера мы не использовали механизм шаблонов, а ограничились банальным выводом строки. Однако пример позволяет нам убедиться что все работает.
Использование шаблонов
Давайте напишем первый шаблон, который будет «превращен» шаблонизатором библиотеки в файл *.cpp.
Итак, первым делом добавим заголовочный файл, содержащий структуру динамических данных ( данных шаблона ). По умолчанию будут помещаются в папку data внутри проекта.
data/tmpl_master.h
#ifndef TMPL_MASTER_H
#define TMPL_MASTER_H
#include <cppcms/view.h>
namespace Data {
//-------------------------------------------------------------------------------------
// Dsc: Структура основной информации о странице
//-------------------------------------------------------------------------------------
struct infoPage {
std::string title; // титул страницы
std::string description; // описание страницы
std::string keywords; // ключевые слова страницы
std::map<std::string,std::string> menuList; // список выводимых пунктов меню (url,desc)
//-------------------------------------------------------------------------------------
// Dsc: Конструктор, инициализирующий переменные
//-------------------------------------------------------------------------------------
infoPage() :
title (""),
description(""),
keywords (""),
menuList ( )
{}
//-------------------------------------------------------------------------------------
// Dsc: Деструктор, ничего не делающий
//-------------------------------------------------------------------------------------
~infoPage(){}
};
//-------------------------------------------------------------------------------------
// Dsc: Базовый контент который есть на каждой странице
//-------------------------------------------------------------------------------------
struct Master :public cppcms::base_content {
infoPage page;
//-------------------------------------------------------------------------------------
// Dsc: Конструктор страницы
//-------------------------------------------------------------------------------------
Master() :
page()
{}
//-------------------------------------------------------------------------------------
// Dsc: Ленивый деструктор
//-------------------------------------------------------------------------------------
~Master(){}
};
}
#endif
Содержимое данного файла как правило не отличается каким-либо умным кодом и по сути это всего лишь контейнеры для описания переменных, используемых в шаблонах.
Приступим к написанию собственно шаблона, создадим папку templates, в ней файл master.tmpl, следующего содержания:
<% c++ #include "data/tmpl_master.h" %>
<% skin defskin %>
<% view Master uses Data::Master %>
<% template page_main() %>MAIN TEMPLATE<% end %>
<% template page_footer() %>Все права защищены<% end %>
<% template page_left_sidebar() %>Левая панелька<% end %>
<% template render() %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title><%= page.title %></title>
<meta name="keywords" content="<%= page.keywords %>" />
<meta name="description" content="<%= page.description %>" />
<link href="/media/css/style.css" rel="stylesheet">
</head>
<body>
<div class="wrapper">
<header class="header">
<div class="nav" >
<% foreach menuItem in page.menuList %>
<ul>
<% item %>
<li><a href="<%= menuItem.first %>"><%= menuItem.second %></a></li>
<% end %>
</ul>
<% end %>
</div>
</header>
<div class="middle">
<div class="container">
<main class="content"><% include page_main() %></main>
</div>
<aside class="left-sidebar">
<div>
<% include page_left_sidebar() %>
</div>
</aside>
</div>
</div>
<footer class="footer"><% include page_footer() %></footer>
</body>
</html>
<% end template %>
<% end view %>
<% end skin %>
Что же тут понаписано?
В самой первой строчке
<% c++ #include "data/tmpl_master.h" %>
мы пишем заголовочный файл, в котором будут объявлены наши структуры данных.Строка
<% skin defskin %>
определяет название текущего скина, то есть у вас могут быть разные отображения для страниц сайта.Строка
<% view Master uses Data::Master %>
определяет название текущего шаблона как «Master» ( В последствии мы будем его указывать для механизма наполнения страниц ), а так же создает структуру Data::Master внутри класса-обертки. Что в переводе на с++ будет выглядеть как «Data::Master context;»( если вас интересуют подробности — то всегда можно посмотреть сгенерированный фал )Строки
<% template page_main() %>MAIN TEMPLATE<% end %>
<% template page_footer() %>Все права защищены<% end %>
<% template page_left_sidebar() %>Левая панелька<% end %>
определяют дефолтные значения, которые будут выведены пользователю в случае если мы их не переопределим( то есть являются
virtual const char* page_main(){ return "MAIN TEMPLATE"; }
, так наверное понятнее. ).Давайте попробуем собрать. Ясное дело что компилятор с++ не проглотит файл tmpl. Поэтому на помощь должна прийти утилита, собравшаяся вместе с библиотекой, которая переработает шаблон до нужного состояния.
Для этого создадим внутри проекта файл «make_templates.sh», внутрь которого поместим нужные нам операции( Данный файл легко можно заменить или ручным вызовом данной утилиты или прописанием ее в «исполняемую часть» вашей среды ):
#!/bin/bash
INPUT=""
OUTPUT=""
while getopts ":i:o:" opt; do
case $opt in
i)
INPUT=$OPTARG
;;
o)
OUTPUT=$OPTARG
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
# копируем файлик конфигурации в папку билда
cp $INPUT/config.json $OUTPUT
# сюда пишем все шаблоны
TEMPLATES="$INPUT/templates/master.tmpl"
# прожевываем шаблоны в срр-шник
cppcms_tmpl_cc $TEMPLATES -o $INPUT/all_tmpl.cpp
# собираем шаблоны в библиотеку
g++ -shared -fPIC $INPUT/all_tmpl.cpp -o $OUTPUT/libcpp_defskin.so -lcppcms -lbooster
Теперь в настройках проекта QtCreator необходимо добавить «пользовательский шаг»
Команда: "./make_templates.sh"
Рабочая директория: "%{sourceDir}"
Аргументы команды: "-i %{sourceDir} -o %{buildDir}"
Не забудьте добавить файлу «исполняемость».
Если сборка прошла удачно, то в директории построения появится кроме прочего библиотека libcpp_defskin.so.
Важно отметить что собрать библиотеку можно статически или динамически. У меня сделано вторым способом, крайне вам не советую использовать первый, так как файлы TMPL приходится менять частенько, а перекомпилировать из-за этого весь проект — весьма неблагодарное занятие.
Так же что бы шаблоны были привязаны к проекту — необходимо дополнить файл config.json
{
"WebSite" : {
"root" : "",
"host" : "localhost:8080",
"locdomain" : "localhost",
},
"service" : {
"ip" : "0.0.0.0",
"api" : "http",
"port" : 8080
},
"http" : {
"script" : "/mb.fcgi" ,
"rewrite" : [
{ "regex" : "/media(/.+)", "pattern" : "$1" },
{ "regex" : ".*" , "pattern" : "/mb.fcgi$0" }
],
},
"views" : {
"default_skin" : "defskin" ,
"paths" : [ "./" ],
"skins" : [ "cpp_defskin" ],
},
}
И внести соответствующие изменения в main.cpp:
#include "data/tmpl_master.h"
...
WebSite::main(std::string path)
{
Data::Master tmpl;
tmpl.page.title = path;
tmpl.page.description = "description";
tmpl.page.keywords = "keywords";
tmpl.page.menuList.insert(std::pair<std::string,std::string>("/","MAIN"));
tmpl.page.menuList.insert(std::pair<std::string,std::string>("/else","ELSE"));
render("Master",tmpl);
}
Теперь при запуске проекта мы должны увидеть вывод шаблона. Но постойте, совсем забыл про css и изображения, сейчас исправлюсь.
Добавляем в config.json еще один пункт
"file_server" : {
"enable" : true,
"listing" : true,
"document_root" : "./media"
},
Здесь определенно нужно пояснить, что данным пунктом мы разрешаем бинарнику смотреть в файловую систему. А правила по которым он это делает описаны в секции http
{ "regex" : "/media(/.+)", "pattern" : "$1" },
то есть любой запрос, начинающийся с /media/ следует перенаправлять «файловому серверу».
Создадим в папке проекта папку media, а так же добавим соответствующий пункт в make_templates.sh:
# копируем медиа данные в папку билда
cp -R $INPUT/media $OUTPUT
Внутри папки media ( в директории исходников проекта ) создадим подпапку css, а в ней файл style.css
Скрытый текст
/* Eric Meyer's CSS Reset */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
/* End of Eric Meyer's CSS Reset */
html {
height: 100%;
}
article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary {
display: block;
}
body {
font: 12px/18px Arial, sans-serif;
width: 100%;
height: 100%;
}
.wrapper {
width: 800px;
margin: 0 auto;
min-height: 100%;
height: auto !important;
height: 100%;
}
/* Header
-----------------------------------------------------------------------------*/
.header {
height: 50px;
background: #FFE680;
}
/* Middle
-----------------------------------------------------------------------------*/
.middle {
width: 100%;
padding: 0 0 50px;
position: relative;
}
.middle:after {
display: table;
clear: both;
content: '';
}
.container {
width: 100%;
float: left;
overflow: hidden;
}
.content {
padding: 0 270px 0 270px;
}
/* Left Sidebar
-----------------------------------------------------------------------------*/
.left-sidebar {
float: left;
width: 250px;
margin-left: -100%;
position: relative;
background: #B5E3FF;
}
/* Footer
-----------------------------------------------------------------------------*/
.footer {
width: 800px;
margin: -50px auto 0;
height: 50px;
background: #BFF08E;
position: relative;
}
Пробуем собрать еще раз.
Теперь, когда сайт похож на первую работу начинающего мастера — можно перейти к самому главному.
Наследование шаблонов
Механизм наследования шаблонов предельно прост. Определяем от какого шаблона наследуемся и дописываем переопределение функции вывода контента.
Создадим файл файл tmpl_news.h в папке data
#ifndef TMPL_NEWS_H
#define TMPL_NEWS_H
#include "tmpl_master.h"
namespace Data {
//-------------------------------------------------------------------------------------
// Dsc: Новостной контент
//-------------------------------------------------------------------------------------
struct News :public Master{
//-------------------------------------------------------------------------------------
// Dsc: Главная новость
//-------------------------------------------------------------------------------------
std::string mainNews;
//-------------------------------------------------------------------------------------
// Dsc: Конструктор страницы
//-------------------------------------------------------------------------------------
News() :
Master()
{}
//-------------------------------------------------------------------------------------
// Dsc: Ленивый деструктор
//-------------------------------------------------------------------------------------
~News(){}
};
}
#endif // TMPL_NEWS_H
Так же добавим файл news.tmpl в папку templates
<% c++ #include "data/tmpl_news.h" %>
<% skin defskin %>
<% view News uses Data::News extends Master %>
<% template page_main() %><%= mainNews %><% end %>
<% end view %>
<% end skin %>
Добавим путь до файлика в скрипт сборки ( должно выглядеть как-то так):
TEMPLATES="$INPUT/templates/master.tmpl"
TEMPLATES="$TEMPLATES $INPUT/templates/news.tmpl"
Изменяем файл main.cpp
#include <cppcms/applications_pool.h>
#include <cppcms/url_dispatcher.h>
#include <cppcms/http_response.h>
#include <cppcms/application.h>
#include <cppcms/url_mapper.h>
#include <cppcms/service.h>
#include "data/tmpl_master.h"
#include "data/tmpl_news.h"
//-------------------------------------------------------------------------------------
// Dsc: Наш класс отрисовки страниц, при запросе некоторого адреса пользователем
// В первую очередь он попадет сюда
//-------------------------------------------------------------------------------------
class WebSite : public cppcms::application{
public:
//-------------------------------------------------------------------------------------
// Dsc: Конструктор, который будет запушен во время старта программы
//-------------------------------------------------------------------------------------
WebSite(cppcms::service &s) : cppcms::application(s)
{
dispatcher().assign("/news(.*)",&WebSite::news,this,1);
mapper().assign("news","/news");
dispatcher().assign("(/?)",&WebSite::master,this,1);
mapper().assign("master","/");
}
//-------------------------------------------------------------------------------------
// Dsc: Функция в которую мы попадем, если иного не указано в конструкторе
// ( об этом позже )
//-------------------------------------------------------------------------------------
virtual void main(std::string path)
{
cppcms::application::main(path);
}
//-------------------------------------------------------------------------------------
// Dsc: Рендеринг базового контента
//-------------------------------------------------------------------------------------
virtual void master(std::string path)
{
Data::Master tmpl;
tmpl.page.title = path;
tmpl.page.description = "description";
tmpl.page.keywords = "keywords";
tmpl.page.menuList.insert(std::pair<std::string,std::string>("/","MASTER"));
tmpl.page.menuList.insert(std::pair<std::string,std::string>("/news","NEWS"));
render("Master",tmpl);
}
//-------------------------------------------------------------------------------------
// Dsc: Рендеринг новостей
//-------------------------------------------------------------------------------------
virtual void news(std::string path)
{
Data::News tmpl;
tmpl.page.title = path;
tmpl.page.description = "description";
tmpl.page.keywords = "keywords";
tmpl.page.menuList.insert(std::pair<std::string,std::string>("/","MASTER"));
tmpl.page.menuList.insert(std::pair<std::string,std::string>("/news","NEWS"));
tmpl.mainNews = "Сенсация! У нас на сайте ничего не произошло!";
render("News",tmpl);
}
};
//-------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------
int main(int argc,char **argv)
{
try {
// создаем сервис
cppcms::service srv(argc,argv);
// задаем корень
srv.applications_pool().mount(cppcms::applications_factory<WebSite>());
// запускаем
srv.run();
}
catch(std::exception const &e) {
std::cerr << "Failed: " << e.what() << std::endl;
std::cerr << booster::trace(e) << std::endl;
return 1;
}
return 0;
}
}
Основные изменения в файле произошли в конструкторе, где мы указали какая функция за вывод какой страницы отвечает.
Теперь эти страницы выводят различные шаблоны.
Особо важный момент — порядок подачи списка файлов шаблонизатору ( файлы-потомки должны следовать после родителей, иначе будут сыпаться ошибки ).
На этом я планирую закончить первую часть.