Compare commits

..

10 Commits

Author SHA1 Message Date
luca0N! 340046a264
Fix URL escape bug
Fixed a bug which caused the Markdown interpreter to ignore escaped '-' characters.
2022-09-12 07:28:42 -03:00
luca0N! eb04e715d5
Open article links on new tab
The catalog now opens new articles on new tabs using the `target'
parameter for the `a' tag.
2022-06-09 22:59:09 -03:00
luca0N! 9eb5c21451
Add help command
Added a help command line option that shows command line syntax and
available options.
2022-06-09 22:44:44 -03:00
luca0N! 5058545c22
Add verbose cmdline option
Added a verbose cmdline option and moved stdout debug messages there.
2022-06-09 22:35:07 -03:00
luca0N! 8b07320f76
Improve command line parsing
Improve command line argument parser.  This will make it easier to add
code to read command line options (which previously weren't available).
2022-06-09 22:07:08 -03:00
luca0N! 3d44312e93
Add missing include
Added missing include which was left out in a previous minor refactor.
2022-05-24 20:38:34 -03:00
luca0N! 1ca68c7c49
Fix article metadata parser
Fixed two issues regarding the metadata parser. The first issue caused
the parser to ignore line breaks if the length of a line was equal to
the length of the buffer, whereas the second issue caused the parser to
ignore more than it should if it came across a comment whose length was
smaller than the buffer length.
2022-05-24 20:35:26 -03:00
luca0N! b58db4b9e7
Fix character escape bug
Fixed a bug which caused MarkdownParser to print the leading backslash
when escaping special Markdown characters.
2022-05-24 20:02:41 -03:00
luca0N! d97ef91db8
Minor code refactoring
Moved certain functions to proper locations and added new source files
for minor refactoring.
2022-05-02 20:58:58 -03:00
luca0N! 647ad56349
Add custom localized timestamp override
Add support for a custom key in the article metadata that allows users
to override the default date string (e.g. January 1, 1970).  This is
useful for other locales.
2022-04-27 20:51:39 -03:00
12 changed files with 395 additions and 235 deletions

View File

@ -24,8 +24,8 @@ CXXFLAGS=-Wall -Wextra -g -O0 -std=c++17
.PHONY: clean
$(PROGRAM_NAME): build/ build/obj/ build/obj/main.o build/obj/ConfigUtils.o build/obj/WebsiteBuilder.o build/obj/SwgRuntime.o build/obj/MarkdownParser.o build/obj/Article.o
$(CXXC) -o $(PROGRAM_NAME) build/obj/main.o build/obj/ConfigUtils.o build/obj/WebsiteBuilder.o build/obj/SwgRuntime.o build/obj/MarkdownParser.o build/obj/Article.o
$(PROGRAM_NAME): build/ build/obj/ build/obj/main.o build/obj/ConfigUtils.o build/obj/WebsiteBuilder.o build/obj/SwgRuntime.o build/obj/MarkdownParser.o build/obj/Article.o build/obj/Common.o build/obj/BlogBuilder.o
$(CXXC) -o $(PROGRAM_NAME) build/obj/main.o build/obj/ConfigUtils.o build/obj/WebsiteBuilder.o build/obj/SwgRuntime.o build/obj/MarkdownParser.o build/obj/Article.o build/obj/Common.o build/obj/BlogBuilder.o
build/obj/main.o: src/main.cxx src/ConfigUtils.hxx src/SwgContext.hxx src/WebsiteBuilder.hxx src/Common.hxx
$(CXXC) $(CXXFLAGS) -o build/obj/main.o -c src/main.cxx
@ -45,6 +45,12 @@ build/obj/MarkdownParser.o: src/MarkdownParser.hxx src/MarkdownParser.cxx
build/obj/Article.o: src/Article.hxx src/Article.cxx
$(CXXC) $(CXXFLAGS) -o build/obj/Article.o -c src/Article.cxx
build/obj/Common.o: src/Common.hxx src/Common.cxx
$(CXXC) $(CXXFLAGS) -o build/obj/Common.o -c src/Common.cxx
build/obj/BlogBuilder.o: src/BlogBuilder.hxx src/BlogBuilder.cxx
$(CXXC) $(CXXFLAGS) -o build/obj/BlogBuilder.o -c src/BlogBuilder.cxx
build/:
mkdir -p build/

View File

@ -54,17 +54,13 @@ namespace Article {
ignore_line = false;
std::list<std::string> cfg_lines = { "" };
while (fgets(buf, buflen, a) != NULL) {
// Ignore blank lines or lines starting with a comment.
if (buf[0] == '#') {
ignore_line = true;
if (strlen(buf) < buflen) continue;
else ignore_line = true;
continue;
} else if (buf[0] == '\n') {
if (ignore_line)
ignore_line = false;
continue;
}
}
int eol = -1;
for (int c = 0; c < buflen; c++) {
// Check if this loop has already done what it's supposed to do by checking if the title has been set.
@ -119,18 +115,21 @@ namespace Article {
bool reading_article_metadata = false;
for (std::string const &line : cfg_lines) {
std::cout << "[!] " << line << "\n";
if (line == "") continue;
// Check namespace
if (reading_article_metadata) {
std::string k_title = "Title=",
k_authors = "Authors=",
k_published = "Published=";
k_published = "Published=",
k_published_str = "PublishedString=";
if (line.find(k_title) == 0)
strncpy(m->title, line.substr(k_title.length()).c_str(), sizeof(m->title));
else if (line.find(k_authors) == 0)
strncpy(m->authors, line.substr(k_authors.length()).c_str(), sizeof(m->authors));
else if (line.find(k_published) == 0)
m->publish_ts = atol(line.substr(k_published.length()).c_str());
else if (line.find(k_published_str) == 0)
strncpy(m->publish_str, line.substr(k_published_str.length()).c_str(), sizeof(m->publish_str));
else
std::cerr << "warning: ignoring unknown key/value due to unknown key: " << line << std::endl;
} else if (line[0] == '[') {

View File

@ -30,9 +30,10 @@ namespace Article {
char path[256]; // Path to this article
// Epoch timestamps
long original_ts; // Original article timestamp (for "Originally written on [...]")
long publish_ts; // (for "Published on [...]")
long update_ts; // (for "Last updated on [...]")
long original_ts; // Original article timestamp (for "Originally written on [...]")
long publish_ts; // (for "Published on [...]")
long update_ts; // (for "Last updated on [...]")
char publish_str[32]; // Override localized publish timestamps
// Article flags
bool partially_obsoleted,

143
src/BlogBuilder.cxx Normal file
View File

@ -0,0 +1,143 @@
/*
* 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 "BlogBuilder.hxx"
#include <regex>
#include <iostream>
#include <cstring>
#include "Article.hxx"
#include "WebsiteBuilder.hxx"
std::string blog_relative_path(std::string const &pathPrefix, std::string const &path) {
return path.substr(pathPrefix.length() - 1);
}
bool is_valid_article(std::string const &pathPrefix, std::string const &path) {
return std::regex_search(blog_relative_path(pathPrefix, path), std::regex("^/\\d{4}/\\d{2}/.*\\.md"));
}
void build_blog_structure(std::string const &path, std::string const &prefix, std::list<std::string> const &articles, Blog *blog) {
std::filesystem::path obp = get_output_path(path); // Output Blog Path
obp /= "blog";
obp /= blog->dir;
try {
// Create blog directory
if (!std::filesystem::exists(obp)) std::filesystem::create_directories(obp);
} catch (std::filesystem::filesystem_error const &e) {
std::cerr << "error: failed to create directory for blog \"" << blog->name << "\": " << e.what() << std::endl;
exit(RETURN_FAILED_UNKNOWN_ERROR);
}
std::list<Article::Metadata*> am;
//std::map<std::string, std::list<std::string>> sorted_articles;
for (std::string const &a : articles) {
std::string articlePath = blog_relative_path(prefix, a);
std::regex yearMonth("(\\d+)");
auto match = std::sregex_iterator(articlePath.begin(), articlePath.end(), yearMonth);
std::string year = match->str(),
month = (++match)->str();
// Go ahead and parse article metadata.
Article::Metadata *articleMetadata = (Article::Metadata*) malloc(sizeof(Article::Metadata));
Article::get_metadata(a, articleMetadata);
if (verbose)
std::cout << "Parsed metadata for article \"" << articleMetadata->title << "\"\n\tPublished on "
<< ctime(&(articleMetadata->publish_ts)) << "\n";
// TODO: This code could be optimized by removing directory
// checks for every single article. Instead, add
// directory-checks for years and months to a queue (skipping
// existing ones) and then checking and creating the
// directories later as needed.
try {
// Create directory for the year of this article if it doesn't exist.
std::filesystem::path oad = obp, // Output Article Directory
rap; // Relative Article Path
oad /= year;
rap /= year;
if (!std::filesystem::exists(oad)) std::filesystem::create_directory(oad);
// Do the same for the article month.
oad /= month;
rap /= month;
if (!std::filesystem::exists(oad)) std::filesystem::create_directory(oad);
// Now create the article file.
std::string new_article_filename = getFilename(a, false);
new_article_filename += ".html";
oad /= new_article_filename;
rap /= new_article_filename;
compile_markdown(path, a, oad, articleMetadata);
strncpy(articleMetadata->path, rap.c_str(), sizeof(articleMetadata->path));
am.push_back(articleMetadata);
} catch (std::filesystem::filesystem_error const &e) {
std::cerr << "error: failed to create directory for an article from blog \""
<< blog->name << "\": " << e.what() << std::endl;
exit(RETURN_FAILED_UNKNOWN_ERROR);
}
}
// Sort am list.
am.sort(Article::Comparator::comp);
// Generate blog catalog.
std::string blog_html_catalog,
last_date = "";
blog_html_catalog += "<h2>";
blog_html_catalog += blog->name;
blog_html_catalog += " - Catalog</h2>\n<ul>";
for (Article::Metadata *m : am) {
struct tm *article_time = gmtime(&(m->publish_ts));
std::string hr_date = get_hr_month(article_time->tm_mon);
hr_date += " ";
hr_date += std::to_string(article_time->tm_year + 1900);
// Check if this article belongs to the same "month + year" group as
// the last article. If it doesn't, add a new group to the catalog.
if (hr_date != last_date) {
last_date = hr_date;
blog_html_catalog += "\n\t<span><b>";
blog_html_catalog += hr_date;
blog_html_catalog += "</b></span>\n";
}
blog_html_catalog += "\t<li><a target=\"_blank\" href=\"./";
blog_html_catalog += m->path;
blog_html_catalog += "\">";
blog_html_catalog += m->title;
blog_html_catalog += "</a></li>\n";
// Free memory as it's no longer needed.
free(m);
}
blog_html_catalog += "</ul>";
// Put catalog HTML into template and save it to the blog directory.
std::filesystem::path catalog_output_path = obp;
catalog_output_path /= "index.html";
std::string html_template = get_template(path);
std::regex content_placeholder("<!--\\[_SWG: \\$CONTENT\\]-->");
std::string catalog_html_contents = std::regex_replace(html_template, content_placeholder, blog_html_catalog);
FILE *catalog_output_file = fopen(catalog_output_path.c_str(), "w");
fputs(catalog_html_contents.c_str(), catalog_output_file);
fclose(catalog_output_file);
}

30
src/BlogBuilder.hxx Normal file
View File

@ -0,0 +1,30 @@
/*
* 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>.
*/
#pragma once
#include <string>
#include <list>
#include "Common.hxx"
bool is_valid_article(std::string const &pathPrefix, std::string const &path);
void build_blog_structure(std::string const &path, std::string const &prefix, std::list<std::string> const &articles, Blog *blog);

97
src/Common.cxx Normal file
View File

@ -0,0 +1,97 @@
/*
* 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 "Common.hxx"
#include <iostream>
const char* HR_MONTH[] = {
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
};
const char* get_hr_month(unsigned short month) {
return HR_MONTH[month];
}
std::string getFilename(std::string const &path, bool const ext) {
int extSeparator = -1;
// Backwards search for directory separator.
for (int x = path.length(); x > 0; --x) {
if (path[x] == '/') return ext ? path.substr(x + 1) : path.substr(x + 1, extSeparator - x - 1);
// If this function was called with "ext" set to false,
// generate a substring of the path containing the filename
// only.
else if (!ext && path[x] == '.' && extSeparator == -1)
extSeparator = x;
}
return path;
}
std::filesystem::path get_output_path(std::string const &path) {
std::filesystem::path output = path;
return output /= "output";
}
/**
* Used to determine whether a file should be copied onto the output directory
* or not. Certain files, like the config file shouldn't be copied.
*/
bool is_special_file(std::string const &filename) {
if (filename == "swg.cfg" || filename.find("__swg_") == 0)
return true;
return false;
}
std::string get_template(std::string const &path) {
// Template lookup
std::string stPath = path;
stPath += "/__swg_template.html";
FILE *swgTemplate = fopen(stPath.c_str(), "r");
if (swgTemplate == NULL) {
std::cerr << "error: couldn't open the SWG HTML template file; does it exist?\n";
perror(stPath.c_str());
exit(RETURN_FAILED_INVALID_DIRECTORY);
}
// Check for content placeholder
int buflen = 8;
char buf[buflen];
std::string htmlTemplate;
while (fgets(buf, buflen, swgTemplate) != NULL) {
htmlTemplate += buf;
}
fclose(swgTemplate);
if (verbose) std::cout << "Loaded HTML template into memory.\n";
return htmlTemplate;
}

View File

@ -21,17 +21,35 @@
#pragma once
#define PROGRAM_NAME "SWG"
#define PROGRAM_VERSION 1
#define PROGRAM_VERSION_NAME "0.1-dev"
#define PROGRAM_COPYRIGHT "luca0N!"
#define PROGRAM_COPYRIGHT_YEARS "2022"
#define PROGRAM_NAME "SWG"
#define PROGRAM_VERSION 1
#define PROGRAM_VERSION_NAME "0.1-dev"
#define PROGRAM_COPYRIGHT "luca0N!"
#define PROGRAM_COPYRIGHT_YEARS "2022"
#define RETURN_SUCCESSFUL 0
#define RETURN_SUCCESSFUL 0
#define RETURN_FAILED_INVALID_SYNTAX 1
#define RETURN_FAILED_CONFIG_INVALID_SYNTAX 2
#define RETURN_FAILED_INVALID_DIRECTORY 3
#define RETURN_FAILED_WEBSITE_BUILD_EXISTS 4
#define RETURN_FAILED_UNKNOWN_ERROR 10
#define RETURN_FAILED_UNKNOWN_ERROR 10
#include <filesystem>
#include <string>
struct Blog {
char name[64],
path[64],
dir[64];
int test;
};
const char* get_hr_month(unsigned short month);
std::string getFilename(std::string const &path, bool const ext = true);
std::filesystem::path get_output_path(std::string const &path);
bool is_special_file(std::string const &filename);
std::string get_template(std::string const &path);
extern bool verbose;

View File

@ -267,7 +267,7 @@ std::string make_html(std::filesystem::path const &path) {
break;
case '[':
// Hyperlink text declaration has begun
if (tag_comment || tag_a != NONE) {
if (tag_comment || tag_a != NONE || buf[x-1] == '\\') {
// Cannot add hyperlinks inside of hyperlinks;
append(c);
break;
@ -278,7 +278,7 @@ std::string make_html(std::filesystem::path const &path) {
break;
case ']':
// Hyperlink text declaration ended
if (tag_comment || tag_a != READING_CONTENTS) {
if (tag_comment || tag_a != READING_CONTENTS || buf[x-1] == '\\') {
// Ignore if not reading hyperlink.
append(c);
break;
@ -312,6 +312,8 @@ std::string make_html(std::filesystem::path const &path) {
break;
} else append(" ");
break;
case '\\':
break;
default:
append(c);
break;

View File

@ -24,12 +24,7 @@
#include <string>
#include <list>
struct Blog {
char name[64],
path[64],
dir[64];
int test;
};
#include "Common.hxx"
struct SwgContext {
std::string websiteName,

View File

@ -21,7 +21,6 @@
#include <iostream>
#include <cstring>
#include <string>
#include <list>
#include <regex>
#include <filesystem>
@ -30,53 +29,11 @@
#include "SwgRuntime.hxx"
#include "SwgContext.hxx"
#include "ConfigUtils.hxx"
// #include "Blog.h"
#include "BlogBuilder.hxx"
#include "Common.hxx"
#include "MarkdownParser.hxx"
#include "Article.hxx"
static std::string HR_MONTH[] = {
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
};
std::string blog_relative_path(std::string const &pathPrefix, std::string const &path) {
return path.substr(pathPrefix.length() - 1);
}
std::string getFilename(std::string const &path, bool const ext = true) {
int extSeparator = -1;
// Backwards search for directory separator.
for (int x = path.length(); x > 0; --x) {
if (path[x] == '/') return ext ? path.substr(x + 1) : path.substr(x + 1, extSeparator - x - 1);
// If this function was called with "ext" set to false,
// generate a substring of the path containing the filename
// only.
else if (!ext && path[x] == '.' && extSeparator == -1)
extSeparator = x;
}
return path;
}
bool isValidArticle(std::string const &pathPrefix, std::string const &path) {
return std::regex_search(blog_relative_path(pathPrefix, path), std::regex("^/\\d{4}/\\d{2}/.*\\.md"));
}
std::filesystem::path get_output_path(std::string const &path) {
std::filesystem::path output = path;
return output /= "output";
}
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);
@ -103,30 +60,6 @@ void build_dir_structure(std::string const &path) {
}
}
std::string get_template(std::string const &path) {
// Template lookup
std::string stPath = path;
stPath += "/__swg_template.html";
FILE *swgTemplate = fopen(stPath.c_str(), "r");
if (swgTemplate == NULL) {
std::cerr << "error: couldn't open the SWG HTML template file; does it exist?\n";
perror(stPath.c_str());
exit(RETURN_FAILED_INVALID_DIRECTORY);
}
// Check for content placeholder
int buflen = 8;
char buf[buflen];
std::string htmlTemplate;
while (fgets(buf, buflen, swgTemplate) != NULL) {
htmlTemplate += buf;
}
fclose(swgTemplate);
std::cout << "Loaded HTML template into memory.\n";
return htmlTemplate;
}
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");
@ -138,16 +71,19 @@ void compile_markdown(std::string const &path, std::string const &md, std::strin
std::string article_html;
if (metadata != NULL) {
// Get time information.
struct tm *publish_tm = gmtime(&(metadata->publish_ts));
article_html = "<h1>";
article_html += metadata->title;
article_html += "</h1>\n<span><b>Published on ";
//article_html += ctime(&(metadata->publish_ts));
article_html += 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);
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/>";
@ -162,118 +98,6 @@ void compile_markdown(std::string const &path, std::string const &md, std::strin
fclose(articleOutput);
}
void build_blog_structure(std::string const &path, std::string const &prefix, std::list<std::string> const &articles, Blog *blog) {
std::filesystem::path obp = get_output_path(path); // Output Blog Path
obp /= "blog";
obp /= blog->dir;
try {
// Create blog directory
if (!std::filesystem::exists(obp)) std::filesystem::create_directories(obp);
} catch (std::filesystem::filesystem_error const &e) {
std::cerr << "error: failed to create directory for blog \"" << blog->name << "\": " << e.what() << std::endl;
exit(RETURN_FAILED_UNKNOWN_ERROR);
}
std::list<Article::Metadata*> am;
//std::map<std::string, std::list<std::string>> sorted_articles;
for (std::string const &a : articles) {
std::string articlePath = blog_relative_path(prefix, a);
std::regex yearMonth("(\\d+)");
auto match = std::sregex_iterator(articlePath.begin(), articlePath.end(), yearMonth);
std::string year = match->str(),
month = (++match)->str();
// Go ahead and parse article metadata.
Article::Metadata *articleMetadata = (Article::Metadata*) malloc(sizeof(Article::Metadata));
Article::get_metadata(a, articleMetadata);
std::cout << "Parsed metadata for article \"" << articleMetadata->title << "\"\n\tPublished on "
<< ctime(&(articleMetadata->publish_ts)) << "\n";
// TODO: This code could be optimized by removing directory
// checks for every single article. Instead, add
// directory-checks for years and months to a queue (skipping
// existing ones) and then checking and creating the
// directories later as needed.
try {
// Create directory for the year of this article if it doesn't exist.
std::filesystem::path oad = obp, // Output Article Directory
rap; // Relative Article Path
oad /= year;
rap /= year;
if (!std::filesystem::exists(oad)) std::filesystem::create_directory(oad);
// Do the same for the article month.
oad /= month;
rap /= month;
if (!std::filesystem::exists(oad)) std::filesystem::create_directory(oad);
// Now create the article file.
std::string new_article_filename = getFilename(a, false);
new_article_filename += ".html";
oad /= new_article_filename;
rap /= new_article_filename;
compile_markdown(path, a, oad, articleMetadata);
strncpy(articleMetadata->path, rap.c_str(), sizeof(articleMetadata->path));
am.push_back(articleMetadata);
} catch (std::filesystem::filesystem_error const &e) {
std::cerr << "error: failed to create directory for an article from blog \""
<< blog->name << "\": " << e.what() << std::endl;
exit(RETURN_FAILED_UNKNOWN_ERROR);
}
}
// Sort am list.
am.sort(Article::Comparator::comp);
// Generate blog catalog.
std::string blog_html_catalog = "<ul>\n",
last_date = "";
for (Article::Metadata *m : am) {
struct tm *article_time = gmtime(&(m->publish_ts));
std::string hr_date = HR_MONTH[article_time->tm_mon];
hr_date += " ";
hr_date += std::to_string(article_time->tm_year + 1900);
// Check if this article belongs to the same "month + year" group as
// the last article. If it doesn't, add a new group to the catalog.
if (hr_date != last_date) {
last_date = hr_date;
blog_html_catalog += "\n\t<span><b>";
blog_html_catalog += hr_date;
blog_html_catalog += "</b></span>\n";
}
blog_html_catalog += "\t<li><a href=\"./";
blog_html_catalog += m->path;
blog_html_catalog += "\">";
blog_html_catalog += m->title;
blog_html_catalog += "</a></li>\n";
// Free memory as it's no longer needed.
free(m);
}
blog_html_catalog += "</ul>";
// Put catalog HTML into template and save it to the blog directory.
std::filesystem::path catalog_output_path = obp;
catalog_output_path /= "index.html";
std::string html_template = get_template(path);
std::regex content_placeholder("<!--\\[_SWG: \\$CONTENT\\]-->");
std::string catalog_html_contents = std::regex_replace(html_template, content_placeholder, blog_html_catalog);
FILE *catalog_output_file = fopen(catalog_output_path.c_str(), "w");
fputs(catalog_html_contents.c_str(), catalog_output_file);
fclose(catalog_output_file);
}
/**
* Used to determine whether a file should be copied onto the output directory
* or not. Certain files, like the config file shouldn't be copied.
*/
bool is_special_file(std::string const &filename) {
if (filename == "swg.cfg" || filename.find("__swg_") == 0)
return true;
return false;
}
void build_website(SwgContext &ctx, std::string const &path) {
build_dir_structure(path);
@ -349,15 +173,14 @@ void build_website(SwgContext &ctx, std::string const &path) {
// Skip all files inside the "output" directory.
if (dir_entry.path().string().find(path + "output/") == 0) continue;
// Directory item iteration
std::cout << "\t" << dir_entry << std::endl;
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) {
std::cout << "\t\tIs file: " << getFilename(dir_entry.path()) << "\n";
// Markdown files should be insite a YYYY/MM directory.
if (!isValidArticle(relativePath, dir_entry.path()))
if (!is_valid_article(relativePath, dir_entry.path()))
failedArticles.insert(failedArticles.end(), dir_entry.path());
else parsedArticles.insert(parsedArticles.end(), dir_entry.path());
}

View File

@ -23,5 +23,10 @@
#include "SwgContext.hxx"
void build_website(SwgContext &ctx, std::string const &path);
#include "Article.hxx"
#include <string>
void build_website(SwgContext &ctx, std::string const &path);
void compile_markdown(std::string const &path, std::string const &md, std::string const &to, Article::Metadata *metadata = NULL);

View File

@ -33,11 +33,19 @@
#include "Common.hxx"
#include "WebsiteBuilder.hxx"
bool verbose = false;
void printUsage(const char *programName) {
std::cerr << programName << ": usage: " << programName << " [path]"
std::cerr << programName << ": usage: " << programName << " [options] [path]"
<< std::endl;
}
void print_help(const char *program_name) {
printUsage(program_name);
std::cout << " -h, --help\t\tShows this help message.\n"
<< " -v, --verbose\t\tPrints additional messages to standard output.\n";
}
void printIntro() {
printf("%s v%s (%d)\nCopyright (C) %s %s\n\n",
PROGRAM_NAME, PROGRAM_VERSION_NAME, PROGRAM_VERSION,
@ -47,31 +55,64 @@ void printIntro() {
int main(int argc, char *argv[]) {
printIntro();
std::string path;
std::string path = "";
// Parse cmdline options
switch (argc) {
case 1:
path = ".";
std::cout << "Assuming working directory is \".\""
<< std::endl;
break;
case 2:
path = argv[1];
std::cout << "Using specified directory: "
<< path << std::endl;
break;
default:
for (int argi = 1; argi < argc; argi++) {
if (argv[argi][0] == '-') {
// This is a cmdline option (starts with a dash).
if (argv[argi][1] == '-') {
// Long cmdline option (--example).
if (strcmp(argv[argi], "--verbose") == 0) {
verbose = true;
std::cout << "Verbose output enabled.\n";
} else if (strcmp(argv[argi], "--help") == 0) {
print_help(argv[0]);
exit(RETURN_SUCCESSFUL);
} else {
std::cerr << "error: unknown command line option `" <<
argv[argi] << "'." << std::endl;
exit(RETURN_FAILED_INVALID_SYNTAX);
}
} else {
for (int i = 1; i < strlen(argv[argi]); i++) {
switch (argv[argi][i]) {
case 'v':
verbose = true;
std::cout << "Verbose output enabled.\n";
break;
case 'h':
print_help(argv[0]);
exit(RETURN_SUCCESSFUL);
break;
default:
std::cerr << "error: unknown command line option `-" <<
argv[argi][i] << "'." << std::endl;
exit(RETURN_FAILED_INVALID_SYNTAX);
break;
}
}
}
} else if (path == "") {
path = argv[argi];
} else {
std::cerr << "error: website directory was already " <<
"provided (`" << path << "'), refusing to continue with `" <<
argv[argi] << "'." << std::endl;
printUsage(argv[0]);
exit(RETURN_FAILED_INVALID_SYNTAX);
break;
}
}
std::cout << "Using specified directory: " << path << "\n";
SwgContext ctx;
parse_config(&ctx, path);
build_website(ctx, path);
std::cout << "Website build succeeded.\n";
// Website blog
// Append blog to website