219 lines
7.8 KiB
C++
219 lines
7.8 KiB
C++
/*
|
|
* Copyright (C) 2022 luca0N!
|
|
*
|
|
* This file is part of Static Website Generator (swg).
|
|
*
|
|
* Static Website Generator (swg) is free software: you can redistribute it
|
|
* and/or modify it under the terms of the version 3 of the GNU Lesser General
|
|
* Public License as published by the Free Software Foundation.
|
|
*
|
|
* Static Website Generator (swg) is distributed in the hope that it will be
|
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with Static Website Generator (swg). If not, see
|
|
* <https://www.gnu.org/licenses/>.
|
|
*
|
|
* Contact luca0N! by e-mail: <luca0n [at] luca0n [dot] com>.
|
|
*/
|
|
|
|
#include <iostream>
|
|
#include <cstring>
|
|
#include <list>
|
|
#include <regex>
|
|
#include <filesystem>
|
|
#include "time.h"
|
|
|
|
#include "SwgRuntime.hxx"
|
|
#include "SwgContext.hxx"
|
|
#include "ConfigUtils.hxx"
|
|
#include "BlogBuilder.hxx"
|
|
#include "Common.hxx"
|
|
#include "MarkdownParser.hxx"
|
|
#include "Article.hxx"
|
|
|
|
void build_dir_structure(std::string const &path) {
|
|
// Create directory tree, which will be used for the website.
|
|
std::filesystem::path rootDir = get_output_path(path);
|
|
|
|
// Create output directory if it doesn't exist.
|
|
try {
|
|
if(!std::filesystem::exists(rootDir)) std::filesystem::create_directory(rootDir);
|
|
} catch (std::filesystem::filesystem_error const &e) {
|
|
std::cerr << "error: fs error while generating output website directory structure on \""
|
|
<< path << "\": " << e.what() << std::endl;
|
|
exit(RETURN_FAILED_UNKNOWN_ERROR);
|
|
}
|
|
|
|
// Check if a website has already been built.
|
|
//
|
|
// This is a safe measure to prevent overwriting an existing website
|
|
// output, but it can be skipped if explicitly asked by the end-user.
|
|
if (!swg_rt_global_bool("overwrite_existing", false)) {
|
|
std::filesystem::path websiteLock = rootDir /= ".swg_built";
|
|
if (std::filesystem::exists(websiteLock)) {
|
|
std::cout << "error: existing website build detected, exiting...\n";
|
|
exit(RETURN_FAILED_WEBSITE_BUILD_EXISTS);
|
|
}
|
|
}
|
|
}
|
|
|
|
void compile_markdown(std::string const &path, std::string const &md, std::string const &to, Article::Metadata *metadata = NULL) {
|
|
FILE *articleOutput = fopen(to.c_str(), "w");
|
|
|
|
std::string htmlTemplate = get_template(path);
|
|
// NOTE: std::regex requires the C++11 standard.
|
|
std::regex contentPlaceholder("<!--\\[_SWG: \\$CONTENT\\]-->");
|
|
|
|
// Write basic article metadata to article if this is an article.
|
|
std::string article_html;
|
|
if (metadata != NULL) {
|
|
// Get time information.
|
|
article_html = "<h1>";
|
|
article_html += metadata->title;
|
|
article_html += "</h1>\n<span><b>Published on ";
|
|
if (metadata->publish_str[0] == '\0') {
|
|
struct tm *publish_tm = gmtime(&(metadata->publish_ts));
|
|
//article_html += ctime(&(metadata->publish_ts));
|
|
article_html += get_hr_month(publish_tm->tm_mon);
|
|
article_html += " ";
|
|
article_html += std::to_string(publish_tm->tm_mday);
|
|
article_html += ", ";
|
|
article_html += std::to_string(publish_tm->tm_year + 1900);
|
|
} else
|
|
article_html += metadata->publish_str;
|
|
article_html += "</b></span><br/>\n<span><b>Written by ";
|
|
article_html += metadata->authors;
|
|
article_html += "</b></span><br/>";
|
|
}
|
|
|
|
// Concat article to main final content.
|
|
article_html += MarkdownParser::make_html(md);
|
|
|
|
std::string articleContents = std::regex_replace(htmlTemplate, contentPlaceholder, article_html);
|
|
|
|
fputs(articleContents.c_str(), articleOutput);
|
|
fclose(articleOutput);
|
|
}
|
|
|
|
void build_website(SwgContext &ctx, std::string const &path) {
|
|
build_dir_structure(path);
|
|
|
|
// Build regular webpages (index, privacy notice, etc)
|
|
std::filesystem::path ws = path;
|
|
// Copy all non-MD files to output (to include assets, for instance).
|
|
try {
|
|
for (auto const &ws_entry : std::filesystem::recursive_directory_iterator(ws)) {
|
|
// Skip all files inside the "output" directory.
|
|
if (ws_entry.path().string().find(path + "output/") == 0 ||
|
|
// Skip all files inside the ".swg_ignore" directory.
|
|
ws_entry.path().string().find(path + ".swg_ignore/") == 0) continue;
|
|
std::string currentFile = getFilename(ws_entry.path());
|
|
// Assuming this is a non-MD file, copy it to the output.
|
|
if (ws_entry.is_regular_file() &&
|
|
!is_special_file(getFilename(ws_entry.path()))) {
|
|
// Subdirectories should be respected! If this
|
|
// file is in a subdirectory, it should also be
|
|
// created in the output.
|
|
std::filesystem::path copied_subdir = path;
|
|
copied_subdir /= "output";
|
|
copied_subdir /= ws_entry.path().string()
|
|
.substr(path.length(),
|
|
ws_entry.path().string().length() - path.length() -
|
|
getFilename(ws_entry.path()).length());
|
|
if (!std::filesystem::exists(copied_subdir)) {
|
|
std::filesystem::create_directories(copied_subdir);
|
|
std::cout << "Creating: " << copied_subdir << "\n";
|
|
}
|
|
// If this isn't a Markdown file, just copy it.
|
|
bool compiled = false;
|
|
if (currentFile.find(".md") == std::string::npos) {
|
|
try {
|
|
std::filesystem::copy(ws_entry, copied_subdir);
|
|
} catch (std::filesystem::filesystem_error const &cpyErr) {
|
|
// Ignore exception if it was
|
|
// thrown due to an existing
|
|
// file error while copying.
|
|
if (cpyErr.code().value() != 17)
|
|
throw cpyErr;
|
|
}
|
|
} else {
|
|
// This is a Markdown file. Compile a HTML version.
|
|
std::filesystem::path output_html = copied_subdir;
|
|
// Remove the .md extension and use the HTML extension instead
|
|
output_html /= currentFile.substr(0, currentFile.length() - 3) + ".html";
|
|
compile_markdown(path, ws_entry.path(), output_html);
|
|
compiled = true;
|
|
}
|
|
std::cout << (compiled ? "Compiled: " : "Copied: ") <<
|
|
ws_entry.path().string().substr(path.length()) << "\n";
|
|
}
|
|
}
|
|
} catch (std::filesystem::filesystem_error const &e) {
|
|
std::cerr << "error: could not copy website assets to output: " << e.what() << std::endl;
|
|
exit(RETURN_FAILED_UNKNOWN_ERROR);
|
|
}
|
|
|
|
// Blog lookup
|
|
bool failed = false;
|
|
|
|
for (Blog *b : ctx.blogs) {
|
|
std::list<std::string> parsedArticles = { },
|
|
failedArticles = { };
|
|
|
|
// std::filesystem requires the C++17 standard.
|
|
std::string relativePath = path;
|
|
relativePath += "/";
|
|
relativePath += b->path;
|
|
const std::filesystem::path blogPath(relativePath);
|
|
try {
|
|
for (auto const &dir_entry : std::filesystem::recursive_directory_iterator(blogPath)) {
|
|
// Skip all files inside the "output" directory.
|
|
if (dir_entry.path().string().find(path + "output/") == 0) continue;
|
|
// Directory item iteration
|
|
if (verbose) std::cout << "\t" << dir_entry << std::endl;
|
|
|
|
// Check if the item is a Markdown file.
|
|
std::string filename = getFilename(dir_entry.path());
|
|
if (dir_entry.is_regular_file() &&
|
|
filename.find(".md") == filename.length() - 3) {
|
|
// Markdown files should be insite a YYYY/MM directory.
|
|
if (!is_valid_article(relativePath, dir_entry.path()))
|
|
failedArticles.insert(failedArticles.end(), dir_entry.path());
|
|
else parsedArticles.insert(parsedArticles.end(), dir_entry.path());
|
|
}
|
|
}
|
|
} catch (std::filesystem::filesystem_error const &e) {
|
|
std::cerr << "error: fs error while attempting to read path for "
|
|
<< b->name << ": " << e.what() << std::endl;
|
|
failed = true;
|
|
}
|
|
|
|
/*std::cout << "Parsed " << parsedArticles << " articles;\n" <<
|
|
"Failed to parse " << failedArticles << " articles\n";*/
|
|
|
|
for (std::string const &a : parsedArticles)
|
|
std::cout << "Parsed: " << a << "\n";
|
|
for (std::string const &a : failedArticles)
|
|
std::cout << "Unable to parse: " << a << "\n";
|
|
|
|
build_blog_structure(path, relativePath, parsedArticles, b);
|
|
|
|
free(b);
|
|
}
|
|
|
|
if (failed) {
|
|
std::cerr << "Refusing to proceed due to previous errors\n";
|
|
exit(RETURN_FAILED_CONFIG_INVALID_SYNTAX);
|
|
}
|
|
|
|
// Website blog
|
|
|
|
// Append blog to website
|
|
//ctx.appendBlog(blog1);
|
|
|
|
// Generate website
|
|
//ctx.generateWebsite();
|
|
}
|