(FEAT) Abstracted file system into its own class. #20
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@
|
||||
/build/*
|
||||
parser
|
||||
/.vscode
|
||||
/*.html
|
||||
|
||||
8
Makefile
8
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)
|
||||
|
||||
6
input.md
6
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 `<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
62
lib/fileSystem.cpp
Normal 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
71
lib/fileSystem.h
Normal 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
|
||||
@ -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
|
||||
|
||||
48
lib/parser.h
48
lib/parser.h
@ -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)
|
||||
*/
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user