Initial site generation

This commit is contained in:
James Grogan 2022-12-05 17:50:49 +00:00
parent f44c79dc1f
commit fc44290e3f
35 changed files with 667 additions and 303 deletions

View file

@ -1,8 +1,11 @@
#include "ContentFile.h" #include "ContentFile.h"
#include "PathUtils.h" #include "PathUtils.h"
#include "StringUtils.h"
#include "MarkdownDocument.h" #include "MarkdownDocument.h"
#include "MarkdownElement.h" #include "MarkdownElement.h"
#include "MarkdownComponents.h"
ContentFile::ContentFile(const Path& filename) ContentFile::ContentFile(const Path& filename)
: mFilename(filename) : mFilename(filename)
@ -35,6 +38,23 @@ void ContentFile::load()
mContentBody = std::move(result.second); mContentBody = std::move(result.second);
} }
void ContentFile::doLinkTagSubstitution(const Path& basePath)
{
auto links = mContentBody->getAllLinks();
for (const auto link : links)
{
auto target = link->getTarget();
auto replaced_target = StringUtils::removeUpTo(target, "{filename}");
if (replaced_target != target)
{
auto full_path = mFilename.parent_path() / Path(replaced_target);
auto base_relative_path = PathUtils::getRelativePath(full_path, basePath);
auto output_path = PathUtils::getPathDelimited(base_relative_path);
link->setTarget(Path(output_path).replace_extension(".html"));
}
}
}
std::string ContentFile::getMetadataItem(const std::string& key) const std::string ContentFile::getMetadataItem(const std::string& key) const
{ {
const auto check = mMetadata.find(key); const auto check = mMetadata.find(key);
@ -50,5 +70,6 @@ std::string ContentFile::getMetadataItem(const std::string& key) const
void ContentFile::write(const Path& path) void ContentFile::write(const Path& path)
{ {
File file(path);
file.writeText(mProcessedOutput);
} }

View file

@ -32,7 +32,14 @@ public:
return mContentBody.get(); return mContentBody.get();
} }
void doLinkTagSubstitution(const Path& basePath);
void write(const Path& path); void write(const Path& path);
void setProcessedOutput(const std::string& output)
{
mProcessedOutput = output;
}
protected: protected:
Path mFilename; Path mFilename;
FileMetadata mMetadata; FileMetadata mMetadata;

View file

@ -6,9 +6,14 @@
#include "ContentPage.h" #include "ContentPage.h"
#include "TemplateFile.h" #include "TemplateFile.h"
#include "TemplatingEngine.h" #include "TemplatingEngine.h"
#include "TemplateSubstitutionContext.h"
#include "SiteGeneratorConfig.h" #include "SiteGeneratorConfig.h"
#include "PathUtils.h" #include "PathUtils.h"
#include "MarkdownConverter.h"
#include "HtmlElement.h"
#include "HtmlWriter.h"
#include "FileLogger.h" #include "FileLogger.h"
WebsiteGenerator::WebsiteGenerator() WebsiteGenerator::WebsiteGenerator()
@ -104,18 +109,35 @@ void WebsiteGenerator::parseTemplateFiles()
{ {
const auto template_path = mProjectPath / mConfig->getThemePath() / mConfig->getActiveTheme(); const auto template_path = mProjectPath / mConfig->getThemePath() / mConfig->getActiveTheme();
mTemplateEngine = std::make_unique<TemplatingEngine>(template_path); mTemplateEngine = std::make_unique<TemplatingEngine>(template_path);
mTemplateEngine->loadTemplateFiles();
} }
void WebsiteGenerator::doSubstitutions() void WebsiteGenerator::doSubstitutions()
{ {
auto article_template = mTemplateEngine->processTemplate("article"); MarkdownConverter converter;
for (auto& article : mArticles) for (auto& article : mArticles)
{ {
article->doLinkTagSubstitution(getArticlesPath());
auto containing_div = std::make_unique<HtmlDivElement>();
converter.convert(article->getContentBody(), containing_div.get());
HtmlWriter writer;
auto md_html_content = writer.toString(containing_div.get());
TemplateSubstitutionContext sub_context;
sub_context.addSubstitution("content", md_html_content);
auto content = mTemplateEngine->renderTemplate("article", &sub_context);
article->setProcessedOutput(content);
} }
} }
Path WebsiteGenerator::getOutputPath() const
{
return mProjectPath / "output";
}
void WebsiteGenerator::write() void WebsiteGenerator::write()
{ {
// Setup output dir // Setup output dir
@ -129,6 +151,7 @@ void WebsiteGenerator::write()
auto relative_path = PathUtils::getRelativePath(article_path, getArticlesPath()); auto relative_path = PathUtils::getRelativePath(article_path, getArticlesPath());
auto updated_filename = PathUtils::getPathDelimited(relative_path); auto updated_filename = PathUtils::getPathDelimited(relative_path);
auto final_path = output_dir / Path(updated_filename).replace_extension(".html"); auto final_path = output_dir / Path(updated_filename).replace_extension(".html");
article->write(final_path);
} }
} }

View file

@ -43,6 +43,8 @@ private:
Path getConfigPath() const; Path getConfigPath() const;
Path getOutputPath() const;
std::filesystem::path mProjectPath; std::filesystem::path mProjectPath;
std::unique_ptr<SiteGeneratorConfig> mConfig; std::unique_ptr<SiteGeneratorConfig> mConfig;

View file

@ -2,24 +2,27 @@ set(MODULE_NAME compiler)
list(APPEND TARGET_HEADERS list(APPEND TARGET_HEADERS
Lexer.h Lexer.h
TemplatingEngine.h template_engine/TemplatingEngine.h
TemplateFile.h template_engine/TemplateFile.h
TemplateNodes.h template_engine/TemplateNode.h
template_engine/TemplateElements.h
) )
list(APPEND TARGET_SOURCES list(APPEND TARGET_SOURCES
Lexer.cpp Lexer.cpp
TemplatingEngine.cpp template_engine/TemplatingEngine.cpp
TemplateFile.cpp template_engine/TemplateFile.cpp
TemplateNodes.cpp template_engine/TemplateNode.cpp
template_engine/TemplateElements.cpp
) )
add_library(${MODULE_NAME} SHARED ${TARGET_SOURCES} ${TARGET_HEADERS}) add_library(${MODULE_NAME} SHARED ${TARGET_SOURCES} ${TARGET_HEADERS})
target_include_directories(${MODULE_NAME} PUBLIC target_include_directories(${MODULE_NAME} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/template_engine
) )
target_link_libraries( ${MODULE_NAME} PUBLIC core) target_link_libraries( ${MODULE_NAME} PUBLIC core)
set_target_properties( ${MODULE_NAME} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON ) set_target_properties( ${MODULE_NAME} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON )
set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER src) set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER src)

