scdoc2mdoc

A fork of scdoc to output mdoc(7)
git clone git://git.sgregoratto.me/scdoc2mdoc
Log | Files | Refs | README | LICENSE

commit e5dec0cbd0b022fbe020c374a9f1f8d649657437
parent 8ed8b6bd77d02c94a37dd7bca9f788c86596380f
Author: Drew DeVault <sir@cmpwn.com>
Date:   Thu, 10 May 2018 22:48:27 -0400

Implement tables

Diffstat:
MMakefile | 2+-
Minclude/util.h | 2++
Mscdoc.1.scd | 39+++++++++++++++++++++++++++++++++++++++
Msrc/main.c | 190+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/util.c | 12++++++++++++
5 files changed, 244 insertions(+), 1 deletion(-)

diff --git a/Makefile b/Makefile @@ -1,5 +1,5 @@ VERSION=1.2.3 -CFLAGS=-DVERSION='"$(VERSION)"' -Wall -Wextra -Werror -Wno-unused-parameter +CFLAGS=-g -DVERSION='"$(VERSION)"' -Wall -Wextra -Werror -Wno-unused-parameter LDFLAGS=-static INCLUDE=-Iinclude PREFIX=/usr/local diff --git a/include/util.h b/include/util.h @@ -10,6 +10,7 @@ struct parser { int qhead; uint32_t queue[32]; uint32_t flags; + const char *str; }; enum formatting { @@ -21,6 +22,7 @@ enum formatting { void parser_fatal(struct parser *parser, const char *err); uint32_t parser_getch(struct parser *parser); void parser_pushch(struct parser *parser, uint32_t ch); +void parser_pushstr(struct parser *parser, const char *str); int roff_macro(struct parser *p, char *cmd, ...); #endif diff --git a/scdoc.1.scd b/scdoc.1.scd @@ -112,6 +112,45 @@ of dashes (-), like so: . Item 3, with multiple lines +## TABLES + +To begin a table, add an empty line followed by any number of rows. + +Each line of a table should start with | or : to start a new row or column +respectively, followed by [ or - or ] to align the contents to the left, +center, or right, followed by a space and the contents of that cell. You may +use a space instead of an alignment specifier to inherit the alignment of the +same column in the previous row. + +The first character of the first row is not limited to | and has special +meaning. [ will produce a table with borders around each cell. | will produce a +table with no borders. ] will produce a table with one border around the whole +table. + +To conclude your table, add an empty line after the last row. + +``` +[[ *Foo* +:- _Bar_ +:- _Baz_ +| *Row 1* +: Hello +:] world! +| *Row 2* +: こんにちは +: 世界 +``` + +[[ *Foo* +:- _Bar_ +:- _Baz_ +| *Row 1* +: Hello +:] world! +| *Row 2* +: こんにちは +: 世界 + ## LITERAL TEXT You may turn off scdoc formatting and output literal text with escape codes and diff --git a/src/main.c b/src/main.c @@ -9,6 +9,8 @@ #include "unicode.h" #include "util.h" +char *strstr(const char *haystack, const char *needle); + static int parse_section(struct parser *p) { str_t *section = str_create(); uint32_t ch; @@ -300,6 +302,186 @@ static void parse_literal(struct parser *p, int *indent) { } while (ch != UTF8_INVALID); } +enum table_align { + ALIGN_LEFT, + ALIGN_CENTER, + ALIGN_RIGHT, +}; + +struct table_row { + struct table_cell *cell; + struct table_row *next; +}; + +struct table_cell { + enum table_align align; + str_t *contents; + struct table_cell *next; +}; + +static void parse_table(struct parser *p, uint32_t style) { + struct table_row *table = NULL; + struct table_row *currow = NULL, *prevrow = NULL; + struct table_cell *curcell = NULL; + int column = 0; + uint32_t ch; + parser_pushch(p, '|'); + + do { + if ((ch = parser_getch(p)) == UTF8_INVALID) { + break; + } + switch (ch) { + case '\n': + goto commit_table; + case '|': + prevrow = currow; + currow = calloc(1, sizeof(struct table_row)); + if (prevrow) { + // TODO: Verify the number of columns match + prevrow->next = currow; + } + curcell = calloc(1, sizeof(struct table_cell)); + currow->cell = curcell; + column = 0; + if (!table) { + table = currow; + } + break; + case ':': + if (!currow) { + parser_fatal(p, "Cannot start a column without " + "starting a row first"); + } else { + struct table_cell *prev = curcell; + curcell = calloc(1, sizeof(struct table_cell)); + if (prev) { + prev->next = curcell; + } + ++column; + } + break; + default: + parser_fatal(p, "Expected either '|' or ':'"); + break; + } + if ((ch = parser_getch(p)) == UTF8_INVALID) { + break; + } + switch (ch) { + case '[': + curcell->align = ALIGN_LEFT; + break; + case '-': + curcell->align = ALIGN_CENTER; + break; + case ']': + curcell->align = ALIGN_RIGHT; + break; + case ' ': + if (prevrow) { + struct table_cell *pcell = prevrow->cell; + for (int i = 0; i <= column && pcell; ++i, pcell = pcell->next) { + if (i == column) { + curcell->align = pcell->align; + break; + } + } + } else { + parser_fatal(p, "No previous row to infer alignment from"); + } + break; + default: + parser_fatal(p, "Expected one of '[', '-', ']', or ' '"); + break; + } + if ((ch = parser_getch(p)) != ' ') { + parser_fatal(p, "Expected ' '"); + break; + } + // Read out remainder of the text + curcell->contents = str_create(); + while ((ch = parser_getch(p)) != UTF8_INVALID) { + switch (ch) { + case '\n': + goto commit_cell; + default: + assert(str_append_ch(curcell->contents, ch) != -1); + break; + } + } +commit_cell: + if (strstr(curcell->contents->str, "T{") + || strstr(curcell->contents->str, "T}")) { + parser_fatal(p, "Cells cannot contain T{ or T} " + "due to roff limitations"); + } + } while (ch != UTF8_INVALID); +commit_table: + + if (ch == UTF8_INVALID) { + return; + } + + roff_macro(p, "TS", NULL); + + const char *_style = NULL; + switch (style) { + case '[': + _style = "allbox "; + break; + case '|': + _style = ""; + break; + case ']': + _style = "box "; + break; + } + + fprintf(p->output, "%s tab(:);\n", _style); + + // Print alignments first + currow = table; + while (currow) { + curcell = currow->cell; + while (curcell) { + fprintf(p->output, "%c%s", "lcr"[curcell->align], + curcell->next ? " " : ""); + curcell = curcell->next; + } + fprintf(p->output, "%s\n", currow->next ? "" : "."); + currow = currow->next; + } + + // Then contents + currow = table; + while (currow) { + curcell = currow->cell; + fprintf(p->output, "T{\n"); + while (curcell) { + parser_pushstr(p, curcell->contents->str); + parse_text(p); + if (curcell->next) { + fprintf(p->output, "\nT}:T{\n"); + } else { + fprintf(p->output, "\nT}"); + } + struct table_cell *prev = curcell; + curcell = curcell->next; + str_free(prev->contents); + free(prev); + } + fprintf(p->output, "\n"); + struct table_row *prev = currow; + currow = currow->next; + free(prev); + } + + roff_macro(p, "TE", NULL); + roff_macro(p, "sp", "1", NULL); + roff_macro(p, "RE", NULL); +} + static void parse_document(struct parser *p) { uint32_t ch; int indent = 0; @@ -332,6 +514,14 @@ static void parse_document(struct parser *p) { case '`': parse_literal(p, &indent); break; + case '[': + case '|': + case ']': + if (indent != 0) { + parser_fatal(p, "Tables cannot be indented"); + } + parse_table(p, ch); + break; case ' ': parser_fatal(p, "Tabs are required for indentation"); break; diff --git a/src/util.c b/src/util.c @@ -17,6 +17,14 @@ uint32_t parser_getch(struct parser *parser) { if (parser->qhead) { return parser->queue[--parser->qhead]; } + if (parser->str) { + uint32_t ch = utf8_decode(&parser->str); + if (!ch || ch == UTF8_INVALID) { + parser->str = NULL; + return UTF8_INVALID; + } + return ch; + } uint32_t ch = utf8_fgetch(parser->input); if (ch == '\n') { parser->col = 0; @@ -33,6 +41,10 @@ void parser_pushch(struct parser *parser, uint32_t ch) { } } +void parser_pushstr(struct parser *parser, const char *str) { + parser->str = str; +} + int roff_macro(struct parser *p, char *cmd, ...) { FILE *f = p->output; int l = fprintf(f, ".%s", cmd);