Welcome to little lamb

Code » qmdoc » commit c78a96b

Add "man page" mode with 4 possible header lines

author Olivier Brunel
2023-01-06 09:57:24 UTC
committer Olivier Brunel
2023-01-06 10:48:57 UTC
parent c2ad0725827c0c158c2899b20f2c99da9cbaf258

Add "man page" mode with 4 possible header lines

An "header line" is a line starting with "% " on top of a markdown file.
Up to four are supported:
- First is the page's title. It can be the only header line.
- Then is the page's name. This will enable so-called "man page" mode,
  meaning the HTML <title> will be "<page name> - <page title>" whilst
  the page name only will be used in TOC.
  Additionally, new header and footer will be added in the page's
  content. Each will have three text areas: left, middle and right, as
  follows :
    Header: page name, page title, page name
    Footer: page version, page date, page name
  The page version and page date being specified on the third and fourth
  header line. If not specified, page name will be used in place of page
  version, and page title in place of page date.

common.css +2 -2
dark.css +2 -2
light.css +2 -2
main.c +86 -11
struct.css +45 -2

diff --git a/common.css b/common.css
index 5cb09ed..fe0dfb2 100644
--- a/common.css
+++ b/common.css
@@ -92,12 +92,12 @@ main div.box > :first-child::before {
 main div.box > :first-child {
     font-weight: 700;
 }
-main footer {
+main footer.page {
     margin-top: 16px;
     opacity: 0.8;
     font-size: 80%;
 }
-main footer .generated {
+main footer.page .generated {
     opacity: 0.8;
     font-size: 90%;
 }
diff --git a/dark.css b/dark.css
index 5b8cdf5..787cdcf 100644
--- a/dark.css
+++ b/dark.css
@@ -40,7 +40,7 @@
     main section > ul.toc > li > ul > li > ul > li > ul > li > ul > li > ul > li > ul > li > a {
         color: #0f0b37;
     }
-    main, main footer {
+    main, main footer.page {
         background: #80732d;
     }
     main {
@@ -110,7 +110,7 @@
         color: inherit;
         box-shadow: inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 #232323;
     }
-    main footer {
+    main footer.page {
         border-top: 2px solid #515151;
     }
     main pre code {
diff --git a/light.css b/light.css
index d3a12c5..480dde8 100644
--- a/light.css
+++ b/light.css
@@ -41,7 +41,7 @@ main section > ul.toc > li > ul > li > ul > li > ul > li > ul > li > ul > li > u
 main section > ul.toc a:hover {
     color: blue;
 }
-main, main footer {
+main, main footer.page {
     background: #fbf4ce;
 }
 main {
@@ -111,6 +111,6 @@ main #navbuttons a {
     color: inherit;
     box-shadow: inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 #232323;
 }
-main footer {
+main footer.page {
     border-top: 1px solid #979518;
 }
diff --git a/main.c b/main.c
index f7051ce..d72439f 100644
--- a/main.c
+++ b/main.c
@@ -55,6 +55,9 @@ struct page {
     const char *sce;
     size_t fileoff;
     size_t titleoff;
+    size_t nameoff;
+    size_t veroff;
+    size_t dateoff;
     int fd;
     size_t size;
 };
@@ -307,7 +310,10 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_)
     switch (type) {
         case MD_BLOCK_DOC:
             {
-#define str_title(i)    ctx->sa.s + ctx->pages[i].titleoff
+                struct page *p = &ctx->pages[ctx->cur_page];
+
+#define offset(i)       ((ctx->pages[i].nameoff) ? ctx->pages[i].nameoff : ctx->pages[i].titleoff)
+#define str_title(i)    ctx->sa.s + offset(i)
 #define str_file(i)    ctx->sa.s + ctx->pages[i].fileoff
                 size_t authlen = strlen(ctx->doc.author);
                 if (!raw_str(ctx, "<!DOCTYPE html>\n<html lang=\"")
@@ -317,9 +323,16 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_)
                         || (authlen && (!raw_str(ctx, "<meta name=\"author\" content=\"")
                                         || !escape_text(ctx, ctx->doc.author, authlen)
                                         || !raw_str(ctx, "\">")))
-                        || !raw_str(ctx, "<title>")
-                        || !escape_text(ctx, str_title(ctx->cur_page),
-                                        strlen(str_title(ctx->cur_page)))
+                        || !raw_str(ctx, "<title>"))
+                    return ERR_PARSER_ENTER_BLOCK;
+                if (p->nameoff) {
+                    if (!escape_text(ctx, ctx->sa.s + p->nameoff,
+                                strlen(ctx->sa.s + p->nameoff))
+                            || !raw_str(ctx, " - "))
+                        return ERR_PARSER_ENTER_BLOCK;
+                }
+                if (!escape_text(ctx, ctx->sa.s + p->titleoff,
+                            strlen(ctx->sa.s + p->titleoff))
                         || !raw_str(ctx, "</title>"))
                     return ERR_PARSER_ENTER_BLOCK;
                 if (ctx->options & OPT_INLINE_CSS) {
@@ -408,6 +421,20 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_)
 
                 if (!raw_str(ctx, "<section>"))
                     return ERR_PARSER_ENTER_BLOCK;
+
+                if (p->nameoff) {
+                    if (!raw_str(ctx, "<header><div>")
+                            || !escape_text(ctx, ctx->sa.s + p->nameoff,
+                                            strlen(ctx->sa.s + p->nameoff))
+                            || !raw_str(ctx, "</div><div>")
+                            || !escape_text(ctx, ctx->sa.s + p->titleoff,
+                                            strlen(ctx->sa.s + p->titleoff))
+                            || !raw_str(ctx, "</div><div>")
+                            || !escape_text(ctx, ctx->sa.s + p->nameoff,
+                                            strlen(ctx->sa.s + p->nameoff))
+                            || !raw_str(ctx, "</div></header>"))
+                        return ERR_PARSER_ENTER_BLOCK;
+                }
 #undef str_title
 #undef str_file
             }
@@ -575,6 +602,8 @@ leave_block(MD_BLOCKTYPE type, void *details, void *ctx_)
     switch (type) {
         case MD_BLOCK_DOC:
             ;
+            struct page *p = &ctx->pages[ctx->cur_page];
+
             char year[UINT32_FMT];
             struct timespec ts;
             clock_gettime(CLOCK_REALTIME, &ts);
@@ -583,6 +612,19 @@ leave_block(MD_BLOCKTYPE type, void *details, void *ctx_)
             year[uint32_fmt(year, (uint32) 1900 + tm.tm_year)] = '\0';
             if ((ctx->doc.flags & DOC_HAS_TITLE) && !raw_str(ctx, "</section>"))
                 return ERR_PARSER_LEAVE_BLOCK;
+            if (p->nameoff) {
+                if (!raw_str(ctx, "<footer><div>")
+                        || !escape_text(ctx, ctx->sa.s + p->veroff,
+                                        strlen(ctx->sa.s + p->veroff))
+                        || !raw_str(ctx, "</div><div>")
+                        || !escape_text(ctx, ctx->sa.s + p->dateoff,
+                                        strlen(ctx->sa.s + p->dateoff))
+                        || !raw_str(ctx, "</div><div>")
+                        || !escape_text(ctx, ctx->sa.s + p->nameoff,
+                                        strlen(ctx->sa.s + p->nameoff))
+                        || !raw_str(ctx, "</div></footer>"))
+                    return ERR_PARSER_ENTER_BLOCK;
+            }
             if (ctx->options & OPT_BUTTONS) {
 #define str_title(i)    ctx->sa.s + ctx->pages[i].titleoff
 #define str_file(i)    ctx->sa.s + ctx->pages[i].fileoff
@@ -611,7 +653,7 @@ leave_block(MD_BLOCKTYPE type, void *details, void *ctx_)
 #undef str_file
 #undef str_title
             }
-            if (!raw_str(ctx, "<footer>Copyright &copy; ")
+            if (!raw_str(ctx, "<footer class=\"page\">Copyright &copy; ")
                     || !raw_str(ctx, year)
                     || !raw_str(ctx, " ")
                     || !escape_text(ctx, ctx->doc.author, strlen(ctx->doc.author))
@@ -1091,6 +1133,14 @@ convert_page(struct ctx *ctx, int fddest)
     return 0;
 }
 
+static int
+empty(const char *s)
+{
+    for ( ; *s; ++s)
+        if (*s != ' ' && *s != '\t') return 0;
+    return 1;
+}
+
 static int
 load_page_from_file(const char *file, struct page *page, stralloc *sa)
 {
@@ -1124,17 +1174,19 @@ load_page_from_file(const char *file, struct page *page, stralloc *sa)
         if (begin) is_hdr = (b[0] == '%' && b[1] == ' ');
         if (begin && !is_hdr)
             break;
+        size_t *offset = NULL;
         switch (line) {
             case 1:
-                if (begin && is_hdr) page->titleoff = sa->len;
-                if (is_hdr
-                        && (!stralloc_catb(sa, b + ((is_hdr) ? 2 : 0),
-                                           ((e) ? e - b : left) - ((is_hdr) ? 2 : 0))
-                            || (e && !stralloc_0(sa))))
-                    retwusys(ERR_MEM, "load page title from '", file, "'");
+                offset = &page->titleoff;
                 break;
             case 2:
+                offset = &page->nameoff;
+                break;
             case 3:
+                offset = &page->veroff;
+                break;
+            case 4:
+                offset = &page->dateoff;
                 break;
             default:
                 if (begin && is_hdr && !warned) {
@@ -1142,6 +1194,14 @@ load_page_from_file(const char *file, struct page *page, stralloc *sa)
                     warned = 1;
                 }
         }
+        if (offset) {
+            if (begin && is_hdr) *offset = sa->len;
+            if (is_hdr
+                    && (!stralloc_catb(sa, b + ((is_hdr) ? 2 : 0),
+                                       ((e) ? e - b : left) - ((is_hdr) ? 2 : 0))
+                        || (e && !stralloc_0(sa))))
+                retwusys(ERR_MEM, "load page title from '", file, "'");
+        }
         if (e) {
             int l = e - b + 1;
             done += l;
@@ -1161,6 +1221,19 @@ load_page_from_file(const char *file, struct page *page, stralloc *sa)
         }
     }
 
+    if (empty(sa->s + page->titleoff))
+        page->titleoff = page->fileoff;
+
+    if (page->nameoff && empty(sa->s + page->nameoff))
+        page->nameoff = 0;
+
+    if (page->nameoff) {
+        if (!page->veroff || empty(sa->s + page->veroff))
+            page->veroff = page->nameoff;
+        if (!page->dateoff || empty(sa->s + page->dateoff))
+            page->dateoff = page->titleoff;
+    }
+
     page->size = lseek(page->fd, 0, SEEK_END);
     if (page->size == (off_t) -1 || lseek(page->fd, done, SEEK_SET) < 0)
         retwusys(ERR_MEM, "seek into '", file, "'");
@@ -1305,6 +1378,7 @@ main (int argc, char *argv[])
     struct page pages[argc - optind + 1];
     ctx.pages = pages;
     ctx.nb_pages = sizeof(pages) / sizeof(*pages) - 1;
+    memset(pages, 0, ctx.nb_pages * sizeof(*pages));
 
     int err = 0;
     int idx_page = -1;
@@ -1354,6 +1428,7 @@ main (int argc, char *argv[])
             /* fd == -1 means use index_md instead of reading from fd */
             pages[0].fd = -1;
             pages[0].size = strlen(index_md);
+            pages[0].nameoff = pages[0].veroff = pages[0].dateoff = 0;
 
             ++ctx.nb_pages;
         } else if (idx_page > 0) { /* move index's page to first */
diff --git a/struct.css b/struct.css
index c31a5a7..a3d38e9 100644
--- a/struct.css
+++ b/struct.css
@@ -25,6 +25,32 @@ main header {
 main > section {
     margin-left: 300px;
 }
+main > section > header {
+    width: 100%;
+}
+main > section > header > div,
+main > section > footer > div {
+    width: 100%;
+    position: absolute;
+}
+main > section > header > div + div,
+main > section > footer > div + div {
+    text-align: center;
+    width: 100%;
+    position: absolute;
+}
+main > section > header > div + div + div,
+main > section > footer > div + div + div {
+    text-align: right;
+    width: 100%;
+    position: absolute;
+}
+main > section > header {
+    margin-bottom: 23px;
+}
+main > section > footer {
+    margin-top: 23px;
+}
 @supports (display: grid) {
     main {
         display: grid;
@@ -41,6 +67,20 @@ main > section {
     main > section {
         margin-left: inherit;
     }
+    main > section > header, main > section > footer {
+        display: grid;
+        grid-template-areas: "left middle right";
+        grid-template-columns: 1fr 2fr 1fr;
+    }
+    main > section > header > div,
+    main > section > footer > div,
+    main > section > header > div + div,
+    main > section > footer > div + div,
+    main > section > header > div + div + div,
+    main > section > footer > div + div + div {
+        width: inherit;
+        position: inherit;
+    }
 }
 main header section {
     position: sticky;
@@ -172,7 +212,7 @@ main pre.lineno + pre {
 }
 main pre.lineno + pre > span {
     position: absolute;
-    left: 55px;
+    margin-left: -8px;
     margin-top: -2px;
     padding: 0 23px;
     font-weight: 600;
@@ -277,7 +317,10 @@ main #navbuttons a.next {
 main #navbuttons a.next::after {
     content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAJFBMVEUAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEC4lvDfAAAAC3RSTlMAx711ZjlFPh3zLASjkrYAAABDSURBVAjXY6AUsGhvcgAzOKR3797YAGIx7t5mvVsAxPLevZ159xYQS3v3zgLrTSDW7tTQBcy7ESyELEIHwhSEyQjbAAH1HsMY8tCHAAAAAElFTkSuQmCC);
 }
-main footer {
+main footer.page {
+    display: inherit;
+}
+main footer.page {
     padding: 8px 23px;
     margin: 0 -23px;
 }