cld-ssg

The static site generator (to be) used for hellocld.com
git clone git://git.hellocld.com/cld-ssg
Log | Files | Refs | README

main.c (11986B)


      1 #define _XOPEN_SOURCE 700 /* eliminates gcc warning about strptime */
      2 
      3 #include <dirent.h>
      4 #include <stdlib.h>
      5 #include <time.h>
      6 #include <stdio.h>
      7 #include <string.h>
      8 #include <sys/stat.h>
      9 #include <sys/types.h>
     10 #include <unistd.h>
     11 #include <errno.h>
     12 
     13 #include "util.h"
     14 #include "cmark.h"
     15 
     16 #include "config.h"
     17 
     18 int md_filter(const struct dirent *);
     19 
     20 struct post {
     21 	char *title;
     22 	char *dir;
     23 	char *fsource;
     24 	char *fhtml;
     25 	char *content;
     26 	char *desc;
     27 	struct tm *time;
     28 	int is_static;
     29 };
     30 
     31 /* Post generation functions */
     32 struct post *create_post(const char *file);
     33 char *get_post_title(struct cmark_node *root);
     34 char *get_post_desc(struct cmark_node *root);
     35 struct tm *get_post_time(const char *file);
     36 void free_post(struct post *p);
     37 int insert_post_time(struct cmark_node *root, struct tm *time);
     38 
     39 int write_index(struct post *posts[], int totalPosts);
     40 int write_archive(struct post *posts[], int totalPosts);
     41 int write_rss(struct post *posts[], int totalPosts);
     42 int write_post(struct post *post);
     43 int is_post_static(char *file);
     44 
     45 void copy_resources(char *);
     46 
     47 void errprintf(const char *, int);
     48 
     49 char buf[MAX_POST_CHARS];
     50 char *header;
     51 char *footer;
     52 char *rssHeader;
     53 char *rssFooter;
     54 
     55 int main()
     56 {
     57 	printf("---- Beginning website generation...\n");
     58 	/* Load header and footer html */
     59 	header = read_text(HEADER_HTML, MAX_POST_CHARS);
     60 	footer = read_text(FOOTER_HTML, MAX_POST_CHARS);
     61 	rssHeader = read_text(HEADER_RSS, MAX_POST_CHARS);
     62 	rssFooter = read_text(FOOTER_RSS, MAX_POST_CHARS);
     63 
     64 	/* Load all posts */
     65 	struct dirent **t_mds;
     66 	int t_postcount = scandir(POSTDIR, &t_mds, md_filter, alphasort);
     67 	if(t_postcount <= 0) {
     68 		printf("ERROR: Failed to load posts from %s\n", POSTDIR);
     69 		return -1;
     70 	}
     71 
     72 	int i = t_postcount;
     73 	struct post *posts[t_postcount];
     74 	while(i--) {
     75 		posts[i] = create_post(t_mds[i]->d_name);
     76 		/* Since we're already iterating through, write the post now */
     77 		write_post(posts[i]);
     78 	}
     79 
     80 	/* Write index.html */
     81 	write_index(posts, t_postcount);
     82 
     83 	/* Write archive.html */
     84 	write_archive(posts, t_postcount);
     85 
     86 	/* write the rss feed */
     87 	write_rss(posts, t_postcount);
     88 
     89 	/* Copy images/videos/audio/static pages */
     90 	copy_resources(RESOURCEDIR);
     91 
     92 	/* free up all the posts on the heap */
     93 	i = t_postcount;
     94 	while(i--)
     95 		free_post(posts[i]);
     96 
     97 	while(t_postcount--)
     98 		free(t_mds[t_postcount]);
     99 	free(t_mds);
    100 
    101 	printf("--- Website generated.\n");
    102 	return 0;
    103 }
    104 
    105 /* returns non-zero if filename ends with ".md" */
    106 int md_filter(const struct dirent *d)
    107 {
    108 	/* if d->d_name doesn't end with ".md" we don't want it */
    109 	if(strcmp(d->d_name + (strlen(d->d_name) - 3), ".md") == 0)
    110 		return 1;
    111 	return 0;
    112 }
    113 
    114 /* Returns a pointer to a malloc'd post struct from a .md file */
    115 struct post *create_post(const char *file)
    116 {
    117 	struct post *p = malloc(sizeof(struct post));
    118 	p->fsource = malloc(MAX_URL_CHARS);
    119 	memcpy(p->fsource, file, MAX_URL_CHARS);
    120 
    121 	sprintf(buf, "%s%s", POSTDIR, p->fsource);
    122 	printf("Loading post %s ... \n", buf);
    123 	/* read in the post text file */
    124 	char *tmp = read_text(buf, MAX_POST_CHARS);
    125 	/* Check if it's a post or a static (other) page */
    126 	p->is_static = is_post_static(p->fsource);
    127 	if(p->is_static)
    128 		printf("** Page %s is static\n", p->fsource);
    129 	printf("Parsing post to cmark_node...\n");
    130 	struct cmark_node *t_root = cmark_parse_document(
    131 			tmp, strlen(tmp), CMARK_OPT_UNSAFE);
    132 	free(tmp);
    133 	printf("Extracting title...\n");
    134 	/* extract the post title from the first header in the post */
    135 	p->title = get_post_title(t_root);
    136 	printf("Extracting description...\n");
    137 	p->desc = get_post_desc(t_root);
    138 	printf("Generating post time...\n");
    139 	/* generate the struct tm representing the post date */
    140 	p->time = get_post_time(p->fsource);
    141 	printf("Inserting post time into cmark tree...\n");
    142 	if(!p->is_static)
    143 		insert_post_time(t_root, p->time);
    144 	printf("Rendering HTML...\n");
    145 	/* convert the markdown to html */
    146 	p->content = cmark_render_html(t_root, CMARK_OPT_UNSAFE);
    147 	printf("Generating html filename...\n");
    148 	/* generate the html file name */
    149 	p->fhtml = malloc(MAX_URL_CHARS);
    150 	char *t_html = p->fhtml;
    151 	char *t_title = p->title;
    152 	while((*t_html++ = *t_title++))
    153 		;
    154 	t_html = p->fhtml;
    155 	while((*t_html++))
    156 		if(*t_html == ' ')
    157 			*t_html = '-';
    158 	strncat(p->fhtml, ".html", 6);
    159 	printf("Generating html web directory...\n");
    160 	/* generate the base directory of the post */
    161 	p->dir = malloc(MAX_URL_CHARS);
    162 	if(!p->is_static) {
    163 		sprintf(p->dir, "%d/%02d/%02d/", 
    164 		p->time->tm_year + 1900,
    165 		p->time->tm_mon + 1,
    166 		p->time->tm_mday);
    167 
    168 	} else 
    169 		sprintf(p->dir, "");
    170 	printf("Generation complete. Freeing node...\n");
    171 	cmark_node_free(t_root);
    172 	return p;
    173 }
    174 
    175 /* Gets the post title from the first HEADER in the cmark tree */
    176 char *get_post_title(struct cmark_node *root)
    177 {
    178 	/* Loop through the tree to find the first header */
    179 	cmark_iter *t_iter = cmark_iter_new(root);
    180 	while(cmark_iter_next(t_iter) != CMARK_EVENT_DONE)
    181 		if(cmark_node_get_type(cmark_iter_get_node(t_iter)) == CMARK_NODE_HEADING)
    182 			break;
    183 	
    184 	/* Store the parent HEADING node */
    185 	cmark_node *t_title = cmark_iter_get_node(t_iter);
    186 	cmark_iter *t_titleIter = cmark_iter_new(t_title);
    187 	buf[0] = '\0';
    188 	while(cmark_iter_next(t_titleIter) != CMARK_EVENT_DONE)
    189 		if(cmark_node_get_type(cmark_iter_get_node(t_titleIter)) == CMARK_NODE_TEXT) 
    190 			strcat(buf, cmark_node_get_literal(cmark_iter_get_node(t_titleIter)));
    191 	cmark_iter_free(t_iter);
    192 	cmark_iter_free(t_titleIter);
    193 	char *title = malloc(strlen(buf)+1);
    194 	char *t = title;
    195 	int i = 0;
    196 	while((*t++ = buf[i++]))
    197 		;
    198 	return title;
    199 }
    200 
    201 /* Gets the first paragraph (or make a blank one) for the RSS description */
    202 char *get_post_desc(struct cmark_node *root)
    203 {
    204 	cmark_iter *t_iter = cmark_iter_new(root);
    205 	while(cmark_iter_next(t_iter) != CMARK_EVENT_DONE)
    206 		if(cmark_node_get_type(cmark_iter_get_node(t_iter)) == CMARK_NODE_PARAGRAPH)
    207 			break;
    208 	cmark_node *t_desc = cmark_iter_get_node(t_iter);
    209 	cmark_iter *t_descIter = cmark_iter_new(t_desc);
    210 	buf[0] = '\0';
    211 	while(cmark_iter_next(t_descIter) != CMARK_EVENT_DONE)
    212 		if(cmark_node_get_type(cmark_iter_get_node(t_descIter)) == CMARK_NODE_TEXT)
    213 			strcat(buf, cmark_node_get_literal(cmark_iter_get_node(t_descIter)));
    214 	cmark_iter_free(t_iter);
    215 	cmark_iter_free(t_descIter);
    216 	char *desc = malloc(strlen(buf)+1);
    217 	char *t = desc;
    218 	int i = 0;
    219 	while((*t++ = buf[i++]))
    220 		;
    221 	return desc;
    222 }
    223 
    224 /* Generates a tm struct based on the time in the post filename */
    225 struct tm *get_post_time(const char *file)
    226 {
    227 	struct tm *time = malloc(sizeof(struct tm));
    228 	strncpy(buf, file, 16);
    229 	if(strptime(buf, "%Y-%m-%d-%H-%M", time) == NULL) {
    230 		printf("ERROR: Failed to convert time\n");
    231 		free(time);
    232 		return NULL;
    233 	}
    234 	return time;
    235 }
    236 
    237 /* Inserts a new text node containing the date and time of the post */
    238 int insert_post_time(struct cmark_node *root, struct tm *time)
    239 {
    240 	if(time == NULL)
    241 		return 1;
    242 	printf("-- Inserting date into post...\n");
    243 	cmark_iter *t_iter = cmark_iter_new(root);
    244 	while(cmark_iter_next(t_iter) != CMARK_EVENT_DONE)
    245 		if(cmark_node_get_type(cmark_iter_get_node(t_iter)) == CMARK_NODE_HEADING)
    246 			break;
    247 	
    248 	/* Store the parent HEADING node */
    249 	cmark_node *t_title = cmark_iter_get_node(t_iter);
    250 	
    251 	/* Create the new node for the date */
    252 	cmark_node *t_date_block = cmark_node_new(CMARK_NODE_HTML_BLOCK);
    253 	/* Create the date string */
    254 	char timebuf[MAX_URL_CHARS];
    255 	strftime(timebuf, MAX_URL_CHARS, "%A, %B %e, %Y", time);
    256 	sprintf(buf, "<p class=\"postdate\">%s</p>", timebuf);
    257 	if(!cmark_node_set_literal(t_date_block, buf))
    258 		return -1;
    259 	cmark_node_insert_after(t_title, t_date_block);
    260 	printf("-- Date inserted.\n");
    261 	return 0;
    262 }
    263 
    264 /* Frees a post from the heap */
    265 void free_post(struct post *p)
    266 {
    267 	free(p->title);
    268 	free(p->dir);
    269 	free(p->fsource);
    270 	free(p->fhtml);
    271 	free(p->content);
    272 	if(p->time != NULL)
    273 		free(p->time);
    274 	free(p);
    275 }
    276 
    277 /* Writes a post to an html file */
    278 int write_post(struct post *post)
    279 {
    280 	printf("-- Writing post %s...\n", post->title);
    281 	/* Generate the directory structure */
    282 	sprintf(buf, "%s%s", HTMLDIR, post->dir);
    283 
    284 	if(create_directory(buf) < 0)
    285 		return -1;
    286 
    287 	sprintf(buf, "%s%s%s", HTMLDIR, post->dir, post->fhtml);
    288 	FILE *f = fopen(buf, "w");
    289 	sprintf(buf, "%s<article>%s</article>%s", header, post->content, footer);
    290 	fprintf(f, buf);
    291 	fclose(f);
    292 	printf("-- Post complete.\n");
    293 	return 0;
    294 }
    295 
    296 /* Writes the index.html file */
    297 int write_index(struct post *posts[], int totalPosts)
    298 {
    299 	printf("-- Writing index.html...\n");
    300 	sprintf(buf, "%s%s", HTMLDIR, "index.html");
    301 	FILE *f = fopen(buf, "w");
    302 	if(f == NULL) {
    303 		errprintf("write_index", errno);
    304 		return -1;
    305 	}
    306 	if(fprintf(f, header) < 0) {
    307 		errprintf("write_index", errno);
    308 		return -1;
    309 	}
    310 	int c = 0;
    311 	while(totalPosts-- > 0 && c < INDEX_POSTS) {
    312 		if(posts[totalPosts]->is_static)
    313 			continue;
    314 		fprintf(f, "<article>\n");
    315 		if(fprintf(f, posts[totalPosts]->content) < 0) {
    316 			errprintf("write_index", errno);
    317 			fclose(f);
    318 			return -1;
    319 		}
    320 		fprintf(f, "</article>\n");
    321 		if(totalPosts > 0) 
    322 			fprintf(f, "<hr>\n");
    323 		c++;
    324 	}
    325 	if(fprintf(f, footer) < 0) {
    326 		errprintf("write_index", errno);
    327 		fclose(f);
    328 		return -1;
    329 	}
    330 	fclose(f);
    331 	printf("-- index.html complete.\n");
    332 	return 0;
    333 }
    334 
    335 /* Writes the archive.html file */
    336 int write_archive(struct post *posts[], int totalPosts)
    337 {
    338 	printf("-- Writing archive.html...\n");
    339 	sprintf(buf, "%s%s", HTMLDIR, "archive.html");
    340 	FILE *f = fopen(buf, "w");
    341 	fprintf(f, header);
    342 	fprintf(f, "<article class=\"archive\">\n<ul>\n");
    343 	while(totalPosts-- > 0) {
    344 		if(posts[totalPosts]->is_static)
    345 			continue;
    346 		strftime(buf, MAX_URL_CHARS, "%Y-%m-%d", posts[totalPosts]->time);
    347 		fprintf(f, "<li><a href=\"%s%s\">%s - %s</a></li>\n",
    348 				posts[totalPosts]->dir,
    349 				posts[totalPosts]->fhtml,
    350 				buf,
    351 				posts[totalPosts]->title);
    352 	}
    353 	fprintf(f, "</ul>\n</article>\n<hl>\n");
    354 	fprintf(f, footer);
    355 	fclose(f);
    356 	printf("-- archive.html complete.\n");
    357 	return 0;
    358 }
    359 
    360 int write_rss(struct post *posts[], int totalPosts)
    361 {
    362 	printf("-- Writing RSS feed...\n");
    363 	sprintf(buf, "%s%s", HTMLDIR, "feed.xml");
    364 	FILE *f = fopen(buf, "w");
    365 	fprintf(f, rssHeader);
    366 	while(totalPosts-- > 0) {
    367 		fprintf(f, "<item>\n<title>%s</title>\n<link>%s%s%s</link>\n",
    368 				posts[totalPosts]->title,
    369 				WEBSITE,
    370 				posts[totalPosts]->dir,
    371 				posts[totalPosts]->fhtml);
    372 		fprintf(f, "<description>");
    373 		int i;
    374 		char *c = posts[totalPosts]->desc;
    375 		for(i = 0; i < 200 && *c != '\0'; ++i) {
    376 			fprintf(f, "%c", *c++);
    377 		}
    378 		if(*c != '\0')
    379 			fprintf(f, "...");
    380 		fprintf(f, "</description>\n</item>\n");
    381 	}
    382 	fprintf(f, rssFooter);
    383 	fclose(f);
    384 	printf("-- feed.xml complete.\n");
    385 	return 0;
    386 }
    387 
    388 /* Recursively copies all the static files in RESOURCEDIR to HTMLDIR */
    389 void copy_resources(char *dir)
    390 {
    391 	printf("-- Copying resources...\n");
    392 	printf("Creating directory %s\n", dir);
    393 	sprintf(buf, "%s%s", HTMLDIR, dir + strlen(RESOURCEDIR));
    394 	create_directory(buf);
    395 	char t_dest[MAX_URL_CHARS];
    396 	struct stat t_stat;
    397 	/* find all files in resource directory */
    398 	struct dirent **t_files;
    399 	printf("Scanning directory %s\n", dir);
    400 	int t_count = scandir(dir, &t_files, NULL, NULL);
    401 	int t_cleanup = t_count;
    402 	while(t_count-- > 0) {
    403 		if(t_files[t_count]->d_name[0] == '.')
    404 			continue;
    405 		printf("Found %s%s\n", dir, t_files[t_count]->d_name);
    406 		sprintf(buf, "%s%s", dir, t_files[t_count]->d_name);
    407 		stat(buf, &t_stat);
    408 		if(S_ISDIR(t_stat.st_mode)) {
    409 			char t_dir[MAX_URL_CHARS];
    410 			sprintf(t_dir, "%s%s/", dir, t_files[t_count]->d_name);
    411 			printf("Directory found: %s\n", t_dir);
    412 			copy_resources(t_dir);
    413 		}
    414 		if(S_ISREG(t_stat.st_mode)) {	
    415 			sprintf(t_dest, "%s%s", HTMLDIR, buf + strlen(RESOURCEDIR));
    416 			printf("Copying %s to %s\n", buf, t_dest);
    417 			copy_file(buf, t_dest);
    418 		}
    419 	}
    420 	while(t_cleanup-- > 0)
    421 		free(t_files[t_cleanup]);
    422 	free(t_files);
    423 	printf("-- Finished copying resources.\n");
    424 }
    425 
    426 /* Checks if a file is a blog post or a static page */
    427 int is_post_static(char *file)
    428 {
    429 	printf("-- Checking if post %s is static...\n", file);
    430 	if (strncmp(file, STATIC_PAGE, sizeof(STATIC_PAGE) - 1) == 0) {
    431 		printf("Yep!\n");
    432 		return 1;
    433 	}
    434 	printf("Nope!\n");
    435 	return 0;
    436 }