(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.
This commit is contained in:
Hayden Hargreaves 2025-10-17 13:08:37 -07:00
parent cf3b57c8e4
commit 7587e493d7
6 changed files with 184 additions and 70 deletions

View File

@ -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)

62
lib/fileSystem.cpp Normal file
View File

@ -0,0 +1,62 @@
#include "fileSystem.h"
#include "util.h"
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <string>
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();
}

71
lib/fileSystem.h Normal file
View File

@ -0,0 +1,71 @@
#ifndef FILESYSTEM_H
#define FILESYSTEM_H
#include <string>
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

View File

@ -1,49 +1,19 @@
#include "parser.h"
#include "fileSystem.h"
#include "inlineNode.h"
#include "structureNode.h"
#include "util.h"
#include <algorithm>
#include <cctype>
#include <fstream>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
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

View File

@ -1,6 +1,7 @@
#ifndef PARSER_H
#define PARSER_H
#include "fileSystem.h"
#include "node.h"
#include <iostream>
#include <memory>
@ -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<any> 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)
*/

View File

@ -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();
}