From 7587e493d7fdd2f72fbe61a5a67a1939b769e56e Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Fri, 17 Oct 2025 13:08:37 -0700 Subject: [PATCH 1/2] (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(); } From 2fdab0134a08ad415b75ffa05afd77d700fa171b Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Fri, 17 Oct 2025 13:15:42 -0700 Subject: [PATCH 2/2] (FIX): Small file updates. We won't need a stack yet. --- .gitignore | 1 + input.md | 6 +++--- lib/parser.h | 3 --- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 3943bed..be655f3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /build/* parser /.vscode +/*.html diff --git a/input.md b/input.md index 77ed442..c031c06 100644 --- a/input.md +++ b/input.md @@ -4,7 +4,7 @@ The goal is to create a program that reads a file containing text formatted in a simple version of Markdown and converts it into a valid HTML file. The program will need to identify and translate -specific syntax (e.g., `# Heading` to `

Heading

`, `*text*` to `text`). +specific syntax (e.g., ` Heading` to `\Heading\`, `*text*` to `\text\`). ### Implementation Requirements (Generated by Gemini) @@ -42,8 +42,8 @@ that all tags are correctly opened and closed. Your presentation can visually de with a stack diagram. Hash Map or Map: A hash map (std::unordered_map) or a map (std::map) can be used to efficiently store -and retrieve the HTML equivalent for each Markdown tag. For example, you could map `#` to `

`or `*` -to ``. This provides O(1) average-case lookup time. +and retrieve the HTML equivalent for each Markdown tag. For example, you could map `#` to `\`or `*` +to `\`. This provides O(1) average-case lookup time. ### Contribution Policy diff --git a/lib/parser.h b/lib/parser.h index 509f1d6..cd24c74 100644 --- a/lib/parser.h +++ b/lib/parser.h @@ -87,9 +87,6 @@ protected: */ std::unique_ptr DOM; - // NOTE: We need a stack, just not sure what goes in it yet - // std::stack stack; - private: /** * @brief Normalize the input stream.