View file

@ -1,124 +0,0 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
class TemplateNode
{
public:
TemplateNode(TemplateNode* parent);
virtual ~TemplateNode() = default;
TemplateNode* getParent() const;
virtual void addChild(std::unique_ptr<TemplateNode> child);
std::size_t getNumChildren() const;
virtual std::string getIdentifier() const;
TemplateNode* getChild(std::size_t index) const;
virtual std::string getRawContent() const;
template<typename T>
T* getFirstChildShallow(const std::string& identifier = {}) const
{
for (const auto& child : mChildren)
{
if (auto ret = dynamic_cast<T*>(child.get()))
{
if (!identifier.empty())
{
if (child->getIdentifier() == identifier)
{
return ret;
}
}
else
{
return ret;
}
}
}
return nullptr;
}
void setExtensionParent(TemplateNode* parent);
virtual std::string render(TemplateNode* parentContext = nullptr);
protected:
std::vector<std::unique_ptr<TemplateNode> > mChildren;
TemplateNode* mParent{ nullptr };
TemplateNode* mExtensionParent{ nullptr };
};
using TemplateNodePtr = std::unique_ptr<TemplateNode>;
class TemplateExtends : public TemplateNode
{
public:
TemplateExtends(TemplateNode* parent, const std::string& path);
virtual ~TemplateExtends() = default;
std::string getRawContent() const override;
std::string getPath() const;
private:
std::string mPath;
};
class TemplateBlock : public TemplateNode
{
public:
TemplateBlock(TemplateNode* parent, const std::string& name);
virtual ~TemplateBlock() = default;
void addLine(const std::string& line);
std::string getRawContent() const override;
std::string renderAsParent(TemplateNode* base);
std::string render(TemplateNode* parentContext) override;
private:
std::string mName;
std::vector<std::string> mBody;
};
class TemplateExpression : public TemplateNode
{
public:
TemplateExpression(TemplateNode* parent, const std::string& content);
virtual ~TemplateExpression() = default;
std::string getRawContent() const override;
private:
std::string mContent;
};
class TemplateTextBody : public TemplateNode
{
public:
TemplateTextBody(TemplateNode* parent);
virtual ~TemplateTextBody() = default;
void addLine(const std::string& content);
std::string getRawContent() const override;
bool hasContent() const;
std::string render(TemplateNode* parentContext) override;
private:
std::vector<std::string> mContent;
};

View file

