slweb

Једноставни генератор статичких веб страна
git clone https://git.sr.ht/~strahinja/slweb
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

чување e96e4c18ba5ebec60ca19e3d99e3ca03f2ed9d15
родитељ 9c5daccbf45b9d5aa21a474cf9b1f9d096783d1b
Аутор: Страхиња Радић <sr@strahinja.org>
Датум:   Mon, 22 Jul 2024 21:01:40 +0200

slweb.c: Code cleanup; use assert; sort variables and functions

Diffstat:
Mconfig.mk | 5++---
Mslweb.c | 4555++++++++++++++++++++++++++++++++++++++-----------------------------------------
измењених датотека: 2, додавања: 2194(+), брисања: 2366(-)

diff --git a/config.mk b/config.mk @@ -1,10 +1,10 @@ #CC = cc CFLAGS = -Os -Wextra -Wall -pedantic -std=c99 CPPFLAGS = -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L \ - -D_XOPEN_SOURCE=700 + -D_XOPEN_SOURCE=700 -DNDEBUG # OpenBSD #CPPFLAGS = -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L \ -# -D_XOPEN_SOURCE=700 -D_BSD_SOURCE +# -D_XOPEN_SOURCE=700 -D_BSD_SOURCE -DNDEBUG HTMLS = examples/basic/index.html examples/blockquote/index.html \ examples/breakmarks/index.html \ examples/csv-tsv/index-csv.html examples/csv-tsv/index.html \ @@ -26,7 +26,6 @@ SLW_includes = examples/includes/index.slw examples/includes/bugs.slw \ examples/includes/options.slw \ examples/includes/reference.slw examples/includes/seealso.slw - # Derived macros BINDIR = $(DESTDIR)$(PREFIX)/bin DOCDIR = $(DESTDIR)$(PREFIX)/share/doc/$(PROG) diff --git a/slweb.c b/slweb.c @@ -24,112 +24,273 @@ #include "defs.h" #include "version.h" -static size_t lineno = 0; -static size_t colno = 1; -static int output_firstcol = 1; -static char* input_filename = NULL; -static char* input_dirname = NULL; -static size_t ifn_size = 0; -static char* basedir = NULL; -static size_t basedir_size = 0; -static char* incdir = NULL; -static char* global_link_prefix = NULL; -static size_t global_link_prefix_size = 0; -static KeyValue* vars = NULL; -static KeyValue* pvars = NULL; -static size_t vars_count = 0; -static KeyValue* macros = NULL; -static KeyValue* pmacros = NULL; -static size_t macros_count = 0; -static KeyValue* links = NULL; -static KeyValue* plinks = NULL; -static size_t links_count = 0; static KeyValue* footnotes = NULL; +static KeyValue* links = NULL; static KeyValue* pfootnotes = NULL; -static size_t footnote_count = 0; -static size_t current_footnote = 0; +static KeyValue* plinks = NULL; +static KeyValue* macros = NULL; +static KeyValue* pmacros = NULL; +static KeyValue* pvars = NULL; +static KeyValue* vars = NULL; static u8** inline_footnotes = NULL; -static size_t inline_footnote_count = 0; -static size_t current_inline_footnote = 0; static u8* csv_template = NULL; +static u8* tsv_template = NULL; +static char* basedir = NULL; +static char* csv_filename = NULL; +static char* global_link_prefix = NULL; +static char* incdir = NULL; +static char* input_dirname = NULL; +static char* input_filename = NULL; +static char* tsv_filename = NULL; +static unsigned long long state = ST_NONE; +static size_t basedir_size = 0; +static size_t colno = 1; +static size_t csv_iter = 0; static size_t csv_template_len = 0; static size_t csv_template_size = 0; -static char* csv_filename = NULL; -static long csv_iter = 0; -static u8* tsv_template = NULL; +static size_t current_footnote = 0; +static size_t current_inline_footnote = 0; +static size_t footnote_count = 0; +static size_t global_link_prefix_size = 0; +static size_t ifn_size = 0; +static size_t inline_footnote_count = 0; +static size_t lineno = 0; +static size_t links_count = 0; +static size_t macros_count = 0; +static size_t tsv_iter = 0; static size_t tsv_template_len = 0; static size_t tsv_template_size = 0; -static char* tsv_filename = NULL; -static long tsv_iter = 0; -static ULLONG state = ST_NONE; +static size_t vars_count = 0; static int incdir_only_summary = 0; +static int output_firstcol = 1; -int cleanup(void); -int usage(void); -int version(const int full); +static int add_css(FILE* output, int output_yaml); +static int error(const int code, const char* file, const char* func, + const int line, const char* fmt, ...); +static u8* format_date(const u8* date_arg, const char* timestamp_format); +static int free_keyvalue(KeyValue** list, const size_t list_count); +static u8* get_value(KeyValue* list, const size_t list_count, const u8* key, + KeyValue** cursor); +static int print_meta_var(FILE* output, u8** tsv_header, u8** tsv_register); +static int print_output(FILE* output, const char* fmt, ...); +static int process_horizontal_rule(FILE* output); +static int process_incdir_subdir(FILE* output, const char* subdirname, + const u8* link_prefix, const int details_open, const u8* macro_body, + const u8* footer_permalink_text, const char* permalink_url, + const int ext_in_permalink, const int list_only); +static int process_inline_image(FILE* output, const u8* image_text, + const u8* image_file_prefix, const u8* link_prefix, const u8* image_url, + const int add_link, const int add_figcaption); +static int process_inline_stylesheet(FILE* output, const u8* css_filename, + int output_yaml); +static void process_list_end(FILE* output); +static void process_list_item_end(FILE* output); +static void process_numlist_end(FILE* output); +static void process_stylesheet(FILE* output, const u8* css_filename, + int output_yaml); +static void process_timestamp(FILE* output, const u8* link_prefix, + const char* link, const u8* permalink_macro, const u8* date, + const int ext_in_permalink); +static int process_tsv(FILE* output, const u8* arg_token, + const int read_yaml_macros_and_links, const int end_tag); +static int process_tsv_count(FILE* output, const u8* arg_token, + const int read_yaml_macros_and_links, const int is_tsv); +static void read_csv(FILE* output, const char* filename, + csv_callback_t callback); +static int read_file_into_buffer(FILE** input, u8** buffer, size_t* buffer_size, + const char* input_filename, char** input_dirname); +static int read_tsv(FILE* output, const char* filename, tsv_callback_t callback); +static int reverse_alphacompare(const struct dirent** a, + const struct dirent** b); +static int set_basedir(char** basedir, size_t* basedir_size, const char* arg); +static int simple_parse_yaml_line(const u8* line, KeyValue** vars, + size_t* vars_count, KeyValue** pvars); +static int slweb_parse(FILE* output, const char* source_filename, + const size_t sfn_size, const u8* buffer, const int body_only, + const int read_yaml_macros_and_links); +static int startswith(const char* s, const char* what); +static char* strip_ext(const char* fn, const size_t fn_size); +static int url_is_local(const char* url); +static int warning(const int code, const u8* fmt, ...); -int -version(const int full) +static int +add_css(FILE* output, int output_yaml) { - printf("%s %s, committed on %s\n", PROGRAMNAME, VERSION, DATE); - if (full) - puts(COPYRIGHT); + KeyValue* pvars = vars; + assert(vars != NULL); + while (pvars < vars + vars_count) + { + assert(pvars->key != NULL); + if (!strcmp((char*)pvars->key, "stylesheet")) + process_stylesheet(output, pvars->value, output_yaml); + else if (!strcmp((char*)pvars->key, "inline-stylesheet")) + process_inline_stylesheet(output, pvars->value, + output_yaml); + pvars++; + } return 0; } -int -usage(void) +static int +all_numeric(const u8* start, const u8* end) { - printf("Usage:\t%s -h | --help | -V | --full-version | -v | --version\n" - "\t%s [-b | --body-only] [-d directory | --basedir directory]" - " [-p URL | --global-link-prefix URL] [filename]\n", - PROGRAMNAME, PROGRAMNAME); - return 0; + const u8* ps = start; + assert((start != NULL) && (end != NULL)); + while (ps != end) + { + if (*ps && !isdigit((const int)*ps)) + return 0; + ps++; + } + return 1; } -int -error(const int code, const char* file, const char* func, const int line, - const char* fmt, ...) +static int +begin_article(FILE* output, const char* source_filename, const size_t sfn_size, + const u8* link_prefix, const int add_article_header, const u8* author, + const u8* title, const u8* header_text, const char* title_heading_level, + u8* date, const int ext_in_permalink, const char* permalink_url) { - char buf[BUFSIZE]; - va_list args; - va_start(args, fmt); - vsnprintf(buf, sizeof(buf), (const char*)fmt, args); - va_end(args); - fprintf(stderr, "%s:%s:%lu:%lu:%s:%s:%d: %s\n", PROGRAMNAME, - input_filename ? input_filename : "(init/stdin)", lineno, colno, - func, file, line, buf); - return code; -} + char* temp = NULL; + int save_incdir = IN(state, ST_INCDIR); -int -warning(const int code, const u8* fmt, ...) -{ - char buf[BUFSIZE]; - va_list args; - va_start(args, fmt); - vsnprintf(buf, sizeof(buf), (const char*)fmt, args); - va_end(args); - fprintf(stderr, "Warning: %s\n", buf); - return code; + UNUSED(add_article_header); + CLEAR(state, ST_INCDIR); + + if (author || date || header_text || title) + print_output(output, "<header>\n"); + + if (title) + print_output(output, "<h%s>%s</h%s>\n", + title_heading_level ? title_heading_level : "2", + (char*)title, + title_heading_level ? title_heading_level : "2"); + + if (header_text) + print_output(output, "<p>%s\n", (char*)header_text); + + if (author) + print_output(output, "<address>%s</address>\n", author); + + if (date && source_filename) + { + u8* permalink_macro = NULL; + char* link = NULL; + size_t link_len; + + permalink_macro = get_value(macros, macros_count, + (u8*)"permalink", NULL); + link = strip_ext((const char*)source_filename, BUFSIZE); + assert(sfn_size != 0); + link_len = strlen(link); + if (ext_in_permalink) + MEMCCPY((link + link_len), timestamp_output_ext, + sfn_size - link_len, temp); + if (permalink_url) + process_timestamp(output, link_prefix, permalink_url, + permalink_macro, date, ext_in_permalink); + else + process_timestamp(output, link_prefix, link, + permalink_macro, date, ext_in_permalink); + free(link); + } + + if (author || date || header_text || title) + print_output(output, "</header>\n"); + + output_firstcol = author || date || header_text || title; + + if (save_incdir) + SET(state, ST_INCDIR); + + return 0; } -int -free_keyvalue(KeyValue** list, const size_t list_count) +static int +begin_html_and_head(FILE* output) { - if (!list || !*list) - return 1; + KeyValue* cursor = NULL; + KeyValue* cursor_type = NULL; + KeyValue* cursor_desc = NULL; + u8* lang = get_value(vars, vars_count, (u8*)"lang", NULL); + u8* site_name = get_value(vars, vars_count, (u8*)"site-name", NULL); + u8* site_desc = get_value(vars, vars_count, (u8*)"site-desc", NULL); + u8* canonical = get_value(vars, vars_count, (u8*)"canonical", NULL); + u8* favicon_url = get_value(vars, vars_count, (u8*)"favicon-url", NULL); + u8* meta = get_value(vars, vars_count, (u8*)"meta", NULL); + u8* feed = NULL; + u8* feed_type = NULL; + u8* feed_desc = NULL; + char* favicon = NULL; + char* filename = NULL; - for (size_t index = 0; index < list_count; index++) + print_output(output, + "<!DOCTYPE html>\n" + "<html lang=\"%s\">\n" + "<head>\n" + "<title>%s</title>\n" + "<meta charset=\"utf-8\">\n", + lang ? (char*)lang : "en", site_name ? (char*)site_name : ""); + + CALLOC(favicon, char, BUFSIZE); + snprintf(favicon, BUFSIZE - 1, "%s/favicon.ico", basedir); + if (!access(favicon, R_OK)) + print_output(output, + "<link rel=\"shortcut icon\" type=\"image/x-icon\"" + " href=\"%s\">\n", + favicon_url ? (char*)favicon_url : "/favicon.ico"); + free(favicon); + + if (meta) { - KeyValue* current = *list + index; - free(current->value); - free(current->key); + CALLOC(filename, char, BUFSIZE); + snprintf(filename, BUFSIZE, "%s/%s", input_dirname, (char*)meta); + read_tsv(output, filename, &print_meta_var); + free(filename); } + + if (canonical && *canonical) + print_output(output, "<link rel=\"canonical\" href=\"%s\">\n", + (char*)canonical); + + cursor = vars; + cursor_type = vars; + cursor_desc = vars; + do + { + feed = get_value(cursor, vars_count - (cursor - vars), + (u8*)"feed", &cursor); + feed_type = get_value(cursor_type, + vars_count - (cursor_type - vars), (u8*)"feed-type", + &cursor_type); + feed_desc = get_value(cursor_desc, + vars_count - (cursor_desc - vars), (u8*)"feed-desc", + &cursor_desc); + if (feed && *feed && feed_type && *feed_type && feed_desc + && *feed_desc) + print_output(output, + "<link rel=\"alternate\"" + " type=\"%s\" href=\"%s\" " + "title=\"%s\">\n", + (char*)feed_type, (char*)feed, + (char*)feed_desc); + } while (feed && feed_type && feed_desc); + + if (site_desc && *site_desc) + print_output(output, + "<meta name=\"description\" content=\"%s\">\n", + (char*)site_desc); + + print_output(output, + "<meta name=\"viewport\" content=\"width=device-width," + " initial-scale=1\">\n<meta name=\"generator\" " + "content=\"slweb\">\n"); + + output_firstcol = 1; return 0; } -int +static int cleanup(void) { free(basedir); @@ -149,73 +310,216 @@ cleanup(void) return 0; } -int add_css(FILE* output, int output_yaml); -u8* format_date(const u8* date_arg, const char* timestamp_format); -int simple_parse_yaml_line(const u8* line, KeyValue** vars, size_t* vars_count, - KeyValue** pvars); -int slweb_parse(FILE* output, const char* source_filename, - const size_t sfn_size, const u8* buffer, const int body_only, - const int read_yaml_macros_and_links); - -int -startswith(const char* s, const char* what) +static int +end_body_and_html(FILE* output) { - if (!s || !what) - return 0; + print_output(output, "</body>\n</html>\n"); + output_firstcol = 1; + return 0; +} - do +static int +end_footnotes(FILE* output, int add_footnote_div) +{ + size_t footnote = 0; + + CLEAR(state, ST_PARA_OPEN); + + if (add_footnote_div) { - if (*s++ != *what++) - return 0; - } while (*s && *what); + print_output(output, "<div class=\"footnotes\">\n"); + output_firstcol = 1; + } - return !*what; + (void)process_horizontal_rule(output); + + for (footnote = 0; footnote < inline_footnote_count; footnote++) + { + print_output(output, + "<p id=\"inline-footnote-%d\">" + "<a href=\"#inline-footnote-text-%d\">%d.</a> " + "%s\n", + footnote + 1, footnote + 1, footnote + 1, + (char*)inline_footnotes[footnote]); + output_firstcol = 1; + } + + KeyValue* pfootnote = footnotes; + footnote = 0; + while (pfootnote && footnote < footnote_count) + { + print_output(output, + "<p id=\"footnote-%d\">" + "<a href=\"#footnote-text-%d\">%d.</a> %s\n", + footnote + 1, footnote + 1, footnote + 1, + (char*)pfootnote->value); + output_firstcol = 1; + pfootnote++; + footnote++; + } + + if (add_footnote_div) + { + print_output(output, "</div><!--footnotes-->\n"); + output_firstcol = 1; + } + + return 0; } -int -endswith(const char* s, const char* what) +static int +end_head_start_body(FILE* output) +{ + print_output(output, "</head>\n<body>\n"); + output_firstcol = 1; + return 0; +} + +static int +error(const int code, const char* file, const char* func, const int line, + const char* fmt, ...) +{ + char buf[BUFSIZE]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), (const char*)fmt, args); + va_end(args); + fprintf(stderr, "%s:%s:%lu:%lu:%s:%s:%d: %s\n", PROGRAMNAME, + input_filename ? input_filename : "(init/stdin)", lineno, colno, + func, file, line, buf); + return code; +} + +static int +filter_slw(const struct dirent* node) +{ + size_t node_len; + size_t slw_len; + + assert(node != NULL); + if (((*node->d_name == '.') + && (!*(node->d_name + 1) + || ((*(node->d_name + 1) == '.') + && !(*(node->d_name + 2)))))) + return 0; + node_len = strlen(node->d_name); + slw_len = strlen(".slw"); + if (slw_len >= node_len) + return 0; + return !strcmp(node->d_name + strlen(node->d_name) - slw_len, ".slw"); +} + +static int +filter_subdirs(const struct dirent* node) { - const char* ps = s; - const char* pwhat = what; + struct stat st; + char* nodename = NULL; - if (!s || !what) + assert(node != NULL); + if (((*node->d_name == '.') + && (!*(node->d_name + 1) + || ((*(node->d_name + 1) == '.') + && !(*(node->d_name + 2)))))) return 0; - if (!*what) - return 1; + CALLOC(nodename, char, BUFSIZE); + snprintf(nodename, BUFSIZE, "%s/%s", incdir, node->d_name); - while (*ps) - ps++; - while (*pwhat) - pwhat++; + if (lstat(nodename, &st) < 0 || !S_ISDIR(st.st_mode)) + { + free(nodename); + return 0; + } - while (ps != s && pwhat != what) - if (*ps-- != *pwhat--) - return 0; + free(nodename); - return *ps == *pwhat && pwhat == what; + return 1; } -int -all_numeric(const u8* start, const u8* end) +static u8* +format_date(const u8* date_arg, const char* timestamp_format) { - const u8* ps = start; - while (ps != end) + u8* day = NULL; + u8* month = NULL; + u8* year = NULL; + u8* formatted_date = NULL; + u8* ptr = NULL; + u8* date = NULL; + const char* ptimestamp_format = NULL; + char* temp = NULL; + size_t formatted_date_len; + + assert((date_arg != NULL) && (timestamp_format != NULL)); + date = (u8*)strdup((const char*)date_arg); + if (!date) + goto format_date_cleanup; + CALLOC(formatted_date, u8, DATEBUFSIZE); + ptr = NULL; + year = (u8*)strtok_r((char*)date, "-", (char**)&ptr); + if (!year) + goto format_date_cleanup; + month = (u8*)strtok_r(NULL, "-", (char**)&ptr); + if (!month) + goto format_date_cleanup; + day = (u8*)strtok_r(NULL, "T", (char**)&ptr); + if (!day) + goto format_date_cleanup; + ptimestamp_format = timestamp_format; + formatted_date_len = 0; + while (*ptimestamp_format) { - if (*ps && !isdigit((const int)*ps)) - return 0; - ps++; + if (*ptimestamp_format == 'd' || *ptimestamp_format == 'D') + { + MEMCCPY((formatted_date + formatted_date_len), day, + DATEBUFSIZE - formatted_date_len, temp); + formatted_date_len += strlen((char*)day); + } + else if (*ptimestamp_format == 'm' || *ptimestamp_format == 'M') + { + MEMCCPY((formatted_date + formatted_date_len), month, + DATEBUFSIZE - formatted_date_len, temp); + formatted_date_len += strlen((char*)month); + } + else if (*ptimestamp_format == 'y' || *ptimestamp_format == 'Y') + { + MEMCCPY((formatted_date + formatted_date_len), year, + DATEBUFSIZE - formatted_date_len, temp); + formatted_date_len += strlen((char*)year); + } + else + *(formatted_date + formatted_date_len++) + = *ptimestamp_format; + ptimestamp_format++; } - return 1; + return formatted_date; + +format_date_cleanup: + free(formatted_date); + free(date); + return NULL; +} + +static int +free_keyvalue(KeyValue** list, const size_t list_count) +{ + if (!list || !*list) + return 1; + + for (size_t index = 0; index < list_count; index++) + { + KeyValue* current = *list + index; + free(current->value); + free(current->key); + } + return 0; } -u8* +static u8* get_value(KeyValue* list, const size_t list_count, const u8* key, KeyValue** cursor) { - if (!list) - return NULL; KeyValue* plist = list; + assert((list != NULL) && (key != NULL)); while (plist < list + list_count) { if (!strcmp((char*)plist->key, (char*)key)) @@ -236,205 +540,53 @@ get_value(KeyValue* list, const size_t list_count, const u8* key, return NULL; } -int -set_basedir(char** basedir, size_t* basedir_size, const char* arg) -{ - size_t arg_len; - char* temp = NULL; - - if (!arg) - exit(error(1, __FILE__, __func__, __LINE__, - "NULL argument supplied to set_basedir")); - if (!*arg) - exit(error(1, __FILE__, __func__, __LINE__, - "Argument required")); - - arg_len = strlen(arg); - if (arg_len + 1 > *basedir_size) - { - *basedir_size = arg_len + 1; - REALLOC(*basedir, char, *basedir_size); - } - - MEMCCPY(*basedir, arg, *basedir_size, temp); - - return 0; -} +#define PIPE_READ_INDEX 0 +#define PIPE_WRITE_INDEX 1 -int -set_global_link_prefix(char** global_link_prefix, - size_t* global_link_prefix_size, const char* arg) +static int +print_command(FILE* output, const char* command, const u8* pass_arguments[], + const u8* pipe_arguments[], const int strip_newlines) { - char* temp = NULL; - size_t arg_len; - - if (!arg) - exit(error(1, __FILE__, __func__, __LINE__, - "NULL argument supplied to set_basedir")); - if (!*arg) - exit(error(1, __FILE__, __func__, __LINE__, - "Argument required")); + FILE* cmd_input = NULL; + FILE* cmd_output = NULL; + u8* cmd_output_line = NULL; + char* eol = NULL; + pid_t pid = 0; + pid_t wpid; + int pstatus = 0; + int arg_pipe_fds[2]; + int output_pipe_fds[2]; - arg_len = strlen(arg); - if (arg_len + 1 > *global_link_prefix_size) + assert((output != NULL) && (command != NULL) + && (pass_arguments != NULL)); + fflush(output); + pipe(arg_pipe_fds); + pipe(output_pipe_fds); + pid = fork(); + if (pid == 0) { - *global_link_prefix_size = arg_len + 1; - REALLOC(*global_link_prefix, char, *global_link_prefix_size); - } - MEMCCPY(*global_link_prefix, arg, *global_link_prefix_size, temp); - - return 0; -} + close(arg_pipe_fds[PIPE_WRITE_INDEX]); + close(output_pipe_fds[PIPE_READ_INDEX]); -char* -strip_ext(const char* fn, const size_t fn_size) -{ - char* newname = NULL; - char* pnewname = NULL; - const char* pfn = NULL; - char* dot = NULL; + dup2(arg_pipe_fds[PIPE_READ_INDEX], STDIN_FILENO); + dup2(output_pipe_fds[PIPE_WRITE_INDEX], STDOUT_FILENO); - dot = strrchr(fn, '.'); + execvp(command, (char* const*)pass_arguments); + exit(1); + } + else if (pid < 0) + exit(error(errno, __FILE__, __func__, __LINE__, "fork failed")); - if (!dot) - return NULL; + /* Parent */ + close(arg_pipe_fds[PIPE_READ_INDEX]); + close(output_pipe_fds[PIPE_WRITE_INDEX]); + cmd_input = fdopen(arg_pipe_fds[PIPE_WRITE_INDEX], "w"); + cmd_output = fdopen(output_pipe_fds[PIPE_READ_INDEX], "r"); - newname = strndup(fn, fn_size); - if (!newname) + if (!cmd_input || !cmd_output) { - perror(PROGRAMNAME ": strndup"); - exit(error(ENOMEM, __FILE__, __func__, __LINE__, - "Allocation error")); - } - pnewname = newname; - pfn = fn; - - while (pfn != dot && *pfn) - *pnewname++ = *pfn++; - *pnewname = 0; - - return newname; -} - -int -print_output(FILE* output, const char* fmt, ...) -{ - u8* var_incdir_only_summary = NULL; - char* temp = NULL; - char buf[BUFSIZE]; - va_list args; - int incdir_only_summary = 0; - - if (!output || !fmt) - exit(error(EINVAL, __FILE__, __func__, __LINE__, - "Invalid argument")); - - va_start(args, fmt); - - var_incdir_only_summary - = get_value(vars, vars_count, (u8*)"incdir-only-summary", NULL); - incdir_only_summary - = var_incdir_only_summary && *var_incdir_only_summary == '1'; - - if (IN(state, ST_INCDIR) && !IN(state, ST_SUMMARY) - && incdir_only_summary) - return 0; - else if (IN(state, ST_CSV_BODY)) - { - size_t buf_len = 0; - vsnprintf((char*)buf, BUFSIZE, fmt, args); - buf_len = strlen(buf); - if (!csv_template) - { - csv_template_size = BUFSIZE; - CALLOC(csv_template, u8, csv_template_size); - MEMCCPY(csv_template, buf, csv_template_size, temp); - } - else - { - csv_template_len = strlen((char*)csv_template); - if (csv_template_len + buf_len > csv_template_size) - { - csv_template_size += BUFSIZE; - REALLOC(csv_template, u8, csv_template_size); - } - MEMCCPY((csv_template + csv_template_len), buf, - csv_template_size - csv_template_len, temp); - } - } - else if (IN(state, ST_TSV_BODY)) - { - size_t buf_len = 0; - vsnprintf((char*)buf, BUFSIZE, fmt, args); - buf_len = strlen(buf); - if (!tsv_template) - { - tsv_template_size = BUFSIZE; - CALLOC(tsv_template, u8, tsv_template_size); - MEMCCPY(tsv_template, buf, tsv_template_size, temp); - } - else - { - tsv_template_len = strlen((char*)tsv_template); - if (tsv_template_len + buf_len > tsv_template_size) - { - tsv_template_size += BUFSIZE; - REALLOC(tsv_template, u8, tsv_template_size); - } - MEMCCPY((tsv_template + tsv_template_len), buf, - tsv_template_size - tsv_template_len, temp); - } - } - else - vfprintf(output, fmt, args); - va_end(args); - return 0; -} - -#define PIPE_READ_INDEX 0 -#define PIPE_WRITE_INDEX 1 - -int -print_command(FILE* output, const char* command, const u8* pass_arguments[], - const u8* pipe_arguments[], const int strip_newlines) -{ - if (!command || !pass_arguments) - exit(error(EINVAL, __FILE__, __func__, __LINE__, - "Invalid argument")); - - pid_t pid = 0; - int pstatus = 0; - int arg_pipe_fds[2]; - int output_pipe_fds[2]; - - fflush(output); - pipe(arg_pipe_fds); - pipe(output_pipe_fds); - pid = fork(); - if (pid == 0) - { - close(arg_pipe_fds[PIPE_WRITE_INDEX]); - close(output_pipe_fds[PIPE_READ_INDEX]); - - dup2(arg_pipe_fds[PIPE_READ_INDEX], STDIN_FILENO); - dup2(output_pipe_fds[PIPE_WRITE_INDEX], STDOUT_FILENO); - - execvp(command, (char* const*)pass_arguments); - exit(1); - } - else if (pid < 0) - exit(error(errno, __FILE__, __func__, __LINE__, "fork failed")); - - /* Parent */ - close(arg_pipe_fds[PIPE_READ_INDEX]); - close(output_pipe_fds[PIPE_WRITE_INDEX]); - FILE* cmd_input = fdopen(arg_pipe_fds[PIPE_WRITE_INDEX], "w"); - FILE* cmd_output = fdopen(output_pipe_fds[PIPE_READ_INDEX], "r"); - - if (!cmd_input || !cmd_output) - { - perror("fdopen"); - exit(error(1, __FILE__, __func__, __LINE__, "fdopen failed")); + perror("fdopen"); + exit(error(1, __FILE__, __func__, __LINE__, "fdopen failed")); } if (pipe_arguments) @@ -448,236 +600,258 @@ print_command(FILE* output, const char* command, const u8* pass_arguments[], } fclose(cmd_input); - u8* cmd_output_line = NULL; CALLOC(cmd_output_line, u8, BUFSIZE); while (!feof(cmd_output)) { - char* eol = NULL; - if (!fgets((char*)cmd_output_line, BUFSIZE, cmd_output)) continue; - eol = strchr((char*)cmd_output_line, '\n'); if (eol) *eol = 0; - print_output(output, "%s%s", cmd_output_line, strip_newlines ? "" : "\n"); } if (!strip_newlines) output_firstcol = 1; free(cmd_output_line); - fclose(cmd_output); - kill(pid, SIGKILL); - pid_t wpid = waitpid(pid, &pstatus, 0); + wpid = waitpid(pid, &pstatus, 0); if (wpid < 0) warning(pstatus, (u8*)"Child returned nonzero status, errno = %d", errno); if (WIFEXITED(pstatus)) return WEXITSTATUS(pstatus); - return wpid; } -int -read_file_into_buffer(FILE** input, u8** buffer, size_t* buffer_size, - const char* input_filename, char** input_dirname) -{ - struct stat fs; - char* slash = NULL; - - *input = fopen(input_filename, "r"); - if (!*input) - { - perror("fopen"); - return error(ENOENT, __FILE__, __func__, __LINE__, - "fopen failed: %s", input_filename); - } - - fstat(fileno(*input), &fs); - if (S_ISDIR(fs.st_mode)) - return error(EISDIR, __FILE__, __func__, __LINE__, - "Is a directory: %s", input_filename); - - free(*buffer); - *buffer_size = fs.st_size + 1; - CALLOC(*buffer, u8, *buffer_size); - fread((void*)*buffer, 1, *buffer_size, *input); - - free(*input_dirname); - - slash = strrchr(input_filename, '/'); - if (slash) - { - CALLOC(*input_dirname, char, strlen(input_filename) + 1); - char* pinput_dirname = *input_dirname; - const char* pinput_filename = input_filename; - while (pinput_filename && *pinput_filename - && pinput_filename != slash) - *pinput_dirname++ = *pinput_filename++; - } - else - { - CALLOC(*input_dirname, char, 2); - **input_dirname = '.'; - } - - fclose(*input); - - return 0; -} - -int read_csv(FILE* output, const char* filename, csv_callback_t callback); -int read_tsv(FILE* output, const char* filename, tsv_callback_t callback); +#define PRINTCSVIF(format, arg) \ + do \ + { \ + if ((ALL(csv_state, ST_CS_COND_NONEMPTY) \ + && *csv_register[conditional_index - 1]) \ + || (ALL(csv_state, ST_CS_COND_EMPTY) \ + && !*csv_register[conditional_index - 1]) \ + || !ANY(csv_state, \ + ST_CS_COND_EMPTY | ST_CS_COND_NONEMPTY)) \ + { \ + fprintf(output, format, arg); \ + } \ + } while (0) -int -print_meta_var(FILE* output, u8** tsv_header, u8** tsv_register) +static int +print_csv_row(FILE* output, u8** csv_header, u8** csv_register) { - UNUSED(tsv_header); - if (!*tsv_register || !*(tsv_register + 1)) - return 0; - u8* pvalue = *(tsv_register + 1); - size_t value_len = strlen((char*)pvalue); - if (*pvalue == '%' && *(pvalue + value_len - 1) == '%') + u8* pcsv_template = csv_template; + unsigned char csv_state = ST_CS_NONE; + unsigned char num = 0; + unsigned char conditional_index = 0; + assert((output != NULL) && (csv_header != NULL) + && (csv_register != NULL)); +csv_template_start: + if (!*pcsv_template) + goto csv_template_done; + if (IN(csv_state, ST_CS_ESCAPE)) { - u8* var_name = NULL; - var_name = (u8*)strdup((char*)pvalue + 1); - *(var_name + value_len - 2) = 0; - u8* value = get_value(vars, vars_count, var_name, NULL); - - if (value) - print_output(output, - "<meta name=\"%s\" content=\"%s\">\n", - *tsv_register, value); - - free(var_name); + PRINTCSVIF("%c", *pcsv_template); + CLEAR(csv_state, ST_CS_ESCAPE); + pcsv_template++; + goto csv_template_start; } - else - print_output(output, "<meta name=\"%s\" content=\"%s\">\n", - *tsv_register, *(tsv_register + 1)); - return 0; -} - -int -process_heading_start(FILE* output, const UBYTE heading_level, - const ULONG heading_count) -{ - print_output(output, "<h%d id=\"heading-%lu\">", heading_level, - heading_count); - return 0; -} - -int -process_heading(FILE* output, const u8* token, const UBYTE heading_level) -{ - if (!token || strlen((char*)token) < 1) - warning(1, (u8*)"Empty heading"); - - print_output(output, "%s</h%d>", token ? (char*)token : "", - heading_level); - - return 0; -} - -int -process_git_log(FILE* output) -{ - if (!input_filename) - return warning(1, (u8*)"Cannot use 'git-log' in stdin"); - - u8* pipe_args[] = {NULL, NULL}; - char* basename = NULL; - char* temp = NULL; - size_t basename_size = BUFSIZE; - char* slash = NULL; - pid_t result = 0; - - slash = strrchr(input_filename, '/'); - CALLOC(basename, char, basename_size); - if (slash) - MEMCCPY(basename, slash + 1, basename_size, temp); - else - MEMCCPY(basename, input_filename, basename_size, temp); - pipe_args[0] = (u8*)basename; - - print_output(output, "<div id=\"git-log\">\nPrevious commit:\n"); - result = print_command(output, CMD_GIT_LOG, - (const u8**)CMD_GIT_LOG_ARGS, (const u8**)pipe_args, 0); - print_output(output, "</div><!--git-log-->\n"); - output_firstcol = 1; - free(basename); - - return result ? warning(result, (u8*)"git-log: Cannot run git") : 0; -} - -int -process_stylesheet(FILE* output, const u8* css_filename, int output_yaml) -{ - if (output_yaml) - print_output(output, "stylesheet: %s\n", css_filename); - else - print_output(output, "<link rel=\"stylesheet\" href=\"%s\">\n", - css_filename); - output_firstcol = 1; + switch (*pcsv_template) + { + case '\\': + SET(csv_state, ST_CS_ESCAPE); + pcsv_template++; + break; + case '$': + if (IN(csv_state, ST_CS_REGISTER)) + { + PRINTCSVIF("%c", '$'); + CLEAR(csv_state, ST_CS_REGISTER); + } + else + SET(csv_state, ST_CS_REGISTER); + pcsv_template++; + break; + case '#': + if (IN(csv_state, ST_CS_HEADER)) + { + error(1, __FILE__, __func__, __LINE__, + "Invalid header register mark"); + CLEAR(csv_state, ST_CS_REGISTER | ST_CS_HEADER); + } + else if (IN(csv_state, ST_CS_REGISTER)) + { + CLEAR(csv_state, ST_CS_REGISTER); + SET(csv_state, ST_CS_HEADER); + } + else + PRINTCSVIF("%c", '#'); + pcsv_template++; + break; + case '?': + if (IN(csv_state, ST_CS_COND)) + { + error(1, __FILE__, __func__, __LINE__, + "Invalid conditional mark"); + CLEAR(csv_state, ST_CS_COND); + } + else if (IN(csv_state, ST_CS_REGISTER)) + { + CLEAR(csv_state, ST_CS_REGISTER); + SET(csv_state, ST_CS_COND); + } + else + PRINTCSVIF("%c", '?'); + pcsv_template++; + break; + case '!': + if (ALL(csv_state, ST_CS_COND | ST_CS_COND_NONEMPTY)) + { + CLEAR(csv_state, ST_CS_COND | ST_CS_COND_NONEMPTY); + SET(csv_state, ST_CS_COND_EMPTY); + } + else if (IN(csv_state, ST_CS_COND)) + { + error(1, __FILE__, __func__, __LINE__, + "Empty conditional before/without nonempty " + "conditional"); + CLEAR(csv_state, ST_CS_COND | ST_CS_COND_EMPTY); + } + else + PRINTCSVIF("%c", '!'); + pcsv_template++; + break; + case '/': + if (ALL(csv_state, ST_CS_COND | ST_CS_COND_EMPTY)) + CLEAR(csv_state, ST_CS_COND | ST_CS_COND_EMPTY); + else if (ALL(csv_state, ST_CS_COND)) + CLEAR(csv_state, ST_CS_COND | ST_CS_COND_NONEMPTY); + else + PRINTCSVIF("%c", '/'); + pcsv_template++; + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (IN(csv_state, ST_CS_COND)) + { + conditional_index = *pcsv_template - '0'; + CLEAR(csv_state, ST_CS_COND); + SET(csv_state, ST_CS_COND_NONEMPTY); + } + else if (IN(csv_state, ST_CS_HEADER)) + { + num = *pcsv_template - '0'; + PRINTCSVIF("%s", csv_header[num - 1]); + CLEAR(csv_state, ST_CS_HEADER); + } + else if (IN(csv_state, ST_CS_REGISTER)) + { + num = *pcsv_template - '0'; + PRINTCSVIF("%s", csv_register[num - 1]); + CLEAR(csv_state, ST_CS_REGISTER); + } + else + PRINTCSVIF("%c", *pcsv_template); + pcsv_template++; + break; + default: + if (IN(csv_state, ST_CS_HEADER)) + { + error(1, __FILE__, __func__, __LINE__, + "Invalid header register mark"); + CLEAR(csv_state, ST_CS_HEADER); + } + else if (IN(csv_state, ST_CS_REGISTER)) + { + error(1, __FILE__, __func__, __LINE__, + "Invalid register mark"); + CLEAR(csv_state, ST_CS_REGISTER); + } + else + PRINTCSVIF("%c", *pcsv_template); + pcsv_template++; + } + goto csv_template_start; +csv_template_done: return 0; } -int -process_inline_stylesheet(FILE* output, const u8* css_filename, int output_yaml) +static int +print_image_dimensions(FILE* output, const u8* image_file_prefix, const u8* path) { - FILE* css = NULL; - u8* bufline = NULL; - char* css_pathname = NULL; - size_t css_filename_len = 0; - size_t css_pathname_size = 0; - - if (!css_filename) - exit(error(EINVAL, __FILE__, __func__, __LINE__, - "Invalid argument")); - - css_filename_len = strlen((char*)css_filename); - css_pathname_size = basedir_size + css_filename_len + 1; + struct stat sb; + FILE* cmd_output = NULL; + char command[BUFSIZE]; + char cmd_output_line[BUFSIZE]; + char image_path[BUFSIZE - strlen(CMD_IDENTIFY)]; + char* eol = NULL; - CALLOC(css_pathname, char, css_pathname_size); - if ((size_t)snprintf(css_pathname, css_pathname_size, "%s/%s", basedir, - css_filename) - > css_pathname_size) - warning(1, (u8*)"snprintf:%d: Overflow", __LINE__); - if (!(css = fopen((const char*)css_pathname, "rt"))) - { - perror("fopen"); - exit(error(ENOENT, __FILE__, __func__, __LINE__, - "fopen failed: %s", css_pathname)); - } + assert((output != NULL) && (path != NULL)); + if (!*path) + return 1; + snprintf(image_path, BUFSIZE - strlen(CMD_IDENTIFY) - 1, "%s%s%s", + image_file_prefix ? (char*)image_file_prefix : ".", + *path == '/' ? "" : "/", path); + errno = 0; + if (stat(image_path, &sb) < 0) + return 1; + snprintf(command, BUFSIZE - 1, CMD_IDENTIFY, image_path); + cmd_output = popen(command, "r"); + if (!cmd_output) + return error(errno, __FILE__, __func__, __LINE__, + "popen failed"); + if (!fgets(cmd_output_line, BUFSIZE, cmd_output)) + goto print_dimensions_cleanup; - if (output_yaml) - { - print_output(output, "inline-stylesheet: %s\n", css_filename); - output_firstcol = 1; - goto process_inline_stylesheet_cleanup; - } + eol = strchr((char*)cmd_output_line, '\n'); + if (eol) + *eol = 0; + print_output(output, "%s", cmd_output_line); + output_firstcol = 0; - CALLOC(bufline, u8, BUFSIZE); +print_dimensions_cleanup: + pclose(cmd_output); + return 0; +} - print_output(output, "<style>\n"); - while (!feof(css)) +static int +print_meta_var(FILE* output, u8** tsv_header, u8** tsv_register) +{ + u8* pvalue; + u8* var_name = NULL; + size_t value_len; + UNUSED(tsv_header); + assert((output != NULL) && (tsv_header != NULL) + && (tsv_register != NULL)); + if (!*tsv_register || !*(tsv_register + 1)) + return 0; + pvalue = *(tsv_register + 1); + value_len = strlen((char*)pvalue); + if (*pvalue == '%' && *(pvalue + value_len - 1) == '%') { - if (!fgets((char*)bufline, BUFSIZE, css)) - break; - print_output(output, "%s", bufline); + var_name = (u8*)strdup((char*)pvalue + 1); + *(var_name + value_len - 2) = 0; + u8* value = get_value(vars, vars_count, var_name, NULL); + if (value) + print_output(output, + "<meta name=\"%s\" content=\"%s\">\n", + *tsv_register, value); + free(var_name); } - print_output(output, "</style>\n"); - output_firstcol = 1; - -process_inline_stylesheet_cleanup: - free(css_pathname); - fclose(css); - free(bufline); - + else + print_output(output, "<meta name=\"%s\" content=\"%s\">\n", + *tsv_register, *(tsv_register + 1)); return 0; } @@ -690,26 +864,26 @@ process_inline_stylesheet_cleanup: && !*tsv_register[conditional_index - 1]) \ || !ANY(tsv_state, \ ST_CS_COND_EMPTY | ST_CS_COND_NONEMPTY)) \ - { \ fprintf(output, format, arg); \ - } \ } while (0) -int +static int print_tsv_row(FILE* output, u8** tsv_header, u8** tsv_register) { - u8* ptsv_template = tsv_template; - UBYTE tsv_state = ST_CS_NONE; - UBYTE num = 0; - UBYTE conditional_index = 0; + u8* ptsv_template = tsv_template; + unsigned char tsv_state = ST_CS_NONE; + unsigned char num = 0; + unsigned char conditional_index = 0; + assert((output != NULL) && (tsv_header != NULL) + && (tsv_register != NULL)); tsv_template_start: if (!*ptsv_template) goto tsv_template_done; if (IN(tsv_state, ST_CS_ESCAPE)) { PRINTTSVIF("%c", *ptsv_template); - tsv_state &= ~ST_CS_ESCAPE; + CLEAR(tsv_state, ST_CS_ESCAPE); ptsv_template++; goto tsv_template_start; } @@ -717,17 +891,17 @@ tsv_template_start: switch (*ptsv_template) { case '\\': - tsv_state |= ST_CS_ESCAPE; + SET(tsv_state, ST_CS_ESCAPE); ptsv_template++; break; case '$': if (IN(tsv_state, ST_CS_REGISTER)) { PRINTTSVIF("%c", '$'); - tsv_state &= ~ST_CS_REGISTER; + CLEAR(tsv_state, ST_CS_REGISTER); } else - tsv_state |= ST_CS_REGISTER; + SET(tsv_state, ST_CS_REGISTER); ptsv_template++; break; case '#': @@ -735,12 +909,12 @@ tsv_template_start: { error(1, __FILE__, __func__, __LINE__, "Invalid header register mark"); - tsv_state &= ~(ST_CS_REGISTER | ST_CS_HEADER); + CLEAR(tsv_state, ST_CS_REGISTER | ST_CS_HEADER); } else if (IN(tsv_state, ST_CS_REGISTER)) { - tsv_state &= ~ST_CS_REGISTER; - tsv_state |= ST_CS_HEADER; + CLEAR(tsv_state, ST_CS_REGISTER); + SET(tsv_state, ST_CS_HEADER); } else PRINTTSVIF("%c", '#'); @@ -751,12 +925,12 @@ tsv_template_start: { error(1, __FILE__, __func__, __LINE__, "Invalid conditional mark"); - tsv_state &= ~ST_CS_COND; + CLEAR(tsv_state, ST_CS_COND); } else if (IN(tsv_state, ST_CS_REGISTER)) { - tsv_state &= ~ST_CS_REGISTER; - tsv_state |= ST_CS_COND; + CLEAR(tsv_state, ST_CS_REGISTER); + SET(tsv_state, ST_CS_COND); } else PRINTTSVIF("%c", '?'); @@ -765,15 +939,15 @@ tsv_template_start: case '!': if (ALL(tsv_state, ST_CS_COND | ST_CS_COND_NONEMPTY)) { - tsv_state &= ~(ST_CS_COND | ST_CS_COND_NONEMPTY); - tsv_state |= ST_CS_COND_EMPTY; + CLEAR(tsv_state, ST_CS_COND | ST_CS_COND_NONEMPTY); + SET(tsv_state, ST_CS_COND_EMPTY); } else if (IN(tsv_state, ST_CS_COND)) { error(1, __FILE__, __func__, __LINE__, "Empty conditional before/without nonempty " "conditional"); - tsv_state &= ~(ST_CS_COND | ST_CS_COND_EMPTY); + CLEAR(tsv_state, ST_CS_COND | ST_CS_COND_EMPTY); } else PRINTTSVIF("%c", '!'); @@ -781,9 +955,9 @@ tsv_template_start: break; case '/': if (ALL(tsv_state, ST_CS_COND | ST_CS_COND_EMPTY)) - tsv_state &= ~(ST_CS_COND | ST_CS_COND_EMPTY); + CLEAR(tsv_state, ST_CS_COND | ST_CS_COND_EMPTY); else if (ALL(tsv_state, ST_CS_COND)) - tsv_state &= ~(ST_CS_COND | ST_CS_COND_NONEMPTY); + CLEAR(tsv_state, ST_CS_COND | ST_CS_COND_NONEMPTY); else PRINTTSVIF("%c", '/'); ptsv_template++; @@ -800,20 +974,20 @@ tsv_template_start: if (IN(tsv_state, ST_CS_COND)) { conditional_index = *ptsv_template - '0'; - tsv_state &= ~ST_CS_COND; - tsv_state |= ST_CS_COND_NONEMPTY; + CLEAR(tsv_state, ST_CS_COND); + SET(tsv_state, ST_CS_COND_NONEMPTY); } else if (IN(tsv_state, ST_CS_HEADER)) { num = *ptsv_template - '0'; PRINTTSVIF("%s", tsv_header[num - 1]); - tsv_state &= ~ST_CS_HEADER; + CLEAR(tsv_state, ST_CS_HEADER); } else if (IN(tsv_state, ST_CS_REGISTER)) { num = *ptsv_template - '0'; PRINTTSVIF("%s", tsv_register[num - 1]); - tsv_state &= ~ST_CS_REGISTER; + CLEAR(tsv_state, ST_CS_REGISTER); } else PRINTTSVIF("%c", *ptsv_template); @@ -824,13 +998,13 @@ tsv_template_start: { error(1, __FILE__, __func__, __LINE__, "Invalid header register mark"); - tsv_state &= ~ST_CS_HEADER; + CLEAR(tsv_state, ST_CS_HEADER); } else if (IN(tsv_state, ST_CS_REGISTER)) { error(1, __FILE__, __func__, __LINE__, "Invalid register mark"); - tsv_state &= ~ST_CS_REGISTER; + CLEAR(tsv_state, ST_CS_REGISTER); } else PRINTTSVIF("%c", *ptsv_template); @@ -841,157 +1015,128 @@ tsv_template_done: return 0; } -int -read_tsv(FILE* output, const char* filename, tsv_callback_t callback) +static int +print_output(FILE* output, const char* fmt, ...) { - FILE* tsv = NULL; - u8* bufline = NULL; - u8* pbufline = NULL; - u8* token = NULL; - u8* ptoken = NULL; - u8* tsv_header[MAX_TSV_REGISTERS]; - u8* tsv_register[MAX_TSV_REGISTERS]; - char* ctemp = NULL; - size_t tsv_lineno = 0; - size_t token_size = 0; - UBYTE current_header = 0; - UBYTE current_register = 0; - - if (!callback) - exit(error(EINVAL, __FILE__, __func__, __LINE__, - "Invalid callback argument")); + u8* var_incdir_only_summary = NULL; + u8* temp = NULL; + char buf[BUFSIZE]; + va_list args; + int incdir_only_summary = 0; - if (!(tsv = fopen(filename, "rt"))) + assert((output != NULL) && (fmt != NULL)); + va_start(args, fmt); + var_incdir_only_summary + = get_value(vars, vars_count, (u8*)"incdir-only-summary", NULL); + incdir_only_summary + = var_incdir_only_summary && (*var_incdir_only_summary == '1'); + if (IN(state, ST_INCDIR) && !IN(state, ST_SUMMARY) + && incdir_only_summary) + return 0; + else if (IN(state, ST_CSV_BODY)) { - perror("fopen"); - exit(error(ENOENT, __FILE__, __func__, __LINE__, - "fopen failed: %s", filename)); + size_t buf_len = 0; + vsnprintf((char*)buf, BUFSIZE, fmt, args); + buf_len = strlen(buf); + if (!csv_template) + { + csv_template_size = BUFSIZE; + CALLOC(csv_template, u8, csv_template_size); + MEMCCPY(csv_template, buf, csv_template_size, temp); + } + else + { + csv_template_len = strlen((char*)csv_template); + ENSURE_SIZE(csv_template, temp, csv_template_size, + csv_template_len + buf_len + 1, + csv_template_len + buf_len + 1, + print_output_alloc_error, u8); + MEMCCPY((csv_template + csv_template_len), buf, + csv_template_size - csv_template_len, temp); + } } - - CALLOC(bufline, u8, BUFSIZE); - token_size = BUFSIZE; - CALLOC(token, u8, token_size); - for (UBYTE i = 0; i < MAX_TSV_REGISTERS; i++) - CALLOC(tsv_header[i], u8, BUFSIZE); - for (UBYTE i = 0; i < MAX_TSV_REGISTERS; i++) - CALLOC(tsv_register[i], u8, BUFSIZE); - - while (!feof(tsv) && (!tsv_iter || tsv_lineno <= (size_t)tsv_iter)) + else if (IN(state, ST_TSV_BODY)) { - u8* eol = NULL; - if (!fgets((char*)bufline, BUFSIZE, tsv)) - break; - eol = (u8*)strchr((char*)bufline, '\n'); - if (eol) - *eol = 0; - - pbufline = bufline; - RESET_TOKEN(token, ptoken, token_size); - current_register = 0; - tsv_bufline_start: - if (!*pbufline) - goto tsv_bufline_done; - switch (*pbufline) - { - case '\t': - *ptoken = 0; - if (tsv_lineno > 0 - && current_register < MAX_TSV_REGISTERS) - { - MEMCCPY(tsv_register[current_register], - (char*)token, BUFSIZE, ctemp); - current_register++; - } - else if (tsv_lineno <= 0 - && current_header < MAX_TSV_REGISTERS) - { - MEMCCPY(tsv_header[current_header], - (char*)token, BUFSIZE, ctemp); - current_header++; - } - RESET_TOKEN(token, ptoken, token_size); - pbufline++; - break; - default: - CHECKCOPY(token, ptoken, token_size, pbufline); - } - goto tsv_bufline_start; - tsv_bufline_done: - *ptoken = 0; - if (tsv_lineno > 0) + size_t buf_len = 0; + vsnprintf((char*)buf, BUFSIZE, fmt, args); + buf_len = strlen(buf); + if (!tsv_template) { - if (current_register < MAX_TSV_REGISTERS) - { - MEMCCPY(tsv_register[current_register], - (char*)token, BUFSIZE, ctemp); - current_register++; - } + tsv_template_size = BUFSIZE; + CALLOC(tsv_template, u8, tsv_template_size); + MEMCCPY(tsv_template, buf, tsv_template_size, temp); } else { - if (current_header < MAX_TSV_REGISTERS) - { - MEMCCPY(tsv_header[current_header], - (char*)token, BUFSIZE, ctemp); - current_header++; - } + tsv_template_len = strlen((char*)tsv_template); + ENSURE_SIZE(tsv_template, temp, tsv_template_size, + tsv_template_len + buf_len + 1, + tsv_template_len + buf_len + 1, + print_output_alloc_error, u8); + MEMCCPY((tsv_template + tsv_template_len), buf, + tsv_template_size - tsv_template_len, temp); } - RESET_TOKEN(token, ptoken, token_size); + } + else + vfprintf(output, fmt, args); + va_end(args); + return 0; +print_output_alloc_error: + exit(error(errno, __FILE__, __func__, __LINE__, "Allocation error")); + return 1; +} - if (tsv_lineno > 0 && pbufline != bufline) - (*callback)(output, tsv_header, tsv_register); +static int +process_blockquote(FILE* output, const int end_tag) +{ + print_output(output, "<%sblockquote>", end_tag ? "/" : ""); + return 0; +} - for (UBYTE i = 0; i < MAX_TSV_REGISTERS; i++) - *tsv_register[i] = 0; - tsv_lineno++; - } - fclose(tsv); - for (UBYTE i = MAX_TSV_REGISTERS; i > 0; i--) - free(tsv_register[i - 1]); - for (UBYTE i = MAX_TSV_REGISTERS; i > 0; i--) - free(tsv_header[i - 1]); - free(token); - free(bufline); +static int +process_bold(FILE* output, const int end_tag) +{ + print_output(output, "<%sstrong>", end_tag ? "/" : ""); + return 0; +} +static int +process_code(FILE* output, const int end_tag) +{ + print_output(output, "<%scode>", end_tag ? "/" : ""); return 0; } -int -process_tsv(FILE* output, const u8* arg_token, +static int +process_csv(FILE* output, const u8* arg_token, const int read_yaml_macros_and_links, const int end_tag) { - u8* saveptr = NULL; - u8* args = NULL; - size_t args_len; + u8* saveptr = NULL; + u8* args = NULL; u8* args_base = NULL; - + size_t args_len; + assert((output != NULL) && (arg_token != NULL)); if (end_tag) { - state &= ~ST_TSV_BODY; - + CLEAR(state, ST_CSV_BODY); if (read_yaml_macros_and_links) return 0; - - read_tsv(output, tsv_filename, &print_tsv_row); - - free(tsv_filename); - tsv_filename = NULL; - free(tsv_template); - tsv_template = NULL; - tsv_template_size = 0; + read_csv(output, csv_filename, &print_csv_row); + free(csv_filename); + csv_filename = NULL; + free(csv_template); + csv_template = NULL; + csv_template_size = 0; } else { if (ANY(state, ST_CSV_BODY | ST_TSV_BODY)) exit(error(1, __FILE__, __func__, __LINE__, "Can't nest csv/tsv directives")); - - state |= ST_TSV_BODY; - + SET(state, ST_CSV_BODY); if (read_yaml_macros_and_links) return 0; - - tsv_iter = 0; + csv_iter = 0; (void)strtok_r((char*)arg_token, " ", (char**)&saveptr); args = (u8*)strtok_r(NULL, " ", (char**)&saveptr); if (!args) @@ -1001,729 +1146,257 @@ process_tsv(FILE* output, const u8* arg_token, if (*args != '"' || *(args + args_len - 1) != '"') exit(error(EINVAL, __FILE__, __func__, __LINE__, "First argument must be a string")); - if (!tsv_filename) - CALLOC(tsv_filename, char, BUFSIZE); + if (!csv_filename) + CALLOC(csv_filename, char, BUFSIZE); args_base = (u8*)strdup((char*)args + 1); *(args_base + strlen((char*)args_base) - 1) = 0; - snprintf(tsv_filename, BUFSIZE, "%s/%s.tsv", input_dirname, + snprintf(csv_filename, BUFSIZE, "%s/%s.csv", input_dirname, (char*)args_base); free(args_base); args = (u8*)strtok_r(NULL, " ", (char**)&saveptr); if (args) { errno = 0; - tsv_iter = strtol((char*)args, NULL, 10); + csv_iter = (size_t)strtol((char*)args, NULL, 10); if (errno) exit(error(errno, __FILE__, __func__, __LINE__, "Invalid argument '%s'", args)); } } - return 0; } -int -process_tsv_count(FILE* output, const u8* arg_token, - const int read_yaml_macros_and_links, const int is_tsv) +static int +process_footnote(FILE* output, const u8* token, const int footnote_definition, + const int footnote_output) { - FILE* tsv = NULL; - u8* saveptr = NULL; - u8* args = NULL; - size_t args_len; - u8* args_base = NULL; - char* bufline = NULL; - size_t tsv_lines = 0; - - if (read_yaml_macros_and_links) - return 0; - - tsv_iter = 0; - - (void)strtok_r((char*)arg_token, " ", (char**)&saveptr); - args = (u8*)strtok_r(NULL, " ", (char**)&saveptr); - if (!args) - exit(error(EINVAL, __FILE__, __func__, __LINE__, - "Arguments required")); - args_len = strlen((char*)args); - if (*args != '"' || *(args + args_len - 1) != '"') - exit(error(EINVAL, __FILE__, __func__, __LINE__, - "First argument must be a string")); - if (!tsv_filename) - CALLOC(tsv_filename, char, BUFSIZE); - args_base = (u8*)strdup((char*)args + 1); - *(args_base + strlen((char*)args_base) - 1) = 0; - snprintf(tsv_filename, BUFSIZE, "%s/%s.%csv", input_dirname, - (char*)args_base, is_tsv ? 't' : 'c'); - free(args_base); - - // output, tsv_filename, print_tsv_row - if (!(tsv = fopen(tsv_filename, "rt"))) - { - perror("fopen"); - exit(error(ENOENT, __FILE__, __func__, __LINE__, - "fopen failed: %s", tsv_filename)); - } - - CALLOC(bufline, char, BUFSIZE); + char* temp = NULL; - while (!feof(tsv)) + assert(token != NULL); + current_footnote++; + if (footnote_definition) { - char* eol = NULL; - if (!fgets(bufline, BUFSIZE, tsv)) - break; - eol = strchr(bufline, '\n'); - if (eol) + footnote_count++; + if (footnote_count == 1 && inline_footnote_count > 0) + warning(1, + (u8*)"Both inline and regular footnotes " + "present"); + else if (!footnotes) { - *eol = 0; - tsv_lines++; + CALLOC(footnotes, KeyValue, footnote_count); + pfootnotes = footnotes; + } + else + { + REALLOCARRAY(footnotes, KeyValue, footnote_count); + pfootnotes = footnotes + footnote_count - 1; } + CALLOC(pfootnotes->key, u8, KEYSIZE); + MEMCCPY(pfootnotes->key, (char*)token, KEYSIZE, temp); + pfootnotes->value = NULL; + pfootnotes->value_size = 0; + } + if (footnote_output) + { + print_output(output, + "<a href=\"#footnote-%d\" id=\"footnote-text-%d\">" + "<sup>%d</sup></a>", + current_footnote, current_footnote, current_footnote); + output_firstcol = 0; } - fclose(tsv); - - /* And take back one kadam for the header row */ - print_output(output, "%ld", tsv_lines - 1); - - free(bufline); - free(tsv_filename); - tsv_filename = NULL; - return 0; } -#define PRINTCSVIF(format, arg) \ - do \ - { \ - if ((ALL(csv_state, ST_CS_COND_NONEMPTY) \ - && *csv_register[conditional_index - 1]) \ - || (ALL(csv_state, ST_CS_COND_EMPTY) \ - && !*csv_register[conditional_index - 1]) \ - || !ANY(csv_state, \ - ST_CS_COND_EMPTY | ST_CS_COND_NONEMPTY)) \ - { \ - fprintf(output, format, arg); \ - } \ - } while (0) - -int -print_csv_row(FILE* output, u8** csv_header, u8** csv_register) +static int +process_formula(FILE* output, const u8* token, const int display_formula) { - u8* pcsv_template = csv_template; - UBYTE csv_state = ST_CS_NONE; - UBYTE num = 0; - UBYTE conditional_index = 0; + const u8* pipe_args[] = {token, NULL}; + int result = 0; -csv_template_start: - if (!*pcsv_template) - goto csv_template_done; - if (IN(csv_state, ST_CS_ESCAPE)) + assert(token != NULL); + result = print_command(output, CMD_KATEX, + display_formula ? (const u8**)CMD_KATEX_DISPLAY_ARGS + : (const u8**)CMD_KATEX_INLINE_ARGS, + (const u8**)pipe_args, 1); + if (result) { - PRINTCSVIF("%c", *pcsv_template); - csv_state &= ~ST_CS_ESCAPE; - pcsv_template++; - goto csv_template_start; + print_output(output, "%s$%s$%s", display_formula ? "$" : "", + token, display_formula ? "$" : ""); + output_firstcol = 0; } + return result; +} - switch (*pcsv_template) - { - case '\\': - csv_state |= ST_CS_ESCAPE; - pcsv_template++; - break; - case '$': - if (IN(csv_state, ST_CS_REGISTER)) - { - PRINTCSVIF("%c", '$'); - csv_state &= ~ST_CS_REGISTER; - } - else - csv_state |= ST_CS_REGISTER; - pcsv_template++; - break; - case '#': - if (IN(csv_state, ST_CS_HEADER)) - { - error(1, __FILE__, __func__, __LINE__, - "Invalid header register mark"); - csv_state &= ~(ST_CS_REGISTER | ST_CS_HEADER); - } - else if (IN(csv_state, ST_CS_REGISTER)) - { - csv_state &= ~ST_CS_REGISTER; - csv_state |= ST_CS_HEADER; - } - else - PRINTCSVIF("%c", '#'); - pcsv_template++; - break; - case '?': - if (IN(csv_state, ST_CS_COND)) - { - error(1, __FILE__, __func__, __LINE__, - "Invalid conditional mark"); - csv_state &= ~ST_CS_COND; - } - else if (IN(csv_state, ST_CS_REGISTER)) - { - csv_state &= ~ST_CS_REGISTER; - csv_state |= ST_CS_COND; - } - else - PRINTCSVIF("%c", '?'); - pcsv_template++; - break; - case '!': - if (ALL(csv_state, ST_CS_COND | ST_CS_COND_NONEMPTY)) - { - csv_state &= ~(ST_CS_COND | ST_CS_COND_NONEMPTY); - csv_state |= ST_CS_COND_EMPTY; - } - else if (IN(csv_state, ST_CS_COND)) - { - error(1, __FILE__, __func__, __LINE__, - "Empty conditional before/without nonempty " - "conditional"); - csv_state &= ~(ST_CS_COND | ST_CS_COND_EMPTY); - } - else - PRINTCSVIF("%c", '!'); - pcsv_template++; - break; - case '/': - if (ALL(csv_state, ST_CS_COND | ST_CS_COND_EMPTY)) - csv_state &= ~(ST_CS_COND | ST_CS_COND_EMPTY); - else if (ALL(csv_state, ST_CS_COND)) - csv_state &= ~(ST_CS_COND | ST_CS_COND_NONEMPTY); - else - PRINTCSVIF("%c", '/'); - pcsv_template++; - break; - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - if (IN(csv_state, ST_CS_COND)) - { - conditional_index = *pcsv_template - '0'; - csv_state &= ~ST_CS_COND; - csv_state |= ST_CS_COND_NONEMPTY; - } - else if (IN(csv_state, ST_CS_HEADER)) - { - num = *pcsv_template - '0'; - PRINTCSVIF("%s", csv_header[num - 1]); - csv_state &= ~ST_CS_HEADER; - } - else if (IN(csv_state, ST_CS_REGISTER)) - { - num = *pcsv_template - '0'; - PRINTCSVIF("%s", csv_register[num - 1]); - csv_state &= ~ST_CS_REGISTER; - } - else - PRINTCSVIF("%c", *pcsv_template); - pcsv_template++; - break; - default: - if (IN(csv_state, ST_CS_HEADER)) - { - error(1, __FILE__, __func__, __LINE__, - "Invalid header register mark"); - csv_state &= ~ST_CS_HEADER; - } - else if (IN(csv_state, ST_CS_REGISTER)) - { - error(1, __FILE__, __func__, __LINE__, - "Invalid register mark"); - csv_state &= ~ST_CS_REGISTER; - } - else - PRINTCSVIF("%c", *pcsv_template); - pcsv_template++; - } - goto csv_template_start; -csv_template_done: - return 0; -} - -int -read_csv(FILE* output, const char* filename, csv_callback_t callback) -{ - if (!callback) - exit(error(EINVAL, __FILE__, __func__, __LINE__, - "Invalid callback argument")); - - FILE* csv = NULL; - size_t csv_lineno = 0; - u8* bufline = NULL; - u8* pbufline = NULL; - u8* token = NULL; - u8* ptoken = NULL; - size_t token_size = 0; - UBYTE csv_state = ST_CS_NONE; - u8* csv_header[MAX_CSV_REGISTERS]; - UBYTE current_header = 0; - u8* csv_register[MAX_CSV_REGISTERS]; - UBYTE current_register = 0; - u8* csv_delimiter = NULL; - char* temp = NULL; - - csv_delimiter = get_value(vars, vars_count, (u8*)"csv-delimiter", NULL); - - if (!(csv = fopen(filename, "rt"))) - { - perror("fopen"); - exit(error(ENOENT, __FILE__, __func__, __LINE__, - "fopen failed: %s", filename)); - } - - CALLOC(bufline, u8, BUFSIZE); - token_size = BUFSIZE; - CALLOC(token, u8, token_size); - for (UBYTE i = 0; i < MAX_CSV_REGISTERS; i++) - CALLOC(csv_header[i], u8, BUFSIZE); - for (UBYTE i = 0; i < MAX_CSV_REGISTERS; i++) - CALLOC(csv_register[i], u8, BUFSIZE); - - while (!feof(csv) && (!csv_iter || csv_lineno <= (size_t)csv_iter)) - { - u8* eol = NULL; - if (!fgets((char*)bufline, BUFSIZE, csv)) - break; - eol = (u8*)strchr((char*)bufline, '\n'); - if (eol) - *eol = 0; - - pbufline = bufline; - RESET_TOKEN(token, ptoken, token_size); - current_register = 0; - csv_bufline_start: - if (!*pbufline) - goto csv_bufline_done; - switch (*pbufline) - { - case '"': - if (IN(csv_state, ST_CS_QUOTE)) - csv_state &= ~ST_CS_QUOTE; - else - csv_state |= ST_CS_QUOTE; - pbufline++; - break; - case ';': - case ',': - if (IN(csv_state, ST_CS_QUOTE)) - CHECKCOPY(token, ptoken, token_size, pbufline); - else - { - *ptoken = 0; - if (csv_lineno > 0 - && current_register < MAX_CSV_REGISTERS) - { - MEMCCPY(csv_register[current_register], - (char*)token, BUFSIZE, temp); - current_register++; - } - else if (csv_lineno <= 0 - && current_header < MAX_CSV_REGISTERS) - { - MEMCCPY(csv_header[current_header], - (char*)token, BUFSIZE, temp); - current_header++; - } - RESET_TOKEN(token, ptoken, token_size); - pbufline++; - } - break; - default: - if (IN(csv_state, ST_CS_QUOTE)) - CHECKCOPY(token, ptoken, token_size, pbufline); - else if (csv_delimiter && *pbufline == *csv_delimiter) - { - *ptoken = 0; - if (csv_lineno > 0 - && current_register < MAX_CSV_REGISTERS) - { - MEMCCPY(csv_register[current_register], - (char*)token, BUFSIZE, temp); - current_register++; - } - else if (csv_lineno <= 0 - && current_header < MAX_CSV_REGISTERS) - { - MEMCCPY(csv_header[current_header], - (char*)token, BUFSIZE, temp); - current_header++; - } - RESET_TOKEN(token, ptoken, token_size); - pbufline++; - } - else - CHECKCOPY(token, ptoken, token_size, pbufline); - } - goto csv_bufline_start; - csv_bufline_done: - *ptoken = 0; - if (csv_lineno > 0) - { - if (current_register < MAX_CSV_REGISTERS) - { - MEMCCPY(csv_register[current_register], - (char*)token, BUFSIZE, temp); - current_register++; - } - } - else - { - if (current_header < MAX_CSV_REGISTERS) - { - MEMCCPY(csv_header[current_header], - (char*)token, BUFSIZE, temp); - current_header++; - } - } - RESET_TOKEN(token, ptoken, token_size); - - if (csv_lineno > 0 && pbufline != bufline) - (*callback)(output, csv_header, csv_register); - - for (UBYTE i = 0; i < MAX_CSV_REGISTERS; i++) - *csv_register[i] = 0; - csv_lineno++; - } - fclose(csv); - for (UBYTE i = MAX_CSV_REGISTERS; i > 0; i--) - free(csv_register[i - 1]); - for (UBYTE i = MAX_CSV_REGISTERS; i > 0; i--) - free(csv_header[i - 1]); - free(token); - free(bufline); - - return 0; -} - -int -process_csv(FILE* output, const u8* arg_token, - const int read_yaml_macros_and_links, const int end_tag) -{ - if (end_tag) - { - state &= ~ST_CSV_BODY; - - if (read_yaml_macros_and_links) - return 0; - - read_csv(output, csv_filename, &print_csv_row); - - free(csv_filename); - csv_filename = NULL; - free(csv_template); - csv_template = NULL; - csv_template_size = 0; - } - else - { - if (ANY(state, ST_CSV_BODY | ST_TSV_BODY)) - exit(error(1, __FILE__, __func__, __LINE__, - "Can't nest csv/tsv directives")); - - state |= ST_CSV_BODY; - - if (read_yaml_macros_and_links) - return 0; - - csv_iter = 0; - u8* saveptr = NULL; - u8* args = NULL; - - (void)strtok_r((char*)arg_token, " ", (char**)&saveptr); - args = (u8*)strtok_r(NULL, " ", (char**)&saveptr); - if (!args) - exit(error(EINVAL, __FILE__, __func__, __LINE__, - "Arguments required")); - size_t args_len = strlen((char*)args); - if (*args != '"' || *(args + args_len - 1) != '"') - exit(error(EINVAL, __FILE__, __func__, __LINE__, - "First argument must be a string")); - if (!csv_filename) - CALLOC(csv_filename, char, BUFSIZE); - u8* args_base = (u8*)strdup((char*)args + 1); - *(args_base + strlen((char*)args_base) - 1) = 0; - snprintf(csv_filename, BUFSIZE, "%s/%s.csv", input_dirname, - (char*)args_base); - free(args_base); - args = (u8*)strtok_r(NULL, " ", (char**)&saveptr); - if (args) - { - errno = 0; - csv_iter = strtol((char*)args, NULL, 10); - if (errno) - exit(error(errno, __FILE__, __func__, __LINE__, - "Invalid argument '%s'", args)); - } - } - - return 0; -} - -int -process_include(FILE* output, const u8* token, - const int read_yaml_macros_and_links) +static int +process_git_log(FILE* output) { - u8* ptoken; - char* include_filename = NULL; - char* pinclude_filename = NULL; - pid_t pid = 0; - int pstatus = 0; - int arg_pipe_fds[2]; - int output_pipe_fds[2]; - - ptoken = (u8*)strchr((char*)token, ' '); + u8* pipe_args[] = {NULL, NULL}; + char* basename = NULL; + char* temp = NULL; + char* slash = NULL; + size_t basename_size = BUFSIZE; + pid_t result = 0; + assert(output != NULL); if (!input_filename) - return warning(1, - (u8*)"process_include: Cannot use 'include' in stdin"); - - if (!ptoken) - exit(error(1, __FILE__, __func__, __LINE__, - "Directive 'include' requires an argument")); - - fflush(output); - pipe(arg_pipe_fds); - pipe(output_pipe_fds); - pid = fork(); - - if (pid > 0) /* parent */ - { - FILE* child_output = NULL; - u8* child_output_line = NULL; - close(arg_pipe_fds[PIPE_READ_INDEX]); - close(output_pipe_fds[PIPE_WRITE_INDEX]); - if (!(child_output - = fdopen(output_pipe_fds[PIPE_READ_INDEX], "r"))) - { - wait(&pstatus); - perror("fdopen"); - exit(error(1, __FILE__, __func__, __LINE__, - "fdopen failed")); - } - - CALLOC(child_output_line, u8, BUFSIZE); - while (!feof(child_output)) - { - char* eol = NULL; - - if (!fgets((char*)child_output_line, BUFSIZE, - child_output)) - continue; - - eol = strchr((char*)child_output_line, '\n'); - if (eol) - *eol = 0; - simple_parse_yaml_line(child_output_line, &vars, - &vars_count, &pvars); - } - free(child_output_line); - fclose(child_output); - - wait(&pstatus); - } - else if (pid == 0) /* child */ - { - char* filename = NULL; - FILE* input = NULL; - FILE* output = stdout; - u8* buffer = NULL; - size_t buffer_size = 0; - int result = 0; - FILE* child_output = NULL; - - close(arg_pipe_fds[PIPE_WRITE_INDEX]); - close(output_pipe_fds[PIPE_READ_INDEX]); - if (!(child_output - = fdopen(output_pipe_fds[PIPE_WRITE_INDEX], "w"))) - { - perror("fdopen"); - exit(error(1, __FILE__, __func__, __LINE__, - "fdopen failed")); - } - - CALLOC(include_filename, char, BUFSIZE); - pinclude_filename = include_filename; - ptoken++; - while (ptoken && *ptoken) - if (*ptoken != '"') - *pinclude_filename++ = *ptoken++; - else - ptoken++; - - if (!strcmp(basedir, ".")) - set_basedir(&basedir, &basedir_size, input_dirname); - - CALLOC(filename, char, BUFSIZE); - snprintf(filename, BUFSIZE, "%s/%s.slw", basedir, - include_filename); - free(include_filename); - - read_file_into_buffer(&input, &buffer, &buffer_size, filename, - &input_dirname); - - free_keyvalue(&links, links_count); - free(links); - links = NULL; - links_count = 0; - - free_keyvalue(&footnotes, footnote_count); - free(footnotes); - footnotes = NULL; - footnote_count = 0; - current_footnote = 0; - - while (inline_footnote_count--) - free(inline_footnotes[inline_footnote_count]); - free(inline_footnotes); - inline_footnotes = NULL; - inline_footnote_count = 0; - current_inline_footnote = 0; - state = ST_NONE; - - /* First pass: read YAML, macros and links */ - result = slweb_parse(output, filename, BUFSIZE, buffer, 1, 1); - - add_css(child_output, 1); - if (result || read_yaml_macros_and_links) - goto process_include_child_cleanup; - - state = ST_NONE; - current_footnote = 0; - current_inline_footnote = 0; - - /* Second pass: parse and output */ - result = slweb_parse(output, filename, BUFSIZE, buffer, 1, 0); + return warning(1, (u8*)"Cannot use 'git-log' in stdin"); - process_include_child_cleanup: - fflush(output); - cleanup(); - free(filename); - free(buffer); - fclose(child_output); - exit(result); - } + slash = strrchr(input_filename, '/'); + CALLOC(basename, char, basename_size); + if (slash) + MEMCCPY(basename, slash + 1, basename_size, temp); else - exit(error(1, __FILE__, __func__, __LINE__, "fork failed")); - - return 0; + MEMCCPY(basename, input_filename, basename_size, temp); + pipe_args[0] = (u8*)basename; + print_output(output, "<div id=\"git-log\">\nPrevious commit:\n"); + result = print_command(output, CMD_GIT_LOG, + (const u8**)CMD_GIT_LOG_ARGS, (const u8**)pipe_args, 0); + print_output(output, "</div><!--git-log-->\n"); + output_firstcol = 1; + free(basename); + return result ? warning(result, (u8*)"git-log: Cannot run git") : 0; } -int -process_list_start(FILE* output) +static int +process_heading(FILE* output, const u8* token, const unsigned char heading_level) { - print_output(output, "<ul>"); + assert((output != NULL) && (token != NULL)); + if (!*token) + warning(1, (u8*)"Empty heading"); + print_output(output, "%s</h%hhu>", (char*)token, heading_level); return 0; } -int -process_list_item_start(FILE* output) +static int +process_heading_start(FILE* output, const unsigned char heading_level, + const size_t heading_count) { - print_output(output, "%s<li><p>", output_firstcol ? "" : "\n"); + print_output(output, "<h%hhu id=\"heading-%zu\">", heading_level, + heading_count); return 0; } -int -process_list_item_end(FILE* output) +static int +process_horizontal_rule(FILE* output) { + print_output(output, "<hr>\n"); if (IN(state, ST_PARA_OPEN)) - state &= ~ST_PARA_OPEN; - print_output(output, "</li>"); - output_firstcol = 0; - return 0; -} - -int -process_list_end(FILE* output) -{ - print_output(output, "</ul>\n"); + print_output(output, "<p>\n"); output_firstcol = 1; return 0; } -int -process_numlist_start(FILE* output) +static int +process_image(FILE* output, const u8* image_text, const u8* image_file_prefix, + const u8* link_prefix, const u8* image_id, const int add_link, + const int add_figcaption) { - print_output(output, "<ol>"); - output_firstcol = 0; - return 0; + u8* url = get_value(links, links_count, image_id, NULL); + return process_inline_image(output, image_text, image_file_prefix, + link_prefix, url, add_link, add_figcaption); } -int -process_numlist_end(FILE* output) +static int +process_incdir(FILE* output, const u8* token, const u8* link_prefix, + const u8* footer_permalink_text, const char* permalink_url, + const int ext_in_permalink, const int read_yaml_macros_and_links) { - print_output(output, "</ol>\n"); - output_firstcol = 1; - return 0; -} + struct dirent** namelist; + struct dirent** pnamelist; + u8* saveptr = NULL; + /* skipping the first token (incdir) */ + u8* arg = NULL; + u8* macro_body = NULL; + u8* parg = NULL; + size_t arg_len = 0; + int names_output; + int num = 5; + int names_total = 0; + int details_open = 1; + int list_only = 0; + int pass; -int -filter_subdirs(const struct dirent* node) -{ - if (!node - || ((*node->d_name == '.') - && (!*(node->d_name + 1) - || ((*(node->d_name + 1) == '.') - && !(*(node->d_name + 2)))))) + assert((output != NULL) && (token != NULL)); + if (read_yaml_macros_and_links) return 0; + (void)strtok_r((char*)token, " ", (char**)&saveptr); + arg = (u8*)strtok_r(NULL, " ", (char**)&saveptr); + if (!arg) + exit(error(1, __FILE__, __func__, __LINE__, + "Arguments required")); + arg_len = strlen((char*)arg); + if (*arg != '"' || *(arg + arg_len - 1) != '"') + exit(error(1, __FILE__, __func__, __LINE__, + "First argument not string")); - struct stat st; - char* nodename = NULL; - - CALLOC(nodename, char, BUFSIZE); - snprintf(nodename, BUFSIZE, "%s/%s", incdir, node->d_name); + incdir = strdup((char*)(arg + 1)); + *(incdir + strlen(incdir) - 1) = 0; /* trim ending " */ - if (lstat(nodename, &st) < 0 || !S_ISDIR(st.st_mode)) + pass = 0; +incdir_next_arg: + arg = (u8*)strtok_r(NULL, " ", (char**)&saveptr); + if (!arg) + goto incdir_done_args; + parg = arg; + if (*parg == '=') + macro_body = get_value(macros, macros_count, arg + 1, NULL); + else if (!strcmp((const char*)arg, "listonly")) + list_only = 1; + else { - free(nodename); - return 0; + while (parg && *parg) + { + if (*parg < '0' || *parg > '9') + exit(error(1, __FILE__, __func__, __LINE__, + "Invalid argument")); + parg++; + } + errno = 0; + num = strtol((char*)arg, NULL, 10); + if (errno) + exit(error(errno, __FILE__, __func__, __LINE__, + "Invalid parameter 'num'")); } + pass++; + if (pass < 3) + goto incdir_next_arg; - free(nodename); - - return 1; -} - -int -filter_slw(const struct dirent* node) -{ - if (!node - || ((*node->d_name == '.') - && (!*(node->d_name + 1) - || ((*(node->d_name + 1) == '.') - && !(*(node->d_name + 2)))))) - return 0; - - size_t node_len = strlen(node->d_name); - size_t slw_len = strlen(".slw"); - - if (slw_len >= node_len) - return 0; - return !strcmp(node->d_name + strlen(node->d_name) - slw_len, ".slw"); -} - -int -reverse_alphacompare(const struct dirent** a, const struct dirent** b) -{ - if (!a || !b) - return 0; - - return -1 * strcmp((*a)->d_name, (*b)->d_name); +incdir_done_args: + print_output(output, "<ul class=\"incdir\">\n"); + if ((names_total = scandir(incdir, &namelist, &filter_subdirs, + &reverse_alphacompare)) + < 0) + { + perror(PROGRAMNAME ": scandir"); + exit(error(errno, __FILE__, __func__, __LINE__, + "scandir '%s' failed", incdir)); + } + pnamelist = namelist; + names_output = 0; + while (names_output < MIN(names_total, num)) + { + /*if (list_only) + print_output(output, "<li>%s\n", + (*pnamelist)->d_name);*/ + process_incdir_subdir(output, (*pnamelist)->d_name, link_prefix, + details_open, macro_body, footer_permalink_text, + permalink_url, ext_in_permalink, list_only); + details_open = 0; + pnamelist++; + names_output++; + /*if (list_only) + print_output(output, "</li>\n");*/ + } + while (names_total--) + free(namelist[names_total]); + free(namelist); + free(incdir); + print_output(output, "</ul>\n"); + output_firstcol = 1; + return 0; } -int process_timestamp(FILE* output, const u8* link_prefix, const char* link, - const u8* permalink_macro, const u8* date, const int ext_in_permalink); - -int +static int process_incdir_subdir(FILE* output, const char* subdirname, const u8* link_prefix, const int details_open, const u8* macro_body, const u8* footer_permalink_text, const char* permalink_url, @@ -1731,12 +1404,29 @@ process_incdir_subdir(FILE* output, const char* subdirname, { struct dirent** namelist; struct dirent** pnamelist; - char* abs_subdirname = NULL; - char* temp = NULL; - long names_total = 0; - long names_output; + FILE* input = NULL; + u8* buffer = NULL; + u8* date = NULL; + u8* formatted_date = NULL; + u8* title = NULL; + u8* var_incdir_only_summary = NULL; + char* abs_subdirname = NULL; + char* temp = NULL; + char* filename = NULL; + char* link = NULL; + unsigned long long saved_state = ST_NONE; + size_t names_total = 0; + size_t names_output; + size_t buffer_size = 0; + size_t link_len; + pid_t pid; + int pstatus = 0; + int incdir_only_summary = 0; + int result = 0; UNUSED(link_prefix); + assert((output != NULL) && (subdirname != NULL) + && (link_prefix != NULL)); if (list_only) ; /*print_output(output, "<ul>\n");*/ @@ -1745,7 +1435,6 @@ process_incdir_subdir(FILE* output, const char* subdirname, details_open ? " open" : ""); if (macro_body) print_output(output, "%s", macro_body); - if (!list_only) print_output(output, "%s</summary>\n<div>\n", subdirname); @@ -1757,7 +1446,7 @@ process_incdir_subdir(FILE* output, const char* subdirname, &reverse_alphacompare)) < 0) { - perror("scandir"); + perror(PROGRAMNAME ": scandir"); exit(error(errno, __FILE__, __func__, __LINE__, "scandir '%s' failed", abs_subdirname)); } @@ -1766,33 +1455,16 @@ process_incdir_subdir(FILE* output, const char* subdirname, names_output = 0; while (names_output < names_total) { - int pstatus = 0; - + pstatus = 0; if (!list_only) print_output(output, "<article>\n"); - fflush(output); - pid_t pid = fork(); - + pid = fork(); if (pid > 0) wait(&pstatus); else if (pid == 0) { - FILE* input = NULL; - FILE* output = stdout; - u8* buffer = NULL; - u8* date = NULL; - u8* formatted_date = NULL; - u8* title = NULL; - u8* var_incdir_only_summary = NULL; - char* filename = NULL; - char* link = NULL; - ULLONG saved_state = ST_NONE; - size_t buffer_size = 0; - size_t link_len; - int incdir_only_summary = 0; - int result = 0; - + output = stdout; if (!strcmp(basedir, ".")) set_basedir(&basedir, &basedir_size, abs_subdirname); @@ -1814,7 +1486,6 @@ process_incdir_subdir(FILE* output, const char* subdirname, footnotes = NULL; footnote_count = 0; current_footnote = 0; - while (inline_footnote_count--) free(inline_footnotes[inline_footnote_count]); free(inline_footnotes); @@ -1902,727 +1573,697 @@ process_incdir_subdir(FILE* output, const char* subdirname, MEMCCPY((link + link_len), timestamp_output_ext, BUFSIZE - link_len, temp); - } - - /* TODO: Get rid of orphan <p> just before - * <footer>; would probably need another flag - * and another layer of code, so maybe not - */ - print_output(output, - "<footer>\n" - "<p><a href=\"%s\">%s</a>\n" - "</footer>\n", - permalink_url ? permalink_url : link, - footer_permalink_text); - } - - fflush(output); - cleanup(); - free(formatted_date); - free(link); - free(filename); - free(buffer); - free(abs_subdirname); - while (names_total--) - free(namelist[names_total]); - free(namelist); - exit(result); - } - else - exit(error(1, __FILE__, __func__, __LINE__, - "fork failed")); - - if (!list_only) - print_output(output, "</article>\n"); - - pnamelist++; - names_output++; - } - - while (names_total--) - free(namelist[names_total]); - free(namelist); - free(abs_subdirname); - - if (list_only) - ; /*print_output(output, "</ul>\n");*/ - else - print_output(output, "</div>\n</details>\n</li>\n"); - - output_firstcol = 0; - return 0; -} - -int -process_incdir(FILE* output, const u8* token, const u8* link_prefix, - const u8* footer_permalink_text, const char* permalink_url, - const int ext_in_permalink, const int read_yaml_macros_and_links) -{ - u8* saveptr = NULL; - /* skipping the first token (incdir) */ - u8* arg = NULL; - size_t arg_len = 0; - long num = 5; - u8* macro_body = NULL; - int names_total = 0; - struct dirent** namelist; - struct dirent** pnamelist; - long names_output; - int details_open = 1; - int list_only = 0; - int pass; - u8* parg = NULL; - - if (read_yaml_macros_and_links) - return 0; - - (void)strtok_r((char*)token, " ", (char**)&saveptr); - arg = (u8*)strtok_r(NULL, " ", (char**)&saveptr); - if (!arg) - exit(error(1, __FILE__, __func__, __LINE__, - "Arguments required")); - - arg_len = strlen((char*)arg); - - if (*arg != '"' || *(arg + arg_len - 1) != '"') - exit(error(1, __FILE__, __func__, __LINE__, - "First argument not string")); - - incdir = strdup((char*)(arg + 1)); - *(incdir + strlen(incdir) - 1) = 0; /* trim ending " */ - - pass = 0; - -incdir_next_arg: - arg = (u8*)strtok_r(NULL, " ", (char**)&saveptr); - if (!arg) - goto incdir_done_args; + } - parg = arg; - if (*parg == '=') - macro_body = get_value(macros, macros_count, arg + 1, NULL); - else if (!strcmp((const char*)arg, "listonly")) - list_only = 1; - else - { - while (parg && *parg) - { - if (*parg < '0' || *parg > '9') - exit(error(1, __FILE__, __func__, __LINE__, - "Invalid argument")); - parg++; - } - errno = 0; - num = strtol((char*)arg, NULL, 10); - if (errno) - exit(error(errno, __FILE__, __func__, __LINE__, - "Invalid parameter 'num'")); - } - pass++; - if (pass < 3) - goto incdir_next_arg; + /* TODO: Get rid of orphan <p> just before + * <footer>; would probably need another flag + * and another layer of code, so maybe not + */ + print_output(output, + "<footer>\n" + "<p><a href=\"%s\">%s</a>\n" + "</footer>\n", + permalink_url ? permalink_url : link, + footer_permalink_text); + } -incdir_done_args: - print_output(output, "<ul class=\"incdir\">\n"); + fflush(output); + cleanup(); + free(formatted_date); + free(link); + free(filename); + free(buffer); + free(abs_subdirname); + while (names_total--) + free(namelist[names_total]); + free(namelist); + exit(result); + } + else + exit(error(1, __FILE__, __func__, __LINE__, + "fork failed")); - if ((names_total = scandir(incdir, &namelist, &filter_subdirs, - &reverse_alphacompare)) - < 0) - { - perror("scandir"); - exit(error(errno, __FILE__, __func__, __LINE__, - "scandir '%s' failed", incdir)); - } + if (!list_only) + print_output(output, "</article>\n"); - pnamelist = namelist; - names_output = 0; - while (names_output < MIN(names_total, num)) - { - /*if (list_only) - print_output(output, "<li>%s\n", - (*pnamelist)->d_name);*/ - process_incdir_subdir(output, (*pnamelist)->d_name, link_prefix, - details_open, macro_body, footer_permalink_text, - permalink_url, ext_in_permalink, list_only); - details_open = 0; pnamelist++; names_output++; - /*if (list_only) - print_output(output, "</li>\n");*/ } + while (names_total--) free(namelist[names_total]); free(namelist); - free(incdir); + free(abs_subdirname); - print_output(output, "</ul>\n"); - output_firstcol = 1; + if (list_only) + ; /*print_output(output, "</ul>\n");*/ + else + print_output(output, "</div>\n</details>\n</li>\n"); + output_firstcol = 0; return 0; } -int url_is_local(const char* url); - -u8* -format_date(const u8* date_arg, const char* timestamp_format) +static int +process_include(FILE* output, const u8* token, + const int read_yaml_macros_and_links) { - const char* ptimestamp_format = NULL; - u8* day = NULL; - u8* month = NULL; - u8* year = NULL; - u8* formatted_date = NULL; - u8* ptr = NULL; - u8* date = NULL; - char* temp = NULL; - size_t formatted_date_len; - - date = (u8*)strdup((const char*)date_arg); - if (!date) - goto format_date_cleanup; + FILE* child_output = NULL; + FILE* input = NULL; + u8* child_output_line = NULL; + u8* ptoken; + u8* buffer = NULL; + char* include_filename = NULL; + char* pinclude_filename = NULL; + char* eol = NULL; + char* filename = NULL; + int arg_pipe_fds[2]; + int output_pipe_fds[2]; + size_t buffer_size = 0; + pid_t pid = 0; + int pstatus = 0; + int result = 0; - CALLOC(formatted_date, u8, DATEBUFSIZE); - ptr = NULL; - year = (u8*)strtok_r((char*)date, "-", (char**)&ptr); - if (!year) - goto format_date_cleanup; - month = (u8*)strtok_r(NULL, "-", (char**)&ptr); - if (!month) - goto format_date_cleanup; - day = (u8*)strtok_r(NULL, "T", (char**)&ptr); - if (!day) - goto format_date_cleanup; - ptimestamp_format = timestamp_format; - formatted_date_len = 0; - while (*ptimestamp_format) + assert((output != NULL) && (token != NULL)); + ptoken = (u8*)strchr((char*)token, ' '); + if (!input_filename) + return warning(1, + (u8*)"process_include: Cannot use 'include' in stdin"); + if (!ptoken) + exit(error(1, __FILE__, __func__, __LINE__, + "Directive 'include' requires an argument")); + fflush(output); + pipe(arg_pipe_fds); + pipe(output_pipe_fds); + pid = fork(); + if (pid > 0) /* parent */ { - if (*ptimestamp_format == 'd' || *ptimestamp_format == 'D') + child_output = NULL; + child_output_line = NULL; + close(arg_pipe_fds[PIPE_READ_INDEX]); + close(output_pipe_fds[PIPE_WRITE_INDEX]); + if (!(child_output + = fdopen(output_pipe_fds[PIPE_READ_INDEX], "r"))) { - MEMCCPY((formatted_date + formatted_date_len), day, - DATEBUFSIZE - formatted_date_len, temp); - formatted_date_len += strlen((char*)day); + wait(&pstatus); + perror(PROGRAMNAME ": fdopen"); + exit(error(1, __FILE__, __func__, __LINE__, + "fdopen failed")); } - else if (*ptimestamp_format == 'm' || *ptimestamp_format == 'M') + CALLOC(child_output_line, u8, BUFSIZE); + while (!feof(child_output)) { - MEMCCPY((formatted_date + formatted_date_len), month, - DATEBUFSIZE - formatted_date_len, temp); - formatted_date_len += strlen((char*)month); + eol = NULL; + if (!fgets((char*)child_output_line, BUFSIZE, + child_output)) + continue; + eol = strchr((char*)child_output_line, '\n'); + if (eol) + *eol = 0; + simple_parse_yaml_line(child_output_line, &vars, + &vars_count, &pvars); } - else if (*ptimestamp_format == 'y' || *ptimestamp_format == 'Y') + free(child_output_line); + fclose(child_output); + wait(&pstatus); + } + else if (pid == 0) /* child */ + { + close(arg_pipe_fds[PIPE_WRITE_INDEX]); + close(output_pipe_fds[PIPE_READ_INDEX]); + if (!(child_output + = fdopen(output_pipe_fds[PIPE_WRITE_INDEX], "w"))) { - MEMCCPY((formatted_date + formatted_date_len), year, - DATEBUFSIZE - formatted_date_len, temp); - formatted_date_len += strlen((char*)year); + perror("fdopen"); + exit(error(1, __FILE__, __func__, __LINE__, + "fdopen failed")); } - else - *(formatted_date + formatted_date_len++) - = *ptimestamp_format; + CALLOC(include_filename, char, BUFSIZE); + pinclude_filename = include_filename; + ptoken++; + while (ptoken && *ptoken) + if (*ptoken != '"') + *pinclude_filename++ = *ptoken++; + else + ptoken++; + if (!strcmp(basedir, ".")) + set_basedir(&basedir, &basedir_size, input_dirname); + CALLOC(filename, char, BUFSIZE); + snprintf(filename, BUFSIZE, "%s/%s.slw", basedir, + include_filename); + free(include_filename); - ptimestamp_format++; - } - return formatted_date; + result = read_file_into_buffer(&input, &buffer, &buffer_size, + filename, &input_dirname); + if (result) + exit(result); -format_date_cleanup: - free(formatted_date); - free(date); + free_keyvalue(&links, links_count); + free(links); + links = NULL; + links_count = 0; - return NULL; -} + free_keyvalue(&footnotes, footnote_count); + free(footnotes); + footnotes = NULL; + footnote_count = 0; + current_footnote = 0; + while (inline_footnote_count--) + free(inline_footnotes[inline_footnote_count]); + free(inline_footnotes); + inline_footnotes = NULL; + inline_footnote_count = 0; + current_inline_footnote = 0; + state = ST_NONE; -int -process_timestamp(FILE* output, const u8* link_prefix, const char* link, - const u8* permalink_macro, const u8* date, const int ext_in_permalink) -{ - char* in_filename = NULL; - char* in_line = NULL; - u8* formatted_date = NULL; + /* First pass: read YAML, macros and links */ + result = slweb_parse(output, filename, BUFSIZE, buffer, 1, 1); - formatted_date = format_date(date, article_timestamp_format); - if (!formatted_date) - goto process_timestamp_cleanup; + add_css(child_output, 1); + if (result || read_yaml_macros_and_links) + goto process_include_child_cleanup; - print_output(output, - "<a href=\"%s%s%s%s\"" - " class=\"timestamp\">%s%s</a>\n", - url_is_local(link) && global_link_prefix - ? (const char*)global_link_prefix - : "", - url_is_local(link) && link_prefix ? (const char*)link_prefix - : "", - link, ext_in_permalink ? timestamp_output_ext : "", - permalink_macro ? (char*)permalink_macro : "", formatted_date); - output_firstcol = 1; + state = ST_NONE; + current_footnote = 0; + current_inline_footnote = 0; -process_timestamp_cleanup: - free(formatted_date); - free(in_line); - free(in_filename); + /* Second pass: parse and output */ + result = slweb_parse(output, filename, BUFSIZE, buffer, 1, 0); + process_include_child_cleanup: + fflush(output); + cleanup(); + free(filename); + free(buffer); + fclose(child_output); + exit(result); + } + else + exit(error(1, __FILE__, __func__, __LINE__, "fork failed")); return 0; } -int -process_macro_def(FILE* output, const u8* token, - const int read_yaml_macros_and_links, const int end_tag) +static int +process_inline_footnote(const u8* token, const int read_yaml_macros_and_links, + FILE* output) { char* temp = NULL; - - if (end_tag) - { - fflush(output); - state &= ~ST_MACRO_BODY; - return 0; - } - - if (IN(state, ST_MACRO_BODY)) - exit(error(1, __FILE__, __func__, __LINE__, - "Cannot nest definitions")); - + size_t token_len; + assert(token != NULL); + current_inline_footnote++; if (read_yaml_macros_and_links) { - macros_count++; - - if (!macros) + token_len = strlen((char*)token); + inline_footnote_count++; + if (inline_footnote_count == 1 && footnote_count > 0) + warning(1, + (u8*)"Both inline and regular footnotes " + "present"); + else if (!inline_footnotes) { - CALLOC(macros, KeyValue, macros_count); - pmacros = macros; + inline_footnote_count = 1; + CALLOC(inline_footnotes, u8*, inline_footnote_count); } else - { - REALLOC(macros, KeyValue, - macros_count * sizeof(KeyValue)); - pmacros = macros + macros_count - 1; - } - CALLOC(pmacros->key, u8, KEYSIZE); - MEMCCPY(pmacros->key, (char*)token + 2, KEYSIZE, temp); - pmacros->value = NULL; - pmacros->value_size = 0; + REALLOC(inline_footnotes, u8*, + sizeof(u8*) * inline_footnote_count); + CALLOC(inline_footnotes[inline_footnote_count - 1], u8, + token_len + 1); + MEMCCPY(inline_footnotes[inline_footnote_count - 1], + (char*)token, token_len + 1, temp); + } + else + { + print_output(output, + "<a href=\"#inline-footnote-%d\" " + "id=\"inline-footnote-text-%d\"><sup>%d</sup></a>", + current_inline_footnote, current_inline_footnote, + current_inline_footnote); + output_firstcol = 0; } - fflush(output); - state |= ST_MACRO_BODY; - return 0; } -int -process_macro(FILE* output, const u8* token, const int end_tag) +static int +process_inline_image(FILE* output, const u8* image_text, + const u8* image_file_prefix, const u8* link_prefix, const u8* image_url, + const int add_link, const int add_figcaption) { - u8* macro_body; - u8* eol; - size_t macro_body_len; - - UNUSED(end_tag); - - macro_body = get_value(macros, macros_count, token + 1, NULL); - if (!macro_body) - exit(error(1, __FILE__, __func__, __LINE__, - "Macro '%s' undefined", token + 1)); - - eol = (u8*)strrchr((char*)macro_body, '\n'); - macro_body_len = strlen((char*)macro_body); - if (eol) - output_firstcol = macro_body + macro_body_len - - (u8*)strrchr((char*)macro_body, '\n') - > 0; - else - output_firstcol = !*macro_body - || *(macro_body + macro_body_len - 1) == '\n'; - print_output(output, "%s", macro_body); + assert((output != NULL) && (image_text != NULL)); + if (add_figcaption) + print_output(output, "<figure>\n"); + if (add_link) + print_output(output, + "<a href=\"%s%s%s\" title=\"%s\" class=\"image\"" + " target=\"_blank\">", + global_link_prefix ? (const char*)global_link_prefix + : "", + link_prefix ? (const char*)link_prefix : "", + image_url ? (char*)image_url : "", image_text); + print_output(output, "<img src=\"%s\" alt=\"%s\"%s", + image_url ? (char*)image_url : "", image_text, + image_url ? " " : ""); + if (image_url) + print_image_dimensions(output, image_file_prefix, image_url); + print_output(output, ">"); + output_firstcol = 0; + if (add_link) + print_output(output, "</a>"); + if (add_figcaption) + { + print_output(output, "<figcaption>%s</figcaption>\n</figure>\n", + image_text); + output_firstcol = 1; + } + return 0; +} +static int +process_inline_link(FILE* output, const u8* link_text, const u8* link_prefix, + const u8* link_macro_body, const u8* link_url) +{ + print_output(output, "<a href=\"%s%s%s\">%s%s</a>", + global_link_prefix ? (const char*)global_link_prefix : "", + link_prefix ? (const char*)link_prefix : "", + link_url ? (char*)link_url : "", + link_macro_body ? (char*)link_macro_body : "", link_text); + output_firstcol = 0; return 0; } -int -process_tag(FILE* output, const u8* token, const char* source_filename, - const u8* link_prefix, const u8* footer_permalink_text, - const char* permalink_url, const int ext_in_permalink, - const int read_yaml_macros_and_links, int* skip_eol, const int end_tag) +static int +process_inline_stylesheet(FILE* output, const u8* css_filename, int output_yaml) { - UNUSED(source_filename); - if (!token || strlen((char*)token) < 1) - return warning(1, (u8*)"%s:%ld:%ld: Empty tag name", - input_filename, lineno, colno); + FILE* css = NULL; + u8* bufline = NULL; + char* css_pathname = NULL; + size_t css_filename_len = 0; + size_t css_pathname_size = 0; - if (!strcmp((char*)token, "git-log") - && !read_yaml_macros_and_links) /* {git-log} */ - { - process_git_log(output); - } - else if (startswith((char*)token, "csv-count")) /* {csv-count} */ - process_tsv_count(output, token, read_yaml_macros_and_links, 0); - else if (startswith((char*)token, "csv")) /* {csv} */ - process_csv(output, token, read_yaml_macros_and_links, end_tag); - else if (startswith((char*)token, "tsv-count")) /* {tsv-count} */ - process_tsv_count(output, token, read_yaml_macros_and_links, 1); - else if (startswith((char*)token, "tsv")) /* {tsv} */ - process_tsv(output, token, read_yaml_macros_and_links, end_tag); - else if (startswith((char*)token, "include")) /* {include} */ - { - process_include(output, token, read_yaml_macros_and_links); - *skip_eol = 1; - } - else if (startswith((char*)token, "incdir")) /* {incdir} */ + assert((output != NULL) && (css_filename != NULL)); + css_filename_len = strlen((char*)css_filename); + css_pathname_size = basedir_size + css_filename_len + 1; + + CALLOC(css_pathname, char, css_pathname_size); + if ((size_t)snprintf(css_pathname, css_pathname_size, "%s/%s", basedir, + css_filename) + > css_pathname_size) + warning(1, (u8*)"snprintf:%d: Overflow", __LINE__); + if (!(css = fopen((const char*)css_pathname, "rt"))) { - process_incdir(output, token, link_prefix, - footer_permalink_text, permalink_url, ext_in_permalink, - read_yaml_macros_and_links); - *skip_eol = 1; + perror(PROGRAMNAME ": fopen"); + exit(error(errno, __FILE__, __func__, __LINE__, + "fopen failed: %s", css_pathname)); } - else if (*token == '=') /* {=macro} */ + + if (output_yaml) { - if (*(token + 1) == '!') - process_macro_def(output, token, - read_yaml_macros_and_links, end_tag); - else if ((*(token + 1) != '!') && !read_yaml_macros_and_links) - process_macro(output, token, end_tag); - *skip_eol = 1; + print_output(output, "inline-stylesheet: %s\n", css_filename); + output_firstcol = 1; + goto process_inline_stylesheet_cleanup; } - else if (!read_yaml_macros_and_links) /* general tags */ - { - print_output(output, "<"); - if (end_tag) - print_output(output, "/"); - - if (*token == '.' || *token == '#') - { - print_output(output, "div"); - } - - while (*token && *token != '#' && *token != '.') - print_output(output, "%c", *token++); - if (!end_tag) - { - if (*token == '#') - { - token++; - print_output(output, " id=\""); - while (*token && *token != '.') - print_output(output, "%c", *token++); - print_output(output, "\""); - if (*token == '.') - { - token++; - print_output(output, " class=\""); - while (*token) - print_output(output, "%c", - *token++); - print_output(output, "\""); - } - } - else if (*token == '.') - { - token++; - print_output(output, " class=\""); - while (*token && *token != '#') - print_output(output, "%c", *token++); - print_output(output, "\""); - if (*token == '#') - { - token++; - print_output(output, " id=\""); - while (*token) - print_output(output, "%c", - *token++); - print_output(output, "\""); - } - } - } - print_output(output, ">"); - output_firstcol = 0; + CALLOC(bufline, u8, BUFSIZE); + print_output(output, "<style>\n"); + while (!feof(css)) + { + if (!fgets((char*)bufline, BUFSIZE, css)) + break; + print_output(output, "%s", bufline); } - - return 0; -} - -int -process_bold(FILE* output, const int end_tag) -{ - print_output(output, "<%sstrong>", end_tag ? "/" : ""); + print_output(output, "</style>\n"); + output_firstcol = 1; + +process_inline_stylesheet_cleanup: + free(css_pathname); + fclose(css); + free(bufline); return 0; } -int +static void process_italic(FILE* output, const int end_tag) { print_output(output, "<%sem>", end_tag ? "/" : ""); - return 0; -} - -int -process_code(FILE* output, const int end_tag) -{ - print_output(output, "<%scode>", end_tag ? "/" : ""); - return 0; } -int -process_blockquote(FILE* output, const int end_tag) -{ - print_output(output, "<%sblockquote>", end_tag ? "/" : ""); - return 0; -} - -int +static void process_kbd(FILE* output, const int end_tag) { print_output(output, "<%skbd>", end_tag ? "/" : ""); - return 0; } -int -process_madeby(FILE* output) +static void +process_line_start(FILE* output, const u8* line, const u8* link_prefix, + const int first_line_in_doc, const int previous_line_blank, + int* previous_line_block, const int read_yaml_macros_and_links, + const int list_para, const int new_para_ok) { - print_output(output, - "<div id=\"made-by\"><p><small>\n" - "Generated by <a href=\"%s\">slweb</a>\n" - "© %s Strahinya Radich.\n" - "</small></div><!--made-by-->\n", - MADEBY_URL, COPYRIGHT_YEAR); - return 0; -} + UNUSED(line); + UNUSED(link_prefix); + if ((first_line_in_doc || previous_line_blank || *previous_line_block) + && !(ANY(state, ST_BLOCKQUOTE | ST_PRE))) + { + if (!list_para) + { + if (IN(state, ST_LIST)) + { + CLEAR(state, ST_LIST); + if (!read_yaml_macros_and_links) + { + process_list_item_end(output); + process_list_end(output); + } + } -int -process_strike(FILE* output, const int end_tag) -{ - print_output(output, "<%ss>", end_tag ? "/" : ""); - return 0; + if (IN(state, ST_NUMLIST)) + { + CLEAR(state, ST_NUMLIST); + if (!read_yaml_macros_and_links) + { + process_list_item_end(output); + process_numlist_end(output); + } + } + + if (IN(state, ST_FOOTNOTE_TEXT)) + CLEAR(state, ST_FOOTNOTE_TEXT | ST_PARA_OPEN); + } + if (new_para_ok + && !ANY(state, + ST_TABLE | ST_TABLE_HEADER | ST_TABLE_LINE)) + { + if (!read_yaml_macros_and_links) + { + print_output(output, "<p>"); + *previous_line_block = 0; + output_firstcol = 0; + } + SET(state, ST_PARA_OPEN); + } + } } -int -process_table_start(FILE* output) +static int +process_link(FILE* output, const u8* link_text, const u8* link_prefix, + const u8* link_macro_body, const u8* link_id) { - print_output(output, - "<table>\n" - "<thead>\n<tr><th>"); - return 0; + u8* url = get_value(links, links_count, link_id, NULL); + return process_inline_link(output, link_text, link_prefix, + link_macro_body, url); } -int -process_table_header_cell(FILE* output) +static void +process_list_end(FILE* output) { - print_output(output, "</th><th>"); - return 0; + print_output(output, "</ul>\n"); + output_firstcol = 1; } -int -process_table_header_end(FILE* output) +static void +process_list_item_end(FILE* output) { - print_output(output, "</th></tr>\n</thead>\n"); - return 0; + CLEAR(state, ST_PARA_OPEN); + print_output(output, "</li>"); + output_firstcol = 0; } -int -process_table_body_start(FILE* output, const int start_row) +static void +process_list_item_start(FILE* output) { - print_output(output, "<tbody>\n"); - if (start_row) - print_output(output, "<tr><td>"); - return 0; + print_output(output, "%s<li><p>", output_firstcol ? "" : "\n"); } -int -process_table_body_row_start(FILE* output) +static void +process_list_start(FILE* output) { - print_output(output, "<tr><td>"); - return 0; + print_output(output, "<ul>"); } -int -process_table_body_cell(FILE* output) +static int +process_macro(FILE* output, const u8* token, const int end_tag) { - print_output(output, "</td><td>"); + u8* macro_body; + u8* eol; + size_t macro_body_len; + UNUSED(end_tag); + assert((output != NULL) && (token != NULL)); + macro_body = get_value(macros, macros_count, token + 1, NULL); + if (!macro_body) + exit(error(1, __FILE__, __func__, __LINE__, + "Macro '%s' undefined", token + 1)); + + eol = (u8*)strrchr((char*)macro_body, '\n'); + macro_body_len = strlen((char*)macro_body); + if (eol) + output_firstcol = macro_body + macro_body_len + - (u8*)strrchr((char*)macro_body, '\n') + > 0; + else + output_firstcol = !*macro_body + || *(macro_body + macro_body_len - 1) == '\n'; + print_output(output, "%s", macro_body); return 0; } -int -process_table_body_row_end(FILE* output) +static int +process_macro_def(FILE* output, const u8* token, + const int read_yaml_macros_and_links, const int end_tag) { - print_output(output, "</td></tr>\n"); + char* temp = NULL; + assert((output != NULL) && (token != NULL)); + if (end_tag) + { + fflush(output); + CLEAR(state, ST_MACRO_BODY); + return 0; + } + if (IN(state, ST_MACRO_BODY)) + exit(error(1, __FILE__, __func__, __LINE__, + "Cannot nest definitions")); + + if (read_yaml_macros_and_links) + { + macros_count++; + if (!macros) + { + CALLOC(macros, KeyValue, macros_count); + pmacros = macros; + } + else + { + REALLOC(macros, KeyValue, + macros_count * sizeof(KeyValue)); + pmacros = macros + macros_count - 1; + } + CALLOC(pmacros->key, u8, KEYSIZE); + MEMCCPY(pmacros->key, (char*)token + 2, KEYSIZE, temp); + pmacros->value = NULL; + pmacros->value_size = 0; + } + fflush(output); + SET(state, ST_MACRO_BODY); return 0; } -int -process_table_end(FILE* output) +static void +process_madeby(FILE* output) { - print_output(output, "</tbody>\n</table>\n"); - return 0; + print_output(output, + "<div id=\"made-by\"><p><small>\n" + "Generated by <a href=\"%s\">slweb</a>\n" + "© %s Strahinya Radich.\n" + "</small></div><!--made-by-->\n", + MADEBY_URL, COPYRIGHT_YEAR); } -int -url_is_local(const char* url) +static void +process_numlist_end(FILE* output) { - return !(startswith(url, "http://") || startswith(url, "https://") - || startswith(url, "gemini://") || startswith(url, "ftp://") - || startswith(url, "ftps://") || startswith(url, "mailto://")); + print_output(output, "</ol>\n"); + output_firstcol = 1; } -int -process_inline_link(FILE* output, const u8* link_text, const u8* link_prefix, - const u8* link_macro_body, const u8* link_url) +static void +process_numlist_start(FILE* output) { - print_output(output, "<a href=\"%s%s%s\">%s%s</a>", - global_link_prefix ? (const char*)global_link_prefix : "", - link_prefix ? (const char*)link_prefix : "", - link_url ? (char*)link_url : "", - link_macro_body ? (char*)link_macro_body : "", link_text); + print_output(output, "<ol>"); output_firstcol = 0; - - return 0; } -int -process_link(FILE* output, const u8* link_text, const u8* link_prefix, - const u8* link_macro_body, const u8* link_id) +static void +process_strike(FILE* output, const int end_tag) { - u8* url = get_value(links, links_count, link_id, NULL); - return process_inline_link(output, link_text, link_prefix, - link_macro_body, url); + print_output(output, "<%ss>", end_tag ? "/" : ""); } -int -print_image_dimensions(FILE* output, const u8* image_file_prefix, const u8* path) +static void +process_stylesheet(FILE* output, const u8* css_filename, int output_yaml) { - struct stat sb; - FILE* cmd_output = NULL; - char command[BUFSIZE]; - char cmd_output_line[BUFSIZE]; - char* eol = NULL; - char image_path[BUFSIZE - strlen(CMD_IDENTIFY)]; - - if (!path || !*path) - return 1; - - snprintf(image_path, BUFSIZE - strlen(CMD_IDENTIFY) - 1, "%s%s%s", - image_file_prefix ? (char*)image_file_prefix : ".", - *path == '/' ? "" : "/", path); - - if (stat(image_path, &sb) < 0) - return 1; - - snprintf(command, BUFSIZE - 1, CMD_IDENTIFY, image_path); - cmd_output = popen(command, "r"); - if (!cmd_output) - return error(errno, __FILE__, __func__, __LINE__, - "popen failed"); - if (!fgets(cmd_output_line, BUFSIZE, cmd_output)) - goto print_dimensions_cleanup; - - eol = strchr((char*)cmd_output_line, '\n'); - if (eol) - *eol = 0; - - print_output(output, "%s", cmd_output_line); - output_firstcol = 0; - -print_dimensions_cleanup: - pclose(cmd_output); + if (output_yaml) + print_output(output, "stylesheet: %s\n", css_filename); + else + print_output(output, "<link rel=\"stylesheet\" href=\"%s\">\n", + css_filename); + output_firstcol = 1; +} - return 0; +static void +process_table_body_cell(FILE* output) +{ + print_output(output, "</td><td>"); } -int -process_inline_image(FILE* output, const u8* image_text, - const u8* image_file_prefix, const u8* link_prefix, const u8* image_url, - const int add_link, const int add_figcaption) +static void +process_table_body_row_end(FILE* output) { - if (add_figcaption) - { - print_output(output, "<figure>\n"); - } + print_output(output, "</td></tr>\n"); +} - if (add_link) - print_output(output, - "<a href=\"%s%s%s\" title=\"%s\" class=\"image\"" - " target=\"_blank\">", - global_link_prefix ? (const char*)global_link_prefix - : "", - link_prefix ? (const char*)link_prefix : "", - image_url ? (char*)image_url : "", image_text); +static void +process_table_body_row_start(FILE* output) +{ + print_output(output, "<tr><td>"); +} - print_output(output, "<img src=\"%s\" alt=\"%s\"%s", - image_url ? (char*)image_url : "", image_text, - image_url ? " " : ""); - if (image_url) - print_image_dimensions(output, image_file_prefix, image_url); - print_output(output, ">"); +static void +process_table_body_start(FILE* output, const int start_row) +{ + print_output(output, "<tbody>\n"); + if (start_row) + print_output(output, "<tr><td>"); +} - output_firstcol = 0; +static void +process_table_end(FILE* output) +{ + print_output(output, "</tbody>\n</table>\n"); +} - if (add_link) - print_output(output, "</a>"); +static void +process_table_header_cell(FILE* output) +{ + print_output(output, "</th><th>"); +} - if (add_figcaption) - { - print_output(output, "<figcaption>%s</figcaption>\n</figure>\n", - image_text); - output_firstcol = 1; - } - return 0; +static void +process_table_header_end(FILE* output) +{ + print_output(output, "</th></tr>\n</thead>\n"); } -int -process_image(FILE* output, const u8* image_text, const u8* image_file_prefix, - const u8* link_prefix, const u8* image_id, const int add_link, - const int add_figcaption) +static void +process_table_start(FILE* output) { - u8* url = get_value(links, links_count, image_id, NULL); - return process_inline_image(output, image_text, image_file_prefix, - link_prefix, url, add_link, add_figcaption); + print_output(output, + "<table>\n" + "<thead>\n<tr><th>"); } -int -process_line_start(FILE* output, const u8* line, const u8* link_prefix, - const int first_line_in_doc, const int previous_line_blank, - int* previous_line_block, const int read_yaml_macros_and_links, - const int list_para, const int new_para_ok) +static int +process_tag(FILE* output, const u8* token, const char* source_filename, + const u8* link_prefix, const u8* footer_permalink_text, + const char* permalink_url, const int ext_in_permalink, + const int read_yaml_macros_and_links, int* skip_eol, const int end_tag) { - UNUSED(line); - UNUSED(link_prefix); - if ((first_line_in_doc || previous_line_blank || *previous_line_block) - && !(ANY(state, ST_BLOCKQUOTE | ST_PRE))) + UNUSED(source_filename); + assert((output != NULL) && (token != NULL) && (source_filename != NULL) + && (skip_eol != NULL)); + if (!*token) + return warning(1, (u8*)"%s:%ld:%ld: Empty tag name", + input_filename, lineno, colno); + if (!strcmp((char*)token, "git-log") + && !read_yaml_macros_and_links) /* {git-log} */ { - if (!list_para) + process_git_log(output); + } + else if (startswith((char*)token, "csv-count")) /* {csv-count} */ + process_tsv_count(output, token, read_yaml_macros_and_links, 0); + else if (startswith((char*)token, "csv")) /* {csv} */ + process_csv(output, token, read_yaml_macros_and_links, end_tag); + else if (startswith((char*)token, "tsv-count")) /* {tsv-count} */ + process_tsv_count(output, token, read_yaml_macros_and_links, 1); + else if (startswith((char*)token, "tsv")) /* {tsv} */ + process_tsv(output, token, read_yaml_macros_and_links, end_tag); + else if (startswith((char*)token, "include")) /* {include} */ + { + process_include(output, token, read_yaml_macros_and_links); + *skip_eol = 1; + } + else if (startswith((char*)token, "incdir")) /* {incdir} */ + { + process_incdir(output, token, link_prefix, + footer_permalink_text, permalink_url, ext_in_permalink, + read_yaml_macros_and_links); + *skip_eol = 1; + } + else if (*token == '=') /* {=macro} */ + { + if (*(token + 1) == '!') + process_macro_def(output, token, + read_yaml_macros_and_links, end_tag); + else if ((*(token + 1) != '!') && !read_yaml_macros_and_links) + process_macro(output, token, end_tag); + *skip_eol = 1; + } + else if (!read_yaml_macros_and_links) /* general tags */ + { + print_output(output, "<"); + if (end_tag) + print_output(output, "/"); + + if (*token == '.' || *token == '#') { - if (IN(state, ST_LIST)) + print_output(output, "div"); + } + + while (*token && *token != '#' && *token != '.') + print_output(output, "%c", *token++); + + if (!end_tag) + { + if (*token == '#') { - state &= ~ST_LIST; - if (!read_yaml_macros_and_links) + token++; + print_output(output, " id=\""); + while (*token && *token != '.') + print_output(output, "%c", *token++); + print_output(output, "\""); + if (*token == '.') { - process_list_item_end(output); - process_list_end(output); + token++; + print_output(output, " class=\""); + while (*token) + print_output(output, "%c", + *token++); + print_output(output, "\""); } } - - if (IN(state, ST_NUMLIST)) + else if (*token == '.') { - state &= ~ST_NUMLIST; - if (!read_yaml_macros_and_links) + token++; + print_output(output, " class=\""); + while (*token && *token != '#') + print_output(output, "%c", *token++); + print_output(output, "\""); + if (*token == '#') { - process_list_item_end(output); - process_numlist_end(output); + token++; + print_output(output, " id=\""); + while (*token) + print_output(output, "%c", + *token++); + print_output(output, "\""); } } - - if (IN(state, ST_FOOTNOTE_TEXT)) - state &= ~(ST_FOOTNOTE_TEXT | ST_PARA_OPEN); - } - if (new_para_ok - && !ANY(state, - ST_TABLE | ST_TABLE_HEADER | ST_TABLE_LINE)) - { - if (!read_yaml_macros_and_links) - { - print_output(output, "<p>"); - *previous_line_block = 0; - output_firstcol = 0; - } - state |= ST_PARA_OPEN; } + print_output(output, ">"); + output_firstcol = 0; } return 0; } -int +static void process_text_token(FILE* output, const u8* line, const u8* link_prefix, const int first_line_in_doc, const int previous_line_blank, int* previous_line_block, const int processed_start_of_line, @@ -2630,393 +2271,556 @@ process_text_token(FILE* output, const u8* line, const u8* link_prefix, u8** ptoken, size_t* token_size, const int add_enclosing_paragraph, const int new_para_ok) { + u8* eol; + assert((token != NULL) && (ptoken != NULL)); if (!(IN(state, ST_YAML))) { - if (add_enclosing_paragraph && !processed_start_of_line) - process_line_start(output, line, link_prefix, - first_line_in_doc, previous_line_blank, - previous_line_block, read_yaml_macros_and_links, - list_para, new_para_ok); - **ptoken = 0; - if (**token && !read_yaml_macros_and_links - && !(IN(state, ST_MACRO_BODY))) + if (add_enclosing_paragraph && !processed_start_of_line) + process_line_start(output, line, link_prefix, + first_line_in_doc, previous_line_blank, + previous_line_block, read_yaml_macros_and_links, + list_para, new_para_ok); + **ptoken = 0; + if (**token && !read_yaml_macros_and_links + && !(IN(state, ST_MACRO_BODY))) + { + print_output(output, "%s", *token); + eol = (u8*)strrchr((char*)*token, '\n'); + if (eol) + output_firstcol + = *token + strlen((char*)*token) > eol; + else + output_firstcol = 0; + } + } + RESET_TOKEN(*token, *ptoken, *token_size); +} + +static void +process_timestamp(FILE* output, const u8* link_prefix, const char* link, + const u8* permalink_macro, const u8* date, const int ext_in_permalink) +{ + u8* formatted_date = NULL; + char* in_filename = NULL; + char* in_line = NULL; + + assert((output != NULL) && (link != NULL)); + formatted_date = format_date(date, article_timestamp_format); + if (!formatted_date) + goto process_timestamp_cleanup; + print_output(output, + "<a href=\"%s%s%s%s\"" + " class=\"timestamp\">%s%s</a>\n", + url_is_local(link) && global_link_prefix + ? (const char*)global_link_prefix + : "", + url_is_local(link) && link_prefix ? (const char*)link_prefix + : "", + link, ext_in_permalink ? timestamp_output_ext : "", + permalink_macro ? (char*)permalink_macro : "", formatted_date); + output_firstcol = 1; + +process_timestamp_cleanup: + free(formatted_date); + free(in_line); + free(in_filename); +} + +static int +process_tsv(FILE* output, const u8* arg_token, + const int read_yaml_macros_and_links, const int end_tag) +{ + u8* saveptr = NULL; + u8* args = NULL; + u8* args_base = NULL; + size_t args_len; + + assert((output != NULL) && (arg_token != NULL)); + if (end_tag) + { + CLEAR(state, ST_TSV_BODY); + if (read_yaml_macros_and_links) + return 0; + read_tsv(output, tsv_filename, &print_tsv_row); + free(tsv_filename); + tsv_filename = NULL; + free(tsv_template); + tsv_template = NULL; + tsv_template_size = 0; + } + else + { + if (ANY(state, ST_CSV_BODY | ST_TSV_BODY)) + exit(error(1, __FILE__, __func__, __LINE__, + "Can't nest csv/tsv directives")); + SET(state, ST_TSV_BODY); + if (read_yaml_macros_and_links) + return 0; + tsv_iter = 0; + (void)strtok_r((char*)arg_token, " ", (char**)&saveptr); + args = (u8*)strtok_r(NULL, " ", (char**)&saveptr); + if (!args) + exit(error(EINVAL, __FILE__, __func__, __LINE__, + "Arguments required")); + args_len = strlen((char*)args); + if (*args != '"' || *(args + args_len - 1) != '"') + exit(error(EINVAL, __FILE__, __func__, __LINE__, + "First argument must be a string")); + if (!tsv_filename) + CALLOC(tsv_filename, char, BUFSIZE); + args_base = (u8*)strdup((char*)args + 1); + *(args_base + strlen((char*)args_base) - 1) = 0; + snprintf(tsv_filename, BUFSIZE, "%s/%s.tsv", input_dirname, + (char*)args_base); + free(args_base); + args = (u8*)strtok_r(NULL, " ", (char**)&saveptr); + if (args) { - print_output(output, "%s", *token); - u8* eol = (u8*)strrchr((char*)*token, '\n'); - if (eol) - output_firstcol - = *token + strlen((char*)*token) - eol - > 0; - else - output_firstcol = 0; + errno = 0; + tsv_iter = (size_t)strtol((char*)args, NULL, 10); + if (errno) + exit(error(errno, __FILE__, __func__, __LINE__, + "Invalid argument '%s'", args)); } } - RESET_TOKEN(*token, *ptoken, *token_size); return 0; } -int -process_inline_footnote(const u8* token, const int read_yaml_macros_and_links, - FILE* output) +static int +process_tsv_count(FILE* output, const u8* arg_token, + const int read_yaml_macros_and_links, const int is_tsv) { - char* temp = NULL; - size_t token_len; + FILE* tsv = NULL; + u8* saveptr = NULL; + u8* args = NULL; + u8* args_base = NULL; + char* bufline = NULL; + char* eol = NULL; + size_t args_len; + size_t tsv_lines = 0; - current_inline_footnote++; + assert((output != NULL) && (arg_token != NULL)); if (read_yaml_macros_and_links) + return 0; + tsv_iter = 0; + (void)strtok_r((char*)arg_token, " ", (char**)&saveptr); + args = (u8*)strtok_r(NULL, " ", (char**)&saveptr); + if (!args) + exit(error(EINVAL, __FILE__, __func__, __LINE__, + "Arguments required")); + args_len = strlen((char*)args); + if (*args != '"' || *(args + args_len - 1) != '"') + exit(error(EINVAL, __FILE__, __func__, __LINE__, + "First argument must be a string")); + if (!tsv_filename) + CALLOC(tsv_filename, char, BUFSIZE); + args_base = (u8*)strdup((char*)args + 1); + *(args_base + strlen((char*)args_base) - 1) = 0; + snprintf(tsv_filename, BUFSIZE, "%s/%s.%csv", input_dirname, + (char*)args_base, is_tsv ? 't' : 'c'); + free(args_base); + if (!(tsv = fopen(tsv_filename, "rt"))) { - token_len = strlen((char*)token); - inline_footnote_count++; - if (inline_footnote_count == 1 && footnote_count > 0) - warning(1, - (u8*)"Both inline and regular footnotes " - "present"); - else if (!inline_footnotes) - { - inline_footnote_count = 1; - CALLOC(inline_footnotes, u8*, inline_footnote_count); - } - else - REALLOC(inline_footnotes, u8*, - sizeof(u8*) * inline_footnote_count); - CALLOC(inline_footnotes[inline_footnote_count - 1], u8, - token_len + 1); - - MEMCCPY(inline_footnotes[inline_footnote_count - 1], - (char*)token, token_len + 1, temp); + perror("fopen"); + exit(error(ENOENT, __FILE__, __func__, __LINE__, + "fopen failed: %s", tsv_filename)); } - else + CALLOC(bufline, char, BUFSIZE); + while (!feof(tsv)) { - print_output(output, - "<a href=\"#inline-footnote-%d\" " - "id=\"inline-footnote-text-%d\"><sup>%d</sup></a>", - current_inline_footnote, current_inline_footnote, - current_inline_footnote); - output_firstcol = 0; + eol = NULL; + if (!fgets(bufline, BUFSIZE, tsv)) + break; + eol = strchr(bufline, '\n'); + if (eol) + { + *eol = 0; + tsv_lines++; + } } + fclose(tsv); + + /* And take back one kadam for the header row */ + print_output(output, "%ld", tsv_lines - 1); + free(bufline); + free(tsv_filename); + tsv_filename = NULL; return 0; } -int -process_footnote(FILE* output, const u8* token, const int footnote_definition, - const int footnote_output) +static void +read_csv(FILE* output, const char* filename, csv_callback_t callback) { - char* temp = NULL; - - current_footnote++; - - if (footnote_definition) + FILE* csv = NULL; + u8* bufline = NULL; + u8* pbufline = NULL; + u8* token = NULL; + u8* ptoken = NULL; + u8* csv_header[MAX_CSV_REGISTERS]; + u8* csv_register[MAX_CSV_REGISTERS]; + u8* csv_delimiter = NULL; + u8* eol = NULL; + char* temp = NULL; + size_t csv_lineno = 0; + size_t token_size = 0; + unsigned char csv_state = ST_CS_NONE; + unsigned char current_header = 0; + unsigned char current_register = 0; + unsigned char i; + + assert((output != NULL) && (filename != NULL) && (callback != NULL)); + csv_delimiter = get_value(vars, vars_count, (u8*)"csv-delimiter", NULL); + if (!(csv = fopen(filename, "rt"))) { - footnote_count++; - if (footnote_count == 1 && inline_footnote_count > 0) - warning(1, - (u8*)"Both inline and regular footnotes " - "present"); - else if (!footnotes) + perror("fopen"); + exit(error(errno, __FILE__, __func__, __LINE__, + "fopen failed: %s", filename)); + } + CALLOC(bufline, u8, BUFSIZE); + token_size = BUFSIZE; + CALLOC(token, u8, token_size); + for (i = 0; i < MAX_CSV_REGISTERS; i++) + CALLOC(csv_header[i], u8, BUFSIZE); + for (i = 0; i < MAX_CSV_REGISTERS; i++) + CALLOC(csv_register[i], u8, BUFSIZE); + while (!feof(csv) && (!csv_iter || csv_lineno <= csv_iter)) + { + eol = NULL; + if (!fgets((char*)bufline, BUFSIZE, csv)) + break; + eol = (u8*)strchr((char*)bufline, '\n'); + if (eol) + *eol = 0; + pbufline = bufline; + RESET_TOKEN(token, ptoken, token_size); + current_register = 0; + csv_bufline_start: + if (!*pbufline) + goto csv_bufline_done; + switch (*pbufline) { - CALLOC(footnotes, KeyValue, footnote_count); - pfootnotes = footnotes; + case '"': + if (IN(csv_state, ST_CS_QUOTE)) + csv_state &= ~ST_CS_QUOTE; + else + csv_state |= ST_CS_QUOTE; + pbufline++; + break; + case ';': + case ',': + if (IN(csv_state, ST_CS_QUOTE)) + CHECKCOPY(token, ptoken, token_size, pbufline); + else + { + *ptoken = 0; + if (csv_lineno > 0 + && current_register < MAX_CSV_REGISTERS) + { + MEMCCPY(csv_register[current_register], + (char*)token, BUFSIZE, temp); + current_register++; + } + else if (csv_lineno <= 0 + && current_header < MAX_CSV_REGISTERS) + { + MEMCCPY(csv_header[current_header], + (char*)token, BUFSIZE, temp); + current_header++; + } + RESET_TOKEN(token, ptoken, token_size); + pbufline++; + } + break; + default: + if (IN(csv_state, ST_CS_QUOTE)) + CHECKCOPY(token, ptoken, token_size, pbufline); + else if (csv_delimiter && *pbufline == *csv_delimiter) + { + *ptoken = 0; + if (csv_lineno > 0 + && current_register < MAX_CSV_REGISTERS) + { + MEMCCPY(csv_register[current_register], + (char*)token, BUFSIZE, temp); + current_register++; + } + else if (csv_lineno <= 0 + && current_header < MAX_CSV_REGISTERS) + { + MEMCCPY(csv_header[current_header], + (char*)token, BUFSIZE, temp); + current_header++; + } + RESET_TOKEN(token, ptoken, token_size); + pbufline++; + } + else + CHECKCOPY(token, ptoken, token_size, pbufline); } - else + goto csv_bufline_start; + csv_bufline_done: + *ptoken = 0; + if (csv_lineno > 0) { - REALLOCARRAY(footnotes, KeyValue, footnote_count); - pfootnotes = footnotes + footnote_count - 1; + if (current_register < MAX_CSV_REGISTERS) + { + MEMCCPY(csv_register[current_register], + (char*)token, BUFSIZE, temp); + current_register++; + } } - CALLOC(pfootnotes->key, u8, KEYSIZE); - MEMCCPY(pfootnotes->key, (char*)token, KEYSIZE, temp); - pfootnotes->value = NULL; - pfootnotes->value_size = 0; - } - - if (footnote_output) - { - print_output(output, - "<a href=\"#footnote-%d\" id=\"footnote-text-%d\">" - "<sup>%d</sup></a>", - current_footnote, current_footnote, current_footnote); - output_firstcol = 0; - } + else + { + if (current_header < MAX_CSV_REGISTERS) + { + MEMCCPY(csv_header[current_header], + (char*)token, BUFSIZE, temp); + current_header++; + } + } + RESET_TOKEN(token, ptoken, token_size); - return 0; -} + if (csv_lineno > 0 && pbufline != bufline) + (*callback)(output, csv_header, csv_register); -int -process_horizontal_rule(FILE* output) -{ - print_output(output, "<hr>\n"); - if (IN(state, ST_PARA_OPEN)) - print_output(output, "<p>\n"); - output_firstcol = 1; - return 0; + for (i = 0; i < MAX_CSV_REGISTERS; i++) + *csv_register[i] = 0; + csv_lineno++; + } + fclose(csv); + for (i = MAX_CSV_REGISTERS; i > 0; i--) + free(csv_register[i - 1]); + for (i = MAX_CSV_REGISTERS; i > 0; i--) + free(csv_header[i - 1]); + free(token); + free(bufline); } -int -process_formula(FILE* output, const u8* token, const int display_formula) +static int +read_file_into_buffer(FILE** input, u8** buffer, size_t* buffer_size, + const char* input_filename, char** input_dirname) { - int result = 0; - const u8* pipe_args[] = {token, NULL}; - - result = print_command(output, CMD_KATEX, - display_formula ? (const u8**)CMD_KATEX_DISPLAY_ARGS - : (const u8**)CMD_KATEX_INLINE_ARGS, - (const u8**)pipe_args, 1); + struct stat fs; + char* slash = NULL; + char* pinput_dirname; + const char* pinput_filename; - if (result) + assert((input != NULL) && (buffer != NULL) && (buffer_size !-NULL) + && (input_dirname != NULL)); + errno = 0; + *input = fopen(input_filename, "r"); + if (!*input) { - print_output(output, "%s$%s$%s", display_formula ? "$" : "", - token, display_formula ? "$" : ""); - output_firstcol = 0; + perror(PROGRAMNAME ": fopen"); + return error(errno, __FILE__, __func__, __LINE__, + "fopen failed: %s", input_filename); } - return result; -} - -int -begin_html_and_head(FILE* output) -{ - u8* lang = get_value(vars, vars_count, (u8*)"lang", NULL); - u8* site_name = get_value(vars, vars_count, (u8*)"site-name", NULL); - u8* site_desc = get_value(vars, vars_count, (u8*)"site-desc", NULL); - u8* canonical = get_value(vars, vars_count, (u8*)"canonical", NULL); - u8* favicon_url = get_value(vars, vars_count, (u8*)"favicon-url", NULL); - u8* meta = get_value(vars, vars_count, (u8*)"meta", NULL); - u8* feed = NULL; - u8* feed_type = NULL; - u8* feed_desc = NULL; - KeyValue* cursor = NULL; - KeyValue* cursor_type = NULL; - KeyValue* cursor_desc = NULL; - - print_output(output, - "<!DOCTYPE html>\n" - "<html lang=\"%s\">\n" - "<head>\n" - "<title>%s</title>\n" - "<meta charset=\"utf-8\">\n", - lang ? (char*)lang : "en", site_name ? (char*)site_name : ""); + errno = 0; + fstat(fileno(*input), &fs); + if (S_ISDIR(fs.st_mode)) + return error(EISDIR, __FILE__, __func__, __LINE__, + "Is a directory: %s", input_filename); - char* favicon = NULL; - CALLOC(favicon, char, BUFSIZE); - snprintf(favicon, BUFSIZE - 1, "%s/favicon.ico", basedir); - if (!access(favicon, R_OK)) - print_output(output, - "<link rel=\"shortcut icon\" type=\"image/x-icon\"" - " href=\"%s\">\n", - favicon_url ? (char*)favicon_url : "/favicon.ico"); - free(favicon); + free(*buffer); + *buffer_size = fs.st_size + 1; + CALLOC(*buffer, u8, *buffer_size); + fread((void*)*buffer, 1, *buffer_size, *input); + free(*input_dirname); - if (meta) + slash = strrchr(input_filename, '/'); + if (slash) { - char* filename = NULL; - CALLOC(filename, char, BUFSIZE); - snprintf(filename, BUFSIZE, "%s/%s", input_dirname, (char*)meta); - read_tsv(output, filename, &print_meta_var); - free(filename); + CALLOC(*input_dirname, char, strlen(input_filename) + 1); + pinput_dirname = *input_dirname; + pinput_filename = input_filename; + while (pinput_filename && *pinput_filename + && pinput_filename != slash) + *pinput_dirname++ = *pinput_filename++; } - - if (canonical && *canonical) - print_output(output, "<link rel=\"canonical\" href=\"%s\">\n", - (char*)canonical); - - cursor = vars; - cursor_type = vars; - cursor_desc = vars; - do + else { - feed = get_value(cursor, vars_count - (cursor - vars), - (u8*)"feed", &cursor); - feed_type = get_value(cursor_type, - vars_count - (cursor_type - vars), (u8*)"feed-type", - &cursor_type); - feed_desc = get_value(cursor_desc, - vars_count - (cursor_desc - vars), (u8*)"feed-desc", - &cursor_desc); - - if (feed && *feed && feed_type && *feed_type && feed_desc - && *feed_desc) - print_output(output, - "<link rel=\"alternate\"" - " type=\"%s\" href=\"%s\" " - "title=\"%s\">\n", - (char*)feed_type, (char*)feed, - (char*)feed_desc); - } while (feed && feed_type && feed_desc); - - if (site_desc && *site_desc) - print_output(output, - "<meta name=\"description\" content=\"%s\">\n", - (char*)site_desc); + CALLOC(*input_dirname, char, 2); + **input_dirname = '.'; + } + fclose(*input); - print_output(output, - "<meta name=\"viewport\" content=\"width=device-width," - " initial-scale=1\">\n<meta name=\"generator\" " - "content=\"slweb\">\n"); - output_firstcol = 1; return 0; } -int -add_css(FILE* output, int output_yaml) +static int +read_tsv(FILE* output, const char* filename, tsv_callback_t callback) { - if (!vars) - return -1; - KeyValue* pvars = vars; - while (pvars < vars + vars_count) + FILE* tsv = NULL; + u8* tsv_header[MAX_TSV_REGISTERS]; + u8* tsv_register[MAX_TSV_REGISTERS]; + u8* bufline = NULL; + u8* pbufline = NULL; + u8* token = NULL; + u8* ptoken = NULL; + u8* eol = NULL; + char* ctemp = NULL; + size_t tsv_lineno = 0; + size_t token_size = 0; + unsigned char current_header = 0; + unsigned char current_register = 0; + unsigned char i; + + assert((output != NULL) && (filename != NULL) && (callback != NULL)); + if (!(tsv = fopen(filename, "rt"))) { - if (!strcmp((char*)pvars->key, "stylesheet")) - process_stylesheet(output, pvars->value, output_yaml); - else if (!strcmp((char*)pvars->key, "inline-stylesheet")) - process_inline_stylesheet(output, pvars->value, - output_yaml); - pvars++; + perror(PROGRAMNAME ": fopen"); + exit(error(errno, __FILE__, __func__, __LINE__, + "fopen failed: %s", filename)); } - return 0; -} - -int -end_head_start_body(FILE* output) -{ - print_output(output, "</head>\n<body>\n"); - output_firstcol = 1; - - return 0; -} - -int -begin_article(FILE* output, const char* source_filename, const size_t sfn_size, - const u8* link_prefix, const int add_article_header, const u8* author, - const u8* title, const u8* header_text, const char* title_heading_level, - u8* date, const int ext_in_permalink, const char* permalink_url) -{ - int save_incdir = IN(state, ST_INCDIR); - char* temp = NULL; - - UNUSED(add_article_header); - state &= ~ST_INCDIR; - - if (author || date || header_text || title) - print_output(output, "<header>\n"); - - if (title) - print_output(output, "<h%s>%s</h%s>\n", - title_heading_level ? title_heading_level : "2", - (char*)title, - title_heading_level ? title_heading_level : "2"); - - if (header_text) - print_output(output, "<p>%s\n", (char*)header_text); - if (author) - print_output(output, "<address>%s</address>\n", author); + CALLOC(bufline, u8, BUFSIZE); + token_size = BUFSIZE; + CALLOC(token, u8, token_size); + for (i = 0; i < MAX_TSV_REGISTERS; i++) + CALLOC(tsv_header[i], u8, BUFSIZE); + for (i = 0; i < MAX_TSV_REGISTERS; i++) + CALLOC(tsv_register[i], u8, BUFSIZE); - if (date && source_filename) + while (!feof(tsv) && (!tsv_iter || tsv_lineno <= tsv_iter)) { - u8* permalink_macro = NULL; - char* link = NULL; - size_t link_len; - - permalink_macro = get_value(macros, macros_count, - (u8*)"permalink", NULL); - link = strip_ext((const char*)source_filename, BUFSIZE); - assert(sfn_size != 0); - link_len = strlen(link); - if (ext_in_permalink) - MEMCCPY((link + link_len), timestamp_output_ext, - sfn_size - link_len, temp); - if (permalink_url) - process_timestamp(output, link_prefix, permalink_url, - permalink_macro, date, ext_in_permalink); + eol = NULL; + if (!fgets((char*)bufline, BUFSIZE, tsv)) + break; + eol = (u8*)strchr((char*)bufline, '\n'); + if (eol) + *eol = 0; + pbufline = bufline; + RESET_TOKEN(token, ptoken, token_size); + current_register = 0; + tsv_bufline_start: + if (!*pbufline) + goto tsv_bufline_done; + switch (*pbufline) + { + case '\t': + *ptoken = 0; + if (tsv_lineno > 0 + && current_register < MAX_TSV_REGISTERS) + { + MEMCCPY(tsv_register[current_register], + (char*)token, BUFSIZE, ctemp); + current_register++; + } + else if (tsv_lineno <= 0 + && current_header < MAX_TSV_REGISTERS) + { + MEMCCPY(tsv_header[current_header], + (char*)token, BUFSIZE, ctemp); + current_header++; + } + RESET_TOKEN(token, ptoken, token_size); + pbufline++; + break; + default: + CHECKCOPY(token, ptoken, token_size, pbufline); + } + goto tsv_bufline_start; + tsv_bufline_done: + *ptoken = 0; + if (tsv_lineno > 0) + { + if (current_register < MAX_TSV_REGISTERS) + { + MEMCCPY(tsv_register[current_register], + (char*)token, BUFSIZE, ctemp); + current_register++; + } + } else - process_timestamp(output, link_prefix, link, - permalink_macro, date, ext_in_permalink); - free(link); - } - - if (author || date || header_text || title) - print_output(output, "</header>\n"); + { + if (current_header < MAX_TSV_REGISTERS) + { + MEMCCPY(tsv_header[current_header], + (char*)token, BUFSIZE, ctemp); + current_header++; + } + } + RESET_TOKEN(token, ptoken, token_size); - output_firstcol = author || date || header_text || title; + if (tsv_lineno > 0 && pbufline != bufline) + (*callback)(output, tsv_header, tsv_register); - if (save_incdir) - state |= ST_INCDIR; + for (i = 0; i < MAX_TSV_REGISTERS; i++) + *tsv_register[i] = 0; + tsv_lineno++; + } + fclose(tsv); + for (i = MAX_TSV_REGISTERS; i > 0; i--) + free(tsv_register[i - 1]); + for (i = MAX_TSV_REGISTERS; i > 0; i--) + free(tsv_header[i - 1]); + free(token); + free(bufline); return 0; } -int -end_footnotes(FILE* output, int add_footnote_div) +static int +reverse_alphacompare(const struct dirent** a, const struct dirent** b) { - size_t footnote = 0; - - if (IN(state, ST_PARA_OPEN)) - state &= ~ST_PARA_OPEN; - - if (add_footnote_div) - { - print_output(output, "<div class=\"footnotes\">\n"); - output_firstcol = 1; - } - - process_horizontal_rule(output); - - for (footnote = 0; footnote < inline_footnote_count; footnote++) - { - print_output(output, - "<p id=\"inline-footnote-%d\">" - "<a href=\"#inline-footnote-text-%d\">%d.</a> " - "%s\n", - footnote + 1, footnote + 1, footnote + 1, - (char*)inline_footnotes[footnote]); - output_firstcol = 1; - } - - KeyValue* pfootnote = footnotes; - footnote = 0; - while (pfootnote && footnote < footnote_count) - { - print_output(output, - "<p id=\"footnote-%d\">" - "<a href=\"#footnote-text-%d\">%d.</a> %s\n", - footnote + 1, footnote + 1, footnote + 1, - (char*)pfootnote->value); - output_firstcol = 1; - pfootnote++; - footnote++; - } - - if (add_footnote_div) - { - print_output(output, "</div><!--footnotes-->\n"); - output_firstcol = 1; - } + assert((a != NULL) && (b != NULL)); + return -1 * strcmp((*a)->d_name, (*b)->d_name); +} +static int +set_basedir(char** basedir, size_t* basedir_size, const char* arg) +{ + size_t arg_len; + char* temp = NULL; + assert((basedir != NULL) && (*basedir != NULL) && (basedir_size != NULL) + && (arg != NULL)); + arg_len = strlen(arg); + ENSURE_SIZE(*basedir, temp, *basedir_size, (arg_len + 1), (arg_len + 1), + set_basedir_alloc_error, char); + MEMCCPY(*basedir, arg, *basedir_size, temp); return 0; +set_basedir_alloc_error: + exit(error(errno, __FILE__, __func__, __LINE__, "Allocation error")); + return 1; } -int -end_body_and_html(FILE* output) +static int +set_global_link_prefix(char** global_link_prefix, + size_t* global_link_prefix_size, const char* arg) { - print_output(output, "</body>\n</html>\n"); - output_firstcol = 1; + char* temp = NULL; + size_t arg_len; + assert((arg != NULL) && (global_link_prefix != NULL) + && (*global_link_prefix != NULL) + && (global_link_prefix_size != NULL)); + arg_len = strlen(arg); + ENSURE_SIZE(*global_link_prefix, temp, *global_link_prefix_size, + (arg_len + 1), (arg_len + 1), + set_global_link_prefix_alloc_error, char); + MEMCCPY(*global_link_prefix, arg, *global_link_prefix_size, temp); return 0; +set_global_link_prefix_alloc_error: + exit(error(errno, __FILE__, __func__, __LINE__, "Allocation error")); + return 1; } -int +static int simple_parse_yaml_line(const u8* line, KeyValue** vars, size_t* vars_count, KeyValue** pvars) { + KeyValue* cvar = NULL; u8* token = NULL; u8* ptoken = NULL; u8* var_key = NULL; const u8* pline = line; char* temp = NULL; int in_arg = 0; + int found = 0; - if (!line || !vars || !vars_count || !pvars) - exit(error(EINVAL, __FILE__, __func__, __LINE__, - "Invalid argument")); - + assert((line != NULL) && (vars != NULL) && (vars_count != NULL) + && (pvars != NULL)); CALLOC(token, u8, BUFSIZE); ptoken = token; *ptoken = 0; @@ -3044,10 +2848,8 @@ simple_parse_yaml_line(const u8* line, KeyValue** vars, size_t* vars_count, *ptoken = 0; if (var_key && *var_key && *token) { - int found = 0; - KeyValue* cvar = NULL; - - cvar = *vars; + found = 0; + cvar = *vars; while (!found && cvar != *vars + *vars_count) { if (!strcmp((const char*)cvar->key, (const char*)var_key) @@ -3083,6 +2885,84 @@ simple_parse_yaml_line(const u8* line, KeyValue** vars, size_t* vars_count, return 0; } +static int +startswith(const char* s, const char* what) +{ + assert((s != NULL) && (what != NULL)); + do + { + if (*s++ != *what++) + return 0; + } while (*s && *what); + return !*what; +} + +static char* +strip_ext(const char* fn, const size_t fn_size) +{ + char* newname = NULL; + char* pnewname = NULL; + const char* pfn = NULL; + char* dot = NULL; + + assert(fn != NULL); + dot = strrchr(fn, '.'); + if (!dot) + return NULL; + newname = strndup(fn, fn_size); + if (!newname) + { + perror(PROGRAMNAME ": strndup"); + exit(error(errno, __FILE__, __func__, __LINE__, + "Allocation error")); + } + pnewname = newname; + pfn = fn; + while (pfn != dot && *pfn) + *pnewname++ = *pfn++; + *pnewname = 0; + return newname; +} + +static int +url_is_local(const char* url) +{ + return !(startswith(url, "http://") || startswith(url, "https://") + || startswith(url, "gemini://") || startswith(url, "ftp://") + || startswith(url, "ftps://") || startswith(url, "mailto://")); +} + +static int +usage(void) +{ + printf("Usage:\t%s -h | --help | -V | --full-version | -v | --version\n" + "\t%s [-b | --body-only] [-d directory | --basedir directory]" + " [-p URL | --global-link-prefix URL] [filename]\n", + PROGRAMNAME, PROGRAMNAME); + return 0; +} + +static int +version(const int full) +{ + printf("%s %s, committed on %s\n", PROGRAMNAME, VERSION, DATE); + if (full) + puts(COPYRIGHT); + return 0; +} + +static int +warning(const int code, const u8* fmt, ...) +{ + char buf[BUFSIZE]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), (const char*)fmt, args); + va_end(args); + fprintf(stderr, "Warning: %s\n", buf); + return code; +} + #define APPEND_TOKEN(what, what_len) \ do \ { \ @@ -3094,7 +2974,7 @@ simple_parse_yaml_line(const u8* line, KeyValue** vars, size_t* vars_count, ptoken = temp ? temp - 1 : &token[token_size - 1]; \ } while (0) -int +static int slweb_parse(FILE* output, const char* source_filename, const size_t sfn_size, const u8* buffer, const int body_only, const int read_yaml_macros_and_links) @@ -3117,21 +2997,22 @@ slweb_parse(FILE* output, const char* source_filename, const size_t sfn_size, const u8* pbuffer = NULL; u8* line = NULL; u8* pline = NULL; + u8* token = NULL; + u8* ptoken = NULL; + u8* link_text = NULL; + u8* link_macro = NULL; + u8* temp = NULL; + u8* temp2 = NULL; + u8* eol = NULL; size_t line_len = 0; size_t line_size = 0; size_t entity_len = 0; - u8* token = NULL; - u8* ptoken = NULL; size_t token_size = 0; size_t token_len = 0; size_t value_len = 0; - u8* link_text = NULL; size_t link_size = 0; - u8* link_macro = NULL; - u8* temp = NULL; - u8* temp2 = NULL; - UBYTE heading_level = 0; - ULONG heading_count = 0; + size_t heading_count = 0; + size_t pline_len = 0; int break_mark = 0; int end_tag = 0; int first_line_in_doc = 1; @@ -3146,9 +3027,8 @@ slweb_parse(FILE* output, const char* source_filename, const size_t sfn_size, int add_footnote_div = 0; int list_para = 0; int footnote_at_line_start = 0; - size_t pline_len = 0; int madeby_present = 0; - u8* eol = NULL; + unsigned char heading_level = 0; if (!buffer) exit(error(1, __FILE__, __func__, __LINE__, "Empty buffer")); @@ -3259,9 +3139,9 @@ do_line: else { if (lineno == 1) - state |= ST_YAML; + SET(state, ST_YAML); else - state &= ~ST_YAML; + CLEAR(state, ST_YAML); skip_change_first_line_in_doc = 1; } @@ -3271,7 +3151,7 @@ do_line: { if (IN(state, ST_NUMLIST)) { - state &= ~ST_NUMLIST; + CLEAR(state, ST_NUMLIST); if (!read_yaml_macros_and_links) { process_list_item_end(output); @@ -3285,7 +3165,7 @@ do_line: else process_list_item_end(output); process_list_item_start(output); - state |= ST_PARA_OPEN; + SET(state, ST_PARA_OPEN); } processed_start_of_line = 1; @@ -3754,7 +3634,7 @@ do_line: { if (IN(state, ST_NUMLIST)) { - state &= ~ST_NUMLIST; + CLEAR(state, ST_NUMLIST); if (!read_yaml_macros_and_links) { process_list_item_end(output); @@ -3768,14 +3648,12 @@ do_line: else process_list_item_end(output); process_list_item_start(output); - state |= ST_PARA_OPEN; + SET(state, ST_PARA_OPEN); } processed_start_of_line = 1; skip_eol = 0; - - state |= ST_LIST; - + SET(state, ST_LIST); pline += 2; colno += 2; } @@ -3807,8 +3685,7 @@ do_line: ST_PRE | ST_CODE | ST_HEADING))) process_bold(output, IN(state, ST_BOLD)); } - - state ^= ST_BOLD; + TOGGLE(state, ST_BOLD); pline += 2; colno += 2; } @@ -3819,7 +3696,6 @@ do_line: if (ANY(state, ST_INLINE_FOOTNOTE | ST_HEADING | ST_FOOTNOTE_TEXT | ST_LINK)) - APPEND_TOKEN(IN(state, ST_ITALIC) ? "</em>" : "<em>", sizeof(IN(state, ST_ITALIC) ? "</em>" @@ -3834,15 +3710,13 @@ do_line: read_yaml_macros_and_links, list_para, &token, &ptoken, &token_size, 1, 1); processed_start_of_line = 1; - if (!read_yaml_macros_and_links && !(ANY(state, ST_PRE | ST_CODE | ST_HEADING))) process_italic(output, IN(state, ST_ITALIC)); } - - state ^= ST_ITALIC; + TOGGLE(state, ST_ITALIC); pline++; colno++; } @@ -3867,7 +3741,7 @@ do_line: heading_count); output_firstcol = 0; } - state |= ST_HEADING_TEXT; + SET(state, ST_HEADING_TEXT); pline++; colno++; break; @@ -3904,7 +3778,7 @@ do_line: MEMCCPY(link_macro, (char*)token, BUFSIZE, temp); *(link_macro + strlen((char*)token)) = 0; RESET_TOKEN(token, ptoken, token_size); - state &= ~ST_LINK_MACRO; + CLEAR(state, ST_LINK_MACRO); } else if (!(IN(state, ST_HEADING) && *(pline - 1) == '#')) CHECKCOPY(token, ptoken, token_size, pline); @@ -3940,7 +3814,6 @@ do_line: { size_t value_len = strlen((char*)pmacros->value); - if (pmacros->value_size < value_len + token_len + 1) { @@ -3970,9 +3843,8 @@ do_line: processed_start_of_line = 1; parse_lbrace_end: - state |= ST_TAG; + SET(state, ST_TAG); RESET_TOKEN(token, ptoken, token_size); - pline++; colno++; break; @@ -4001,16 +3873,13 @@ do_line: read_yaml_macros_and_links, list_para, &token, &ptoken, &token_size, 0, 0); processed_start_of_line = 1; - - state |= ST_SUMMARY; - + SET(state, ST_SUMMARY); if (!read_yaml_macros_and_links) { - state |= ST_PARA_OPEN; + SET(state, ST_PARA_OPEN); print_output(output, "<p>\n"); output_firstcol = 1; } - pline += 2; colno++; } @@ -4031,7 +3900,6 @@ do_line: } *ptoken = 0; - if (IN(state, ST_MACRO_BODY) && !(end_tag && startswith((const char*)token, "="))) { @@ -4040,7 +3908,6 @@ do_line: token_len = strlen((char*)token); skip_eol = 1; value_len = strlen((char*)pmacros->value); - if (pmacros->value_size < value_len + token_len + 4) { @@ -4058,20 +3925,18 @@ do_line: *token = 0; ptoken = token; - state &= ~ST_TAG; + CLEAR(state, ST_TAG); end_tag = 0; } else if (IN(state, ST_TAG)) { - state &= ~ST_TAG; - + CLEAR(state, ST_TAG); if (break_mark) { - if (IN(state, ST_PARA_OPEN)) - state &= ~ST_PARA_OPEN; + CLEAR(state, ST_PARA_OPEN); if (IN(state, ST_LIST)) { - state &= ~ST_LIST; + CLEAR(state, ST_LIST); if (!read_yaml_macros_and_links) { process_list_item_end(output); @@ -4080,7 +3945,7 @@ do_line: } else if (IN(state, ST_NUMLIST)) { - state &= ~ST_NUMLIST; + CLEAR(state, ST_NUMLIST); if (!read_yaml_macros_and_links) { process_list_item_end(output); @@ -4104,7 +3969,6 @@ do_line: end_tag = 0; previous_line_block = 0; } - pline++; colno++; break; @@ -4146,13 +4010,12 @@ do_line: && !(ANY(state, ST_PRE | ST_CODE | ST_HEADING))) { - state ^= ST_KBD; + TOGGLE(state, ST_KBD); process_kbd(output, !(IN(state, ST_KBD))); output_firstcol = 0; } } - pline += 2; colno += 2; } @@ -4161,10 +4024,8 @@ do_line: && strlen((char*)pline) > 1 && *(pline + 1) == '@') { skip_eol = 1; - pline += 2; colno += 2; - switch (*pline) { case '\\': @@ -4177,13 +4038,13 @@ do_line: break; case '#': - state |= ST_TABLE_HEADER; + SET(state, ST_TABLE_HEADER); pline++; colno++; break; case '-': - state &= ~ST_TABLE_HEADER; + CLEAR(state, ST_TABLE_HEADER); if (!read_yaml_macros_and_links) { process_table_body_start(output, 0); @@ -4193,7 +4054,7 @@ do_line: break; case ' ': - state |= ST_TABLE; + SET(state, ST_TABLE); if (!read_yaml_macros_and_links) { process_table_body_row_start(output); @@ -4202,7 +4063,7 @@ do_line: break; case '/': - state &= ~ST_TABLE; + CLEAR(state, ST_TABLE); if (!read_yaml_macros_and_links) { process_table_end(output); @@ -4220,19 +4081,18 @@ do_line: else if (!(IN(state, ST_PRE)) && colno == 1) { skip_eol = 1; - if (IN(state, ST_TABLE_HEADER)) { - state &= ~ST_TABLE_HEADER; - state |= ST_TABLE_LINE; + CLEAR(state, ST_TABLE_HEADER); + SET(state, ST_TABLE_LINE); pline = NULL; break; } if (IN(state, ST_TABLE_LINE)) { - state &= ~ST_TABLE_LINE; - state |= ST_TABLE; + CLEAR(state, ST_TABLE_LINE); + SET(state, ST_TABLE); if (!read_yaml_macros_and_links) { process_table_body_start(output, 1); @@ -4249,7 +4109,7 @@ do_line: } else { - state |= ST_TABLE_HEADER; + SET(state, ST_TABLE_HEADER); if (!read_yaml_macros_and_links) { process_table_start(output); @@ -4330,16 +4190,13 @@ do_line: colno++; break; } - - state |= ST_HTML_TAG; + SET(state, ST_HTML_TAG); CHECKCOPY(token, ptoken, token_size, pline); colno++; break; case '>': - if (IN(state, ST_HTML_TAG)) - state &= ~ST_HTML_TAG; - + CLEAR(state, ST_HTML_TAG); if (ANY(state, ST_DISPLAY_FORMULA | ST_FORMULA | ST_IMAGE | ST_MACRO_BODY)) @@ -4356,14 +4213,11 @@ do_line: if (!read_yaml_macros_and_links && !(IN(state, ST_BLOCKQUOTE))) { - if (IN(state, ST_PARA_OPEN)) - state &= ~ST_PARA_OPEN; + CLEAR(state, ST_PARA_OPEN); process_blockquote(output, 0); output_firstcol = 0; } - - state |= ST_BLOCKQUOTE; - + SET(state, ST_BLOCKQUOTE); pline++; colno++; } @@ -4405,10 +4259,8 @@ do_line: read_yaml_macros_and_links, list_para, &token, &ptoken, &token_size, 0, 1); processed_start_of_line = 1; - RESET_TOKEN(token, ptoken, token_size); - - state |= ST_IMAGE; + SET(state, ST_IMAGE); keep_token = 1; pline += 2; colno += 2; @@ -4433,7 +4285,7 @@ do_line: if (colno != 1 && *(pline - 1) == '[') { - state |= ST_LINK_MACRO; + SET(state, ST_LINK_MACRO); pline++; colno++; } @@ -4446,7 +4298,6 @@ do_line: case '[': pline_len = strlen((char*)pline); - if (ANY(state, ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA | ST_HEADING | ST_IMAGE | ST_MACRO_BODY @@ -4458,7 +4309,7 @@ do_line: } if (IN(state, ST_FOOTNOTE_TEXT)) - state &= ~(ST_FOOTNOTE_TEXT | ST_PARA_OPEN); + CLEAR(state, ST_FOOTNOTE_TEXT | ST_PARA_OPEN); if (pline_len > 1 && *(pline + 1) == '^') { @@ -4477,7 +4328,7 @@ do_line: processed_start_of_line = 1; RESET_TOKEN(token, ptoken, token_size); - state |= ST_FOOTNOTE; + SET(state, ST_FOOTNOTE); pline += 2; colno += 2; break; @@ -4496,7 +4347,7 @@ do_line: processed_start_of_line = 1; RESET_TOKEN(token, ptoken, token_size); - state |= ST_LINK; + SET(state, ST_LINK); keep_token = 1; *link_macro = 0; pline++; @@ -4505,7 +4356,7 @@ do_line: if (*pline == '(') { APPEND_TOKEN("<span>", sizeof("<span>")); - state |= ST_LINK_SPAN; + SET(state, ST_LINK_SPAN); pline++; colno++; } @@ -4522,7 +4373,7 @@ do_line: else if (*link_macro && (IN(state, ST_LINK))) { APPEND_TOKEN("<span>", sizeof("<span>")); - state |= ST_LINK_SPAN; + SET(state, ST_LINK_SPAN); pline++; } else @@ -4546,7 +4397,7 @@ do_line: && *(pline + 1) == ']') { APPEND_TOKEN("</span>", sizeof("</span>")); - state &= ~ST_LINK_SPAN; + CLEAR(state, ST_LINK_SPAN); pline++; colno++; } @@ -4565,20 +4416,19 @@ do_line: { if (add_figcaption && IN(state, ST_PARA_OPEN)) - state &= ~ST_PARA_OPEN; - + CLEAR(state, ST_PARA_OPEN); process_inline_image(output, link_text, image_file_prefix, link_prefix, token, add_image_links, add_figcaption); - if (add_figcaption) previous_line_block = 1; } } RESET_TOKEN(token, ptoken, token_size); - state &= ~(ST_LINK | ST_LINK_SECOND_ARG | ST_IMAGE - | ST_IMAGE_SECOND_ARG); + CLEAR(state, + ST_LINK | ST_LINK_SECOND_ARG | ST_IMAGE + | ST_IMAGE_SECOND_ARG); pline++; } else @@ -4601,10 +4451,9 @@ do_line: { process_inline_footnote(token, read_yaml_macros_and_links, output); - keep_token = 0; RESET_TOKEN(token, ptoken, token_size); - state &= ~ST_INLINE_FOOTNOTE; + CLEAR(state, ST_INLINE_FOOTNOTE); pline++; colno++; } @@ -4613,15 +4462,13 @@ do_line: int footnote_definition = (strlen((char*)pline) > 1) && (*(pline + 1) == ':') && footnote_at_line_start; - process_footnote(output, token, footnote_definition && read_yaml_macros_and_links, !footnote_definition && !read_yaml_macros_and_links); - RESET_TOKEN(token, ptoken, token_size); - state &= ~ST_FOOTNOTE; + CLEAR(state, ST_FOOTNOTE); footnote_at_line_start = 0; pline++; colno++; @@ -4634,7 +4481,7 @@ do_line: pline++; colno++; } - state |= ST_FOOTNOTE_TEXT; + SET(state, ST_FOOTNOTE_TEXT); } } else if (IN(state, ST_LINK_SECOND_ARG)) @@ -4645,7 +4492,7 @@ do_line: link_macro, NULL), token); RESET_TOKEN(token, ptoken, token_size); - state &= ~(ST_LINK | ST_LINK_SECOND_ARG); + CLEAR(state, ST_LINK | ST_LINK_SECOND_ARG); pline++; colno++; } @@ -4654,17 +4501,15 @@ do_line: if (!read_yaml_macros_and_links) { if (add_figcaption && IN(state, ST_PARA_OPEN)) - state &= ~ST_PARA_OPEN; - + CLEAR(state, ST_PARA_OPEN); process_image(output, link_text, image_file_prefix, link_prefix, token, add_image_links, add_figcaption); - if (add_figcaption) previous_line_block = 1; } RESET_TOKEN(token, ptoken, token_size); - state &= ~(ST_IMAGE | ST_IMAGE_SECOND_ARG); + CLEAR(state, ST_IMAGE | ST_IMAGE_SECOND_ARG); pline++; colno++; } @@ -4707,7 +4552,7 @@ do_line: } RESET_TOKEN(token, ptoken, token_size); keep_token = 0; - state |= ST_LINK_SECOND_ARG_END; + SET(state, ST_LINK_SECOND_ARG_END); break; case '[': @@ -4730,14 +4575,14 @@ do_line: RESET_TOKEN(token, ptoken, token_size); keep_token = 0; if (IN(state, ST_LINK)) - state |= ST_LINK_SECOND_ARG; + SET(state, ST_LINK_SECOND_ARG); else - state |= ST_IMAGE_SECOND_ARG; + SET(state, ST_IMAGE_SECOND_ARG); break; default: CHECKCOPY(token, ptoken, token_size, pline); colno++; - state &= ~(ST_LINK | ST_IMAGE); + CLEAR(state, ST_LINK | ST_IMAGE); break; } } @@ -4769,9 +4614,8 @@ do_line: read_yaml_macros_and_links, list_para, &token, &ptoken, &token_size, 1, 1); processed_start_of_line = 1; - RESET_TOKEN(token, ptoken, token_size); - state |= ST_INLINE_FOOTNOTE; + SET(state, ST_INLINE_FOOTNOTE); keep_token = 1; pline += 2; colno += 2; @@ -4838,22 +4682,17 @@ do_line: &token, &ptoken, &token_size, 1, 1); } processed_start_of_line = 1; - - state ^= ST_DISPLAY_FORMULA; - + TOGGLE(state, ST_DISPLAY_FORMULA); if (IN(state, ST_DISPLAY_FORMULA)) keep_token = 1; else { *ptoken = 0; - if (!read_yaml_macros_and_links) process_formula(output, token, 1); - keep_token = 0; RESET_TOKEN(token, ptoken, token_size); } - pline += 2; colno += 2; } @@ -4863,7 +4702,6 @@ do_line: exit(error(1, __FILE__, __func__, __LINE__, "Formula within an open display " "formula")); - if (!(IN(state, ST_FORMULA))) { /* Output existing text up to $ */ @@ -4875,22 +4713,17 @@ do_line: &token, &ptoken, &token_size, 1, 1); } processed_start_of_line = 1; - - state ^= ST_FORMULA; - + TOGGLE(state, ST_FORMULA); if (IN(state, ST_FORMULA)) keep_token = 1; else { *ptoken = 0; - if (!read_yaml_macros_and_links) process_formula(output, token, 0); - keep_token = 0; RESET_TOKEN(token, ptoken, token_size); } - pline++; colno++; } @@ -4919,10 +4752,9 @@ do_line: { ptoken = token; *ptoken = 0; - if (IN(state, ST_LIST)) { - state &= ~ST_LIST; + CLEAR(state, ST_LIST); if (!read_yaml_macros_and_links) { process_list_item_end(output); @@ -4936,14 +4768,11 @@ do_line: else process_list_item_end(output); process_list_item_start(output); - state |= ST_PARA_OPEN; + SET(state, ST_PARA_OPEN); } - processed_start_of_line = 1; skip_eol = 0; - - state |= ST_NUMLIST; - + SET(state, ST_NUMLIST); pline += 2; colno += 2; } @@ -5145,7 +4974,7 @@ done_line: } else if (IN(state, ST_HEADING)) { - state &= ~(ST_HEADING | ST_HEADING_TEXT); + CLEAR(state, ST_HEADING | ST_HEADING_TEXT); if (!read_yaml_macros_and_links) { process_heading(output, token, @@ -5176,7 +5005,7 @@ done_line: { if (IN(state, ST_LIST)) skip_eol = 1; - state &= ~ST_PARA_OPEN; + CLEAR(state, ST_PARA_OPEN); } if (!*pbuffer && (IN(state, ST_LIST))) @@ -5186,7 +5015,7 @@ done_line: process_list_item_end(output); process_list_end(output); } - state &= ~ST_LIST; + CLEAR(state, ST_LIST); } if (!*pbuffer && (IN(state, ST_NUMLIST))) @@ -5196,12 +5025,12 @@ done_line: process_list_item_end(output); process_numlist_end(output); } - state &= ~ST_NUMLIST; + CLEAR(state, ST_NUMLIST); } if (!*pbuffer && (IN(state, ST_FOOTNOTE_TEXT))) { - state &= ~ST_FOOTNOTE_TEXT; + CLEAR(state, ST_FOOTNOTE_TEXT); *ptoken = 0; token_len = strlen((char*)token); skip_eol = 1; @@ -5250,7 +5079,7 @@ done_line: if (IN(state, ST_BLOCKQUOTE) && (!*pbuffer || *pbuffer != '>')) { - state &= ~ST_BLOCKQUOTE; + CLEAR(state, ST_BLOCKQUOTE); if (!read_yaml_macros_and_links) { process_blockquote(output, 1); @@ -5268,7 +5097,7 @@ done_line: process_table_end(output); output_firstcol = 1; } - state &= ~(ST_TABLE_HEADER | ST_TABLE_LINE); + CLEAR(state, ST_TABLE_HEADER | ST_TABLE_LINE); } if ((IN(state, ST_TABLE)) && (!line_len || !*pbuffer)) @@ -5278,7 +5107,7 @@ done_line: process_table_end(output); output_firstcol = 1; } - state &= ~ST_TABLE; + CLEAR(state, ST_TABLE); } previous_line_blank = 0; @@ -5304,8 +5133,9 @@ done_line: previous_line_blank = 1; /* Lasts until the end of line */ - state &= ~(ST_YAML_VAL | ST_IMAGE_SECOND_ARG | ST_LINK_SECOND_ARG - | ST_LINK_SECOND_ARG_END); + CLEAR(state, + ST_YAML_VAL | ST_IMAGE_SECOND_ARG | ST_LINK_SECOND_ARG + | ST_LINK_SECOND_ARG_END); if (pbuffer && *pbuffer) goto do_buffer; @@ -5329,7 +5159,6 @@ done_buffer: free(link_macro); free(token); free(line); - return 0; parse_alloc_error: @@ -5373,6 +5202,7 @@ main(const int argc, const char** argv) cmd = CMD_FULL_VERSION; else if (!strcmp(arg, "version")) cmd = CMD_VERSION; + else if (!strcmp(arg, "body-only")) body_only = 1; else if (startswith(arg, "basedir")) @@ -5435,7 +5265,6 @@ main(const int argc, const char** argv) else { int result; - switch (cmd) { case CMD_BASEDIR: