stamd

Static Markdown Page Generator
git clone git://git.dimitrijedobrota.com/stamd.git
Log | Files | Refs | README | LICENSE

commit 04e983cf30b9c6a9a45da5980e2354ade6ef6654
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Wed, 14 Sep 2022 21:03:11 +0200

Initial commit

Diffstat:
A.clang-format | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.gitignore | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AMakefile | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
AREADME.md | 1+
Abin/dir.info | 0
Ainclude/dir.info | 0
Aobj/dir.info | 0
Asrc/dir.info | 0
Asrc/stamd.c | 589+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 877 insertions(+), 0 deletions(-)

diff --git a/.clang-format b/.clang-format @@ -0,0 +1,178 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: true +AlignArrayOfStructures: Right +AlignConsecutiveMacros: true +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: true +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Right +PPIndentWidth: -1 +ReferenceAlignment: Pointer +ReflowComments: true +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/.gitignore b/.gitignore @@ -0,0 +1,58 @@ +.ccls-cache/ +.ccls +docs/* +bin/* +!bin/dir.info + +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf diff --git a/Makefile b/Makefile @@ -0,0 +1,51 @@ +# GNU Makefile for Game of Life simulation +# +# Usage: make [-f path\Makefile] [DEBUG=Y] target + +NAME = stamd +CC = gcc + +CFLAGS = -Iinclude +LDFLAGS += -lmd4c-html + +SRC = src +OBJ = obj +BINDIR = bin + +BIN = bin/$(NAME) +SRCS=$(wildcard $(SRC)/*.c) +OBJS=$(patsubst $(SRC)/%.c, $(OBJ)/%.o, $(SRCS)) + +ifeq ($(DEBUG),Y) + CFLAGS += -lciid + CFLAGS += -Wall -ggdb +else + CFLAGS += -lcii +endif + +all: $(BIN) + +$(BIN): $(OBJS) + $(CC) $^ $(CFLAGS) $(LDFLAGS) -o $@ + +$(OBJ)/%.o: $(SRC)/%.c + $(CC) -c $< -o $@ $(CFLAGS) $(LDFLAGS) + +clean: + -$(RM) $(BIN) $(OBJS) + +help: + @echo "Stamd - Static Markdown Page Generator" + @echo + @echo "Usage: make [-f path\Makefile] [DEBUG=Y] target" + @echo + @echo "Target rules:" + @echo " all - Compiles binary file [Default]" + @echo " clean - Clean the project by removing binaries" + @echo " help - Prints a help message with target rules" + @echo + @echo "Optional parameters:" + @echo " DEBUG - Compile binary file with debug flags enabled" + @echo + +.PHONY: all clean help docs diff --git a/README.md b/README.md @@ -0,0 +1 @@ +# Stamd - Static Markdown Page Generator diff --git a/bin/dir.info b/bin/dir.info diff --git a/include/dir.info b/include/dir.info diff --git a/obj/dir.info b/obj/dir.info diff --git a/src/dir.info b/src/dir.info diff --git a/src/stamd.c b/src/stamd.c @@ -0,0 +1,589 @@ +#define _XOPEN_SOURCE 700 + +#include <ctype.h> +#include <errno.h> +#include <error.h> +#include <fcntl.h> +#include <libgen.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> + +#include "md4c-html.h" + +#include <cii/atom.h> +#include <cii/list.h> +#include <cii/mem.h> +#include <cii/str.h> +#include <cii/table.h> + +#define MAX_SIZE 100 + +#define BASE_URL "https://dimitrijedobrota.com/blog" +#define TITLE "Dimitrije Dobrota's blog" +#define AUTHOR "Dimitrije Dobrota" +#define AUTHOR_EMAIL "mail@dimitrijedobrota.com" + +#define SETTINGS_TIME_FORMAT "%Y-%m-%d" + +#define ATOM_TIME_FORMAT "%Y-%m-%dT%H:%M:%SZ" +#define ATOM_FILE "index.atom" +#define ATOM_LOCATION BASE_URL "/" ATOM_FILE + +#define RSS_TIME_FORMAT "%a, %d %b %Y %H:%M:%S +0200" +#define RSS_FILE "rss.xml" +#define RSS_LOCATION BASE_URL "/" RSS_FILE + +#define SITEMAP "%a, %d %b %Y %H:%M:%S +0200" +#define SITEMAP_FILE "sitemap.xml" + +List_T articles; /* List of all articles */ +List_T articlesVisible; /* List of all articles that are not hidden*/ +Table_T category_table; /* Table of all non hidden articles for each category*/ + +void usage(char *argv0) { + fprintf(stderr, "Usage: %s [-o output_dir] article\n", argv0); + exit(EXIT_FAILURE); +} + +void process(const MD_CHAR *text, MD_SIZE size, void *userdata) { + fprintf((FILE *)userdata, "%.*s", size, text); +} + +char *memory_open(char *infile, int *size) { + char *addr; + int fd, ret; + struct stat st; + + if ((fd = open(infile, O_RDWR)) < 0) + error(EXIT_FAILURE, errno, "%s", infile); + + if ((ret = fstat(fd, &st)) < 0) + error(EXIT_FAILURE, errno, "line %d, fstat", __LINE__); + + if ((addr = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, + 0)) == MAP_FAILED) + error(EXIT_FAILURE, errno, "line %d, addr", __LINE__); + + if (size) + *size = st.st_size; + + return addr; +} + +void memory_close(char *content, int size) { + if (munmap(content, size) == -1) + error(EXIT_FAILURE, errno, "line %d, munmap", __LINE__); +} + +char *normalize(char *name) { + char *s = name, *c = name; + + while (isspace(*c)) + c++; + + for (; *c; c++) + if (isspace(*c) && *(name - 1) != '_') + *name++ = '_'; + else if (isalnum(*c)) + *name++ = *c; + *name = '\0'; + return s; +} + +void strip_whitspace(char *str) { + char *p; + + if (!str) + return; + + p = str + strlen(str); + do { + p--; + } while (isspace(*p)); + + *(p + 1) = '\0'; + + p = str; + while (isspace(*p)) + p++; + + while (*p) + *str++ = *p++; + + *str = '\0'; +} + +int strscmp(const void *a, const void *b) { + return -strcmp((char *)b, (char *)a); +} + +void applyListFree(void **ptr, void *cl) { FREE(*ptr); } + +#define T Article_T +typedef struct T *T; +struct T { + char *content; + char *output_dir; + int content_size; + + int hidden; + int nonav; + + FILE *outfile; + + Table_T symbols; + List_T categories; +}; + +#define AP(...) fprintf(article->outfile, __VA_ARGS__); + +T Article_new(char *output_dir, char *title) { + T p; + + NEW0(p); + + if (!title) + title = "article"; + + p->output_dir = output_dir; + + p->symbols = Table_new(0, NULL, NULL); + p->categories = List_list(NULL); + + Table_put(p->symbols, Atom_string("title"), title); + Table_put(p->symbols, Atom_string("date"), "1970-01-01"); + Table_put(p->symbols, Atom_string("lang"), "en"); + + return p; +} + +void Article_setContent(T self, char *content, int content_size) { + self->content = content; + self->content_size = content_size; +} + +int Article_cmp(const void *a, const void *b) { + Article_T a1 = (Article_T)a; + Article_T a2 = (Article_T)b; + + int res = strcmp(Table_get(a1->symbols, Atom_string("date")), + Table_get(a2->symbols, Atom_string("date"))); + if (res) + return -res; + + return strcmp(Table_get(a1->symbols, Atom_string("title")), + Table_get(a2->symbols, Atom_string("title"))); +} + +void Article_openWrite(T self) { + char outfile[2 * PATH_MAX]; + + if (!Table_get(self->symbols, Atom_string("filename"))) { + Table_put(self->symbols, Atom_string("filename"), + Str_dup(Table_get(self->symbols, Atom_string("title")), 1, 0, 1)); + } + + char *filename = Table_get(self->symbols, Atom_string("filename")); + normalize(filename); + sprintf(outfile, "%s/%s.html", self->output_dir, filename); + + if ((self->outfile = fopen(outfile, "w")) == NULL) + error(EXIT_FAILURE, errno, "line %d, fopen(%s)", __LINE__, outfile); +} + +void Article_closeWrite(T self) { + if (self->outfile) + fclose(self->outfile); +} + +void Article_free(T self) { + Table_free(&self->symbols); + + /* List_map(self->categories, applyListFree, NULL); */ + List_free(&self->categories); + + FREE(self); +} + +void print_category_item(void **item, void *article_pointer) { + char *category = *(char **)item, *norm; + + Article_T article = (Article_T)article_pointer; + norm = normalize(Str_dup(category, 1, 0, 1)); + AP("<a href=\"./%s.html\">%s</a>\n", norm, category); + FREE(norm); +} + +void print_header(T article) { + AP("<!DOCTYPE html>\n" + "<html lang=\"%s\">\n" + "<head>\n" + "<title>%s</title>\n", + (char *)Table_get(article->symbols, Atom_string("lang")), + (char *)Table_get(article->symbols, Atom_string("title"))); + + AP("<meta charset=\"UTF-8\" />\n" + "<meta name=\"viewport\" " + "content=\"width=device-width,initial-scale=1\"/>\n" + "<meta name=\"description\" content=\"Dimitrije Dobrota's personal site. " + "You can find my daily findings in a form of articles on my blog as well " + "as various programming projects.\" />\n" + "<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/index.css\" />\n" + "<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/colors.css\" />\n" + + "<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" " + "href=\"/img/favicon-32x32.png\">\n" + "<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" " + "href=\"/img/favicon-16x16.png\">\n" + "</head>\n" + "<body>\n" + "<input type=\"checkbox\" id=\"theme_switch\" class=\"theme_switch\">\n" + "<main>\n" + "<div class=\"content\">\n" + "<label for=\"theme_switch\" class=\"switch_label\"></label>\n"); + + if (article->nonav) + return; + + AP("<div>\n<nav><a class=\"back\">&lt;-- back</a><a " + "href=\"" BASE_URL "\">index</a><a href=\"/\">home " + "--&gt;</a></nav><hr>\n</div>\n"); + + if (List_length(article->categories) > 0) { + List_sort(article->categories, strscmp); + + AP("<div class=\"categories\"><h3>Categories:</h3><p>\n"); + List_map(article->categories, print_category_item, article); + AP("</p></div>\n"); + } +} + +void print_footer(T article) { + if (!article->nonav) { + AP("<div class=\"bottom\">\n<hr>\n<nav>\n<a class=\"back\">&lt;-- " + "back</a><a href=\"" BASE_URL "\">index</a><a href=\"/\">home " + "--&gt;</a></nav></div>\n"); + } + + AP("</div>\n</main>\n" + "<script src=\"/scripts/main.js\"></script>\n" + "</body>\n</html>\n"); +} + +void print_index_item(void **article_item_pointer, void *article_pointer) { + Article_T article_item = *(Article_T *)article_item_pointer; + Article_T article = (Article_T)article_pointer; + + AP("<li><div>%s - </div><div><a href=\"%s.html\">%s</a></div></li>\n", + (char *)Table_get(article_item->symbols, Atom_string("date")), + (char *)Table_get(article_item->symbols, Atom_string("filename")), + (char *)Table_get(article_item->symbols, Atom_string("title"))) +} + +void print_index(Article_T article, List_T articles, List_T categories) { + + article->categories = categories; + Article_openWrite(article); + + print_header(article); + { + List_sort(articles, Article_cmp); + + AP("<h1>%s</h1>\n", + (char *)Table_get(article->symbols, Atom_string("title"))); + + AP("<ul class=\"index\">\n"); + List_map(articles, print_index_item, article); + AP("</ul>\n"); + } + print_footer(article); + + Article_closeWrite(article); + Article_free(article); +} + +void Article_preprocess(T self) { + char *text = self->content; + + char *line; + for (line = strtok(text, "\n"); line; line = strtok(NULL, "\n")) { + strip_whitspace(line); + if (!*line) + continue; + else if (*line == '@') { + char *keys, *values; + + keys = CALLOC(1000, sizeof(char)); + values = CALLOC(1000, sizeof(char)); + + sscanf(line, " @%[^:]: %[^\n] ", keys, values); + + if (!strcmp(keys, "hidden")) + self->hidden = 1; + else if (!strcmp(keys, "nonav")) + self->nonav = 1; + else + Table_put(self->symbols, Atom_string(keys), Str_dup(values, 1, 0, 1)); + + FREE(values); + FREE(keys); + } else { + *(line + strlen(line)) = '\n'; + text = line; + break; + } + } + + self->content_size = self->content_size - (text - self->content); + self->content = text; + + char *cat; + if ((cat = (char *)Table_get(self->symbols, Atom_string("categories")))) { + char delim[] = ",", *category; + for (category = strtok(cat, delim); category; + category = strtok(NULL, delim)) { + if (strlen(category) > 1) { + strip_whitspace(category); + + const char *atom = Atom_string(category); + self->categories = List_push(self->categories, category); + + /* append the article to the list of articles for a current category*/ + if (!self->hidden) + Table_put(category_table, atom, + List_push(Table_get(category_table, atom), self)); + } + } + } + + if (!self->hidden) + articlesVisible = List_push(articlesVisible, self); +} + +void Article_translate(T self) { + Article_preprocess(self); + Article_openWrite(self); + print_header(self); + md_html(self->content, self->content_size, process, self->outfile, + MD_DIALECT_GITHUB, 0); + print_footer(self); + Article_closeWrite(self); +} + +char *get_date(char *date_str, char **date_buf, char *conversion) { + struct tm date; + memset(&date, 0, sizeof(date)); + + strptime(date_str, SETTINGS_TIME_FORMAT, &date); + strftime(*date_buf, MAX_SIZE, conversion, &date); + + return *date_buf; +} + +void print_atom_item(void **article_item_pointer, void *file_pointer) { + Article_T article_item = *(Article_T *)article_item_pointer; + char *date_buffer = ALLOC(MAX_SIZE); + FILE *f = (FILE *)file_pointer; + + get_date((char *)Table_get(article_item->symbols, Atom_string("date")), + &date_buffer, ATOM_TIME_FORMAT); + fprintf(f, + "<entry>\n" + " <title>%s</title>\n" + " <link href=\"" BASE_URL "/%s.html\"/>\n" + " <id>" BASE_URL "/%s.html</id>\n" + " <updated>%s</updated>\n" + " <summary>Click on the article link to read...</summary>\n" + "</entry>\n", + (char *)Table_get(article_item->symbols, Atom_string("title")), + (char *)Table_get(article_item->symbols, Atom_string("filename")), + (char *)Table_get(article_item->symbols, Atom_string("filename")), + date_buffer); + FREE(date_buffer); +} +void print_atom(List_T articles, FILE *f) { + fprintf(f, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n" + "<title>" TITLE "</title>\n" + "<link href=\"" BASE_URL "/\"/>\n" + "<link rel=\"self\" " + "href=\"" ATOM_LOCATION "\" />\n" + "<id>" BASE_URL "</id>\n" + "<updated>2003-12-13T18:30:02Z</updated>\n" + "<author>\n" + "<name>" AUTHOR "</name>\n" + "</author>\n"); + List_map(articles, print_atom_item, f); + fprintf(f, "</feed>\n"); +} + +void print_rss_item(void **article_item_pointer, void *file_pointer) { + Article_T article_item = *(Article_T *)article_item_pointer; + char *date_buffer = ALLOC(MAX_SIZE); + FILE *f = (FILE *)file_pointer; + + get_date((char *)Table_get(article_item->symbols, Atom_string("date")), + &date_buffer, RSS_TIME_FORMAT); + fprintf(f, + "<item>\n" + " <title>%s</title>\n" + " <link>" BASE_URL "/%s.html</link>\n" + " <guid>" BASE_URL "/%s.html</guid>\n" + " <pubDate>%s</pubDate>\n" + " <author>" AUTHOR_EMAIL " (" AUTHOR ")</author>\n" + "</item>\n", + (char *)Table_get(article_item->symbols, Atom_string("title")), + (char *)Table_get(article_item->symbols, Atom_string("filename")), + (char *)Table_get(article_item->symbols, Atom_string("filename")), + date_buffer); + FREE(date_buffer); +} + +void print_rss(List_T articles, FILE *f) { + fprintf(f, + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n" + "<channel>\n" + "<title>" TITLE "</title>\n" + "<link>" BASE_URL "</link>\n" + "<description>Contents of Dimitrije Dobrota's webpage</description>" + "<generator>stamd</generator>" + "<language>en-us</language>\n" + "<atom:link href=\"" RSS_LOCATION "\" rel=\"self\" " + "type=\"application/rss+xml\" />"); + List_map(articles, print_rss_item, f); + fprintf(f, "</channel>\n" + "</rss>\n"); +} + +void print_sitemap_item(void **article_item_pointer, void *file_pointer) { + Article_T article_item = *(Article_T *)article_item_pointer; + char *date_buffer = ALLOC(MAX_SIZE); + FILE *f = (FILE *)file_pointer; + + get_date((char *)Table_get(article_item->symbols, Atom_string("date")), + &date_buffer, RSS_TIME_FORMAT); + fprintf(f, + "<url>\n" + " <loc>" BASE_URL "/%s.html</loc>\n" + " <changefreq>weekly</changefreq>\n" + "</url>\n", + (char *)Table_get(article_item->symbols, Atom_string("filename"))); + FREE(date_buffer); +} + +void print_sitemap(List_T articles, FILE *f) { + fprintf(f, + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n"); + List_map(articles, print_sitemap_item, f); + fprintf(f, "</ulrset>\n"); +} + +/* void print_xml(char *file_name, void(*)) */ + +int main(int argc, char *argv[]) { + char output_dir[PATH_MAX]; + int opt; + + while ((opt = getopt(argc, argv, "o:")) != -1) { + switch (opt) { + case 'o': + if (!realpath(optarg, output_dir)) + error(EXIT_FAILURE, errno, "-o %s", optarg); + break; + default: + usage(argv[0]); + } + } + + if (optind >= argc) + usage(argv[0]); + + if (!*output_dir) + realpath(".", output_dir); + + category_table = Table_new(0, NULL, NULL); + + articles = List_list(NULL); + articlesVisible = List_list(NULL); + + for (; optind < argc; optind++) { + T article; + char *content; + int content_size; + + content = memory_open(argv[optind], &content_size); + article = Article_new(output_dir, NULL); + Article_setContent(article, content, content_size); + Article_translate(article); + memory_close(content, content_size); + + articles = List_push(articles, article); + } + + /* Print main index and index for each encountered category*/ + { + List_T categories = List_list(NULL); + void **array = Table_toArray(category_table, NULL); + + for (int i = 0; array[i]; i += 2) { + categories = List_push(categories, array[i]); + print_index(Article_new(output_dir, array[i]), array[i + 1], NULL); + } + + if (List_length(articlesVisible) > 1) { + print_index(Article_new(output_dir, "index"), articlesVisible, + categories); + + char outfile[2 * PATH_MAX]; + FILE *f; + + sprintf(outfile, "%s/%s", output_dir, ATOM_FILE); + f = fopen(outfile, "w"); + print_atom(articlesVisible, f); + fclose(f); + + sprintf(outfile, "%s/%s", output_dir, RSS_FILE); + f = fopen(outfile, "w"); + print_rss(articlesVisible, f); + fclose(f); + + sprintf(outfile, "%s/%s", output_dir, SITEMAP_FILE); + f = fopen(outfile, "w"); + print_sitemap(articlesVisible, f); + fclose(f); + } + + FREE(array); + } + + /* Free category table*/ + { + List_T *symbols = (List_T *)Table_toArray(category_table, NULL); + for (int i = 0; symbols[i]; i += 2) + List_free(&symbols[i + 1]); + FREE(symbols); + } + + /* Free articles */ + { + Article_T *article_list = (Article_T *)List_toArray(articles, NULL); + for (int i = 0; article_list[i]; i++) + Article_free(article_list[i]); + FREE(article_list); + } + + Table_free(&category_table); + + List_free(&articles); + List_free(&articlesVisible); + + return 0; +}