@ -1,67 +1,9 @@
#include "TemplateNodes.h" #include "TemplateElements.h"
#include "StringUtils.h" #include "StringUtils.h"
#include "TemplateSubstitutionContext.h"
TemplateNode::TemplateNode(TemplateNode* parent) #include <iostream>
: mParent(parent)
{
}
TemplateNode* TemplateNode::getParent() const
{
return mParent;
}
void TemplateNode::addChild(std::unique_ptr<TemplateNode> child)
{
mChildren.push_back(std::move(child));
}
std::size_t TemplateNode::getNumChildren() const
{
return mChildren.size();
}
std::string TemplateNode::getIdentifier() const
{
return {};
}
TemplateNode* TemplateNode::getChild(std::size_t index) const
{
return mChildren[index].get();
}
std::string TemplateNode::getRawContent() const
{
std::string content;
for (const auto& child : mChildren)
{
content += child->getRawContent() + "\n";
}
return content;
}
void TemplateNode::setExtensionParent(TemplateNode* parent)
{
mExtensionParent = parent;
}
std::string TemplateNode::render(TemplateNode* parentContext)
{
if (!parentContext && mExtensionParent)
{
parentContext = mExtensionParent;
}
std::string content;
for (size_t idx = 0; idx < mChildren.size(); idx++)
{
content += mChildren[idx]->render(parentContext);
}
return content;
}
TemplateExtends::TemplateExtends(TemplateNode* parent, const std::string& path) TemplateExtends::TemplateExtends(TemplateNode* parent, const std::string& path)
: TemplateNode(parent) : TemplateNode(parent)
@ -98,24 +40,64 @@ std::string TemplateBlock::getRawContent() const
return content; return content;
} }
std::string TemplateBlock::renderAsParent(TemplateNode* base) std::string TemplateBlock::getIdentifier() const
{ {
return {}; return mName;
} }
std::string TemplateBlock::render(TemplateNode* parentContext) std::string TemplateBlock::renderAsParent(TemplateSubstitutionContext* substitutions, TemplateBlock* base)
{
std::string content;
for (auto& child : mChildren)
{
if (child->getType() == Type::EXPRESSION)
{
auto expression = dynamic_cast<TemplateExpression*>(child.get());
std::cout << "Got expression with content " << expression->getContent() << std::endl;
if (expression->getContent() == "super()")
{
std::cout << "Adding expression base" << std::endl;
content += base->render(substitutions, nullptr);
}
}
else if(child->getType() == Type::TEXT_BODY)
{
content += child->render(substitutions, nullptr);
}
}
return content;
}
std::string TemplateBlock::renderAsLeaf(TemplateSubstitutionContext* substitutions)
{
std::string content;
for (auto& child : mChildren)
{
if(child->getType() == Type::TEXT_BODY)
{
content += child->render(substitutions, nullptr);
}
}
return content;
}
std::string TemplateBlock::render(TemplateSubstitutionContext* substitutions, TemplateNode* parentContext)
{ {
std::string content; std::string content;
if (parentContext) if (parentContext)
{ {
if (auto parent_node = parentContext->getFirstChildShallow<TemplateBlock>(getIdentifier())) if (auto parent_node = parentContext->getFirstChildShallow(Type::BLOCK, getIdentifier()))
{ {
content = dynamic_cast<TemplateBlock*>(parent_node)->renderAsParent(this); content = dynamic_cast<TemplateBlock*>(parent_node)->renderAsParent(substitutions, this);
}
else
{
content= renderAsLeaf(substitutions);
} }
} }
else else
{ {
content= renderAsLeaf(substitutions);
} }
return content; return content;
} }
@ -127,6 +109,20 @@ TemplateExpression::TemplateExpression(TemplateNode* parent, const std::string&
} }
const std::string& TemplateExpression::getContent() const
{
return mContent;
}
std::string TemplateExpression::render(TemplateSubstitutionContext* substitutions, TemplateNode* parentContext)
{
if (substitutions->hasSubstitution(mContent))
{
return substitutions->getSubstitution(mContent);
}
return {};
}
std::string TemplateExpression::getRawContent() const std::string TemplateExpression::getRawContent() const
{ {
return "TemplateExpression: " + mContent; return "TemplateExpression: " + mContent;
@ -148,7 +144,7 @@ bool TemplateTextBody::hasContent() const
return !mContent.empty(); return !mContent.empty();
} }
std::string TemplateTextBody::render(TemplateNode* parentContext) std::string TemplateTextBody::render(TemplateSubstitutionContext* substitutions, TemplateNode* parentContext)
{ {
std::string content; std::string content;
for (const auto& line : mContent) for (const auto& line : mContent)

View file

@ -0,0 +1,96 @@
#pragma once
#include "TemplateNode.h"
class TemplateExtends : public TemplateNode
{
public:
TemplateExtends(TemplateNode* parent, const std::string& path);
virtual ~TemplateExtends() = default;
std::string getRawContent() const override;
std::string getPath() const;
Type getType() const override
{
return Type::EXTENDS;
}
private:
std::string mPath;
};
class TemplateBlock : public TemplateNode
{
public:
TemplateBlock(TemplateNode* parent, const std::string& name);
virtual ~TemplateBlock() = default;
void addLine(const std::string& line);
std::string getRawContent() const override;
std::string getIdentifier() const override;
Type getType() const override
{
return Type::BLOCK;
}
std::string renderAsParent(TemplateSubstitutionContext* substitutions, TemplateBlock* base);
std::string renderAsLeaf(TemplateSubstitutionContext* substitutions);
std::string render(TemplateSubstitutionContext* substitutions, TemplateNode* parentContext) override;
private:
std::string mName;
std::vector<std::string> mBody;
};
class TemplateExpression : public TemplateNode
{
public:
TemplateExpression(TemplateNode* parent, const std::string& content);
virtual ~TemplateExpression() = default;
std::string getRawContent() const override;
const std::string& getContent() const;
Type getType() const override
{
return Type::EXPRESSION;
}
std::string render(TemplateSubstitutionContext* substitutions, TemplateNode* parentContext) override;
private:
std::string mContent;
};
class TemplateTextBody : public TemplateNode
{
public:
TemplateTextBody(TemplateNode* parent);
virtual ~TemplateTextBody() = default;
void addLine(const std::string& content);
std::string getRawContent() const override;
bool hasContent() const;
std::string render(TemplateSubstitutionContext* substitutions, TemplateNode* parentContext) override;
Type getType() const override
{
return Type::TEXT_BODY;
}
private:
std::vector<std::string> mContent;
};

View file

@ -1,19 +1,20 @@
#include "TemplateFile.h" #include "TemplateFile.h"
#include "StringUtils.h" #include "StringUtils.h"
#include "TemplateElements.h"
#include <iostream> #include <iostream>
TemplateFile::TemplateFile(const Path& path) TemplateFile::TemplateFile(const Path& path)
: mPath(path), : mPath(path),
mRootNode(std::make_unique<TemplateNode>(nullptr)) mRootNode(std::make_unique<TemplateNode>(nullptr))
{ {
} }
std::vector<std::string> TemplateFile::getProcessedContent() const TemplateFile::~TemplateFile()
{ {
return mProcessedContent;
} }
std::string TemplateFile::getName() const std::string TemplateFile::getName() const
@ -21,9 +22,23 @@ std::string TemplateFile::getName() const
return mPath.stem().string(); return mPath.stem().string();
} }
bool TemplateFile::hasLoaded() const
{
return mHasLoaded;
}
TemplateNode* TemplateFile::getContent() const
{
return mRootNode.get();
}
void TemplateFile::loadContent() void TemplateFile::loadContent()
{ {
//std::cout << "Trying to load file at: " << mPath << std::endl; if (mHasLoaded)
{
return;
}
mRawContent = File(mPath).readLines(); mRawContent = File(mPath).readLines();
mWorkingLine = 0; mWorkingLine = 0;
mWorkingNode = mRootNode.get(); mWorkingNode = mRootNode.get();
@ -34,6 +49,8 @@ void TemplateFile::loadContent()
mWorkingLine++; mWorkingLine++;
} }
onTextBodyFinished(); onTextBodyFinished();
mHasLoaded = true;
} }
void TemplateFile::onTextBodyFinished(std::string working_string) void TemplateFile::onTextBodyFinished(std::string working_string)
@ -178,12 +195,12 @@ void TemplateFile::onFoundExtends(const std::vector<std::string> args)
void TemplateFile::onFoundExpression(const std::string& expression_string) void TemplateFile::onFoundExpression(const std::string& expression_string)
{ {
auto expression = std::make_unique<TemplateExpression>(mWorkingNode, expression_string); auto stripped = StringUtils::strip(expression_string);
auto expression = std::make_unique<TemplateExpression>(mWorkingNode, stripped);
mWorkingNode->addChild(std::move(expression)); mWorkingNode->addChild(std::move(expression));
} }
void TemplateFile::dumpContent() std::string TemplateFile::dumpContent()
{ {
auto content = mRootNode->getRawContent(); return mRootNode->getRawContent();
//std::cout << content << std::endl;
} }

View file

