Add a directory scanner to detect application routes
All checks were successful
buildbot/mdml-cgi-linux-podman-builder Build done.
buildbot/mdml-cgi-darwin-macos-builder Build done.
buildbot/mdml-cgi-freebsd-jail-builder Build done.

Close #24
This commit is contained in:
S David 2023-11-24 15:14:16 -05:00
parent 79e0206fc1
commit 9b8d2f566e
9 changed files with 162 additions and 43 deletions

View File

@ -8,6 +8,7 @@
*/
#include "CgiApplication.hpp"
#include "MarkdownRouteHandler.hpp"
#include "libmdml.hpp"
#include "types.hpp"
@ -43,9 +44,17 @@ handle_error(
int
main(int argc, const char* argv[], const char* envp[])
{
auto app = mdml::CgiApplication(argc, argv, envp);
try {
auto app = mdml::CgiApplication(argc, argv, envp);
auto markdown_routes =
mdml::MarkdownRouteHandler::GenerateRoutes(
"/usr/local/www/content",
"/usr/local/www/templates/main.thmtl"
);
app.ImportRoutes(markdown_routes);
auto result = app.ProcessRequest();
if (result.IsError) {

View File

@ -23,20 +23,18 @@ class IRouteHandler;
class CgiApplication : public Application {
public:
template<typename T>
using ptr = std::shared_ptr<T>;
template<typename T>
inline static ptr<T> make_ptr()
{
return std::make_shared<T>();
}
using RouteDictionary = std::map<std::string, ptr<IRouteHandler>>;
using RouteDictionary = Dictionary<route::ptr<IRouteHandler>>;
CgiApplication(int argc, c::const_string argv[], c::const_string env[]);
virtual ~CgiApplication();
Result<std::string> ProcessRequest();
inline void ImportRoutes(RouteDictionary& route_collection)
{
this->routes = std::move(route_collection);
}
#ifdef TESTING
/* Expose a public reference to the Routes map, but only for unit-testing
* builds */

View File

@ -12,6 +12,7 @@
#include "types.hpp"
#include <filesystem>
#include <map>
#include <string>
namespace mdml {
@ -23,21 +24,34 @@ namespace fs = std::filesystem;
class MarkdownRouteHandler : public IRouteHandler {
public:
MarkdownRouteHandler();
MarkdownRouteHandler(const MarkdownRouteHandler& other) = default;
/*: IRouteHandler(other)
, html_data(other.html_data)
, markdown_data(other.markdown_data)
, OutputStream()
{
}*/
virtual ~MarkdownRouteHandler();
void LoadTemplate(const std::string& template_name);
void LoadMarkdown(const std::string& markdown_page_name);
void LoadTemplate(const fs::path& template_name);
void LoadMarkdown(const fs::path& markdown_page_name);
virtual Result<std::string> Process(
const std::string& name, const std::string& request_uri
);
std::reference_wrapper<std::ostream> OutputStream;
#ifdef TESTING
inline std::string& GetHtmlData() { return html_data; }
inline std::string& GetMarkdownData() { return markdown_data; }
#endif
static Dictionary<route::ptr<IRouteHandler>> GenerateRoutes(
std::filesystem::path content_dir,
std::filesystem::path main_template
);
std::reference_wrapper<std::ostream> OutputStream;
protected:
std::string render_document(
const std::string& title, const std::string& request_uri
@ -47,7 +61,7 @@ class MarkdownRouteHandler : public IRouteHandler {
const fs::path& document_path, std::string& out_document
);
fs::path work_dir;
static fs::path work_dir;
std::string html_data;
std::string markdown_data;
};

View File

@ -11,6 +11,7 @@
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <utility>
@ -21,6 +22,17 @@ using string = char*;
using const_string = const char*;
}
namespace route {
template<typename T>
using ptr = std::shared_ptr<T>;
template<typename T>
inline static ptr<T>
make_ptr()
{
return std::make_shared<T>();
}
}
enum error_t : bool { NO_ERROR = false, ERROR = true };
using count_t = size_t;

View File

@ -18,16 +18,19 @@
#include <filesystem>
#include <fstream>
#include <iostream>
#include <map>
#include <string>
using namespace mdml;
#if defined(TESTING)
constexpr const char* DEFAULT_WORKDIR = ".";
constexpr const char* DEFAULT_WORKDIR = "./data";
#else
constexpr const char* DEFAULT_WORKDIR = "/usr/local/www/templates";
constexpr const char* DEFAULT_WORKDIR = "/usr/local/www";
#endif
fs::path MarkdownRouteHandler::work_dir = fs::absolute(DEFAULT_WORKDIR);
const auto NOT_FOUND = std::string::npos;
void
@ -48,7 +51,7 @@ string_replace(
}
MarkdownRouteHandler::MarkdownRouteHandler()
: IRouteHandler(), OutputStream(std::cout), work_dir(DEFAULT_WORKDIR)
: IRouteHandler(), OutputStream(std::cout)
{
work_dir = fs::absolute(work_dir);
}
@ -56,17 +59,28 @@ MarkdownRouteHandler::MarkdownRouteHandler()
MarkdownRouteHandler::~MarkdownRouteHandler() {}
void
MarkdownRouteHandler::LoadTemplate(const std::string& template_filename)
MarkdownRouteHandler::LoadTemplate(const fs::path& template_filename)
{
fs::path full_html_path = work_dir / template_filename;
fs::path full_html_path;
if (template_filename.is_relative()) {
full_html_path = work_dir / template_filename;
} else {
full_html_path = template_filename;
}
this->load_document(full_html_path, this->html_data);
}
void
MarkdownRouteHandler::LoadMarkdown(const std::string& markdown_filename)
MarkdownRouteHandler::LoadMarkdown(const fs::path& markdown_filename)
{
fs::path full_md_path = work_dir / markdown_filename;
fs::path full_md_path;
if (markdown_filename.is_relative()) {
full_md_path = work_dir / markdown_filename;
} else {
full_md_path = markdown_filename;
}
this->load_document(full_md_path, this->markdown_data);
}
@ -84,6 +98,44 @@ MarkdownRouteHandler::Process(
return { NO_ERROR, document };
}
Dictionary<route::ptr<IRouteHandler>>
MarkdownRouteHandler::GenerateRoutes(
std::filesystem::path content_dir, std::filesystem::path main_template
)
{
if (content_dir.is_relative()) {
content_dir = work_dir / content_dir;
}
Dictionary<route::ptr<IRouteHandler>> results;
for (const auto& entry :
std::filesystem::directory_iterator(content_dir)) {
if (entry.is_regular_file()) {
auto filename = entry.path().filename().string();
auto routename = entry.path().stem().string();
auto extension = entry.path().extension().string();
// Check if the file has a .md extension and a specific
// name pattern
if (extension == ".md") {
auto& routeHandler =
*(new MarkdownRouteHandler());
// Load template and document
routeHandler.LoadTemplate(
(work_dir / main_template).string()
);
routeHandler.LoadMarkdown(entry.path().string());
// Move the routeHandler into the vector
results.emplace(routename, &routeHandler);
}
}
}
return results;
}
void
MarkdownRouteHandler::load_document(
const fs::path& document_path, std::string& out_document

View File

@ -26,6 +26,21 @@ inline const char* simulated_launch::env[] = {
"REQUEST_URI=markdown?msg=hello-world",
nullptr
};
// Define a test oute handler for the "/markdown" route
class ExampleRouteHandler : public mdml::IRouteHandler {
public:
virtual mdml::Result<std::string> Process(
const std::string& name, const std::string& request_uri
) override
{
// Implement test logic for the route
// handler
return { mdml::NO_ERROR, "Processed" };
}
virtual ~ExampleRouteHandler() = default;
};
}
BEGIN_TEST_SUITE("CgiApplication")
@ -49,6 +64,26 @@ BEGIN_TEST_SUITE("CgiApplication")
);
};
TEST("CgiApplication RegisterRoutes Test")
{
CgiApplication test(
1, simulated_launch::argv, simulated_launch::env
);
mdml::CgiApplication::RouteDictionary routes;
routes.emplace("test", new ExampleRouteHandler());
routes.emplace("test2", new ExampleRouteHandler());
routes.emplace("fasdfdshjk", new ExampleRouteHandler());
test.ImportRoutes(routes);
REQUIRE(routes.size() < 1);
REQUIRE(test.Routes.size() == 3);
REQUIRE(test.Routes.at("test") != nullptr);
};
TEST("CgiApplication::ProcessRequest no REQUEST_URI variable")
{
const char* no_request_env[] = { "PATH=/usr/bin",
@ -62,23 +97,9 @@ BEGIN_TEST_SUITE("CgiApplication")
FIXTURE_TEST("CgiApplication ProcessRequest with valid route")
{
// Define a test oute handler for the "/markdown" route
class ExampleRouteHandler : public IRouteHandler {
public:
virtual Result<std::string> Process(
const std::string& name,
const std::string& request_uri
) override
{
// Implement test logic for the route
// handler
return { mdml::NO_ERROR, "Processed" };
}
virtual ~ExampleRouteHandler() = default;
};
// Add the test route handler to the Routes dictionary
auto handler = CgiApplication::make_ptr<ExampleRouteHandler>();
auto handler = mdml::route::make_ptr<ExampleRouteHandler>();
cgi_app.Routes["markdown"] = handler;
auto result = cgi_app.ProcessRequest();
@ -123,7 +144,7 @@ BEGIN_TEST_SUITE("CgiApplication")
}
virtual ~BadRouteHandler() = default;
};
auto handler = CgiApplication::make_ptr<BadRouteHandler>();
auto handler = mdml::route::make_ptr<BadRouteHandler>();
cgi_app.Routes["markdown"] = handler;
REQUIRE_THROWS_AS(

View File

@ -40,6 +40,19 @@ BEGIN_TEST_SUITE("MarkdownRouteHandler Unit Tests")
REQUIRE_NOTHROW([]() { MarkdownRouteHandler test; }());
}
TEST("MarkdownRouteHandler Static Route Generator")
{
REQUIRE_NOTHROW([]() {
auto routes = MarkdownRouteHandler::GenerateRoutes(
"content", "templates/main.thtml"
);
REQUIRE(routes.size() > 0);
REQUIRE(routes.begin()->first == "test");
}());
}
struct TestFixture {
MarkdownRouteHandler test_obj;
};
@ -51,7 +64,7 @@ BEGIN_TEST_SUITE("MarkdownRouteHandler Unit Tests")
SECTION("File exists")
{
REQUIRE_NOTHROW([&]() {
test_obj.LoadTemplate("data/test.thtml");
test_obj.LoadTemplate("templates/main.thtml");
}());
REQUIRE(false == test_obj.GetHtmlData().empty());
@ -59,7 +72,7 @@ BEGIN_TEST_SUITE("MarkdownRouteHandler Unit Tests")
SECTION("File does not exists")
{
REQUIRE_THROWS([&]() {
test_obj.LoadTemplate("not-found.txt");
test_obj.LoadTemplate("templates/not-found.txt");
}());
}
}
@ -70,7 +83,7 @@ BEGIN_TEST_SUITE("MarkdownRouteHandler Unit Tests")
SECTION("File exists")
{
REQUIRE_NOTHROW([&]() {
test_obj.LoadMarkdown("data/test.md");
test_obj.LoadMarkdown("content/test.md");
}());
REQUIRE(false == test_obj.GetMarkdownData().empty());
@ -78,7 +91,7 @@ BEGIN_TEST_SUITE("MarkdownRouteHandler Unit Tests")
SECTION("File does not exists")
{
REQUIRE_THROWS([&]() {
test_obj.LoadMarkdown("not-found.txt");
test_obj.LoadMarkdown("content/not-found.txt");
}());
}
}
@ -88,8 +101,8 @@ BEGIN_TEST_SUITE("MarkdownRouteHandler Unit Tests")
test_obj.OutputStream = buffer;
REQUIRE_NOTHROW([&]() {
test_obj.LoadTemplate("data/test.thtml");
test_obj.LoadMarkdown("data/test.md");
test_obj.LoadTemplate("templates/main.thtml");
test_obj.LoadMarkdown("content/test.md");
test_obj.Process("test", "test?param=1");
}());