diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 2f9e96c..3c64889 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -17,7 +17,9 @@ jobs: run: | sudo apt update sudo apt install -y g++ catch2 libmd4c-dev libmd4c-html0-dev libtomlplusplus-dev - - name: make - run: make -C src - - name: make check - run: make check -C src + - name: make all + run: make all + - name: make test + run: make test + - name: make clean + run: make clean diff --git a/.gitignore b/.gitignore index c528bab..3b73a2c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ posts/ src/build/ /src/blog /src/tests +/src/.cache/ *.o diff --git a/src/config/config.cpp b/src/config/config.cpp index 4deede6..37b7c6e 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -5,10 +5,10 @@ #include #include -Config get_config(const std::string &CONFIG_FILE) { +Config get_config(const std::string &CONFIGURATION) { toml::table toml_table; try { - toml_table = toml::parse_file(CONFIG_FILE); + toml_table = toml::parse(CONFIGURATION); } catch (const toml::parse_error &err) { std::cerr << "Parsing failed:" << std::endl << err << std::endl; } diff --git a/src/config/config.hpp b/src/config/config.hpp index 30e25ee..1c40f89 100644 --- a/src/config/config.hpp +++ b/src/config/config.hpp @@ -17,4 +17,7 @@ struct Config { General general; }; -Config get_config(const std::string &CONFIG_FILE); +// @brief Load config into `Config` struct +// @param CONFIGURATION content of `.toml` file containing configuration +// @return Config with parsed configuration +Config get_config(const std::string &CONFIGURATION); diff --git a/src/format/format.cpp b/src/format/format.cpp index 5bf4f23..68516de 100644 --- a/src/format/format.cpp +++ b/src/format/format.cpp @@ -1,6 +1,4 @@ #include "markdown/markdown.hpp" -#include -#include #include static bool replace_once(std::string &str, const std::string &from, @@ -11,20 +9,3 @@ static bool replace_once(std::string &str, const std::string &from, str.replace(start_pos, from.length(), to); return true; } - -std::string inject_html_into_template(const std::string &FILE_NAME, - const std::string &TEMPLATE, - const std::string &HTML) { - std::string result; - std::ifstream file(FILE_NAME); - if (!file.is_open()) { - return ""; - } - std::ostringstream ss; - ss << file.rdbuf(); - result = ss.str(); - if (replace_once(result, TEMPLATE, HTML)) { - return result; - } - return ""; -} diff --git a/src/io/io.cpp b/src/io/io.cpp new file mode 100644 index 0000000..444c436 --- /dev/null +++ b/src/io/io.cpp @@ -0,0 +1,36 @@ +#include "io/io.hpp" +#include +#include +#include +#include + +std::string read_file(const std::string &FILE_PATH) { + std::ifstream file(FILE_PATH); + if (!file) { + throw std::runtime_error("Error while opening file at " + FILE_PATH); + } + std::stringstream buffer; + buffer << file.rdbuf(); + + if (file.bad()) { + throw std::runtime_error("Error while reading file at " + FILE_PATH); + } + + return buffer.str(); +} + +void write_file(const std::string &FILE_PATH, const std::string &CONTENT) { + std::ofstream out(FILE_PATH); + if (!out) { + throw std::runtime_error("Error while opening file for writing at " + + FILE_PATH); + } + + out << CONTENT; + + if (!out) { + throw std::runtime_error("Error while writing to the " + FILE_PATH); + } + + out.close(); +} diff --git a/src/io/io.hpp b/src/io/io.hpp new file mode 100644 index 0000000..a43df36 --- /dev/null +++ b/src/io/io.hpp @@ -0,0 +1,13 @@ +#include + +// @brief Read file from given path +// @param FILE PATH Path to the file +// @return File content +// @throws std::runtime_error if any errors +std::string read_file(const std::string &FILE_PATH); + +/// @brief Writes the given content into a file. +/// @param FILE_PATH Path to the file to write. +/// @param CONTENT Content to write into the file. +/// @throws std::runtime_error if the file cannot be opened or written. +void write_file(const std::string &FILE_PATH, const std::string &CONTENT); diff --git a/src/makefile b/src/makefile index d921679..fa8f076 100644 --- a/src/makefile +++ b/src/makefile @@ -8,14 +8,16 @@ BUILD_DIR = build SOURCES = main.cpp \ config/config.cpp \ format/format.cpp \ - markdown/markdown.cpp + markdown/markdown.cpp \ + io/io.cpp OBJECTS = $(SOURCES:%.cpp=$(BUILD_DIR)/%.o) TEST_SOURCES = tests.cpp \ config/config.cpp \ format/format.cpp \ - markdown/markdown.cpp + markdown/markdown.cpp \ + io/io.cpp TEST_OBJECTS = $(TEST_SOURCES:%.cpp=$(BUILD_DIR)/%.o) diff --git a/src/markdown/markdown.cpp b/src/markdown/markdown.cpp index 34bf0c5..81790c5 100644 --- a/src/markdown/markdown.cpp +++ b/src/markdown/markdown.cpp @@ -1,6 +1,5 @@ #include "markdown.hpp" #include -#include #include #include #include @@ -8,21 +7,21 @@ #include #include -std::optional get_metadata(const std::string &FILE_PATH) { - std::fstream file(FILE_PATH); +std::optional get_metadata(const std::string &FILE_CONTENTS) { + std::stringstream file_contents(FILE_CONTENTS); std::stringstream metadata_s; std::string buffer; /* if there's no ---, then no metadata in file there is */ - if (!getline(file, buffer) || buffer != "---") { - std::cerr << "File " << FILE_PATH << " does not contain metadata. " + if (!getline(file_contents, buffer) || buffer != "---") { + std::clog << "File does not contain any metadata. Is this intentional?" << std::endl; return std::nullopt; } std::map metadata_map; - while (getline(file, buffer)) { + while (getline(file_contents, buffer)) { if (buffer == "---") { break; } @@ -69,15 +68,15 @@ static void html_callback(const MD_CHAR *data, MD_SIZE size, void *userdata) { out->append(data, size); } -std::string get_html(const std::string &FILE_PATH) { - std::fstream file(FILE_PATH); +std::string get_html(const std::string &FILE_CONTENTS) { + std::stringstream file_contents(FILE_CONTENTS); std::string buffer, file_string, out; - getline(file, buffer); + getline(file_contents, buffer); bool is_metadata = buffer == "---"; /* skip metadata */ if (is_metadata) { - while (getline(file, buffer)) { + while (getline(file_contents, buffer)) { if (buffer == "---") { break; } @@ -86,7 +85,7 @@ std::string get_html(const std::string &FILE_PATH) { file_string += buffer + '\n'; } - while (getline(file, buffer)) { + while (getline(file_contents, buffer)) { file_string += buffer + '\n'; } diff --git a/src/tests.cpp b/src/tests.cpp index b0eb144..e13575e 100644 --- a/src/tests.cpp +++ b/src/tests.cpp @@ -1,5 +1,6 @@ #include "config/config.hpp" #include "format/format.hpp" +#include "io/io.hpp" #include "markdown/markdown.hpp" #include #include @@ -7,10 +8,88 @@ #include #include -TEST_CASE("Checking config file read", "[config]") { - const std::string FILE_NAME = "test_files/config.toml"; +namespace TestFiles { +constexpr const char *config_toml = R"([general] +lang = "en" +title = "Blog" - Config config = get_config(FILE_NAME); +[contact] +author = "author" +email = "email@example.com" +signal = "signal url" +)"; + +constexpr const char *formatter_html = R"( + + + + + Document + + + {{replace_me}} + +)"; + +constexpr const char *no_metadata_post_md = R"(# This markdown has no metadata. +That's bad. Don't do it. It's for tests only.)"; + +constexpr const char *post_md = R"(--- +[metadata] +author = "joseph" +date = 2026-01-01 +title = "Example of a blog post" +tags = ["blog", "test", "lalilulelo"] +--- +# This is an example post + +In this example we will do a couple of cool markdown things. + +## This is a h2 header +*This text is written in italic!* +### Look at this! +**This text is BOLD!** + +## Hello world! +```cpp +#include + +int main() { + std::cout << "Hello world!" << std::endl; +} +``` +This is super cool! + +## Time to link some random website! +This [should be clickable](https://example.com) + +## Image test +![this should show an image](./image.png) + +## This is an ordered list +1. one +2. six +3. seven + +## This is an unordered one +- six +- nine +- six +- nine)"; +} // namespace TestFiles + +// IO tests +TEST_CASE("Test reading a file successful") { + std::string result = read_file("./test_files/config.toml"); + REQUIRE(result == TestFiles::config_toml); +} + +TEST_CASE("Test reading a file that doesn't exist") { + REQUIRE_THROWS(read_file("this/path/doesnt/exist")); +} + +TEST_CASE("Checking config file read", "[config]") { + Config config = get_config(TestFiles::config_toml); REQUIRE(config.contact.author == "author"); REQUIRE(config.contact.email == "email@example.com"); @@ -20,8 +99,7 @@ TEST_CASE("Checking config file read", "[config]") { } TEST_CASE("get_metadata function data correctness test") { - const std::string FILE_NAME = "./test_files/post.md"; - Metadata result = get_metadata(FILE_NAME).value(); + Metadata result = get_metadata(TestFiles::post_md).value(); std::string expected_author = "joseph"; struct tm datetime = {0}; @@ -41,14 +119,13 @@ TEST_CASE("get_metadata function data correctness test") { } TEST_CASE("get_metadata function test with a file that doesn't have metadata") { - const std::string FILE_NAME = ".test_files/no_metadata_post.md"; - std::optional result = get_metadata(FILE_NAME); + std::optional result = get_metadata(TestFiles::no_metadata_post_md); REQUIRE(result == std::nullopt); } TEST_CASE("get_html function parsing test") { - const std::string INPUT = "./test_files/post.md"; - const std::string EXPECTED_OUTPUT = R"HTML(

This is an example post

+ const std::string EXPECTED_OUTPUT = + R"HTML(

This is an example post

In this example we will do a couple of cool markdown things.

This is a h2 header

This text is written in italic!

@@ -81,24 +158,5 @@ int main() { )HTML"; - REQUIRE(get_html(INPUT) == EXPECTED_OUTPUT); -} - -TEST_CASE("formatter injection test") { - const std::string FILE_NAME = "./test_files/formatter.html"; - const std::string EXPECTED_OUTPUT = R"( - - - - - Document - - - I'm replaced! - - -)"; - const std::string output = - inject_html_into_template(FILE_NAME, "{{replace_me}}", "I'm replaced!"); - REQUIRE(EXPECTED_OUTPUT == output); + REQUIRE(get_html(TestFiles::post_md) == EXPECTED_OUTPUT); }