@ -2,21 +2,31 @@
#include "File.h" #include "File.h"
#include "TemplateNodes.h" #include "TemplateNode.h"
#include <vector> #include <vector>
class TemplateNode;
class TemplateTextBody;
class TemplateFile class TemplateFile
{ {
public: public:
TemplateFile(const Path& path); TemplateFile(const Path& path);
std::vector<std::string> getProcessedContent() const; ~TemplateFile();
std::string dumpContent();
std::string getName() const; std::string getName() const;
TemplateNode* getContent() const;
bool hasLoaded() const;
void loadContent(); void loadContent();
private:
void onTextBodyFinished(std::string working_string = {}); void onTextBodyFinished(std::string working_string = {});
void processLine(const std::string& line); void processLine(const std::string& line);
@ -31,19 +41,12 @@ public:
void onFoundExpression(const std::string& expression_string); void onFoundExpression(const std::string& expression_string);
void dumpContent();
TemplateNode* getContent() const
{
return mRootNode.get();
}
private:
Path mPath; Path mPath;
unsigned mWorkingLine{ 0 };
std::string mParentName; std::string mParentName;
std::vector<std::string> mRawContent; std::vector<std::string> mRawContent;
std::vector<std::string> mProcessedContent; bool mHasLoaded{false};
unsigned mWorkingLine{ 0 };
std::unique_ptr<TemplateNode> mRootNode; std::unique_ptr<TemplateNode> mRootNode;
TemplateNode* mWorkingNode{ nullptr }; TemplateNode* mWorkingNode{ nullptr };
std::unique_ptr<TemplateTextBody> mWorkingTextBody; std::unique_ptr<TemplateTextBody> mWorkingTextBody;

View file

@ -0,0 +1,91 @@
#include "TemplateNode.h"
#include "TemplateSubstitutionContext.h"
TemplateNode::TemplateNode(TemplateNode* parent)
: mParent(parent)
{
}
TemplateNode* TemplateNode::getParent() const
{
return mParent;
}
void TemplateNode::addChild(std::unique_ptr<TemplateNode> child)
{
mChildren.push_back(std::move(child));
}
std::size_t TemplateNode::getNumChildren() const
{
return mChildren.size();
}
TemplateNode* TemplateNode::getFirstChildShallow(Type type, const std::string& identifier) const
{
for (const auto& child : mChildren)
{
if (child->getType() == type)
{
if (!identifier.empty())
{
if (child->getIdentifier() == identifier)
{
return child.get();
}
}
else
{
return child.get();
}
}
}
return nullptr;
}
TemplateNode::Type TemplateNode::getType() const
{
return Type::NONE;
}
std::string TemplateNode::getIdentifier() const
{
return {};
}
TemplateNode* TemplateNode::getChild(std::size_t index) const
{
return mChildren[index].get();
}
std::string TemplateNode::getRawContent() const
{
std::string content;
for (const auto& child : mChildren)
{
content += child->getRawContent() + "\n";
}
return content;
}
void TemplateNode::setExtensionParent(TemplateNode* parent)
{
mExtensionParent = parent;
}
std::string TemplateNode::render(TemplateSubstitutionContext* substitutions, TemplateNode* parentContext)
{
if (!parentContext && mExtensionParent)
{
parentContext = mExtensionParent;
}
std::string content;
for (size_t idx = 0; idx < mChildren.size(); idx++)
{
content += mChildren[idx]->render(substitutions, parentContext);
}
return content;
}

View file

@ -0,0 +1,51 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
class TemplateSubstitutionContext;
class TemplateNode
{
public:
enum class Type
{
NONE,
BLOCK,
EXPRESSION,
EXTENDS,
TEXT_BODY
};
TemplateNode(TemplateNode* parent);
virtual ~TemplateNode() = default;
virtual void addChild(std::unique_ptr<TemplateNode> child);
TemplateNode* getChild(std::size_t index) const;
TemplateNode* getFirstChildShallow(Type type, const std::string& identifier = {}) const;
virtual std::string getIdentifier() const;
std::size_t getNumChildren() const;
TemplateNode* getParent() const;
virtual std::string getRawContent() const;
virtual Type getType() const;
virtual std::string render(TemplateSubstitutionContext* substitutions, TemplateNode* parentContext = nullptr);
void setExtensionParent(TemplateNode* parent);
protected:
std::vector<std::unique_ptr<TemplateNode> > mChildren;
TemplateNode* mParent{ nullptr };
TemplateNode* mExtensionParent{ nullptr };
};
using TemplateNodePtr = std::unique_ptr<TemplateNode>;

View file

@ -0,0 +1,35 @@
#pragma once
#include <string>
#include <unordered_map>
class TemplateSubstitutionContext
{
public:
void addSubstitution(const std::string& key, const std::string& value)
{
mSubstitutions[key] = value;
}
bool hasSubstitution(const std::string& key) const
{
return mSubstitutions.find(key) != mSubstitutions.end();
}
std::string getSubstitution(const std::string& key) const
{
auto iter = mSubstitutions.find(key);
if(iter != mSubstitutions.end())
{
return iter->second;
}
else
{
return {};
}
}
private:
std::unordered_map<std::string, std::string> mSubstitutions;
};

View file

@ -3,6 +3,9 @@
#include "Directory.h" #include "Directory.h"
#include "FileLogger.h" #include "FileLogger.h"
#include "TemplateElements.h"
#include "TemplateSubstitutionContext.h"
#include <iostream> #include <iostream>
TemplatingEngine::TemplatingEngine(const Path& workingDirectory) TemplatingEngine::TemplatingEngine(const Path& workingDirectory)
@ -11,6 +14,32 @@ TemplatingEngine::TemplatingEngine(const Path& workingDirectory)
} }
std::string TemplatingEngine::renderTemplate(const std::string& name, TemplateSubstitutionContext* substitutionContext)
{
if (mTemplateFiles.empty())
{
loadTemplateFiles();
}
if (auto file = getTemplateFile(name))
{
if (!file->hasLoaded())
{
file->loadContent();
std::cout << file->dumpContent();
processTemplate(file, nullptr);
}
return file->getContent()->render(substitutionContext, nullptr);
}
else
{
return {};
}
}
void TemplatingEngine::loadTemplateFiles() void TemplatingEngine::loadTemplateFiles()
{ {
const auto files = Directory::getFilesWithExtension(mWorkingDirectory, mTemplateExtension); const auto files = Directory::getFilesWithExtension(mWorkingDirectory, mTemplateExtension);
@ -21,15 +50,6 @@ void TemplatingEngine::loadTemplateFiles()
MLOG_INFO("Found: " << mTemplateFiles.size() << " templates in " << mWorkingDirectory); MLOG_INFO("Found: " << mTemplateFiles.size() << " templates in " << mWorkingDirectory);
} }
std::string TemplatingEngine::processTemplate(const std::string& name)
{
if (auto file = getTemplateFile(name))
{
return processTemplate(file);
}
return {};
}
TemplateFile* TemplatingEngine::getTemplateFile(const Path& path) TemplateFile* TemplatingEngine::getTemplateFile(const Path& path)
{ {
return getTemplateFile(path.stem().string()); return getTemplateFile(path.stem().string());
@ -40,37 +60,24 @@ TemplateFile* TemplatingEngine::getTemplateFile(const std::string& name)
return mTemplateFiles[name].get(); return mTemplateFiles[name].get();
} }
std::string TemplatingEngine::processTemplate(TemplateFile* file, TemplateNode* parent) void TemplatingEngine::processTemplate(TemplateFile* file, TemplateNode* parent)
{ {
file->loadContent(); std::cout << "Processing file " << file->getName() << std::endl;
file->dumpContent();
auto content = file->getContent(); auto content = file->getContent();
if (parent) if (parent)
{ {
std::cout << "Setting extension parent" << std::endl;
content->setExtensionParent(parent); content->setExtensionParent(parent);
} }
if (auto extension_node = content->getFirstChildShallow<TemplateExtends>()) if (auto extension_node = dynamic_cast<TemplateExtends*>(content->getFirstChildShallow(TemplateNode::Type::EXTENDS)))
{ {
//std::cout << "Found extension node" << std::endl; std::cout << "Found extension node " << std::endl;
if (auto extension_template = getTemplateFile(Path(extension_node->getPath()))) if (auto extension_template = getTemplateFile(Path(extension_node->getPath())))
{ {
//std::cout << "Found extension template" << std::endl; std::cout << "Found extension template " << std::endl;
return processTemplate(extension_template, parent); processTemplate(extension_template, content);
}
else
{
return render(content);
} }
} }
else
{
return render(content);
}
}
std::string TemplatingEngine::render(TemplateNode* content)
{
return content->render();
} }

