[RSS] Repository clover by nishi (Revision 13): /trunk/clover.c

HTML 2.0 compliant static site generator

File Latest Commit

r13 | nishi | 2024-09-07 07:37:46 +0900 (Sat, 07 Sep 2024) | 1 line

add link

/* $Id: clover.c 13 2024-09-06 22:37:46Z nishi $ */
/* --- START LICENSE --- */
/* -------------------------------------------------------------------------- */
/*                                  HTML 2.0 compliant static site generator  */
/* -------------------------------------------------------------------------- */
/* Copyright (c) 2024 Nishi.                                                  */
/* Redistribution and use in source and binary forms, with or without modific */
/* ation, are permitted provided that the following conditions are met:       */
/*     1. Redistributions of source code must retain the above copyright noti */
/* ce, this list of conditions and the following disclaimer.                  */
/*     2. Redistributions in binary form must reproduce the above copyright n */
/* otice, this list of conditions and the following disclaimer in the documen */
/* tation and/or other materials provided with the distribution.              */
/*     3. Neither the name of the copyright holder nor the names of its contr */
/* ibutors may be used to endorse or promote products derived from this softw */
/* are without specific prior written permission.                             */
/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS */
/* " AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, TH */
/* E IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPO */
/* SE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS  */
/* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CON */
/* SEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITU */
/* TE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPT */
/* ION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, S */
/* TRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN AN */
/* Y WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY  */
/* OF SUCH DAMAGE.                                                            */
/* -------------------------------------------------------------------------- */
/* --- END LICENSE --- */

#include <dirent.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>

#include <expat.h>

#define CLOVER_VERSION "1.0E"

char* concat_str(const char* str1, const char* str2) {
	char* r = malloc(strlen(str1) + strlen(str2) + 1);
	memcpy(r, str1, strlen(str1));
	memcpy(r + strlen(str1), str2, strlen(str2));
	r[strlen(str1) + strlen(str2)] = 0;
	return r;
}

char* concat3_str(const char* str1, const char* str2, const char* str3) {
	char* tmp = concat_str(str1, str2);
	char* str = concat_str(tmp, str3);
	free(tmp);
	return str;
}

char* dup_str(const char* str) {
	char* r = malloc(strlen(str) + 1);
	memcpy(r, str, strlen(str));
	r[strlen(str)] = 0;
	return r;
}

char* input_dir;
char* output_dir;
char* title;
char* icon;
char* http_path;
char* static_dir;

int parse_config(const char* path) {
	FILE* f = fopen(path, "r");
	if(f == NULL) {
		fprintf(stderr, "%s: could not open the file\n", path);
		return 1;
	}
	char* bf = malloc(2);
	bf[1] = 0;
	char* str = malloc(1);
	str[0] = 0;
	while(1) {
		fread(bf, 1, 1, f);
		if(feof(f)) break;
		if(bf[0] == '\n') {
			int i;
			bool found = false;
			for(i = 0; str[i] != 0; i++) {
				if(str[i] == ' ') {
					for(; str[i] != 0 && (str[i] == ' ' || str[i] == '\t'); i++) str[i] = 0;
					found = true;
					break;
				}
			}
			if(str[0] == '#') {
			} else if(strcmp(str, "output") == 0) {
				if(found) {
					free(output_dir);
					output_dir = dup_str(str + i);
					mkdir(str + i, 0775);
				}
			} else if(strcmp(str, "input") == 0) {
				if(found) {
					free(input_dir);
					input_dir = dup_str(str + i);
				}
			} else if(strcmp(str, "static") == 0) {
				if(found) {
					free(static_dir);
					static_dir = dup_str(str + i);
				}
			} else if(strcmp(str, "title") == 0) {
				if(found) {
					free(title);
					title = dup_str(str + i);
				}
			} else if(strcmp(str, "icon") == 0) {
				if(found) {
					free(icon);
					icon = dup_str(str + i);
				}
			} else if(strcmp(str, "http-path") == 0) {
				if(found) {
					free(http_path);
					http_path = dup_str(str + i);
				}
			}
			free(str);
			str = malloc(1);
			str[0] = 0;
		} else if(bf[0] != '\r') {
			char* tmp = str;
			str = concat_str(tmp, bf);
			free(tmp);
		}
	}
	free(str);
	free(bf);
	fclose(f);
	return 0;
}

int typearr[1024];
int typearr2[1024];
int depth;
int depth2;

enum TYPES { ELEM_NONE, ELEM_METADATA, ELEM_TITLE, ELEM_PAGE, ELEM_ICON, ELEM_SHORTDESC };

char* page_title;
char* page_data;
char* page_icon;
char* page_shortdesc;
bool page_index;
bool page_do_not_index;

