stamdStatic Markdown Page Generator |
git clone git://git.dimitrijedobrota.com/stamd.git |
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING | |
commit | 04e983cf30b9c6a9a45da5980e2354ade6ef6654 |
author | Dimitrije Dobrota <mail@dimitrijedobrota.com> |
date | Wed, 14 Sep 2022 19:03:11 +0200 |
Initial commit
Diffstat:A | .clang-format | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | .gitignore | | | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | Makefile | | | +++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | README.md | | | + |
A | bin/dir.info | | | |
A | include/dir.info | | | |
A | obj/dir.info | | | |
A | src/dir.info | | | |
A | src/stamd.c | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
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,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\"><-- back</a><a "
"href=\"" BASE_URL "\">index</a><a href=\"/\">home "
"--></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\"><-- "
"back</a><a href=\"" BASE_URL "\">index</a><a href=\"/\">home "
"--></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;
}