View file

@ -8,20 +8,21 @@
#include <memory> #include <memory>
#include <unordered_map> #include <unordered_map>
class TemplateSubstitutionContext;
class TemplatingEngine class TemplatingEngine
{ {
public: public:
TemplatingEngine(const Path& workingDirectory); TemplatingEngine(const Path& workingDirectory);
void loadTemplateFiles(); std::string renderTemplate(const std::string& name, TemplateSubstitutionContext* substitutionContext);
std::string processTemplate(const std::string& name);
private: private:
TemplateFile* getTemplateFile(const std::string& name); TemplateFile* getTemplateFile(const std::string& name);
TemplateFile* getTemplateFile(const Path& path); TemplateFile* getTemplateFile(const Path& path);
std::string render(TemplateNode* content);
std::string processTemplate(TemplateFile* file, TemplateNode* parent = nullptr); void loadTemplateFiles();
void processTemplate(TemplateFile* file, TemplateNode* parent = nullptr);
std::unordered_map<std::string, std::unique_ptr<TemplateFile> > mTemplateFiles; std::unordered_map<std::string, std::unique_ptr<TemplateFile> > mTemplateFiles;
Path mWorkingDirectory; Path mWorkingDirectory;

View file

@ -53,13 +53,11 @@ std::string StringUtils::strip(const std::string& input)
return {}; return {};
} }
std::locale loc;
std::string working_string;
std::size_t first_nonspace = 0; std::size_t first_nonspace = 0;
std::size_t last_nonspace = working_string.size() - 1; std::size_t last_nonspace = input.size() - 1;
for (std::size_t idx = 0; idx < working_string.size(); idx++) for (std::size_t idx = 0; idx < input.size(); idx++)
{ {
if (!std::isspace(working_string[idx], loc)) if (!std::isspace(input[idx]))
{ {
first_nonspace = idx; first_nonspace = idx;
break; break;
@ -73,13 +71,13 @@ std::string StringUtils::strip(const std::string& input)
for (std::size_t idx = last_nonspace; idx > 0; idx--) for (std::size_t idx = last_nonspace; idx > 0; idx--)
{ {
if (!std::isspace(working_string[idx], loc)) if (!std::isspace(input[idx]))
{ {
last_nonspace = idx; last_nonspace = idx;
break; break;
} }
} }
return working_string.substr(first_nonspace, last_nonspace-first_nonspace); return input.substr(first_nonspace, last_nonspace-first_nonspace + 1);
} }
std::vector<std::string> StringUtils::split(const std::string& input) std::vector<std::string> StringUtils::split(const std::string& input)
@ -166,3 +164,21 @@ std::string StringUtils::stripQuotes(const std::string& input)
} }
return input.substr(start_index, end_index - start_index + 1); return input.substr(start_index, end_index - start_index + 1);
} }
std::string StringUtils::replaceWith(const std::string& inputString, const std::string& searchString, const std::string& replaceString)
{
return inputString;
}
std::string StringUtils::removeUpTo(const std::string& input, const std::string& prefix)
{
std::size_t found = input.find(prefix);
if (found!=std::string::npos)
{
return input.substr(found, prefix.size());
}
else
{
return input;
}
}

View file

@ -27,6 +27,8 @@ public:
static std::vector<std::string> split(const std::string& input); static std::vector<std::string> split(const std::string& input);
static std::string strip(const std::string& input); static std::string strip(const std::string& input);
static std::string removeUpTo(const std::string& input, const std::string& prefix);
static std::vector<std::string> toLines(const std::string& input); static std::vector<std::string> toLines(const std::string& input);
static std::string stripQuotes(const std::string& input); static std::string stripQuotes(const std::string& input);
@ -34,4 +36,6 @@ public:
static std::vector<unsigned char> toBytes(const std::string& input); static std::vector<unsigned char> toBytes(const std::string& input);
static std::string toString(const std::vector<unsigned char>& bytes); static std::string toString(const std::vector<unsigned char>& bytes);
static std::string replaceWith(const std::string& inputString, const std::string& searchString, const std::string& replaceString);
}; };

View file

@ -3,6 +3,7 @@
#include "MarkdownParser.h" #include "MarkdownParser.h"
#include "MarkdownDocument.h" #include "MarkdownDocument.h"
#include "MarkdownConverter.h" #include "MarkdownConverter.h"
#include "MarkdownElement.h"
#include "HtmlDocument.h" #include "HtmlDocument.h"
#include "HtmlWriter.h" #include "HtmlWriter.h"

View file

@ -31,3 +31,8 @@ void HtmlDocument::addElementToBody(std::unique_ptr<HtmlElement> element)
body_element->addChild(std::move(element)); body_element->addChild(std::move(element));
} }
} }
HtmlBodyElement* HtmlDocument::getBodyElement() const
{
return dynamic_cast<HtmlBodyElement*>(getRoot()->getFirstChildWithTagName("body"));
}

