From 9b8d2f566e272575238066ebc75a5cc922483c0a Mon Sep 17 00:00:00 2001 From: S David <2100425+s-daveb@users.noreply.github.com> Date: Fri, 24 Nov 2023 15:14:16 -0500 Subject: [PATCH] Add a directory scanner to detect application routes Close #24 --- apps/mdml.cpp | 11 +++- data/{ => content}/test.md | 0 data/{test.thtml => templates/main.thtml} | 0 include/CgiApplication.hpp | 14 +++-- include/MarkdownRouteHandler.hpp | 24 +++++++-- include/types.hpp | 12 +++++ src/MarkdownRouteHandler.cpp | 66 ++++++++++++++++++++--- tests/CgiApplication-test.cpp | 53 ++++++++++++------ tests/MarkdownRouteHandler-test.cpp | 25 ++++++--- 9 files changed, 162 insertions(+), 43 deletions(-) rename data/{ => content}/test.md (100%) rename data/{test.thtml => templates/main.thtml} (100%) diff --git a/apps/mdml.cpp b/apps/mdml.cpp index 533483f..9f52cd4 100644 --- a/apps/mdml.cpp +++ b/apps/mdml.cpp @@ -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) { diff --git a/data/test.md b/data/content/test.md similarity index 100% rename from data/test.md rename to data/content/test.md diff --git a/data/test.thtml b/data/templates/main.thtml similarity index 100% rename from data/test.thtml rename to data/templates/main.thtml diff --git a/include/CgiApplication.hpp b/include/CgiApplication.hpp index 059f694..bc9e603 100644 --- a/include/CgiApplication.hpp +++ b/include/CgiApplication.hpp @@ -23,20 +23,18 @@ class IRouteHandler; class CgiApplication : public Application { public: - template - using ptr = std::shared_ptr; - template - inline static ptr make_ptr() - { - return std::make_shared(); - } - using RouteDictionary = std::map>; + using RouteDictionary = Dictionary>; CgiApplication(int argc, c::const_string argv[], c::const_string env[]); virtual ~CgiApplication(); Result 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 */ diff --git a/include/MarkdownRouteHandler.hpp b/include/MarkdownRouteHandler.hpp index 67291eb..aba7500 100644 --- a/include/MarkdownRouteHandler.hpp +++ b/include/MarkdownRouteHandler.hpp @@ -12,6 +12,7 @@ #include "types.hpp" #include +#include #include 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 Process( const std::string& name, const std::string& request_uri ); - std::reference_wrapper OutputStream; - #ifdef TESTING inline std::string& GetHtmlData() { return html_data; } inline std::string& GetMarkdownData() { return markdown_data; } #endif + + static Dictionary> GenerateRoutes( + std::filesystem::path content_dir, + std::filesystem::path main_template + ); + + std::reference_wrapper 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; }; diff --git a/include/types.hpp b/include/types.hpp index 6c859db..d25ac49 100644 --- a/include/types.hpp +++ b/include/types.hpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -21,6 +22,17 @@ using string = char*; using const_string = const char*; } +namespace route { +template +using ptr = std::shared_ptr; +template +inline static ptr +make_ptr() +{ + return std::make_shared(); +} +} + enum error_t : bool { NO_ERROR = false, ERROR = true }; using count_t = size_t; diff --git a/src/MarkdownRouteHandler.cpp b/src/MarkdownRouteHandler.cpp index 37d5b43..caef2b2 100644 --- a/src/MarkdownRouteHandler.cpp +++ b/src/MarkdownRouteHandler.cpp @@ -18,16 +18,19 @@ #include #include #include +#include #include 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> +MarkdownRouteHandler::GenerateRoutes( + std::filesystem::path content_dir, std::filesystem::path main_template +) +{ + if (content_dir.is_relative()) { + content_dir = work_dir / content_dir; + } + Dictionary> 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 diff --git a/tests/CgiApplication-test.cpp b/tests/CgiApplication-test.cpp index 874d793..ab17022 100644 --- a/tests/CgiApplication-test.cpp +++ b/tests/CgiApplication-test.cpp @@ -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 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 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(); + auto handler = mdml::route::make_ptr(); 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(); + auto handler = mdml::route::make_ptr(); cgi_app.Routes["markdown"] = handler; REQUIRE_THROWS_AS( diff --git a/tests/MarkdownRouteHandler-test.cpp b/tests/MarkdownRouteHandler-test.cpp index f99f0be..d3a1715 100644 --- a/tests/MarkdownRouteHandler-test.cpp +++ b/tests/MarkdownRouteHandler-test.cpp @@ -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"); }());