diff options
author | Rafael G. Martins <rafael@rafaelmartins.eng.br> | 2020-09-11 01:07:43 +0200 |
---|---|---|
committer | Rafael G. Martins <rafael@rafaelmartins.eng.br> | 2020-09-11 01:15:14 +0200 |
commit | ff8898476685e6b7dff03231275a6a8bc2cbaf17 (patch) | |
tree | afe908628d29fac856bd246f7959c53e95ccce4f /src | |
parent | 15264fdb1062cf27be5c103f6bff5df67a7f78dc (diff) | |
download | blogc-ff8898476685e6b7dff03231275a6a8bc2cbaf17.tar.gz blogc-ff8898476685e6b7dff03231275a6a8bc2cbaf17.tar.bz2 blogc-ff8898476685e6b7dff03231275a6a8bc2cbaf17.zip |
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.
Diffstat (limited to 'src')
-rw-r--r-- | src/blogc/content-parser.c | 34 | ||||
-rw-r--r-- | src/blogc/content-parser.h | 6 | ||||
-rw-r--r-- | src/blogc/loader.c | 20 | ||||
-rw-r--r-- | src/blogc/loader.h | 5 | ||||
-rw-r--r-- | src/blogc/main.c | 2 | ||||
-rw-r--r-- | src/blogc/source-parser.c | 36 | ||||
-rw-r--r-- | src/blogc/source-parser.h | 4 | ||||
-rw-r--r-- | src/blogc/toctree.c | 103 | ||||
-rw-r--r-- | src/blogc/toctree.h | 26 |
9 files changed, 217 insertions, 19 deletions
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 <rafael@rafaelmartins.eng.br> + * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> * * This program can be distributed under the terms of the BSD License. * See the file LICENSE. @@ -11,6 +11,7 @@ #include <string.h> #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, "<h%d>%s</h%d>%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, "<blockquote>%s</blockquote>%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 <rafael@rafaelmartins.eng.br> + * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> * * This program can be distributed under the terms of the BSD License. * See the file LICENSE. @@ -11,6 +11,7 @@ #include <stddef.h> #include <stdbool.h> +#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 <rafael@rafaelmartins.eng.br> + * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> * * 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 <rafael@rafaelmartins.eng.br> + * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> * * 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 <rafael@rafaelmartins.eng.br> + * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> * * 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 <rafael@rafaelmartins.eng.br> + * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> * * 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 <rafael@rafaelmartins.eng.br> + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#include <stdlib.h> +#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, "<ul>%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</ul>%s", spacing, "", + endl == NULL ? "\n" : endl); + current_level--; + } + while (current_level < t->level) { + bc_string_append_printf(rv, "%*s<ul>%s", spacing, "", + endl == NULL ? "\n" : endl); + current_level++; + spacing += 4; + } + bc_string_append_printf(rv, "%*s<li>", spacing, ""); + if (t->slug != NULL) { + bc_string_append_printf(rv, "<a href=\"#%s\">%s</a>", t->slug, + t->text != NULL ? t->text : ""); + } + else { + bc_string_append(rv, t->text); + } + bc_string_append_printf(rv, "</li>%s", endl == NULL ? "\n" : endl); + } + + // close leftovers + while (current_level >= lower_level) { + spacing -= 4; + bc_string_append_printf(rv, "%*s</ul>%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 <rafael@rafaelmartins.eng.br> + * + * 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 */ |