View file

@ -5,6 +5,7 @@
#include <memory> #include <memory>
class HtmlElement; class HtmlElement;
class HtmlBodyElement;
class HtmlDocument : public XmlDocument class HtmlDocument : public XmlDocument
{ {
@ -17,6 +18,8 @@ public:
void addElementToBody(std::unique_ptr<HtmlElement> element); void addElementToBody(std::unique_ptr<HtmlElement> element);
HtmlBodyElement* getBodyElement() const;
private: private:
}; };

View file

@ -19,7 +19,8 @@ public:
HYPERLINK, HYPERLINK,
IMAGE, IMAGE,
UNORDERED_LIST, UNORDERED_LIST,
LIST_ITEM LIST_ITEM,
DIV
}; };
HtmlElement(const std::string& tagName); HtmlElement(const std::string& tagName);
@ -27,6 +28,20 @@ public:
virtual Type getType() const = 0; virtual Type getType() const = 0;
}; };
class HtmlDivElement : public HtmlElement
{
public:
HtmlDivElement() : HtmlElement("div")
{
}
Type getType() const override
{
return Type::DIV;
}
};
class HtmlCodeElement : public HtmlElement class HtmlCodeElement : public HtmlElement
{ {
public: public:

View file

@ -1,16 +1,20 @@
#include "HtmlWriter.h" #include "HtmlWriter.h"
#include "HtmlDocument.h" #include "HtmlDocument.h"
#include "HtmlElement.h"
#include "XmlElement.h" #include "XmlElement.h"
#include "XmlAttribute.h" #include "XmlAttribute.h"
#include <iostream>
HtmlWriter::HtmlWriter() HtmlWriter::HtmlWriter()
{ {
} }
std::string HtmlWriter::toString(HtmlElement* element, unsigned startDepth)
{
return element->toString(startDepth);
}
std::string HtmlWriter::toString(HtmlDocument* document) std::string HtmlWriter::toString(HtmlDocument* document)
{ {
std::string content = "<!DOCTYPE html>\n"; std::string content = "<!DOCTYPE html>\n";

View file

@ -3,6 +3,7 @@
#include <string> #include <string>
class HtmlDocument; class HtmlDocument;
class HtmlElement;
class HtmlWriter class HtmlWriter
{ {
@ -10,4 +11,6 @@ public:
HtmlWriter(); HtmlWriter();
std::string toString(HtmlDocument* document); std::string toString(HtmlDocument* document);
std::string toString(HtmlElement* element, unsigned startDepth=0);
}; };

View file

@ -1,5 +1,7 @@
#include "MarkdownComponents.h" #include "MarkdownComponents.h"
#include "StringUtils.h"
MarkdownTextSpan::Type MarkdownTextSpan::getType() const MarkdownTextSpan::Type MarkdownTextSpan::getType() const
{ {
return Type::TEXT_SPAN; return Type::TEXT_SPAN;
@ -87,6 +89,13 @@ MarkdownLink::Type MarkdownLink::getType() const
return Type::LINK; return Type::LINK;
} }
void MarkdownLink::doFieldSubstitution(Type elementType, const std::string& searchPhrase, const std::string& replacementPhrase)
{
if (elementType == Type::LINK)
{
mTarget = StringUtils::replaceWith(mTarget, searchPhrase, replacementPhrase);
}
}
MarkdownImage::MarkdownImage(const std::string& source, const std::string& alt) MarkdownImage::MarkdownImage(const std::string& source, const std::string& alt)
: mSource(source), : mSource(source),

View file

@ -13,6 +13,28 @@ public:
Type getType() const override; Type getType() const override;
}; };
class MarkdownLink : public MarkdownInlineElement
{
public:
MarkdownLink(const std::string& target);
virtual ~MarkdownLink() = default;
const std::string& getTarget() const;
Type getType() const override;
void setTarget(const std::string& target)
{
mTarget = target;
}
void doFieldSubstitution(Type elementType, const std::string& searchPhrase, const std::string& replacementPhrase) override;
private:
std::string mTarget;
};
class MarkdownParagraph : public MarkdownElement class MarkdownParagraph : public MarkdownElement
{ {
public: public:
@ -26,6 +48,27 @@ public:
MarkdownInlineElement* getChild(std::size_t idx) const; MarkdownInlineElement* getChild(std::size_t idx) const;
void doFieldSubstitution(Type elementType, const std::string& searchPhrase, const std::string& replacementPhrase) override
{
for(auto& child : mChildren)
{
child->doFieldSubstitution(elementType, searchPhrase, replacementPhrase);
}
}
std::vector<MarkdownLink*> getAllLinks() const
{
std::vector<MarkdownLink*> links;
for(auto& child : mChildren)
{
if (child->getType() == Type::LINK)
{
links.push_back(dynamic_cast<MarkdownLink*>(child.get()));
}
}
return links;
}
private: private:
std::vector<std::unique_ptr<MarkdownInlineElement> > mChildren; std::vector<std::unique_ptr<MarkdownInlineElement> > mChildren;
}; };
@ -79,20 +122,6 @@ public:
Type getType() const override; Type getType() const override;
}; };
class MarkdownLink : public MarkdownInlineElement
{
public:
MarkdownLink(const std::string& target);
virtual ~MarkdownLink() = default;
const std::string& getTarget() const;
Type getType() const override;
private:
std::string mTarget;
};
class MarkdownImage : public MarkdownInlineElement class MarkdownImage : public MarkdownInlineElement
{ {
public: public:

View file

@ -3,16 +3,15 @@
#include "HtmlDocument.h" #include "HtmlDocument.h"
#include "HtmlElement.h" #include "HtmlElement.h"
#include "HtmlParagraphElement.h" #include "HtmlParagraphElement.h"
#include "HtmlBodyElement.h"
#include "HtmlTextRun.h" #include "HtmlTextRun.h"
#include "MarkdownElement.h" #include "MarkdownElement.h"
#include "MarkdownComponents.h" #include "MarkdownComponents.h"
#include "MarkdownDocument.h" #include "MarkdownDocument.h"
std::unique_ptr<HtmlDocument> MarkdownConverter::convert(MarkdownDocument* markdownDoc) const void MarkdownConverter::convert(MarkdownDocument* markdownDoc, HtmlElement* parentElement) const
{ {
auto html_doc = std::make_unique<HtmlDocument>();
for(unsigned idx=0; idx<markdownDoc->getNumElements();idx++) for(unsigned idx=0; idx<markdownDoc->getNumElements();idx++)
{ {
auto md_element = markdownDoc->getElement(idx); auto md_element = markdownDoc->getElement(idx);
@ -23,7 +22,7 @@ std::unique_ptr<HtmlDocument> MarkdownConverter::convert(MarkdownDocument* markd
auto html_element = std::make_unique<HtmlHeadingElement>(heading_level); auto html_element = std::make_unique<HtmlHeadingElement>(heading_level);
html_element->setText(md_element->getTextContent()); html_element->setText(md_element->getTextContent());
html_doc->addElementToBody(std::move(html_element)); parentElement->addChild(std::move(html_element));
} }
else if(md_element->getType() == MarkdownElement::Type::PARAGRAPH) else if(md_element->getType() == MarkdownElement::Type::PARAGRAPH)
{ {
@ -59,7 +58,7 @@ std::unique_ptr<HtmlDocument> MarkdownConverter::convert(MarkdownDocument* markd
html_p_element->addChild(std::move(html_text)); html_p_element->addChild(std::move(html_text));
} }
} }
html_doc->addElementToBody(std::move(html_p_element)); parentElement->addChild(std::move(html_p_element));
} }
else if(md_element->getType() == MarkdownElement::Type::BULLET_LIST) else if(md_element->getType() == MarkdownElement::Type::BULLET_LIST)
{ {
@ -72,15 +71,23 @@ std::unique_ptr<HtmlDocument> MarkdownConverter::convert(MarkdownDocument* markd
html_list_item->setText(child->getTextContent()); html_list_item->setText(child->getTextContent());
html_list->addChild(std::move(html_list_item)); html_list->addChild(std::move(html_list_item));
} }
html_doc->addElementToBody(std::move(html_list)); parentElement->addChild(std::move(html_list));
} }
else if(md_element->getType() == MarkdownElement::Type::MULTILINE_QUOTE) else if(md_element->getType() == MarkdownElement::Type::MULTILINE_QUOTE)
{ {
auto html_quote = std::make_unique<HtmlCodeElement>(); auto html_quote = std::make_unique<HtmlCodeElement>();
html_quote->setText(md_element->getTextContent()); html_quote->setText(md_element->getTextContent());
html_doc->addElementToBody(std::move(html_quote)); parentElement->addChild(std::move(html_quote));
} }
} }
}
std::unique_ptr<HtmlDocument> MarkdownConverter::convert(MarkdownDocument* markdownDoc) const
{
auto html_doc = std::make_unique<HtmlDocument>();
auto body_element = html_doc->getBodyElement();
convert(markdownDoc, body_element);
return std::move(html_doc); return std::move(html_doc);
} }

