From ff8898476685e6b7dff03231275a6a8bc2cbaf17 Mon Sep 17 00:00:00 2001 From: "Rafael G. Martins" Date: Fri, 11 Sep 2020 01:07:43 +0200 Subject: blogc: generate table of contents tree this commit allows users to use the `{{ TOCTREE }}` variable in their templates, to get automatically generated table of contents for entries. The variable is binded to each entry, so it can be used in any block that runs in entry context, like `{% block entry %}`. There's a small performance penalty for this, because the table of contents is rendered for any entry, despite being used or not, and not generated on-demand. still missing documentation. tests are good enough. --- src/blogc/content-parser.c | 34 ++++++++++++--- src/blogc/content-parser.h | 6 ++- src/blogc/loader.c | 20 +++++++-- src/blogc/loader.h | 5 ++- src/blogc/main.c | 2 +- src/blogc/source-parser.c | 36 ++++++++++++++-- src/blogc/source-parser.h | 4 +- src/blogc/toctree.c | 103 +++++++++++++++++++++++++++++++++++++++++++++ src/blogc/toctree.h | 26 ++++++++++++ 9 files changed, 217 insertions(+), 19 deletions(-) create mode 100644 src/blogc/toctree.c create mode 100644 src/blogc/toctree.h (limited to 'src') diff --git a/src/blogc/content-parser.c b/src/blogc/content-parser.c index 047af4b..a42f6f6 100644 --- a/src/blogc/content-parser.c +++ b/src/blogc/content-parser.c @@ -1,6 +1,6 @@ /* * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins + * Copyright (C) 2014-2020 Rafael G. Martins * * This program can be distributed under the terms of the BSD License. * See the file LICENSE. @@ -11,6 +11,7 @@ #include #include "content-parser.h" +#include "toctree.h" #include "../common/utils.h" // this is a half ass implementation of a markdown-like syntax. bugs are @@ -676,7 +677,7 @@ blogc_is_ordered_list_item(const char *str, size_t prefix_len) char* blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, - char **description) + char **description, char **endl, bc_slist_t **headers) { // src is always nul-terminated. size_t src_len = strlen(src); @@ -696,11 +697,28 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, char *parsed = NULL; char *slug = NULL; + char *line_ending = NULL; + bool line_ending_found = false; + if (endl != NULL) { + if (*endl != NULL) { + line_ending_found = true; + } + else { + *endl = bc_malloc(3 * sizeof(char)); + } + line_ending = *endl; + } + else { + line_ending = bc_malloc(3 * sizeof(char)); + } + // this isn't empty because we need some reasonable default value in the // unlikely case that we need to print some line ending before evaluating // the "real" value. - char line_ending[3] = "\n"; - bool line_ending_found = false; + if (!line_ending_found) { + line_ending[0] = '\n'; + line_ending[1] = '\0'; + } char d = '\0'; @@ -840,6 +858,8 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, *first_header = blogc_htmlentities(tmp); parsed = blogc_content_parse_inline(tmp); slug = blogc_slugify(tmp); + if (headers != NULL) + *headers = blogc_toctree_append(*headers, header_level, slug, parsed); if (slug == NULL) bc_string_append_printf(rv, "%s%s", header_level, parsed, header_level, line_ending); @@ -922,7 +942,7 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, // do not propagate title and description to blockquote parsing, // because we just want paragraphs from first level of // content. - tmp = blogc_content_parse(tmp_str->str, NULL, NULL, NULL); + tmp = blogc_content_parse(tmp_str->str, NULL, NULL, NULL, endl, NULL); bc_string_append_printf(rv, "
%s
%s", tmp, line_ending); free(tmp); @@ -1280,5 +1300,9 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, current++; } + if (endl == NULL) { + free(line_ending); + } + return bc_string_free(rv, false); } diff --git a/src/blogc/content-parser.h b/src/blogc/content-parser.h index ea5d29d..a321155 100644 --- a/src/blogc/content-parser.h +++ b/src/blogc/content-parser.h @@ -1,6 +1,6 @@ /* * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins + * Copyright (C) 2014-2020 Rafael G. Martins * * This program can be distributed under the terms of the BSD License. * See the file LICENSE. @@ -11,6 +11,7 @@ #include #include +#include "../common/utils.h" char* blogc_slugify(const char *str); char* blogc_htmlentities(const char *str); @@ -18,6 +19,7 @@ char* blogc_fix_description(const char *paragraph); char* blogc_content_parse_inline(const char *src); bool blogc_is_ordered_list_item(const char *str, size_t prefix_len); char* blogc_content_parse(const char *src, size_t *end_excerpt, - char **first_header, char **description); + char **first_header, char **description, char **endl, + bc_slist_t **headers); #endif /* _CONTENT_PARSER_H */ diff --git a/src/blogc/loader.c b/src/blogc/loader.c index d620988..4e03ec3 100644 --- a/src/blogc/loader.c +++ b/src/blogc/loader.c @@ -1,6 +1,6 @@ /* * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins + * Copyright (C) 2014-2020 Rafael G. Martins * * This program can be distributed under the terms of the BSD License. * See the file LICENSE. @@ -76,7 +76,7 @@ blogc_template_parse_from_file(const char *f, bc_error_t **err) bc_trie_t* -blogc_source_parse_from_file(const char *f, bc_error_t **err) +blogc_source_parse_from_file(bc_trie_t *conf, const char *f, bc_error_t **err) { if (err == NULL || *err != NULL) return NULL; @@ -85,7 +85,19 @@ blogc_source_parse_from_file(const char *f, bc_error_t **err) char *s = bc_file_get_contents(f, true, &len, err); if (s == NULL) return NULL; - bc_trie_t *rv = blogc_source_parse(s, len, err); + + int toctree_maxdepth = -1; + const char *maxdepth = bc_trie_lookup(conf, "TOCTREE_MAXDEPTH"); + if (maxdepth != NULL) { + char *endptr; + toctree_maxdepth = strtol(maxdepth, &endptr, 10); + if (*maxdepth != '\0' && *endptr != '\0') { + fprintf(stderr, "warning: invalid value for 'TOCTREE_MAXDEPTH' " + "variable: %s. using %d instead\n", maxdepth, toctree_maxdepth); + } + } + + bc_trie_t *rv = blogc_source_parse(s, len, toctree_maxdepth, err); // set FILENAME variable if (rv != NULL) { @@ -133,7 +145,7 @@ blogc_source_parse_from_files(bc_trie_t *conf, bc_slist_t *l, bc_error_t **err) size_t with_date = 0; for (bc_slist_t *tmp = l; tmp != NULL; tmp = tmp->next) { char *f = tmp->data; - bc_trie_t *s = blogc_source_parse_from_file(f, &tmp_err); + bc_trie_t *s = blogc_source_parse_from_file(conf, f, &tmp_err); if (s == NULL) { *err = bc_error_new_printf(BLOGC_ERROR_LOADER, "An error occurred while parsing source file: %s\n\n%s", diff --git a/src/blogc/loader.h b/src/blogc/loader.h index 66da7d0..fe88730 100644 --- a/src/blogc/loader.h +++ b/src/blogc/loader.h @@ -1,6 +1,6 @@ /* * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins + * Copyright (C) 2014-2020 Rafael G. Martins * * This program can be distributed under the terms of the BSD License. * See the file LICENSE. @@ -14,7 +14,8 @@ char* blogc_get_filename(const char *f); bc_slist_t* blogc_template_parse_from_file(const char *f, bc_error_t **err); -bc_trie_t* blogc_source_parse_from_file(const char *f, bc_error_t **err); +bc_trie_t* blogc_source_parse_from_file(bc_trie_t *conf, const char *f, + bc_error_t **err); bc_slist_t* blogc_source_parse_from_files(bc_trie_t *conf, bc_slist_t *l, bc_error_t **err); diff --git a/src/blogc/main.c b/src/blogc/main.c index f952957..7024967 100644 --- a/src/blogc/main.c +++ b/src/blogc/main.c @@ -307,7 +307,7 @@ main(int argc, char **argv) listing_entries_source = bc_slist_append(listing_entries_source, NULL); continue; } - bc_trie_t *e = blogc_source_parse_from_file(tmp->data, &err); + bc_trie_t *e = blogc_source_parse_from_file(config, tmp->data, &err); if (err != NULL) { bc_error_print(err, "blogc"); rv = 1; diff --git a/src/blogc/source-parser.c b/src/blogc/source-parser.c index 18cf95a..13df9e3 100644 --- a/src/blogc/source-parser.c +++ b/src/blogc/source-parser.c @@ -1,6 +1,6 @@ /* * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins + * Copyright (C) 2014-2020 Rafael G. Martins * * This program can be distributed under the terms of the BSD License. * See the file LICENSE. @@ -11,6 +11,7 @@ #include "content-parser.h" #include "source-parser.h" +#include "toctree.h" #include "../common/error.h" #include "../common/utils.h" @@ -27,7 +28,8 @@ typedef enum { bc_trie_t* -blogc_source_parse(const char *src, size_t src_len, bc_error_t **err) +blogc_source_parse(const char *src, size_t src_len, int toctree_maxdepth, + bc_error_t **err) { if (err == NULL || *err != NULL) return NULL; @@ -153,8 +155,11 @@ blogc_source_parse(const char *src, size_t src_len, bc_error_t **err) bc_trie_insert(rv, "RAW_CONTENT", tmp); char *first_header = NULL; char *description = NULL; + char *endl = NULL; + bc_slist_t *headers = NULL; + bool read_headers = (NULL == bc_trie_lookup(rv, "TOCTREE")); content = blogc_content_parse(tmp, &end_excerpt, - &first_header, &description); + &first_header, &description, &endl, read_headers ? &headers : NULL); if (first_header != NULL) { // do not override source-provided first_header. if (NULL == bc_trie_lookup(rv, "FIRST_HEADER")) { @@ -177,6 +182,31 @@ blogc_source_parse(const char *src, size_t src_len, bc_error_t **err) free(description); } } + if (headers != NULL) { + // we already validated that the user do not defined TOCTREE + // manually in source file. + const char *maxdepth = bc_trie_lookup(rv, "TOCTREE_MAXDEPTH"); + if (maxdepth != NULL) { + char *endptr; + toctree_maxdepth = strtol(maxdepth, &endptr, 10); + if (*maxdepth != '\0' && *endptr != '\0') { + *err = bc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len, + current, + "Invalid value for 'TOCTREE_MAXDEPTH' variable: %s.", + maxdepth); + blogc_toctree_free(headers); + free(endl); + free(content); + break; + } + } + char *toctree = blogc_toctree_render(headers, toctree_maxdepth, endl); + blogc_toctree_free(headers); + if (toctree != NULL) { + bc_trie_insert(rv, "TOCTREE", toctree); + } + } + free(endl); bc_trie_insert(rv, "CONTENT", content); bc_trie_insert(rv, "EXCERPT", end_excerpt == 0 ? bc_strdup(content) : bc_strndup(content, end_excerpt)); diff --git a/src/blogc/source-parser.h b/src/blogc/source-parser.h index 8672fb0..2acd753 100644 --- a/src/blogc/source-parser.h +++ b/src/blogc/source-parser.h @@ -1,6 +1,6 @@ /* * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins + * Copyright (C) 2014-2020 Rafael G. Martins * * This program can be distributed under the terms of the BSD License. * See the file LICENSE. @@ -13,7 +13,7 @@ #include "../common/error.h" #include "../common/utils.h" -bc_trie_t* blogc_source_parse(const char *src, size_t src_len, +bc_trie_t* blogc_source_parse(const char *src, size_t src_len, int toctree_maxdepth, bc_error_t **err); #endif /* _SOURCE_PARSER_H */ diff --git a/src/blogc/toctree.c b/src/blogc/toctree.c new file mode 100644 index 0000000..307c62c --- /dev/null +++ b/src/blogc/toctree.c @@ -0,0 +1,103 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2014-2020 Rafael G. Martins + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#include +#include "../common/utils.h" +#include "toctree.h" + +bc_slist_t* +blogc_toctree_append(bc_slist_t *headers, size_t level, const char *slug, const char *text) +{ + if (level == 0) + return headers; + + blogc_toctree_header_t *t = bc_malloc(sizeof(blogc_toctree_header_t)); + t->level = level; + t->slug = bc_strdup(slug); + t->text = bc_strdup(text); + return bc_slist_append(headers, t); +} + + +char* +blogc_toctree_render(bc_slist_t *headers, int maxdepth, const char *endl) +{ + if (headers == NULL || maxdepth == 0) + return NULL; + + // find lower level + size_t lower_level = 0; + for (bc_slist_t *l = headers; l != NULL; l = l->next) { + size_t lv = ((blogc_toctree_header_t*) l->data)->level; + if (lower_level == 0 || lower_level > lv) { + lower_level = lv; + } + } + + if (lower_level == 0) + return NULL; + + // render + bc_string_t *rv = bc_string_new(); + bc_string_append_printf(rv, "
    %s", endl == NULL ? "\n" : endl); + size_t spacing = 4; + size_t current_level = lower_level; + for (bc_slist_t *l = headers; l != NULL; l = l->next) { + blogc_toctree_header_t *t = l->data; + if (t->level - lower_level >= maxdepth) { + continue; + } + while (current_level > t->level) { + spacing -= 4; + bc_string_append_printf(rv, "%*s
