slweb

Једноставни генератор статичких веб страна
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

чување 3cf0fb3bd7df67df893cd16841c624cb02e9283a
родитељ c8fd99c3b8ab2205d6c847f9b367ef6cf58dd729
Аутор: Страхиња Радић <contact@strahinja.org>
Датум:   Sat,  2 Oct 2021 00:48:49 +0200

Add {tsv} directive; change meta to read TSV instead of CSV; remove extra EOL at the end of files

Signed-off-by: Страхиња Радић <contact@strahinja.org>

Diffstat:
MTODO | 2+-
Mdefs.h | 29++++++++++++++++-------------
Mexamples/all.do | 1-
Mexamples/basic/all.do | 1-
Mexamples/blockquote/all.do | 1-
Mexamples/blockquote/index.slw | 1-
Mexamples/blockquote/style.css | 1-
Aexamples/csv-tsv/all.do | 1+
Aexamples/csv-tsv/index-csv.slw | 10++++++++++
Aexamples/csv-tsv/index.slw | 10++++++++++
Aexamples/csv-tsv/sales.csv | 3+++
Aexamples/csv-tsv/sales.tsv | 3+++
Dexamples/csv/all.do | 2--
Dexamples/csv/index.slw | 11-----------
Dexamples/csv/sales.csv | 4----
Mexamples/footnotes/all.do | 1-
Mexamples/footnotes/index.slw | 1-
Mexamples/footnotes/inline.slw | 1-
Mexamples/footnotes/warning.slw | 1-
Mexamples/includes/all.do | 1-
Mexamples/includes/bugs.slw | 1-
Mexamples/includes/description.slw | 1-
Mexamples/includes/inc/copy.slw | 1-
Mexamples/includes/inc/footer.slw | 1-
Mexamples/includes/inc/header.slw | 1-
Mexamples/includes/index.css | 1-
Mexamples/includes/index.slw | 1-
Mexamples/includes/options.slw | 1-
Mexamples/includes/reference.slw | 1-
Mexamples/includes/seealso.slw | 1-
Mexamples/links/all.do | 1-
Mexamples/links/index.slw | 1-
Mexamples/links/style.css | 1-
Mexamples/macros/all.do | 1-
Mexamples/macros/index.slw | 1-
Mexamples/macros/style.css | 1-
Mexamples/math/all.do | 1-
Mexamples/math/index.slw | 1-
Mexamples/tables/all.do | 1-
Dexamples/tables/csv.slw | 28----------------------------
Mexamples/tables/index.slw | 3+--
Mexamples/tables/standard.slw | 1-
Mexamples/tables/tables.css | 1-
Aexamples/tables/tsv.slw | 27+++++++++++++++++++++++++++
Mexamples/tags/all.do | 1-
Mexamples/tags/index.slw | 1-
Dindex-meta.csv | 16----------------
Aindex-meta.tsv | 16++++++++++++++++
Mindex.slw | 2+-
Mslweb.1.in | 207++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mslweb.c | 395++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
измењених датотека: 51, додавања: 565(+), брисања: 237(-)