View file

@ -3,6 +3,7 @@
#include <memory> #include <memory>
class HtmlDocument; class HtmlDocument;
class HtmlElement;
class MarkdownDocument; class MarkdownDocument;
class MarkdownConverter class MarkdownConverter
@ -10,4 +11,6 @@ class MarkdownConverter
public: public:
std::unique_ptr<HtmlDocument> convert(MarkdownDocument* markdownDoc) const; std::unique_ptr<HtmlDocument> convert(MarkdownDocument* markdownDoc) const;
void convert(MarkdownDocument* markdownDoc, HtmlElement* parentElement) const;
}; };

View file

@ -1,6 +1,8 @@
#include "MarkdownDocument.h" #include "MarkdownDocument.h"
#include "MarkdownElement.h" #include "MarkdownElement.h"
#include "MarkdownComponents.h"
void MarkdownDocument::addElement(std::unique_ptr<MarkdownElement> element) void MarkdownDocument::addElement(std::unique_ptr<MarkdownElement> element)
{ {
@ -16,3 +18,25 @@ MarkdownElement* MarkdownDocument::getElement(std::size_t idx) const
{ {
return mElements[idx].get(); return mElements[idx].get();
} }
void MarkdownDocument::doLinkTargetSubstitution(const std::string& targetString, const std::string& replacementString)
{
for(auto& element : mElements)
{
element->doFieldSubstitution(MarkdownElement::Type::LINK, targetString, replacementString);
}
}
std::vector<MarkdownLink*> MarkdownDocument::getAllLinks() const
{
std::vector<MarkdownLink*> links;
for(auto& element : mElements)
{
if (element->getType() == MarkdownElement::Type::PARAGRAPH)
{
auto para_links = dynamic_cast<MarkdownParagraph*>(element.get())->getAllLinks();
links.insert(links.end(), para_links.begin(), para_links.end());
}
}
return links;
}

View file

@ -4,6 +4,7 @@
#include <memory> #include <memory>
class MarkdownElement; class MarkdownElement;
class MarkdownLink;
class MarkdownDocument class MarkdownDocument
{ {
@ -14,6 +15,10 @@ public:
MarkdownElement* getElement(std::size_t idx) const; MarkdownElement* getElement(std::size_t idx) const;
void doLinkTargetSubstitution(const std::string& targetString, const std::string& replacementString);
std::vector<MarkdownLink*> getAllLinks() const;
private: private:
std::vector<std::unique_ptr<MarkdownElement> > mElements; std::vector<std::unique_ptr<MarkdownElement> > mElements;
}; };

View file

@ -29,6 +29,11 @@ public:
const std::string& getTextContent() const; const std::string& getTextContent() const;
virtual Type getType() const = 0; virtual Type getType() const = 0;
virtual void doFieldSubstitution(Type elementType, const std::string& searchPhrase, const std::string& replacementPhrase)
{
}
private: private:
std::string mTextContent; std::string mTextContent;
}; };

View file

