diff --git a/Makefile b/Makefile index 3a8a9c4..e45b2df 100644 --- a/Makefile +++ b/Makefile @@ -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/ diff --git a/src/Article.cxx b/src/Article.cxx index 239d9fc..15ac2c8 100644 --- a/src/Article.cxx +++ b/src/Article.cxx @@ -54,17 +54,13 @@ namespace Article { ignore_line = false; std::list 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] == '[') { diff --git a/src/Article.hxx b/src/Article.hxx index 702f9d1..98f0850 100644 --- a/src/Article.hxx +++ b/src/Article.hxx @@ -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, diff --git a/src/BlogBuilder.cxx b/src/BlogBuilder.cxx new file mode 100644 index 0000000..c8bd273 --- /dev/null +++ b/src/BlogBuilder.cxx @@ -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 + * . + * + * Contact luca0N! by e-mail: . + */ + +#include "BlogBuilder.hxx" + +#include +#include +#include + +#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 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 am; + //std::map> 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 += "

"; + blog_html_catalog += blog->name; + blog_html_catalog += " - Catalog

\n
    "; + + 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"; + blog_html_catalog += hr_date; + blog_html_catalog += "\n"; + } + blog_html_catalog += "\t
  • path; + blog_html_catalog += "\">"; + blog_html_catalog += m->title; + blog_html_catalog += "
  • \n"; + // Free memory as it's no longer needed. + free(m); + } + blog_html_catalog += "
"; + // 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(""); + 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); +} + diff --git a/src/BlogBuilder.hxx b/src/BlogBuilder.hxx new file mode 100644 index 0000000..91a369f --- /dev/null +++ b/src/BlogBuilder.hxx @@ -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 + * . + * + * Contact luca0N! by e-mail: . + */ + +#pragma once + +#include +#include + +#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 const &articles, Blog *blog); diff --git a/src/Common.cxx b/src/Common.cxx new file mode 100644 index 0000000..a54dbe5 --- /dev/null +++ b/src/Common.cxx @@ -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 + * . + * + * Contact luca0N! by e-mail: . + */ + +#include "Common.hxx" + +#include + +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; +} + diff --git a/src/Common.hxx b/src/Common.hxx index 449f707..fde210c 100644 --- a/src/Common.hxx +++ b/src/Common.hxx @@ -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 +#include + +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; diff --git a/src/MarkdownParser.cxx b/src/MarkdownParser.cxx index 235b8b5..a531677 100644 --- a/src/MarkdownParser.cxx +++ b/src/MarkdownParser.cxx @@ -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; diff --git a/src/SwgContext.hxx b/src/SwgContext.hxx index df0d3de..61e92ea 100644 --- a/src/SwgContext.hxx +++ b/src/SwgContext.hxx @@ -24,12 +24,7 @@ #include #include -struct Blog { - char name[64], - path[64], - dir[64]; - int test; -}; +#include "Common.hxx" struct SwgContext { std::string websiteName, diff --git a/src/WebsiteBuilder.cxx b/src/WebsiteBuilder.cxx index 1f59044..13156a9 100644 --- a/src/WebsiteBuilder.cxx +++ b/src/WebsiteBuilder.cxx @@ -21,7 +21,6 @@ #include #include -#include #include #include #include @@ -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 = "

"; article_html += metadata->title; article_html += "

\nPublished 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 += "
\nWritten by "; article_html += metadata->authors; article_html += "
"; @@ -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 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 am; - //std::map> 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 = "
    \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"; - blog_html_catalog += hr_date; - blog_html_catalog += "\n"; - } - blog_html_catalog += "\t
  • path; - blog_html_catalog += "\">"; - blog_html_catalog += m->title; - blog_html_catalog += "
  • \n"; - // Free memory as it's no longer needed. - free(m); - } - blog_html_catalog += "
"; - // 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(""); - 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()); } diff --git a/src/WebsiteBuilder.hxx b/src/WebsiteBuilder.hxx index 68b10e8..96cd556 100644 --- a/src/WebsiteBuilder.hxx +++ b/src/WebsiteBuilder.hxx @@ -23,5 +23,10 @@ #include "SwgContext.hxx" -void build_website(SwgContext &ctx, std::string const &path); +#include "Article.hxx" + +#include + +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); diff --git a/src/main.cxx b/src/main.cxx index 5882f46..92dd8f5 100644 --- a/src/main.cxx +++ b/src/main.cxx @@ -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