diff --git a/TODO b/TODO @@ -7,7 +7,7 @@ [ ] Investigate & in headings - [ ] Add {tsv} directive + [x] Add {tsv} directive [x] Change charset="utf8" to charset="utf-8" (in documentation) diff --git a/defs.h b/defs.h @@ -79,6 +79,7 @@ typedef struct } KeyValue; typedef int (*csv_callback_t)(FILE* output, u8** csv_header, u8** csv_register); +typedef int (*tsv_callback_t)(FILE* output, u8** tsv_header, u8** tsv_register); #ifdef __GNUC__ #pragma GCC diagnostic push @@ -86,6 +87,7 @@ typedef int (*csv_callback_t)(FILE* output, u8** csv_header, u8** csv_register); #endif #define MAX_HEADING_LEVEL 4 #define MAX_CSV_REGISTERS 9 +#define MAX_TSV_REGISTERS 9 #define ST_NONE 0 #define ST_YAML 1 @@ -108,19 +110,20 @@ typedef int (*csv_callback_t)(FILE* output, u8** csv_header, u8** csv_register); #define ST_IMAGE_SECOND_ARG ((ULLONG)1 << 17) #define ST_MACRO_BODY ((ULLONG)1 << 18) #define ST_CSV_BODY ((ULLONG)1 << 19) -#define ST_HTML_TAG ((ULLONG)1 << 20) -#define ST_KBD ((ULLONG)1 << 21) -#define ST_LIST ((ULLONG)1 << 22) -#define ST_NUMLIST ((ULLONG)1 << 23) -#define ST_FOOTNOTE ((ULLONG)1 << 24) -#define ST_FOOTNOTE_TEXT ((ULLONG)1 << 25) -#define ST_INLINE_FOOTNOTE ((ULLONG)1 << 26) -#define ST_FORMULA ((ULLONG)1 << 27) -#define ST_DISPLAY_FORMULA ((ULLONG)1 << 28) -#define ST_TABLE_HEADER ((ULLONG)1 << 29) -#define ST_TABLE_LINE ((ULLONG)1 << 30) -#define ST_TABLE ((ULLONG)1 << 31) -#define ST_STRIKE ((ULLONG)1 << 32) +#define ST_TSV_BODY ((ULLONG)1 << 20) +#define ST_HTML_TAG ((ULLONG)1 << 21) +#define ST_KBD ((ULLONG)1 << 22) +#define ST_LIST ((ULLONG)1 << 23) +#define ST_NUMLIST ((ULLONG)1 << 24) +#define ST_FOOTNOTE ((ULLONG)1 << 25) +#define ST_FOOTNOTE_TEXT ((ULLONG)1 << 26) +#define ST_INLINE_FOOTNOTE ((ULLONG)1 << 27) +#define ST_FORMULA ((ULLONG)1 << 28) +#define ST_DISPLAY_FORMULA ((ULLONG)1 << 29) +#define ST_TABLE_HEADER ((ULLONG)1 << 30) +#define ST_TABLE_LINE ((ULLONG)1 << 31) +#define ST_TABLE ((ULLONG)1 << 32) +#define ST_STRIKE ((ULLONG)1 << 33) #define ST_CS_NONE 0 #define ST_CS_HEADER 1 diff --git a/examples/all.do b/examples/all.do @@ -1,4 +1,3 @@ for d in *; do [ -d $d ] && echo $d/all done | xargs redo-ifchange - diff --git a/examples/basic/all.do b/examples/basic/all.do @@ -1,2 +1 @@ redo-ifchange index.html - diff --git a/examples/blockquote/all.do b/examples/blockquote/all.do @@ -1,2 +1 @@ redo-ifchange index.html - diff --git a/examples/blockquote/index.slw b/examples/blockquote/index.slw @@ -11,4 +11,3 @@ This is a text with lines in-between: > This is a quote And another line. - diff --git a/examples/blockquote/style.css b/examples/blockquote/style.css @@ -12,4 +12,3 @@ blockquote margin: 0; padding: .5em; } - diff --git a/examples/csv-tsv/all.do b/examples/csv-tsv/all.do @@ -0,0 +1 @@ +redo-ifchange index.html index-csv.html diff --git a/examples/csv-tsv/index-csv.slw b/examples/csv-tsv/index-csv.slw @@ -0,0 +1,10 @@ +{csv "sales"} +{.sales-segment} +$#1 {q}$1{/q} sales by quarter for the last year in +thousands of units sold (for $$) were as follows: +$?2{.q1}$2{/.q1}$?!{.q1 na}N/A{/.q1}$?/ +$?3{.q2}$3{/.q2}$?!{.q2 na}N/A{/.q2}$?/ +$?4{.q3}$4{/.q3}$?!{.q3 na}N/A{/.q3}$?/ +$?5{.q4}$5{/.q4}$?!{.q4 na}N/A{/.q4}$?/ +{/.sales-segment} +{/csv} diff --git a/examples/csv-tsv/index.slw b/examples/csv-tsv/index.slw @@ -0,0 +1,10 @@ +{tsv "sales"} +{.sales-segment} +$#1 {q}$1{/q} sales by quarter for the last year in +thousands of units sold (for $$) were as follows: +$?2{.q1}$2{/.q1}$?!{.q1 na}N/A{/.q1}$?/ +$?3{.q2}$3{/.q2}$?!{.q2 na}N/A{/.q2}$?/ +$?4{.q3}$4{/.q3}$?!{.q3 na}N/A{/.q3}$?/ +$?5{.q4}$5{/.q4}$?!{.q4 na}N/A{/.q4}$?/ +{/.sales-segment} +{/tsv} diff --git a/examples/csv-tsv/sales.csv b/examples/csv-tsv/sales.csv @@ -0,0 +1,3 @@ +"Item","Q1 2020","Q2 2020","Q3 2020","Q4 2020" +"Toothpick","15.2","12","","10" +"Teapot","1.2","","5","3.5" diff --git a/examples/csv-tsv/sales.tsv b/examples/csv-tsv/sales.tsv @@ -0,0 +1,3 @@ +Item Q1 2020 Q2 2020 Q3 2020 Q4 2020 +Toothpick 15.2 12 10 +Teapot 1.2 5 3.5 diff --git a/examples/csv/all.do b/examples/csv/all.do @@ -1,2 +0,0 @@ -redo-ifchange index.html - diff --git a/examples/csv/index.slw b/examples/csv/index.slw @@ -1,11 +0,0 @@ -{csv "sales"} -{.sales-segment} -$#1 {q}$1{/q} sales by quarter for the last year in -thousands of units sold (for $$) were as follows: -$?2{.q1}$2{/.q1}$?!{.q1 na}N/A{/.q1}$?/ -$?3{.q2}$3{/.q2}$?!{.q2 na}N/A{/.q2}$?/ -$?4{.q3}$4{/.q3}$?!{.q3 na}N/A{/.q3}$?/ -$?5{.q4}$5{/.q4}$?!{.q4 na}N/A{/.q4}$?/ -{/.sales-segment} -{/csv} - diff --git a/examples/csv/sales.csv b/examples/csv/sales.csv @@ -1,4 +0,0 @@ -"Item","Q1 2020","Q2 2020","Q3 2020","Q4 2020" -"Toothpick","15.2","12","","10" -"Teapot","1.2","","5","3.5" - diff --git a/examples/footnotes/all.do b/examples/footnotes/all.do @@ -1,2 +1 @@ echo "index.html inline.html" | xargs redo-ifchange - diff --git a/examples/footnotes/index.slw b/examples/footnotes/index.slw @@ -14,4 +14,3 @@ itself[^links]. [^links]: Similar to links. Footnotes themselves can contain **some** `markup`, and can be broken up into multiple lines. - diff --git a/examples/footnotes/inline.slw b/examples/footnotes/inline.slw @@ -9,4 +9,3 @@ Footnotes can also be inline be on a single line.]. Here's another paragraph^[With a footnote.]. - diff --git a/examples/footnotes/warning.slw b/examples/footnotes/warning.slw @@ -12,4 +12,3 @@ but the anchors will be unique, having `inline-` prepended to the _id_ for inline footnotes. [^A]: As evident in this example. - diff --git a/examples/includes/all.do b/examples/includes/all.do @@ -1,2 +1 @@ redo-ifchange index.html - diff --git a/examples/includes/bugs.slw b/examples/includes/bugs.slw @@ -2,4 +2,3 @@ Bugs can be reported using the ticket tracker at: [https://sr.ht/~strahinja/slweb/trackers](https://sr.ht/~strahinja/slweb/trackers) - diff --git a/examples/includes/description.slw b/examples/includes/description.slw @@ -3,4 +3,3 @@ **Slweb** is a static website generator which aims at being simplistic. It uses **sed** (1) to transform a custom Markdown-like syntax into {abbr}HTML{/abbr}. - diff --git a/examples/includes/inc/copy.slw b/examples/includes/inc/copy.slw @@ -1,2 +1 @@ {span}([Link to index](../../index.html), from include) Copyright &copy; _someguy_{/span} - diff --git a/examples/includes/inc/footer.slw b/examples/includes/inc/footer.slw @@ -10,4 +10,3 @@ slweb v0.2.2-beta SLWEB(1) {/.right} {/footer} - diff --git a/examples/includes/inc/header.slw b/examples/includes/inc/header.slw @@ -9,4 +9,3 @@ General Commands Manual SLWEB(1) {/.right} {/header} - diff --git a/examples/includes/index.css b/examples/includes/index.css @@ -5,4 +5,3 @@ footer flex-direction: row; justify-content: space-between; } - diff --git a/examples/includes/index.slw b/examples/includes/index.slw @@ -20,4 +20,3 @@ stylesheet: index.css {/main} {include "inc/footer"} - diff --git a/examples/includes/options.slw b/examples/includes/options.slw @@ -18,4 +18,3 @@ Print this usage information screen. **-v** **--version** Print program version and exit. - diff --git a/examples/includes/reference.slw b/examples/includes/reference.slw @@ -45,4 +45,3 @@ directives. Supported **Markdown features** are: - **Brand &ldquo;watermark&rdquo;.** Directive `\{made-by}` is converted into a div containing the &ldquo;watermark&rdquo; text and a link to the **slweb** home page. - diff --git a/examples/includes/seealso.slw b/examples/includes/seealso.slw @@ -1,4 +1,3 @@ ## SEE ALSO **sed**(1) - diff --git a/examples/links/all.do b/examples/links/all.do @@ -1,2 +1 @@ redo-ifchange index.html - diff --git a/examples/links/index.slw b/examples/links/index.slw @@ -23,4 +23,3 @@ Link without span: [Without span (with parentheses)](https://example.com). [1]: https://strahinja.org [2]: https://materialdesignicons.com [3]: ../../index.html - diff --git a/examples/links/style.css b/examples/links/style.css @@ -51,4 +51,3 @@ a:hover>span::after { width: 100%; } - diff --git a/examples/macros/all.do b/examples/macros/all.do @@ -1,2 +1 @@ redo-ifchange index.html - diff --git a/examples/macros/index.slw b/examples/macros/index.slw @@ -21,4 +21,3 @@ Some more text. {/main} {footer}Footer: {=copyright}{/footer} - diff --git a/examples/macros/style.css b/examples/macros/style.css @@ -9,4 +9,3 @@ footer background-color: #bbf; padding: 1ex; } - diff --git a/examples/math/all.do b/examples/math/all.do @@ -1,2 +1 @@ redo-ifchange index.html - diff --git a/examples/math/index.slw b/examples/math/index.slw @@ -11,4 +11,3 @@ Some more: $$\begin{aligned}(a-b)(a+b) &=a^2-ab+ab-b^2\\ &=a^2-b^2\end{aligned}$$ - diff --git a/examples/tables/all.do b/examples/tables/all.do @@ -1,2 +1 @@ redo-ifchange index.html - diff --git a/examples/tables/csv.slw b/examples/tables/csv.slw @@ -1,28 +0,0 @@ -## Tables and CSV - -CSV can easily be formatted into a table, like this: - -``` -|@\\------|-----|-----|-----|-----| -{csv "../csv/sales" 1} -|@# $#1 | $#2 | $#3 | $#4 | $#5 | -{/csv} -|@-------|-----|-----|-----|-----| -{csv "../csv/sales"} -|@ $1 | $2 | $3 | $4 | $5 | -{/csv} -|@/------|-----|-----|-----|-----| -``` - -which gives: - -|@\------|-----|-----|-----|-----| -{csv "../csv/sales" 1} -|@# $#1 | $#2 | $#3 | $#4 | $#5 | -{/csv} -|@-------|-----|-----|-----|-----| -{csv "../csv/sales"} -|@ $1 | $2 | $3 | $4 | $5 | -{/csv} -|@/------|-----|-----|-----|-----| - diff --git a/examples/tables/index.slw b/examples/tables/index.slw @@ -12,5 +12,4 @@ CSS. {include "standard"} -{include "csv"} - +{include "tsv"} diff --git a/examples/tables/standard.slw b/examples/tables/standard.slw @@ -15,4 +15,3 @@ gives: |----|--------------|-----| | 1 | John Smith | 23 | | 2 | Peter Robson | 34 | - diff --git a/examples/tables/tables.css b/examples/tables/tables.css @@ -8,4 +8,3 @@ td, th { border: 1px solid #000; } - diff --git a/examples/tables/tsv.slw b/examples/tables/tsv.slw @@ -0,0 +1,27 @@ +## Tables and TSV + +TSV can easily be formatted into a table, like this: + +``` +|@\\------|-----|-----|-----|-----| +{tsv "../csv-tsv/sales" 1} +|@# $#1 | $#2 | $#3 | $#4 | $#5 | +{/tsv} +|@-------|-----|-----|-----|-----| +{tsv "../csv-tsv/sales"} +|@ $1 | $2 | $3 | $4 | $5 | +{/tsv} +|@/------|-----|-----|-----|-----| +``` + +which gives: + +|@\------|-----|-----|-----|-----| +{tsv "../csv-tsv/sales" 1} +|@# $#1 | $#2 | $#3 | $#4 | $#5 | +{/tsv} +|@-------|-----|-----|-----|-----| +{tsv "../csv-tsv/sales"} +|@ $1 | $2 | $3 | $4 | $5 | +{/tsv} +|@/------|-----|-----|-----|-----| diff --git a/examples/tags/all.do b/examples/tags/all.do @@ -1,2 +1 @@ redo-ifchange index.html - diff --git a/examples/tags/index.slw b/examples/tags/index.slw @@ -13,4 +13,3 @@ site-name: Tag test {#someid}Some id{/#someid} {.someclass}Some class{/.someclass} {/test} - diff --git a/index-meta.csv b/index-meta.csv @@ -1,16 +0,0 @@ -"Name","Content" -"og:type","website" -"og:url","%canonical%" -"og:title","%site-name%" -"og:description","%site-desc%" -"og:image","%image%" -"og:image:type","image/webp" -"og:image:width","1127" -"og:image:height","492" -"og:image:alt","The text 'slweb' in lowercase italics" -"twitter:card","summary_large_image" -"twitter:url","%canonical%" -"twitter:title","%site-name%" -"twitter:creator","@strahinja_radic" -"twitter:description","%site-desc%" -"twitter:image","%image%" diff --git a/index-meta.tsv b/index-meta.tsv @@ -0,0 +1,16 @@ +Name Content +og:type website +og:url %canonical% +og:title %site-name% +og:description %site-desc% +og:image %image% +og:image:type image/webp +og:image:width 1127 +og:image:height 492 +og:image:alt The text 'slweb' in lowercase italics +twitter:card summary_large_image +twitter:url %canonical% +twitter:title %site-name% +twitter:creator @strahinja_radic +twitter:description %site-desc% +twitter:image %image% diff --git a/index.slw b/index.slw @@ -6,7 +6,7 @@ stylesheet: /slweb/katex.min.css favicon-url: /slweb/favicon.ico image: https://strahinja.srht.site/slweb/logo-social.webp canonical: https://strahinja.srht.site/slweb/ -meta: index-meta.csv +meta: index-meta.tsv --- {main} # slweb diff --git a/slweb.1.in b/slweb.1.in @@ -474,6 +474,8 @@ will be transformed into: .IP "" 4 .B Partial tables are a modification of Markdown tables, allowing them to be combined with the +.SM TSV +or .SM CSV directive. In order for tables to work with templating, they need two additional lines at the top and the bottom, to mark the start and the end of the table. All @@ -482,13 +484,13 @@ character following at-mark being different: . .CDS 8 |@\\------|-----|-----|-----|-----| -{csv "../csv/sales" 1} +{tsv "../tsv/sales" 1} |@# $#1 | $#2 | $#3 | $#4 | $#5 | -{/csv} +{/tsv} |@-------|-----|-----|-----|-----| -{csv "../csv/sales"} +{tsv "../tsv/sales"} |@ $1 | $2 | $3 | $4 | $5 | -{/csv} +{/tsv} |@/------|-----|-----|-----|-----| .CDE . @@ -496,7 +498,7 @@ character following at-mark being different: The rest of the line following the third character in top, bottom and lines separating header from body is ignored by the parser and is included in this example only for aesthetic purposes. See -.BR "CSV templating" . +.BR "TSV/CSV templating" . . .SS Math mode . @@ -526,96 +528,6 @@ containing the \[lq]watermark\[rq] text and a link to the .B slweb home page. . -.IP \[bu] -.BR "CSV templating" . -Directive \fC{csv "\f[CI]csvfile\fC" \f[CI]iter\fC}{/csv}\fR marks a template. -Whatever is between \fC{csv}\fP and \fC{/csv}\fP will be processed by -.B slweb -and collected as a template. Afterwards, file \fIcsvfile.csv\fP will be read and -for each of its lines (up to -.IR iter , -if present) the collected template will be output, substituting each occurrence -of a register mark \fC$\f[CI]n\fR (where \fIn\fP is between 1 and 9, inclusive) -with the corresponding -.SM CSV -field of the read line. -.SM CSV -file needs to use semicolons (;) or commas (,) as delimiters (or set -.B csv-delimiter -as a -.SM YAML -variable in the calling -.I .slw -file) and double quotation marks (") as field boundaries. First line in the -.SM CSV -file is parsed as a header and associated with register marks \fC$#1\fP to -\fC$#9\fP. The symbol $ is represented as \fC$$\fP. Consequently, math modes -(both inline and display) cannot be used within templates. -. -.IP "" 8 -Parts of the template can be conditionally rendered by using the construct: -. -.CDS 8 -$?\f[CI]n\fP - <!-- code to include if $\f[CI]n\f[CR] nonempty --> -$?! - <!-- code to include if $\f[CI]n\f[CR] empty --> -$?/ -.CDE -. -.IP "" 4 -where \fIn\fP is between 1 and 9, inclusive. For example, having a file -\fIsales.csv\fP: -. -.CDS 8 -"Item";"Q1 2020";"Q2 2020";"Q3 2020";"Q4 2020" -"Toothpick";"15.2";"12";"";"10" -"Teapot";"1.2";"";"5";"3.5" -.CDE -. -.IP "" 4 -We can write -. -.CDS 8 -{csv "sales"} -{.sales-segment} -$#1 {q}$1{/q} sales by quarter for the last year in -thousands of units sold (for $$) were as follows: -$?2{.q1}$2{/.q1}$?!{.q1 na}N/A{/.q1}$?/ -$?3{.q2}$3{/.q2}$?!{.q2 na}N/A{/.q2}$?/ -$?4{.q3}$4{/.q3}$?!{.q3 na}N/A{/.q3}$?/ -$?5{.q4}$5{/.q4}$?!{.q4 na}N/A{/.q4}$?/ -{/.sales-segment} -{/csv} -.CDE -. -.IP "" 4 -to get: -. -.CDS 8 -<div class="sales-segment"> -Item <q>Toothpick</q> sales by quarter for the last year in -thousands of units sold (for $) were as follows: -<div class="q1">15.2</div> -<div class="q2">12</div> -<div class="q3 na">N/A</div> -<div class="q4">10</div> -</div> - -<div class="sales-segment"> -Item <q>Teapot</q> sales by quarter for the last year in -thousands of units sold (for $) were as follows: -<div class="q1">1.2</div> -<div class="q2 na">N/A</div> -<div class="q3">5</div> -<div class="q4">3.5</div> -</div> -.CDE -. -.IP "" 4 -.SM CSV -directives can't be nested and doing so will produce an error. -. .IP \[bu] 4 .BR "General tags" . Directive \fC{sometag}{/sometag}\fP will be transformed into @@ -722,6 +634,99 @@ directive. (See .BR samedir-permalink " variables.)" .RE . +.IP \[bu] +.BR "TSV/CSV templating" . +Directive \fC{tsv "\f[CI]tsvfile\fC" \f[CI]iter\fC}{/tsv}\fR marks a template. +Whatever is between \fC{tsv}\fP and \fC{/tsv}\fP will be processed by +.B slweb +and collected as a template. Afterwards, file \fItsvfile.tsv\fP will be read and +for each of its lines (up to +.IR iter , +if present) the collected template will be output, substituting each occurrence +of a register mark \fC$\f[CI]n\fR (where \fIn\fP is between 1 and 9, inclusive) +with the corresponding +.SM TSV +field of the read line. All of this applies to the similar \fC{csv}\fP directive +for +.SM CSV +files. In the case of +.SM CSV +files, they need to use semicolons (;) or commas (,) as delimiters (or set +.B csv-delimiter +as a +.SM YAML +variable in the calling +.I .slw +file) and double quotation marks (") as field boundaries. First line in the +.SM TSV +file is parsed as a header and associated with register marks \fC$#1\fP to +\fC$#9\fP. The symbol $ is represented as \fC$$\fP. Consequently, math modes +(both inline and display) cannot be used within templates. +. +.IP "" 8 +Parts of the template can be conditionally rendered by using the construct: +. +.CDS 8 +$?\f[CI]n\fP + <!-- code to include if $\f[CI]n\f[CR] nonempty --> +$?! + <!-- code to include if $\f[CI]n\f[CR] empty --> +$?/ +.CDE +. +.IP "" 4 +where \fIn\fP is between 1 and 9, inclusive. For example, having a file +\fIsales.tsv\fP: +. +.CDS 8 +Item Q1 2020 Q2 2020 Q3 2020 Q4 2020 +Toothpick 15.2 12 10 +Teapot 1.2 5 3.5 +.CDE +. +.IP "" 4 +We can write +. +.CDS 8 +{tsv "sales"} +{.sales-segment} +$#1 {q}$1{/q} sales by quarter for the last year in +thousands of units sold (for $$) were as follows: +$?2{.q1}$2{/.q1}$?!{.q1 na}N/A{/.q1}$?/ +$?3{.q2}$3{/.q2}$?!{.q2 na}N/A{/.q2}$?/ +$?4{.q3}$4{/.q3}$?!{.q3 na}N/A{/.q3}$?/ +$?5{.q4}$5{/.q4}$?!{.q4 na}N/A{/.q4}$?/ +{/.sales-segment} +{/tsv} +.CDE +. +.IP "" 4 +to get: +. +.CDS 8 +<div class="sales-segment"> +Item <q>Toothpick</q> sales by quarter for the last year in +thousands of units sold (for $) were as follows: +<div class="q1">15.2</div> +<div class="q2">12</div> +<div class="q3 na">N/A</div> +<div class="q4">10</div> +</div> + +<div class="sales-segment"> +Item <q>Teapot</q> sales by quarter for the last year in +thousands of units sold (for $) were as follows: +<div class="q1">1.2</div> +<div class="q2 na">N/A</div> +<div class="q3">5</div> +<div class="q4">3.5</div> +</div> +.CDE +. +.IP "" 4 +.SM TSV +directives can't be nested and doing so will produce an error. +. .SS Special YAML variables . .IP \[bu] 4 @@ -827,9 +832,9 @@ is a complete path. . .IP \[bu] .BR meta . -Contents of this variable is treated as a filename specifying the CSV file +Contents of this variable is treated as a filename specifying the TSV file containing a list of values to be inserted into meta tags. The first row of this -CSV file is treated as the header row and ignored. First column in this CSV file +TSV file is treated as the header row and ignored. First column in this TSV file contains the meta variable names, and the second column contains meta variable values. For each row, a \fC<meta name="\f[CI]firstcol\fC" content="\f[CI]secondcol\fC" />\fR is inserted, where @@ -839,11 +844,11 @@ is the first column, and is the second column. If the values in the second column are surrounded with percent signs (\fC%\fP), the value between them is treated as the name of the YAML variable to insert. For example, having the file -.IR m.csv : +.IR m.tsv : . .CDS 8 -"Name","Content" -"og:url","%canonical%" +Name Content +og:url %canonical% .CDE . .IP "" @@ -852,7 +857,7 @@ and .CDS 8 --- canonical: https://example.com/ -meta: m.csv +meta: m.tsv --- .CDE . diff --git a/slweb.c b/slweb.c @@ -32,6 +32,10 @@ static u8* csv_template = NULL; 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 tsv_template_size = 0; +static char* tsv_filename = NULL; +static long tsv_iter = 0; static ULLONG state = ST_NONE; #define CHECKEXITNOMEM(ptr) \ @@ -310,6 +314,31 @@ print_output(FILE* output, const char* fmt, ...) - 1); } } + 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); + strncpy((char*)tsv_template, buf, BUFSIZE - 1); + *(tsv_template + buf_len) = 0; + } + else + { + if (strlen((char*)tsv_template) + buf_len + > tsv_template_size) + { + tsv_template_size += BUFSIZE; + REALLOC(tsv_template, u8, tsv_template_size); + } + strncat((char*)tsv_template, buf, + tsv_template_size - strlen((char*)tsv_template) + - 1); + } + } else vfprintf(output, fmt, args); va_end(args); @@ -446,13 +475,14 @@ read_file_into_buffer(FILE** input, u8** buffer, size_t* buffer_size, } int read_csv(FILE* output, const char* filename, csv_callback_t callback); +int read_tsv(FILE* output, const char* filename, tsv_callback_t callback); int -print_meta_var(FILE* output, u8** csv_header, u8** csv_register) +print_meta_var(FILE* output, u8** tsv_header, u8** tsv_register) { - if (!*csv_register || !*(csv_register + 1)) + if (!*tsv_register || !*(tsv_register + 1)) return 0; - u8* pvalue = *(csv_register + 1); + u8* pvalue = *(tsv_register + 1); size_t value_len = strlen((char*)pvalue); if (*pvalue == '%' && *(pvalue + value_len - 1) == '%') { @@ -464,13 +494,13 @@ print_meta_var(FILE* output, u8** csv_header, u8** csv_register) if (value) print_output(output, "<meta name=\"%s\" content=\"%s\" />\n", - *csv_register, value); + *tsv_register, value); free(var_name); } else print_output(output, "<meta name=\"%s\" content=\"%s\" />\n", - *csv_register, *(csv_register + 1)); + *tsv_register, *(tsv_register + 1)); return 0; } @@ -526,7 +556,322 @@ process_git_log(FILE* output) return result ? warning(result, (u8*)"git-log: Cannot run git") : 0; } -#define PRINTIF(format, arg) \ +#define PRINTTSVIF(format, arg) \ + do \ + { \ + if ((ALL(tsv_state, ST_CS_COND_NONEMPTY) \ + && *tsv_register[conditional_index - 1]) \ + || (ALL(tsv_state, ST_CS_COND_EMPTY) \ + && !*tsv_register[conditional_index - 1]) \ + || !ANY(tsv_state, \ + ST_CS_COND_EMPTY | ST_CS_COND_NONEMPTY)) \ + { \ + fprintf(output, format, arg); \ + } \ + } while (0) + +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; + +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; + ptsv_template++; + goto tsv_template_start; + } + + switch (*ptsv_template) + { + case '\\': + tsv_state |= ST_CS_ESCAPE; + ptsv_template++; + break; + case '$': + if (IN(tsv_state, ST_CS_REGISTER)) + { + PRINTTSVIF("%c", '$'); + tsv_state &= ~ST_CS_REGISTER; + } + else + tsv_state |= ST_CS_REGISTER; + ptsv_template++; + break; + case '#': + if (IN(tsv_state, ST_CS_HEADER)) + { + error(1, (u8*)"tsv: Invalid header register mark"); + 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; + } + else + PRINTTSVIF("%c", '#'); + ptsv_template++; + break; + case '?': + if (IN(tsv_state, ST_CS_COND)) + { + error(1, (u8*)"tsv: Invalid conditional mark"); + tsv_state &= ~ST_CS_COND; + } + else if (IN(tsv_state, ST_CS_REGISTER)) + { + tsv_state &= ~ST_CS_REGISTER; + tsv_state |= ST_CS_COND; + } + else + PRINTTSVIF("%c", '?'); + ptsv_template++; + break; + 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; + } + else if (IN(tsv_state, ST_CS_COND)) + { + error(1, + (u8*)"Empty conditional before/without nonempty" + " conditional"); + tsv_state &= ~(ST_CS_COND | ST_CS_COND_EMPTY); + } + else + PRINTTSVIF("%c", '!'); + ptsv_template++; + break; + case '/': + if (ALL(tsv_state, ST_CS_COND | ST_CS_COND_EMPTY)) + 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); + else + PRINTTSVIF("%c", '/'); + ptsv_template++; + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (IN(tsv_state, ST_CS_COND)) + { + conditional_index = *ptsv_template - '0'; + tsv_state &= ~ST_CS_COND; + 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; + } + else if (IN(tsv_state, ST_CS_REGISTER)) + { + num = *ptsv_template - '0'; + PRINTTSVIF("%s", tsv_register[num - 1]); + tsv_state &= ~ST_CS_REGISTER; + } + else + PRINTTSVIF("%c", *ptsv_template); + ptsv_template++; + break; + default: + if (IN(tsv_state, ST_CS_HEADER)) + { + error(1, (u8*)"tsv: Invalid header register mark"); + tsv_state &= ~ST_CS_HEADER; + } + else if (IN(tsv_state, ST_CS_REGISTER)) + { + error(1, (u8*)"tsv: Invalid register mark"); + tsv_state &= ~ST_CS_REGISTER; + } + else + PRINTTSVIF("%c", *ptsv_template); + ptsv_template++; + } + goto tsv_template_start; +tsv_template_done: + return 0; +} + +int +read_tsv(FILE* output, const char* filename, tsv_callback_t callback) +{ + if (!callback) + exit(error(EINVAL, (u8*)"read_tsv: Invalid callback argument")); + + FILE* tsv = NULL; + size_t tsv_lineno = 0; + u8* bufline = NULL; + u8* pbufline = NULL; + u8* token = NULL; + u8* ptoken = NULL; + size_t token_size = 0; + u8* tsv_header[MAX_TSV_REGISTERS]; + UBYTE current_header = 0; + u8* tsv_register[MAX_TSV_REGISTERS]; + UBYTE current_register = 0; + + if (!(tsv = fopen(filename, "rt"))) + exit(error(ENOENT, (u8*)"tsv: No such file: %s", filename)); + + 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 <= tsv_iter)) + { + 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_CSV_REGISTERS) + strncpy((char*)tsv_register[current_register++], + (char*)token, BUFSIZE - 1); + else if (tsv_lineno <= 0 + && current_header < MAX_CSV_REGISTERS) + strncpy((char*)tsv_header[current_header++], + (char*)token, BUFSIZE - 1); + 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) + strncpy((char*)tsv_register[current_register++], + (char*)token, BUFSIZE - 1); + } + else + { + if (current_header < MAX_TSV_REGISTERS) + strncpy((char*)tsv_header[current_header++], + (char*)token, BUFSIZE - 1); + } + RESET_TOKEN(token, ptoken, token_size); + + if (tsv_lineno > 0 && pbufline != bufline) + (*callback)(output, tsv_header, tsv_register); + + 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); + + return 0; +} + +int +process_tsv(FILE* output, const u8* arg_token, + const BOOL read_yaml_macros_and_links, const BOOL end_tag) +{ + if (end_tag) + { + 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, (u8*)"Can't nest csv/tsv directives")); + + state |= ST_TSV_BODY; + + if (read_yaml_macros_and_links) + return 0; + + tsv_iter = 0; + u8* saveptr = NULL; + u8* args = (u8*)strtok_r((char*)arg_token, " ", + (char**)&saveptr); + args = (u8*)strtok_r(NULL, " ", (char**)&saveptr); + if (!args) + exit(error(EINVAL, (u8*)"tsv: Arguments required")); + size_t args_len = strlen((char*)args); + if (*args != '"' || *(args + args_len - 1) != '"') + exit(error(EINVAL, + (u8*)"tsv: First argument must be a string")); + if (!tsv_filename) + CALLOC(tsv_filename, u8, BUFSIZE); + u8* 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) + { + errno = 0; + tsv_iter = strtol((char*)args, NULL, 10); + if (errno) + exit(error(errno, + (u8*)"tsv: Invalid argument '%s'", + args)); + } + } + + return 0; +} + +#define PRINTCSVIF(format, arg) \ do \ { \ if ((ALL(csv_state, ST_CS_COND_NONEMPTY) \ @@ -553,7 +898,7 @@ csv_template_start: goto csv_template_done; if (IN(csv_state, ST_CS_ESCAPE)) { - PRINTIF("%c", *pcsv_template); + PRINTCSVIF("%c", *pcsv_template); csv_state &= ~ST_CS_ESCAPE; pcsv_template++; goto csv_template_start; @@ -568,7 +913,7 @@ csv_template_start: case '$': if (IN(csv_state, ST_CS_REGISTER)) { - PRINTIF("%c", '$'); + PRINTCSVIF("%c", '$'); csv_state &= ~ST_CS_REGISTER; } else @@ -587,7 +932,7 @@ csv_template_start: csv_state |= ST_CS_HEADER; } else - PRINTIF("%c", '#'); + PRINTCSVIF("%c", '#'); pcsv_template++; break; case '?': @@ -602,7 +947,7 @@ csv_template_start: csv_state |= ST_CS_COND; } else - PRINTIF("%c", '?'); + PRINTCSVIF("%c", '?'); pcsv_template++; break; case '!': @@ -619,7 +964,7 @@ csv_template_start: csv_state &= ~(ST_CS_COND | ST_CS_COND_EMPTY); } else - PRINTIF("%c", '!'); + PRINTCSVIF("%c", '!'); pcsv_template++; break; case '/': @@ -628,7 +973,7 @@ csv_template_start: else if (ALL(csv_state, ST_CS_COND)) csv_state &= ~(ST_CS_COND | ST_CS_COND_NONEMPTY); else - PRINTIF("%c", '/'); + PRINTCSVIF("%c", '/'); pcsv_template++; break; case '1': @@ -649,17 +994,17 @@ csv_template_start: else if (IN(csv_state, ST_CS_HEADER)) { num = *pcsv_template - '0'; - PRINTIF("%s", csv_header[num - 1]); + PRINTCSVIF("%s", csv_header[num - 1]); csv_state &= ~ST_CS_HEADER; } else if (IN(csv_state, ST_CS_REGISTER)) { num = *pcsv_template - '0'; - PRINTIF("%s", csv_register[num - 1]); + PRINTCSVIF("%s", csv_register[num - 1]); csv_state &= ~ST_CS_REGISTER; } else - PRINTIF("%c", *pcsv_template); + PRINTCSVIF("%c", *pcsv_template); pcsv_template++; break; default: @@ -674,7 +1019,7 @@ csv_template_start: csv_state &= ~ST_CS_REGISTER; } else - PRINTIF("%c", *pcsv_template); + PRINTCSVIF("%c", *pcsv_template); pcsv_template++; } goto csv_template_start; @@ -835,8 +1180,8 @@ process_csv(FILE* output, const u8* arg_token, } else { - if (IN(state, ST_CSV_BODY)) - exit(error(1, (u8*)"Can't nest csv directives")); + if (ANY(state, ST_CSV_BODY | ST_TSV_BODY)) + exit(error(1, (u8*)"Can't nest csv/tsv directives")); state |= ST_CSV_BODY; @@ -1486,9 +1831,9 @@ process_tag(FILE* output, const u8* token, const u8* link_prefix, output_firstcol = TRUE; } else if (startswith((char*)token, "csv")) /* {csv} */ - { process_csv(output, token, read_yaml_macros_and_links, end_tag); - } + else if (startswith((char*)token, "tsv")) /* {csv} */ + 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); @@ -2022,7 +2367,7 @@ begin_html_and_head(FILE* output) char* filename = NULL; CALLOC(filename, char, BUFSIZE); snprintf(filename, BUFSIZE, "%s/%s", input_dirname, (char*)meta); - read_csv(output, filename, &print_meta_var); + read_tsv(output, filename, &print_meta_var); free(filename); } @@ -2308,7 +2653,7 @@ do_buffer:; output_firstcol = TRUE; processed_start_of_line = FALSE; skip_eol = FALSE; - list_para = FALSE; + list_para = FALSE; do_line: if (!pline || !*pline) @@ -3900,7 +4245,8 @@ do_line: case '$': if (ANY(state, - ST_CODE | ST_CSV_BODY | ST_IMAGE | ST_PRE | ST_YAML)) + ST_CODE | ST_CSV_BODY | ST_IMAGE | ST_PRE + | ST_TSV_BODY | ST_YAML)) { CHECKCOPY(token, ptoken, token_size, pline); colno++; @@ -4071,8 +4417,7 @@ done_line: } pvars->value_size = strlen((char*)token) + 1; CALLOC(pvars->value, u8, pvars->value_size); - strncpy((char*)pvars->value, (char*)token, - pvars->value_size); + strncpy((char*)pvars->value, (char*)token, pvars->value_size); } else if (keep_token) {