%s", spacing, "", + endl == NULL ? "\n" : endl); + current_level--; + } + while (current_level < t->level) { + bc_string_append_printf(rv, "%*s
    %s", spacing, "", + endl == NULL ? "\n" : endl); + current_level++; + spacing += 4; + } + bc_string_append_printf(rv, "%*s
  • ", spacing, ""); + if (t->slug != NULL) { + bc_string_append_printf(rv, "%s", t->slug, + t->text != NULL ? t->text : ""); + } + else { + bc_string_append(rv, t->text); + } + bc_string_append_printf(rv, "
  • %s", endl == NULL ? "\n" : endl); + } + + // close leftovers + while (current_level >= lower_level) { + spacing -= 4; + bc_string_append_printf(rv, "%*s
%s", spacing, "", + endl == NULL ? "\n" : endl); + current_level--; + } + + return bc_string_free(rv, false); +} + + +static void +free_header(blogc_toctree_header_t *h) +{ + free(h->slug); + free(h->text); + free(h); +} + + +void +blogc_toctree_free(bc_slist_t *l) +{ + bc_slist_free_full(l, (bc_free_func_t) free_header); +} diff --git a/src/blogc/toctree.h b/src/blogc/toctree.h new file mode 100644 index 0000000..460119b --- /dev/null +++ b/src/blogc/toctree.h @@ -0,0 +1,26 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2014-2020 Rafael G. Martins + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#ifndef ___TOCTREE_H +#define ___TOCTREE_H + +#include "../common/utils.h" + +typedef struct { + size_t level; + char *slug; + char *text; +} blogc_toctree_header_t; + +bc_slist_t* blogc_toctree_append(bc_slist_t *headers, size_t level, + const char *slug, const char *text); +char* blogc_toctree_render(bc_slist_t *headers, int maxdepth, + const char *endl); +void blogc_toctree_free(bc_slist_t *l); + +#endif /* ___TOCTREE_H */ -- cgit v1.2.3-18-g5258