char* page_title2;
char* page_data2;
char* page_icon2;
char* page_shortdesc2;
bool page_index2;
bool page_do_not_index2;

static void XMLCALL xml_startelem(void* user_data, const XML_Char* el, const XML_Char* attr[]) {
	typearr[depth] = ELEM_NONE;
	if(strcmp(el, "metadata") == 0) typearr[depth] = ELEM_METADATA;
	if(strcmp(el, "title") == 0) typearr[depth] = ELEM_TITLE;
	if(strcmp(el, "page") == 0) typearr[depth] = ELEM_PAGE;
	if(strcmp(el, "icon") == 0) typearr[depth] = ELEM_ICON;
	if(strcmp(el, "short-description") == 0) typearr[depth] = ELEM_SHORTDESC;
	if(strcmp(el, "index") == 0) page_index = true;
	if(strcmp(el, "do-not-index") == 0) page_do_not_index = true;
	if(depth == 0 && typearr[depth] != ELEM_METADATA) {
		fprintf(stderr, "toplevel must be metadata\n");
		exit(1);
	}
	depth++;
}

static void XMLCALL xml_startelem2(void* user_data, const XML_Char* el, const XML_Char* attr[]) {
	typearr2[depth2] = ELEM_NONE;
	if(strcmp(el, "metadata") == 0) typearr2[depth2] = ELEM_METADATA;
	if(strcmp(el, "title") == 0) typearr2[depth2] = ELEM_TITLE;
	if(strcmp(el, "page") == 0) typearr2[depth2] = ELEM_PAGE;
	if(strcmp(el, "icon") == 0) typearr2[depth2] = ELEM_ICON;
	if(strcmp(el, "index") == 0) page_index2 = true;
	if(strcmp(el, "do-not-index") == 0) page_do_not_index2 = true;
	if(strcmp(el, "short-description") == 0) typearr2[depth2] = ELEM_SHORTDESC;
	if(depth2 == 0 && typearr2[depth2] != ELEM_METADATA) {
		fprintf(stderr, "toplevel must be metadata\n");
		exit(1);
	}
	depth2++;
}

static void XMLCALL xml_endelem(void* user_data, const XML_Char* el) { depth--; }

static void XMLCALL xml_endelem2(void* user_data, const XML_Char* el) { depth2--; }

static void XMLCALL xml_elemdata(void* user_data, const XML_Char* data, int data_size) {
	if(data_size > 0) {
		char* str = malloc(data_size + 1);
		memcpy(str, data, data_size);
		str[data_size] = 0;
		if(typearr[depth - 1] == ELEM_TITLE) {
			if(page_title != NULL) {
				char* tmp = page_title;
				page_title = concat_str(tmp, str);
				free(tmp);
			} else {
				page_title = dup_str(str);
			}
		} else if(typearr[depth - 1] == ELEM_PAGE) {
			if(page_data != NULL) {
				char* tmp = page_data;
				page_data = concat_str(tmp, str);
				free(tmp);
			} else {
				page_data = dup_str(str);
			}
		} else if(typearr[depth - 1] == ELEM_ICON) {
			if(page_icon != NULL) {
				char* tmp = page_icon;
				page_icon = concat_str(tmp, str);
				free(tmp);
			} else {
				page_icon = dup_str(str);
			}
		} else if(typearr[depth - 1] == ELEM_SHORTDESC) {
			if(page_shortdesc != NULL) {
				char* tmp = page_shortdesc;
				page_shortdesc = concat_str(tmp, str);
				free(tmp);
			} else {
				page_shortdesc = dup_str(str);
			}
		}
		free(str);
	}
}

static void XMLCALL xml_elemdata2(void* user_data, const XML_Char* data, int data_size) {
	if(data_size > 0) {
		char* str = malloc(data_size + 1);
		memcpy(str, data, data_size);
		str[data_size] = 0;
		if(typearr2[depth2 - 1] == ELEM_TITLE) {
			if(page_title2 != NULL) {
				char* tmp = page_title2;
				page_title2 = concat_str(tmp, str);
				free(tmp);
			} else {
				page_title2 = dup_str(str);
			}
		} else if(typearr2[depth2 - 1] == ELEM_PAGE) {
			if(page_data2 != NULL) {
				char* tmp = page_data2;
				page_data2 = concat_str(tmp, str);
				free(tmp);
			} else {
				page_data2 = dup_str(str);
			}
		} else if(typearr2[depth2 - 1] == ELEM_ICON) {
			if(page_icon2 != NULL) {
				char* tmp = page_icon2;
				page_icon2 = concat_str(tmp, str);
				free(tmp);
			} else {
				page_icon2 = dup_str(str);
			}
		} else if(typearr2[depth2 - 1] == ELEM_SHORTDESC) {
			if(page_shortdesc2 != NULL) {
				char* tmp = page_shortdesc2;
				page_shortdesc2 = concat_str(tmp, str);
				free(tmp);
			} else {
				page_shortdesc2 = dup_str(str);
			}
		}
		free(str);
	}
}

