From d20f7051cbc93f189858e2ac86364a18ed079c65 Mon Sep 17 00:00:00 2001 From: Kristaps Dz Date: Sat, 14 Dec 2024 16:13:17 -0800 Subject: [PATCH] Add --template debugging. This allows **-s -ttree** to see the template AST, which eases debugging of complex templates. It introduce an API change of the tree interface needing to touch the lowdown options. While here, considerably clean up the main manpages, introducing a new section for metadata and cleaning up the standalone part. This cleanup is due to the -s behaviour changes w/r/t the new template functionality. --- extern.h | 6 +- gemini.c | 2 +- html.c | 2 +- latex.c | 2 +- library.c | 2 +- lowdown.h | 3 +- man/lowdown-diff.1 | 220 +++++++------- man/lowdown.1 | 173 +++++------ man/lowdown_tree_rndr.3 | 1 + nroff.c | 2 +- template.c | 626 ++++++++++++++++++++++------------------ tree.c | 67 ++++- 12 files changed, 630 insertions(+), 476 deletions(-) diff --git a/extern.h b/extern.h index 642cd1b..33c6540 100644 --- a/extern.h +++ b/extern.h @@ -24,9 +24,11 @@ struct hentry { TAILQ_HEAD(hentryq, hentry); struct lowdown_meta - *lowdown_get_meta(const struct lowdown_node *, struct lowdown_metaq *); + *lowdown_get_meta(const struct lowdown_node *, + struct lowdown_metaq *); int lowdown_template(const char *, const struct lowdown_buf *, - struct lowdown_buf *, const struct lowdown_metaq *); + struct lowdown_buf *, const struct lowdown_metaq *, + int); int smarty(struct lowdown_node *, size_t, enum lowdown_type); diff --git a/gemini.c b/gemini.c index 3e627f7..3d86139 100644 --- a/gemini.c +++ b/gemini.c @@ -990,7 +990,7 @@ lowdown_gemini_rndr(struct lowdown_buf *ob, void *arg, goto out; if (!rndr(tmp, &metaq, st, n)) goto out; - rc = lowdown_template(st->templ, tmp, ob, &metaq); + rc = lowdown_template(st->templ, tmp, ob, &metaq, 0); } else rc = rndr(ob, &metaq, st, n); diff --git a/html.c b/html.c index 4747874..c72a435 100644 --- a/html.c +++ b/html.c @@ -1037,7 +1037,7 @@ rndr_root(struct lowdown_buf *ob, const struct lowdown_buf *content, if (!(st->flags & LOWDOWN_STANDALONE)) return hbuf_putb(ob, content); if (st->templ != NULL) - return lowdown_template(st->templ, content, ob, mq); + return lowdown_template(st->templ, content, ob, mq, 0); TAILQ_FOREACH(m, mq, entries) if (strcasecmp(m->key, "author") == 0) diff --git a/latex.c b/latex.c index d1cb9d7..c487193 100644 --- a/latex.c +++ b/latex.c @@ -676,7 +676,7 @@ rndr_root(const struct latex *st, struct lowdown_buf *ob, if (!(st->oflags & LOWDOWN_STANDALONE)) return hbuf_putb(ob, content); if (st->templ != NULL) - return lowdown_template(st->templ, content, ob, mq); + return lowdown_template(st->templ, content, ob, mq, 0); TAILQ_FOREACH(m, mq, entries) if (strcasecmp(m->key, "author") == 0) diff --git a/library.c b/library.c index ffd5d0e..e85776c 100644 --- a/library.c +++ b/library.c @@ -88,7 +88,7 @@ lowdown_render(const struct lowdown_opts *opts, lowdown_term_free(rndr); break; case LOWDOWN_TREE: - c = lowdown_tree_rndr(ob, n); + c = lowdown_tree_rndr(ob, n, opts); break; default: c = 1; diff --git a/lowdown.h b/lowdown.h index 8ae121e..b415618 100644 --- a/lowdown.h +++ b/lowdown.h @@ -447,7 +447,8 @@ int lowdown_nroff_rndr(struct lowdown_buf *, void *, const struct lowdown_node *); int lowdown_tree_rndr(struct lowdown_buf *, - const struct lowdown_node *); + const struct lowdown_node *, + const struct lowdown_opts *); void lowdown_latex_free(void *); void *lowdown_latex_new(const struct lowdown_opts *); diff --git a/man/lowdown-diff.1 b/man/lowdown-diff.1 index 658a925..3024f68 100644 --- a/man/lowdown-diff.1 +++ b/man/lowdown-diff.1 @@ -38,35 +38,25 @@ Results are written to standard output. The short arguments are as follows: .Bl -tag -width Ds .It Fl s -Standalone mode. -This emits a document envelope surrounding the output by drawing from -document metadata. +Produce a standalone document (e.g., a valid HTML document including +document type, head, etc.) around the formatted content. See -.Sx Metadata -on providing information to the document envelope. -This applies to -.Fl t Ns Ar gemini , -.Fl t Ns Ar html , -.Fl t Ns Ar latex , -.Fl t Ns Ar ms , -.Fl t Ns Ar man , -and -.Fl t Ns Ar fodt . +.Sx Standalone documents +for details. .It Fl M Ar metadata Provide a single metadata key-value pair. -This may be in the syntax specified by -.Xr lowdown 5 -or as pairs separated by an equal sign, depending upon which character -(a colon or equal sign) comes first. -Exits with an error if given neither colon nor equal sign. -May be invoked multiple times for each pair. +This may be in the normal syntax or as pairs separated by an equal sign, +depending upon which character (a colon or equal sign) comes first. +Exits with an error if given neither. This overrides .Fl m and what's parsed from the document. +See +.Sx Metadata . .It Fl m Ar metadata Like .Fl M , -but is overridden by what's parsed the document, then +but overridden by what's parsed the document, then .Fl M . .It Fl o Ar file Send output to @@ -100,7 +90,7 @@ to show the parse tree of the input document, and .Ar null to parse the document but do no rendering. See -.Sx Output modes . +.Sx Output media . The .Fl T Ar mode form is retained for backward compatibility. @@ -185,7 +175,7 @@ syntax. .El .Pp There are many output long options. -The following are shared by all output modes: +The following are shared by all output media: .Bl -tag -width Ds .It Fl -out-standalone Alias for @@ -205,8 +195,9 @@ Currently only for .Fl t Ns Ar html , .Fl t Ns Ar latex , .Fl t Ns Ar man , +.Fl t Ns Ar ms , and -.Fl t Ns Ar ms . +.Fl t Ns Ar tree . .El .Pp What follows are per-output long options. @@ -458,7 +449,7 @@ and XML elements in the root of the document. This is not syntax-checked in any way. .El -.Ss Output modes +.Ss Output media The output media is specified by .Fl t , which defaults to @@ -471,18 +462,12 @@ Automatic styles (those conditional upon document state) are generated with output. Classes specified by PHP extended attributes are not checked for existence. -Differences are rendered using document tracking. .It Fl t Ns Ar gemini Gemini .Dq gemtext format. .It Fl t Ns Ar html HTML5 output with UTF-8 encoding. -Differences are rendered using the -.Li -and -.Li -elements. .It Fl t Ns Ar latex Simple LaTeX output. The following packages are required: @@ -504,8 +489,6 @@ for Latin modern font, for the difference engine output, and .Li hyperref for links. -Differences are rendered by colouring in blue (insert) and red (delete) -(this format is not fixed). .It Fl t Ns Ar man The .Ar man @@ -522,8 +505,6 @@ Table support is provided by Since UTF-8 may be passed as input values, .Xr preconv 1 may need to be used. -Differences are rendered by colouring in blue (insert) and red (delete) -(this format is not fixed). .It Fl t Ns Ar ms The .Ar ms @@ -541,20 +522,50 @@ Table support is provided by Since UTF-8 may be passed as input values, .Xr preconv 1 may need to be used. -Differences are rendered by colouring in blue (insert) and red (delete) -(this format is not fixed). .It Fl t Ns Ar term ANSI-escaped UTF-8 output suitable for reading on the terminal. Images and equations not supported. +.It Fl t Ns Ar tree +Debugging output. +Not for programmatic use, as the format may change between versions. +.El +.Ss Differences +The differences between old and new document are illustrated in the following +ways: +.Bl -tag -width Ds +.It Fl t Ns Ar fodt +.Dq Flat +Differences are rendered using document tracking. +.It Fl t Ns Ar gemini +Differences are not rendered. +.It Fl t Ns Ar latex +Differences are rendered by colouring in blue (insert) and red (delete) +(this format is not fixed). +.It Fl t Ns Ar man +Differences are rendered by colouring in blue (insert) and red (delete) +(this format is not fixed). +.It Fl t Ns Ar ms +Differences are rendered by colouring in blue (insert) and red (delete) +(this format is not fixed). +.It Fl t Ns Ar term Differences are rendered by background-colouring in blue (insert) and red (delete) (this format is not fixed). .It Fl t Ns Ar tree -Debugging output: not for general use. +Differences are manually in the tree output. .El +.Pp +Differences in content metadata use the following rule: deleted metadata +key-value pairs are not processed in the output, so only inserted or +retained metadata are processed. .Ss Standalone documents When .Fl s -is specified, additional content may be added to output: +is specified, the formatted content is serialised into a self-contained +document template as defined by the output type. +.Pp +If not explicitly set with +.Fl -template , +a default template is produced as follows: .Bl -tag -width Ds .It Fl t Ns Ar fodt Envelope @@ -595,18 +606,32 @@ statements. Prologue macros. .It Fl t Ns Ar term Prologue lines. +.It Fl t Ns Ar tree +Metadata and parsed template. .El .Pp -If parsed from the document or as given by +See +.Sx Metadata +for metadata values used by the default template. +.Ss Metadata +Metadata keys are canonicalised and de-deduplicated in the following +order: .Fl m -or -.Fl M , -the following metadata keys are used by additional content. -The metadata keys are canonicalised in lowercase and without spaces. +.Pq overridden by document content and Fl M , +the prologue of the document itself, then +.Fl M +.Pq overriding document content and Fl m . .Pp -Metadata values should not be encoded in their output format, e.g., -.Dq css: foo&bar . -The renderer will perform any necessary output encoding. +After de-duplication, metadata is serialised into document variables +and/or standalone +.Fl s +output. +.Pp +When not using +.Fl -template , +the following metadata keys are used in the default +.Fl s +template: .Bl -tag -width Ds .It Li affiliation Author affiliation (organisation or institution). @@ -765,29 +790,14 @@ and .Fl t Ns Ar term . .El .Pp -Metadata values are parsed and may be used as variables in markdown -documents regardless of whether -.Fl s -is specified or not. -.Pp Default values, such .Dq 7 for the .Li section , are not set as metadata values, and will not appear if the metadata key is used as a variable. -.Pp -Differences in additional content metadata are rendered differently than -in the document body: deleted metadata key-value pairs are not processed -in the output, so only inserted or retained metadata are processed. -.Pp -In formats where metadata are part of the document body, such as -.Fl t Ns Ar term -and -.Fl t Ns Ar tree , -all metadata are shown as if in the document body. .Ss Templates -Some output modes accept a template +Some output media accept a template .Pq Fl -template to customise standalone .Pq Fl s @@ -796,65 +806,65 @@ Parsed input content is filled into templates through control statements that support conditionals, loops, and variable transformation sequences. .Pp Control statements are delimited as -.Li $statement$ +.Cm $statement$ or -.Li ${statement} . +.Cm ${statement} . Arbitrary white-space may surround the case-insensitive statement between matching delimiters. Statements without a closing delimiter are considered opaque text. .Pp The following statements are available: .Bl -tag -width Ds -.It Li $$ , ${} +.It Cm $$ , ${} Output a literal -.Li $ . +.Cm $ . This may contain arbitrary white-space. -.It Li $ifdef(expression)$ +.It Cm $ifdef(expression)$ Conditional statement. There may not be any white-space between the -.Li ifdef +.Cm ifdef and the opening parenthesis. Begins a block that is ended by a -.Li else , -.Li endif , +.Cm else , +.Cm endif , or the end of file. Its contents are output only if -.Li expression +.Cm expression evaluates to a non-empty string. -.It Li $else$ +.It Cm $else$ Begins a block that is ended by a -.Li endif +.Cm endif or end of file. Its contents are output only if the condition of a preceding -.Li ifdef +.Cm ifdef evaluates to an empty string. An -.Li else +.Cm else without a preceding -.Li ifdef +.Cm ifdef is not output. -.It Li $endif$ +.It Cm $endif$ Closes a block begin with -.Li ifdef +.Cm ifdef or -.Li else . +.Cm else . If not preceded by one of those statements, is silently ignored. -.It Li $for(expression)$ +.It Cm $for(expression)$ Looping statement. There may not be any white-space between the -.Li for +.Cm for and the opening parenthesis. Begins a block that is ended by an -.Li endfor +.Cm endfor or the end of file. Block contents contents are repeatedly output for each list item evaluated from -.Li expression . +.Cm expression . The anaphoric keyword -.Li this +.Cm this may be used to access the current loop expression within the block. -.It Li $expression$ +.It Cm $expression$ Replaced by the result of evaluating a template expression. .El .Pp @@ -875,38 +885,38 @@ The .Li initial value is one of the following: .Bl -tag -width Ds -.It Li and(expression[,expression]*) +.It Cm and(expression[,expression]*) A non-empty list containing the value .Li true if all expressions evaluate to non-empty lists, otherwise an empty list. An empty expression evaluates to an empty list. .It canonicalised metadata key The value for the given metadata key, if found, otherwise an empty list. -.It Li body +.It Cm body The parsed input document body. -.It Li meta(val) -Evaluate -.Li val -as a canonicalised metadata key, even if it's a keyword like +.It Cm meta(key) +Produce the metadata value for the canonicalised metadata +.Cm key . +Allows for keys that are also keywords like .Li body or .Li endif . -.It Li not(expression) +.It Cm not(expression) A non-empty list containing the value .Li true if the expression evaluates as an empty list, otherwise an empty list. -.It Li or(expression[,expression]*) +.It Cm or(expression[,expression]*) A non-empty list containing the value .Li true if one expression evaluates to non-empty lists, otherwise an empty list. An empty expression evaluates to an empty list. -.It Li this +.It Cm this The value of a current loop context or an empty list. .El .Pp If a metadata key is not specified in the input, or if the anaphoric -.Li this +.Cm this has not initialised by a looping context, the initial value evaluates to an empty list. Otherwise, the value is a singleton list. @@ -915,40 +925,40 @@ If transforms are invalid, they will transform into an empty list. .Pp The following transformations are available: .Bl -tag -width Ds -.It Li escapegemini , escapegeminiline +.It Cm escapegemini , escapegeminiline Escape list items for gemini .Pq Fl t Ns Ar gemini , either for multiple lines or compressed to a single line. -.It Li escapehtml , escapehtmlattr , escapehtmlurl +.It Cm escapehtml , escapehtmlattr , escapehtmlurl Escape list items for HTML .Pq Fl t Ns Ar html body content, attributes, and URL attributes, respectively. -.It Li escapelatex +.It Cm escapelatex Escape list items for LaTeX .Pq Fl t Ns Ar latex body content. -.It Li escaperoff , escaperoffline +.It Cm escaperoff , escaperoffline Escape list items for roff .Pq Fl t Ns Ar ms , Fl t Ns Ar man , either for multiple lines or compressed to a single line. -.It Li join +.It Cm join Join a list into a singleton list using two spaces as a join delimiter. -.It Li lowercase +.It Cm lowercase Lowercase all list items. -.It Li split +.It Cm split Split list items on sequences of two or more white-space tokens (space, newline, tab). This is usually invoked on a singleton, but may be repeatedly invoked on a pre-split list. Invokes -.Li trim +.Cm trim prior to the first split. The resulting list has no items that are only white-space. -.It Li trim +.It Cm trim Trim white-space from the beginning and end of all list items. If an item has no non-white-space, it is discarded. -.It Li uppercase +.It Cm uppercase Uppercase all list items. .El .Sh ENVIRONMENT diff --git a/man/lowdown.1 b/man/lowdown.1 index 38586fe..336abc4 100644 --- a/man/lowdown.1 +++ b/man/lowdown.1 @@ -46,35 +46,25 @@ Metadata is automatically enabled. Unsets .Fl X . .It Fl s -Standalone mode. -This emits a document envelope surrounding the output by drawing from -document metadata. +Produce a standalone document (e.g., a valid HTML document including +document type, head, etc.) around the formatted content. See -.Sx Metadata -on providing information to the document envelope. -This applies to -.Fl t Ns Ar gemini , -.Fl t Ns Ar html , -.Fl t Ns Ar latex , -.Fl t Ns Ar ms , -.Fl t Ns Ar man , -and -.Fl t Ns Ar fodt . +.Sx Standalone documents +for details. .It Fl M Ar metadata Provide a single metadata key-value pair. -This may be in the syntax specified by -.Xr lowdown 5 -or as pairs separated by an equal sign, depending upon which character -(a colon or equal sign) comes first. -Exits with an error if given neither colon nor equal sign. -May be invoked multiple times for each pair. +This may be in the normal syntax or as pairs separated by an equal sign, +depending upon which character (a colon or equal sign) comes first. +Exits with an error if given neither. This overrides .Fl m and what's parsed from the document. +See +.Sx Metadata . .It Fl m Ar metadata Like .Fl M , -but is overridden by what's parsed the document, then +but overridden by what's parsed the document, then .Fl M . .It Fl o Ar file Send output to @@ -108,7 +98,7 @@ to show the parse tree of the input document, and .Ar null to parse the document but do no rendering. See -.Sx Output modes . +.Sx Output media . The .Fl T Ar mode form is retained for backward compatibility. @@ -203,7 +193,7 @@ syntax. .El .Pp There are many output long options. -The following are shared by all output modes: +The following are shared by all output media: .Bl -tag -width Ds .It Fl -out-standalone Alias for @@ -223,8 +213,9 @@ Currently only for .Fl t Ns Ar html , .Fl t Ns Ar latex , .Fl t Ns Ar man , +.Fl t Ns Ar ms , and -.Fl t Ns Ar ms . +.Fl t Ns Ar tree . .El .Pp What follows are per-output long options. @@ -476,7 +467,7 @@ and XML elements in the root of the document. This is not syntax-checked in any way. .El -.Ss Output modes +.Ss Output media The output media is specified by .Fl t , which defaults to @@ -553,12 +544,18 @@ may need to be used. ANSI-escaped UTF-8 output suitable for reading on the terminal. Images and equations not supported. .It Fl t Ns Ar tree -Debugging output: not for general use. +Debugging output. +Not for programmatic use, as the format may change between versions. .El .Ss Standalone documents When .Fl s -is specified, additional content may be added to output: +is specified, the formatted content is serialised into a self-contained +document template as defined by the output type. +.Pp +If not explicitly set with +.Fl -template , +a default template is produced as follows: .Bl -tag -width Ds .It Fl t Ns Ar fodt Envelope @@ -599,18 +596,35 @@ statements. Prologue macros. .It Fl t Ns Ar term Prologue lines. +.It Fl t Ns Ar tree +Metadata and parsed template. .El .Pp -If parsed from the document or as given by +See +.Sx Metadata +for metadata values used by the default template. +.Ss Metadata +Metadata keys are canonicalised and de-deduplicated in the following +order: .Fl m +.Pq overridden by document content and Fl M , +the prologue of the document itself, then +.Fl M +.Pq overriding document content and Fl m . +.Pp +After de-duplication, metadata is either accessed directly using +.Fl X or -.Fl M , -the following metadata keys are used by additional content. -The metadata keys are canonicalised in lowercase and without spaces. +.Fl L , +or serialised into document variables and/or standalone +.Fl s +output. .Pp -Metadata values should not be encoded in their output format, e.g., -.Dq css: foo&bar . -The renderer will perform any necessary output encoding. +When not using +.Fl -template , +the following metadata keys are used in the default +.Fl s +template: .Bl -tag -width Ds .It Li affiliation Author affiliation (organisation or institution). @@ -769,11 +783,6 @@ and .Fl t Ns Ar term . .El .Pp -Metadata values are parsed and may be used as variables in markdown -documents regardless of whether -.Fl s -is specified or not. -.Pp Default values, such .Dq 7 for the @@ -781,7 +790,7 @@ for the are not set as metadata values, and will not appear if the metadata key is used as a variable. .Ss Templates -Some output modes accept a template +Some output media accept a template .Pq Fl -template to customise standalone .Pq Fl s @@ -790,65 +799,65 @@ Parsed input content is filled into templates through control statements that support conditionals, loops, and variable transformation sequences. .Pp Control statements are delimited as -.Li $statement$ +.Cm $statement$ or -.Li ${statement} . +.Cm ${statement} . Arbitrary white-space may surround the case-insensitive statement between matching delimiters. Statements without a closing delimiter are considered opaque text. .Pp The following statements are available: .Bl -tag -width Ds -.It Li $$ , ${} +.It Cm $$ , ${} Output a literal -.Li $ . +.Cm $ . This may contain arbitrary white-space. -.It Li $ifdef(expression)$ +.It Cm $ifdef(expression)$ Conditional statement. There may not be any white-space between the -.Li ifdef +.Cm ifdef and the opening parenthesis. Begins a block that is ended by a -.Li else , -.Li endif , +.Cm else , +.Cm endif , or the end of file. Its contents are output only if -.Li expression +.Cm expression evaluates to a non-empty string. -.It Li $else$ +.It Cm $else$ Begins a block that is ended by a -.Li endif +.Cm endif or end of file. Its contents are output only if the condition of a preceding -.Li ifdef +.Cm ifdef evaluates to an empty string. An -.Li else +.Cm else without a preceding -.Li ifdef +.Cm ifdef is not output. -.It Li $endif$ +.It Cm $endif$ Closes a block begin with -.Li ifdef +.Cm ifdef or -.Li else . +.Cm else . If not preceded by one of those statements, is silently ignored. -.It Li $for(expression)$ +.It Cm $for(expression)$ Looping statement. There may not be any white-space between the -.Li for +.Cm for and the opening parenthesis. Begins a block that is ended by an -.Li endfor +.Cm endfor or the end of file. Block contents contents are repeatedly output for each list item evaluated from -.Li expression . +.Cm expression . The anaphoric keyword -.Li this +.Cm this may be used to access the current loop expression within the block. -.It Li $expression$ +.It Cm $expression$ Replaced by the result of evaluating a template expression. .El .Pp @@ -869,38 +878,38 @@ The .Li initial value is one of the following: .Bl -tag -width Ds -.It Li and(expression[,expression]*) +.It Cm and(expression[,expression]*) A non-empty list containing the value .Li true if all expressions evaluate to non-empty lists, otherwise an empty list. An empty expression evaluates to an empty list. .It canonicalised metadata key The value for the given metadata key, if found, otherwise an empty list. -.It Li body +.It Cm body The parsed input document body. -.It Li meta(val) -Evaluate -.Li val -as a canonicalised metadata key, even if it's a keyword like +.It Cm meta(key) +Produce the metadata value for the canonicalised metadata +.Cm key . +Allows for keys that are also keywords like .Li body or .Li endif . -.It Li not(expression) +.It Cm not(expression) A non-empty list containing the value .Li true if the expression evaluates as an empty list, otherwise an empty list. -.It Li or(expression[,expression]*) +.It Cm or(expression[,expression]*) A non-empty list containing the value .Li true if one expression evaluates to non-empty lists, otherwise an empty list. An empty expression evaluates to an empty list. -.It Li this +.It Cm this The value of a current loop context or an empty list. .El .Pp If a metadata key is not specified in the input, or if the anaphoric -.Li this +.Cm this has not initialised by a looping context, the initial value evaluates to an empty list. Otherwise, the value is a singleton list. @@ -909,40 +918,40 @@ If transforms are invalid, they will transform into an empty list. .Pp The following transformations are available: .Bl -tag -width Ds -.It Li escapegemini , escapegeminiline +.It Cm escapegemini , escapegeminiline Escape list items for gemini .Pq Fl t Ns Ar gemini , either for multiple lines or compressed to a single line. -.It Li escapehtml , escapehtmlattr , escapehtmlurl +.It Cm escapehtml , escapehtmlattr , escapehtmlurl Escape list items for HTML .Pq Fl t Ns Ar html body content, attributes, and URL attributes, respectively. -.It Li escapelatex +.It Cm escapelatex Escape list items for LaTeX .Pq Fl t Ns Ar latex body content. -.It Li escaperoff , escaperoffline +.It Cm escaperoff , escaperoffline Escape list items for roff .Pq Fl t Ns Ar ms , Fl t Ns Ar man , either for multiple lines or compressed to a single line. -.It Li join +.It Cm join Join a list into a singleton list using two spaces as a join delimiter. -.It Li lowercase +.It Cm lowercase Lowercase all list items. -.It Li split +.It Cm split Split list items on sequences of two or more white-space tokens (space, newline, tab). This is usually invoked on a singleton, but may be repeatedly invoked on a pre-split list. Invokes -.Li trim +.Cm trim prior to the first split. The resulting list has no items that are only white-space. -.It Li trim +.It Cm trim Trim white-space from the beginning and end of all list items. If an item has no non-white-space, it is discarded. -.It Li uppercase +.It Cm uppercase Uppercase all list items. .El .Sh ENVIRONMENT diff --git a/man/lowdown_tree_rndr.3 b/man/lowdown_tree_rndr.3 index cebde12..7def602 100644 --- a/man/lowdown_tree_rndr.3 +++ b/man/lowdown_tree_rndr.3 @@ -28,6 +28,7 @@ .Fo lowdown_tree_rndr .Fa "struct lowdown_buf *out" .Fa "const struct lowdown_node *n" +.Fa "const struct lowdown_opts *opts" .Fc .Sh DESCRIPTION Renders a node tree diff --git a/nroff.c b/nroff.c index 2cfd838..a2525de 100644 --- a/nroff.c +++ b/nroff.c @@ -2091,7 +2091,7 @@ lowdown_nroff_rndr(struct lowdown_buf *ob, !hbuf_putc(tmp, '\n')) goto out; rc = st->templ == NULL ? hbuf_putb(ob, tmp) : - lowdown_template(st->templ, tmp, ob, &metaq); + lowdown_template(st->templ, tmp, ob, &metaq, 0); } out: diff --git a/template.c b/template.c index f87cc43..77785ed 100644 --- a/template.c +++ b/template.c @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -31,12 +32,43 @@ TAILQ_HEAD(opq, op); TAILQ_HEAD(op_resq, op_res); +TAILQ_HEAD(op_argq, op_arg); +enum op_type { + OP_FOR, + OP_IFDEF, + OP_ELSE, + OP_STR, + OP_EXPR, + OP_ROOT, +}; + +static const char *const op_types[OP_ROOT + 1] = { + "for", + "ifdef", + "else", + "str", + "expr", + "root", +}; + +/* + * Carry the result of an evaluation as an allocated string. + */ struct op_res { char *res; TAILQ_ENTRY(op_res) entries; }; +/* + * An argument to a transformation. + */ +struct op_arg { + const char *arg; + size_t argsz; + TAILQ_ENTRY(op_arg) entries; +}; + /* * Operation for an opaque string. */ @@ -70,15 +102,6 @@ struct op_for { size_t sz; }; -enum op_type { - OP_FOR, - OP_IFDEF, - OP_ELSE, - OP_STR, - OP_EXPR, - OP_ROOT, -}; - /* * Operations are laid out in a tree under an OP_ROOT. Each block * (OP_IFDEF, OP_ELSE, OP_FOR) introduces a sub-tree. @@ -97,14 +120,21 @@ struct op { TAILQ_ENTRY(op) _all; /* queue of all operations */ }; +struct op_out { + int debug; + size_t depth; + struct lowdown_buf *ob; + const struct lowdown_buf *content; + const struct lowdown_metaq *mq; +}; + /* Forward declaration. */ -static int op_exec(const struct op *, struct lowdown_buf *, - const struct lowdown_metaq *, const struct lowdown_buf *, - const char *); -static struct op_resq *op_eval(const char *, size_t, - const struct lowdown_metaq *, const char *, const struct op_resq *, - const struct lowdown_buf *); +static int op_exec(struct op_out *, const struct op *, const char *); +static struct op_resq *op_eval(struct op_out *, const char *, size_t, + const char *, const struct op_resq *); +static int op_debug(struct op_out *, const char *, ...) + __attribute__((format (printf, 2, 3))); /* * Allocate the generic members of "struct op". The caller should be @@ -274,14 +304,31 @@ op_queue(struct opq *q, struct op **cop, const char *str, size_t sz) } /* - * Copy the string into the output. Returns zero on failure (memory - * allocation), non-zero on success. + * If "debug" has been set, print out a message after a number of spaces + * proportionate to the debug depth. + * Return zero on failure (memory allocation), non-zero otherwise. */ static int -op_exec_str(const struct op *op, struct lowdown_buf *ob) +op_debug(struct op_out *out, const char *fmt, ...) { - assert(op->op_type == OP_STR); - return hbuf_put(ob, op->op_str.str, op->op_str.sz); + size_t i; + char buf[256]; + int rc; + va_list ap; + + if (!out->debug) + return 1; + + for (i = 0; i < out->depth; i++) + if (!HBUF_PUTSL(out->ob, " ")) + return 0; + + va_start(ap, fmt); + rc = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + if (rc == -1) + return 0; + return hbuf_puts(out->ob, buf) && HBUF_PUTSL(out->ob, "\n"); } static void @@ -346,6 +393,57 @@ op_resq_clone(const struct op_resq *q, int trim) return nq; } +static void +op_argq_free(struct op_argq *q) +{ + struct op_arg *arg; + + while ((arg = TAILQ_FIRST(q)) != NULL) { + TAILQ_REMOVE(q, arg, entries); + free(arg); + } +} + +static int +op_argq_new(struct op_argq *q, const char *args, size_t argsz) +{ + size_t i, start, substack = 0; + int inquot = 0; + struct op_arg *arg; + + for (start = i = 0; i < argsz; i++) { + if (args[i] == '"') { + inquot = !inquot; + continue; + } else if (args[i] == '(') { + substack++; + continue; + } else if (args[i] == ')') { + substack--; + continue; + } else if (args[i] != ',' || substack > 0 || inquot) + continue; + + if ((arg = calloc(1, sizeof(struct op_arg))) == NULL) + return 0; + TAILQ_INSERT_TAIL(q, arg, entries); + arg->arg = &args[start]; + arg->argsz = i - start; + start = i + 1; + } + + if (i >= start) { + if ((arg = calloc(1, sizeof(struct op_arg))) == NULL) + return 0; + TAILQ_INSERT_TAIL(q, arg, entries); + arg->arg = &args[start]; + arg->argsz = i - start; + start = i + 1; + } + + return 1; +} + /* * Split each input string along white-space boundaries. This trims * white-space around all strings during the split. The result is a @@ -353,15 +451,14 @@ op_resq_clone(const struct op_resq *q, int trim) * allocation failure. */ static struct op_resq * -op_eval_function_split(const struct lowdown_metaq *mq, - const struct op_resq *input) +op_eval_function_split(struct op_out *out, const struct op_resq *input) { - struct op_resq *nq; + struct op_resq *nq = NULL; struct op_res *nres, *nnres; char *cp, *ncp; if ((nq = op_resq_clone(input, 1)) == NULL) - return NULL; + goto err; TAILQ_FOREACH(nres, nq, entries) { for (cp = nres->res; *cp != '\0'; cp++) @@ -394,18 +491,17 @@ op_eval_function_split(const struct lowdown_metaq *mq, /* Split at white-space. */ *cp = '\0'; - if ((nnres = calloc(1, sizeof(struct op_res))) == NULL) { - op_resq_free(nq); - return NULL; - } + if ((nnres = calloc(1, sizeof(struct op_res))) == NULL) + goto err; TAILQ_INSERT_AFTER(nq, nres, nnres, entries); - if ((nnres->res = strdup(ncp)) == NULL) { - op_resq_free(nq); - return NULL; - } + if ((nnres->res = strdup(ncp)) == NULL) + goto err; } return nq; +err: + op_resq_free(nq); + return NULL; } /* @@ -413,13 +509,13 @@ op_eval_function_split(const struct lowdown_metaq *mq, * Returns NULL on allocation failure. */ static struct op_resq * -op_eval_function_escape_htmlurl(const struct lowdown_metaq *mq, +op_eval_function_escape_htmlurl(struct op_out *out, const struct op_resq *input) { struct op_resq *nq = NULL; struct op_res *nres; const struct op_res *res; - struct lowdown_buf *buf; + struct lowdown_buf *buf = NULL; if ((buf = hbuf_new(32)) == NULL) goto err; @@ -451,13 +547,13 @@ op_eval_function_escape_htmlurl(const struct lowdown_metaq *mq, * Returns NULL on allocation failure. */ static struct op_resq * -op_eval_function_escape_htmlattr(const struct lowdown_metaq *mq, +op_eval_function_escape_htmlattr(struct op_out *out, const struct op_resq *input) { struct op_resq *nq = NULL; struct op_res *nres; const struct op_res *res; - struct lowdown_buf *buf; + struct lowdown_buf *buf = NULL; if ((buf = hbuf_new(32)) == NULL) goto err; @@ -485,17 +581,17 @@ op_eval_function_escape_htmlattr(const struct lowdown_metaq *mq, } /* - * Escape all characters in all list items for general LaTeX content. + * HTML-escape (for general content) all characters in all list items. * Returns NULL on allocation failure. */ static struct op_resq * -op_eval_function_escape_latex(const struct lowdown_metaq *mq, +op_eval_function_escape_html(struct op_out *out, const struct op_resq *input) { struct op_resq *nq = NULL; struct op_res *nres; const struct op_res *res; - struct lowdown_buf *buf; + struct lowdown_buf *buf = NULL; if ((buf = hbuf_new(32)) == NULL) goto err; @@ -505,7 +601,8 @@ op_eval_function_escape_latex(const struct lowdown_metaq *mq, TAILQ_FOREACH(res, input, entries) { hbuf_truncate(buf); - if (!lowdown_latex_esc(buf, res->res, strlen(res->res))) + if (!lowdown_html_esc(buf, res->res, strlen(res->res), + 1, 0, 0)) goto err; if ((nres = calloc(1, sizeof(struct op_res))) == NULL) goto err; @@ -523,18 +620,17 @@ op_eval_function_escape_latex(const struct lowdown_metaq *mq, } /* - * Escape all characters in all list items for Gemini, either over - * multiple lines or with newlines removed for a single line. + * Escape all characters in all list items for general LaTeX content. * Returns NULL on allocation failure. */ static struct op_resq * -op_eval_function_escape_gemini(const struct lowdown_metaq *mq, - const struct op_resq *input, int oneline) +op_eval_function_escape_latex(struct op_out *out, + const struct op_resq *input) { struct op_resq *nq = NULL; struct op_res *nres; const struct op_res *res; - struct lowdown_buf *buf; + struct lowdown_buf *buf = NULL; if ((buf = hbuf_new(32)) == NULL) goto err; @@ -544,8 +640,7 @@ op_eval_function_escape_gemini(const struct lowdown_metaq *mq, TAILQ_FOREACH(res, input, entries) { hbuf_truncate(buf); - if (!lowdown_gemini_esc(buf, res->res, strlen(res->res), - oneline)) + if (!lowdown_latex_esc(buf, res->res, strlen(res->res))) goto err; if ((nres = calloc(1, sizeof(struct op_res))) == NULL) goto err; @@ -563,18 +658,18 @@ op_eval_function_escape_gemini(const struct lowdown_metaq *mq, } /* - * Escape all characters in all list items for roff (ms/man), either - * over multiple lines or with newlines removed for a single line. + * Escape all characters in all list items for Gemini, either over + * multiple lines or with newlines removed for a single line. * Returns NULL on allocation failure. */ static struct op_resq * -op_eval_function_escape_roff(const struct lowdown_metaq *mq, +op_eval_function_escape_gemini(struct op_out *out, const struct op_resq *input, int oneline) { struct op_resq *nq = NULL; struct op_res *nres; const struct op_res *res; - struct lowdown_buf *buf; + struct lowdown_buf *buf = NULL; if ((buf = hbuf_new(32)) == NULL) goto err; @@ -584,8 +679,8 @@ op_eval_function_escape_roff(const struct lowdown_metaq *mq, TAILQ_FOREACH(res, input, entries) { hbuf_truncate(buf); - if (!lowdown_nroff_esc(buf, res->res, strlen(res->res), - oneline, 0)) + if (!lowdown_gemini_esc(buf, res->res, strlen(res->res), + oneline)) goto err; if ((nres = calloc(1, sizeof(struct op_res))) == NULL) goto err; @@ -603,17 +698,18 @@ op_eval_function_escape_roff(const struct lowdown_metaq *mq, } /* - * HTML-escape (for general content) all characters in all list items. + * Escape all characters in all list items for roff (ms/man), either + * over multiple lines or with newlines removed for a single line. * Returns NULL on allocation failure. */ static struct op_resq * -op_eval_function_escape_html(const struct lowdown_metaq *mq, - const struct op_resq *input) +op_eval_function_escape_roff(struct op_out *out, + const struct op_resq *input, int oneline) { struct op_resq *nq = NULL; struct op_res *nres; const struct op_res *res; - struct lowdown_buf *buf; + struct lowdown_buf *buf = NULL; if ((buf = hbuf_new(32)) == NULL) goto err; @@ -623,8 +719,8 @@ op_eval_function_escape_html(const struct lowdown_metaq *mq, TAILQ_FOREACH(res, input, entries) { hbuf_truncate(buf); - if (!lowdown_html_esc(buf, res->res, strlen(res->res), - 1, 0, 0)) + if (!lowdown_nroff_esc(buf, res->res, strlen(res->res), + oneline, 0)) goto err; if ((nres = calloc(1, sizeof(struct op_res))) == NULL) goto err; @@ -642,12 +738,12 @@ op_eval_function_escape_html(const struct lowdown_metaq *mq, } /* - * Lowercase all characters in all list items. Returns NULL on - * allocation failure. + * Lowercase or uppercase all characters in all list items. Returns + * NULL on allocation failure. */ static struct op_resq * -op_eval_function_lowercase(const struct lowdown_metaq *mq, - const struct op_resq *input) +op_eval_function_case(struct op_out *out, const struct op_resq *input, + int lower) { struct op_resq *nq; struct op_res *nres; @@ -657,27 +753,9 @@ op_eval_function_lowercase(const struct lowdown_metaq *mq, return NULL; TAILQ_FOREACH(nres, nq, entries) for (cp = nres->res; *cp != '\0'; cp++) - *cp = tolower((unsigned char)*cp); - return nq; -} - -/* - * Uppercase all characters in all list items. Returns NULL on - * allocation failure. - */ -static struct op_resq * -op_eval_function_uppercase(const struct lowdown_metaq *mq, - const struct op_resq *input) -{ - struct op_resq *nq; - struct op_res *nres; - char *cp; - - if ((nq = op_resq_clone(input, 0)) == NULL) - return NULL; - TAILQ_FOREACH(nres, nq, entries) - for (cp = nres->res; *cp != '\0'; cp++) - *cp = toupper((unsigned char)*cp); + *cp = lower ? + tolower((unsigned char)*cp) : + toupper((unsigned char)*cp); return nq; } @@ -687,17 +765,16 @@ op_eval_function_uppercase(const struct lowdown_metaq *mq, * empty output. Returns NULL on allocation failure. */ static struct op_resq * -op_eval_function_join(const struct lowdown_metaq *mq, - const struct op_resq *input) +op_eval_function_join(struct op_out *out, const struct op_resq *input) { - struct op_resq *nq; + struct op_resq *nq = NULL; struct op_res *nres; const struct op_res *res; size_t sz = 0; void *p; if ((nq = malloc(sizeof(struct op_resq))) == NULL) - return NULL; + goto err; TAILQ_INIT(nq); /* Empty list -> empty singleton. */ @@ -705,25 +782,19 @@ op_eval_function_join(const struct lowdown_metaq *mq, if (TAILQ_EMPTY(input)) return nq; - if ((nres = calloc(1, sizeof(struct op_res))) == NULL) { - op_resq_free(nq); - return NULL; - } + if ((nres = calloc(1, sizeof(struct op_res))) == NULL) + goto err; TAILQ_INSERT_TAIL(nq, nres, entries); - nres->res = NULL; + TAILQ_FOREACH(res, input, entries) { if (sz == 0) { - if ((nres->res = strdup(res->res)) == NULL) { - op_resq_free(nq); - return NULL; - } + if ((nres->res = strdup(res->res)) == NULL) + goto err; sz = strlen(nres->res) + 1; } else { sz += strlen(res->res) + 2; - if ((p = realloc(nres->res, sz)) == NULL) { - op_resq_free(nq); - return NULL; - } + if ((p = realloc(nres->res, sz)) == NULL) + goto err; nres->res = p; strlcat(nres->res, " ", sz); strlcat(nres->res, res->res, sz); @@ -731,44 +802,55 @@ op_eval_function_join(const struct lowdown_metaq *mq, } assert(sz > 0); return nq; +err: + op_resq_free(nq); + return NULL; } static struct op_resq * -op_eval_function(const char *expr, size_t exprsz, const char *args, - size_t argsz, const struct lowdown_metaq *mq, - const struct op_resq *input) +op_eval_function(struct op_out *out, const char *expr, size_t exprsz, + const char *args, size_t argsz, const struct op_resq *input) { struct op_resq *nq; + if (!op_debug(out, "%s: %.*s", __func__, (int)exprsz, expr)) + return NULL; + out->depth++; + if (exprsz == 9 && strncasecmp(expr, "uppercase", 9) == 0) - nq = op_eval_function_uppercase(mq, input); + nq = op_eval_function_case(out, input, 0); else if (exprsz == 9 && strncasecmp(expr, "lowercase", 9) == 0) - nq = op_eval_function_lowercase(mq, input); + nq = op_eval_function_case(out, input, 1); else if (exprsz == 5 && strncasecmp(expr, "split", 5) == 0) - nq = op_eval_function_split(mq, input); + nq = op_eval_function_split(out, input); else if (exprsz == 4 && strncasecmp(expr, "join", 4) == 0) - nq = op_eval_function_join(mq, input); + nq = op_eval_function_join(out, input); else if (exprsz == 4 && strncasecmp(expr, "trim", 4) == 0) nq = op_resq_clone(input, 1); else if (exprsz == 12 && strncasecmp(expr, "escapegemini", 12) == 0) - nq = op_eval_function_escape_gemini(mq, input, 0); + nq = op_eval_function_escape_gemini(out, input, 0); else if (exprsz == 16 && strncasecmp(expr, "escapegeminiline", 16) == 0) - nq = op_eval_function_escape_gemini(mq, input, 1); + nq = op_eval_function_escape_gemini(out, input, 1); else if (exprsz == 10 && strncasecmp(expr, "escapehtml", 10) == 0) - nq = op_eval_function_escape_html(mq, input); + nq = op_eval_function_escape_html(out, input); else if (exprsz == 14 && strncasecmp(expr, "escapehtmlattr", 14) == 0) - nq = op_eval_function_escape_htmlattr(mq, input); + nq = op_eval_function_escape_htmlattr(out, input); else if (exprsz == 13 && strncasecmp(expr, "escapehtmlurl", 13) == 0) - nq = op_eval_function_escape_htmlurl(mq, input); + nq = op_eval_function_escape_htmlurl(out, input); else if (exprsz == 11 && strncasecmp(expr, "escapelatex", 11) == 0) - nq = op_eval_function_escape_latex(mq, input); + nq = op_eval_function_escape_latex(out, input); else if (exprsz == 10 && strncasecmp(expr, "escaperoff", 10) == 0) - nq = op_eval_function_escape_roff(mq, input, 0); + nq = op_eval_function_escape_roff(out, input, 0); else if (exprsz == 14 && strncasecmp(expr, "escaperoffline", 14) == 0) - nq = op_eval_function_escape_roff(mq, input, 1); - else if ((nq = malloc(sizeof(struct op_resq))) != NULL) - TAILQ_INIT(nq); + nq = op_eval_function_escape_roff(out, input, 1); + else { + if (!op_debug(out, "transform not recognised")) + return NULL; + if ((nq = malloc(sizeof(struct op_resq))) != NULL) + TAILQ_INIT(nq); + } + out->depth--; return nq; } @@ -779,138 +861,82 @@ op_eval_function(const char *expr, size_t exprsz, const char *args, * an empty list. Returns NULL on allocation failure. */ static struct op_resq * -op_eval_initial(const char *expr, size_t exprsz, const char *args, - size_t argsz, const char *this, const struct lowdown_metaq *mq, - const struct lowdown_buf *content) +op_eval_initial(struct op_out *out, const char *expr, size_t exprsz, + const char *args, size_t argsz, const char *this) { struct op_resq *q, *resq; struct op_res *res; + struct op_argq argq; + struct op_arg *arg; const struct lowdown_meta *m; const char *v = NULL; - size_t i, vsz, stack = 0, start; + size_t vsz; int rc; + if (!op_debug(out, "%s: %.*s", __func__, (int)exprsz, expr)) + return NULL; + out->depth++; + if ((q = malloc(sizeof(struct op_resq))) == NULL) return NULL; + TAILQ_INIT(&argq); TAILQ_INIT(q); - if (exprsz == 4 && strncasecmp(expr, "this", exprsz) == 0) { + if (exprsz == 4 && strncasecmp(expr, "this", 4) == 0) { /* Anaphoric keyword in current loop or NULL. */ v = this; vsz = this == NULL ? 0 : strlen(this); - } else if (exprsz == 4 && strncasecmp(expr, "body", exprsz) == 0) { + } else if (exprsz == 4 && strncasecmp(expr, "body", 4) == 0) { /* Body of HTML document. */ - v = content->data; - vsz = content->size; - } else if (exprsz == 3 && strncasecmp(expr, "not", exprsz) == 0) { - /* "NOT" single argument (reduce to boolean). */ - if (argsz > 0) { - resq = op_eval(args, argsz, mq, this, NULL, content); - if (resq == NULL) - return 0; - rc = TAILQ_EMPTY(resq); - op_resq_free(resq); - } else - rc = 1; - if (rc) { + v = out->content->data; + vsz = out->content->size; + } else if (exprsz == 3 && strncasecmp(expr, "not", 3) == 0) { + /* "NOT" of argument. The rest are ignored. */ + resq = op_eval(out, args, argsz, this, NULL); + if (resq == NULL) + goto err; + rc = TAILQ_EMPTY(resq); + op_resq_free(resq); + if (rc == 1) { v = "true"; vsz = 4; } - } else if (exprsz == 2 && strncasecmp(expr, "or", exprsz) == 0) { - /* "OR" of all arguments. */ - for (rc = 0, start = i = 0; rc == 0 && i < argsz; i++) { - /* - * Read until next comma, then evaluate its - * arguments. Short-circuit if arguments - * evaluate to true. Check stack to pass over - * sub-arguments. - */ - - if (args[i] == '(') { - stack++; - continue; - } else if (args[i] == ')') { - stack--; - continue; - } else if (args[i] != ',' || stack > 0) - continue; - - /* Evaluate or empty evaluates to false. */ - - if (i > start) { - resq = op_eval(&args[start], i - start, - mq, this, NULL, content); - if (resq == NULL) - return 0; - rc = !TAILQ_EMPTY(resq); - op_resq_free(resq); - } else - rc = 0; - start = i + 1; - } - - /* Catch remaining arguments. */ - - if (i > start && rc == 0) { - resq = op_eval(&args[start], i - start, mq, - this, NULL, content); + } else if (exprsz == 2 && strncasecmp(expr, "or", 2) == 0) { + /* "OR" of all arguments. Short-circuit on TRUE. */ + if (!op_argq_new(&argq, args, argsz)) + goto err; + rc = 0; + TAILQ_FOREACH(arg, &argq, entries) { + resq = op_eval(out, arg->arg, arg->argsz, this, + NULL); if (resq == NULL) - return 0; + goto err; rc = !TAILQ_EMPTY(resq); op_resq_free(resq); + if (rc == 1) + break; } - if (rc == 1) { v = "true"; vsz = 4; } - } else if (exprsz == 3 && strncasecmp(expr, "and", exprsz) == 0) { - /* "And" of all arguments. */ - for (rc = 1, start = i = 0; rc == 1 && i < argsz; i++) { - /* - * Read until next comma, then evaluate its - * arguments. Short-circuit if arguments - * evaluate to false. Check stack to pass over - * sub-arguments. - */ - - if (args[i] == '(') { - stack++; - continue; - } else if (args[i] == ')') { - stack--; - continue; - } else if (args[i] != ',' || stack > 0) - continue; - - /* Evaluate or empty evaluates to false. */ - - if (i > start) { - resq = op_eval(&args[start], i - start, - mq, this, NULL, content); - if (resq == NULL) - return 0; - rc = !TAILQ_EMPTY(resq); - op_resq_free(resq); - } else - rc = 0; - start = i + 1; - } - - /* Catch remaining arguments. */ - - if (i > start && rc == 1) { - resq = op_eval(&args[start], i - start, mq, - this, NULL, content); + } else if (exprsz == 3 && strncasecmp(expr, "and", 3) == 0) { + /* "AND" of all arguments. Short-circuit on FALSE. */ + if (!op_argq_new(&argq, args, argsz)) + goto err; + rc = TAILQ_EMPTY(&argq) ? 0 : 1; + TAILQ_FOREACH(arg, &argq, entries) { + resq = op_eval(out, arg->arg, arg->argsz, this, + NULL); if (resq == NULL) - return 0; + goto err; rc = !TAILQ_EMPTY(resq); op_resq_free(resq); - } else if (i == start) - rc = 0; - - if (rc == 1) { + if (rc == 0) + break; + } + if (rc != 0) { v = "true"; vsz = 4; } @@ -920,13 +946,16 @@ op_eval_initial(const char *expr, size_t exprsz, const char *args, * key, allowing the use of the overridden names e.g. * body. */ - if (exprsz == 4 && - strncasecmp(expr, "meta", exprsz) == 0 && + if (exprsz == 4 && strncasecmp(expr, "meta", 4) == 0 && args != NULL) { + if (!op_debug(out, "arg: %.*s", + (int)argsz, args)) + goto err; expr = args; exprsz = argsz; } - TAILQ_FOREACH(m, mq, entries) + + TAILQ_FOREACH(m, out->mq, entries) if (strlen(m->key) == exprsz && strncasecmp(m->key, expr, exprsz) == 0) { v = m->value; @@ -935,32 +964,40 @@ op_eval_initial(const char *expr, size_t exprsz, const char *args, } } + op_argq_free(&argq); + out->depth--; + /* Invariant: non-empty or empty list. */ if (v == NULL || v[0] == '\0') return q; - - if ((res = calloc(1, sizeof(struct op_res))) == NULL) { - op_resq_free(q); - return NULL; - } + if ((res = calloc(1, sizeof(struct op_res))) == NULL) + goto err; TAILQ_INSERT_TAIL(q, res, entries); - if ((res->res = strndup(v, vsz)) == NULL) { - op_resq_free(q); - return NULL; - } + if ((res->res = strndup(v, vsz)) == NULL) + goto err; return q; +err: + op_resq_free(q); + op_argq_free(&argq); + return NULL; } static struct op_resq * -op_eval(const char *expr, size_t sz, const struct lowdown_metaq *mq, - const char *this, const struct op_resq *input, - const struct lowdown_buf *content) +op_eval(struct op_out *out, const char *expr, size_t sz, + const char *this, const struct op_resq *input) { size_t nextsz = 0, exprsz, argsz = 0; const char *next, *args; struct op_resq *q, *nextq; + if (sz == 0) { + if ((q = malloc(sizeof(struct op_resq))) == NULL) + return NULL; + TAILQ_INIT(q); + return q; + } + /* Find next expression in chain. */ next = memchr(expr, '.', sz); @@ -986,46 +1023,59 @@ op_eval(const char *expr, size_t sz, const struct lowdown_metaq *mq, */ q = (input == NULL) ? - op_eval_initial(expr, exprsz, args, argsz, this, mq, - content) : - op_eval_function(expr, exprsz, args, argsz, mq, input); + op_eval_initial(out, expr, exprsz, args, argsz, this) : + op_eval_function(out, expr, exprsz, args, argsz, input); /* Return or pass to the next, then free current. */ if (next == NULL) return q; - nextq = op_eval(next, nextsz, mq, this, q, content); + nextq = op_eval(out, next, nextsz, this, q); op_resq_free(q); return nextq; } +/* + * Copy the string into the output. Returns zero on failure (memory + * allocation), non-zero on success. + */ +static int +op_exec_str(struct op_out *out, const struct op *op) +{ + assert(op->op_type == OP_STR); + + if (!op_debug(out, "length: %zu", op->op_str.sz)) + return 0; + if (out->debug) + return 1; + return hbuf_put(out->ob, op->op_str.str, op->op_str.sz); +} + /* * Copy the result running an expression into the output. Returns zero * on failure (memory allocation), non-zero on success. */ static int -op_exec_expr(const struct op *op, struct lowdown_buf *ob, - const struct lowdown_metaq *mq, const char *this, - const struct lowdown_buf *content) +op_exec_expr(struct op_out *out, const struct op *op, const char *this) { struct op_resq *resq; struct op_res *res; int rc = 0, first = 1; assert(op->op_type == OP_EXPR); - resq = op_eval(op->op_expr.expr, op->op_expr.sz, mq, this, - NULL, content); + resq = op_eval(out, op->op_expr.expr, op->op_expr.sz, this, + NULL); if (resq == NULL) return 0; - - TAILQ_FOREACH(res, resq, entries) { - if (!first && !HBUF_PUTSL(ob, " ")) - goto out; - if (!hbuf_puts(ob, res->res)) - goto out; - first = 0; - } + if (!out->debug) + TAILQ_FOREACH(res, resq, entries) { + if (!first && !HBUF_PUTSL(out->ob, " ")) + goto out; + if (!hbuf_puts(out->ob, res->res)) + goto out; + first = 0; + } rc = 1; out: op_resq_free(resq); @@ -1037,30 +1087,37 @@ op_exec_expr(const struct op *op, struct lowdown_buf *ob, * on how the expression evaluates. */ static int -op_exec_for(const struct op *op, struct lowdown_buf *ob, - const struct lowdown_metaq *mq, const char *this, - const struct lowdown_buf *content) +op_exec_for(struct op_out *out, const struct op *op, const char *this) { struct op_resq *resq; struct op_res *res; + size_t loops = 0; assert(op->op_type == OP_FOR); /* Empty arguments evaluate to an empty list. */ - if (op->op_for.sz == 0) + if (op->op_for.sz == 0) { + if (!op_debug(out, "no loop expression")) + return 0; return 1; + } - resq = op_eval(op->op_for.expr, op->op_for.sz, mq, this, NULL, - content); + resq = op_eval(out, op->op_for.expr, op->op_for.sz, this, NULL); if (resq == NULL) return 0; - TAILQ_FOREACH(res, resq, entries) - if (!op_exec(op, ob, mq, content, res->res)) { + TAILQ_FOREACH(res, resq, entries) { + if (!op_debug(out, "loop iteration: %zu", ++loops)) + return 0; + if (!op_exec(out, op, res->res)) { op_resq_free(resq); return 0; } + } + + if (loops == 0 && !op_debug(out, "no loop iterations")) + return 0; op_resq_free(resq); return 1; @@ -1071,9 +1128,7 @@ op_exec_for(const struct op *op, struct lowdown_buf *ob, * on how the expression evaluates. */ static int -op_exec_ifdef(const struct op *op, struct lowdown_buf *ob, - const struct lowdown_metaq *mq, const char *this, - const struct lowdown_buf *content) +op_exec_ifdef(struct op_out *out, const struct op *op, const char *this) { struct op_resq *resq; int rc; @@ -1083,8 +1138,8 @@ op_exec_ifdef(const struct op *op, struct lowdown_buf *ob, /* Empty arguments evaluate to an empty list. */ if (op->op_ifdef.sz > 0) { - resq = op_eval(op->op_ifdef.expr, op->op_ifdef.sz, mq, this, - NULL, content); + resq = op_eval(out, op->op_ifdef.expr, op->op_ifdef.sz, + this, NULL); if (resq == NULL) return 0; rc = !TAILQ_EMPTY(resq); @@ -1092,34 +1147,42 @@ op_exec_ifdef(const struct op *op, struct lowdown_buf *ob, } else rc = 0; - return rc ? op_exec(op, ob, mq, content, this) : + if (!op_debug(out, "result: %s%s", rc ? "true" : "false", + rc || op->op_ifdef.chain == NULL ? "" : + " (taking else branch)")) + return 0; + + return rc ? op_exec(out, op,this) : op->op_ifdef.chain == NULL ? 1 : - op_exec(op->op_ifdef.chain, ob, mq, content, this); + op_exec(out, op->op_ifdef.chain, this); } static int -op_exec(const struct op *cop, struct lowdown_buf *ob, - const struct lowdown_metaq *mq, const struct lowdown_buf *content, - const char *this) +op_exec(struct op_out *out, const struct op *cop, const char *this) { const struct op *op; - TAILQ_FOREACH(op, &cop->children, _siblings) + out->depth++; + TAILQ_FOREACH(op, &cop->children, _siblings) { + if (!op_debug(out, "%s: %s", __func__, + op_types[op->op_type])) + return 0; + out->depth++; switch (op->op_type) { case OP_STR: - if (!op_exec_str(op, ob)) + if (!op_exec_str(out, op)) return 0; break; case OP_EXPR: - if (!op_exec_expr(op, ob, mq, this, content)) + if (!op_exec_expr(out, op, this)) return 0; break; case OP_IFDEF: - if (!op_exec_ifdef(op, ob, mq, this, content)) + if (!op_exec_ifdef(out, op, this)) return 0; break; case OP_FOR: - if (!op_exec_for(op, ob, mq, this, content)) + if (!op_exec_for(out, op, this)) return 0; break; case OP_ELSE: @@ -1132,6 +1195,9 @@ op_exec(const struct op *cop, struct lowdown_buf *ob, case OP_ROOT: break; } + out->depth--; + } + out->depth--; return 1; } @@ -1142,7 +1208,7 @@ op_exec(const struct op *cop, struct lowdown_buf *ob, */ int lowdown_template(const char *templ, const struct lowdown_buf *content, - struct lowdown_buf *ob, const struct lowdown_metaq *mq) + struct lowdown_buf *ob, const struct lowdown_metaq *mq, int dbg) { char delim; const char *cp, *nextcp, *savecp; @@ -1150,6 +1216,7 @@ lowdown_template(const char *templ, const struct lowdown_buf *content, struct op *op, *cop, *root; int rc = 0, igneoln; size_t sz; + struct op_out out; TAILQ_INIT(&q); if ((root = op_alloc(&q, OP_ROOT, NULL)) == NULL) @@ -1258,7 +1325,12 @@ lowdown_template(const char *templ, const struct lowdown_buf *content, /* Execute the generation operation tree. */ - rc = op_exec(root, ob, mq, content, NULL); + out.debug = dbg; + out.ob = ob; + out.content = content; + out.mq = mq; + out.depth = 0; + rc = op_exec(&out, root, NULL); out: while ((op = TAILQ_FIRST(&q)) != NULL) { TAILQ_REMOVE(&q, op, _all); diff --git a/tree.c b/tree.c index 88229d6..202817b 100644 --- a/tree.c +++ b/tree.c @@ -111,7 +111,7 @@ rndr_short(struct lowdown_buf *ob, const struct lowdown_buf *b) } static int -rndr(struct lowdown_buf *ob, +rndr(struct lowdown_buf *ob, struct lowdown_metaq *mq, const struct lowdown_node *root, size_t indent) { const struct lowdown_node *n; @@ -354,6 +354,8 @@ rndr(struct lowdown_buf *ob, return 0; break; case LOWDOWN_META: + if (lowdown_get_meta(root, mq) == NULL) + return 0; if (!rndr_indent(ob, indent + 1)) return 0; if (!hbuf_printf(ob, "key: ")) @@ -463,7 +465,7 @@ rndr(struct lowdown_buf *ob, return 0; TAILQ_FOREACH(n, &root->children, entries) - if (!rndr(tmp, n, indent + 1)) { + if (!rndr(tmp, mq, n, indent + 1)) { hbuf_free(tmp); return 0; } @@ -475,9 +477,66 @@ rndr(struct lowdown_buf *ob, int lowdown_tree_rndr(struct lowdown_buf *ob, - const struct lowdown_node *root) + const struct lowdown_node *root, + const struct lowdown_opts *opts) { + struct lowdown_buf *obtmp = NULL, *mqtmp = NULL; + struct lowdown_metaq mq; + struct lowdown_meta *m; + int rc = 0; + size_t init_depth = 0; + + TAILQ_INIT(&mq); + + if ((obtmp = hbuf_new(64)) == NULL) + goto out; + if ((mqtmp = hbuf_new(64)) == NULL) + goto out; + + /* + * Output the parsed document into obtmp, with optional envelope + * in standalone mode. + */ + + if (opts != NULL && (opts->oflags & LOWDOWN_STANDALONE)) { + init_depth = 1; + if (!HBUF_PUTSL(obtmp, "document:\n")) + goto out; + } + if (!rndr(obtmp, &mq, root, init_depth)) + goto out; + + /* Optionally output the parsed metadata into mqtmp. */ + + if (opts != NULL && (opts->oflags & LOWDOWN_STANDALONE)) { + if (!HBUF_PUTSL(mqtmp, "metadata:\n")) + return 0; + TAILQ_FOREACH(m, &mq, entries) + if (!hbuf_printf(mqtmp, " %s: %s\n", m->key, + m->value)) + return 0; + } + + /* + * If a template has been provided in standalone mode, print + * that after the body and metadata. Otherwise, directly print + * the body (and optional metadata). + */ + + if (opts != NULL && opts->templ != NULL && + (opts->oflags & LOWDOWN_STANDALONE)) { + if (!(hbuf_putb(ob, obtmp) && hbuf_putb(ob, mqtmp))) + return 0; + if (!HBUF_PUTSL(ob, "template:\n")) + return 0; + rc = lowdown_template(opts->templ, obtmp, ob, &mq, 1); + } else + rc = hbuf_putb(ob, obtmp) && hbuf_putb(ob, mqtmp); - return rndr(ob, root, 0); +out: + lowdown_metaq_free(&mq); + hbuf_free(obtmp); + hbuf_free(mqtmp); + return rc; }