aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlns <matzeton@googlemail.com>2021-10-20 19:58:38 +0200
committerlns <matzeton@googlemail.com>2021-10-20 19:58:38 +0200
commit384724a00be9dbb8f40d295f99b9c6bcfb48b387 (patch)
tree6e053365bb995bf675397368281c0a3ff92bd9fc
parent7b01dd8e4b1779e4c4dd782dbec87acce0f4754b (diff)
Added MD4C support to render HTML from Markdown text.
* additional blog metadata properties Signed-off-by: lns <matzeton@googlemail.com>
-rw-r--r--CMakeLists.txt19
-rw-r--r--blog/my-second-blog-entry.json5
-rw-r--r--blog/my-second-blog-entry.md3
-rw-r--r--blog/my-third-blog-entry.json8
-rw-r--r--blog/my-third-blog-entry.md2
-rw-r--r--blog/my-very-first-blog-entry.json5
-rw-r--r--src/EventManager.cpp6
-rw-r--r--src/EventManager.hpp1
-rw-r--r--src/Filesystem.cpp14
-rw-r--r--src/Filesystem.hpp3
-rw-r--r--src/TemplateManager.cpp63
-rw-r--r--src/content/blog/Blog.cpp54
-rw-r--r--src/content/blog/Blog.hpp4
-rw-r--r--src/content/markdown/Markdown.cpp32
-rw-r--r--src/main.cpp57
-rw-r--r--wwwroot/index.html21
16 files changed, 182 insertions, 115 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b107660..1de9b72 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6,14 +6,27 @@ set(CMAKE_CXX_FLAGS_DEBUG "-g3 -fpic -fno-omit-frame-pointer -fno-rtti -ffunctio
set(CMAKE_CXX_FLAGS_RELEASE "-Os -fpic -fomit-frame-pointer -ffunction-sections -fdata-sections -flto")
set(CMAKE_EXE_LINKER_FLAGS "-Wl,-gc-sections")
-set(INJA_SRCDIR "${PROJECT_SOURCE_DIR}/deps/inja" CACHE STRING "Path to the inja source directory.")
+set(INJA_SRCDIR "${PROJECT_SOURCE_DIR}/deps/inja" CACHE STRING
+ "Path to the inja source directory.")
if(NOT EXISTS "${INJA_SRCDIR}/single_include/inja/inja.hpp")
message(FATAL_ERROR "inja missing")
endif()
-include_directories(${CPP_HTTPLIB_SRCDIR} ${INJA_SRCDIR}/single_include ${INJA_SRCDIR}/third_party/include)
+set(MD4C_SRCDIR "${PROJECT_SOURCE_DIR}/deps/md4c/src" CACHE STRING
+ "Path to the md4c source directory.")
+if(NOT EXISTS "${MD4C_SRCDIR}/md4c-html.h")
+ message(FATAL_ERROR "md4c missing")
+endif()
+
+include_directories(${CPP_HTTPLIB_SRCDIR} ${INJA_SRCDIR}/single_include
+ ${INJA_SRCDIR}/third_party/include
+ ${MD4C_SRCDIR})
+set(MD4C_SOURCES "${MD4C_SRCDIR}/entity.c"
+ "${MD4C_SRCDIR}/md4c.c"
+ "${MD4C_SRCDIR}/md4c-html.c")
+
file(GLOB_RECURSE SOURCES "src/*.cpp")
-add_executable(cpp-web ${SOURCES})
+add_executable(cpp-web ${MD4C_SOURCES} ${SOURCES})
#target_compile_definitions(cpp-web PUBLIC CPPHTTPLIB_THREAD_POOL_COUNT=4)
#target_compile_options(cpp-web PUBLIC "-std=c++14")
if(CMAKE_BUILD_TYPE MATCHES Release)
diff --git a/blog/my-second-blog-entry.json b/blog/my-second-blog-entry.json
index b8c2ec0..ecb2b8b 100644
--- a/blog/my-second-blog-entry.json
+++ b/blog/my-second-blog-entry.json
@@ -1,5 +1,8 @@
{
+ "title": "Second blog entry.",
+ "tags": [ "bla" ],
+ "author": "lns",
"createDate": "23.02.1989 23:59",
"publishDate": "30.12.2020 1:00",
- "published": false
+ "published": true
}
diff --git a/blog/my-second-blog-entry.md b/blog/my-second-blog-entry.md
index 292b1e1..054046a 100644
--- a/blog/my-second-blog-entry.md
+++ b/blog/my-second-blog-entry.md
@@ -1,2 +1,5 @@
Text
=======
+
+*lorem* **ipsum**
+~~striked~~
diff --git a/blog/my-third-blog-entry.json b/blog/my-third-blog-entry.json
new file mode 100644
index 0000000..9682cd2
--- /dev/null
+++ b/blog/my-third-blog-entry.json
@@ -0,0 +1,8 @@
+{
+ "title": "Third.",
+ "tags": [ "test" ],
+ "author": "lns",
+ "createDate": "24.02.1989 23:59",
+ "publishDate": "31.12.2020 1:00",
+ "published": false
+}
diff --git a/blog/my-third-blog-entry.md b/blog/my-third-blog-entry.md
new file mode 100644
index 0000000..292b1e1
--- /dev/null
+++ b/blog/my-third-blog-entry.md
@@ -0,0 +1,2 @@
+Text
+=======
diff --git a/blog/my-very-first-blog-entry.json b/blog/my-very-first-blog-entry.json
index dec2711..52f52f7 100644
--- a/blog/my-very-first-blog-entry.json
+++ b/blog/my-very-first-blog-entry.json
@@ -1,5 +1,8 @@
{
+ "title": "My first blog entry!",
+ "tags": [ "test", "bla" ],
+ "author": "lns",
"createDate": "22.02.1989 23:59",
"publishDate": "31.12.2020 1:00",
- "published": false
+ "published": true
}
diff --git a/src/EventManager.cpp b/src/EventManager.cpp
index 6c735a8..d45dcee 100644
--- a/src/EventManager.cpp
+++ b/src/EventManager.cpp
@@ -189,13 +189,15 @@ bool EventManager::Init(std::string host, uint16_t port)
}
evhttp_set_gencb(m_EvHttp, EvGenericInterceptor, &m_DefaultCallback);
- m_EvSocket = evhttp_bind_socket_with_handle(m_EvHttp, host.c_str(), port);
- if (m_EvSocket == nullptr)
+ if (evhttp_bind_socket(m_EvHttp, host.c_str(), port) != 0)
{
fprintf(stderr, "couldn't bind to %s:%d. Exiting.\n", host.c_str(), port);
return false;
}
+ evhttp_set_allowed_methods(m_EvHttp, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_HEAD);
+ evhttp_set_default_content_type(m_EvHttp, NULL);
+
m_EvTermEvent = evsignal_new(m_EvBase, SIGINT, do_term, m_EvBase);
if (m_EvTermEvent == nullptr)
{
diff --git a/src/EventManager.hpp b/src/EventManager.hpp
index 5d74602..f994510 100644
--- a/src/EventManager.hpp
+++ b/src/EventManager.hpp
@@ -42,7 +42,6 @@ private:
struct event_config * m_EvConfig = nullptr;
struct event_base * m_EvBase = nullptr;
struct evhttp * m_EvHttp = nullptr;
- struct evhttp_bound_socket * m_EvSocket = nullptr;
struct evconnlistener * m_EvListener = nullptr;
struct event * m_EvTermEvent = nullptr;
};
diff --git a/src/Filesystem.cpp b/src/Filesystem.cpp
index 04dbb1b..9fe13bb 100644
--- a/src/Filesystem.cpp
+++ b/src/Filesystem.cpp
@@ -72,8 +72,18 @@ bool Filesystem::AddSingleFile(std::string path, std::string root)
std::cout << "Adding file: " << path << " (" << size << " bytes) as " << relpath << " to " << this << std::endl;
}
- fd.mime = magic_file(m_Magic, path.c_str());
- m_Files[relpath] = fd;
+ {
+ char const * const mp = magic_file(m_Magic, path.c_str());
+ if (mp != NULL)
+ {
+ fd.mime = magic_file(m_Magic, path.c_str());
+ }
+ else
+ {
+ fd.mime = "application/octet-stream";
+ }
+ m_Files[relpath] = fd;
+ }
return true;
}
diff --git a/src/Filesystem.hpp b/src/Filesystem.hpp
index 02150fb..98fbdcd 100644
--- a/src/Filesystem.hpp
+++ b/src/Filesystem.hpp
@@ -9,11 +9,12 @@
#include <vector>
using FilesDict = std::unordered_map<std::string, struct file_data>;
+using Data = std::vector<unsigned char>;
struct file_data
{
std::string mime;
- std::vector<unsigned char> data;
+ Data data;
};
class Filesystem
diff --git a/src/TemplateManager.cpp b/src/TemplateManager.cpp
index ddde2c4..1e027f2 100644
--- a/src/TemplateManager.cpp
+++ b/src/TemplateManager.cpp
@@ -4,36 +4,45 @@
TemplateManager::TemplateManager()
{
- AddInjaCallback("test_fn", 0, [](inja::Arguments & args) {
- (void)args;
- return "Just a test fn.";
- });
- AddInjaCallback("test_return_true", 0, [](inja::Arguments & args) {
- (void)args;
- return true;
- });
+ AddInjaCallback("test_fn",
+ 0,
+ [](inja::Arguments & args)
+ {
+ (void)args;
+ return "Just a test fn.";
+ });
+ AddInjaCallback("test_return_true",
+ 0,
+ [](inja::Arguments & args)
+ {
+ (void)args;
+ return true;
+ });
/*
* indent(input: str, width: int, first: bool, blank: bool);
*/
- AddInjaCallback("indent", 4, [](inja::Arguments & args) {
- std::stringstream stream(args.at(0)->get<std::string>());
- std::string line, out;
- bool is_first_line = true;
- while (std::getline(stream, line))
- {
- if (is_first_line == false || args.at(2)->get<bool>() == true)
- {
- if (line != "" || args.at(3)->get<bool>() == false)
- {
- line.insert(0, args.at(1)->get<std::size_t>(), ' ');
- }
- }
- line += '\n';
- out += line;
- is_first_line = false;
- }
- return out;
- });
+ AddInjaCallback("indent",
+ 4,
+ [](inja::Arguments & args)
+ {
+ std::stringstream stream(args.at(0)->get<std::string>());
+ std::string line, out;
+ bool is_first_line = true;
+ while (std::getline(stream, line))
+ {
+ if (is_first_line == false || args.at(2)->get<bool>() == true)
+ {
+ if (line != "" || args.at(3)->get<bool>() == false)
+ {
+ line.insert(0, args.at(1)->get<std::size_t>(), ' ');
+ }
+ }
+ line += '\n';
+ out += line;
+ is_first_line = false;
+ }
+ return out;
+ });
}
void TemplateManager::ParseTemplates(Filesystem & fs)
diff --git a/src/content/blog/Blog.cpp b/src/content/blog/Blog.cpp
index d2a66c8..ba8638e 100644
--- a/src/content/blog/Blog.cpp
+++ b/src/content/blog/Blog.cpp
@@ -41,9 +41,9 @@ bool Blog::Init()
m_Redirections.push_back(std::filesystem::path(jfile.first).stem());
}
- std::sort(m_BlogEntriesSortedByDate.begin(), m_BlogEntriesSortedByDate.end(), [](auto const & a, auto const & b) {
- return a->publishDate > b->publishDate;
- });
+ std::sort(m_BlogEntriesSortedByDate.begin(),
+ m_BlogEntriesSortedByDate.end(),
+ [](auto const & a, auto const & b) { return a->publishDate > b->publishDate; });
m_BlogContents.Init();
@@ -100,17 +100,19 @@ bool Blog::ValidateAndSetMetdadata(BlogMetadata const & blogMetadata, BlogEntry
{
bool retval = true;
std::function<bool(BlogMetadata const &, std::string const)> validateMetadata =
- [blogEntry](BlogMetadata const & bm, std::string const tname) {
- if (bm.find(tname) == bm.cend())
- {
- std::cerr << "Metadata validation: JSON key '" << tname << "' missing in "
- << blogEntry->metadata_filename << std::endl;
- return false;
- }
- return true;
- };
- std::function<bool(std::string const &, std::time_t &)> parseDateTime = [](std::string const & timeStr,
- std::time_t & time) {
+ [blogEntry](BlogMetadata const & bm, std::string const tname)
+ {
+ if (bm.find(tname) == bm.cend())
+ {
+ std::cerr << "Metadata validation: JSON key '" << tname << "' missing in " << blogEntry->metadata_filename
+ << std::endl;
+ return false;
+ }
+ return true;
+ };
+ std::function<bool(std::string const &, std::time_t &)> parseDateTime =
+ [](std::string const & timeStr, std::time_t & time)
+ {
std::tm tm = {};
std::stringstream ss(timeStr);
ss >> std::get_time(&tm, "%d.%m.%y %H:%M");
@@ -124,6 +126,27 @@ bool Blog::ValidateAndSetMetdadata(BlogMetadata const & blogMetadata, BlogEntry
return true;
};
+ if (validateMetadata(blogMetadata, "title") == false)
+ {
+ retval = false;
+ }
+ blogEntry->title = blogMetadata["title"];
+
+ if (validateMetadata(blogMetadata, "tags") == false)
+ {
+ retval = false;
+ }
+ for (auto const & tag : blogMetadata["tags"])
+ {
+ blogEntry->tags.push_back(tag);
+ }
+
+ if (validateMetadata(blogMetadata, "author") == false)
+ {
+ retval = false;
+ }
+ blogEntry->author = blogMetadata["author"];
+
if (validateMetadata(blogMetadata, "createDate") == false ||
parseDateTime(blogMetadata["createDate"], blogEntry->createDate) == false)
{
@@ -184,6 +207,9 @@ void Blog::GenerateBlogListing(RenderData & rd)
RenderData re;
re["metadata_filename"] = e->metadata_filename;
re["content_filename"] = e->content_filename;
+ re["title"] = e->title;
+ re["tags"] = e->tags;
+ re["author"] = e->author;
re["createDate"] = e->createDate;
re["publishDate"] = e->publishDate;
re["published"] = e->published;
diff --git a/src/content/blog/Blog.hpp b/src/content/blog/Blog.hpp
index f577d69..d99185a 100644
--- a/src/content/blog/Blog.hpp
+++ b/src/content/blog/Blog.hpp
@@ -16,6 +16,10 @@ struct blog_entry
std::string const metadata_filename;
std::string const content_filename;
+
+ std::string title;
+ std::vector<std::string> tags;
+ std::string author;
std::time_t createDate;
std::time_t publishDate;
bool published;
diff --git a/src/content/markdown/Markdown.cpp b/src/content/markdown/Markdown.cpp
index 63358b4..a85f333 100644
--- a/src/content/markdown/Markdown.cpp
+++ b/src/content/markdown/Markdown.cpp
@@ -1,5 +1,7 @@
#include "Markdown.hpp"
+#include <md4c-html.h>
+
Markdown::Markdown(std::string uriBasePath, std::string markdownFilesPath, std::string mainTemplatePath)
: Content(),
m_UriBasePath(uriBasePath),
@@ -8,6 +10,13 @@ Markdown::Markdown(std::string uriBasePath, std::string markdownFilesPath, std::
{
}
+extern "C" void markdown_to_html_conversion(const MD_CHAR * const text, MD_SIZE size, void * const userdata)
+{
+ std::string * html = (std::string *)userdata;
+
+ html->append(text, size);
+}
+
bool Markdown::Init()
{
std::cout << "Markdown files path: " << m_MarkdownFilesPath << std::endl;
@@ -22,8 +31,27 @@ bool Markdown::Init()
for (auto const & mfile : fs.GetFiles())
{
- m_Markdowns[mfile.first] =
- std::make_shared<std::string>(std::string(mfile.second.data.begin(), mfile.second.data.end()));
+ Data const & data = mfile.second.data;
+ std::string html;
+
+ html.reserve(data.size() / 8 + 64);
+ int ret = md_html((MD_CHAR const *)data.data(),
+ data.size(),
+ markdown_to_html_conversion,
+ &html,
+ MD_DIALECT_GITHUB,
+ MD_FLAG_COLLAPSEWHITESPACE | MD_FLAG_PERMISSIVEURLAUTOLINKS | MD_FLAG_PERMISSIVEWWWAUTOLINKS |
+ MD_FLAG_PERMISSIVEEMAILAUTOLINKS | MD_FLAG_PERMISSIVEAUTOLINKS | MD_FLAG_TABLES |
+ MD_FLAG_STRIKETHROUGH | MD_FLAG_LATEXMATHSPANS | MD_FLAG_WIKILINKS | MD_FLAG_TASKLISTS |
+ MD_FLAG_UNDERLINE);
+
+ if (ret != 0)
+ {
+ std::cerr << "Markdown HTML rendering failed." << std::endl;
+ return false;
+ }
+
+ m_Markdowns[mfile.first] = std::make_shared<std::string>(html);
}
return true;
diff --git a/src/main.cpp b/src/main.cpp
index 50f0ec5..4969d70 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -11,61 +11,6 @@
#include <inja/inja.hpp>
#include <iostream>
-static void example_inja_render(struct evhttp_request * const req, EvUserData ud)
-{
- (void)ud;
-
- inja::Environment env;
- nlohmann::json data;
- data["name"] = "Peter";
- data["city"] = "Brunswick";
- data["age"] = 29;
- data["names"] = {"Jeff", "Seb"};
- data["brother"]["name"] = "Chris";
- data["brother"]["daughters"] = {"Maria", "Helen"};
- data["brother"]["daughter0"] = {{"name", "Maria"}};
- data["is_happy"] = true;
- data["is_sad"] = false;
- data["relatives"]["mother"] = "Maria";
- data["relatives"]["brother"] = "Chris";
- data["relatives"]["sister"] = "Jenny";
- data["vars"] = {2, 3, 4, 0, -1, -2, -3};
-
- auto reply = env.render(
- "<html><body>\n"
- "Hello {{ name }}! I come from {{ city }}.<br>\n"
- "Hello {{ names.1 }}!<br>\n"
- "Hello {{ brother.name }}!<br>\n"
- "Hello {{ brother.daughter0.name }}!<br>\n"
- "{{ \"{{ no_value }}\" }}<br>\n"
- "Hello{# This is a comment #}!<br>\n"
- "{# --- #Todo --- #}<br>\n"
- "{% for name in names %}a{% endfor %}<br>\n"
- "Hello {% for name in names %}{{ name }} {% endfor %}!<br>\n"
- "Hello {% for name in names %}{{ loop.index }}: {{ name }}, {% "
- "endfor %}!<br>\n"
- "{% for type, name in relatives %}{{ type }}: {{ name }}, {% endfor "
- "%}<br>\n"
- "{% for v in vars %}{% if v > 0 %}+{% endif %}{% endfor %}<br>\n"
- "{% for name in names %}{{ loop.index }}: {{ name }}{% if not "
- "loop.is_last %}, {% endif %}{% endfor %}!<br>\n"
- "{% for name in names %}{{ loop.index }}: {{ name }}{% if "
- "loop.is_last == false %}, {% endif %}{% endfor %}!<br>\n"
- "{% for name in names %}a{% endfor %}<br>\n"
- "</body></html>\n",
- data);
-
- evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "text/html");
-
- struct evbuffer * const output = evbuffer_new();
- if (output != nullptr)
- {
- evbuffer_add(output, reply.c_str(), reply.size());
- evhttp_send_reply(req, 200, "OK", output);
- evbuffer_free(output);
- }
-}
-
int main(int argc, char ** argv)
{
char const * host = "127.0.0.1";
@@ -130,8 +75,6 @@ int main(int argc, char ** argv)
}
EventManager evmgr(ctmgr);
- // evmgr.SetDefaultCallback(example_inja_render, {});
- evmgr.AddCallback("/bla", example_inja_render, {});
evmgr.Init(host, port);
// ctmgr.ShutdownAll();
diff --git a/wwwroot/index.html b/wwwroot/index.html
index 8d3bbd4..554a952 100644
--- a/wwwroot/index.html
+++ b/wwwroot/index.html
@@ -2,13 +2,26 @@
<p>
<b>blabla</b>
Test fn....: <b>{{ test_fn }}</b><br>
-Test RetFN.: <b>{{ test_return_true }}</b><br>
+Test RetFN.: <b>{{ test_return_true }}</b><br><br><br>
{% if exists("blog_listing") and length(blog_listing) > 0 %}
+<table>
## for entry in blog_listing
- {{ loop.index1 }}: {{ entry.content_filename }} -> {{ entry.published }}<br>
- <b>{{ entry }}</b><br>
- {{ indent(entry.content, 8, false, false) }}<br>
+ <tr>
+ <td>{{ loop.index1 }}</td>
+ <td>{{ entry.content_filename }}</td>
+ <td>{{ entry.title }}</td>
+ <td>{{ entry.tags }}</td>
+ <td>{{ entry.author }}</td>
+ <td>{{ entry.createDate }}</td>
+ <td>{{ entry.publishDate }}</td>
+ <td>{{ entry.published }}</td>
+ <td>
+ {{ indent(entry.content, 12, false, false) }}
+ </td>
+ <td>{% if loop.is_last == false %}more{% else %}eof{% endif %}<br>
+ </tr>
## endfor
+</table>
{% endif %}
</p>
</body></html>