From 7587e493d7fdd2f72fbe61a5a67a1939b769e56e Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Fri, 17 Oct 2025 13:08:37 -0700 Subject: [PATCH] (FEAT) Abstracted file system into its own class. This class is then composed into the parser class and called to write the expected outputs. This is a huge step towards the final product. Furthermore, the output it being written to the file generated. Until the CLI is implemented, this is the best it will do. --- Makefile | 8 +++--- lib/fileSystem.cpp | 62 ++++++++++++++++++++++++++++++++++++++++ lib/fileSystem.h | 71 ++++++++++++++++++++++++++++++++++++++++++++++ lib/parser.cpp | 61 ++++++++++----------------------------- lib/parser.h | 45 ++++++++++++++++++----------- src/main.cpp | 7 +++-- 6 files changed, 184 insertions(+), 70 deletions(-) create mode 100644 lib/fileSystem.cpp create mode 100644 lib/fileSystem.h diff --git a/Makefile b/Makefile index 9f77ed3..4380f7b 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Define the C++ compiler and flags CXX = g++ -CXXFLAGS = -Wall -g +CXXFLAGS = -Wall -g -fPIE # Directories BUILD_DIR = build @@ -29,15 +29,15 @@ $(BUILD_DIR): mkdir -p $(BUILD_DIR) $(TARGET): $(OBJECTS) - $(CXX) $(CXXFLAGS) $(INCLUDES) $^ -o $@ + $(CXX) $(CXXFLAGS) $(INCLUDES) $^ -o $@ -pie # Generic rule for all .cpp files in the src/ directory $(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp - $(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@ + $(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@ -pie # Generic rule for all .cpp files in the lib/ directory $(BUILD_DIR)/%.o: $(LIB_DIR)/%.cpp - $(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@ + $(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@ -pie test: all ./$(TARGET) diff --git a/lib/fileSystem.cpp b/lib/fileSystem.cpp new file mode 100644 index 0000000..3742b7a --- /dev/null +++ b/lib/fileSystem.cpp @@ -0,0 +1,62 @@ +#include "fileSystem.h" +#include "util.h" + +#include +#include +#include +#include + +FileSystem::FileSystem(string input_file_path, string output_file_path) { + removeWhitespace(input_file_path); + removeWhitespace(output_file_path); + + if (input_file_path.empty()) + throw std::runtime_error("input_file_path cannot be empty"); + + this->input_file_path = input_file_path; + + this->output_file_path = output_file_path; + if (this->output_file_path.empty()) + GenerateOutputFilePath(); +}; + +void FileSystem::GenerateOutputFilePath() { + if (this->input_file_path.empty()) + throw std::runtime_error("Cannot generate output path from empty input."); + + int ext_idx = this->input_file_path.find_last_of('.'); + string output_cleaned = this->input_file_path.substr(0, ext_idx) + ".html"; + this->output_file_path = output_cleaned; +} + +std::string FileSystem::ReadInputFile() { + // Cannot read file if the path does not exist + if (this->input_file_path.empty()) + throw std::runtime_error("Cannot open file: path was not provided."); + + std::ifstream input_file(this->input_file_path); + + if (!input_file.is_open()) + throw std::runtime_error("Failed to open input file."); + + // Read the file into a single string using a string stream + std::stringstream buffer; + buffer << input_file.rdbuf(); + input_file.close(); + + return buffer.str(); +} + +void FileSystem::WriteOutputFile(std::string content) { + // Cannot write to file if the path does not exist + if (this->output_file_path.empty()) + throw std::runtime_error("Cannot open file: path was not provided."); + + std::ofstream output_file(this->output_file_path); + + if (!output_file.is_open()) + throw std::runtime_error("Failed to open output file."); + + output_file << content; + output_file.close(); +} diff --git a/lib/fileSystem.h b/lib/fileSystem.h new file mode 100644 index 0000000..b1e5ac2 --- /dev/null +++ b/lib/fileSystem.h @@ -0,0 +1,71 @@ +#ifndef FILESYSTEM_H +#define FILESYSTEM_H + +#include + +using std::string; + +class FileSystem { +public: + FileSystem(string input_file_path, string output_file_path = ""); + + /** + * @brief Read the input file and return its content. + * + * This method will read the file at the input_file_path and create a single + * output string. Each line will be delimited by either `\n` (Unix) or `\r\n` + * (Windows). If the file path does not exist OR the file fails to open, this + * method will throw a run time error. + * + * @return File contents as a single string. + * + * @author Hayden Hargreaves (hhargreaves2006@gmail.com) + */ + string ReadInputFile(); + + /** + * @brief Write the provided string to the output file. + * + * This method will attempt to open the output file and write the content + * provided to the method in the file. If the file does not exist, it will be + * created. If the file path does not exist OR the file fails to open, this + * method will throw a run time error. + * + * @author Hayden Hargreaves (hhargreaves2006@gmail.com) + */ + void WriteOutputFile(string content); + +protected: + /** + * @brief Input file path. + * + * Must be provided by the user. + * + * @author Hayden Hargreaves (hhargreaves2006@gmail.com) + */ + string input_file_path; + + /** + * @brief Output file path. + * + * If not provided, will be generated using the `input_file_path` by removing + * the extension and appending `.html`. + * + * @author Hayden Hargreaves (hhargreaves2006@gmail.com) + */ + string output_file_path; + +private: + /** + * @brief Generate an output file path. + * + * If the user does not provide an output file path, this method can be + * used to generate the path. This is done by simply swapping the `.md` + * with `.html`. + * + * @author Hayden Hargreaves (hhargreaves2006@gmail.com) + */ + void GenerateOutputFilePath(); +}; + +#endif diff --git a/lib/parser.cpp b/lib/parser.cpp index 51bde62..86ab32d 100644 --- a/lib/parser.cpp +++ b/lib/parser.cpp @@ -1,49 +1,19 @@ #include "parser.h" +#include "fileSystem.h" #include "inlineNode.h" #include "structureNode.h" -#include "util.h" #include #include -#include #include -#include -#include #include using std::string; using std::vector; -Parser::Parser(string input_file_path, string output_file_path) { - // NOTE: Remove any white space AROUND the inputs - removeWhitespace(input_file_path); - removeWhitespace(output_file_path); - - if (input_file_path == "") { - throw std::runtime_error("input_file_path cannot be empty"); - } - - this->input_file_path = input_file_path; - - // NOTE: If the user does not provide an output file, then we should construct - // one using the input file with .md swapped with the extension. - if (output_file_path == "") { - int ext_idx = input_file_path.find_last_of('.'); - string output_cleaned = input_file_path.substr(0, ext_idx) + ".html"; - this->output_file_path = output_cleaned; - return; - } - - this->output_file_path = output_file_path; -} - void Parser::Inspect() { - std::cout << "std::string input_file_path: " << this->input_file_path - << std::endl; - std::cout << "std::string output_file_path: " << this->output_file_path - << std::endl; + std::cerr << "Parser::Inspect() is not yet implemented." << std::endl; } -// replace '\r\n' with '\n' void Parser::NormalizeInputStream() { if (this->content.empty()) return; @@ -60,22 +30,23 @@ void Parser::NormalizeInputStream() { this->content.end()); } -void Parser::ParseDocument() { - // Open the input file - std::ifstream input_file(this->input_file_path); +void Parser::WriteOutput() { + if (this->DOM == nullptr) + throw std::runtime_error( + "Cannot write output, output DOM tree does not exist. Please run the " + "Parser::ParserDocument method first."); - if (!input_file.is_open()) { - throw std::runtime_error("Failed to open input file."); + this->filesystem.WriteOutputFile(this->DOM->ToHtml()); +} + +void Parser::ParseDocument() { + try { + this->content = this->filesystem.ReadInputFile(); + } catch (const std::runtime_error &e) { + std::cerr << "Caught an error: " << e.what() << std::endl; return; } - // Read the file into a single string - std::stringstream buffer; - buffer << input_file.rdbuf(); - this->content = buffer.str(); - - input_file.close(); - // Remove the windows BS NormalizeInputStream(); @@ -88,8 +59,6 @@ void Parser::ParseDocument() { if (block != nullptr) this->DOM->AddChild(std::move(block)); } - - std::cout << this->DOM->ToHtml(); } // All this does is pick which subparser to call diff --git a/lib/parser.h b/lib/parser.h index ce7590d..509f1d6 100644 --- a/lib/parser.h +++ b/lib/parser.h @@ -1,6 +1,7 @@ #ifndef PARSER_H #define PARSER_H +#include "fileSystem.h" #include "node.h" #include #include @@ -25,7 +26,8 @@ using std::vector; */ class Parser { public: - Parser(string input_file_path, string output_file_path = ""); + Parser(string input_file_path, string output_file_path = "") + : filesystem(input_file_path, output_file_path) {}; /** * @brief Inspect (view) contents of the class. @@ -37,7 +39,6 @@ public: void Inspect(); /** - * * @brief Parse an entire document. * * This function will be called to yield the result. This is the entry point @@ -53,25 +54,27 @@ public: */ void ParseDocument(); + /** + * @brief Write the output to the file. + * + * Once the tree is generated, this method should be called to actually + * write the output. Having this functionality separate allows for more + * portability. + * + * @author Hayden Hargreaves (hhargreaves2006@gmail.com) + */ + void WriteOutput(); + protected: /** - * @brief Input file path. + * @brief File system module to handle file I/O. + * + * Anything requiring file I/O operations will be handled by this module. * - * Must be provided by the user. * * @author Hayden Hargreaves (hhargreaves2006@gmail.com) */ - string input_file_path; - - /** - * @brief Output file path. - * - * If not provided, will be generated using the `input_file_path` by removing - * the extension and appending `.html`. - * - * @author Hayden Hargreaves (hhargreaves2006@gmail.com) - */ - string output_file_path; + FileSystem filesystem; /** * @brief Parser generated tree. @@ -88,7 +91,15 @@ protected: // std::stack stack; private: - // windows... >:( + /** + * @brief Normalize the input stream. + * + * Replaces all `\r\n` with just `\n` since that is what the parser expects. + * Then removes any left over `\r` elements in the stream. If the stream is + * empty this method does nothing. + * + * @author Hayden Hargreaves (hhargreaves2006@gmail.com) + */ void NormalizeInputStream(); /** @@ -97,7 +108,7 @@ private: * How does this function work... * This is where the magic happens. * - * @return DOMNode, once exists + * @return Node, to be appended to the callers children. * * @author Hayden Hargreaves (hhargreaves2006@gmail.com) */ diff --git a/src/main.cpp b/src/main.cpp index 5fc78ed..cf4be4c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -79,9 +79,10 @@ void test_input(int argc, char **argv) { } int main(int argc, char **argv) { - Parser p("input.md"); + Parser p("syntax.md"); p.ParseDocument(); + p.WriteOutput(); - Parser p2("README.md"); - p2.ParseDocument(); + // Parser p2("README.md"); + // p2.ParseDocument(); }