void scan_and_print(FILE* f, const char* path, const char* http){
	DIR* dir = opendir(path);
	if(dir != NULL){
		struct dirent* d;
		while((d = readdir(dir)) != NULL){
			if(strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) continue;
			char* p = concat3_str(path, "/", d->d_name);
			struct stat s;
			if(stat(p, &s) == 0){
				if(S_ISDIR(s.st_mode)){
					char* new_http = concat3_str(http, d->d_name, "/");
					scan_and_print(f, p, new_http);
					free(new_http);
				}else if(strcmp(d->d_name, "metadata.xml") == 0){
					page_title2 = NULL;
					page_data2 = NULL;
					page_icon2 = NULL;
					page_shortdesc2 = NULL;
					page_do_not_index2 = false;
					FILE* fp = fopen(p, "r");
					XML_Parser sparser = XML_ParserCreate(NULL);
					XML_SetElementHandler(sparser, xml_startelem2, xml_endelem2);
					XML_SetCharacterDataHandler(sparser, xml_elemdata2);
					if(fp != NULL){
						char* buf = malloc(1024);
						int done;
						do{
							int len = fread(buf, 1, 1024, fp);
							done = len < 1024;
							if(XML_Parse(sparser, buf, len, done) == XML_STATUS_ERROR){
								break;
							}
						}while(!done);
						free(buf);
						fclose(fp);
					}
					XML_ParserFree(sparser);
					if(!page_do_not_index2){
						fprintf(f, "<dt><a href=\"%s/%s\">", http_path, http);
						if(page_icon2 != NULL) fprintf(f, "<img src=\"%s/static/%s\" alt=\"%s\">", http_path, page_icon2, page_title2);
						if(page_icon2 != NULL) fprintf(f, "</a> ");
						if(page_title2 != NULL) fprintf(f, "<b>%s </b>", page_title2);
						if(page_icon2 == NULL) fprintf(f, "</a>");
						fprintf(f, "</dt>\n");
						if(page_shortdesc2 != NULL){
							fprintf(f, "<dd>%s</dd>\n", page_shortdesc2);
						}
					}
					if(page_title2 != NULL) free(page_title2);
					if(page_data2 != NULL) free(page_data2);
					if(page_icon2 != NULL) free(page_icon2);
					if(page_shortdesc2 != NULL) free(page_shortdesc2);
				}
			}
			free(p);
		}
		closedir(dir);
	}
}

