aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorToni Uhlig <matzeton@googlemail.com>2021-10-14 18:41:55 +0200
committerToni Uhlig <matzeton@googlemail.com>2021-10-14 18:41:55 +0200
commit51d9d23c99d77edad03e6425f3ab35a390156117 (patch)
tree82b632c2aac22c3d79f43e427f311a46591f30ca
parentf0f4b8a4d139a855ad15f9d79190edf6320eba51 (diff)
Blog Metadata parsing, validationa and templating.
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
-rw-r--r--CMakeLists.txt6
-rw-r--r--blog/my-very-first-blog-entry.json5
-rw-r--r--blog/my-very-first-blog-entry.md2
-rw-r--r--src/Content.hpp2
-rw-r--r--src/ContentManager.cpp17
-rw-r--r--src/TemplateManager.cpp11
-rw-r--r--src/content/blog/Blog.cpp138
-rw-r--r--src/content/blog/Blog.hpp28
-rw-r--r--src/content/markdown/Markdown.cpp47
-rw-r--r--src/content/markdown/Markdown.hpp11
-rw-r--r--src/content/static/Static.cpp12
-rw-r--r--src/content/static/Static.hpp1
-rw-r--r--wwwroot/index.html5
13 files changed, 249 insertions, 36 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ecc3b2d..b107660 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 3.1.9)
project(cpp-web)
set(CMAKE_CXX_FLAGS "-Wall -Wextra -std=c++17")
-set(CMAKE_CXX_FLAGS_DEBUG "-g3 -fpic -fomit-frame-pointer -flto -fno-rtti -ffunction-sections -fdata-sections -fsanitize=address -fsanitize=leak")
-set(CMAKE_CXX_FLAGS_RELEASE "-Os")
-set(CMAKE_EXE_LINKER_FLAGS "-flto")
+set(CMAKE_CXX_FLAGS_DEBUG "-g3 -fpic -fno-omit-frame-pointer -fno-rtti -ffunction-sections -fdata-sections -fsanitize=address -fsanitize=leak")
+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.")
if(NOT EXISTS "${INJA_SRCDIR}/single_include/inja/inja.hpp")
diff --git a/blog/my-very-first-blog-entry.json b/blog/my-very-first-blog-entry.json
new file mode 100644
index 0000000..dec2711
--- /dev/null
+++ b/blog/my-very-first-blog-entry.json
@@ -0,0 +1,5 @@
+{
+ "createDate": "22.02.1989 23:59",
+ "publishDate": "31.12.2020 1:00",
+ "published": false
+}
diff --git a/blog/my-very-first-blog-entry.md b/blog/my-very-first-blog-entry.md
new file mode 100644
index 0000000..292b1e1
--- /dev/null
+++ b/blog/my-very-first-blog-entry.md
@@ -0,0 +1,2 @@
+Text
+=======
diff --git a/src/Content.hpp b/src/Content.hpp
index 18c3d70..7539e9a 100644
--- a/src/Content.hpp
+++ b/src/Content.hpp
@@ -9,7 +9,7 @@
#include <vector>
using Redirections = std::vector<std::string>;
-using RenderData = std::unordered_map<std::string, std::string>;
+using RenderData = inja::json;
class Content
{
diff --git a/src/ContentManager.cpp b/src/ContentManager.cpp
index 8bff717..a110e05 100644
--- a/src/ContentManager.cpp
+++ b/src/ContentManager.cpp
@@ -8,15 +8,14 @@ void ContentManager::SetTemplateSystem(std::shared_ptr<TemplateManager> const &
bool ContentManager::RegisterModule(std::shared_ptr<Content> ctnt)
{
std::string const & basePath = ctnt->GetUriBasePath();
- Redirections const & rs = ctnt->GetRedirections();
- m_ContentModules[basePath] = ctnt;
- for (auto & redirect : rs)
+ if (basePath.empty() == true)
{
- m_ContentModulesRoutes[redirect] = ctnt;
+ return false;
}
+ m_ContentModules[basePath] = ctnt;
- return false;
+ return true;
}
bool ContentManager::InitAll(void)
@@ -29,6 +28,12 @@ bool ContentManager::InitAll(void)
{
ret = false;
}
+
+ Redirections const & rs = content.second->GetRedirections();
+ for (auto & redirect : rs)
+ {
+ m_ContentModulesRoutes[redirect] = content.second;
+ }
}
return ret;
@@ -76,10 +81,10 @@ bool ContentManager::Render(char const * basePath, RequestResponse & rr, std::st
cntm = m_ContentModules[basePath];
}
- RenderData rd;
auto & main = cntm->GetMainTemplate();
out.clear();
+ RenderData rd;
if (cntm->Render(rr, rd, out) == false)
{
return false;
diff --git a/src/TemplateManager.cpp b/src/TemplateManager.cpp
index a751116..99af2ad 100644
--- a/src/TemplateManager.cpp
+++ b/src/TemplateManager.cpp
@@ -50,8 +50,15 @@ bool TemplateManager::RenderTemplate(std::string const & templatePath, RenderDat
return false;
}
- inja::json ij(rd);
- out = m_Inja.render(m_Templates[templatePath].content, ij);
+ try
+ {
+ out = m_Inja.render(m_Templates[templatePath].content, rd);
+ }
+ catch (inja::RenderError & re)
+ {
+ std::cerr << "Render Error: " << re.what() << std::endl;
+ return false;
+ }
return true;
}
diff --git a/src/content/blog/Blog.cpp b/src/content/blog/Blog.cpp
index b473bf3..1893cc5 100644
--- a/src/content/blog/Blog.cpp
+++ b/src/content/blog/Blog.cpp
@@ -1,11 +1,13 @@
#include "Blog.hpp"
+#include <filesystem>
+
Blog::Blog(std::string uriBasePath, std::string mainTemplatePath, std::string blogPath)
: Content(),
m_UriBasePath(uriBasePath),
m_MainTemplatePath(mainTemplatePath),
m_BlogPath(blogPath),
- m_BlogEntries("", blogPath)
+ m_BlogContents("", blogPath)
{
m_Redirections.push_back(uriBasePath + "/");
m_Redirections.push_back(uriBasePath + "/index.html");
@@ -13,25 +15,48 @@ Blog::Blog(std::string uriBasePath, std::string mainTemplatePath, std::string bl
bool Blog::Init()
{
- std::cout << "Blog entries path: " << m_BlogPath << std::endl;
+ bool retval = true;
+ std::cout << "Blog entries path: " << m_BlogPath << std::endl;
std::vector<std::string> extensions = {"json"};
- if (m_BlogEntriesMetadata.Scan(m_BlogPath, extensions, false) == false)
+ Filesystem fs;
+ if (fs.Scan(m_BlogPath, extensions, false) == false)
{
return false;
}
- m_BlogEntries.Init();
+ for (auto const & jfile : fs.GetFiles())
+ {
+ auto const json_metadata = inja::json::parse(jfile.second.data);
+ BlogEntry be =
+ std::make_shared<struct blog_entry>(jfile.first,
+ std::string(std::filesystem::path(jfile.first).stem()) + ".md");
+ if (Blog::ValidateAndSetMetdadata(json_metadata, be) == false)
+ {
+ std::cerr << "Blog Metadata validation failed." << std::endl;
+ retval = false;
+ }
+ m_BlogEntriesSortedByDate.push_back(be);
- return true;
+ m_Redirections.push_back(std::filesystem::path(jfile.first).stem());
+ }
+
+ m_BlogContents.Init();
+
+ if (retval == false)
+ {
+ return false;
+ }
+
+ return ValidateEntries();
}
void Blog::Shutdown()
{
std::cout << "Blog module shutdown" << std::endl;
- m_BlogEntries.Shutdown();
+ m_BlogContents.Shutdown();
}
bool Blog::Render(RequestResponse & rr, RenderData & rd, std::string & out)
@@ -40,7 +65,15 @@ bool Blog::Render(RequestResponse & rr, RenderData & rd, std::string & out)
(void)rd;
(void)out;
- rd["blah"] = "Yooooh!";
+ if (rr.GetUriPath() == m_UriBasePath || rr.GetUriPath() == m_UriBasePath + "/" ||
+ rr.GetUriPath() == m_UriBasePath + "/index.html")
+ {
+ GenerateBlogListing(rd["blog_listing"]);
+ }
+ else
+ {
+ rd["blog_content"] = "bla";
+ }
return true;
}
@@ -59,3 +92,94 @@ Redirections const & Blog::GetRedirections() const
{
return m_Redirections;
}
+
+bool Blog::ValidateAndSetMetdadata(BlogMetadata const & blogMetadata, BlogEntry & 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) {
+ std::tm tm = {};
+ std::stringstream ss(timeStr);
+ ss >> std::get_time(&tm, "%d.%m.%y %H:%M");
+ time = std::mktime(&tm);
+ if (time <= 0)
+ {
+ std::cerr << "Metadata validation: Invalid time string '" << timeStr
+ << "', format required: '%d.%m.%y %H:%M'" << std::endl;
+ return false;
+ }
+ return true;
+ };
+
+ if (validateMetadata(blogMetadata, "createDate") == false ||
+ parseDateTime(blogMetadata["createDate"], blogEntry->createDate) == false)
+ {
+ retval = false;
+ }
+
+ if (validateMetadata(blogMetadata, "publishDate") == false ||
+ parseDateTime(blogMetadata["publishDate"], blogEntry->publishDate) == false)
+ {
+ retval = false;
+ }
+
+ if (validateMetadata(blogMetadata, "published") == false)
+ {
+ retval = false;
+ }
+ blogEntry->published = blogMetadata["published"];
+
+ return retval;
+}
+
+bool Blog::ValidateEntries() const
+{
+ bool retval = true;
+
+ for (auto const & e : m_BlogEntriesSortedByDate)
+ {
+ if (m_BlogContents.HasMarkdownFile(e->content_filename) == false)
+ {
+ std::cerr << "Blog entry metadata " << e->metadata_filename << " exists, but markdown file "
+ << e->content_filename << " not." << std::endl;
+ retval = false;
+ }
+ }
+ for (auto const & m : m_BlogContents.GetMarkdowns())
+ {
+ if (std::any_of(m_BlogEntriesSortedByDate.cbegin(),
+ m_BlogEntriesSortedByDate.cend(),
+ [m](BlogEntry const & be) { return m.first == be->content_filename; }) == false)
+ {
+ std::cerr << "Blog entry markdown " << m.first << " exists, but metadata not." << std::endl;
+ retval = false;
+ }
+ }
+
+ return retval;
+}
+
+void Blog::GenerateBlogListing(RenderData & rd) const
+{
+ for (auto const & e : m_BlogEntriesSortedByDate)
+ {
+ RenderData re;
+ re["metadata_filename"] = e->metadata_filename;
+ re["content_filename"] = e->content_filename;
+ re["createDate"] = e->createDate;
+ re["publishDate"] = e->publishDate;
+ re["published"] = e->published;
+
+ rd += re;
+ }
+}
diff --git a/src/content/blog/Blog.hpp b/src/content/blog/Blog.hpp
index 970c7fd..a0e35b1 100644
--- a/src/content/blog/Blog.hpp
+++ b/src/content/blog/Blog.hpp
@@ -1,10 +1,30 @@
#ifndef BLOG_H
#define BLOG_H 1
+#include <inja/inja.hpp>
+
#include "../../Content.hpp"
#include "../../Filesystem.hpp"
#include "../markdown/Markdown.hpp"
+struct blog_entry
+{
+ explicit blog_entry(std::string const & metadata_filename, std::string const & content_filename)
+ : metadata_filename(metadata_filename), content_filename(content_filename)
+ {
+ }
+
+ std::string const metadata_filename;
+ std::string const content_filename;
+ std::time_t createDate;
+ std::time_t publishDate;
+ bool published;
+};
+
+using BlogMetadata = inja::json;
+using BlogEntry = std::shared_ptr<struct blog_entry>;
+using BlogEntries = std::vector<BlogEntry>;
+
class Blog : public Content
{
public:
@@ -18,13 +38,17 @@ public:
std::string const & GetMainTemplate() const;
Redirections const & GetRedirections() const;
+ static bool ValidateAndSetMetdadata(BlogMetadata const & blogMetadata, BlogEntry & blogEntry);
+ bool ValidateEntries() const;
+ void GenerateBlogListing(RenderData & rd) const;
+
private:
std::string m_UriBasePath;
std::string m_MainTemplatePath;
std::string m_BlogPath;
Redirections m_Redirections;
- Filesystem m_BlogEntriesMetadata;
- Markdown m_BlogEntries;
+ Markdown m_BlogContents;
+ BlogEntries m_BlogEntriesSortedByDate;
};
#endif
diff --git a/src/content/markdown/Markdown.cpp b/src/content/markdown/Markdown.cpp
index 2009772..63358b4 100644
--- a/src/content/markdown/Markdown.cpp
+++ b/src/content/markdown/Markdown.cpp
@@ -1,7 +1,10 @@
#include "Markdown.hpp"
-Markdown::Markdown(std::string uriBasePath, std::string markdownFilesPath)
- : Content(), m_UriBasePath(uriBasePath), m_MainTemplatePath(""), m_MarkdownFilesPath(markdownFilesPath)
+Markdown::Markdown(std::string uriBasePath, std::string markdownFilesPath, std::string mainTemplatePath)
+ : Content(),
+ m_UriBasePath(uriBasePath),
+ m_MainTemplatePath(mainTemplatePath),
+ m_MarkdownFilesPath(markdownFilesPath)
{
}
@@ -11,11 +14,18 @@ bool Markdown::Init()
std::vector<std::string> extensions = {"md"};
- if (m_MarkdownFiles.Scan(m_MarkdownFilesPath, extensions, false) == false)
+ Filesystem fs;
+ if (fs.Scan(m_MarkdownFilesPath, extensions, false) == false)
{
return false;
}
+ 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()));
+ }
+
return true;
}
@@ -26,13 +36,16 @@ void Markdown::Shutdown()
bool Markdown::Render(RequestResponse & rr, RenderData & rd, std::string & out)
{
+ (void)rr;
+ (void)rd;
(void)out;
- rd["blub"] = "Yoh21!";
- rr.UseOutputHeader();
- rr.AddOutputHeader("blaaaa", "blubb");
+ if (m_MainTemplatePath.empty() == true)
+ {
+ return false;
+ }
- return true;
+ return false; /* TODO: Make markdown module usable as standalone module?! */
}
std::string const & Markdown::GetUriBasePath() const
@@ -49,3 +62,23 @@ Redirections const & Markdown::GetRedirections() const
{
return m_Redirections;
}
+
+bool Markdown::HasMarkdownFile(std::string filePath) const
+{
+ return m_Markdowns.find(filePath) != m_Markdowns.end();
+}
+
+bool Markdown::HasMarkdownURI(std::string uriPath) const
+{
+ return HasMarkdownFile(uriPath.substr(m_UriBasePath.length() + 1, std::string::npos));
+}
+
+Markdowns const & Markdown::GetMarkdowns() const
+{
+ return m_Markdowns;
+}
+
+std::shared_ptr<std::string> const & Markdown::GetMarkdownHTML(std::string uriPath)
+{
+ return m_Markdowns[uriPath];
+}
diff --git a/src/content/markdown/Markdown.hpp b/src/content/markdown/Markdown.hpp
index 41c8b4d..9c4d3c4 100644
--- a/src/content/markdown/Markdown.hpp
+++ b/src/content/markdown/Markdown.hpp
@@ -4,10 +4,12 @@
#include "../../Content.hpp"
#include "../../Filesystem.hpp"
+using Markdowns = std::unordered_map<std::string, std::shared_ptr<std::string> >;
+
class Markdown : public Content
{
public:
- explicit Markdown(std::string uriBasePath, std::string markdownFilesPath);
+ explicit Markdown(std::string uriBasePath, std::string markdownFilesPath, std::string mainTemplatePath = "");
bool Init();
void Shutdown();
@@ -17,12 +19,17 @@ public:
std::string const & GetMainTemplate() const;
Redirections const & GetRedirections() const;
+ bool HasMarkdownFile(std::string filePath) const;
+ bool HasMarkdownURI(std::string uriPath) const;
+ Markdowns const & GetMarkdowns() const;
+ std::shared_ptr<std::string> const & GetMarkdownHTML(std::string uriPath);
+
private:
std::string m_UriBasePath;
std::string m_MainTemplatePath;
std::string m_MarkdownFilesPath;
Redirections m_Redirections;
- Filesystem m_MarkdownFiles;
+ Markdowns m_Markdowns;
};
#endif
diff --git a/src/content/static/Static.cpp b/src/content/static/Static.cpp
index b9977a9..132f0a0 100644
--- a/src/content/static/Static.cpp
+++ b/src/content/static/Static.cpp
@@ -6,7 +6,6 @@ Static::Static(std::string uriBasePath, std::shared_ptr<Filesystem> const & fs)
for (auto const & file : fs->GetFiles())
{
m_Redirections.push_back(uriBasePath + "/" + file.first);
- m_UriToFsMapping[uriBasePath + "/" + file.first] = file.first;
}
}
@@ -26,16 +25,21 @@ bool Static::Render(RequestResponse & rr, RenderData & rd, std::string & out)
{
(void)rd;
+ if (rr.GetUriPath() == m_UriBasePath)
+ {
+ return false;
+ }
+
rr.UseOutputHeader();
auto & files = m_StaticFiles->GetFiles();
- auto const & path = std::string(rr.GetUriPath());
+ auto const & path = std::string(rr.GetUriPath()).substr(m_UriBasePath.length() + 1, std::string::npos);
- if (rr.AddOutputHeader("Content-Type", files[m_UriToFsMapping[path]].mime) == false)
+ if (rr.AddOutputHeader("Content-Type", files[path].mime) == false)
{
return false;
}
- out = std::string(files[m_UriToFsMapping[path]].data.begin(), files[m_UriToFsMapping[path]].data.end());
+ out = std::string(files[path].data.begin(), files[path].data.end());
return true;
}
diff --git a/src/content/static/Static.hpp b/src/content/static/Static.hpp
index 8dcf410..b9de983 100644
--- a/src/content/static/Static.hpp
+++ b/src/content/static/Static.hpp
@@ -22,7 +22,6 @@ private:
std::string m_MainTemplatePath;
Redirections m_Redirections;
std::shared_ptr<Filesystem> const m_StaticFiles;
- std::unordered_map<std::string, std::string> m_UriToFsMapping;
};
#endif
diff --git a/wwwroot/index.html b/wwwroot/index.html
index 002ea72..b0a9fbf 100644
--- a/wwwroot/index.html
+++ b/wwwroot/index.html
@@ -3,6 +3,9 @@
<b>blabla</b>
Test fn....: <b>{{ test_fn }}</b><br>
Test RetFN.: <b>{{ test_return_true }}</b><br>
-Blog Render: <b>{{ blah }}</b><br>
+## for entry in blog_listing
+ {{ loop.index1 }}: {{ entry.content_filename }} -> {{ entry.published }}<br>
+ <b>{{ entry }}</b><br>
+## endfor
</p>
</body></html>