@ -19,7 +19,6 @@ MarkdownParser::~MarkdownParser()
void MarkdownParser::onMultilineQuote() void MarkdownParser::onMultilineQuote()
{ {
std::cout << "Adding multiline quote " << mDocumentContent << std::endl;
auto quote = std::make_unique<MarkdownMultilineQuote>(mWorkingTag); auto quote = std::make_unique<MarkdownMultilineQuote>(mWorkingTag);
quote->appendTextContent(mDocumentContent); quote->appendTextContent(mDocumentContent);
@ -34,8 +33,6 @@ void MarkdownParser::onMultilineQuote()
void MarkdownParser::onInlineQuote() void MarkdownParser::onInlineQuote()
{ {
std::cout << "Adding inline quote " << mLineContent << std::endl;
auto quote = std::make_unique<MarkdownInlineQuote>(); auto quote = std::make_unique<MarkdownInlineQuote>();
quote->appendTextContent(mLineContent); quote->appendTextContent(mLineContent);
mLineContent.clear(); mLineContent.clear();
@ -49,8 +46,6 @@ void MarkdownParser::onInlineQuote()
void MarkdownParser::onHeading(unsigned level) void MarkdownParser::onHeading(unsigned level)
{ {
std::cout << "Adding heading: " << mLineContent << std::endl;
auto heading = std::make_unique<MarkdownHeading>(level); auto heading = std::make_unique<MarkdownHeading>(level);
heading->appendTextContent(mLineContent); heading->appendTextContent(mLineContent);
mMarkdownDocument->addElement(std::move(heading)); mMarkdownDocument->addElement(std::move(heading));
@ -60,7 +55,6 @@ void MarkdownParser::onNewParagraph()
{ {
if (mWorkingBulletList) if (mWorkingBulletList)
{ {
std::cout << "Adding bullets to document" << std::endl;
mMarkdownDocument->addElement(std::move(mWorkingBulletList)); mMarkdownDocument->addElement(std::move(mWorkingBulletList));
mWorkingBulletList.reset(); mWorkingBulletList.reset();
mDocumentState == DocumentState::NONE; mDocumentState == DocumentState::NONE;
@ -71,7 +65,6 @@ void MarkdownParser::onNewParagraph()
if (!mWorkingParagraph->getNumChildren() == 0) if (!mWorkingParagraph->getNumChildren() == 0)
{ {
std::cout << "Adding para to document" << std::endl;
mMarkdownDocument->addElement(std::move(mWorkingParagraph)); mMarkdownDocument->addElement(std::move(mWorkingParagraph));
} }
} }
@ -84,8 +77,6 @@ void MarkdownParser::onTextSpan()
if(mWorkingParagraph && !mDocumentContent.empty()) if(mWorkingParagraph && !mDocumentContent.empty())
{ {
std::cout << "Adding text " << mDocumentContent << std::endl;
auto text_span = std::make_unique<MarkdownTextSpan>(); auto text_span = std::make_unique<MarkdownTextSpan>();
text_span->appendTextContent(mDocumentContent); text_span->appendTextContent(mDocumentContent);
mWorkingParagraph->addChild(std::move(text_span)); mWorkingParagraph->addChild(std::move(text_span));
@ -141,7 +132,6 @@ std::pair<unsigned, bool> MarkdownParser::onTick(unsigned tickCount)
void MarkdownParser::onLink() void MarkdownParser::onLink()
{ {
std::cout << "Adding hyperlink to " << mLineContent << " with tag " << mWorkingTag << std::endl;
auto element = std::make_unique<MarkdownLink>(mLineContent); auto element = std::make_unique<MarkdownLink>(mLineContent);
mLineContent.clear(); mLineContent.clear();
@ -157,7 +147,6 @@ void MarkdownParser::onLink()
void MarkdownParser::onImage() void MarkdownParser::onImage()
{ {
std::cout << "Adding image with path " << mLineContent << " and alt" << mWorkingTag << std::endl;
auto element = std::make_unique<MarkdownImage>(mLineContent, mWorkingTag); auto element = std::make_unique<MarkdownImage>(mLineContent, mWorkingTag);
mLineContent.clear(); mLineContent.clear();
@ -173,8 +162,6 @@ void MarkdownParser::onImage()
void MarkdownParser::onBulletItem() void MarkdownParser::onBulletItem()
{ {
std::cout << "Adding bullet item " << mLineContent << std::endl;
if (!mWorkingBulletList) if (!mWorkingBulletList)
{ {
mWorkingBulletList = std::make_unique<MarkdownBulletList>(); mWorkingBulletList = std::make_unique<MarkdownBulletList>();
@ -231,7 +218,6 @@ void MarkdownParser::processLine()
{ {
if (!flushed_pre_inline) if (!flushed_pre_inline)
{ {
std::cout << "Flushing pre-line " << std::endl;
mDocumentContent += mLineContent; mDocumentContent += mLineContent;
onTextSpan(); onTextSpan();
flushed_pre_inline = true; flushed_pre_inline = true;

View file

@ -8,8 +8,7 @@
TEST_CASE(TestTemplatingEngine, "compiler") TEST_CASE(TestTemplatingEngine, "compiler")
{ {
auto engine = TemplatingEngine(TestUtils::getTestDataDir()); auto engine = TemplatingEngine(TestUtils::getTestDataDir());
engine.loadTemplateFiles(); const auto content = engine.renderTemplate("index", nullptr);
const auto content = engine.processTemplate("index");
File outfile(TestUtils::getTestOutputDir() / "index.html"); File outfile(TestUtils::getTestOutputDir() / "index.html");
outfile.writeText(content); outfile.writeText(content);

View file

@ -1,8 +1,9 @@
set(CORE_UNIT_TEST_FILES set(CORE_UNIT_TEST_FILES
core/TestByteUtils.cpp core/TestByteUtils.cpp
core/TestBitStream.cpp core/TestBitStream.cpp
core/TestTomlReader.cpp
core/TestDataStructures.cpp core/TestDataStructures.cpp
core/TestTomlReader.cpp
core/TestStringUtils.cpp
PARENT_SCOPE PARENT_SCOPE
) )

View file

@ -0,0 +1,15 @@
#include "StringUtils.h"
#include "TestFramework.h"
#include "TestUtils.h"
#include <iostream>
TEST_CASE(TestStringUtils_Strip, "core")
{
std::string input = " super() ";
std::string stripped = StringUtils::strip(input);
auto predicate = stripped == "super()";
REQUIRE(predicate);
}

View file

@ -5,6 +5,7 @@
#include "HtmlDocument.h" #include "HtmlDocument.h"
#include "MarkdownDocument.h" #include "MarkdownDocument.h"
#include "MarkdownConverter.h" #include "MarkdownConverter.h"
#include "MarkdownElement.h"
#include "HtmlWriter.h" #include "HtmlWriter.h"
#include "TestFramework.h" #include "TestFramework.h"