(FEAT) Abstracted file system into its own class. #20

Merged
shultzp1 merged 2 commits from feature/file-handler into main 2025-10-18 14:17:02 -07:00
8 changed files with 188 additions and 76 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@
/build/*
parser
/.vscode
/*.html

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)

View File

@ -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 `<h1>Heading</h1>`, `*text*` to `<em>text</em>`).
specific syntax (e.g., ` Heading` to `\<h1\>Heading\</h1\>`, `*text*` to `\<em\>text\</em\>`).
### 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 `<h1>`or `*`
to `<em>`. This provides O(1) average-case lookup time.
and retrieve the HTML equivalent for each Markdown tag. For example, you could map `#` to `\<h1\>`or `*`
to `\<em\>`. This provides O(1) average-case lookup time.
### Contribution Policy

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,21 +30,22 @@ 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.");
return;
this->filesystem.WriteOutputFile(this->DOM->ToHtml());
}
// Read the file into a single string
std::stringstream buffer;
buffer << input_file.rdbuf();
this->content = buffer.str();
input_file.close();
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;
}
// 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.
@ -84,11 +87,16 @@ protected:
*/
std::unique_ptr<Node> DOM;
// NOTE: We need a stack, just not sure what goes in it yet
// 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 +105,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();
}