int parse_xml(const char* xml, const char* outdir) {
	printf("%s...\n", xml);
	depth = 0;
	page_title = NULL;
	page_data = NULL;
	page_icon = NULL;
	page_index = false;
	page_shortdesc = NULL;
	XML_Parser parser = XML_ParserCreate(NULL);
	XML_SetElementHandler(parser, xml_startelem, xml_endelem);
	XML_SetCharacterDataHandler(parser, xml_elemdata);
	FILE* f = fopen(xml, "r");
	char* buffer = malloc(1024);
	int done = 0;
	do {
		int len = fread(buffer, 1, 1024, f);
		done = len < 1024;
		if(XML_Parse(parser, buffer, len, done) == XML_STATUS_ERROR) {
			fprintf(stderr, "XML parse error\n");
			XML_ParserFree(parser);
			fclose(f);
			free(buffer);
			return 1;
		}
	} while(!done);
	fclose(f);
	free(buffer);
	XML_ParserFree(parser);
	char* html = concat_str(outdir, "/index.html");
	f = fopen(html, "w");
	if(f != NULL) {
		char* lastmod = malloc(513);
		struct stat s;
		stat(xml, &s);
		struct tm* tm = localtime(&s.st_mtime);
		strftime(lastmod, 512, "%a %b %e %H:%M:%S %Z %Y", tm);
		fprintf(f, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n");
		fprintf(f, "<html>\n");
		fprintf(f, "	<head>\n");
		fprintf(f, "		<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
		if(page_title != NULL) {
			fprintf(f, "		<title>%s - %s</title>\n", title, page_title);
		} else {
			fprintf(f, "		<title>%s</title>\n", title);
		}
		fprintf(f, "		<link rel=\"icon\" href=\"%s/static/%s\">\n", http_path, icon);
		fprintf(f, "	</head>\n");
		fprintf(f, "	<body>\n");
		fprintf(f, "		<a href=\"%s\"><img src=\"%s/static/%s\" alt=\"website icon\"></a>\n", http_path, http_path, icon);
		if(page_icon != NULL){
			char* from_path = concat3_str(static_dir, "/", page_icon);
			char* to_path = concat3_str(output_dir, "/static/", page_icon);
			FILE* from = fopen(from_path, "rb");
			FILE* to = fopen(to_path, "wb");
			if(from != NULL){
				char* buf = malloc(1024);
				int done = 0;
				do{
					int len = fread(buf, 1, 1024, from);
					fwrite(buf, 1, len, to);
					done = len < 1024;
				}while(!done);
				free(buf);
				fclose(from);
			}
			fclose(to);
			free(from_path);
			free(to_path);
			fprintf(f, "		<img src=\"%s/static/%s\" alt=\"page icon\">\n", http_path, page_icon);
		}
		if(page_title != NULL) {
			fprintf(f, "		<h1>%s - %s</h1>\n", title, page_title);
		} else {
			fprintf(f, "		<h1>%s</h1>\n", title);
		}
		if(page_data != NULL) {
			fprintf(f, "%s\n", page_data);
		}
		if(page_index){
			fprintf(f, "		<hr>\n");
			fprintf(f, "		<dl>\n");
			scan_and_print(f, input_dir, "");
			fprintf(f, "		</dl>\n");
		}
		fprintf(f, "		<hr>\n");
		fprintf(f, "		<i>Last modified: %s</i><br>\n", lastmod);
		fprintf(f, "		<i>Generated by <a href=\"http://nishi.boats/clover\">Clover</a> %s</i>\n", CLOVER_VERSION);
		fprintf(f, "	</body>\n");
		fprintf(f, "</html>\n");
		fclose(f);
		free(lastmod);
	}
	free(html);
	if(page_title != NULL) free(page_title);
	if(page_data != NULL) free(page_data);
	if(page_icon != NULL) free(page_icon);
	if(page_shortdesc != NULL) free(page_shortdesc);
	return 0;
}

void generate(const char* input, const char* output) {
	mkdir(output, 0775);
	DIR* dir = opendir(input);
	if(dir != NULL) {
		struct dirent* d;
		while((d = readdir(dir)) != NULL) {
			if(strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) continue;
			char* p = concat3_str(input, "/", d->d_name);
			struct stat s;
			if(stat(p, &s) == 0) {
				if(S_ISDIR(s.st_mode)) {
					char* op = concat3_str(output, "/", d->d_name);
					generate(p, op);
					free(op);
				} else if(strcmp(d->d_name, "metadata.xml") == 0) {
					char* xml = concat3_str(input, "/", d->d_name);
					if(parse_xml(xml, output) == 1) {
						free(xml);
						free(p);
						break;
					}
					free(xml);
				}
			}
			free(p);
		}
		closedir(dir);
	}
}

int main(int argc, char** argv) {
	output_dir = dup_str("webroot");
	input_dir = dup_str("input");
	title = dup_str("Unnamed");
	icon = dup_str("icon.png");
	static_dir = dup_str("static");
	http_path = dup_str("/");
	int r;
	if(argv[1] == NULL) {
		r = parse_config("clover.conf");
	} else {
		r = parse_config(argv[1]);
	}
	printf("Clover %s Copyright (c) 2024 Nishi\n", CLOVER_VERSION);
	printf("Using Expat %d.%d.%d\n", XML_MAJOR_VERSION, XML_MINOR_VERSION, XML_MICRO_VERSION);
	printf("       Icon: %s\n", icon);
	printf("      Title: %s\n", title);
	printf("      Input: %s\n", input_dir);
	printf("     Output: %s\n", output_dir);
	printf("  HTTP Path: %s\n", http_path);
	printf("Static Path: %s\n", static_dir);
	char* staticp = concat_str(output_dir, "/static");
	mkdir(staticp, 0775);
	char* toicon = concat3_str(staticp, "/", icon);
	FILE* ficon = fopen(toicon, "wb");
	char* iconpath = concat3_str(static_dir, "/", icon);
	FILE* from = fopen(iconpath, "rb");
	if(from != NULL){
		char* buf = malloc(1024);
		int done = 0;
		do{
			int len = fread(buf, 1, 1024, from);
			fwrite(buf, 1, len, ficon);
			done = len < 1024;
		}while(!done);
		free(buf);
		fclose(from);
	}
	fclose(ficon);
	free(toicon);
	free(staticp);
	free(iconpath);
	generate(input_dir, output_dir);
	if(r != 0) return r;
}
Powered by SwineHub Version: 1.503-gcc $Id: swinehub.h 130 2023-11-14 20:23:25Z nishi $ Copyright