diff options
author | Karl Berry <karl@freefriends.org> | 2024-02-18 21:05:58 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2024-02-18 21:05:58 +0000 |
commit | 45d62c355885e905db2a729761eb384ec447e931 (patch) | |
tree | a80c1c9bd45c98a38855f2ac39f1ac5233862747 /Master/texmf-dist/source/generic | |
parent | c503d4c5c13af6a3749ec30bfee14cffda84240c (diff) |
markdown (18feb24)
git-svn-id: svn://tug.org/texlive/trunk@69962 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/source/generic')
3 files changed, 8774 insertions, 4433 deletions
diff --git a/Master/texmf-dist/source/generic/markdown/docstrip.cfg b/Master/texmf-dist/source/generic/markdown/docstrip.cfg index 853817bb0a7..9dc5f652309 100644 --- a/Master/texmf-dist/source/generic/markdown/docstrip.cfg +++ b/Master/texmf-dist/source/generic/markdown/docstrip.cfg @@ -22,7 +22,7 @@ -- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE^^J% -- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.^^J% --^^J% --- Copyright (C) 2016-2023 Vít Novotný^^J% +-- Copyright (C) 2016-2024 Vít Starý Novotný^^J% --^^J% -- This work may be distributed and/or modified under the^^J% -- conditions of the LaTeX Project Public License, either version 1.3^^J% @@ -35,7 +35,7 @@ -- version 2005/12/01 or later.^^J% --^^J% -- This work has the LPPL maintenance status `maintained'.^^J% --- The Current Maintainer of this work is Vít Novotný.^^J% +-- The Current Maintainer of this work is Vít Starý Novotný.^^J% --^^J% -- Send bug reports, requests for additions and questions^^J% -- either to the GitHub issue tracker at^^J% @@ -62,7 +62,7 @@ \let\luapostamble\empty \declarepreamble\texpreamble -Copyright (C) 2016-2023 Vít Novotný +Copyright (C) 2016-2024 Vít Starý Novotný This work may be distributed and/or modified under the conditions of the LaTeX Project Public License, either version 1.3c @@ -72,7 +72,7 @@ The latest version of this license is in http://www.latex-project.org/lppl.txt This work has the LPPL maintenance status `maintained'. -The Current Maintainer of this work is Vít Novotný. +The Current Maintainer of this work is Vít Starý Novotný. Send bug reports, requests for additions and questions either to the GitHub issue tracker at diff --git a/Master/texmf-dist/source/generic/markdown/markdown.dtx b/Master/texmf-dist/source/generic/markdown/markdown.dtx index 3edb022e15a..26b5db62f10 100644 --- a/Master/texmf-dist/source/generic/markdown/markdown.dtx +++ b/Master/texmf-dist/source/generic/markdown/markdown.dtx @@ -104,13 +104,7 @@ \newunicodechar{☒}{\markdownRendererTickedBox} \newunicodechar{⌛}{\markdownRendererHalfTickedBox} \newunicodechar{☐}{\markdownRendererUntickedBox} -\usepackage{emoji} -\makeatletter -\@ifpackagelater{emoji}{2020/03/16}{ - \newunicodechar{😉}{\emoji{winking-face}} -}{ - \newunicodechar{😉}{;-)} -} +\newunicodechar{😉}{;-)} \makeatother % Set up the catcodes. @@ -122,7 +116,7 @@ --- title: A Markdown Interpreter for \TeX url: https://github.com/witiko/markdown -authors: [Vít Novotný] +authors: [Vít Starý Novotný] email: witiko@mail.muni.cz revision: \markdownVersion date: \markdownLastModified @@ -763,23 +757,17 @@ abbr { author = {Knuth, Donald Ervin}, year = {1986}, title = {\TeX: The Program}, - isbn = {0-201-13437-7}, + isbn = {978-0-201-13437-7}, pagetotal = {xvi, 594}, series = {Computers \& Typesetting}, volume = {B}, location = {Reading, MA}, publisher = {Addison-Wesley}} -@online{novotny21, - author = {Novotný, Vít}, - title = {\Hologo{LaTeX2e} no longer keys packages by pathnames}, - date = {2021-02-20}, - url = {https://github.com/latex3/latex2e/issues/510}, - urldate = {2021-02-21}} @book{eijkhout92, author = {Victor Eijkhout}, title = {\TeX{} by Topic}, subtitle = {A \TeX nician's Reference}, - isbn = {0-201-56882-0}, + isbn = {978-0-201-56882-0}, pagetotal = {307}, date = {1992-02-01}, location = {Wokingham, England}, @@ -792,50 +780,23 @@ abbr { pages = {196-205}, doi = {10.1109/ICPC.2010.41}} %</techdoc-bibliography> -%<*latex-themes-witiko-markdown-techdoc> -\ProvidesPackage{markdownthemewitiko_markdown_techdoc}[2022/02/23] +%<@@=markdown> +%<*themes-witiko-markdown-techdoc> +\ProvidesPackage{markdownthemewitiko_markdown_techdoc}[2022/12/13] \RequirePackage{etoolbox} \markdownSetup{ renderers = { - head*Four = {% - \paragraph{#1}\leavevmode - \def\markdownRendererInterblockSeparator{% - \let\markdownRendererInterblockSeparator\par - }% - \noindent - }, + head*Four = {\paragraph{#1}\leavevmode}, }, rendererPrototypes = { codeSpan = {\inline{#1}}, - jek*llDataEnd = {% + jekyllData(End) = {% \AfterEndPreamble{% \printtitlepage \tableofcontents {\def\addcontentsline##1##2##3{}\listoffigures}% }% }, - headerAttributeContextBegin = { - \markdownSetup{ - rendererPrototypes = { - attributeIdentifier = {% - \begingroup - \def\next####1{% - \def####1########1{% - \endgroup - ####1{########1}% - \label{sec:##1}% - }% - }% - \next\markdownRendererHeadingOne - \next\markdownRendererHeadingTwo - \next\markdownRendererHeadingThree - \next\markdownRendererHeadingFour - \next\markdownRendererHeadingFive - \next\markdownRendererHeadingSix - }, - }, - }% - }, }, jekyllDataRenderers = { /authors/* = {% @@ -853,6 +814,17 @@ abbr { url = {\gdef\ltd@title@url{#1}}, } } +\ExplSyntaxOn +\markdownSetup{ + rendererPrototypes = { + headerAttributeContextEnd = { + \seq_map_inline:Nn + \l_@@_header_identifiers_seq + { \label { sec:##1 } } + }, + }, +} +\ExplSyntaxOff \renewcommand\markdownLaTeXRendererDirectOrIndirectLink[4]{% #1\footnote{See \url{#3}.}} \RequirePackage{varioref} @@ -884,12 +856,12 @@ abbr { dlEnd = {\end{optionlist}}, } } -%</latex-themes-witiko-markdown-techdoc> +%</themes-witiko-markdown-techdoc> %<*manual> --- title: Markdown Package User Manual -author: Vít Novotný +author: Vít Starý Novotný date: \markdownVersion{} \markdownLastModified{} --- @@ -901,7 +873,7 @@ date: \markdownVersion{} \markdownLastModified{} Introduction ============ -The [Markdown package][pkg] converts [markdown][] markup to \TeX{} commands. The +The [Markdown package][pkg] converts [CommonMark][] markup to \TeX{} commands. The functionality is provided both as a Lua module and as plain \TeX{}, \LaTeX{}, and \Hologo{ConTeXt} macro packages that can be used to directly typeset \TeX{} documents containing markdown markup. Unlike other convertors, the Markdown package @@ -909,10 +881,10 @@ does not require any external programs, and makes it easy to redefine how each and every markdown element is rendered. Creative abuse of the markdown syntax is encouraged. 😉 - [markdown]: https://daringfireball.net/projects/markdown/basics - (Daring Fireball: Markdown Basics) - [pkg]: https://ctan.org/pkg/markdown - (CTAN: Package markdown) + [commonmark]: https://commonmark.org/ + (CommonMark: A strongly defined, highly compatible specification of Markdown) + [pkg]: https://ctan.org/pkg/markdown + (CTAN: Package markdown) % This document is a technical documentation for the \pkg{Markdown} package. It % consists of three sections. This section introduces the package and outlines @@ -942,9 +914,9 @@ documentation][techdoc]. local metadata = { version = "(((VERSION)))", comment = "A module for the conversion from markdown to plain TeX", - author = "John MacFarlane, Hans Hagen, Vít Novotný", + author = "John MacFarlane, Hans Hagen, Vít Starý Novotný", copyright = {"2009-2016 John MacFarlane, Hans Hagen", - "2016-2023 Vít Novotný"}, + "2016-2023 Vít Starý Novotný"}, license = "LPPL 1.3c" } @@ -994,15 +966,19 @@ make base `````` This should produce the following files: -* `markdown.lua`, the Lua module, -* `libraries/markdown-tinyyaml.lua`, an external library for reading \acro{yaml}, -* `markdown-cli.lua`, the Lua command-line interface, -* `markdown.tex`, the plain \TeX{} macro package, -* `markdown.sty`, the \LaTeX{} package, -* `markdownthemewitiko_dot.sty`, the `witiko/dot` \LaTeX{} theme, -* `markdownthemewitiko_graphicx_http.sty`, the `witiko/graphicx/http` \LaTeX{} theme, -* `markdownthemewitiko_tilde.sty`, the `witiko/tilde` \LaTeX{} theme, and -* `t-markdown.tex`, the \Hologo{ConTeXt} module. +* `markdown.lua`: The Lua module +* `libraries/markdown-tinyyaml.lua`: An external library for reading \acro{yaml} +* `markdown-cli.lua`: The Lua command-line interface +* `markdown.tex`: The plain \TeX{} macro package +* `markdown.sty`: The \LaTeX{} package +* `markdownthemewitiko_dot.sty`: The `witiko/dot` \LaTeX{} theme +* `markdownthemewitiko_graphicx_http.sty`: The `witiko/graphicx/http` \LaTeX{} theme +* `markdownthemewitiko_tilde.tex`: The `witiko/tilde` theme +* `markdownthemewitiko_markdown_defaults.tex`, + `markdownthemewitiko_markdown_defaults.sty`, and + `t-markdownthemewitiko_markdown_defaults.tex`: The `witiko/markdown/defaults` + theme +* `t-markdown.tex`: The \Hologo{ConTeXt} module ### Local Installation @@ -1014,11 +990,14 @@ placed: * `⟨TEXMF⟩/tex/luatex/markdown/markdown-tinyyaml.lua` * `⟨TEXMF⟩/scripts/markdown/markdown-cli.lua` * `⟨TEXMF⟩/tex/generic/markdown/markdown.tex` +* `⟨TEXMF⟩/tex/generic/markdown/markdownthemewitiko_tilde.tex` +* `⟨TEXMF⟩/tex/generic/markdown/markdownthemewitiko_markdown_defaults.tex` * `⟨TEXMF⟩/tex/latex/markdown/markdown.sty` * `⟨TEXMF⟩/tex/latex/markdown/markdownthemewitiko_dot.sty` * `⟨TEXMF⟩/tex/latex/markdown/markdownthemewitiko_graphicx_http.sty` -* `⟨TEXMF⟩/tex/latex/markdown/markdownthemewitiko_tilde.sty` +* `⟨TEXMF⟩/tex/latex/markdown/markdownthemewitiko_markdown_defaults.sty` * `⟨TEXMF⟩/tex/context/third/markdown/t-markdown.tex` +* `⟨TEXMF⟩/tex/context/third/markdown/t-markdownthemewitiko_markdown_defaults.tex` where `⟨TEXMF⟩` corresponds to a root of your \TeX{} distribution, such as `/usr/share/texmf` and `~/texmf` on UN\*X systems or @@ -1031,10 +1010,20 @@ Alternatively, you can also store the above files in the same folder as your \TeX{} document and distribute them together. This way your document can be portably typeset on legacy \TeX{} distributions. -Since Markdown version 2.10.0, the file `markdown.tex` must be placed in -a directory named `markdown` due to [issue #74][issue-74]. - - [issue-74]: https://github.com/witiko/markdown/issues/74 (Does not compile if user names own document markdown.tex) +This is where the individual files should be placed: + +* `./markdown.lua` +* `./markdown-tinyyaml.lua` +* `./markdown-cli.lua` +* `./markdown/markdown.tex` +* `./markdown.sty` +* `./t-markdown.tex` +* `./markdownthemewitiko_dot.sty` +* `./markdownthemewitiko_graphicx_http.sty` +* `./markdownthemewitiko_tilde.tex` +* `./markdownthemewitiko_markdown_defaults.tex` +* `./markdownthemewitiko_markdown_defaults.sty` +* `./t-markdownthemewitiko_markdown_defaults.tex` %</manual> %<*lua> @@ -1077,23 +1066,7 @@ local lpeg = require("lpeg") % % \end{markdown} % \begin{macrocode} -local unicode -(function() - local ran_ok - ran_ok, unicode = pcall(require, "unicode") -% \end{macrocode} -% \begin{markdown} -% -% If the Selene Unicode library is unavailable (could be because we are using -% LuaMeta\TeX) and we are using Lua${}\geq{}$5.3, we will use the built-in -% support for Unicode. -% -% \end{markdown} -% \begin{macrocode} - if not ran_ok then - unicode = {utf8 = {char=utf8.char}} - end -end)() +local unicode = require("unicode") % \end{macrocode} % \par % \begin{markdown} @@ -1133,22 +1106,10 @@ local md5 = require("md5"); % \begin{macrocode} local should_initialize = package.loaded.kpse == nil or tex.initialize ~= nil - local ran_ok - ran_ok, kpse = pcall(require, "kpse") - if ran_ok and should_initialize then + kpse = require("kpse") + if should_initialize then kpse.set_program_name("luatex") end -% \end{macrocode} -% \begin{markdown} -% -% If the \pkg{Kpathsea} library is unavailable, we will look up files only -% in the current working directory. -% -% \end{markdown} -% \begin{macrocode} - if not ran_ok then - kpse = {lookup = function(f, _) return f end} - end end)() % \end{macrocode} % \par @@ -1164,35 +1125,7 @@ end)() % % \end{markdown} % \begin{macrocode} -local uni_case -(function() - local ran_ok - ran_ok, uni_case = pcall(require, "lua-uni-case") -% \end{macrocode} -% \begin{markdown} -% -% If the lua-uni-algos library is unavailable but the Selene Unicode library -% is available, we will use its Unicode lower-casing support instead of -% the more proper case-folding. -% -% \end{markdown} -% \begin{macrocode} - if not ran_ok then - if unicode.utf8.lower then - uni_case = {casefold = unicode.utf8.lower} - else -% \end{macrocode} -% \begin{markdown} -% -% If the Selene Unicode library is also unavailable, we will defer to using -% ASCII lower-casing. -% -% \end{markdown} -% \begin{macrocode} - uni_case = {casefold = string.lower} - end - end -end)() +local uni_algos = require("lua-uni-algos") % \end{macrocode} % \par % \begin{markdown} @@ -1229,10 +1162,16 @@ end)() % % \end{markdown} % \begin{macrocode} -%<@@=markdown> +%</tex> +%<*context> +\unprotect +%</context> +%<*context,tex> \ifx\ExplSyntaxOn\undefined - \input expl3-generic\relax + \input expl3-generic \fi +%</context,tex> +%<*tex> % \end{macrocode} % \begin{markdown} % @@ -1264,11 +1203,10 @@ end)() % \TeX{} part of the package will require that either the Lua\TeX{} % `\directlua` primitive or the shell access file stream 18 is available in % your \TeX{} engine. If only the shell access file stream is available in your -% \TeX{} engine (as is the case with \hologo{pdfTeX} and \Hologo{XeTeX}) or if -% you enforce the use of shell using the \mref{markdownMode} macro, then unless -% your \TeX{} engine is globally configured to enable shell access, you will -% need to provide the `-shell-escape` parameter to your engine when typesetting -% a document. +% \TeX{} engine (as is the case with \hologo{pdfTeX} and \Hologo{XeTeX}), then +% unless your \TeX{} engine is globally configured to enable shell access, you +% will need to provide the `-shell-escape` parameter to your engine when +% typesetting a document. % % \end{markdown} % \iffalse @@ -1284,13 +1222,13 @@ end)() % loaded, % \end{markdown} % \iffalse -%<*latex-themes-witiko-dot,latex-themes-witiko-graphicx-http,latex-themes-witiko-tilde> +%<*themes-witiko-dot,latex-themes-witiko-graphicx-http> % \fi % \begin{macrocode} \NeedsTeXFormat{LaTeX2e}% % \end{macrocode} % \iffalse -%</latex-themes-witiko-dot,latex-themes-witiko-graphicx-http,latex-themes-witiko-tilde> +%</themes-witiko-dot,latex-themes-witiko-graphicx-http> % \fi % \begin{markdown} % a \TeX{} engine that extends \Hologo{eTeX}, and all the plain \TeX{} @@ -1299,9 +1237,9 @@ end)() % The following packages are soft prerequisites. They are only used to provide % default token renderer prototypes (see sections % <#sec:texrendererprototypes> and -% <#sec:latex-default-renderer-prototypes>) or \LaTeX{} themes (see Section -% <#sec:latexthemes>) and will not be loaded if the `plain` package option -% has been enabled (see Section <#sec:latexplain>): +% <#sec:latex-token-renderer-prototypes>) or \LaTeX{} themes (see Section +% <#sec:latexthemes>) and will not be loaded if the option `plain` has been +% enabled (see Section <#sec:plain>): % % \pkg{url} % @@ -1333,7 +1271,7 @@ end)() % \pkg{csvsimple} % %: A package that provides the `\csvautotabular` macro for typesetting -% \acro{csv} files in the default renderer prototypes for iA\,Writer +% \acro{csv} files in the default renderer prototypes for iA\\,Writer % content blocks. % % \pkg{gobble} @@ -1378,7 +1316,7 @@ end)() % \pkg{soulutf8} % %: A package that is used in the default renderer prototype for -% strike-throughs. +% strike-throughs and marked text. % % \pkg{ltxcmds} % @@ -1411,7 +1349,7 @@ end)() % \pkg{m-database} % %: A module that provides the default token renderer prototype for -% iA\,Writer content blocks with the \acro{csv} filename extension (see +% iA\\,Writer content blocks with the \acro{csv} filename extension (see % Section <#sec:texrendererprototypes>). % % Feedback @@ -1498,7 +1436,11 @@ Using a text editor, create a text document named `document.tex` with the following content: ``` tex \input markdown +\begingroup +\catcode`\%=12 +\catcode`\#=12 \input hello +\endgroup \bye ``````` @@ -1508,8 +1450,8 @@ Using a text editor, create a text document named `hello.lua` with the following content: ``` lua #!/usr/bin/env texlua -local ran_ok, kpse = pcall(require, "kpse") -if ran_ok then kpse.set_program_name("luatex") end +local kpse = require("kpse") +kpse.set_program_name("luatex") local markdown = require("markdown") local convert = markdown.new() print(convert("Hello *world*!")) @@ -1569,10 +1511,35 @@ Next, invoke LuaTeX from the terminal: luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the text -“Hello *world*!” Invoking pdfTeX should have the same effect: +“Hello *world*!” + +Instead of LuaTeX, you may also use pdfTeX: ``` sh pdftex --shell-escape document.tex ``````` +This should also produce a PDF document named `document.pdf` with the same content. + +*** + +Instead of writing your markdown document between `\markdownBegin` and +`\markdownEnd`, you can also include markdown documents using the +`\markdownInput` macro, similarly to how you might use the `\input` TeX +primitive to include \TeX{} documents. + +Using a text editor, create a text document named `hello.md` with the +following content: +``` md +Hello *world*! +`````` +Create also a text document named `document.tex` with the following content: +``` tex +\input markdown +\markdownInput{hello.md} +\bye +``````` +Next, invoke LuaTeX or pdfTeX from the terminal like in the previous example. +A PDF document named `document.pdf` should be produced and contain the text +“Hello *world*!” ### Using \LaTeX{} @@ -1592,10 +1559,39 @@ Next, invoke LuaTeX from the terminal: lualatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the text “Hello -*world*!” Invoking pdfTeX should have the same effect: +*world*!” + +Instead of LuaTeX, you may also use pdfTeX: ``` sh pdflatex --shell-escape document.tex `````` +This should also produce a PDF document named `document.pdf` with the same content. + +*** + +Instead of writing your markdown document between `\begin{markdown}` and +`\end{markdown}`, you can also include markdown documents using the +`\markdownInput` macro, similarly to how you might use the `\input` TeX +primitive to include \LaTeX{} documents. + +Using a text editor, create a text document named `hello.md` with the +following content: +``` md +Hello *world*! +`````` +Create also a text document named `document.tex` with the following content: +``` tex +\documentclass{article} +\usepackage{markdown} +\begin{document} +\begin{markdown} +\markdownInput{hello.md} +\end{markdown} +\end{document} +``````` +Next, invoke LuaTeX or pdfTeX from the terminal like in the previous example. +A PDF document named `document.pdf` should be produced and contain the text +“Hello *world*!” *** @@ -1629,25 +1625,48 @@ Hello *world*! ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the text “Hello *world*!” *** +Instead of writing your markdown document between `\startmarkdown` and +`\stopmarkdown`, you can also include markdown documents using the +`\inputmarkdown` macro, similarly to how you might use the `\input` TeX +primitive to include \Hologo{ConTeXt} documents. + +Using a text editor, create a text document named `hello.md` with the +following content: +``` md +Hello *world*! +`````` +Create also a text document named `document.tex` with the following content: +``` tex +\usemodule[t][markdown] +\starttext +\inputmarkdown{hello.md} +\stoptext +``````` +Next, invoke LuaTeX from the terminal like in the previous example. +A PDF document named `document.pdf` should be produced and contain the text +“Hello *world*!” + +*** + As the next step, try typesetting the example documents distributed along with the Markdown package: ``` sh git clone https://github.com/witiko/markdown cd markdown/examples -context context.tex +context --luatex context.tex `````` A PDF document named `context.pdf` should be produced. Open the text documents `context.tex` and `example.md` in a text editor to see how the example documents are structured. Try changing the documents and typesetting them as follows: ``` sh -context context.tex +context --luatex context.tex `````` to see the effect of your changes. @@ -1674,6 +1693,7 @@ Each part will be shown by example, leaving the implementation details to the % Lua Interface {#luainterface} %--------------- % +% \end{markdown} % \iffalse %</lua> %<*manual-interfaces> @@ -1700,6 +1720,7 @@ module and a command-line interface (CLI). %</manual-interfaces> %<*lua> % \fi +% \begin{markdown} % % The Lua interface provides the conversion from \acro{utf}-\oldstylenums8 % encoded markdown to plain \TeX{}. This interface is used by the plain \TeX{} @@ -1755,8 +1776,11 @@ local walkable_syntax = { "ThematicBreak", "BulletList", "OrderedList", - "Heading", "DisplayHtml", + "Heading", + }, + BlockOrParagraph = { + "Block", "Paragraph", "Plain", }, @@ -1764,11 +1788,8 @@ local walkable_syntax = { "Str", "Space", "Endline", - "UlOrStarLine", - "Strong", - "Emph", - "Link", - "Image", + "EndlineBreak", + "LinkAndEmph", "Code", "AutoLinkUrl", "AutoLinkEmail", @@ -1794,9 +1815,9 @@ local walkable_syntax = { % not be interpreted by the Markdown package; you can treat it as a comment. % % For example. if we'd like to insert `pattern` into the grammar between the -% `Inline -> Emph` and `Inline -> Link` rules, we would call -% \luamref{reader->insert_pattern} with `"Inline after Emph"` (or `"Inline -% before Link"`) and `pattern` as the arguments. +% `Inline -> LinkAndEmph` and `Inline -> Code` rules, we would call +% \luamref{reader->insert_pattern} with `"Inline after LinkAndEmph"` (or `"Inline +% before Code"`) and `pattern` as the arguments. % % The \luamref{reader->add_special_character} method adds a new character with % special meaning to the grammar of markdown. The method receives the character @@ -1824,26 +1845,30 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \input example +\endgroup \bye ``````` Using a text editor, create a text document named `example.lua` with the following content: ``` lua #!/usr/bin/env texlua -local ran_ok, kpse = pcall(require, "kpse") -if ran_ok then kpse.set_program_name("luatex") end +local kpse = require("kpse") +kpse.set_program_name("luatex") local markdown = require("markdown") -local input, convert_safe, convert_unsafe, paragraph +local input, convert_nomath, convert_math, paragraph input = [[$\sqrt{-1}$ *equals* $i$.]] -convert_safe = markdown.new() -convert_unsafe = markdown.new({hybrid = true}) +convert_nomath = markdown.new() +convert_math = markdown.new({texMathDollars = true}) paragraph = [[\par]] print( - convert_safe(input) .. paragraph .. - convert_unsafe(input) + convert_nomath(input) .. paragraph .. + convert_math(input) ) ``````` Next, invoke LuaTeX from the terminal: @@ -1876,16 +1901,16 @@ named `document.tex` with the following content: \input lmfonts \directlua{ local markdown = require("markdown") - local input, convert_safe, convert_unsafe, paragraph + local input, convert_nomath, convert_math, paragraph input = [[$\string\sqrt{-1}$ *equals* $i$.]] - convert_safe = markdown.new() - convert_unsafe = markdown.new({hybrid = true}) + convert_nomath = markdown.new() + convert_math = markdown.new({texMathDollars = true}) paragraph = [[\par]] tex.sprint( - convert_safe(input) .. paragraph .. - convert_unsafe(input) + convert_nomath(input) .. paragraph .. + convert_math(input) ) } \bye @@ -1921,11 +1946,12 @@ following content: ``` tex \input markdown \input lmfonts -\input safe - \begingroup \catcode`\%=12 -\input unsafe +\catcode`\#=12 +\input nomath +\par +\input math \endgroup \bye ``````` @@ -1936,8 +1962,8 @@ $\sqrt{-1}$ *equals* $i$. `````` Next, invoke LuaTeX from the terminal: ``` sh -texlua ⟨CLI pathname⟩ -- example.md safe.tex -texlua ⟨CLI pathname⟩ hybrid=true -- example.md unsafe.tex +texlua ⟨CLI pathname⟩ -- example.md nomath.tex +texlua ⟨CLI pathname⟩ tex_math_dollars=true -- example.md math.tex luatex document.tex `````` where \meta{CLI pathname} corresponds to the location of the Lua CLI script file, @@ -1957,8 +1983,8 @@ following text: Invoking pdfTeX should have the same effect: ``` sh -texlua ⟨CLI pathname⟩ -- example.md safe.tex -texlua ⟨CLI pathname⟩ hybrid=true -- example.md unsafe.tex +texlua ⟨CLI pathname⟩ -- example.md nomath.tex +texlua ⟨CLI pathname⟩ tex_math_dollars=true -- example.md math.tex pdftex document.tex `````` @@ -2331,6 +2357,212 @@ interfaces and all the way up to the \LaTeX{} and \Hologo{ConTeXt} interfaces. % \fi % \begin{markdown} % +%### General Behavior +% +% \end{markdown} +% \par +% \iffalse + +#### Option `eagerCache` + +`eagerCache` (default value: `false`) + +% \fi +% \begin{markdown} +% +% \Optitem[false]{eagerCache}{\opt{true}, \opt{false}} +% +: true + + : Converted markdown documents will be cached in \Opt{cacheDir}. This can be + useful for post-processing the converted documents and for recovering + historical versions of the documents from the cache. However, it also + produces a large number of auxiliary files on the disk and obscures the + output of the Lua command-line interface when it is used for plumbing. + + This behavior will always be used if the \Opt{finalizeCache} option is + enabled. + +: false + + : Converted markdown documents will not be cached. This decreases the number + of auxiliary files that we produce and makes it easier to use the Lua + command-line interface for plumbing. + + This behavior will only be used when the \Opt{finalizeCache} option is + disabled. + +% \end{markdown} +% \iffalse + +##### Lua CLI Example {.unnumbered} + +Using a text editor, create a text document named `hello.md` with the +following content: +``` md +Hello *world*! +`````` +Next, invoke LuaTeX from the terminal with the \Opt{eagerCache} option +disabled: +``` sh +texlua ⟨CLI pathname⟩ eagerCache=false -- hello.md hello.tex +``````` +where \meta{CLI pathname} corresponds to the location of the Lua CLI script file, +such as `~/texmf/scripts/markdown/markdown-cli.lua` on UN\*X systems or +`C:\Users\`\meta{Your username}`\texmf\scripts\markdown\markdown-cli.lua` on Windows +systems. Use the command `kpsewhich -a markdown-cli.lua` to locate the Lua CLI +script file using [Kpathsea][]. + +A \TeX{} document named `hello.tex` should be produced and contain the +following code: +``` tex +Hello \markdownRendererEmphasis{world}!\relax +``` + +*** + +Invoke LuaTeX from the terminal again, this time with the \Opt{eagerCache} +option enabled: + +``` tex +texlua ⟨CLI pathname⟩ eagerCache=true -- hello.md hello.tex +``` + +A \TeX{} document named `hello.tex` should be produced and contain the +following code: + +``` tex +\input ./⟨hash⟩.md.tex\relax +``` + +Additionally, a \TeX{} document named `⟨hash⟩.md.tex` should be produced and +contain the following code: + +``` tex +Hello \markdownRendererEmphasis{world}!\relax +``` + +%</manual-options> +%<*tex> +% \fi +% \begin{macrocode} +\@@_add_lua_option:nnn + { eagerCache } + { boolean } + { false } +% \end{macrocode} +% \iffalse +%</tex> +%<*lua,lua-cli> +% \fi +% \begin{macrocode} +defaultOptions.eagerCache = false +% \end{macrocode} +% \par +% \iffalse +%</lua,lua-cli> +%<*manual-options> + +#### Option `singletonCache` + +`singletonCache` (default value: `true`) + +% \fi +% \begin{markdown} +% +% \Optitem[true]{singletonCache}{\opt{true}, \opt{false}} +% +: true + + : Conversion functions produced by the function \luamref{new}`(options)` + will be cached in an LRU cache of size 1 keyed by `options`. This is + more time- and space-efficient than always producing a new conversion + function but may expose bugs related to the idempotence of conversion + functions. + + This has been the default behavior since version 3.0.0 of the Markdown + package. + +: false + + : Every call to the function \luamref{new}`(options)` will produce a new + conversion function that will not be cached. This is slower than + caching conversion functions and may expose bugs related to memory + leaks in the creation of conversion functions, see also issue [#226][1]. + + This was the default behavior until version 3.0.0 of the Markdown package. + + [1]: https://github.com/witiko/markdown/pull/226#issuecomment-1599641634 + +% \end{markdown} +% \iffalse + +##### Lua Module Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +``` tex +\input markdown +\begingroup +\catcode`\%=12 +\catcode`\#=12 +\directlua{ + local markdown = require("markdown") + local convert1 = markdown.new({}) + local convert2 = markdown.new({singletonCache=false}) + local convert3 = markdown.new({singletonCache=true}) + local newline = [[^^J^^J]] + tex.print(tostring(convert1) .. ", ") + tex.print(tostring(convert2) .. ", ") + tex.print(tostring(convert3)) +} +\endgroup +\bye +``````` +Then, invoke LuaTeX from the terminal: +``` sh +luatex document.tex +``````` +A PDF document named `document.pdf` should be produced and contain the following text: + +> function: 0x1a4a038, function: 0x1a52b18, function: 0x1a4a038 + +As you can see, the caching causes `convert1` and `convert3` to be the same +conversion function. + +%</manual-options> +%<*tex> +% \fi +% \begin{macrocode} +\@@_add_lua_option:nnn + { singletonCache } + { boolean } + { true } +% \end{macrocode} +% \iffalse +%</tex> +%<*lua,lua-cli> +% \fi +% \begin{macrocode} +defaultOptions.singletonCache = true +% \end{macrocode} +% \iffalse +%</lua,lua-cli> +%<*lua> +% \fi +% \begin{macrocode} +local singletonCache = { + convert = nil, + options = nil, +} +% \end{macrocode} +% \par +% \iffalse +%</lua> +%<*manual-options> +% \fi +% \begin{markdown} +% %### File and Directory Names % % \end{markdown} @@ -2365,11 +2597,15 @@ Using a text editor, create a text document named `document.tex` with the following content: ``` tex \input markdown +\begingroup +\catcode`\%=12 +\catcode`\#=12 \directlua{ local markdown = require("markdown") local convert = markdown.new({cacheDir = "cache"}) local input = "Hello *world*!" tex.sprint(convert(input)) } +\endgroup \bye ``````` Create an empty directory named `cache` next to our text document. Then, invoke @@ -2387,7 +2623,11 @@ Using a text editor, create a text document named `document.tex` with the following content: ``` tex \input markdown +\begingroup +\catcode`\%=12 +\catcode`\#=12 \input hello +\endgroup \bye ``````` Using a text editor, create a text document named `hello.md` with the @@ -2469,7 +2709,7 @@ Hello *world*! ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the text “Hello *world*!” A directory named `cache` containing several cache files of the @@ -2507,7 +2747,7 @@ defaultOptions.cacheDir = "." % \Valitem[markdown-languages.json]{contentBlocksLanguageMap}{filename} % : The filename of the \acro{JSON} file that maps filename extensions to - programming language names in the iA\,Writer content blocks when the + programming language names in the iA\\,Writer content blocks when the \Opt{contentBlocks} option is enabled. % See Section <#sec:texcontentblockrenderers> for more information. @@ -2621,7 +2861,7 @@ Create also a text document named `document.tex` with the following content: ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -2739,11 +2979,15 @@ Using a text editor, create a text document named `document.tex` with the following content: ``` tex \input markdown +\begingroup +\catcode`\%=12 +\catcode`\#=12 \directlua{ local markdown = require("markdown") local convert = markdown.new({finalizeCache = true, frozenCacheFileName = "cache.tex"}) local input = "Hello *world*!" tex.sprint(convert(input)) } +\endgroup \bye ``````` Then, invoke LuaTeX from the terminal: @@ -2761,7 +3005,11 @@ Using a text editor, create a text document named `document.tex` with the following content: ``` tex \input markdown +\begingroup +\catcode`\%=12 +\catcode`\#=12 \input hello +\endgroup \bye ``````` Using a text editor, create a text document named `hello.md` with the @@ -2891,7 +3139,7 @@ Hello *world*! ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the text “Hello *world*!” A frozen cache will also be produced in the `cache.tex` @@ -2913,9 +3161,9 @@ Hi *world*! \stopmarkdown \stoptext ``````` -Last, invoke pdfTeX without shell access from the terminal: +Last, invoke LuaTeX from the terminal: ``` sh -context frozen-document.tex +context --luatex frozen-document.tex `````` A PDF document named `frozen-document.pdf` should be produced and contain the text “Hello *world*!” Since we used the contents of the frozen cache using @@ -2952,6 +3200,55 @@ defaultOptions.frozenCacheFileName = "frozenCache.tex" % \par % \iffalse +#### Option `autoIdentifiers` + +`autoIdentifiers` (default value: `false`) + +% \fi +% \begin{markdown} +% \Optitem[false]{autoIdentifiers}{\opt{true}, \opt{false}} +% +: true + + : Enable the Pandoc [auto identifiers syntax extension][pandoc-auto-identifiers]: + + ``` md + The following heading received the identifier `sesame-street`: + + # 123 Sesame Street + `````` + + false + + : Disable the Pandoc auto identifiers syntax extension. + +See also the option \Opt{gfmAutoIdentifiers}. + + [pandoc-auto-identifiers]: https://pandoc.org/MANUAL.html#extension-auto_identifiers + +% \end{markdown} +% \iffalse +%</manual-options> +%<*tex> +% \fi +% \begin{macrocode} +\@@_add_lua_option:nnn + { autoIdentifiers } + { boolean } + { false } +% \end{macrocode} +% \iffalse +%</tex> +%<*lua,lua-cli> +% \fi +% \begin{macrocode} +defaultOptions.autoIdentifiers = false +% \end{macrocode} +% \par +% \iffalse +%</lua,lua-cli> +%<*manual-options> + #### Option `blankBeforeBlockquote` `blankBeforeBlockquote` (default value: `false`) @@ -2979,6 +3276,9 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \directlua{ local markdown = require("markdown") local newline = [[^^J^^J]] @@ -2993,6 +3293,7 @@ following content: input = "A paragraph." .. newline .. "> Not a quote." .. newline tex.sprint(convert(input)) } +\endgroup \bye ``````` Then, invoke LuaTeX from the terminal: @@ -3015,8 +3316,12 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \input optionfalse \input optiontrue +\endgroup \bye ``````` Using a text editor, create a text document named `content.md` with the @@ -3093,10 +3398,10 @@ A paragraph. > A quote. \end{markdown} -\begin{markdown*}{blankBeforeBlockquote} +\begin{markdown}[blankBeforeBlockquote] A paragraph. > Not a quote. -\end{markdown*} +\end{markdown} \end{document} ``````` @@ -3136,7 +3441,7 @@ A paragraph. ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -3197,6 +3502,9 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \directlua{ local markdown = require("markdown") local newline = [[^^J^^J]] @@ -3216,6 +3524,7 @@ following content: "Not a fenced code." .. newline .. "```" .. newline tex.sprint(convert(input)) } +\endgroup \bye ``````` Then, invoke LuaTeX from the terminal: @@ -3240,8 +3549,12 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \input optionfalse \input optiontrue +\endgroup \bye ``````` Using a text editor, create a text document named `content.md` with the @@ -3331,12 +3644,12 @@ A fenced code. ``` \end{markdown} -\begin{markdown*}{blankBeforeCodeFence} +\begin{markdown}[blankBeforeCodeFence] A paragraph. ``` Not a fenced code. ``` -\end{markdown*} +\end{markdown} \end{document} ```````` @@ -3383,7 +3696,7 @@ Not a fenced code. ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -3454,13 +3767,13 @@ A fenced div. ::: \end{markdown} -\begin{markdown*}{blankBeforeDivFence} +\begin{markdown}[blankBeforeDivFence] A paragraph. ::: {.identifier} Not a fenced div. ::: -\end{markdown*} +\end{markdown} \end{document} ```````` @@ -3529,6 +3842,9 @@ following content: \input markdown \input lmfonts \def\markdownRendererHeadingOne#1{{\bf #1}\par} +\begingroup +\catcode`\%=12 +\catcode`\#=12 \directlua{ local markdown = require("markdown") local newline = [[^^J^^J]] @@ -3545,6 +3861,7 @@ following content: "Not a heading." .. newline .. "==============" .. newline tex.sprint(convert(input)) } +\endgroup \bye ``````` Then, invoke LuaTeX from the terminal: @@ -3568,8 +3885,12 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \input optionfalse \input optiontrue +\endgroup \bye ``````` Using a text editor, create a text document named `content.md` with the @@ -3652,11 +3973,11 @@ A heading. ========== \end{markdown} -\begin{markdown*}{blankBeforeHeading} +\begin{markdown}[blankBeforeHeading] A paragraph. Not a heading. ============== -\end{markdown*} +\end{markdown} \end{document} ``````` @@ -3699,7 +4020,7 @@ Not a heading. ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -3732,6 +4053,232 @@ defaultOptions.blankBeforeHeading = false %</lua,lua-cli> %<*manual-options> +#### Option `blankBeforeList` + +`blankBeforeList` (default value: `false`) + +% \fi +% \begin{markdown} +% +% \Optitem[false]{blankBeforeList}{\opt{true}, \opt{false}} +% +: true + + : Require a blank line between a paragraph and the following list. + + false + + : Do not require a blank line between a paragraph and the following + list. + +% \end{markdown} +% \iffalse + +##### Lua Module Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +``` tex +\input markdown +\input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 +\directlua{ + local markdown = require("markdown") + local newline = [[^^J^^J]] + local convert, input + + convert = markdown.new() + input = "A paragraph." .. newline .. + "- a list" .. newline + tex.sprint(convert(input)) + + convert = markdown.new({ + blankBeforeList = true}) + input = "A paragraph." .. newline .. + "- not a list" .. newline + tex.sprint(convert(input)) } +\endgroup +\bye +``````` +Then, invoke LuaTeX from the terminal: +``` sh +luatex document.tex +``````` +A PDF document named `document.pdf` should be produced and contain the +following text: + +> A paragraph. +> +> - a list +> +> A paragraph. - not a list + +##### Lua CLI Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +``` tex +\input markdown +\input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 +\input optionfalse +\input optiontrue +\endgroup +\bye +``````` +Using a text editor, create a text document named `content.md` with the +following content: +```` md +A paragraph. +- a list? +``````` +Next, invoke LuaTeX from the terminal: +``` sh +texlua ⟨CLI pathname⟩ -- content.md optionfalse.tex +texlua ⟨CLI pathname⟩ blankBeforeList=true -- content.md optiontrue.tex +luatex document.tex +``````` +where \meta{CLI pathname} corresponds to the location of the Lua CLI script file, +such as `~/texmf/scripts/markdown/markdown-cli.lua` on UN\*X systems or +`C:\Users\`\meta{Your username}`\texmf\scripts\markdown\markdown-cli.lua` on Windows +systems. Use the command `kpsewhich -a markdown-cli.lua` to locate the Lua CLI +script file using [Kpathsea][]. + +A PDF document named `document.pdf` should be produced and contain the +following text: + +> A paragraph. +> +> - a list? +> +> A paragraph. - a list? + +##### Plain \TeX{} Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +```` tex +\input markdown + +\markdownBegin +A paragraph. +- a list +\markdownEnd + +\def\markdownOptionBlankBeforeList{true} +\markdownBegin +A paragraph. +- not a list +\markdownEnd + +\bye +```````` +Next, invoke LuaTeX from the terminal: +``` sh +luatex document.tex +`````` +A PDF document named `document.pdf` should be produced and contain the +following text: + +> A paragraph. +> +> - a list +> +> A paragraph. - not a list + +##### \LaTeX{} Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +```` tex +\documentclass{article} +\usepackage{markdown} +\begin{document} + +\begin{markdown} +A paragraph. +- a list +\end{markdown} + +\begin{markdown}[blankBeforeList] +A paragraph. +- not a list +\end{markdown} + +\end{document} +```````` +Next, invoke LuaTeX from the terminal: +``` sh +lualatex document.tex +`````` +A PDF document named `document.pdf` should be produced and contain the +following text: + +> A paragraph. +> +> - a list +> +> A paragraph. - not a list + +##### \Hologo{ConTeXt} Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +```` tex +\usemodule[t][markdown] +\starttext + +\startmarkdown +A paragraph. +- a list +\stopmarkdown + +\setupmarkdown[blankBeforeList = yes] +\startmarkdown +A paragraph. +- not a list +\stopmarkdown + +\stoptext +```````` +Next, invoke LuaTeX from the terminal: +``` sh +context document.tex +`````` +A PDF document named `document.pdf` should be produced and contain the +following text: + +> A paragraph. +> +> - a list +> +> A paragraph. - not a list + +%</manual-options> +%<*tex> +% \fi +% \begin{macrocode} +\@@_add_lua_option:nnn + { blankBeforeList } + { boolean } + { false } +% \end{macrocode} +% \iffalse +%</tex> +%<*lua,lua-cli> +% \fi +% \begin{macrocode} +defaultOptions.blankBeforeList = false +% \end{macrocode} +% \par +% \iffalse +%</lua,lua-cli> +%<*manual-options> + #### Option `bracketedSpans` `bracketedSpans` (default value: `false`) @@ -3830,12 +4377,12 @@ defaultOptions.bracketedSpans = false #### Option `breakableBlockquotes` -`breakableBlockquotes` (default value: `false`) +`breakableBlockquotes` (default value: `true`) % \fi % \begin{markdown} % -% \Optitem[false]{breakableBlockquotes}{\opt{true}, \opt{false}} +% \Optitem[true]{breakableBlockquotes}{\opt{true}, \opt{false}} % : true @@ -3856,20 +4403,24 @@ following content: \input markdown \input lmfonts \def\markdownRendererHeadingOne#1{{\bf #1}\par} +\begingroup +\catcode`\%=12 +\catcode`\#=12 \directlua{ local markdown = require("markdown") local newline = [[^^J^^J]] local convert, input - convert = markdown.new() + convert = markdown.new({breakableBlockquotes = false}) input = "> A single" .. newline .. newline .. "> block quote." .. newline tex.sprint(convert(input)) - convert = markdown.new({breakableBlockquotes = true}) + convert = markdown.new() input = "> A block quote." .. newline .. newline .. "> Another block quote." .. newline tex.sprint(convert(input)) } +\endgroup \bye ``````` Then, invoke LuaTeX from the terminal: @@ -3892,8 +4443,12 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \input optionfalse \input optiontrue +\endgroup \bye ``````` Using a text editor, create a text document named `content.md` with the @@ -3905,8 +4460,8 @@ following content: `````` Next, invoke LuaTeX from the terminal: ``` sh -texlua ⟨CLI pathname⟩ -- content.md optionfalse.tex -texlua ⟨CLI pathname⟩ breakableBlockquotes=true -- content.md optiontrue.tex +texlua ⟨CLI pathname⟩ breakableBlockquotes=false -- content.md optionfalse.tex +texlua ⟨CLI pathname⟩ -- content.md optiontrue.tex luatex document.tex ``````` where \meta{CLI pathname} corresponds to the location of the Lua CLI script file, @@ -3931,6 +4486,7 @@ following content: ``` tex \input markdown +\def\markdownOptionBreakableBlockquotes{false} \markdownBegin > A single @@ -3968,17 +4524,17 @@ following content: \usepackage{markdown} \begin{document} -\begin{markdown} +\begin{markdown}[breakableBlockquotes = false] > A single > block quote. \end{markdown} -\begin{markdown*}{breakableBlockquotes} +\begin{markdown} > A block quote. > Another block quote. -\end{markdown*} +\end{markdown} \end{document} ``````` @@ -4003,6 +4559,7 @@ following content: \usemodule[t][markdown] \starttext +\setupmarkdown[breakableBlockquotes = no] \startmarkdown > A single @@ -4020,7 +4577,7 @@ following content: ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -4038,14 +4595,14 @@ following text: \@@_add_lua_option:nnn { breakableBlockquotes } { boolean } - { false } + { true } % \end{macrocode} % \iffalse %</tex> %<*lua,lua-cli> % \fi % \begin{macrocode} -defaultOptions.breakableBlockquotes = false +defaultOptions.breakableBlockquotes = true % \end{macrocode} % \par % \iffalse @@ -4099,9 +4656,9 @@ following content: The TeXbook [@knuth:tex, p. 123 and 130] is good. \end{markdown} -\begin{markdown*}{citationNbsps = false} +\begin{markdown}[citationNbsps = false] The TeXbook [@knuth:tex, p. 123 and 130] is good. -\end{markdown*} +\end{markdown} \bibliographystyle{plain} \bibliography{document.bib} @@ -4287,6 +4844,9 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \directlua{ local markdown = require("markdown") local convert = markdown.new() @@ -4302,6 +4862,7 @@ following content: "``This is a quote.'' " .. "``This is another quote.''" tex.sprint(convert(input)) } +\endgroup \bye ``````` Then, invoke LuaTeX from the terminal: @@ -4322,9 +4883,13 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \input optionfalse \par \input optiontrue +\endgroup \bye ``````` Using a text editor, create a text document named `content.md` with the @@ -4396,10 +4961,10 @@ following content: ``This is no longer a code span.'' \end{markdown} -\begin{markdown*}{codeSpans=false} +\begin{markdown}[codeSpans=false] ``This is a quote.'' ``This is another quote.'' -\end{markdown*} +\end{markdown} \end{document} ``````` @@ -4437,7 +5002,7 @@ following content: ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -4479,9 +5044,9 @@ defaultOptions.codeSpans = true : true : Enable the -% iA\,Writer content blocks syntax extension~[@sotkov17]: +% iA\\,Writer content blocks syntax extension~[@sotkov17]: % \iffalse - iA\,Writer content blocks syntax extension: + iA\\,Writer content blocks syntax extension: % \fi ``` md @@ -4496,7 +5061,7 @@ defaultOptions.codeSpans = true : false : Disable the - iA\,Writer content blocks syntax extension. + iA\\,Writer content blocks syntax extension. % \end{markdown} % \iffalse @@ -4601,7 +5166,7 @@ Create also a text document named `document.tex` with the following content: ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -4641,6 +5206,143 @@ defaultOptions.contentBlocks = false %</lua,lua-cli> %<*manual-options> +#### Option `contentLevel` + +`contentLevel` (default value: `block`) + +% \fi +% \begin{markdown} +% +% \Optitem[block]{contentLevel}{\opt{block}, \opt{inline}} +% +: block + + : Treat content as a sequence of blocks. + ``` md + - this is a list + - it contains two items + `````` + +: inline + + : Treat all content as inline content. + ``` md + - this is a text + - not a list + `````` + +% \end{markdown} +% \iffalse + +##### Plain \TeX{} Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +``` tex +\input markdown +\def\markdownOptionContentLevel{inline} +\markdownBegin +- this is +- a text +\markdownEnd +\bye +``````` +Next, invoke LuaTeX from the terminal: +``` sh +luatex document.tex +`````` +A PDF document named `document.pdf` should be produced and contain the +following text: + +> \- this is - a text + +##### \LaTeX{} Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +``` tex +\documentclass{article} +\usepackage{markdown} +\begin{document} + +\begin{markdown} +- this is +- a list +\end{markdown} + +\begin{markdown}[contentLevel=inline] +- this is +- a text +\end{markdown} + +\end{document} +``````` +Next, invoke LuaTeX from the terminal: +``` sh +lualatex document.tex +`````` +A PDF document named `document.pdf` should be produced and contain the +following text: + +> - this is +> - a list +> +> \- this is - a text + +##### \Hologo{ConTeXt} Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +``` tex +\usemodule[t][markdown] +\starttext + +\startmarkdown +- this is +- a list +\stopmarkdown + +\setupmarkdown[contentLevel = inline] +\startmarkdown +- this is +- a text +\stopmarkdown + +\stoptext +```````` +Next, invoke LuaTeX from the terminal: +``` sh +context --luatex document.tex +````` +A PDF document named `document.pdf` should be produced and contain the +following text: + +> - this is +> - a list +> +> \- this is - a text + +%</manual-options> +%<*tex> +% \fi +% \begin{macrocode} +\@@_add_lua_option:nnn + { contentLevel } + { string } + { block } +% \end{macrocode} +% \iffalse +%</tex> +%<*lua,lua-cli> +% \fi +% \begin{macrocode} +defaultOptions.contentLevel = "block" +% \end{macrocode} +% \par +% \iffalse +%</lua,lua-cli> +%<*manual-options> + #### Option `debugExtensions` `debugExtensions` (default value: `false`) @@ -4685,7 +5387,7 @@ following content: ``` lua local strike_through = { api_version = 2, - grammar_version = 2, + grammar_version = 4, finalize_grammar = function(reader) local nonspacechar = lpeg.P(1) - lpeg.S("\t ") local doubleslashes = lpeg.P("//") @@ -4699,7 +5401,7 @@ local strike_through = { lpeg.V("Inline"), doubleslashes, doubleslashes ) / function(s) return {"\\st{", s, "}"} end - reader.insert_pattern("Inline after Emph", read_strike_through, + reader.insert_pattern("Inline after LinkAndEmph", read_strike_through, "StrikeThrough") reader.add_special_character("/") end @@ -4737,24 +5439,28 @@ be produced and contain the following text: "Block": [ "Blockquote", "Verbatim", + "FencedCode (built-in fenced_code syntax extension)", "ThematicBreak", "BulletList", "OrderedList", - "Heading", "DisplayHtml", + "Heading" + ], + "BlockOrParagraph": [ + "Block", "Paragraph", "Plain" ], + "EndlineExceptions": [ + "EndlineExceptions (built-in fenced_code syntax extension)" + ], "Inline": [ "Str", "Space", "Endline", - "UlOrStarLine", - "Strong", - "Emph", + "EndlineBreak", + "LinkAndEmph", "StrikeThrough (user-defined \"./strike-through.lua\" syntax extension)", - "Link", - "Image", "Code", "AutoLinkUrl", "AutoLinkEmail", @@ -4893,7 +5599,7 @@ Term 2 with *inline markup* ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -4931,107 +5637,6 @@ defaultOptions.definitionLists = false %</lua,lua-cli> %<*manual-options> -#### Option `eagerCache` - -`eagerCache` (default value: `true`) - -% \fi -% \begin{markdown} -% -% \Optitem[true]{eagerCache}{\opt{true}, \opt{false}} -% -: true - - : Converted markdown documents will be cached in \Opt{cacheDir}. This can be - useful for post-processing the converted documents and for recovering - historical versions of the documents from the cache. However, it also - produces a large number of auxiliary files on the disk and obscures the - output of the Lua command-line interface when it is used for plumbing. - - This behavior will always be used if the \Opt{finalizeCache} option is - enabled. - -: false - - : Converted markdown documents will not be cached. This decreases the number - of auxiliary files that we produce and makes it easier to use the Lua - command-line interface for plumbing. - - This behavior will only be used when the \Opt{finalizeCache} option is - disabled. Recursive nesting of markdown document fragments is undefined - behavior when \Opt{eagerCache} is disabled. - -% \end{markdown} -% \iffalse - -##### Lua Module Example {.unnumbered} - -Using a text editor, create a text document named `hello.md` with the -following content: -``` md -Hello *world*! -`````` -Next, invoke LuaTeX from the terminal with the \Opt{eagerCache} option -disabled: -``` sh -texlua ⟨CLI pathname⟩ eagerCache=false -- hello.md hello.tex -``````` -where \meta{CLI pathname} corresponds to the location of the Lua CLI script file, -such as `~/texmf/scripts/markdown/markdown-cli.lua` on UN\*X systems or -`C:\Users\`\meta{Your username}`\texmf\scripts\markdown\markdown-cli.lua` on Windows -systems. Use the command `kpsewhich -a markdown-cli.lua` to locate the Lua CLI -script file using [Kpathsea][]. - -A \TeX{} document named `hello.tex` should be produced and contain the -following code: -``` tex -Hello \markdownRendererEmphasis{world}!\relax -``` - -*** - -Invoke LuaTeX from the terminal again, this time with the \Opt{eagerCache} -option enabled: - -``` tex -texlua ⟨CLI pathname⟩ eagerCache=true -- hello.md hello.tex -``` - -A \TeX{} document named `hello.tex` should be produced and contain the -following code: - -``` tex -\input ./⟨hash⟩.md.tex\relax -``` - -Additionally, a \TeX{} document named `⟨hash⟩.md.tex` should be produced and -contain the following code: - -``` tex -Hello \markdownRendererEmphasis{world}!\relax -``` - -%</manual-options> -%<*tex> -% \fi -% \begin{macrocode} -\@@_add_lua_option:nnn - { eagerCache } - { boolean } - { true } -% \end{macrocode} -% \iffalse -%</tex> -%<*lua,lua-cli> -% \fi -% \begin{macrocode} -defaultOptions.eagerCache = true -% \end{macrocode} -% \par -% \iffalse -%</lua,lua-cli> -%<*manual-options> - #### Option `expectJekyllData` `expectJekyllData` (default value: `false`) @@ -5175,7 +5780,7 @@ defaultOptions.expectJekyllData = false % ``` lua % local strike_through = { % api_version = 2, -% grammar_version = 2, +% grammar_version = 4, % finalize_grammar = function(reader) % local nonspacechar = lpeg.P(1) - lpeg.S("\t ") % local doubleslashes = lpeg.P("//") @@ -5189,7 +5794,7 @@ defaultOptions.expectJekyllData = false % lpeg.V("Inline"), doubleslashes, doubleslashes % ) / function(s) return {"\\st{", s, "}"} end % -% reader.insert_pattern("Inline after Emph", read_strike_through, +% reader.insert_pattern("Inline after LinkAndEmph", read_strike_through, % "StrikeThrough") % reader.add_special_character("/") % end @@ -5210,7 +5815,7 @@ defaultOptions.expectJekyllData = false % \fi % \begin{macrocode} metadata.user_extension_api_version = 2 -metadata.grammar_version = 2 +metadata.grammar_version = 4 % \end{macrocode} % \iffalse %</lua> @@ -5218,17 +5823,17 @@ metadata.grammar_version = 2 % \fi % \begin{markdown} % -% Any changes to the syntax extension API or grammar will cause the -% corresponding current version to be incremented. After Markdown 3.0.0, -% any changes to the API and the grammar will be either backwards-compatible -% or constitute a breaking change that will cause the major version of the -% Markdown package to increment (to 4.0.0). +% Any changes to the syntax extension API or grammar will cause the +% corresponding current version to be incremented. After Markdown 3.0.0, +% any changes to the API and the grammar will be either backwards-compatible +% or constitute a breaking change that will cause the major version of the +% Markdown package to increment (to 4.0.0). % -% The `finalize_grammar` field is a function that finalizes the grammar of -% markdown using the interface of a Lua \luamref{reader} object, such as -% the \luamref{reader->insert_pattern} and -% \luamref{reader->add_special_character} methods, -% see Section <#lua-user-extensions>. +% The `finalize_grammar` field is a function that finalizes the grammar of +% markdown using the interface of a Lua \luamref{reader} object, such as +% the \luamref{reader->insert_pattern} and +% \luamref{reader->add_special_character} methods, +% see Section <#sec:lua-user-extensions>. % % \end{markdown} % \iffalse @@ -5240,7 +5845,7 @@ following content: ``` lua local strike_through = { api_version = 2, - grammar_version = 2, + grammar_version = 4, finalize_grammar = function(reader) local nonspacechar = lpeg.P(1) - lpeg.S("\t ") local doubleslashes = lpeg.P("//") @@ -5254,7 +5859,7 @@ local strike_through = { lpeg.V("Inline"), doubleslashes, doubleslashes ) / function(s) return {"\\st{", s, "}"} end - reader.insert_pattern("Inline after Emph", read_strike_through, + reader.insert_pattern("Inline after LinkAndEmph", read_strike_through, "StrikeThrough") reader.add_special_character("/") end @@ -5379,7 +5984,7 @@ c) third item ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -5411,12 +6016,12 @@ defaultOptions.fancyLists = false #### Option `fencedCode` -`fencedCode` (default value: `false`) +`fencedCode` (default value: `true`) % \fi % \begin{markdown} % -% \Optitem[false]{fencedCode}{\opt{true}, \opt{false}} +% \Optitem[true]{fencedCode}{\opt{true}, \opt{false}} % : true @@ -5455,7 +6060,7 @@ following content: ```` tex \documentclass{article} \usepackage{minted} -\usepackage[fencedCode]{markdown} +\usepackage{markdown} \begin{document} \begin{markdown} ~~~ js @@ -5489,7 +6094,7 @@ following text: > moveShip(5 * gravity, DOWN); > } > ``` -> +> > ``` html > <pre> > <code> @@ -5507,7 +6112,6 @@ Using a text editor, create a text document named `document.tex` with the following content: ```` tex \usemodule[t][markdown] -\setupmarkdown[fencedCode = yes] \definetyping [js] \definetyping [html] \setuptyping [html] [option=XML] @@ -5534,7 +6138,7 @@ if (a > 3) { ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -5544,7 +6148,7 @@ following text: > moveShip(5 * gravity, DOWN); > } > ``` -> +> > ``` html > <pre> > <code> @@ -5563,14 +6167,14 @@ following text: \@@_add_lua_option:nnn { fencedCode } { boolean } - { false } + { true } % \end{macrocode} % \iffalse %</tex> %<*lua,lua-cli> % \fi % \begin{macrocode} -defaultOptions.fencedCode = false +defaultOptions.fencedCode = true % \end{macrocode} % \par % \iffalse @@ -5873,7 +6477,7 @@ Hello *world*! ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the text “Hello *world*!” A frozen cache will also be produced in the `cache.tex` @@ -5889,9 +6493,9 @@ Hi *world*! \stopmarkdown \stoptext ``````` -Last, invoke pdfTeX without shell access from the terminal: +Last, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A new PDF document named `document.pdf` should be produced and contain the same text “Hello *world*!” Since we used the contents of the frozen cache using @@ -5947,6 +6551,9 @@ Using a text editor, create a text document named `document.tex` with the following content: ``` tex \input markdown +\begingroup +\catcode`\%=12 +\catcode`\#=12 \directlua{ local markdown = require("markdown") local firstConvert = markdown.new({finalizeCache = true, frozenCacheCounter = 0}) @@ -5954,6 +6561,7 @@ following content: local secondConvert = markdown.new({finalizeCache = true, frozenCacheCounter = 1}) local secondInput = "*world*!" tex.sprint(firstConvert(firstInput) .. [[ ]] .. secondConvert(secondInput)) } +\endgroup \bye ``````` Then, invoke LuaTeX from the terminal: @@ -5985,91 +6593,49 @@ defaultOptions.frozenCacheCounter = 0 %</lua,lua-cli> %<*manual-options> -#### Option `hardLineBreaks` +#### Option `gfmAutoIdentifiers` -`hardLineBreaks` (default value: `false`) +`gfmAutoIdentifiers` (default value: `false`) % \fi % \begin{markdown} -% -% \Optitem[false]{hardLineBreaks}{\opt{true}, \opt{false}} +% \Optitem[false]{gfmAutoIdentifiers}{\opt{true}, \opt{false}} % : true - : Interpret all newlines within a paragraph as hard line breaks instead - of spaces. + : Enable the Pandoc [GitHub-flavored auto identifiers syntax extension][pandoc-gfm-auto-identifiers]: -: false - - : Interpret all newlines within a paragraph as spaces. - -% \end{markdown} -% \iffalse - -##### \LaTeX{} Example {.unnumbered} + ``` md + The following heading received the identifier `123-sesame-street`: -Using a text editor, create a text document named `document.tex` with the -following content: -``` tex -\documentclass{article} -\usepackage{markdown} -\begin{document} + # 123 Sesame Street + `````` -\begin{markdown} -'Twas brillig, and the slithy toves -Did gyre and gimble in the wabe; -All mimsy were the borogoves, -And the mome raths outgrabe. -\end{markdown} + false -\begin{markdown*}{hardLineBreaks} -'Twas brillig, and the slithy toves -Did gyre and gimble in the wabe; -All mimsy were the borogoves, -And the mome raths outgrabe. -\end{markdown*} + : Disable the Pandoc GitHub-flavored auto identifiers syntax extension. -\end{document} -``````` -Next, invoke LuaTeX from the terminal: -``` sh -lualatex document.tex -`````` -A PDF document named `document.pdf` should be produced and contain the -following text: +See also the option \Opt{autoIdentifiers}. -> 'Twas brillig, and the slithy toves -> Did gyre and gimble in the wabe; -> All mimsy were the borogoves, -> And the mome raths outgrabe. -> -> 'Twas brillig, and the slithy toves -> Did gyre and gimble in the wabe; -> All mimsy were the borogoves, -> And the mome raths outgrabe. + [pandoc-gfm-auto-identifiers]: https://pandoc.org/MANUAL.html#extension-gfm_auto_identifiers +% \end{markdown} +% \iffalse %</manual-options> %<*tex> % \fi % \begin{macrocode} \@@_add_lua_option:nnn - { hardLineBreaks } + { gfmAutoIdentifiers } { boolean } { false } % \end{macrocode} -% \begin{markdown} -% -% The hardLineBreaks option has been deprecated and will be removed in -% Markdown 3.0.0. From then on, all line breaks within a paragraph will -% be interpreted as soft line breaks. -% -% \end{markdown} % \iffalse %</tex> %<*lua,lua-cli> % \fi % \begin{macrocode} -defaultOptions.hardLineBreaks = false +defaultOptions.gfmAutoIdentifiers = false % \end{macrocode} % \par % \iffalse @@ -6119,11 +6685,11 @@ following content: #. Parish \end{markdown} -\begin{markdown*}{hashEnumerators} +\begin{markdown}[hashEnumerators] #. Bird #. McHale #. Parish -\end{markdown*} +\end{markdown} \end{document} ``````` @@ -6170,7 +6736,7 @@ following content: ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -6258,12 +6824,12 @@ defaultOptions.headerAttributes = false #### Option `html` -`html` (default value: `false`) +`html` (default value: `true`) % \fi % \begin{markdown} % -% \Optitem[false]{html}{\opt{true}, \opt{false}} +% \Optitem[true]{html}{\opt{true}, \opt{false}} % : true @@ -6288,9 +6854,12 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \directlua{ local markdown = require("markdown") - local convert = markdown.new() + local convert = markdown.new({html = false}) local newline = [[^^J^^J]] local input = "<div>*There is no block tag support.*</div>" .. newline .. @@ -6301,13 +6870,14 @@ following content: \par \directlua{ local markdown = require("markdown") - local convert = markdown.new({html = true}) + local convert = markdown.new() local input = "<div>*There is block tag support.*</div>" .. newline .. "*There is <inline tag="tag"></inline> support.*" .. newline .. "_There is <!-- comment --> support._" .. newline .. "_There is <? HTML instruction ?> support._" tex.sprint(convert(input)) } +\endgroup \bye ``````` Then, invoke LuaTeX from the terminal: @@ -6331,9 +6901,13 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \input optionfalse \par \input optiontrue +\endgroup \bye ``````` Using a text editor, create a text document named `content.md` with the @@ -6348,8 +6922,8 @@ _Is there <? HTML instruction ?> support?_ ```````` Next, invoke LuaTeX from the terminal: ``` sh -texlua ⟨CLI pathname⟩ -- content.md optionfalse.tex -texlua ⟨CLI pathname⟩ html=true -- content.md optiontrue.tex +texlua ⟨CLI pathname⟩ html=false -- content.md optionfalse.tex +texlua ⟨CLI pathname⟩ -- content.md optiontrue.tex luatex document.tex ``````` where \meta{CLI pathname} corresponds to the location of the Lua CLI script file, @@ -6376,6 +6950,7 @@ following content: \input markdown \input lmfonts +\def\markdownOptionHtml{false} \markdownBegin <div> *There is no block tag support.* @@ -6420,7 +6995,7 @@ following content: \usepackage{markdown} \begin{document} -\begin{markdown} +\begin{markdown}[html = false] <div> *There is no block tag support.* </div> @@ -6429,14 +7004,14 @@ _There is no <!-- comment --> support._ _There is no <? HTML instruction ?> support._ \end{markdown} -\begin{markdown*}{html} +\begin{markdown} <div> *There is block tag support.* </div> *There is <inline tag="tag"></inline> support.* _There is <!-- comment --> support._ _There is <? HTML instruction ?> support._ -\end{markdown*} +\end{markdown} \end{document} ``````` @@ -6462,6 +7037,7 @@ following content: \usemodule[t][markdown] \starttext +\setupmarkdown[html = no] \startmarkdown <div> *There is no block tag support.* @@ -6485,7 +7061,7 @@ _There is <? HTML instruction ?> support._ ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -6504,14 +7080,14 @@ following text: \@@_add_lua_option:nnn { html } { boolean } - { false } + { true } % \end{macrocode} % \iffalse %</tex> %<*lua,lua-cli> % \fi % \begin{macrocode} -defaultOptions.html = false +defaultOptions.html = true % \end{macrocode} % \par % \iffalse @@ -6552,6 +7128,9 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \directlua{ local markdown = require("markdown") local input, convert_safe, convert_unsafe, paragraph @@ -6566,6 +7145,7 @@ following content: convert_unsafe(input) ) } +\endgroup \bye ``````` Then, invoke LuaTeX from the terminal: @@ -6588,10 +7168,11 @@ following content: ``` tex \input markdown \input lmfonts -\input optionfalse - \begingroup \catcode`\%=12 +\catcode`\#=12 +\input optionfalse +\par \input optiontrue \endgroup \bye @@ -6667,9 +7248,9 @@ following content: $\sqrt{-1}$ *equals* $i$. \end{markdown} -\begin{markdown*}{hybrid} +\begin{markdown}[hybrid] $\sqrt{-1}$ *equals* $i$. -\end{markdown*} +\end{markdown} \end{document} ``````` @@ -6707,7 +7288,7 @@ $\sqrt{-1}$ *equals* $i$. ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -6906,7 +7487,7 @@ move down to type the note.] ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -6918,18 +7499,8 @@ following text: %</manual-options> %<*tex> % \fi -% \begin{markdown} -% -% The inlineFootnotes option has been deprecated and will be removed in -% Markdown 3.0.0. -% -% \end{markdown} % \begin{macrocode} \@@_add_lua_option:nnn - { inlineFootnotes } - { boolean } - { false } -\@@_add_lua_option:nnn { inlineNotes } { boolean } { false } @@ -6939,7 +7510,6 @@ following text: %<*lua,lua-cli> % \fi % \begin{macrocode} -defaultOptions.inlineFootnotes = false defaultOptions.inlineNotes = false % \end{macrocode} % \par @@ -7086,7 +7656,7 @@ age: 99 ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -7315,7 +7885,7 @@ following content: ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -7346,6 +7916,77 @@ defaultOptions.lineBlocks = false %</lua,lua-cli> %<*manual-options> +#### Option `mark` + +`mark` (default value: `false`) + +% \fi +% \begin{markdown} +% +% \Optitem[false]{mark}{\opt{true}, \opt{false}} +% +: true + + : Enable the Pandoc [mark syntax extension][pandoc-mark]: + + ``` md + This ==is highlighted text.== + `````` + +: false + + : Disable the Pandoc mark syntax extension. + + [pandoc-mark]: https://pandoc.org/MANUAL.html#extension-mark + +% \end{markdown} +% \iffalse + +##### \LaTeX{} Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +``` tex +\documentclass{article} +\usepackage[mark]{markdown} +\begin{document} +\begin{markdown} + +This ==is highlighted text.== + +\end{markdown} +\end{document} +``````` +Next, invoke LuaTeX from the terminal: +``` sh +lualatex document.tex +`````` +A PDF document named `document.pdf` should be produced and contain the +following text: + +> This <mark>is highlighted text.</mark> + +%</manual-options> +%<*tex> +% \fi +% \begin{macrocode} +\@@_add_lua_option:nnn + { mark } + { boolean } + { false } +% \end{macrocode} +% \iffalse +%</tex> +%<*lua,lua-cli> +% \fi +% \begin{macrocode} +defaultOptions.mark = false +% \end{macrocode} +% \par +% \iffalse +%</lua,lua-cli> +%<*manual-options> + #### Option `notes` `notes` (default value: `false`) @@ -7473,7 +8114,7 @@ isn't indented. ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -7499,18 +8140,8 @@ following text: %</manual-options> %<*tex> % \fi -% \begin{markdown} -% -% The footnotes option has been deprecated and will be removed in -% Markdown 3.0.0. -% -% \end{markdown} % \begin{macrocode} \@@_add_lua_option:nnn - { footnotes } - { boolean } - { false } -\@@_add_lua_option:nnn { notes } { boolean } { false } @@ -7520,7 +8151,6 @@ following text: %<*lua,lua-cli> % \fi % \begin{macrocode} -defaultOptions.footnotes = false defaultOptions.notes = false % \end{macrocode} % \par @@ -7605,7 +8235,7 @@ following content: ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -7639,12 +8269,12 @@ defaultOptions.pipeTables = false #### Option `preserveTabs` -`preserveTabs` (default value: `false`) +`preserveTabs` (default value: `true`) % \fi % \begin{markdown} % -% \Optitem[false]{preserveTabs}{\opt{true}, \opt{false}} +% \Optitem[true]{preserveTabs}{\opt{true}, \opt{false}} % : true @@ -7663,14 +8293,14 @@ defaultOptions.pipeTables = false \@@_add_lua_option:nnn { preserveTabs } { boolean } - { false } + { true } % \end{macrocode} % \iffalse %</tex> %<*lua,lua-cli> % \fi % \begin{macrocode} -defaultOptions.preserveTabs = false +defaultOptions.preserveTabs = true % \end{macrocode} % \par % \iffalse @@ -7765,7 +8395,7 @@ following text: %<*lua,lua-cli> % \fi % \begin{macrocode} -defaultOptions.rawAttribute = true +defaultOptions.rawAttribute = false % \end{macrocode} % \par % \iffalse @@ -7971,7 +8601,7 @@ following content: ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -8181,7 +8811,7 @@ following content: ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -8246,8 +8876,11 @@ Using a text editor, create a text document named `document.tex` with the following content: ``` tex \input markdown -\def\markdownRendererEllipsis{. . .} \input lmfonts +\def\markdownRendererEllipsis{. . .} +\begingroup +\catcode`\%=12 +\catcode`\#=12 \directlua{ local markdown = require("markdown") local convert = markdown.new() @@ -8259,6 +8892,7 @@ following content: local convert = markdown.new({smartEllipses = true}) local input = "... and this is a victorian ellipsis." tex.sprint(convert(input)) } +\endgroup \bye ``````` Then, invoke LuaTeX from the terminal: @@ -8278,11 +8912,15 @@ Using a text editor, create a text document named `document.tex` with the following content: ``` tex \input markdown -\def\markdownRendererEllipsis{. . .} \input lmfonts +\def\markdownRendererEllipsis{. . .} +\begingroup +\catcode`\%=12 +\catcode`\#=12 \input optionfalse \par \input optiontrue +\endgroup \bye ``````` Using a text editor, create a text document named `content.md` with the @@ -8357,9 +8995,9 @@ following content: These are just three regular dots ... \end{markdown} -\begin{markdown*}{smartEllipses} +\begin{markdown}[smartEllipses] ... and this is a victorian ellipsis. -\end{markdown*} +\end{markdown} \end{document} ``````` @@ -8396,7 +9034,7 @@ These are just three regular dots ... ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -8466,14 +9104,14 @@ The following list respects the numbers specified in the markup: 5. fifth item \end{markdown} -\begin{markdown*}{startNumber=false} +\begin{markdown}[startNumber=false] The following list does not respect the numbers specified in the markup: 3. third item 4. fourth item 5. fifth item -\end{markdown*} +\end{markdown} \end{document} ``````` @@ -8524,7 +9162,7 @@ The following list respects the numbers specified in the markup: ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -8656,7 +9294,7 @@ This is ~~a lunar roving vehicle~~ strike-through text. ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -8771,7 +9409,7 @@ following content: ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the text “Hello *world*!” @@ -8861,7 +9499,7 @@ H~2~O is a liquid. ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -8953,7 +9591,7 @@ following content: ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -8981,7 +9619,132 @@ defaultOptions.superscripts = false %</lua,lua-cli> %<*manual-options> -#### Option `tableCaptions` +#### Option `tableAttributes` + +`tableAttributes` (default value: `false`) + +% \fi +% \begin{markdown} +% +% \Optitem[false]{tableAttributes}{\opt{true}, \opt{false}} +% +: true + + : Enable the assignment of HTML attributes to +% table captions (see the \Opt{tableCaptions} option). +% \iffalse + [table captions](#table-captions). +% \fi + + ``` md + | Right | Left | Default | Center | + |------:|:-----|---------|:------:| + | 12 | 12 | 12 | 12 | + | 123 | 123 | 123 | 123 | + | 1 | 1 | 1 | 1 | + + : Demonstration of pipe table syntax. {#example-table} + ``` + +: false + + : Disable the assignment of HTML attributes to table captions. + +% \end{markdown} +% \iffalse + +##### \LaTeX{} Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +``` tex +\documentclass{article} +\usepackage[ + pipeTables, + tableCaptions, + tableAttributes, + relativeReferences, +]{markdown} +\usepackage{expl3} +\ExplSyntaxOn +\markdownSetup{ + renderers = { + tableAttributeContextBegin = { + \group_begin: + \markdownSetup{ + renderers = { + attributeIdentifier = { + \markdownSetup{ + renderers = { + tableAttributeContextEnd = { + \label{##1} + \group_end: + }, + }, + } + }, + }, + } + }, + tableAttributeContextEnd = { + \group_end: + }, + }, +} +\ExplSyntaxOff +\begin{document} +\begin{markdown} +See Table <#example-table>. + +| Right | Left | Default | Center | +|------:|:-----|---------|:------:| +| 12 | 12 | 12 | 12 | +| 123 | 123 | 123 | 123 | +| 1 | 1 | 1 | 1 | + + : Demonstration of pipe table syntax. {#example-table} +\end{markdown} +\end{document} +``````` +Next, invoke LuaTeX from the terminal: +``` sh +lualatex document.tex +`````` +A PDF document named `document.pdf` should be produced and contain the +following text: + +> See Table 1. +> +> | Right | Left | Default | Center | +> |------:|:-----|---------|:------:| +> | 12 | 12 | 12 | 12 | +> | 123 | 123 | 123 | 123 | +> | 1 | 1 | 1 | 1 | +> +> : Table 1. Demonstration of pipe table syntax. + +%</manual-options> +%<*tex> +% \fi +% \begin{macrocode} +\@@_add_lua_option:nnn + { tableAttributes } + { boolean } + { false } +% \end{macrocode} +% \iffalse +%</tex> +%<*lua,lua-cli> +% \fi +% \begin{macrocode} +defaultOptions.tableAttributes = false +% \end{macrocode} +% \par +% \iffalse +%</lua,lua-cli> +%<*manual-options> + +#### Option `tableCaptions` {#table-captions} `tableCaptions` (default value: `false`) @@ -9077,7 +9840,7 @@ following content: ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -9194,7 +9957,7 @@ following content: ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -9314,7 +10077,7 @@ Hel% this is a comment ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the text “Hello *world*!” @@ -9376,6 +10139,9 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \directlua{ local markdown = require("markdown") local newline = [[^^J^^J]] @@ -9384,6 +10150,7 @@ following content: [[$E=mc^2$]] .. newline .. newline .. [[$$\hat{f} \left ( \xi \right )= \int_{-\infty}^{\infty} f\left ( x \right ) e^{-i2\pi \xi x} dx$$]] tex.sprint(convert(input)) } +\endgroup \bye ``````` Then, invoke LuaTeX from the terminal: @@ -9404,9 +10171,13 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \input optionfalse \par \input optiontrue +\endgroup \bye ``````` Using a text editor, create a text document named `content.md` with the @@ -9432,11 +10203,11 @@ A PDF document named `document.pdf` should be produced and contain the following text: > \$E=mc^2\$ -> +> > \$\$\hat{f} \left ( \xi \right )= \int_{-\infty}^{\infty} f\left ( x \right ) e^{-i2\pi \xi x} dx\$\$ > > $E=mc^2$ -> +> > $$\hat{f} \left ( \xi \right )= \int_{-\infty}^{\infty} f\left ( x \right ) e^{-i2\pi \xi x} dx$$ ##### Plain \TeX{} Example {.unnumbered} @@ -9513,7 +10284,7 @@ $$\hat{f} \left ( \xi \right )= \int_{-\infty}^{\infty} f\left ( x \right ) e^ ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -9579,6 +10350,9 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \directlua{ local markdown = require("markdown") local newline = [[^^J^^J]] @@ -9587,6 +10361,7 @@ following content: [[\\(E=mc^2\\)]] .. newline .. newline .. [[\\[\hat{f} \left ( \xi \right )= \int_{-\infty}^{\infty} f\left ( x \right ) e^{-i2\pi \xi x} dx\\]]] tex.sprint(convert(input)) } +\endgroup \bye ``````` Then, invoke LuaTeX from the terminal: @@ -9607,9 +10382,13 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \input optionfalse \par \input optiontrue +\endgroup \bye ``````` Using a text editor, create a text document named `content.md` with the @@ -9635,11 +10414,11 @@ A PDF document named `document.pdf` should be produced and contain the following text: > \\\(E=mc^2\\) -> +> > \\\[\hat{f} \left ( \xi \right )= \int_{-\infty}^{\infty} f\left ( x \right ) e^{-i2\pi \xi x} dx\\] > > \\(E=mc^2\\) -> +> > \\[\hat{f} \left ( \xi \right )= \int_{-\infty}^{\infty} f\left ( x \right ) e^{-i2\pi \xi x} dx\\] ##### Plain \TeX{} Example {.unnumbered} @@ -9716,7 +10495,7 @@ following content: ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -9782,6 +10561,9 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \directlua{ local markdown = require("markdown") local newline = [[^^J^^J]] @@ -9790,6 +10572,7 @@ following content: [[\(E=mc^2\)]] .. newline .. newline .. [[\[\hat{f} \left ( \xi \right )= \int_{-\infty}^{\infty} f\left ( x \right ) e^{-i2\pi \xi x} dx\]]] tex.sprint(convert(input)) } +\endgroup \bye ``````` Then, invoke LuaTeX from the terminal: @@ -9810,9 +10593,13 @@ following content: ``` tex \input markdown \input lmfonts +\begingroup +\catcode`\%=12 +\catcode`\#=12 \input optionfalse \par \input optiontrue +\endgroup \bye ``````` Using a text editor, create a text document named `content.md` with the @@ -9838,11 +10625,11 @@ A PDF document named `document.pdf` should be produced and contain the following text: > (E=mc^2) -> +> > [\hat{f} \left ( \xi \right )= \int_{-\infty}^{\infty} f\left ( x \right ) e^{-i2\pi \xi x} dx] > > \(E=mc^2\) -> +> > \[\hat{f} \left ( \xi \right )= \int_{-\infty}^{\infty} f\left ( x \right ) e^{-i2\pi \xi x} dx\] ##### Plain \TeX{} Example {.unnumbered} @@ -9919,7 +10706,7 @@ following content: ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -10010,13 +10797,13 @@ The following list is loose: - third item \end{markdown} -\begin{markdown*}{tightLists=false} +\begin{markdown}[tightLists=false] The following list is now also loose: - first item - second item - third item -\end{markdown*} +\end{markdown} \end{document} ``````` @@ -10143,9 +10930,9 @@ following content: This is _emphasized text_ and this is a math subscript: $m\_n$. \end{markdown} -\begin{markdown*}{underscores=false} +\begin{markdown}[underscores=false] This is *emphasized text* and this is a math subscript: $m_n$. -\end{markdown*} +\end{markdown} \end{document} ``````` @@ -10182,7 +10969,7 @@ This is *emphasized text* and this is a math subscript: $m_n$. ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -10468,6 +11255,7 @@ end % Plain \TeX{} Interface {#texinterface} %------------------------ % +% \end{markdown} % \iffalse %</tex> %<*manual-interfaces> @@ -10494,7 +11282,7 @@ following content: $\sqrt{-1}$ *equals* $i$. \markdownEnd -\def\markdownOptionHybrid{true} +\def\markdownOptionTexMathDollars{true} \markdownBegin $\sqrt{-1}$ *equals* $i$. \markdownEnd @@ -10522,6 +11310,7 @@ pdftex --shell-escape document.tex %</manual-interfaces> %<*tex> % \fi +% \begin{markdown} % % The plain \TeX{} interface provides macros for the typesetting of markdown % input from within plain \TeX{}, for setting the Lua interface options (see @@ -10605,9 +11394,11 @@ pdftex --shell-escape document.tex % \bye % ``````` % -% The \mref{markdownInput} macro accepts a single parameter with the filename -% of a markdown document and expands to the result of the conversion of the -% input markdown document to plain \TeX{}. +% You can use the \mref{markdownInput} macro to include markdown documents, +% similarly to how you might use the \mref{input} \TeX{} primitive to include +% \TeX{} documents. The \mref{markdownInput} macro accepts a single parameter +% with the filename of a markdown document and expands to the result of the +% conversion of the input markdown document to plain \TeX{}. % % \end{markdown} % \begin{macrocode} @@ -10648,12 +11439,33 @@ pdftex --shell-escape document.tex % Section <#sec:lua-options>), while some of them are specific to the plain % \TeX{} interface. % +% To determine whether plain \TeX{} is the top layer or if there are other +% layers above plain \TeX{}, we take a look on whether the +% \mdef{c_@@_top_layer_tl} token list has already been defined. If not, +% we will assume that plain \TeX{} is the top layer. +% +% \end{markdown} +% \begin{macrocode} +\ExplSyntaxOn +\tl_const:Nn \c_@@_option_layer_plain_tex_tl { plain_tex } +\cs_generate_variant:Nn + \tl_const:Nn + { NV } +\tl_if_exist:NF + \c_@@_top_layer_tl + { + \tl_const:NV + \c_@@_top_layer_tl + \c_@@_option_layer_plain_tex_tl + } +% \end{macrocode} +% \begin{markdown} +% % To enable the enumeration of plain \TeX{} options, we will maintain the % \mdef{g_\@\@_plain_tex_options_seq} sequence. % % \end{markdown} % \begin{macrocode} -\ExplSyntaxOn \seq_new:N \g_@@_plain_tex_options_seq % \end{macrocode} % \begin{markdown} @@ -10666,7 +11478,6 @@ pdftex --shell-escape document.tex % \begin{macrocode} \prop_new:N \g_@@_plain_tex_option_types_prop \prop_new:N \g_@@_default_plain_tex_options_prop -\tl_const:Nn \c_@@_option_layer_plain_tex_tl { plain_tex } \seq_gput_right:NV \g_@@_option_layers_seq \c_@@_option_layer_plain_tex_tl \cs_new:Nn \@@_add_plain_tex_option:nnn @@ -10705,6 +11516,90 @@ following code in your plain \TeX{} document: % \par % \begin{markdown} % +% The plain \TeX{} options may be also be specified via the \mdef{markdownSetup} +% macro. Here, the plain \TeX{} options are represented by a comma-delimited +% list of \meta{key}`=`\meta{value} pairs. For boolean options, the +% `=`\meta{value} part is optional, and \meta{key} will be interpreted as +% \meta{key}`=true` if the `=`\meta{value} part has been omitted. +% The \mref{markdownSetup} macro receives the options to set up as its only +% argument. +% +% \end{markdown} +% \begin{macrocode} +\cs_new:Nn + \@@_setup:n + { + \keys_set:nn + { markdown/options } + { #1 } + } +\cs_gset_eq:NN + \markdownSetup + \@@_setup:n +% \end{macrocode} +% \iffalse +%</tex> +%<*manual-options> + +Alternatively, you can also set plain \TeX{} options using the `\markdownSetup` +\TeX{} macro. For example, to set the \Opt{taskLists} Lua option to `true`, +you would include the following code in your plain \TeX{} document: + +``` tex +\markdownSetup{taskLists = true} +``` + +%</manual-options> +%<*tex> +% \fi +% \par +% \begin{markdown} +% +% The +% \mdef{markdownIfOption}`{`\meta{name}`}{`\meta{iftrue}`}{`\meta{iffalse}`}` +% macro is provided for testing, whether the value of +% `\markdownOption`\meta{name} is `true`. If the value is `true`, then +% \meta{iftrue} is expanded, otherwise \meta{iffalse} is expanded. +% +% \end{markdown} +% \begin{macrocode} +\prg_new_conditional:Nnn + \@@_if_option:n + { TF, T, F } + { + \@@_get_option_type:nN + { #1 } + \l_tmpa_tl + \str_if_eq:NNF + \l_tmpa_tl + \c_@@_option_type_boolean_tl + { + \msg_error:nnxx + { markdown } + { expected-boolean-option } + { #1 } + { \l_tmpa_tl } + } + \@@_get_option_value:nN + { #1 } + \l_tmpa_tl + \str_if_eq:NNTF + \l_tmpa_tl + \c_@@_option_value_true_tl + { \prg_return_true: } + { \prg_return_false: } + } +\msg_new:nnn + { markdown } + { expected-boolean-option } + { + Option~#1~has~type~#2,~ + but~a~boolean~was~expected. + } +\let\markdownIfOption=\@@_if_option:nTF +% \end{macrocode} +% \begin{markdown} +% %#### Finalizing and Freezing the Cache % The \mdef{markdownOptionFinalizeCache} option corresponds to the Lua % interface \Opt{finalizeCache} option, which creates an output file @@ -10777,11 +11672,9 @@ For more information, see the examples for the \Opt{finalizeCache} option. % \fi % \begin{markdown} % -% The \mdef{markdownOptionHelperScriptFileName} macro sets the filename of the -% helper Lua script file that is created during the conversion from markdown to -% plain \TeX{} in \TeX{} engines without the `\directlua` primitive. It -% defaults to `\jobname.markdown.lua`, where `\jobname` is the base name -% of the document being typeset. +% The \mdef{markdownOptionInputTempFileName} macro sets the filename of the +% temporary input file that is created during the buffering of markdown text +% from a \TeX{} source. It defaults to `\jobname.markdown.in`. % % The expansion of this macro must not contain quotation marks (`"`) or % backslash symbols (`\`). Mind that \TeX{} engines tend to @@ -10790,140 +11683,311 @@ For more information, see the examples for the \Opt{finalizeCache} option. % \end{markdown} % \begin{macrocode} \@@_add_plain_tex_option:nnn - { helperScriptFileName } + { inputTempFileName } { path } - { \jobname.markdown.lua } + { \jobname.markdown.in } % \end{macrocode} % \par % \begin{markdown} % -% The \Opt{helperScriptFileName} macro has been deprecated and -% will be removed in Markdown 3.0.0. To control the filename of the helper Lua -% script file, use the `\g_luabridge_helper_script_filename_str` macro from the -% \pkg{lt3luabridge} package. +% The \mdef{markdownOptionOutputDir} macro sets the path to the directory that +% will contain the auxiliary cache files produced by the Lua implementation and +% also the auxiliary files produced by the plain \TeX{} implementation. The +% option defaults to `.` or, since \TeX{} Live 2024, to the value of the +% `-output-directory` option of your \TeX{} engine. % -% \end{markdown} -% \begin{macrocode} -\str_new:N - \g_luabridge_helper_script_filename_str -\tl_gset:Nn - \g_luabridge_helper_script_filename_str - { \markdownOptionHelperScriptFileName } -% \end{macrocode} -% \begin{markdown} +% The path must be set to the same value as the `-output-directory` option of +% your \TeX{} engine for the package to function correctly. We need this macro +% to make the Lua implementation aware where it should store the helper files. +% The same limitations apply here as in the case of the +% \Opt{inputTempFileName} macro. % -% The \mdef{markdownOptionInputTempFileName} macro sets the filename of the -% temporary input file that is created during the buffering of markdown text -% from a \TeX{} source. It defaults to `\jobname.markdown.in`. The same -% limitations as in the case of the \Opt{helperScriptFileName} -% macro apply here. +% The \mref{markdownOptionOutputDir} macro has been deprecated and will be +% removed in the next major version of the Markdown package. <!-- After the +% macro has been removed, we should remove support for TeX Live 2023 and +% earlier, where the automatic detection does not work. --> % % \end{markdown} % \begin{macrocode} -\@@_add_plain_tex_option:nnn - { inputTempFileName } - { path } - { \jobname.markdown.in } +\cs_generate_variant:Nn + \@@_add_plain_tex_option:nnn + { nnV } % \end{macrocode} -% \par % \begin{markdown} % -% The \mdef{markdownOptionOutputTempFileName} macro sets the filename of the -% temporary output file that is created during the conversion from markdown to -% plain \TeX{} in \mref{markdownMode} other than `2` It defaults to -% `\jobname.markdown.out`. The same limitations apply here as in the case -% of the \Opt{helperScriptFileName} macro. +% Use the \pkg{lt3luabridge} library to determine the default value of the +% \mref{markdownOptionOutputDir} macro by using the environmental variable +% `TEXMF_OUTPUT_DIRECTORY` that is available since TeX~Live 2024. % % \end{markdown} % \begin{macrocode} -\@@_add_plain_tex_option:nnn - { outputTempFileName } +\ExplSyntaxOff +\input lt3luabridge.tex +\ExplSyntaxOn +\bool_if:nTF + { + \cs_if_exist_p:N + \luabridge_tl_set:Nn && + ( + \int_compare_p:nNn + { \g_luabridge_method_int } + = + { \c_luabridge_method_directlua_int } || + \sys_if_shell_unrestricted_p: + ) + } + { + \luabridge_tl_set:Nn + \l_tmpa_tl + { print(os.getenv("TEXMF_OUTPUT_DIRECTORY") or ".") } + } + { + \tl_set:Nn + \l_tmpa_tl + { . } + } +\@@_add_plain_tex_option:nnV + { outputDir } { path } - { \jobname.markdown.out } + \l_tmpa_tl % \end{macrocode} -% \par +% \iffalse +%</tex> +%<*manual-options> + +The plain \TeX{} interface provides the following commands that you can use to +specify the location of temporary files produced during the conversion from +Markdown to \TeX{}: + +- `\markdownOptionInputTempFileName`, +- `\markdownOptionOutputDir`, +- `\markdownOptionCacheDir`, and +- `\markdownOptionFrozenCacheFileName`. + +The `\markdownOptionCacheDir` and `\markdownOptionFrozenCacheFileName` commands +correspond to the `cacheDir` and `frozenCacheFileName` Lua options. + +Using a text editor, create a folder named `output-directory` and a text +document named `document.tex` with the following content: +``` tex +\input lmfonts +\input markdown +\def\markdownOptionInputTempFileName{temporary-input.md} +\def\markdownOptionOutputDir{output-directory} +\def\markdownOptionCacheDir{output-directory/cache-directory} +\def\markdownOptionEagerCache{true} +\def\markdownOptionFinalizeCache{true} +\def\markdownOptionFrozenCacheFileName{output-directory/cache-directory/frozen-cache.tex} +\markdownBegin +Hello *world*! +\markdownEnd +\bye +``````` +Next, invoke LuaTeX from the terminal: +``` sh +luatex --output-directory output-directory document.tex +`````` +A text document named `temporary-input.md` should be produced in the folder +named `output-directory` and contain the following text: + +> Hello \*world\*! + +A folder named `output-directory/cache-directory` should also be produced and +contain fragments of the converted markdown document. LuaTeX does not need +other temporary files to perform the conversion from markdown to \TeX{}. To +produce the remaining temporary files, invoke pdfTeX from the terminal: +``` sh +pdftex --output-directory output-directory --shell-escape document.tex +`````` +Text document named `temporary-output.md` should be produced in the folder +named `output-directory`. The document will contain the input markdown +document converted to \TeX{}. + +% \fi % \begin{markdown} -% -% The \Opt{outputTempFileName} macro has been deprecated and -% will be removed in Markdown 3.0.0. -% + +#### No default token renderer prototypes {#plain} + +The Markdown package provides default definitions for token renderer prototypes +using the `witiko/markdown/defaults` +% theme (see Section~<sec:#themes>). +% \iffalse +theme. +% \fi +Although these default definitions provide a useful starting point for authors, +they use extra resources, especially with higher-level \TeX{} formats such as +\LaTeX{} and \Hologo{ConTeXt}. Furthermore, the default definitions may change +at any time, which may pose a problem for maintainers of Markdown themes and +templates who may require a stable output. + +The \mdef{markdownOptionPlain} macro specifies whether higher-level \TeX{} +formats should only use the plain \TeX{} default definitions or whether they +should also use the format-specific default definitions. Whereas plain \TeX{} +default definitions only provide definitions for simple elements such as +emphasis, strong emphasis, and paragraph separators, format-specific default +definitions add support for more complex elements such as lists, tables, and +citations. On the flip side, plain \TeX{} default definitions load no extra +resources and are rather stable, whereas format-specific default definitions +load extra resources and are subject to a more rapid change. + +Here is how you would enable the macro in a \LaTeX{} document: + +``` tex +\usepackage[plain]{markdown} +``````` + +Here is how you would enable the macro in a \Hologo{ConTeXt} document: + +``` tex +\def\markdownOptionPlain{true} +\usemodule[t][markdown] +``````` + +The macro must be set before or during the loading of the package. Setting the +macro after loading the package has no effect. + % \end{markdown} +% \iffalse +%</manual-options> +%<*tex> +% \fi % \begin{macrocode} -\str_new:N - \g_luabridge_standard_output_filename_str -\tl_gset:Nn - \g_luabridge_standard_output_filename_str - { \markdownOptionOutputTempFileName } +\@@_add_plain_tex_option:nnn + { plain } + { boolean } + { false } % \end{macrocode} +% \iffalse +%</tex> +%<*manual-options> +% \fi % \begin{markdown} -% -% The \mdef{markdownOptionErrorTempFileName} macro sets the filename of the -% temporary output file that is created when a Lua error is encountered during -% the conversion from markdown to plain \TeX{} in \mref{markdownMode} other than -% `2`. It defaults to `\jobname.markdown.err`. The same limitations -% apply here as in the case of the \Opt{helperScriptFileName} -% macro. -% + +The \mdef{markdownOptionNoDefaults} macro specifies whether we should prevent +the loading of default definitions or not. This is useful in contexts, where +we want to have total control over how all elements are rendered. + +Here is how you would enable the macro in a \LaTeX{} document: + +``` tex +\usepackage[noDefaults]{markdown} +``````` + +Here is how you would enable the macro in a \Hologo{ConTeXt} document: + +``` tex +\def\markdownOptionNoDefaults{true} +\usemodule[t][markdown] +``````` + +The macro must be set before or during the loading of the package. Setting the +macro after loading the package has no effect. + % \end{markdown} +% \iffalse +%</manual-options> +%<*tex> +% \fi % \begin{macrocode} \@@_add_plain_tex_option:nnn - { errorTempFileName } - { path } - { \jobname.markdown.err } + { noDefaults } + { boolean } + { false } % \end{macrocode} -% \par % \begin{markdown} % -% The \Opt{errorTempFileName} macro has been deprecated and -% will be removed in Markdown 3.0.0. To control the filename of the temporary -% file for Lua errors, use the `\g_luabridge_error_output_filename_str` macro -% from the \pkg{lt3luabridge} package. +%#### Miscellaneous Options +% +% The \mdef{markdownOptionStripPercentSigns} macro controls whether a percent +% sign (`\%`) at the beginning of a line will be discarded when buffering +% Markdown input (see Section <#sec:buffering>) or not. Notably, this +% enables the use of markdown when writing \TeX{} package documentation using +% the \pkg{Doc} \LaTeX{}~package~[@mittelbach17] or similar. The recognized +% values of the macro are `true` (discard) and `false` (retain). It defaults +% to `false`. % % \end{markdown} % \begin{macrocode} -\str_new:N - \g_luabridge_error_output_filename_str -\tl_gset:Nn - \g_luabridge_error_output_filename_str - { \markdownOptionErrorTempFileName } +\seq_gput_right:Nn + \g_@@_plain_tex_options_seq + { stripPercentSigns } +\prop_gput:Nnn + \g_@@_plain_tex_option_types_prop + { stripPercentSigns } + { boolean } +\prop_gput:Nnx + \g_@@_default_plain_tex_options_prop + { stripPercentSigns } + { false } % \end{macrocode} +% \iffalse +%</tex> +%<*manual-options> + +#### Package Documentation + +The \mdef{markdownOptionStripPercentSigns} macro controls whether a percent +sign (`\%`) at the beginning of a line will be discarded when reading Markdown +input from a \TeX{} document. This enables the use of markdown when writing +\TeX{} package documentation using the [Doc \LaTeX{} package][doc] by Frank +Mittelbach. The recognized values of the macro are `true` (discard) and `false` +(retain). It defaults to `false`. + + [doc]: https://ctan.org/pkg/doc (doc – Format LaTeX documentation) + +Using a text editor, create a text document named `document.dtx` with the +following content: + + ``` tex + % \iffalse + \documentclass{ltxdoc} + \usepackage[stripPercentSigns]{markdown} + \begin{document} + \DocInput{document.dtx} + \end{document} + % \fi + % + % \begin{markdown} + % Hello *world*! + % \end{markdown} + ``````` + +Next, invoke LuaTeX from the terminal: +``` sh +lualatex document.dtx +`````` +A PDF document named `document.pdf` should be produced and contain the text +“Hello *world*!” + +%</manual-options> +%<*tex> +% \fi % \begin{markdown} % -% The \mdef{markdownOptionOutputDir} macro sets the path to the directory that -% will contain the auxiliary cache files produced by the Lua implementation and -% also the auxiliary files produced by the plain \TeX{} implementation. The -% option defaults to `.`. -% -% The path must be set to the same value as the `-output-directory` option of -% your \TeX{} engine for the package to function correctly. We need this macro -% to make the Lua implementation aware where it should store the helper files. -% The same limitations apply here as in the case of the -% \Opt{helperScriptFileName} macro. +%#### Generating Plain \TeX{} Option Macros and Key-Values % -% \end{markdown} -% \begin{macrocode} -\@@_add_plain_tex_option:nnn - { outputDir } - { path } - { . } -% \end{macrocode} -% \begin{markdown} +% We define the command \mdef{@@_define_option_commands_and_keyvals:} that +% defines plain \TeX{} macros and the key-value interface +% of the \mref{markdownSetup} macro for the above plain \TeX{} options. % -% Here, we automatically define plain \TeX{} macros for the above plain \TeX{} -% options. +% The command also defines macros and key-values that map +% directly to the options recognized by the Lua interface, such as +% \mdef{markdownOptionHybrid} for the \Opt{hybrid} Lua option (see Section +% <#sec:lua-options>), which are not processed by the plain \TeX{} +% implementation, only passed along to Lua. % -% Furthemore, we also define macros that map directly to the options recognized -% by the Lua interface, such as \mdef{markdownOptionHybrid} for the -% \Opt{hybrid} Lua option (see Section <#sec:lua-options>), which are not -% processed by the plain \TeX{} implementation, only passed along to Lua. +% Furthermore, the command also defines options and key-values +% for subsequently loaded layers that correspond to higher-level \TeX{} formats +% such as \LaTeX{} and \Hologo{ConTeXt}. % % For the macros that correspond to the non-boolean options recognized by the % Lua interface, the same limitations apply here in the case of the -% \Opt{helperScriptFileName} macro. +% \Opt{inputTempFileName} macro. % % \end{markdown} % \begin{macrocode} -\cs_new:Nn \@@_plain_tex_define_option_commands: +\cs_new:Nn + \@@_define_option_commands_and_keyvals: { \seq_map_inline:Nn \g_@@_option_layers_seq @@ -10931,19 +11995,53 @@ For more information, see the examples for the \Opt{finalizeCache} option. \seq_map_inline:cn { g_@@_ ##1 _options_seq } { - \@@_plain_tex_define_option_command:n + \@@_define_option_command:n { ####1 } +% \end{macrocode} +% \begin{markdown} +% +% To make it easier to copy-and-paste options from Pandoc [@macfarlane22] such +% as `fancy_lists`, `header_attributes`, and `pipe_tables`, we accept +% snake\\\_case in addition to camelCase variants of options. As a bonus, +% studies [@sharif10] also show that snake\\\_case is faster to read than +% camelCase. +% +% \end{markdown} +% \begin{macrocode} + \@@_with_various_cases:nn + { ####1 } + { + \@@_define_option_keyval:nnn + { ##1 } + { ####1 } + { ########1 } + } } } } -\cs_new:Nn \@@_plain_tex_define_option_command:n +\cs_new:Nn + \@@_define_option_command:n { - \@@_get_default_option_value:nN - { #1 } - \l_tmpa_tl - \@@_set_option_value:nV +% \end{macrocode} +% \begin{markdown} +% +% Do not override options defined before loading the package. +% +% \end{markdown} +% \begin{macrocode} + \@@_option_tl_to_csname:nN { #1 } \l_tmpa_tl + \cs_if_exist:cF + { \l_tmpa_tl } + { + \@@_get_default_option_value:nN + { #1 } + \l_tmpa_tl + \@@_set_option_value:nV + { #1 } + \l_tmpa_tl + } } \cs_new:Nn \@@_set_option_value:nn @@ -11000,140 +12098,750 @@ For more information, see the examples for the \Opt{finalizeCache} option. } } } -\@@_plain_tex_define_option_commands: +\cs_new:Nn + \@@_define_option_keyval:nnn + { + \prop_get:cnN + { g_@@_ #1 _option_types_prop } + { #2 } + \l_tmpa_tl + \str_if_eq:VVTF + \l_tmpa_tl + \c_@@_option_type_boolean_tl + { + \keys_define:nn + { markdown/options } + { +% \end{macrocode} +% \begin{markdown} +% +% For boolean options, we also accept `yes` as an alias for +% `true` and `no` as an alias for `false`. +% +% \end{markdown} +% \begin{macrocode} + #3 .code:n = { + \tl_set:Nx + \l_tmpa_tl + { + \str_case:nnF + { ##1 } + { + { yes } { true } + { no } { false } + } + { ##1 } + } + \@@_set_option_value:nV + { #2 } + \l_tmpa_tl + }, + #3 .default:n = { true }, + } + } + { + \keys_define:nn + { markdown/options } + { + #3 .code:n = { + \@@_set_option_value:nn + { #2 } + { ##1 } + }, + } + } +% \end{macrocode} +% \begin{markdown} +% +% For options of type `clist`, we assume that \meta{key} is a regular English +% noun in plural (such as `extensions`) and we also define the +% \meta{singular key}`=`\meta{value} interface, where \meta{singular key} is +% \meta{key} after stripping the trailing -s (such as `extension`). Rather +% than setting the option to \meta{value}, this interface appends \meta{value} +% to the current value as the rightmost item in the list. +% +% \end{markdown} +% \begin{macrocode} + \str_if_eq:VVT + \l_tmpa_tl + \c_@@_option_type_clist_tl + { + \tl_set:Nn + \l_tmpa_tl + { #3 } + \tl_reverse:N + \l_tmpa_tl + \str_if_eq:enF + { + \tl_head:V + \l_tmpa_tl + } + { s } + { + \msg_error:nnn + { markdown } + { malformed-name-for-clist-option } + { #3 } + } + \tl_set:Nx + \l_tmpa_tl + { + \tl_tail:V + \l_tmpa_tl + } + \tl_reverse:N + \l_tmpa_tl + \tl_put_right:Nn + \l_tmpa_tl + { + .code:n = { + \@@_get_option_value:nN + { #2 } + \l_tmpa_tl + \clist_set:NV + \l_tmpa_clist + { \l_tmpa_tl, { ##1 } } + \@@_set_option_value:nV + { #2 } + \l_tmpa_clist + } + } + \keys_define:nV + { markdown/options } + \l_tmpa_tl + } + } +\cs_generate_variant:Nn + \clist_set:Nn + { NV } +\cs_generate_variant:Nn + \keys_define:nn + { nV } +\cs_generate_variant:Nn + \@@_set_option_value:nn + { nV } +\prg_generate_conditional_variant:Nnn + \str_if_eq:nn + { en } + { F } +\msg_new:nnn + { markdown } + { malformed-name-for-clist-option } + { + Clist~option~name~#1~does~not~end~with~-s. + } % \end{macrocode} +% \begin{markdown} +% +% If plain \TeX{} is the top layer, we use the +% \mref{@@_define_option_commands_and_keyvals:} macro to define plain \TeX{} +% option macros and key-values immediately. Otherwise, we +% postpone the definition until the upper layers have been loaded. % +% \end{markdown} +% \begin{macrocode} +\str_if_eq:VVT + \c_@@_top_layer_tl + \c_@@_option_layer_plain_tex_tl + { + \@@_define_option_commands_and_keyvals: + } +\ExplSyntaxOff +% \end{macrocode} % \iffalse %</tex> %<*manual-options> +% \fi +% \begin{markdown} -The plain \TeX{} interface provides the following commands that you can use to -specify the location of temporary files produced during the conversion from -Markdown to \TeX{}: +### Themes {#themes} -- `\markdownOptionHelperScriptFileName`, -- `\markdownOptionInputTempFileName`, -- `\markdownOptionOutputTempFileName`, -- `\markdownOptionErrorTempFileName`, -- `\markdownOptionOutputDir`, -- `\markdownOptionCacheDir`, and -- `\markdownOptionFrozenCacheFileName`. +User-defined themes for the Markdown package provide a domain-specific +interpretation of Markdown tokens. Themes allow the authors to achieve +a specific look and other high-level goals without low-level programming. -The `\markdownOptionCacheDir` and `\markdownOptionFrozenCacheFileName` commands -correspond to the `cacheDir` and `frozenCacheFileName` Lua options. +% The key-values `theme`=\meta{theme name} and `import`=\meta{theme name} load +% a \TeX{} document (further referred to as *a theme*) named +% `markdowntheme`\meta{munged theme name}`.tex`, where the *munged theme name* +% is the *theme name* after the substitution of all forward slashes (`/`) for +% an underscore (`_`). The theme name is *qualified* and contains no +% underscores. A theme name is qualified if and only if it contains at least +% one forward slash. Themes are inspired by the Beamer \LaTeX{} package, which +% provides similar functionality with its `\usetheme` macro [@tantau21, Section +% 15.1]. +% +% Theme names must be qualified to minimize naming conflicts between different +% themes with a similar purpose. The preferred format of a theme name is +% \meta{theme author}`/`\meta{theme purpose}`/`\meta{private naming +% scheme}, where the *private naming scheme* may contain additional forward +% slashes. For example, a theme by a user `witiko` for the MU theme of the +% Beamer document class may have the name `witiko/beamer/MU`. +% +% Theme names are munged to allow structure inside theme names without +% dictating where the themes should be located inside the \TeX{} directory +% structure. For example, loading a theme named `witiko/beamer/MU` would +% load a \TeX{} document package named `markdownthemewitiko_beamer_MU.tex`. +% +% \end{markdown} +% \iffalse +%</manual-options> +%<*tex> +% \fi +% \begin{macrocode} +\ExplSyntaxOn +\keys_define:nn + { markdown/options } + { + theme .code:n = { + \@@_set_theme:n + { #1 } + }, + import .code:n = { + \tl_set:Nn + \l_tmpa_tl + { #1 } +% \end{macrocode} +% \begin{markdown} +% +% To ensure that keys containing forward slashes get passed correctly, we +% replace all forward slashes in the input with backslash tokens with category +% code letter and then undo the replacement. This means that if any unbraced +% backslash tokens with category code letter exist in the input, they will be +% replaced with forward slashes. However, this should be extremely rare. +% +% \end{markdown} +% \begin{macrocode} + \tl_replace_all:NnV + \l_tmpa_tl + { / } + \c_backslash_str + \keys_set:nV + { markdown/options/import } + \l_tmpa_tl + }, + } +% \end{macrocode} +% \begin{markdown} +% +% To keep track of the current theme when themes are nested, we will +% maintain the \mdef{g_\@\@_themes_seq} stack of theme names. +% For convenience, the name of the current theme is also available in the +% \mdef{g_@@_current_theme_tl} macro. +% +% \end{markdown} +% \begin{macrocode} +\seq_new:N + \g_@@_themes_seq +\tl_new:N + \g_@@_current_theme_tl +\tl_gset:Nn + \g_@@_current_theme_tl + { } +\seq_gput_right:NV + \g_@@_themes_seq + \g_@@_current_theme_tl +\cs_new:Nn + \@@_set_theme:n + { +% \end{macrocode} +% \begin{markdown} +% +% First, we validate the theme name. +% +% \end{markdown} +% \begin{macrocode} + \str_if_in:nnF + { #1 } + { / } + { + \msg_error:nnn + { markdown } + { unqualified-theme-name } + { #1 } + } + \str_if_in:nnT + { #1 } + { _ } + { + \msg_error:nnn + { markdown } + { underscores-in-theme-name } + { #1 } + } +% \end{macrocode} +% \begin{markdown} +% +% Next, we munge the theme name. +% +% \end{markdown} +% \begin{macrocode} + \str_set:Nn + \l_tmpa_str + { #1 } + \str_replace_all:Nnn + \l_tmpa_str + { / } + { _ } +% \end{macrocode} +% \begin{markdown} +% +% Finally, we load the theme. +% +% \end{markdown} +% \begin{macrocode} + \tl_gset:Nn + \g_@@_current_theme_tl + { #1 / } + \seq_gput_right:NV + \g_@@_themes_seq + \g_@@_current_theme_tl + \@@_load_theme:nV + { #1 } + \l_tmpa_str + \seq_gpop_right:NN + \g_@@_themes_seq + \l_tmpa_tl + \seq_get_right:NN + \g_@@_themes_seq + \l_tmpa_tl + \tl_gset:NV + \g_@@_current_theme_tl + \l_tmpa_tl + } +\msg_new:nnnn + { markdown } + { unqualified-theme-name } + { Won't~load~theme~with~unqualified~name~#1 } + { Theme~names~must~contain~at~least~one~forward~slash } +\msg_new:nnnn + { markdown } + { underscores-in-theme-name } + { Won't~load~theme~with~an~underscore~in~its~name~#1 } + { Theme~names~must~not~contain~underscores~in~their~names } +\cs_generate_variant:Nn + \tl_replace_all:Nnn + { NnV } +\ExplSyntaxOff +% \end{macrocode} +% \iffalse +%</tex> +%<*manual-options> +% \fi +% \par +% \begin{markdown} -Using a text editor, create a folder named `output-directory` and a text -document named `document.tex` with the following content: +Built-in plain \TeX{} themes provided with the Markdown package include: + +\pkg{witiko/tilde} + +: A theme that makes tilde (`~`) always typeset the non-breaking space even + when the \Opt{hybrid} Lua option is disabled. +% ``` tex +% \input markdown +% \markdownSetup{import=witiko/tilde} +% \markdownBegin +% Bartel~Leendert van~der~Waerden +% \markdownEnd +% \bye +% ``````` +% Typesetting the above document produces the following text: +% “Bartel~Leendert van~der~Waerden”. +% +% \end{markdown} +% \iffalse + +##### Plain \TeX{} Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: ``` tex -\input lmfonts \input markdown -\def\markdownOptionHelperScriptFileName{helper-script.lua} -\def\markdownOptionInputTempFileName{temporary-input.md} -\def\markdownOptionOutputTempFileName{temporary-output.tex} -\def\markdownOptionErrorTempFileName{error-output.txt} -\def\markdownOptionOutputDir{output-directory} -\def\markdownOptionCacheDir{output-directory/cache-directory} -\markdownOptionFinalizeCachetrue -\def\markdownOptionFrozenCacheFileName{output-directory/cache-directory/frozen-cache.tex} +\markdownSetup{import=witiko/tilde} \markdownBegin -Hello *world*! +Bartel~Leendert van~der~Waerden \markdownEnd \bye ``````` Next, invoke LuaTeX from the terminal: ``` sh -luatex --output-directory output-directory document.tex +luatex document.tex `````` -A text document named `temporary-input.md` should be produced in the folder -named `output-directory` and contain the following text: - -> Hello \*world\*! +A PDF document named `document.pdf` should be produced and contain the +following text, where the middot (`·`) denotes a non-breaking space: -A folder named `output-directory/cache-directory` should also be produced and -contain fragments of the converted markdown document. LuaTeX does not need -other temporary files to perform the conversion from markdown to \TeX{}. To -produce the remaining temporary files, invoke pdfTeX from the terminal: -``` sh -pdftex --output-directory output-directory --shell-escape document.tex -`````` -Text documents named `helper-script.lua`, and `temporary-output.md` should be -produced in the folder named `output-directory`. The document named -`helper-script.lua` will contain Lua code that was executed to convert markdown -to plain \TeX{}. The document named `temporary-output.tex` will contain the -input markdown document converted to \TeX{}. +> Bartel·Leendert van·der·Waerden -No document named `error-output.txt` should be produced in the folder named -`output-directory`. This document would only be produced if an error had occured -while executing the Lua code. If this happens, please [file a -bug](https://github.com/witiko/markdown/issues). +% \fi +% \par +% \begin{markdown} -#### Package Documentation +\pkg{witiko/markdown/defaults} -The \mdef{markdownOptionStripPercentSigns} macro controls whether a percent -sign (`\%`) at the beginning of a line will be discarded when reading Markdown -input from a \TeX{} document. This enables the use of markdown when writing -\TeX{} package documentation using the [Doc \LaTeX{} package][doc] by Frank -Mittelbach. The recognized values of the macro are `true` (discard) and `false` -(retain). It defaults to `false`. +: A plain \TeX{} theme with the default definitions of token renderer + prototypes for plain \TeX{}. This theme is loaded automatically together + with the package and explicitly loading it has no effect. - [doc]: https://ctan.org/pkg/doc (doc – Format LaTeX documentation) +% Please, see Section <#sec:themes-implementation> for implementation +% details of the built-in plain \TeX{} themes. -Using a text editor, create a text document named `document.dtx` with the -following content: +### Snippets {#snippets} - ``` tex - % \iffalse - \documentclass{ltxdoc} - \usepackage[stripPercentSigns]{markdown} - \begin{document} - \DocInput{document.dtx} - \end{document} - % \fi - % - % \begin{markdown} - % Hello *world*! - % \end{markdown} - ``````` +% \end{markdown} +% \iffalse -Next, invoke LuaTeX from the terminal: -``` sh -lualatex document.dtx -`````` -A PDF document named `document.pdf` should be produced and contain the text -“Hello *world*!” +User-defined themes provide global control over high-level goals. +However, it is often desirable to change only some local aspects of a document. +Snippets provide syntactic sugar for defining and invoking various +options locally. %</manual-options> %<*tex> % \fi +% \par % \begin{markdown} % -%#### Miscellaneous Options +% We may set up options as *snippets* using the +% \mdef{markdownSetupSnippet} macro and invoke them later. The +% \mref{markdownSetupSnippet} macro receives two arguments: the name +% of the snippet and the options to store. % -% The \mdef{markdownOptionStripPercentSigns} macro controls whether a percent -% sign (`\%`) at the beginning of a line will be discarded when buffering -% Markdown input (see Section <#sec:buffering>) or not. Notably, this -% enables the use of markdown when writing \TeX{} package documentation using -% the \pkg{Doc} \LaTeX{}~package~[@mittelbach17] or similar. The recognized -% values of the macro are `true` (discard) and `false` (retain). It defaults -% to `false`. +% \end{markdown} +% \begin{macrocode} +\ExplSyntaxOn +\prop_new:N + \g_@@_snippets_prop +\cs_new:Nn + \@@_setup_snippet:nn + { + \tl_if_empty:nT + { #1 } + { + \msg_error:nnn + { markdown } + { empty-snippet-name } + { #1 } + } + \tl_set:NV + \l_tmpa_tl + \g_@@_current_theme_tl + \tl_put_right:Nn + \l_tmpa_tl + { #1 } + \@@_if_snippet_exists:nT + { #1 } + { + \msg_warning:nnV + { markdown } + { redefined-snippet } + \l_tmpa_tl + } + \prop_gput:NVn + \g_@@_snippets_prop + \l_tmpa_tl + { #2 } + } +\cs_gset_eq:NN + \markdownSetupSnippet + \@@_setup_snippet:nn +\msg_new:nnnn + { markdown } + { empty-snippet-name } + { Empty~snippet~name~#1 } + { Pick~a~non-empty~name~for~your~snippet } +\msg_new:nnn + { markdown } + { redefined-snippet } + { Redefined~snippet~#1 } +% \end{macrocode} +% \begin{markdown} +% +% To decide whether a snippet exists, we can use the +% \mdef{markdownIfSnippetExists} macro. % % \end{markdown} % \begin{macrocode} -\seq_gput_right:Nn - \g_@@_plain_tex_options_seq - { stripPercentSigns } -\prop_gput:Nnn - \g_@@_plain_tex_option_types_prop - { stripPercentSigns } - { boolean } -\prop_gput:Nnx - \g_@@_default_plain_tex_options_prop - { stripPercentSigns } - { false } +\prg_new_conditional:Nnn + \@@_if_snippet_exists:n + { TF, T, F } + { + \tl_set:NV + \l_tmpa_tl + \g_@@_current_theme_tl + \tl_put_right:Nn + \l_tmpa_tl + { #1 } + \prop_get:NVNTF + \g_@@_snippets_prop + \l_tmpa_tl + \l_tmpb_tl + { \prg_return_true: } + { \prg_return_false: } + } +\cs_gset_eq:NN + \markdownIfSnippetExists + \@@_if_snippet_exists:nTF +% \end{macrocode} +% \begin{markdown} +% +% The option with key `snippet` invokes a snippet named \meta{value}. +% +% \end{markdown} +% \begin{macrocode} +\keys_define:nn + { markdown/options } + { + snippet .code:n = { + \tl_set:NV + \l_tmpa_tl + \g_@@_current_theme_tl + \tl_put_right:Nn + \l_tmpa_tl + { #1 } + \@@_if_snippet_exists:nTF + { #1 } + { + \prop_get:NVN + \g_@@_snippets_prop + \l_tmpa_tl + \l_tmpb_tl + \@@_setup:V + \l_tmpb_tl + } + { + \msg_error:nnV + { markdown } + { undefined-snippet } + \l_tmpa_tl + } + } + } +\msg_new:nnn + { markdown } + { undefined-snippet } + { Can't~invoke~undefined~snippet~#1 } +\cs_generate_variant:Nn + \@@_setup:n + { V } \ExplSyntaxOff % \end{macrocode} % \iffalse %</tex> +%<*manual-options> +% \fi +% \par +% \markdownBegin + +Here is how we can use snippets to store options and invoke them later +in \LaTeX{}: + +``` tex +\markdownSetupSnippet{romanNumerals}{ + renderers = { + olItemWithNumber = {% + \item[\romannumeral#1\relax.]% + }, + }, +} +\begin{markdown} + +The following ordered list will be preceded by arabic numerals: + +1. wahid +2. aithnayn + +\end{markdown} +\begin{markdown}[snippet=romanNumerals] + +The following ordered list will be preceded by roman numerals: + +3. tres +4. quattuor + +\end{markdown} +``````` + +If the `romanNumerals` snippet were defined in the `jdoe/lists` theme, +we could import the `jdoe/lists` theme and use the qualified name +`jdoe/lists/romanNumerals` to invoke the snippet: + +``` tex +\markdownSetup{import=jdoe/lists} +\begin{markdown}[snippet=jdoe/lists/romanNumerals] + +The following ordered list will be preceded by roman numerals: + +3. tres +4. quattuor + +\end{markdown} +``````` + +Alternatively, we can use the extended variant of the `import` \LaTeX{} +option that allows us to import the `romanNumerals` snippet to the +current namespace for easier access: + +``` tex +\markdownSetup{ + import = { + jdoe/lists = romanNumerals, + }, +} +\begin{markdown}[snippet=romanNumerals] + +The following ordered list will be preceded by roman numerals: + +3. tres +4. quattuor + +\end{markdown} +``````` + +Furthermore, we can also specify the name of the snippet in the current +namespace, which can be different from the name of the snippet in the +`jdoe/lists` theme. For example, we can make the snippet +`jdoe/lists/romanNumerals` available under the name `roman`. + +``` tex +\markdownSetup{ + import = { + jdoe/lists = romanNumerals as roman, + }, +} +\begin{markdown}[snippet=roman] + +The following ordered list will be preceded by roman numerals: + +3. tres +4. quattuor + +\end{markdown} +``````` + +Several themes and/or snippets can be loaded at once using the extended +variant of the `import` \LaTeX{} option: + +``` tex +\markdownSetup{ + import = { + jdoe/longpackagename/lists = { + arabic as arabic1, + roman, + alphabetic, + }, + jdoe/anotherlongpackagename/lists = { + arabic as arabic2, + }, + jdoe/yetanotherlongpackagename, + }, +} +``````` + +% \markdownEnd +% \iffalse +%</manual-options> +%<*tex> +% \fi +% \begin{macrocode} +\ExplSyntaxOn +\tl_new:N + \l_@@_import_current_theme_tl +\keys_define:nn + { markdown/options/import } + { +% \end{macrocode} +% \begin{markdown} +% +% If a theme name is given without a list of snippets to import, +% we assume that an empty list was given. +% +% \end{markdown} +% \begin{macrocode} + unknown .default:n = {}, + unknown .code:n = { +% \end{macrocode} +% \begin{markdown} +% +% To ensure that keys containing forward slashes get passed correctly, we +% replace all forward slashes in the input with backslash tokens with category +% code letter and then undo the replacement. This means that if any unbraced +% backslash tokens with category code letter exist in the input, they will be +% replaced with forward slashes. However, this should be extremely rare. +% +% \end{markdown} +% \begin{macrocode} + \tl_set_eq:NN + \l_@@_import_current_theme_tl + \l_keys_key_str + \tl_replace_all:NVn + \l_@@_import_current_theme_tl + \c_backslash_str + { / } +% \end{macrocode} +% \begin{markdown} +% +% Here, we import the snippets. +% +% \end{markdown} +% \begin{macrocode} + \clist_map_inline:nn + { #1 } + { + \regex_extract_once:nnNTF + { ^(.*?)\s+as\s+(.*?)$ } + { ##1 } + \l_tmpa_seq + { + \seq_pop:NN + \l_tmpa_seq + \l_tmpa_tl + \seq_pop:NN + \l_tmpa_seq + \l_tmpa_tl + \seq_pop:NN + \l_tmpa_seq + \l_tmpb_tl + } + { + \tl_set:Nn + \l_tmpa_tl + { ##1 } + \tl_set:Nn + \l_tmpb_tl + { ##1 } + } + \tl_put_left:Nn + \l_tmpa_tl + { / } + \tl_put_left:NV + \l_tmpa_tl + \l_@@_import_current_theme_tl + \@@_setup_snippet:Vx + \l_tmpb_tl + { snippet = { \l_tmpa_tl } } + } +% \end{macrocode} +% \begin{markdown} +% +% Here, we load the theme. +% +% \end{markdown} +% \begin{macrocode} + \@@_set_theme:V + \l_@@_import_current_theme_tl + }, + } +\cs_generate_variant:Nn + \tl_replace_all:Nnn + { NVn } +\cs_generate_variant:Nn + \@@_set_theme:n + { V } +\cs_generate_variant:Nn + \@@_setup_snippet:nn + { Vx } +% \end{macrocode} +% \iffalse +%</tex> %<*manual-tokens> ## Markdown Tokens @@ -11200,19 +12908,25 @@ In this section, I will describe the individual token renderers. % \begin{markdown} #### Attribute Renderers -The following macros are only produced, when the \Opt{headerAttributes} option -is enabled. +The following macros are only produced, when at least one of the following +options for markdown attributes on different elements is enabled: + +- \Opt{autoIdentifiers} +- \Opt{fencedCodeAttributes} +- \Opt{gfmAutoIdentifiers} +- \Opt{headerAttributes} +- \Opt{inlineCodeAttributes} +- \Opt{linkAttributes} \mdef{markdownRendererAttributeIdentifier} represents the \meta{identifier} of a markdown element (`id="`\meta{identifier}`"` in HTML and `#`\meta{identifier} -in Markdown's \Opt{headerAttributes} syntax extension). The macro receives a -single attribute that corresponds to the \meta{identifier}. +in markdown attributes). The macro receives a single attribute that corresponds +to the \meta{identifier}. \mdef{markdownRendererAttributeClassName} represents the \meta{class name} of a markdown element (`class="`\meta{class name} ...`"` in HTML and -`.`\meta{class name} in Markdown's \Opt{headerAttributes} syntax extension). -The macro receives a single attribute that corresponds to the \meta{class -name}. +`.`\meta{class name} in markdown attributes). The macro receives a single +attribute that corresponds to the \meta{class name}. \mdef{markdownRendererAttributeKeyValue} represents a HTML attribute in the form \meta{key}`=`\meta{value} that is neither an identifier nor a class name. @@ -11468,7 +13182,7 @@ A quote from William Shakespeare's King Lear: ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -11512,8 +13226,8 @@ enabled. The \mdef{markdownRendererBracketedSpanAttributeContextBegin} and \mdef{markdownRendererBracketedSpanAttributeContextEnd} macros represent the -beginning and the end of an inline bracketed span in which the attributes of -the span apply. The macros receive no arguments. +beginning and the end of a context in which the attributes of +an inline bracketed span apply. The macros receive no arguments. % \end{markdown} % @@ -11834,7 +13548,7 @@ following content: \usepackage[tightLists]{markdown} \begin{document} -\begin{markdown*}{ +\begin{markdown}[ renderers = { interblockSeparator = {}, ulBeginTight = { (}, @@ -11847,15 +13561,15 @@ following content: ulItemEnd = {}, ulEndTight = {).}, }, -} +] This is a tight list - the first item - the second item - the third item -\end{markdown*} +\end{markdown} -\begin{markdown*}{ +\begin{markdown}[ renderers = { interblockSeparator = {% :\par @@ -11866,7 +13580,7 @@ This is a tight list ulItemEnd = {.}, ulEnd = {\end{itemize}}, }, -} +] This is a loose list - This is the first item @@ -11874,7 +13588,7 @@ This is a loose list - This is the second item - This is the third item -\end{markdown*} +\end{markdown} \end{document} ``````` @@ -11946,7 +13660,7 @@ This is a loose list ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -11987,6 +13701,194 @@ following text: % % \begin{markdown} +#### Citation Renderers +The \mdef{markdownRendererCite} macro represents a string of one or more +parenthetical citations. This macro will only be produced, when the +\Opt{citations} option is enabled. The macro receives the parameter +`{`\meta{number of citations}`}` followed by \meta{suppress author} +`{`\meta{prenote}`}{`\meta{postnote}`}{`\meta{name}`}` repeated +\meta{number of citations} times. The \meta{suppress author} parameter is +either the token `-`, when the author's name is to be suppressed, or `+` +otherwise. + +% \end{markdown} +% +% \iffalse + +##### \LaTeX{} Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +``` tex +\documentclass{article} +\usepackage[citations]{markdown} +\newcount\citationsCounter +\newcount\citationsTotal +\makeatletter +\def\citations#1#2#3#4{% + a parenthesized citation \emph{#4} + \advance\citationsCounter by 1\relax + \ifx\relax#2\relax + \ifx\relax#3\relax\else + with a postfix \emph{#3}% + \fi + \else + with a prefix \emph{#2}% + \ifx\relax#3\relax\else + \ and a postfix \emph{#3}% + \fi + \fi + \ifnum\citationsCounter>\citationsTotal\relax + .% + \expandafter\@gobble + \else + , and + \fi\citations} +\makeatother +\markdownSetup{ + renderers = { + cite = {% + \citationsCounter=1% + \citationsTotal=#1% + This is + \expandafter\citations + }, + }, +} +\begin{document} +\begin{markdown} +[see @abrahams90, pp. 12; @eijkhout91, pp. 34] +\end{markdown} +\end{document} +``````` +Next, invoke LuaTeX from the terminal: +``` sh +lualatex document.tex +`````` +A PDF document named `document.pdf` should be produced and contain the +following text: + +> This is a parenthesized citation *abrahams90* with a prefix see +> and a postfix *pp. 12*, and a citation *eijkhout91* with a +> postfix *pp. 34*. + +%</manual-tokens> +%<*tex> +% \fi +% +% \begin{macrocode} +\def\markdownRendererCite{% + \markdownRendererCitePrototype}% +\ExplSyntaxOn +\seq_gput_right:Nn + \g_@@_renderers_seq + { cite } +\prop_gput:Nnn + \g_@@_renderer_arities_prop + { cite } + { 1 } +\ExplSyntaxOff +% \end{macrocode} +% \par +% +% \iffalse +%</tex> +%<*manual-tokens> +% \fi +% +% \begin{markdown} + +The \mdef{markdownRendererTextCite} macro represents a string of one or more +text citations. This macro will only be produced, when the +\Opt{citations} option is enabled. The macro receives parameters in the same +format as the \mref{markdownRendererCite} macro. + +% \end{markdown} +% +% \iffalse + +##### \LaTeX{} Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +``` tex +\documentclass{article} +\usepackage[citations]{markdown} +\newcount\citationsCounter +\newcount\citationsTotal +\makeatletter +\def\citations#1#2#3#4{% + a text citation \emph{#4} + \advance\citationsCounter by 1\relax + \ifx\relax#2\relax + \ifx\relax#3\relax\else + with a postfix \emph{#3}% + \fi + \else + with a prefix \emph{#2}% + \ifx\relax#3\relax\else + \ and a postfix \emph{#3}% + \fi + \fi + \ifnum\citationsCounter>\citationsTotal\relax + .% + \expandafter\@gobble + \else + , and + \fi\citations} +\makeatother +\markdownSetup{ + renderers = { + textCite = {% + \citationsCounter=1% + \citationsTotal=#1% + This is + \expandafter\citations + }, + }, +} +\begin{document} +\begin{markdown} +@abrahams90 [pp. 12; also @eijkhout91] +\end{markdown} +\end{document} +``````` +Next, invoke LuaTeX from the terminal: +``` sh +lualatex document.tex +`````` +A PDF document named `document.pdf` should be produced and contain the +following text: + +> This is a text citation *abrahams90* with a postfix *pp. 12*, +> and a citation *eijkhout91* with a prefix *also*. + +%</manual-tokens> +%<*tex> +% \fi +% +% \begin{macrocode} +\def\markdownRendererTextCite{% + \markdownRendererTextCitePrototype}% +\ExplSyntaxOn +\seq_gput_right:Nn + \g_@@_renderers_seq + { textCite } +\prop_gput:Nnn + \g_@@_renderer_arities_prop + { textCite } + { 1 } +\ExplSyntaxOff +% \end{macrocode} +% \par +% +% \iffalse +%</tex> +%<*manual-tokens> +% \fi +% +% \begin{markdown} + #### Code Block Renderers The \mdef{markdownRendererInputVerbatim} macro represents a code block. The macro receives a single argument that corresponds to the @@ -12023,8 +13925,10 @@ filename of a file contaning the code block contents. The \mdef{markdownRendererInputFencedCode} macro represents a fenced code block. This macro will only be produced, when the \Opt{fencedCode} option is -enabled. The macro receives two arguments that correspond to the filename of -a file contaning the code block contents and to the code fence infostring. +enabled. The macro receives three arguments that correspond to the filename of +a file contaning the code block contents, the fully escaped code fence infostring +that can be directly typeset, and the raw code fence infostring that can be used +outside typesetting. % \end{markdown} % @@ -12125,7 +14029,7 @@ following text except for the filename, which may differ: \prop_gput:Nnn \g_@@_renderer_arities_prop { inputFencedCode } - { 2 } + { 3 } \ExplSyntaxOff % \end{macrocode} % \par @@ -12203,9 +14107,9 @@ following content: $\sqrt{-1}$ *equals* $i$ \end{markdown} -\begin{markdown*}{hybrid} +\begin{markdown}[hybrid] $\sqrt{-1}$ *equals* $i$ -\end{markdown*} +\end{markdown} \end{document} ``````` @@ -12248,7 +14152,7 @@ $\sqrt{-1}$ *equals* $i$ ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -12293,8 +14197,8 @@ option is enabled. The \mdef{markdownRendererCodeSpanAttributeContextBegin} and \mdef{markdownRendererCodeSpanAttributeContextEnd} macros represent the beginning -and the end of an inline code span in which the attributes of the inline code -span apply. The macros receive no arguments. +and the end of a context in which the attributes of an inline code span apply. +The macros receive no arguments. % \end{markdown} % @@ -12370,7 +14274,7 @@ following text: #### Content Block Renderers {#texcontentblockrenderers} -The \mdef{markdownRendererContentBlock} macro represents an iA\,Writer content +The \mdef{markdownRendererContentBlock} macro represents an iA\\,Writer content block. It receives four arguments: the local file or online image filename extension cast to the lower case, the fully escaped \acro{uri} that can be directly typeset, the raw \acro{uri} that can be used outside typesetting, @@ -12406,7 +14310,7 @@ and the title of the content block. % \begin{markdown} The \mdef{markdownRendererContentBlockOnlineImage} macro represents an -iA\,Writer online image content block. The macro receives the same arguments +iA\\,Writer online image content block. The macro receives the same arguments as \mref{markdownRendererContentBlock}. % \end{markdown} @@ -12438,7 +14342,7 @@ as \mref{markdownRendererContentBlock}. % % \begin{markdown} -The \mdef{markdownRendererContentBlockCode} macro represents an iA\,Writer +The \mdef{markdownRendererContentBlockCode} macro represents an iA\\,Writer content block that was recognized as a file in a known programming language by its filename extension $s$. If any `markdown-languages.json` file found by \pkg{kpathsea}^[Filenames other than `markdown-languages.json` may @@ -12593,7 +14497,7 @@ content: `````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -12996,7 +14900,7 @@ following content: \usepackage[definitionLists, tightLists]{markdown} \begin{document} -\begin{markdown*}{ +\begin{markdown}[ renderers = { interblockSeparator = {% :% @@ -13018,7 +14922,7 @@ following content: dlDefinitionBegin = \item, dlEndTight = {\end{description}}, }, -} +] This is a tight definition list Coffee @@ -13030,9 +14934,9 @@ Milk : white cold drink : nutrient-rich : produced on an industrial scale -\end{markdown*} +\end{markdown} -\begin{markdown*}{ +\begin{markdown}[ renderers = { interblockSeparator = {% \def\markdownRendererInterblockSeparator{\par}% @@ -13051,7 +14955,7 @@ Milk dlDefinitionEnd = {}, dlEnd = {.}, }, -} +] This is a loose definition list Coffee @@ -13069,7 +14973,7 @@ Milk : nutrient-rich : produced on an industrial scale -\end{markdown*} +\end{markdown} \end{document} ``````` @@ -13187,7 +15091,7 @@ Milk ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -13311,7 +15215,7 @@ The secret word is ... ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -13356,6 +15260,39 @@ span of text. % \end{markdown} % % \iffalse +%</manual-tokens> +%<*tex> +% \fi +% +% \begin{macrocode} +\def\markdownRendererEmphasis{% + \markdownRendererEmphasisPrototype}% +\ExplSyntaxOn +\seq_gput_right:Nn + \g_@@_renderers_seq + { emphasis } +\prop_gput:Nnn + \g_@@_renderer_arities_prop + { emphasis } + { 1 } +\ExplSyntaxOff +% \end{macrocode} +% \par +% +% \iffalse +%</tex> +%<*manual-tokens> +% \fi +% +% \begin{markdown} + +The \mdef{markdownRendererStrongEmphasis} macro represents a strongly +emphasized span of text. The macro receives a single argument that +corresponds to the emphasized span of text. + +% \end{markdown} +% +% \iffalse ##### Plain \TeX{} Example {.unnumbered} @@ -13433,7 +15370,7 @@ This is **strong emphasis**. ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -13447,39 +15384,6 @@ following text: % \fi % % \begin{macrocode} -\def\markdownRendererEmphasis{% - \markdownRendererEmphasisPrototype}% -\ExplSyntaxOn -\seq_gput_right:Nn - \g_@@_renderers_seq - { emphasis } -\prop_gput:Nnn - \g_@@_renderer_arities_prop - { emphasis } - { 1 } -\ExplSyntaxOff -% \end{macrocode} -% \par -% -% \iffalse -%</tex> -%<*manual-tokens> -% \fi -% -% \begin{markdown} - -The \mdef{markdownRendererStrongEmphasis} macro represents a strongly -emphasized span of text. The macro receives a single argument that -corresponds to the emphasized span of text. - -% \end{markdown} -% -% \iffalse -%</manual-tokens> -%<*tex> -% \fi -% -% \begin{macrocode} \def\markdownRendererStrongEmphasis{% \markdownRendererStrongEmphasisPrototype}% \ExplSyntaxOn @@ -13606,7 +15510,7 @@ enabled. The \mdef{markdownRendererFencedDivAttributeContextBegin} and \mdef{markdownRendererFencedDivAttributeContextEnd} macros represent the beginning -and the end of a div in which the attributes of the div apply. The macros +and the end of a context in which the attributes of a div apply. The macros receive no arguments. % \end{markdown} @@ -13721,12 +15625,12 @@ following text: % \begin{markdown} #### Header Attribute Context Renderers -The following macros are only produced, when the \Opt{headerAttributes} option -is enabled. +The following macros are only produced, when the \Opt{autoIdentifiers}, +\Opt{gfmAutoIdentifiers}, or \Opt{headerAttributes} options are enabled. The \mdef{markdownRendererHeaderAttributeContextBegin} and \mdef{markdownRendererHeaderAttributeContextEnd} macros represent the beginning -and the end of a section in which the attributes of a heading apply. The macros +and the end of a context in which the attributes of a heading apply. The macros receive no arguments. % \end{markdown} @@ -13790,13 +15694,6 @@ following text: %</manual-tokens> %<*tex> % \fi -% \begin{markdown} -% -% These semantics have been deprecated and will be changed in Markdown 3.0.0. -% From then on, header attribute contexts will only span headings, not the -% surrounding sections. -% -% \end{markdown} % \begin{macrocode} \def\markdownRendererHeaderAttributeContextBegin{% \markdownRendererHeaderAttributeContextBeginPrototype}% @@ -14096,7 +15993,7 @@ following content: ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -14129,17 +16026,13 @@ following text: % % \begin{markdown} -#### HTML Comment Renderers +#### Inline HTML Comment Renderer The \mdef{markdownRendererInlineHtmlComment} macro represents the contents of an inline \acro{HTML} comment. This macro will only be produced, when the \Opt{html} option is enabled. The macro receives a single argument that corresponds to the contents of the \acro{HTML} comment. -The \mdef{markdownRendererBlockHtmlCommentBegin} and -\mdef{markdownRendererBlockHtmlCommentEnd} macros represent the beginning -and the end of a block \acro{HTML} comment. The macros receive no arguments. - % \end{markdown} % % \iffalse @@ -14155,8 +16048,6 @@ following content: \markdownSetup{ renderers = { inlineHtmlComment = {\marginnote{#1}}, - blockHtmlCommentBegin = {\begin{quote}}, - blockHtmlCommentEnd = {\end{quote}}, }, } \begin{document} @@ -14164,14 +16055,6 @@ following content: A useful use of inline HTML comments are side notes. <!-- Side notes are displayed in the horizontal margins next to the relevant passages, which makes them *easier for the reader to find* than notes. --> - -We can render block HTML comments as blockquotes: - -<!-- -Here is a block HTML comment with a code example that a programmer might understand: - - foo = bar + baz - 42 ---> \end{markdown} \end{document} ``````` @@ -14184,13 +16067,6 @@ A PDF document named `document.pdf` should be produced and contain the following body text: > A useful use of HTML comments are side notes. -> -> We can render block HTML comments as blockquotes: -> -> > Here is a block HTML comment with a code example that a programmer might -> > understand: -> > -> > foo = bar + baz - 42 The horizontal margins should contain the following text: @@ -14213,28 +16089,6 @@ The horizontal margins should contain the following text: { inlineHtmlComment } { 1 } \ExplSyntaxOff -\def\markdownRendererBlockHtmlCommentBegin{% - \markdownRendererBlockHtmlCommentBeginPrototype}% -\ExplSyntaxOn -\seq_gput_right:Nn - \g_@@_renderers_seq - { blockHtmlCommentBegin } -\prop_gput:Nnn - \g_@@_renderer_arities_prop - { blockHtmlCommentBegin } - { 0 } -\ExplSyntaxOff -\def\markdownRendererBlockHtmlCommentEnd{% - \markdownRendererBlockHtmlCommentEndPrototype}% -\ExplSyntaxOn -\seq_gput_right:Nn - \g_@@_renderers_seq - { blockHtmlCommentEnd } -\prop_gput:Nnn - \g_@@_renderer_arities_prop - { blockHtmlCommentEnd } - { 0 } -\ExplSyntaxOff % \end{macrocode} % \par % @@ -14374,7 +16228,7 @@ following content: }^^A {^^A \everyeof={\noexpand}^^A - \edef\filename{\@@input"\jobname.fetched" }^^A + \edef\filename{\@@@@input"\jobname.fetched" }^^A \includegraphics[width=\textwidth]{\filename}^^A }^^A } @@ -14429,7 +16283,7 @@ is enabled. The \mdef{markdownRendererImageAttributeContextBegin} and \mdef{markdownRendererImageAttributeContextEnd} macros represent the beginning -and the end of an image in which the attributes of the image apply. The macros +and the end of a context in which the attributes of an image apply. The macros receive no arguments. % \end{markdown} @@ -14504,9 +16358,10 @@ following text: % % \begin{markdown} -#### Interblock Separator Renderer -The \mdef{markdownRendererInterblockSeparator} macro represents a separator -between two markdown block elements. The macro receives no arguments. +#### Interblock Separator Renderers +The \mdef{markdownRendererInterblockSeparator} macro represents an interblock +separator between two markdown block elements. The macro receives no +arguments. % \end{markdown} % @@ -14524,7 +16379,7 @@ following content: \par } \markdownBegin -Hello *world*! +- Hello *world*! _Foo_ bar! \markdownEnd @@ -14537,7 +16392,7 @@ luatex document.tex A PDF document named `document.pdf` should be produced and contain the following text: -> Hello *world*! +> - Hello *world*! > > *(The end of a block)* > @@ -14561,7 +16416,7 @@ following content: } \begin{document} \begin{markdown} -Hello *world*! +- Hello *world*! _Foo_ bar! \end{markdown} @@ -14574,7 +16429,7 @@ lualatex document.tex A PDF document named `document.pdf` should be produced and contain the following text: -> Hello *world*! +> - Hello *world*! > > *(The end of a block)* > @@ -14593,7 +16448,7 @@ following content: } \starttext \startmarkdown -Hello *world*! +- Hello *world*! _Foo_ bar! \stopmarkdown @@ -14601,12 +16456,12 @@ _Foo_ bar! ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: -> Hello *world*! +> - Hello *world*! > > *(The end of a block)* > @@ -14638,7 +16493,82 @@ following text: % % \begin{markdown} -#### Line Block Renderer +Users can use more than one blank line to delimit two block to indicate the +end of a series of blocks that make up a logical paragraph. This produces a +paragraph separator instead of an interblock separator. Between some blocks, +such as markdown paragraphs, a paragraph separator is always produced. + +The \mdef{markdownRendererParagraphSeparator} macro represents a paragraph +separator. The macro receives no arguments. + +% \end{markdown} +% +% \iffalse + +##### \LaTeX{} Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +``` tex +\documentclass{article} +\usepackage{markdown} +\markdownSetup{ + renderers = { + paragraphSeparator = {% + \par + \emph{(The end of a paragraph)}% + \par + }, + }, +} +\begin{document} +\begin{markdown} +Hello *world*! + +_Foo_ bar! +\end{markdown} +\end{document} +``````` +Next, invoke LuaTeX from the terminal: +``` sh +lualatex document.tex +`````` +A PDF document named `document.pdf` should be produced and contain the +following text: + +> Hello *world*! +> +> *(The end of a paragraph)* +> +> _Foo_ bar! + +%</manual-tokens> +%<*tex> +% \fi +% +% \begin{macrocode} +\def\markdownRendererParagraphSeparator{% + \markdownRendererParagraphSeparatorPrototype}% +\ExplSyntaxOn +\seq_gput_right:Nn + \g_@@_renderers_seq + { paragraphSeparator } +\prop_gput:Nnn + \g_@@_renderer_arities_prop + { paragraphSeparator } + { 0 } +\ExplSyntaxOff +% \end{macrocode} +% \par +% +% \iffalse +%</tex> +%<*manual-tokens> +% \fi +% +% \begin{markdown} + +#### Line Block Renderers The following macros are only produced, when the \Opt{lineBlocks} option is enabled. @@ -14722,7 +16652,7 @@ following content: ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -14769,7 +16699,114 @@ following text: % % \begin{markdown} -#### Line Break Renderer +#### Line Break Renderers + +The \mdef{markdownRendererSoftLineBreak} macro represents a soft line break. +The macro receives no arguments. + +% \end{markdown} +% +% \iffalse + +##### Plain \TeX{} Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +``` tex +\input markdown +\def\markdownRendererSoftLineBreak{% + \par + {\it(A soft line break)}% + \par +} +\markdownInput{example.md} +\bye +``````` +Using a text editor, create a text document named `example.md` with the +following content: + +``` md +Hello world! +_Foo_ bar! +`````` +Next, invoke LuaTeX from the terminal: +``` sh +luatex document.tex +`````` +A PDF document named `document.pdf` should be produced and contain the +following text: + +> Hello *world*! +> +> *(A soft line break)* +> +> _Foo_ bar! + +##### \LaTeX{} Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +``` tex +\documentclass{article} +\usepackage{markdown} +\markdownSetup{ + renderers = { + softLineBreak = {% + \par + \emph{(A soft line break)}% + \par + }, + }, +} +\begin{document} +\markdownInput{example.md} +\end{document} +``````` +Using a text editor, create a text document named `example.md` with the +following content: + +``` md +Hello world! +_Foo_ bar! +`````` +Next, invoke LuaTeX from the terminal: +``` sh +lualatex document.tex +`````` +A PDF document named `document.pdf` should be produced and contain the +following text: + +> Hello *world*! +> +> *(A soft line break)* +> +> _Foo_ bar! + +%</manual-tokens> +%<*tex> +% \fi +% \begin{macrocode} +\def\markdownRendererSoftLineBreak{% + \markdownRendererSoftLineBreakPrototype}% +\ExplSyntaxOn +\seq_gput_right:Nn + \g_@@_renderers_seq + { softLineBreak } +\prop_gput:Nnn + \g_@@_renderer_arities_prop + { softLineBreak } + { 0 } +\ExplSyntaxOff +% \end{macrocode} +% \par +% +% \iffalse +%</tex> +%<*manual-tokens> +% \fi +% +% \begin{markdown} + The \mdef{markdownRendererHardLineBreak} macro represents a hard line break. The macro receives no arguments. @@ -14856,51 +16893,10 @@ following text: %</manual-tokens> %<*tex> % \fi -% \begin{markdown} -% -% The \mdef{markdownRendererLineBreak} and -% \mdef{markdownRendererLineBreakPrototype} macros have been deprecated -% and will be removed in Markdown 3.0.0. -% -% \end{markdown} % \begin{macrocode} +\def\markdownRendererHardLineBreak{% + \markdownRendererHardLineBreakPrototype}% \ExplSyntaxOn -\cs_new:Npn - \markdownRendererHardLineBreak - { - \cs_if_exist:NTF - \markdownRendererLineBreak - { - \markdownWarning - { - Line~break~renderer~has~been~deprecated,~ - to~be~removed~in~Markdown~3.0.0 - } - \markdownRendererLineBreak - } - { - \cs_if_exist:NTF - \markdownRendererLineBreakPrototype - { - \markdownWarning - { - Line~break~renderer~prototype~has~been~deprecated,~ - to~be~removed~in~Markdown~3.0.0 - } - \markdownRendererLineBreakPrototype - } - { - \markdownRendererHardLineBreakPrototype - } - } - } -\seq_gput_right:Nn - \g_@@_renderers_seq - { lineBreak } -\prop_gput:Nnn - \g_@@_renderer_arities_prop - { lineBreak } - { 0 } \seq_gput_right:Nn \g_@@_renderers_seq { hardLineBreak } @@ -15009,7 +17005,7 @@ Please visit [the link][ctan]. ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -15049,7 +17045,7 @@ is enabled. The \mdef{markdownRendererLinkAttributeContextBegin} and \mdef{markdownRendererLinkAttributeContextEnd} macros represent the beginning -and the end of a hyperlink in which the attributes of the hyperlink apply. +and the end of a context in which the attributes of a hyperlink apply. The macros receive no arguments. % \end{markdown} @@ -15124,6 +17120,73 @@ following text: % % \begin{markdown} +#### Marked Text Renderer +The following macro is only produced, when the \Opt{mark} option is enabled. + +The \mdef{markdownRendererMark} macro represents a span of marked or +highlighted text. The macro receives a single argument that corresponds to +the marked text. + +% \end{markdown} +% +% \iffalse + +##### \LaTeX{} Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +``` tex +\documentclass{article} +\usepackage[mark]{markdown} +\usepackage{soul} +\markdownSetup{ + renderers = { + mark = {\hl{#1}}, + }, +} +\begin{document} +\begin{markdown} + +This ==is highlighted text.== + +\end{markdown} +\end{document} +``````` +Next, invoke LuaTeX from the terminal: +``` sh +lualatex document.tex +`````` +A PDF document named `document.pdf` should be produced and contain the +following text: + +> This <mark>is highlighted text.</mark> + +%</manual-tokens> +%<*tex> +% \fi +% +% \begin{macrocode} +\def\markdownRendererMark{% + \markdownRendererMarkPrototype}% +\ExplSyntaxOn +\seq_gput_right:Nn + \g_@@_renderers_seq + { mark } +\prop_gput:Nnn + \g_@@_renderer_arities_prop + { mark } + { 1 } +\ExplSyntaxOff +% \end{macrocode} +% \par +% +% \iffalse +%</tex> +%<*manual-tokens> +% \fi +% +% \begin{markdown} + #### Markdown Document Renderers The \mdef{markdownRendererDocumentBegin} and \mdef{markdownRendererDocumentEnd} macros represent the beginning and the end of a *markdown* document. The macros @@ -15450,7 +17513,7 @@ This is some text[^1] and this is some other text[^2]. ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -15461,51 +17524,10 @@ following text: %</manual-tokens> %<*tex> % \fi -% \begin{markdown} -% -% The \mdef{markdownRendererFootnote} and -% \mdef{markdownRendererFootnotePrototype} macros have been deprecated -% and will be removed in Markdown 3.0.0. -% -% \end{markdown} % \begin{macrocode} +\def\markdownRendererNote{% + \markdownRendererNotePrototype}% \ExplSyntaxOn -\cs_new:Npn - \markdownRendererNote - { - \cs_if_exist:NTF - \markdownRendererFootnote - { - \markdownWarning - { - Footnote~renderer~has~been~deprecated,~ - to~be~removed~in~Markdown~3.0.0 - } - \markdownRendererFootnote - } - { - \cs_if_exist:NTF - \markdownRendererFootnotePrototype - { - \markdownWarning - { - Footnote~renderer~prototype~has~been~deprecated,~ - to~be~removed~in~Markdown~3.0.0 - } - \markdownRendererFootnotePrototype - } - { - \markdownRendererNotePrototype - } - } - } -\seq_gput_right:Nn - \g_@@_renderers_seq - { footnote } -\prop_gput:Nnn - \g_@@_renderer_arities_prop - { footnote } - { 1 } \seq_gput_right:Nn \g_@@_renderers_seq { note } @@ -16074,7 +18096,7 @@ following content: \usepackage[tightLists, startNumber]{markdown} \begin{document} -\begin{markdown*}{ +\begin{markdown}[ renderers = { interblockSeparator = {}, olBeginTight = { (}, @@ -16092,15 +18114,15 @@ following content: olItemEnd = {}, olEndTight = {).}, }, -} +] This is a tight list 1. item 2. item 3. item -\end{markdown*} +\end{markdown} -\begin{markdown*}{ +\begin{markdown}[ renderers = { interblockSeparator = {% :\par @@ -16122,7 +18144,7 @@ This is a tight list olItemEnd = {.}, olEnd = {\end{enumerate}}, }, -} +] This is a loose list 1. item @@ -16130,7 +18152,7 @@ This is a loose list 2. item 3. item -\end{markdown*} +\end{markdown} \end{document} ``````` @@ -16223,7 +18245,7 @@ This is a loose list ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -16264,103 +18286,6 @@ following text: % % \begin{markdown} -#### Parenthesized Citations Renderer -The \mdef{markdownRendererCite} macro represents a string of one or more -parenthetical citations. This macro will only be produced, when the -\Opt{citations} option is enabled. The macro receives the parameter -`{`\meta{number of citations}`}` followed by \meta{suppress author} -`{`\meta{prenote}`}{`\meta{postnote}`}{`\meta{name}`}` repeated -\meta{number of citations} times. The \meta{suppress author} parameter is -either the token `-`, when the author's name is to be suppressed, or `+` -otherwise. - -% \end{markdown} -% -% \iffalse - -##### \LaTeX{} Example {.unnumbered} - -Using a text editor, create a text document named `document.tex` with the -following content: -``` tex -\documentclass{article} -\usepackage[citations]{markdown} -\newcount\citationsCounter -\newcount\citationsTotal -\makeatletter -\def\citations#1#2#3#4{% - a parenthesized citation \emph{#4} - \advance\citationsCounter by 1\relax - \ifx\relax#2\relax - \ifx\relax#3\relax\else - with a postfix \emph{#3}% - \fi - \else - with a prefix \emph{#2}% - \ifx\relax#3\relax\else - \ and a postfix \emph{#3}% - \fi - \fi - \ifnum\citationsCounter>\citationsTotal\relax - .% - \expandafter\@gobble - \else - , and - \fi\citations} -\makeatother -\markdownSetup{ - renderers = { - cite = {% - \citationsCounter=1% - \citationsTotal=#1% - This is - \expandafter\citations - }, - }, -} -\begin{document} -\begin{markdown} -[see @abrahams90, pp. 12; @eijkhout91, pp. 34] -\end{markdown} -\end{document} -``````` -Next, invoke LuaTeX from the terminal: -``` sh -lualatex document.tex -`````` -A PDF document named `document.pdf` should be produced and contain the -following text: - -> This is a parenthesized citation *abrahams90* with a prefix see -> and a postfix *pp. 12*, and a citation *eijkhout91* with a -> postfix *pp. 34*. - -%</manual-tokens> -%<*tex> -% \fi -% -% \begin{macrocode} -\def\markdownRendererCite{% - \markdownRendererCitePrototype}% -\ExplSyntaxOn -\seq_gput_right:Nn - \g_@@_renderers_seq - { cite } -\prop_gput:Nnn - \g_@@_renderer_arities_prop - { cite } - { 1 } -\ExplSyntaxOff -% \end{macrocode} -% \par -% -% \iffalse -%</tex> -%<*manual-tokens> -% \fi -% -% \begin{markdown} - #### Raw Content Renderers The \mdef{markdownRendererInputRawInline} macro represents an inline raw span. The macro receives two arguments: the filename of a file contaning the inline @@ -16648,7 +18573,7 @@ Bartel~Leendert van~der~Waerden ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text, where the middot (`·`) denotes a non-breaking space: @@ -16868,7 +18793,7 @@ This is ~~a lunar roving vehicle~~ strike-through text. ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -16976,7 +18901,7 @@ H~2~O is a liquid. ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -17083,7 +19008,7 @@ following content: ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -17116,6 +19041,126 @@ following text: % % \begin{markdown} +#### Table Attribute Context Renderers +The following macros are only produced, when the \Opt{tableCaptions} and +\Opt{tableAttributes} options are enabled. + +The \mdef{markdownRendererTableAttributeContextBegin} and +\mdef{markdownRendererTableAttributeContextEnd} macros represent the +beginning and the end of a context in which the attributes of a table +apply. The macros receive no arguments. + +% \end{markdown} +% +% \iffalse + +##### \LaTeX{} Example {.unnumbered} + +Using a text editor, create a text document named `document.tex` with the +following content: +``` tex +\documentclass{article} +\usepackage[ + pipeTables, + tableCaptions, + tableAttributes, + relativeReferences, +]{markdown} +\usepackage{expl3} +\ExplSyntaxOn +\markdownSetup{ + renderers = { + tableAttributeContextBegin = { + \group_begin: + \markdownSetup{ + renderers = { + attributeIdentifier = { + \markdownSetup{ + renderers = { + tableAttributeContextEnd = { + \label{##1} + \group_end: + }, + }, + } + }, + }, + } + }, + tableAttributeContextEnd = { + \group_end: + }, + }, +} +\ExplSyntaxOff +\begin{document} +\begin{markdown} +See Table <#example-table>. + +| Right | Left | Default | Center | +|------:|:-----|---------|:------:| +| 12 | 12 | 12 | 12 | +| 123 | 123 | 123 | 123 | +| 1 | 1 | 1 | 1 | + + : Demonstration of pipe table syntax. {#example-table} +\end{markdown} +\end{document} +``````` +Next, invoke LuaTeX from the terminal: +``` sh +lualatex document.tex +`````` +A PDF document named `document.pdf` should be produced and contain the +following text: + +> See Table 1. +> +> | Right | Left | Default | Center | +> |------:|:-----|---------|:------:| +> | 12 | 12 | 12 | 12 | +> | 123 | 123 | 123 | 123 | +> | 1 | 1 | 1 | 1 | +> +> : Table 1. Demonstration of pipe table syntax. + +%</manual-tokens> +%<*tex> +% \fi +% +% \begin{macrocode} +\def\markdownRendererTableAttributeContextBegin{% + \markdownRendererTableAttributeContextBeginPrototype}% +\ExplSyntaxOn +\seq_gput_right:Nn + \g_@@_renderers_seq + { tableAttributeContextBegin } +\prop_gput:Nnn + \g_@@_renderer_arities_prop + { tableAttributeContextBegin } + { 0 } +\ExplSyntaxOff +\def\markdownRendererTableAttributeContextEnd{% + \markdownRendererTableAttributeContextEndPrototype}% +\ExplSyntaxOn +\seq_gput_right:Nn + \g_@@_renderers_seq + { tableAttributeContextEnd } +\prop_gput:Nnn + \g_@@_renderer_arities_prop + { tableAttributeContextEnd } + { 0 } +\ExplSyntaxOff +% \end{macrocode} +% \par +% +% \iffalse +%</tex> +%<*manual-tokens> +% \fi +% +% \begin{markdown} + #### Table Renderer The \mdef{markdownRendererTable} macro represents a table. This macro will only be produced, when the \Opt{pipeTables} option is enabled. The macro receives the @@ -17243,10 +19288,10 @@ following text: % % \begin{markdown} -#### Tex Math Renderers +#### \TeX{} Math Renderers The \mdef{markdownRendererInlineMath} and \mdef{markdownRendererDisplayMath} macros represent inline and display \TeX{} math. -Both macros receive a single argument that corresponds to the tex math content. +Both macros receive a single argument that corresponds to the \TeX{} math content. These macros will only be produced, when the \Opt{texMathDollars}, \Opt{texMathSingleBackslash}, or \Opt{texMathDoubleBackslash} option are enabled. @@ -17329,7 +19374,7 @@ $$\hat{f} \left ( \xi \right )= \int_{-\infty}^{\infty} f\left ( x \right ) e^ ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -17375,98 +19420,6 @@ following text: % % \begin{markdown} -#### Text Citations Renderer -The \mdef{markdownRendererTextCite} macro represents a string of one or more -text citations. This macro will only be produced, when the -\Opt{citations} option is enabled. The macro receives parameters in the same -format as the \mref{markdownRendererCite} macro. - -% \end{markdown} -% -% \iffalse - -##### \LaTeX{} Example {.unnumbered} - -Using a text editor, create a text document named `document.tex` with the -following content: -``` tex -\documentclass{article} -\usepackage[citations]{markdown} -\newcount\citationsCounter -\newcount\citationsTotal -\makeatletter -\def\citations#1#2#3#4{% - a text citation \emph{#4} - \advance\citationsCounter by 1\relax - \ifx\relax#2\relax - \ifx\relax#3\relax\else - with a postfix \emph{#3}% - \fi - \else - with a prefix \emph{#2}% - \ifx\relax#3\relax\else - \ and a postfix \emph{#3}% - \fi - \fi - \ifnum\citationsCounter>\citationsTotal\relax - .% - \expandafter\@gobble - \else - , and - \fi\citations} -\makeatother -\markdownSetup{ - renderers = { - textCite = {% - \citationsCounter=1% - \citationsTotal=#1% - This is - \expandafter\citations - }, - }, -} -\begin{document} -\begin{markdown} -@abrahams90 [pp. 12; also @eijkhout91] -\end{markdown} -\end{document} -``````` -Next, invoke LuaTeX from the terminal: -``` sh -lualatex document.tex -`````` -A PDF document named `document.pdf` should be produced and contain the -following text: - -> This is a text citation *abrahams90* with a postfix *pp. 12*, -> and a citation *eijkhout91* with a prefix *also*. - -%</manual-tokens> -%<*tex> -% \fi -% -% \begin{macrocode} -\def\markdownRendererTextCite{% - \markdownRendererTextCitePrototype}% -\ExplSyntaxOn -\seq_gput_right:Nn - \g_@@_renderers_seq - { textCite } -\prop_gput:Nnn - \g_@@_renderer_arities_prop - { textCite } - { 1 } -\ExplSyntaxOff -% \end{macrocode} -% \par -% -% \iffalse -%</tex> -%<*manual-tokens> -% \fi -% -% \begin{markdown} - #### Thematic Break Renderer The \mdef{markdownRendererThematicBreak} macro represents a thematic break. The macro receives no arguments. @@ -17558,7 +19511,7 @@ This is the second page. ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -17572,51 +19525,10 @@ following text: %</manual-tokens> %<*tex> % \fi -% \begin{markdown} -% -% The \mdef{markdownRendererHorizontalRule} and -% \mdef{markdownRendererHorizontalRulePrototype} macros have been deprecated -% and will be removed in Markdown 3.0.0. -% -% \end{markdown} % \begin{macrocode} +\def\markdownRendererThematicBreak{% + \markdownRendererThematicBreakPrototype}% \ExplSyntaxOn -\cs_new:Npn - \markdownRendererThematicBreak - { - \cs_if_exist:NTF - \markdownRendererHorizontalRule - { - \markdownWarning - { - Horizontal~rule~renderer~has~been~deprecated,~ - to~be~removed~in~Markdown~3.0.0 - } - \markdownRendererHorizontalRule - } - { - \cs_if_exist:NTF - \markdownRendererHorizontalRulePrototype - { - \markdownWarning - { - Horizontal~rule~renderer~prototype~has~been~deprecated,~ - to~be~removed~in~Markdown~3.0.0 - } - \markdownRendererHorizontalRulePrototype - } - { - \markdownRendererThematicBreakPrototype - } - } - } -\seq_gput_right:Nn - \g_@@_renderers_seq - { horizontalRule } -\prop_gput:Nnn - \g_@@_renderer_arities_prop - { horizontalRule } - { 0 } \seq_gput_right:Nn \g_@@_renderers_seq { thematicBreak } @@ -17695,7 +19607,7 @@ following content: ```````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex ````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -18182,15 +20094,300 @@ age: 99 ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: > Jane Doe is 99 years old. +%</manual-tokens> +%<*tex> % \fi +% \begin{markdown} +% +%#### Generating Plain \TeX{} Token Renderer Macros and Key-Values {#plain-tex-renderers} +% +% We define the command \mdef{@@_define_renderers:} that defines plain \TeX{} +% macros for token renderers. Futhermore, the `\markdownSetup` macro also accepts +% the `renderers` key, whose value must be a list of key-values, where the keys +% correspond to the markdown token renderer macros and the values are new +% definitions of these token renderers. +% +% \end{markdown} +% \begin{macrocode} +\ExplSyntaxOn +\cs_new:Nn \@@_define_renderers: + { + \seq_map_function:NN + \g_@@_renderers_seq + \@@_define_renderer:n + } +\cs_new:Nn \@@_define_renderer:n + { + \@@_renderer_tl_to_csname:nN + { #1 } + \l_tmpa_tl + \prop_get:NnN + \g_@@_renderer_arities_prop + { #1 } + \l_tmpb_tl + \@@_define_renderer:ncV + { #1 } + { \l_tmpa_tl } + \l_tmpb_tl + } +\cs_new:Nn \@@_renderer_tl_to_csname:nN + { + \tl_set:Nn + \l_tmpa_tl + { \str_uppercase:n { #1 } } + \tl_set:Nx + #2 + { + markdownRenderer + \tl_head:f { \l_tmpa_tl } + \tl_tail:n { #1 } + } + } +\tl_new:N + \l_@@_renderer_definition_tl +\cs_new:Nn \@@_define_renderer:nNn + { + \keys_define:nn + { markdown/options/renderers } + { + #1 .code:n = { + \tl_set:Nn + \l_@@_renderer_definition_tl + { ##1 } + \regex_replace_all:nnN + { \cP\#0 } + { #1 } + \l_@@_renderer_definition_tl + \cs_generate_from_arg_count:NNnV + #2 + \cs_set:Npn + { #3 } + \l_@@_renderer_definition_tl + }, + } + } +\cs_generate_variant:Nn + \@@_define_renderer:nNn + { ncV } +\cs_generate_variant:Nn + \cs_generate_from_arg_count:NNnn + { NNnV } +\keys_define:nn + { markdown/options } + { + renderers .code:n = { + \keys_set:nn + { markdown/options/renderers } + { #1 } + }, + } +\ExplSyntaxOff +% \end{macrocode} +% \par +% \begin{markdown} +% +% The following example code showcases a possible configuration of the +% \mref{markdownRendererLink} and \mref{markdownRendererEmphasis} token +% renderer macros. +% ``` tex +% \markdownSetup{ +% renderers = { +% link = {#4}, \% Render links as the link title. +% emphasis = {{\it #1}}, \% Render emphasized text using italics. +% } +% } +% ``````` +% +% In addition to exact token renderer names, we also support wildcards +% and enumerations that match multiple token renderer names: +% ``` tex +% \markdownSetup{ +% renderers = { +% heading* = {{\bf #1}}, \% Render headings using the bold face. +% jekyllData(String|Number) = { \% Render YAML string and numbers +% {\it #2}\% \% using italics. +% }, +% } +% } +% ``````` +% +% Wildcards and enumerations can be combined: +% ``` tex +% \markdownSetup{ +% renderers = { +% *lItem(|End) = {"}, \% Quote ordered/bullet list items. +% } +% } +% ``````` +% +% To determine the current token renderer, you can use the parameter `#0`: +% ``` tex +% \markdownSetup{ +% renderers = { +% heading* = {#0: #1}, \% Render headings as the renderer name +% } \% followed by the heading text. +% } +% ``````` +% +% \end{markdown} +% \begin{macrocode} +\ExplSyntaxOn +\prop_new:N + \g_@@_glob_cache_prop +\tl_new:N + \l_@@_current_glob_tl +\cs_new:Nn + \@@_glob_seq:nnN + { + \tl_set:Nn + \l_@@_current_glob_tl + { ^ #1 $ } + \prop_get:NeNTF + \g_@@_glob_cache_prop + { #2 / \l_@@_current_glob_tl } + \l_tmpa_clist + { + \seq_set_from_clist:NN + #3 + \l_tmpa_clist + } + { + \seq_clear:N + #3 + \regex_replace_all:nnN + { \* } + { .* } + \l_@@_current_glob_tl + \regex_set:NV + \l_tmpa_regex + \l_@@_current_glob_tl + \seq_map_inline:cn + { #2 } + { + \regex_match:NnT + \l_tmpa_regex + { ##1 } + { + \seq_put_right:Nn + #3 + { ##1 } + } + } + \clist_set_from_seq:NN + \l_tmpa_clist + #3 + \prop_gput:NeV + \g_@@_glob_cache_prop + { #2 / \l_@@_current_glob_tl } + \l_tmpa_clist + } + } +% TODO: Remove in TeX Live 2023. +\prg_generate_conditional_variant:Nnn + \prop_get:NnN + { NeN } + { TF } +\cs_generate_variant:Nn + \regex_set:Nn + { NV } +\cs_generate_variant:Nn + \prop_gput:Nnn + { NeV } +\seq_new:N + \l_@@_renderer_glob_results_seq +\keys_define:nn + { markdown/options/renderers } + { + unknown .code:n = { + \@@_glob_seq:VnN + \l_keys_key_str + { g_@@_renderers_seq } + \l_@@_renderer_glob_results_seq + \seq_if_empty:NTF + \l_@@_renderer_glob_results_seq + { + \msg_error:nnV + { markdown } + { undefined-renderer } + \l_keys_key_str + } + { + \tl_set:Nn + \l_@@_renderer_definition_tl + { #1 } + \seq_map_inline:Nn + \l_@@_renderer_glob_results_seq + { + \@@_renderer_tl_to_csname:nN + { ##1 } + \l_tmpa_tl + \prop_get:NnN + \g_@@_renderer_arities_prop + { ##1 } + \l_tmpb_tl + \int_set:Nn + \l_tmpa_int + \l_tmpb_tl + \tl_set:NV + \l_tmpb_tl + \l_@@_renderer_definition_tl + \regex_replace_all:nnN + { \cP\#0 } + { ##1 } + \l_tmpb_tl + \cs_generate_from_arg_count:cNVV + { \l_tmpa_tl } + \cs_set:Npn + \l_tmpa_int + \l_tmpb_tl + } + } + }, + } +\msg_new:nnn + { markdown } + { undefined-renderer } + { + Renderer~#1~is~undefined. + } +\cs_generate_variant:Nn + \@@_glob_seq:nnN + { VnN } +\cs_generate_variant:Nn + \cs_generate_from_arg_count:NNnn + { cNVV } +\cs_generate_variant:Nn + \msg_error:nnn + { nnV } +% \end{macrocode} +% \begin{markdown} +% +% If plain \TeX{} is the top layer, we use the \mref{@@_define_renderers:} +% macro to define plain \TeX{} token renderer macros and key-values +% immediately. Otherwise, we postpone the definition until the upper layers +% have been loaded. % +% \end{markdown} +% \begin{macrocode} +\str_if_eq:VVT + \c_@@_top_layer_tl + \c_@@_option_layer_plain_tex_tl + { + \@@_define_renderers: + } +\ExplSyntaxOff +% \end{macrocode} +% \iffalse +%</tex> +%<*manual-tokens> +% \fi % \begin{markdown} ### Token Renderer Prototypes {#texrendererprototypes} @@ -18250,13 +20447,13 @@ following content: ~ \end{markdown} -\begin{markdown*}{ +\begin{markdown}[ renderers = { tilde = {User-defined token renderers take precedence.}, }, -} +] ~ -\end{markdown*} +\end{markdown} \end{document} ``````` Next, invoke LuaTeX from the terminal: @@ -18294,7 +20491,7 @@ following content: ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -18416,7 +20613,7 @@ age: 99 ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -18435,49 +20632,89 @@ following text: % \end{macrocode} % \begin{markdown} % -% The following \TeX{} macros provide definitions for the token renderers (see -% Section <#sec:texrenderersuser>) that have not been redefined by the -% user. These macros are intended to be redefined by macro package authors -% who wish to provide sensible default token renderers. They are also redefined -% by the \LaTeX{} and \Hologo{ConTeXt} implementations (see sections -% <#sec:lateximplementation> and <#sec:contextimplementation>). +% The `jekyllDataRenderers` key can be used as a syntactic sugar for setting +% the `markdown/jekyllData` key--values without using the expl3 language. % % \end{markdown} % \begin{macrocode} \ExplSyntaxOn -\cs_new:Nn \@@_plaintex_define_renderer_prototypes: +\@@_with_various_cases:nn + { jekyllDataRenderers } { - \seq_map_function:NN - \g_@@_renderers_seq - \@@_plaintex_define_renderer_prototype:n - \let\markdownRendererBlockHtmlCommentBeginPrototype=\iffalse - \let\markdownRendererBlockHtmlCommentBegin=\iffalse - \let\markdownRendererBlockHtmlCommentEndPrototype=\fi - \let\markdownRendererBlockHtmlCommentEnd=\fi + \keys_define:nn + { markdown/options } + { + #1 .code:n = { + \tl_set:Nn + \l_tmpa_tl + { ##1 } % \end{macrocode} % \begin{markdown} % -% The \mref{markdownRendererFootnote} and -% \mref{markdownRendererFootnotePrototype} macros have been deprecated -% and will be removed in Markdown 3.0.0. +% To ensure that keys containing forward slashes get passed correctly, we +% replace all forward slashes in the input with backslash tokens with category +% code letter and then undo the replacement. This means that if any unbraced +% backslash tokens with category code letter exist in the input, they will be +% replaced with forward slashes. However, this should be extremely rare. % % \end{markdown} % \begin{macrocode} - \cs_undefine:N \markdownRendererFootnote - \cs_undefine:N \markdownRendererFootnotePrototype + \tl_replace_all:NnV + \l_tmpa_tl + { / } + \c_backslash_str + \keys_set:nV + { markdown/options/jekyll-data-renderers } + \l_tmpa_tl + }, + } + } +\keys_define:nn + { markdown/options/jekyll-data-renderers } + { + unknown .code:n = { + \tl_set_eq:NN + \l_tmpa_tl + \l_keys_key_str + \tl_replace_all:NVn + \l_tmpa_tl + \c_backslash_str + { / } + \tl_put_right:Nn + \l_tmpa_tl + { + .code:n = { #1 } + } + \keys_define:nV + { markdown/jekyllData } + \l_tmpa_tl + } + } +\cs_generate_variant:Nn + \keys_define:nn + { nV } +\ExplSyntaxOff % \end{macrocode} % \begin{markdown} % -% The \mref{markdownRendererHorizontalRule} and -% \mref{markdownRendererHorizontalRulePrototype} macros have been deprecated -% and will be removed in Markdown 3.0.0. +%#### Generating Plain \TeX{} Token Renderer Prototype Macros and Key-Values {#plain-tex-renderer-prototypes} +% +% We define the command \mdef{@@_define_renderer_prototypes:} that defines plain \TeX{} +% macros for token renderer prototypes. Futhermore, the `\markdownSetup` macro also accepts +% the `rendererPrototype` key, whose value must be a list of key-values, where the keys +% correspond to the markdown token renderer prototype macros and the values are new +% definitions of these token renderer prototypes. % % \end{markdown} % \begin{macrocode} - \cs_undefine:N \markdownRendererHorizontalRule - \cs_undefine:N \markdownRendererHorizontalRulePrototype +\ExplSyntaxOn +\cs_new:Nn \@@_define_renderer_prototypes: + { + \seq_map_function:NN + \g_@@_renderers_seq + \@@_define_renderer_prototype:n } -\cs_new:Nn \@@_plaintex_define_renderer_prototype:n +\cs_new:Nn \@@_define_renderer_prototype:n { \@@_renderer_prototype_tl_to_csname:nN { #1 } @@ -18486,7 +20723,8 @@ following text: \g_@@_renderer_arities_prop { #1 } \l_tmpb_tl - \@@_plaintex_define_renderer_prototype:cV + \@@_define_renderer_prototype:ncV + { #1 } { \l_tmpa_tl } \l_tmpb_tl } @@ -18504,23 +20742,191 @@ following text: Prototype } } -\cs_new:Nn \@@_plaintex_define_renderer_prototype:Nn +\tl_new:N + \l_@@_renderer_prototype_definition_tl +\cs_new:Nn \@@_define_renderer_prototype:nNn { - \cs_generate_from_arg_count:NNnn - #1 - \cs_set:Npn - { #2 } - { } + \keys_define:nn + { markdown/options/renderer-prototypes } + { + #1 .code:n = { + \tl_set:Nn + \l_@@_renderer_prototype_definition_tl + { ##1 } + \regex_replace_all:nnN + { \cP\#0 } + { #1 } + \l_@@_renderer_prototype_definition_tl + \cs_generate_from_arg_count:NNnV + #2 + \cs_set:Npn + { #3 } + \l_@@_renderer_prototype_definition_tl + }, + } +% \end{macrocode} +% \begin{markdown} +% +% Unless the token renderer prototype macro has already been defined, +% we provide an empty definition. +% +% \end{markdown} +% \begin{macrocode} + \cs_if_free:NT + #2 + { + \cs_generate_from_arg_count:NNnn + #2 + \cs_set:Npn + { #3 } + { } + } } \cs_generate_variant:Nn - \@@_plaintex_define_renderer_prototype:Nn - { cV } -\@@_plaintex_define_renderer_prototypes: + \@@_define_renderer_prototype:nNn + { ncV } \ExplSyntaxOff % \end{macrocode} % \par % \begin{markdown} % +% The following example code showcases a possible configuration of the +% `\markdownRendererImagePrototype` and `\markdownRendererCodeSpanPrototype` +% token renderer prototype macros. +% ``` tex +% \markdownSetup{ +% rendererPrototypes = { +% image = {\pdfximage{#2}}, \% Embed PDF images in the document. +% codeSpan = {{\tt #1}}, \% Render inline code using monospace. +% } +% } +% ``````` +% +% In addition to exact token renderer names, we also support wildcards +% and enumerations that match multiple token renderer prototype names: +% ``` tex +% \markdownSetup{ +% rendererPrototypes = { +% heading* = {{\bf #1}}, \% Render headings using the bold face. +% jekyllData(String|Number) = { \% Render YAML string and numbers +% {\it #2}\% \% using italics. +% }, +% } +% } +% ``````` +% +% Wildcards and enumerations can be combined: +% ``` tex +% \markdownSetup{ +% rendererPrototypes = { +% *lItem(|End) = {"}, \% Quote ordered/bullet list items. +% } +% } +% ``````` +% +% To determine the current token renderer prototype, you can use the +% parameter `#0`: +% ``` tex +% \markdownSetup{ +% rendererPrototypes = { +% heading* = {#0: #1}, \% Render headings as the renderer prototype +% } \% name followed by the heading text. +% } +% ``````` +% +% \end{markdown} +% \begin{macrocode} +\ExplSyntaxOn +\seq_new:N + \l_@@_renderer_prototype_glob_results_seq +\keys_define:nn + { markdown/options/renderer-prototypes } + { + unknown .code:n = { + \@@_glob_seq:VnN + \l_keys_key_str + { g_@@_renderers_seq } + \l_@@_renderer_prototype_glob_results_seq + \seq_if_empty:NTF + \l_@@_renderer_prototype_glob_results_seq + { + \msg_error:nnV + { markdown } + { undefined-renderer-prototype } + \l_keys_key_str + } + { + \tl_set:Nn + \l_@@_renderer_prototype_definition_tl + { #1 } + \seq_map_inline:Nn + \l_@@_renderer_prototype_glob_results_seq + { + \@@_renderer_prototype_tl_to_csname:nN + { ##1 } + \l_tmpa_tl + \prop_get:NnN + \g_@@_renderer_arities_prop + { ##1 } + \l_tmpb_tl + \int_set:Nn + \l_tmpa_int + \l_tmpb_tl + \tl_set:NV + \l_tmpb_tl + \l_@@_renderer_prototype_definition_tl + \regex_replace_all:nnN + { \cP\#0 } + { ##1 } + \l_tmpb_tl + \cs_generate_from_arg_count:cNVV + { \l_tmpa_tl } + \cs_set:Npn + \l_tmpa_int + \l_tmpb_tl + } + } + }, + } +\msg_new:nnn + { markdown } + { undefined-renderer-prototype } + { + Renderer~prototype~#1~is~undefined. + } +\@@_with_various_cases:nn + { rendererPrototypes } + { + \keys_define:nn + { markdown/options } + { + #1 .code:n = { + \keys_set:nn + { markdown/options/renderer-prototypes } + { ##1 } + }, + } + } +% \end{macrocode} +% \begin{markdown} +% +% If plain \TeX{} is the top layer, we use the \mref{@@_define_renderer_prototypes:} +% macro to define plain \TeX{} token renderer prototype macros and key-values +% immediately. Otherwise, we postpone the definition until the upper layers +% have been loaded. +% +% \end{markdown} +% \begin{macrocode} +\str_if_eq:VVT + \c_@@_top_layer_tl + \c_@@_option_layer_plain_tex_tl + { + \@@_define_renderer_prototypes: + } +\ExplSyntaxOff +% \end{macrocode} +% \begin{markdown} +% %### Logging Facilities % The \mdef{markdownInfo}, \mdef{markdownWarning}, and \mdef{markdownError} % macros perform logging for the Markdown package. Their first argument @@ -18529,6 +20935,10 @@ following text: % text. You may redefine these macros to redirect and process the info, % warning, and error messages. % +% The \mref{markdownInfo}, \mref{markdownWarning}, and \mref{markdownError} +% macros have been deprecated and will be removed in the next major version of +% the Markdown package. +% %### Miscellanea % The \mdef{markdownMakeOther} macro is used by the package, when a \TeX{} % engine that does not support direct Lua access is starting to buffer a text. @@ -18572,72 +20982,26 @@ following text: |endgroup % \end{macrocode} % \begin{markdown} -% The macro is exposed in the interface, so that the user can create their own -% markdown environments. Due to the way the arguments are passed to Lua (see -% Section <#sec:direct-lua>), the first argument may not contain the -% string `]]` (regardless of the category code of the bracket symbol (`]`)). -% -% The \mdef{markdownMode} macro specifies how the plain \TeX{} implementation -% interfaces with the Lua interface. The valid values and their meaning are -% as follows: -% -% - `0` – Shell escape via the 18 output file stream -% - `1` – Shell escape via the Lua `os.execute` method -% - `2` – Direct Lua access -% - `3` – The \pkg{lt3luabridge} Lua package -% -% By defining the macro, the user can coerce the package to use a specific mode. -% If the user does not define the macro prior to loading the plain \TeX{} -% implementation, the correct value will be automatically detected. The outcome -% of changing the value of \mref{markdownMode} after the implementation has been -% loaded is undefined. -% -% The \mref{markdownMode} macro has been deprecated and will be removed in -% Markdown 3.0.0. The code that corresponds to \mref{markdownMode} value of `3` -% will be the only implementation. +% The macro is exposed in the interface, so that users can create their own +% markdown environments. Due to the way the arguments are passed to Lua, the +% first argument may not contain the string `]]` (regardless of the category +% code of the bracket symbol). % +% The `code` key, which can be used to immediately expand and execute code. % \end{markdown} % \begin{macrocode} \ExplSyntaxOn -\cs_if_exist:NF - \markdownMode +\keys_define:nn + { markdown/options } { - \file_if_exist:nTF - { lt3luabridge.tex } - { - \cs_new:Npn - \markdownMode - { 3 } - } - { - \cs_if_exist:NTF - \directlua - { - \cs_new:Npn - \markdownMode - { 2 } - } - { - \cs_new:Npn - \markdownMode - { 0 } - } - } + code .code:n = { #1 }, } \ExplSyntaxOff % \end{macrocode} -% \par % \begin{markdown} -% -% The \mdef{markdownLuaRegisterIBCallback} and -% \mdef{markdownLuaUnregisterIBCallback} macros have been deprecated and will -% be removed in Markdown 3.0.0: +% This can be especially useful in snippets. % % \end{markdown} -% \begin{macrocode} -\def\markdownLuaRegisterIBCallback#1{\relax}% -\def\markdownLuaUnregisterIBCallback#1{\relax}% -% \end{macrocode} % \iffalse %</tex> % \fi @@ -18674,9 +21038,9 @@ following content: $\sqrt{-1}$ *equals* $i$ \end{markdown} -\begin{markdown*}{hybrid} +\begin{markdown}[texMathDollars] $\sqrt{-1}$ *equals* $i$ -\end{markdown*} +\end{markdown} \end{document} ``````` @@ -18710,15 +21074,26 @@ pdflatex --shell-escape document.tex % rest of the interface is inherited from the plain \TeX{} interface (see % Section <#sec:texinterface>). % -% The \LaTeX{} implementation redefines the plain \TeX{} logging macros (see -% Section <#sec:tex-interface-logging>) to use the \LaTeX{} `\PackageInfo`, -% `\PackageWarning`, and `\PackageError` macros. +% To determine whether \LaTeX{} is the top layer or if there are other +% layers above \LaTeX{}, we take a look on whether the +% \mref{c_@@_top_layer_tl} token list has already been defined. If not, +% we will assume that \LaTeX{} is the top layer. % % \end{markdown} % \begin{macrocode} -\newcommand\markdownInfo[1]{\PackageInfo{markdown}{#1}}% -\newcommand\markdownWarning[1]{\PackageWarning{markdown}{#1}}% -\newcommand\markdownError[2]{\PackageError{markdown}{#1}{#2.}}% +\ExplSyntaxOn +\tl_const:Nn \c_@@_option_layer_latex_tl { latex } +\cs_generate_variant:Nn + \tl_const:Nn + { NV } +\tl_if_exist:NF + \c_@@_top_layer_tl + { + \tl_const:NV + \c_@@_top_layer_tl + \c_@@_option_layer_latex_tl + } +\ExplSyntaxOff \input markdown/markdown % \end{macrocode} % \begin{markdown} @@ -18733,12 +21108,12 @@ pdflatex --shell-escape document.tex % where \meta{options} are the \LaTeX{} interface options (see Section % <#sec:latex-options>). Note that \meta{options} inside the `\usepackage` % macro may not set the `markdownRenderers` (see Section -% <#sec:latex-renderers>) and `markdownRendererPrototypes` (see Section -% <#sec:latex-renderer-prototypes>) keys. Furthermore, although the +% <#sec:plain-tex-renderers>) and `markdownRendererPrototypes` (see Section +% <#sec:plain-tex-renderer-prototypes>) keys. Furthermore, although the % base variant of the `import` key that loads a single \LaTeX{} theme % (see Section <#sec:latexthemes>) can be used, the extended variant % that can load multiple themes and import snippets from them (see -% Section <#sec:latexsnippets>). This limitation is due to the way +% Section <#sec:snippets>) cannot. This limitation is due to the way % \Hologo{LaTeX2e} parses package options. % % \end{markdown} @@ -18749,11 +21124,14 @@ pdflatex --shell-escape document.tex % The interface exposes the \envmdef{markdown} and \envmdef{markdown*} % \LaTeX{} environments, and redefines the \mref{markdownInput} command. % -% The \envmref{markdown} and \envmref{markdown*} \LaTeX{} environments are used to -% typeset markdown document fragments. The starred version of the -% \envmref{markdown} environment accepts \LaTeX{} interface options (see -% Section <#sec:latex-options>) as its only argument. These options will -% only influence this markdown document fragment. +% The \envmref{markdown} and \envmref{markdown*} \LaTeX{} environments are used +% to typeset markdown document fragments. Both \LaTeX{} environments accept +% \LaTeX{} interface options (see ection <#sec:latex-options>) as the only +% argument. This argument is optional for the \envmref{markdown} environment +% and mandatory for the \envmref{markdown*} environment. +% +% The \envmref{markdown*} environment has been deprecated and will be removed +% in the next major version of the Markdown package. % % \end{markdown} % \begin{macrocode} @@ -18763,7 +21141,7 @@ pdflatex --shell-escape document.tex % \markdownBegin % % You may prepend your own code to the \mdef{markdown} macro and append your own -% code to the \mdef{endmarkdown} macro to produce special effects before and after +% code to the \mref{markdownEnd} macro to produce special effects before and after % the \envmref{markdown} \LaTeX{} environment (and likewise for the starred % version). % @@ -18778,7 +21156,7 @@ pdflatex --shell-escape document.tex % \usepackage{markdown} \usepackage{markdown} % \begin{document} \begin{document} % \% ... \% ... -% \begin{markdown} \begin{markdown*}{smartEllipses} +% \begin{markdown}[smartEllipses] \begin{markdown*}{smartEllipses} % _Hello_ **world** ... _Hello_ **world** ... % \end{markdown} \end{markdown*} % \% ... \% ... @@ -18812,9 +21190,6 @@ pdflatex --shell-escape document.tex % part is optional, and \meta{key} will be interpreted as \meta{key}`=true` % if the `=`\meta{value} part has been omitted. % -% Except for the `plain` option described in Section <#sec:latexplain>, and -% the \LaTeX{} themes described in Section <#sec:latexthemes>, and the -% \LaTeX{} snippets described in Section <#sec:latex-setup-snippets>, % \LaTeX{} options map directly to the options recognized by the plain % \TeX{} interface (see Section <#sec:tex-options>) and to the markdown token % renderers and their prototypes recognized by the plain \TeX{} interface (see @@ -18822,75 +21197,36 @@ pdflatex --shell-escape document.tex % % The \LaTeX{} options may be specified when loading the \LaTeX{} package, when % using the \envmref{markdown*} \LaTeX{} environment or the \mref{markdownInput} -% macro (see Section <#sec:latexinterface>), or via the \mdef{markdownSetup} -% macro. The \mref{markdownSetup} macro receives the options to set up as its only -% argument: -% -% \markdownEnd -% \begin{macrocode} -\ExplSyntaxOn -\cs_new:Nn - \@@_setup:n - { - \keys_set:nn - { markdown/latex-options } - { #1 } - } -\let\markdownSetup=\@@_setup:n -\ExplSyntaxOff -% \end{macrocode} -% \begin{markdown} +% macro (see Section <#sec:latexinterface>), or via the \mref{markdownSetup} +% macro. % -%#### \LaTeX{} snippets {#latex-setup-snippets} +%#### Finalizing and Freezing the Cache % -% We may also set up \LaTeX{} options as *snippets* using the -% \mdef{markdownSetupSnippet} macro and invoke them later. The -% \mref{markdownSetupSnippet} macro receives two arguments: the name -% of the snippet and the options to store: +% To ensure compatibility with the `minted` package [@poore17, Section +% 5.1], which supports the `finalizecache` and `frozencache` package options +% with similar semantics to the \Opt{finalizeCache} and \Opt{frozenCache} plain +% \TeX{} options, the Markdown package also recognizes these as aliases +% and accepts them as document class options. By passing `finalizecache` and +% `frozencache` as document class options, you may conveniently control the +% behavior of both packages at once: % -% \end{markdown} -% \begin{macrocode} -\ExplSyntaxOn -\cs_new:Nn - \@@_latex_setup_snippet:nn - { - \markdownIfSnippetExists - { #1 } - { - \markdownWarning - {Redefined~snippet~\markdownLaTeXThemeName#1} - \csname markdownLaTeXSetupSnippet% - \markdownLaTeXThemeName#1\endcsname={#2} - } - { - \newtoks\next - \next={#2} - \expandafter\let\csname markdownLaTeXSetupSnippet% - \markdownLaTeXThemeName#1\endcsname=\next - } - } -\let\markdownSetupSnippet=\@@_latex_setup_snippet:nn -\ExplSyntaxOff -% \end{macrocode} -% \begin{markdown} +% ``` tex +% \documentclass[frozencache]{article} +% \usepackage{markdown,minted} +% \begin{document} +% % ... +% \end{document} +% ``````` % -% To decide whether a snippet exists, we can use the -% \mdef{markdownIfSnippetExists} macro: +% We hope that other packages will support the `finalizecache` and +% `frozencache` package options in the future, so that they can become a +% standard interface for preparing \LaTeX{} document sources for distribution. % -% \end{markdown} +% \markdownEnd % \begin{macrocode} -\newcommand\markdownIfSnippetExists[3]{% - \@ifundefined - {markdownLaTeXSetupSnippet\markdownLaTeXThemeName#1}% - {#3}{#2}}% +\DeclareOption{finalizecache}{\markdownSetup{finalizeCache}} +\DeclareOption{frozencache}{\markdownSetup{frozenCache}} % \end{macrocode} -% \begin{markdown} -% -% See Section <#sec:latexthemes> for information on interactions between -% snippets and \LaTeX{} themes. See Section <#sec:latexsnippets> -% for information about invoking the set-up snippets. -% -% \end{markdown} % \iffalse %</latex> %<*manual-options> @@ -18914,133 +21250,95 @@ following code in our \LaTeX{} document: ``` We can also set all plain \TeX{} options directly from \LaTeX{}. For example, -to set the `\markdownOptionHelperScriptFileName` plain \TeX{} option to +to set the `\markdownOptionInputTempFileName` plain \TeX{} option to `helper-script.lua`, we would inclde the following code in our \LaTeX{} document: ``` tex \markdownSetup{ - helperScriptFileName = helper-script.lua, + inputTempFileName = temporary-input.md, } ``` %</manual-options> %<*latex> % \fi -% \par % \begin{markdown} % -% To enable the enumeration of \LaTeX{} options, we will maintain the -% \mdef{g_\@\@_latex_options_seq} sequence. +%#### Generating Plain \TeX{} Option, Token Renderer, and Token Renderer Prototype Macros and Key-Values +% +% If \LaTeX{} is the top layer, we use the +% \mref{@@_define_option_commands_and_keyvals:}, \mref{@@_define_renderers:}, +% and \mref{@@_define_renderer_prototypes:} macro to define plain \TeX{} +% option, token renderer, and token renderer prototype macros and key-values +% immediately. Otherwise, we postpone the definition until the upper layers +% have been loaded. % % \end{markdown} % \begin{macrocode} \ExplSyntaxOn -\seq_new:N \g_@@_latex_options_seq +\str_if_eq:VVT + \c_@@_top_layer_tl + \c_@@_option_layer_latex_tl + { + \@@_define_option_commands_and_keyvals: + \@@_define_renderers: + \@@_define_renderer_prototypes: + } +\ExplSyntaxOff % \end{macrocode} +% \par % \begin{markdown} % -% To enable the reflection of default \LaTeX{} options and their types, we -% will maintain the \mdef{g_\@\@_default_latex_options_prop} and -% \mdef{g_\@\@_latex_option_types_prop} property lists, respectively. +% The following example \LaTeX{} code showcases a possible configuration of +% plain \TeX{} interface options \Opt{hybrid}, \Opt{smartEllipses}, and +% \Opt{cacheDir}. +% ``` tex +% \markdownSetup{ +% hybrid, +% smartEllipses, +% cacheDir = /tmp, +% } +% ``````` % % \end{markdown} -% \begin{macrocode} -\prop_new:N \g_@@_latex_option_types_prop -\prop_new:N \g_@@_default_latex_options_prop -\tl_const:Nn \c_@@_option_layer_latex_tl { latex } -\seq_gput_right:NV \g_@@_option_layers_seq \c_@@_option_layer_latex_tl -\cs_new:Nn - \@@_add_latex_option:nnn - { - \@@_add_option:Vnnn - \c_@@_option_layer_latex_tl - { #1 } - { #2 } - { #3 } - } -% \end{macrocode} % \iffalse %</latex> %<*manual-options> % \fi % \begin{markdown} -#### No default token renderer prototypes {#latexplain} - -Default token renderer prototypes require \LaTeX{} packages that may clash with -other packages used in a document. Additionally, if we redefine token -renderers and renderer prototypes ourselves, the default definitions will bring -no benefit to us. Using the `plain` package option, we can keep the default -definitions from the plain \TeX{} implementation -% (see Section <#sec:tex-token-renderer-prototypes>) -and prevent the soft \LaTeX{} prerequisites -% in Section <#sec:latex-prerequisites> -from being loaded: The plain option must be set before or when loading the -package. Setting the option after loading the package will have no effect. - -``` tex -\usepackage[plain]{markdown} -``````` +### Themes {#latexthemes} -% \end{markdown} -% \iffalse -%</manual-options> -%<*latex> +% In Section~\ref{sec:themes}, we described the concept of themes. +In \LaTeX{}, we expand on the concept of +% themes\iffalse +[themes](#themes) % \fi -% \begin{macrocode} -\@@_add_latex_option:nnn - { plain } - { boolean } - { false } -\ExplSyntaxOff -% \end{macrocode} -% \iffalse -%</latex> -%<*manual-options> -% \fi -% \begin{markdown} +by allowing a theme to be a full-blown \LaTeX{} package. Specifically, the +key-values `theme`=\meta{theme name} and `import`=\meta{theme name} load a +\LaTeX{} package named `markdowntheme`\meta{munged theme name}`.sty` if it +exists and a \TeX{} document named `markdowntheme`\meta{munged theme +name}`.tex` otherwise. -#### \LaTeX{} themes {#latexthemes} +Having the Markdown package automatically load either the generic `.tex` +*theme file* or the \LaTeX{}-specific `.sty` theme file allows developers +to have a single *theme file*, when the theme is small or the difference +between \TeX{} formats is unimportant, and scale up to separate theme files +native to different \TeX{} formats for large multi-format themes, where +different code is needed for different \TeX{} formats. To enable code reuse, +developers can load the `.tex` theme file from the `.sty` theme file using the +\mdef{markdownLoadPlainTeXTheme} macro. -User-defined \LaTeX{} themes for the Markdown package provide a domain-specific -interpretation of Markdown tokens. Similarly to \LaTeX{} packages, themes -allow the authors to achieve a specific look and other high-level goals -without low-level programming. - -% The \LaTeX{} option `import`=\meta{theme name} loads a \LaTeX{} package -% (further referred to as *a theme*) named `markdowntheme`\meta{munged theme -% name}`.sty`, where the *munged theme name* is the *theme name* after the -% substitution of all forward slashes (`/`) for an underscore (`_`), the theme -% *name* is *qualified* and contains no underscores, and a value is qualified -% if and only if it contains at least one forward slash. Themes are inspired -% by the Beamer \LaTeX{} package, which provides similar functionality with -% its `\usetheme` macro [@tantau21, Section 15.1]. -% -% Theme names must be qualified to minimize naming conflicts between different -% themes intended for a single \LaTeX{} document class or for a single \LaTeX{} -% package. The preferred format of a theme name is \meta{theme author}`/`<!-- -% -->\meta{target \LaTeX{} document class or package}`/`\meta{private naming -% scheme}, where the *private naming scheme* may contain additional forward -% slashes. For example, a theme by a user `witiko` for the MU theme of the -% Beamer document class may have the name `witiko/beamer/MU`. -% -% Theme names are munged, because \LaTeX{} packages are identified only by -% their filenames, not by their pathnames. [@novotny21] Therefore, we can't -% store the qualified theme names directly using directories, but we must -% encode the individual segments of the qualified theme in the filename. For -% example, loading a theme named `witiko/beamer/MU` would load a \LaTeX{} -% package named `markdownthemewitiko_beamer_MU.sty`. -% -% If the \LaTeX{} option with key `theme` is (repeatedly) specified in the -% `\usepackage` macro, the loading of the theme(s) will be postponed in -% first-in-first-out order until after the Markdown \LaTeX{} package has been -% loaded. Otherwise, the theme(s) will be loaded immediately. For example, -% there is a theme named `witiko/dot`, which typesets fenced code blocks with -% the `dot` infostring as images of directed graphs rendered by the Graphviz -% tools. The following code would first load the Markdown package, then the -% `markdownthemewitiko_beamer_MU.sty` \LaTeX{} package, and finally the -% `markdownthemewitiko_dot.sty` \LaTeX{} package: +% If the \LaTeX{} option with keys `theme` or `import` is (repeatedly) +% specified in the `\usepackage` macro, the loading of the theme(s) will be +% postponed in first-in-first-out order until after the Markdown \LaTeX{} +% package has been loaded. Otherwise, the theme(s) will be loaded immediately. +% For example, there is a theme named `witiko/dot`, which typesets fenced code +% blocks with the `dot` infostring as images of directed graphs rendered by the +% Graphviz tools. The following code would first load the Markdown package, +% then the `markdownthemewitiko_beamer_MU.sty` \LaTeX{} package, and finally +% the `markdownthemewitiko_dot.sty` \LaTeX{} package: % \end{markdown} % \iffalse @@ -19066,121 +21364,22 @@ would use the following code in the preamble of your document: % \begin{macrocode} \newif\ifmarkdownLaTeXLoaded \markdownLaTeXLoadedfalse -\AtEndOfPackage{\markdownLaTeXLoadedtrue} -\ExplSyntaxOn -\tl_new:N \markdownLaTeXThemePackageName -\cs_new:Nn - \@@_set_latex_theme:n - { - \str_if_in:nnF - { #1 } - { / } - { - \markdownError - { Won't~load~theme~with~unqualified~name~#1 } - { Theme~names~must~contain~at~least~one~forward~slash } - } - \str_if_in:nnT - { #1 } - { _ } - { - \markdownError - { Won't~load~theme~with~an~underscore~in~its~name~#1 } - { Theme~names~must~not~contain~underscores~in~their~names } - } - \tl_set:Nn \markdownLaTeXThemePackageName { #1 } - \str_replace_all:Nnn - \markdownLaTeXThemePackageName - { / } - { _ } - \edef\markdownLaTeXThemePackageName{ - markdowntheme\markdownLaTeXThemePackageName} - \expandafter\markdownLaTeXThemeLoad\expandafter{ - \markdownLaTeXThemePackageName}{#1/} - } -\keys_define:nn - { markdown/latex-options } - { - import .code:n = { - \tl_set:Nn - \l_tmpa_tl - { #1 } % \end{macrocode} -% \begin{markdown} -% -% To ensure that keys containing forward slashes get passed correctly, we -% replace all forward slashes in the input with backslash tokens with category -% code letter and then undo the replacement. This means that if any unbraced -% backslash tokens with category code letter exist in the input, they will be -% replaced with forward slashes. However, this should be extremely rare. -% -% \end{markdown} -% \begin{macrocode} - \tl_replace_all:NnV - \l_tmpa_tl - { / } - \c_backslash_str - \keys_set:nV - { markdown/latex-options/import } - \l_tmpa_tl - }, - } -\cs_generate_variant:Nn - \tl_replace_all:Nnn - { NnV } -% \end{macrocode} -% \begin{markdown} -% -% The \LaTeX{} option `theme` has been deprecated and will be removed in -% Markdown 3.0.0. -% -% \end{markdown} -% \begin{macrocode} -\keys_define:nn - { markdown/latex-options } - { - theme .code:n = { \@@_set_latex_theme:n { #1 } }, - } -\ExplSyntaxOff -% \end{macrocode} -% \begin{markdown} -% -% The \LaTeX{} themes have a useful synergy with snippets (see Section -% <#sec:latex-setup-snippets>): To make it less likely that different themes -% will set up snippets with the same name, we will prepend \meta{theme -% name}`/` before the snippet name and use the result as the snippet name. -% For example, if the `witiko/dot` theme sets up the `product` snippet, the -% snippet will be available under the name `witiko/dot/product`. -% -% \end{markdown} % \iffalse %</latex> %<*manual-options> % \fi +% \par % \begin{markdown} Due to limitations of \LaTeX{}, themes may not be loaded after the beginning of a \LaTeX{} document. % \end{markdown} -% \iffalse -%</manual-options> -%<*latex> -% \fi -% \begin{macrocode} -\ExplSyntaxOn -\@onlypreamble - \@@_set_latex_theme:n -\ExplSyntaxOff -% \end{macrocode} -% \iffalse -%</latex> -%<*manual-options> -% \fi % \par % \markdownBegin -Example themes provided with the Markdown package include: +Built-in \LaTeX{} themes provided with the Markdown package include: \pkg{witiko/dot} @@ -19311,13 +21510,13 @@ conference article: > <http://ceur-ws.org/Vol-2696/paper_235.pdf> %</manual-options> -%<*latex-themes-witiko-dot> +%<*themes-witiko-dot> % \fi % \begin{macrocode} \ProvidesPackage{markdownthemewitiko_dot}[2021/03/09]% % \end{macrocode} % \iffalse -%</latex-themes-witiko-dot> +%</themes-witiko-dot> %<*manual-options> % \fi % \par @@ -19370,909 +21569,71 @@ lualatex document.tex A PDF document named `document.pdf` should be produced and contain the following image: -> ![img](https://github.com/witiko/markdown/raw/main/markdown.png -> "The banner of the Markdown package") +> ![img](https://github.com/witiko/markdown/raw/main/markdown.png "The banner of the Markdown package") %</manual-options> -%<*latex-themes-witiko-graphicx-http> +%<*themes-witiko-graphicx-http> % \fi % \begin{macrocode} \ProvidesPackage{markdownthemewitiko_graphicx_http}[2021/03/22]% % \end{macrocode} % \iffalse -%</latex-themes-witiko-graphicx-http> -%<*manual-options> -% \fi -% \par -% \markdownBegin - -\pkg{witiko/tilde} - -: A theme that makes tilde (`~`) always typeset the non-breaking space even - when the \Opt{hybrid} Lua option is disabled. -% ``` tex -% \documentclass{article} -% \usepackage[import=witiko/tilde]{markdown} -% \begin{document} -% \begin{markdown} -% Bartel~Leendert van~der~Waerden -% \end{markdown} -% \end{document} -% ``````` -% Typesetting the above document produces the following text: -% “Bartel~Leendert van~der~Waerden”. -% -% \markdownEnd -% \iffalse - -##### \LaTeX{} Example {.unnumbered} - -Using a text editor, create a text document named `document.tex` with the -following content: -``` tex -\documentclass{article} -\usepackage[import=witiko/tilde]{markdown} -\begin{document} -\begin{markdown} -Bartel~Leendert van~der~Waerden -\end{markdown} -\end{document} -``````` -Next, invoke LuaTeX from the terminal: -``` sh -lualatex document.tex -`````` -A PDF document named `document.pdf` should be produced and contain the -following text, where the middot (`·`) denotes a non-breaking space: - -> Bartel·Leendert van·der·Waerden - -%</manual-options> -%<*latex-themes-witiko-tilde> -% \fi -% \begin{macrocode} -\ProvidesPackage{markdownthemewitiko_tilde}[2021/03/22]% -% \end{macrocode} -% \iffalse -%</latex-themes-witiko-tilde> +%</themes-witiko-graphicx-http> %<*manual-options> % \fi % \par % \begin{markdown} -% -% Please, see Section <#sec:latex-themes-implementation> for implementation -% details of the example themes. -#### \LaTeX{} snippets {#latexsnippets} +\pkg{witiko/markdown/defaults} -% \end{markdown} -% \iffalse +: A \LaTeX{} theme with the default definitions of token renderer prototypes + for plain \TeX{}. This theme is loaded automatically together with the + package and explicitly loading it has no effect. -User-defined \LaTeX{} themes provide global control over high-level goals. -However, it is often desirable to change only some local aspects of a document. -\LaTeX{} snippets provide syntactic sugar for defining and invoking various -options locally. - -% \fi -% \begin{markdown} -% The \LaTeX{} option with key `snippet` invokes a snippet named \meta{value}: % \end{markdown} % \iffalse %</manual-options> %<*latex> % \fi % \begin{macrocode} -\ExplSyntaxOn -\keys_define:nn - { markdown/latex-options } - { - snippet .code:n = { - \markdownIfSnippetExists{#1} - { - \expandafter\markdownSetup\expandafter{ - \the\csname markdownLaTeXSetupSnippet - \markdownLaTeXThemeName#1\endcsname} - }{ - \markdownError - {Can't~invoke~setup~snippet~#1} - {The~setup~snippet~is~undefined} - } - } - } -\ExplSyntaxOff -% \end{macrocode} -% \iffalse -%</latex> -%<*manual-options> -% \fi -% \par -% \markdownBegin - -Here is how we can use snippets to store options and invoke them later: - -``` tex -\markdownSetupSnippet{romanNumerals}{ - renderers = { - olItemWithNumber = {% - \item[\romannumeral#1\relax.]% - }, - }, -} -\begin{markdown} - -The following ordered list will be preceded by arabic numerals: - -1. wahid -2. aithnayn - -\end{markdown} -\begin{markdown*}{snippet=romanNumerals} - -The following ordered list will be preceded by roman numerals: - -3. tres -4. quattuor - -\end{markdown*} -``````` - -If the `romanNumerals` snippet were defined in the `jdoe/lists` theme, -we could import the `jdoe/lists` theme and use the qualified name -`jdoe/lists/romanNumerals` to invoke the snippet: - -``` tex -\markdownSetup{import=jdoe/lists} -\begin{markdown*}{snippet=jdoe/lists/romanNumerals} - -The following ordered list will be preceded by roman numerals: - -3. tres -4. quattuor - -\end{markdown*} -``````` - -Alternatively, we can use the extended variant of the `import` \LaTeX{} -option that allows us to import the `romanNumerals` snippet to the -current namespace for easier access: - -``` tex -\markdownSetup{ - import = { - jdoe/lists = romanNumerals, - }, -} -\begin{markdown*}{snippet=romanNumerals} - -The following ordered list will be preceded by roman numerals: - -3. tres -4. quattuor - -\end{markdown*} -``````` - -Furthermore, we can also specify the name of the snippet in the current -namespace, which can be different from the name of the snippet in the -`jdoe/lists` theme. For example, we can make the snippet -`jdoe/lists/romanNumerals` available under the name `roman`. - -``` tex -\markdownSetup{ - import = { - jdoe/lists = romanNumerals as roman, - }, -} -\begin{markdown*}{snippet=roman} - -The following ordered list will be preceded by roman numerals: - -3. tres -4. quattuor - -\end{markdown*} -``````` - -Several themes and/or snippets can be loaded at once using the extended -variant of the `import` \LaTeX{} option: - -``` tex -\markdownSetup{ - import = { - jdoe/longpackagename/lists = { - arabic as arabic1, - roman, - alphabetic, - }, - jdoe/anotherlongpackagename/lists = { - arabic as arabic2, - }, - jdoe/yetanotherlongpackagename, - }, -} -``````` - -% \markdownEnd -% \iffalse -%</manual-options> -%<*latex> -% \fi -% \begin{macrocode} -\ExplSyntaxOn -\tl_new:N - \l_@@_latex_import_current_theme_tl -\keys_define:nn - { markdown/latex-options/import } - { -% \end{macrocode} -% \begin{markdown} -% -% If a theme name is given without a list of snippets to import, -% we assume that an empty list was given. -% -% \end{markdown} -% \begin{macrocode} - unknown .default:n = {}, - unknown .code:n = { -% \end{macrocode} -% \begin{markdown} -% -% To ensure that keys containing forward slashes get passed correctly, we -% replace all forward slashes in the input with backslash tokens with category -% code letter and then undo the replacement. This means that if any unbraced -% backslash tokens with category code letter exist in the input, they will be -% replaced with forward slashes. However, this should be extremely rare. -% -% \end{markdown} -% \begin{macrocode} - \tl_set_eq:NN - \l_@@_latex_import_current_theme_tl - \l_keys_key_str - \tl_replace_all:NVn - \l_@@_latex_import_current_theme_tl - \c_backslash_str - { / } -% \end{macrocode} -% \begin{markdown} -% -% Here, we load the \LaTeX{} theme. -% -% \end{markdown} -% \begin{macrocode} - \@@_set_latex_theme:V - \l_@@_latex_import_current_theme_tl +\AtEndOfPackage{ + \markdownLaTeXLoadedtrue % \end{macrocode} % \begin{markdown} % -% Here, we import the \LaTeX{} snippets. +% At the end of the \LaTeX{} module, we load the +% `witiko/markdown/defaults` \LaTeX{} theme (see Section <#sec:themes>) with +% the default definitions for token renderer prototypes unless the option +% `noDefaults` has been enabled (see Section <#sec:plain>). % % \end{markdown} % \begin{macrocode} - \clist_map_inline:nn - { #1 } - { - \regex_extract_once:nnNTF - { ^(.*?)\s+as\s+(.*?)$ } - { ##1 } - \l_tmpa_seq - { - \seq_pop:NN - \l_tmpa_seq - \l_tmpa_tl - \seq_pop:NN - \l_tmpa_seq - \l_tmpa_tl - \seq_pop:NN - \l_tmpa_seq - \l_tmpb_tl - } - { - \tl_set:Nn - \l_tmpa_tl - { ##1 } - \tl_set:Nn - \l_tmpb_tl - { ##1 } - } - \tl_put_left:Nn - \l_tmpa_tl - { / } - \tl_put_left:NV - \l_tmpa_tl - \l_@@_latex_import_current_theme_tl - \@@_latex_setup_snippet:Vx - \l_tmpb_tl - { snippet = { \l_tmpa_tl } } - } - }, - } -\cs_generate_variant:Nn - \tl_replace_all:Nnn - { NVn } -\cs_generate_variant:Nn - \@@_set_latex_theme:n - { V } -\cs_generate_variant:Nn - \@@_latex_setup_snippet:nn - { Vx } -\ExplSyntaxOff -% \end{macrocode} -% \begin{markdown} -% -%#### Plain \TeX{} Interface Options -% Here, we automatically define plain \TeX{} macros and the -% \meta{key}`=`\meta{value} interface for the above \LaTeX{} options. -% -% \end{markdown} -% \begin{macrocode} -\ExplSyntaxOn -\cs_new:Nn \@@_latex_define_option_commands_and_keyvals: - { - \seq_map_inline:Nn - \g_@@_latex_options_seq - { - \@@_plain_tex_define_option_command:n - { ##1 } - } -% \end{macrocode} -% \begin{markdown} -% -% Furthermore, we also define the \meta{key}`=`\meta{value} interface -% for all option macros recognized by the Lua and plain \TeX{} interfaces. -% -% \end{markdown} -% \begin{macrocode} - \seq_map_inline:Nn - \g_@@_option_layers_seq - { - \seq_map_inline:cn - { g_@@_ ##1 _options_seq } - { -% \end{macrocode} -% \begin{markdown} -% -% To make it easier to copy-and-paste options from Pandoc [@macfarlane22] such -% as `fancy_lists`, `header_attributes`, and `pipe_tables`, we accept -% snake\\\_case in addition to camelCase variants of options. As a bonus, -% studies [@sharif10] also show that snake\\\_case is faster to read than -% camelCase. -% -% \end{markdown} -% \begin{macrocode} - \@@_with_various_cases:nn - { ####1 } - { - \@@_latex_define_option_keyval:nnn - { ##1 } - { ####1 } - { ########1 } - } - } - } + \markdownIfOption{noDefaults}{}{ + \markdownSetup{theme=witiko/markdown/defaults} } -\cs_new:Nn \@@_latex_define_option_keyval:nnn - { - \prop_get:cnN - { g_@@_ #1 _option_types_prop } - { #2 } - \l_tmpa_tl - \keys_define:nn - { markdown/latex-options } - { - #3 .code:n = { - \@@_set_option_value:nn - { #2 } - { ##1 } - }, - } - \str_if_eq:VVT - \l_tmpa_tl - \c_@@_option_type_boolean_tl - { - \keys_define:nn - { markdown/latex-options } - { - #3 .default:n = { true }, - } - } -% \end{macrocode} -% \begin{markdown} -% -% For options of type `clist`, we assume that \meta{key} is a regular English -% noun in plural (such as `extensions`) and we also define the -% \meta{singular key}`=`\meta{value} interface, where \meta{singular key} is -% \meta{key} after stripping the trailing -s (such as `extension`). Rather -% than setting the option to \meta{value}, this interface appends \meta{value} -% to the current value as the rightmost item in the list. -% -% \end{markdown} -% \begin{macrocode} - \str_if_eq:VVT - \l_tmpa_tl - \c_@@_option_type_clist_tl - { - \tl_set:Nn - \l_tmpa_tl - { #3 } - \tl_reverse:N - \l_tmpa_tl - \str_if_eq:enF - { - \tl_head:V - \l_tmpa_tl - } - { s } - { - \msg_error:nnn - { markdown } - { malformed-name-for-clist-option } - { #3 } - } - \tl_set:Nx - \l_tmpa_tl - { - \tl_tail:V - \l_tmpa_tl - } - \tl_reverse:N - \l_tmpa_tl - \tl_put_right:Nn - \l_tmpa_tl - { - .code:n = { - \@@_get_option_value:nN - { #2 } - \l_tmpa_tl - \clist_set:NV - \l_tmpa_clist - { \l_tmpa_tl, { ##1 } } - \@@_set_option_value:nV - { #2 } - \l_tmpa_clist - } - } - \keys_define:nV - { markdown/latex-options } - \l_tmpa_tl - } - } -\cs_generate_variant:Nn - \clist_set:Nn - { NV } -\cs_generate_variant:Nn - \keys_define:nn - { nV } -\cs_generate_variant:Nn - \@@_set_option_value:nn - { nV } -\prg_generate_conditional_variant:Nnn - \str_if_eq:nn - { en } - { F } -\msg_new:nnn - { markdown } - { malformed-name-for-clist-option } - { - Clist~option~name~#1~does~not~end~with~-s. - } -\@@_latex_define_option_commands_and_keyvals: -\ExplSyntaxOff -% \end{macrocode} -% \par -% \begin{markdown} -% -% The \Opt{finalizeCache} and \Opt{frozenCache} plain -% \TeX{} options are exposed through \LaTeX{} options with keys `finalizeCache` -% and `frozenCache`. -% -% To ensure compatibility with the `minted` package [@poore17, Section -% 5.1], which supports the `finalizecache` and `frozencache` package options -% with similar semantics, the Markdown package also recognizes these as aliases -% and recognizes them as document class options. By passing `finalizecache` and -% `frozencache` as document class options, you may conveniently control the -% behavior of both packages at once: -% -% ``` tex -% \documentclass[frozencache]{article} -% \usepackage{markdown,minted} -% \begin{document} -% % ... -% \end{document} -% ``````` -% -% We hope that other packages will support the `finalizecache` and -% `frozencache` package options in the future, so that they can become a -% standard interface for preparing \LaTeX{} document sources for distribution. -% -% \end{markdown} -% \begin{macrocode} -\DeclareOption{finalizecache}{\markdownSetup{finalizeCache}} -\DeclareOption{frozencache}{\markdownSetup{frozenCache}} -% \end{macrocode} -% \par -% \begin{markdown} -% -% The following example \LaTeX{} code showcases a possible configuration of -% plain \TeX{} interface options \Opt{hybrid}, \Opt{smartEllipses}, and -% \Opt{cacheDir}. -% ``` tex -% \markdownSetup{ -% hybrid, -% smartEllipses, -% cacheDir = /tmp, -% } -% ``````` -% -%#### Plain \TeX{} Markdown Token Renderers {#latex-renderers} -% -% The \LaTeX{} interface recognizes an option with the `renderers` key, -% whose value must be a list of key-values, where the keys correspond -% to the markdown token renderer macros exposed by the plain \TeX{} -% interface (see Section <#sec:texrenderersuser>) and the values are -% new definitions of these token renderers. -% -% \end{markdown} -% \begin{macrocode} -\ExplSyntaxOn -\cs_new:Nn \@@_latex_define_renderers: - { - \seq_map_function:NN - \g_@@_renderers_seq - \@@_latex_define_renderer:n - } -\cs_new:Nn \@@_latex_define_renderer:n - { - \@@_renderer_tl_to_csname:nN - { #1 } - \l_tmpa_tl - \prop_get:NnN - \g_@@_renderer_arities_prop - { #1 } - \l_tmpb_tl - \@@_latex_define_renderer:ncV - { #1 } - { \l_tmpa_tl } - \l_tmpb_tl - } -\cs_new:Nn \@@_renderer_tl_to_csname:nN - { - \tl_set:Nn - \l_tmpa_tl - { \str_uppercase:n { #1 } } - \tl_set:Nx - #2 - { - markdownRenderer - \tl_head:f { \l_tmpa_tl } - \tl_tail:n { #1 } - } - } -\cs_new:Nn \@@_latex_define_renderer:nNn - { - \@@_with_various_cases:nn - { #1 } - { - \keys_define:nn - { markdown/latex-options/renderers } - { - ##1 .code:n = { - \cs_generate_from_arg_count:NNnn - #2 - \cs_set:Npn - { #3 } - { ####1 } - }, - } - } - } -\cs_generate_variant:Nn - \@@_latex_define_renderer:nNn - { ncV } -\ExplSyntaxOff -% \end{macrocode} -% \par -% \begin{markdown} -% -% The following example \LaTeX{} code showcases a possible configuration of the -% \mref{markdownRendererLink} and \mref{markdownRendererEmphasis} markdown token -% renderers. -% ``` tex -% \markdownSetup{ -% renderers = { -% link = {#4}, \% Render links as the link title. -% emphasis = {\emph{#1}}, \% Render emphasized text via `\emph`. -% } -% } -% ``````` -% -% In addition to exact token renderer names, we also support wildcards -% that match multiple token renderer names. -% -% \end{markdown} -% \begin{macrocode} -\ExplSyntaxOn -\tl_new:N - \l_@@_renderer_definition_tl -\keys_define:nn - { markdown/latex-options/renderers } - { - unknown .code:n = { - \regex_match:nVTF - { \* } - \l_keys_key_str - { - \tl_set:Nn - \l_@@_renderer_definition_tl - { #1 } - \tl_set:NV - \l_tmpa_tl - \l_keys_key_str - \regex_replace_all:nnN - { \* } - { .* } - \l_tmpa_tl - \regex_set:NV - \l_tmpa_regex - \l_tmpa_tl - \int_zero:N - \l_tmpa_int - \seq_map_inline:Nn - \g_@@_renderers_seq - { - \@@_with_various_cases:nn - { ##1 } - { - \regex_match:NnT - \l_tmpa_regex - { ####1 } - { - \@@_renderer_tl_to_csname:nN - { ##1 } - \l_tmpa_tl - \prop_get:NnN - \g_@@_renderer_arities_prop - { ##1 } - \l_tmpb_tl - \cs_generate_from_arg_count:cNVV - { \l_tmpa_tl } - \cs_set:Npn - \l_tmpb_tl - \l_@@_renderer_definition_tl - \int_incr:N - \l_tmpa_int - \@@_with_various_cases_break: - } - } - } - \int_compare:nNnT - { \l_tmpa_int } = { 0 } - { - \msg_error:nnV - { markdown } - { nonmatched-renderer-wildcard } - \l_keys_key_str - } - } - { - \msg_error:nnV - { markdown } - { undefined-renderer } - \l_keys_key_str - } - }, - } -\msg_new:nnn - { markdown } - { undefined-renderer } - { - Renderer~#1~is~undefined. - } -\msg_new:nnn - { markdown } - { nonmatched-renderer-wildcard } - { - Wildcard~#1~matches~no~renderers. - } -\cs_generate_variant:Nn - \regex_set:Nn - { NV } -\cs_generate_variant:Nn - \cs_generate_from_arg_count:NNnn - { cNVV } -\cs_generate_variant:Nn - \msg_error:nnn - { nnV } -\prg_generate_conditional_variant:Nnn - \regex_match:nn - { nV } - { TF } -\ExplSyntaxOff -% \end{macrocode} -% \begin{markdown} -% -%#### Plain \TeX{} Markdown Token Renderer Prototypes {#latex-renderer-prototypes} -% -% The \LaTeX{} interface recognizes an option with the -% `rendererPrototypes` key, whose value must be a list of key-values, -% where the keys correspond to the markdown token renderer prototype -% macros exposed by the plain \TeX{} interface (see Section -% <#sec:texrendererprototypes>) and the values are new definitions of -% these token renderer prototypes. -% -% \end{markdown} -% \begin{macrocode} -\ExplSyntaxOn -\cs_new:Nn \@@_latex_define_renderer_prototypes: - { - \seq_map_function:NN - \g_@@_renderers_seq - \@@_latex_define_renderer_prototype:n - } -\cs_new:Nn \@@_latex_define_renderer_prototype:n - { - \@@_renderer_prototype_tl_to_csname:nN - { #1 } - \l_tmpa_tl - \prop_get:NnN - \g_@@_renderer_arities_prop - { #1 } - \l_tmpb_tl - \@@_latex_define_renderer_prototype:ncV - { #1 } - { \l_tmpa_tl } - \l_tmpb_tl - } -\cs_new:Nn \@@_latex_define_renderer_prototype:nNn - { - \@@_with_various_cases:nn - { #1 } - { - \keys_define:nn - { markdown/latex-options/renderer-prototypes } - { - ##1 .code:n = { - \cs_generate_from_arg_count:NNnn - #2 - \cs_set:Npn - { #3 } - { ####1 } - }, - } - } - } -\cs_generate_variant:Nn - \@@_latex_define_renderer_prototype:nNn - { ncV } -\ExplSyntaxOff +} % \end{macrocode} -% \par -% \begin{markdown} -% -% The following example \LaTeX{} code showcases a possible configuration of the -% `\markdownRendererImagePrototype` and `\markdownRendererCodeSpanPrototype` -% markdown token renderer prototypes. -% ``` tex -% \markdownSetup{ -% rendererPrototypes = { -% image = {\includegraphics{#2}}, -% codeSpan = {\texttt{#1}}, \% Render inline code via `\texttt`. -% } -% } -% ``````` -% -% In addition to exact token renderer prototype names, we also support -% wildcards that match multiple token renderer prototype names. -% -% \end{markdown} +% \iffalse +%</latex> +%<*themes-witiko-markdown-defaults-latex> +% \fi % \begin{macrocode} -\ExplSyntaxOn -\tl_new:N - \l_@@_renderer_prototype_definition_tl -\keys_define:nn - { markdown/latex-options/renderer-prototypes } - { - unknown .code:n = { - \regex_match:nVTF - { \* } - \l_keys_key_str - { - \tl_set:Nn - \l_@@_renderer_prototype_definition_tl - { #1 } - \tl_set:NV - \l_tmpa_tl - \l_keys_key_str - \regex_replace_all:nnN - { \* } - { .* } - \l_tmpa_tl - \regex_set:NV - \l_tmpa_regex - \l_tmpa_tl - \int_zero:N - \l_tmpa_int - \seq_map_inline:Nn - \g_@@_renderers_seq - { - \@@_with_various_cases:nn - { ##1 } - { - \regex_match:NnT - \l_tmpa_regex - { ####1 } - { - \@@_renderer_prototype_tl_to_csname:nN - { ##1 } - \l_tmpa_tl - \prop_get:NnN - \g_@@_renderer_arities_prop - { ##1 } - \l_tmpb_tl - \cs_generate_from_arg_count:cNVV - { \l_tmpa_tl } - \cs_set:Npn - \l_tmpb_tl - \l_@@_renderer_prototype_definition_tl - \int_incr:N - \l_tmpa_int - \@@_with_various_cases_break: - } - } - } - \int_compare:nNnT - { \l_tmpa_int } = { 0 } - { - \msg_error:nnV - { markdown } - { nonmatched-renderer-prototype-wildcard } - \l_keys_key_str - } - } - { - \msg_error:nnV - { markdown } - { undefined-renderer-prototype } - \l_keys_key_str - } - }, - } -\msg_new:nnn - { markdown } - { undefined-renderer-prototype } - { - Renderer~prototype~#1~is~undefined. - } -\msg_new:nnn - { markdown } - { nonmatched-renderer-prototype-wildcard } - { - Wildcard~#1~matches~no~renderer~prototypes. - } -\cs_generate_variant:Nn - \regex_set:Nn - { NV } -\cs_generate_variant:Nn - \cs_generate_from_arg_count:NNnn - { cNVV } -\cs_generate_variant:Nn - \msg_error:nnn - { nnV } -\prg_generate_conditional_variant:Nnn - \regex_match:nn - { nV } - { TF } -\ExplSyntaxOff +\ProvidesPackage{markdownthemewitiko_markdown_defaults}[2024/01/03]% % \end{macrocode} -% \begin{markdown} -% % \iffalse -%</latex> +%</themes-witiko-markdown-defaults-latex> %<*context> % \fi +% \par +% \begin{markdown} +% +% Please, see Section <#sec:latex-themes-implementation> for implementation +% details of the built-in \LaTeX{} themes. % % \Hologo{ConTeXt} Interface {#contextinterface} %---------------------------- % +% \end{markdown} % \iffalse %</context> %<*manual-interfaces> @@ -20297,7 +21658,7 @@ following content: $\sqrt{-1}$ *equals* $i$. \stopmarkdown -\setupmarkdown[hybrid = yes] +\setupmarkdown[texmathdollars = yes] \startmarkdown $\sqrt{-1}$ *equals* $i$. \stopmarkdown @@ -20306,7 +21667,7 @@ $\sqrt{-1}$ *equals* $i$. ``````` Next, invoke LuaTeX from the terminal: ``` sh -context document.tex +context --luatex document.tex `````` A PDF document named `document.pdf` should be produced and contain the following text: @@ -20320,6 +21681,30 @@ following text: %</manual-interfaces> %<*context> % \fi +% \begin{markdown} +% +% To determine whether \Hologo{ConTeXt} is the top layer or if there are other +% layers above \Hologo{ConTeXt}, we take a look on whether the +% \mref{c_@@_top_layer_tl} token list has already been defined. If not, +% we will assume that \Hologo{ConTeXt} is the top layer. +% +% \end{markdown} +% \begin{macrocode} +\ExplSyntaxOn +\tl_const:Nn \c_@@_option_layer_context_tl { context } +\cs_generate_variant:Nn + \tl_const:Nn + { NV } +\tl_if_exist:NF + \c_@@_top_layer_tl + { + \tl_const:NV + \c_@@_top_layer_tl + \c_@@_option_layer_context_tl + } +\ExplSyntaxOff +% \end{macrocode} +% \begin{markdown} % % The \Hologo{ConTeXt} interface provides a start-stop macro pair for the % typesetting of markdown input from within \Hologo{ConTeXt} and facilities for @@ -20331,19 +21716,6 @@ following text: % \begin{macrocode} \writestatus{loading}{ConTeXt User Module / markdown}% \startmodule[markdown] -\unprotect -% \end{macrocode} -% \par -% \begin{markdown} -% -% The \Hologo{ConTeXt} implementation redefines the plain \TeX{} logging macros -% (see Section <#sec:tex-interface-logging>) to use the \Hologo{ConTeXt} -% `\writestatus` macro. -% -% \end{markdown} -% \begin{macrocode} -\def\markdownInfo#1{\writestatus{markdown}{#1.}}% -\def\markdownWarning#1{\writestatus{markdown\space warn}{#1.}}% \def\dospecials{\do\ \do\\\do\{\do\}\do\$\do\&% \do\#\do\^\do\_\do\%\do\~}% \input markdown/markdown @@ -20362,7 +21734,7 @@ following text: %### Typesetting Markdown % The interface exposes the \mdef{startmarkdown} and \mdef{stopmarkdown} macro % pair for the typesetting of a markdown document fragment, and defines the -% \mdef{inputmarkdown} command. +% \mdef{inputmarkdown} macro. % % \end{markdown} % \begin{macrocode} @@ -20423,21 +21795,16 @@ following text: % \TeX{} interface (see Section <#sec:tex-options>). % % The \Hologo{ConTeXt} options may be specified when using the -% \mref{inputmarkdown} macro (see Section <#sec:contextinterface>), or via the -% \mdef{setupmarkdown} macro. The \mref{setupmarkdown} macro receives the -% options to set up as its only argument: +% \mref{inputmarkdown} macro (see Section <#sec:contextinterface>), +% via the \mref{markdownSetup} macro, or via the \mdef{setupmarkdown}`[#1]` +% macro, which is an alias for \mref{markdownSetup}`{#1}`. % % \end{markdown} % \begin{macrocode} \ExplSyntaxOn -\cs_new:Nn - \@@_setup:n - { - \keys_set:nn - { markdown/context-options } - { #1 } - } -\long\def\setupmarkdown[#1] +\cs_new:Npn + \setupmarkdown + [ #1 ] { \@@_setup:n { #1 } @@ -20446,51 +21813,14 @@ following text: % \end{macrocode} % \begin{markdown} % -%#### \Hologo{ConTeXt} Interface Options -% We define the \meta{key}`=`\meta{value} interface for all option macros -% recognized by the Lua and plain \TeX{} interfaces. -% -% \end{markdown} -% \begin{macrocode} -\ExplSyntaxOn -\cs_new:Nn \@@_context_define_option_commands_and_keyvals: - { - \seq_map_inline:Nn - \g_@@_option_layers_seq - { - \seq_map_inline:cn - { g_@@_ ##1 _options_seq } - { -% \end{macrocode} -% \begin{markdown} -% -% To make it easier to copy-and-paste options from Pandoc [@macfarlane22] such -% as `fancy_lists`, `header_attributes`, and `pipe_tables`, we accept -% snake\\\_case in addition to camelCase variants of options. As a bonus, -% studies [@sharif10] also show that snake\\\_case is faster to read than -% camelCase. -% -% \end{markdown} -% \begin{macrocode} - \@@_with_various_cases:nn - { ####1 } - { - \@@_context_define_option_keyval:nnn - { ##1 } - { ####1 } - { ########1 } - } - } - } - } -% \end{macrocode} -% \begin{markdown} +%#### Generating Plain \TeX{} Option Macros and Key-Values % -% Furthermore, we also accept caseless variants of options in line with the -% style of \Hologo{ConTeXt}. +% Unlike plain \TeX{}, we also accept caseless variants of options +% in line with the style of \Hologo{ConTeXt}. % % \end{markdown} % \begin{macrocode} +\ExplSyntaxOn \cs_new:Nn \@@_caseless:N { \regex_replace_all:nnN @@ -20502,52 +21832,89 @@ following text: { #1 } } \seq_gput_right:Nn \g_@@_cases_seq { @@_caseless:N } -\cs_new:Nn \@@_context_define_option_keyval:nnn +% \end{macrocode} +% \begin{markdown} +% +% If \Hologo{ConTeXt} is the top layer, we use the +% \mref{@@_define_option_commands_and_keyvals:}, \mref{@@_define_renderers:}, +% and \mref{@@_define_renderer_prototypes:} macro to define plain \TeX{} +% option, token renderer, and token renderer prototype macros and key-values +% immediately. Otherwise, we postpone the definition until the upper layers +% have been loaded. +% +% \end{markdown} +% \begin{macrocode} +\str_if_eq:VVT + \c_@@_top_layer_tl + \c_@@_option_layer_context_tl { - \prop_get:cnN - { g_@@_ #1 _option_types_prop } - { #2 } - \l_tmpa_tl - \keys_define:nn - { markdown/context-options } - { - #3 .code:n = { - \tl_set:Nx - \l_tmpa_tl - { - \str_case:nnF - { ##1 } - { - { yes } { true } - { no } { false } - } - { ##1 } - } - \@@_set_option_value:nV - { #2 } - \l_tmpa_tl - }, - } - \str_if_eq:VVT - \l_tmpa_tl - \c_@@_option_type_boolean_tl - { - \keys_define:nn - { markdown/context-options } - { - #3 .default:n = { true }, - } - } + \@@_define_option_commands_and_keyvals: + \@@_define_renderers: + \@@_define_renderer_prototypes: } -\cs_generate_variant:Nn - \@@_set_option_value:nn - { nV } -\@@_context_define_option_commands_and_keyvals: \ExplSyntaxOff % \end{macrocode} -% \par +% \iffalse +%</context> +%<*manual-options> +% \fi +% \begin{markdown} + +### Themes + +% In Section~\ref{sec:themes}, we described the concept of themes. +In \Hologo{ConTeXt}, we expand on the concept of +% themes\iffalse +[themes](#themes) +% \fi +by allowing a theme to be a full-blown \Hologo{ConTeXt} module. Specifically, +the key-values `theme`=\meta{theme name} and `import`=\meta{theme name} load a +\Hologo{ConTeXt} module named `t-markdowntheme`\meta{munged theme name}`.tex` +if it exists and a \TeX{} document named `markdowntheme`\meta{munged theme +name}`.tex` otherwise. + +Having the Markdown package automatically load either the generic `.tex` +*theme file* or the \Hologo{ConTeXt}-specific `t-*.tex` theme file allows +developers to have a single *theme file*, when the theme is small or the +difference between \TeX{} formats is unimportant, and scale up to separate +theme files native to different \TeX{} formats for large multi-format themes, +where different code is needed for different \TeX{} formats. To enable code +reuse, developers can load the `.tex` theme file from the `t-*.tex` theme file +using the \mref{markdownLoadPlainTeXTheme} macro. + +For example, to load a theme named `witiko/tilde` in your document: + +``` tex +\usemodule[t][markdown] +\setupmarkdown[import=witiko/tilde] +``````` + +Built-in \Hologo{ConTeXt} themes provided with the Markdown package include: + +\pkg{witiko/markdown/defaults} + +: A \Hologo{ConTeXt} theme with the default definitions of token renderer + prototypes for plain \TeX{}. This theme is loaded automatically together + with the package and explicitly loading it has no effect. + +% \end{markdown} +% \iffalse +%</manual-options> +%<*themes-witiko-markdown-defaults-context> +% \fi +% \begin{macrocode} +\startmodule[markdownthemewitiko_markdown_defaults] +\unprotect +% \end{macrocode} +% \iffalse +%</themes-witiko-markdown-defaults-context> +%<*manual-options> +% \fi % \begin{markdown} % +% Please, see Section <#sec:context-themes-implementation> for implementation +% details of the built-in \Hologo{ConTeXt} themes. +% % Implementation {#implementation} %================ % @@ -20564,7 +21931,7 @@ following text: % % \end{markdown} % \iffalse -%</context> +%</manual-options> %<*lua> % \fi % \begin{markdown} @@ -20587,9 +21954,9 @@ following text: % \begin{macrocode} local upper, format, length = string.upper, string.format, string.len -local P, R, S, V, C, Cg, Cb, Cmt, Cc, Ct, B, Cs, any = +local P, R, S, V, C, Cg, Cb, Cmt, Cc, Ct, B, Cs, Cp, any = lpeg.P, lpeg.R, lpeg.S, lpeg.V, lpeg.C, lpeg.Cg, lpeg.Cb, - lpeg.Cmt, lpeg.Cc, lpeg.Ct, lpeg.B, lpeg.Cs, lpeg.P(1) + lpeg.Cmt, lpeg.Cc, lpeg.Ct, lpeg.B, lpeg.Cs, lpeg.Cp, lpeg.P(1) % \end{macrocode} % \par % \begin{markdown} @@ -20949,13 +22316,13 @@ local character_entities = { ["Tab"] = 9, ["NewLine"] = 10, ["excl"] = 33, - ["quot"] = 34, ["QUOT"] = 34, + ["quot"] = 34, ["num"] = 35, ["dollar"] = 36, ["percnt"] = 37, - ["amp"] = 38, ["AMP"] = 38, + ["amp"] = 38, ["apos"] = 39, ["lpar"] = 40, ["rpar"] = 41, @@ -20967,31 +22334,36 @@ local character_entities = { ["sol"] = 47, ["colon"] = 58, ["semi"] = 59, - ["lt"] = 60, ["LT"] = 60, + ["lt"] = 60, + ["nvlt"] = {60, 8402}, + ["bne"] = {61, 8421}, ["equals"] = 61, - ["gt"] = 62, ["GT"] = 62, + ["gt"] = 62, + ["nvgt"] = {62, 8402}, ["quest"] = 63, ["commat"] = 64, - ["lsqb"] = 91, ["lbrack"] = 91, + ["lsqb"] = 91, ["bsol"] = 92, - ["rsqb"] = 93, ["rbrack"] = 93, + ["rsqb"] = 93, ["Hat"] = 94, + ["UnderBar"] = 95, ["lowbar"] = 95, - ["grave"] = 96, ["DiacriticalGrave"] = 96, - ["lcub"] = 123, + ["grave"] = 96, + ["fjlig"] = {102, 106}, ["lbrace"] = 123, + ["lcub"] = 123, + ["VerticalLine"] = 124, ["verbar"] = 124, ["vert"] = 124, - ["VerticalLine"] = 124, - ["rcub"] = 125, ["rbrace"] = 125, - ["nbsp"] = 160, + ["rcub"] = 125, ["NonBreakingSpace"] = 160, + ["nbsp"] = 160, ["iexcl"] = 161, ["cent"] = 162, ["pound"] = 163, @@ -21000,36 +22372,35 @@ local character_entities = { ["brvbar"] = 166, ["sect"] = 167, ["Dot"] = 168, - ["die"] = 168, ["DoubleDot"] = 168, + ["die"] = 168, ["uml"] = 168, - ["copy"] = 169, ["COPY"] = 169, + ["copy"] = 169, ["ordf"] = 170, ["laquo"] = 171, ["not"] = 172, ["shy"] = 173, - ["reg"] = 174, - ["circledR"] = 174, ["REG"] = 174, + ["circledR"] = 174, + ["reg"] = 174, ["macr"] = 175, - ["OverBar"] = 175, ["strns"] = 175, ["deg"] = 176, + ["PlusMinus"] = 177, ["plusmn"] = 177, ["pm"] = 177, - ["PlusMinus"] = 177, ["sup2"] = 178, ["sup3"] = 179, - ["acute"] = 180, ["DiacriticalAcute"] = 180, + ["acute"] = 180, ["micro"] = 181, ["para"] = 182, - ["middot"] = 183, - ["centerdot"] = 183, ["CenterDot"] = 183, - ["cedil"] = 184, + ["centerdot"] = 183, + ["middot"] = 183, ["Cedilla"] = 184, + ["cedil"] = 184, ["sup1"] = 185, ["ordm"] = 186, ["raquo"] = 187, @@ -21044,6 +22415,7 @@ local character_entities = { ["Atilde"] = 195, ["Auml"] = 196, ["Aring"] = 197, + ["angst"] = 197, ["AElig"] = 198, ["Ccedil"] = 199, ["Egrave"] = 200, @@ -21093,8 +22465,8 @@ local character_entities = { ["ocirc"] = 244, ["otilde"] = 245, ["ouml"] = 246, - ["divide"] = 247, ["div"] = 247, + ["divide"] = 247, ["oslash"] = 248, ["ugrave"] = 249, ["uacute"] = 250, @@ -21229,20 +22601,19 @@ local character_entities = { ["gacute"] = 501, ["jmath"] = 567, ["circ"] = 710, - ["caron"] = 711, ["Hacek"] = 711, - ["breve"] = 728, + ["caron"] = 711, ["Breve"] = 728, - ["dot"] = 729, + ["breve"] = 728, ["DiacriticalDot"] = 729, + ["dot"] = 729, ["ring"] = 730, ["ogon"] = 731, - ["tilde"] = 732, ["DiacriticalTilde"] = 732, - ["dblac"] = 733, + ["tilde"] = 732, ["DiacriticalDoubleAcute"] = 733, + ["dblac"] = 733, ["DownBreve"] = 785, - ["UnderBar"] = 818, ["Alpha"] = 913, ["Beta"] = 914, ["Gamma"] = 915, @@ -21267,12 +22638,12 @@ local character_entities = { ["Chi"] = 935, ["Psi"] = 936, ["Omega"] = 937, + ["ohm"] = 937, ["alpha"] = 945, ["beta"] = 946, ["gamma"] = 947, ["delta"] = 948, - ["epsiv"] = 949, - ["varepsilon"] = 949, + ["epsi"] = 949, ["epsilon"] = 949, ["zeta"] = 950, ["eta"] = 951, @@ -21286,38 +22657,39 @@ local character_entities = { ["omicron"] = 959, ["pi"] = 960, ["rho"] = 961, + ["sigmaf"] = 962, ["sigmav"] = 962, ["varsigma"] = 962, - ["sigmaf"] = 962, ["sigma"] = 963, ["tau"] = 964, ["upsi"] = 965, ["upsilon"] = 965, ["phi"] = 966, - ["phiv"] = 966, - ["varphi"] = 966, ["chi"] = 967, ["psi"] = 968, ["omega"] = 969, + ["thetasym"] = 977, ["thetav"] = 977, ["vartheta"] = 977, - ["thetasym"] = 977, ["Upsi"] = 978, ["upsih"] = 978, + ["phiv"] = 981, ["straightphi"] = 981, + ["varphi"] = 981, ["piv"] = 982, ["varpi"] = 982, ["Gammad"] = 988, - ["gammad"] = 989, ["digamma"] = 989, + ["gammad"] = 989, ["kappav"] = 1008, ["varkappa"] = 1008, ["rhov"] = 1009, ["varrho"] = 1009, - ["epsi"] = 1013, + ["epsiv"] = 1013, ["straightepsilon"] = 1013, - ["bepsi"] = 1014, + ["varepsilon"] = 1013, ["backepsilon"] = 1014, + ["bepsi"] = 1014, ["IOcy"] = 1025, ["DJcy"] = 1026, ["GJcy"] = 1027, @@ -21416,40 +22788,40 @@ local character_entities = { ["emsp14"] = 8197, ["numsp"] = 8199, ["puncsp"] = 8200, - ["thinsp"] = 8201, ["ThinSpace"] = 8201, - ["hairsp"] = 8202, + ["thinsp"] = 8201, ["VeryThinSpace"] = 8202, - ["ZeroWidthSpace"] = 8203, - ["NegativeVeryThinSpace"] = 8203, - ["NegativeThinSpace"] = 8203, + ["hairsp"] = 8202, ["NegativeMediumSpace"] = 8203, ["NegativeThickSpace"] = 8203, + ["NegativeThinSpace"] = 8203, + ["NegativeVeryThinSpace"] = 8203, + ["ZeroWidthSpace"] = 8203, ["zwnj"] = 8204, ["zwj"] = 8205, ["lrm"] = 8206, ["rlm"] = 8207, - ["hyphen"] = 8208, ["dash"] = 8208, + ["hyphen"] = 8208, ["ndash"] = 8211, ["mdash"] = 8212, ["horbar"] = 8213, ["Verbar"] = 8214, ["Vert"] = 8214, - ["lsquo"] = 8216, ["OpenCurlyQuote"] = 8216, + ["lsquo"] = 8216, + ["CloseCurlyQuote"] = 8217, ["rsquo"] = 8217, ["rsquor"] = 8217, - ["CloseCurlyQuote"] = 8217, ["lsquor"] = 8218, ["sbquo"] = 8218, - ["ldquo"] = 8220, ["OpenCurlyDoubleQuote"] = 8220, + ["ldquo"] = 8220, + ["CloseCurlyDoubleQuote"] = 8221, ["rdquo"] = 8221, ["rdquor"] = 8221, - ["CloseCurlyDoubleQuote"] = 8221, - ["ldquor"] = 8222, ["bdquo"] = 8222, + ["ldquor"] = 8222, ["dagger"] = 8224, ["Dagger"] = 8225, ["ddagger"] = 8225, @@ -21463,10 +22835,11 @@ local character_entities = { ["prime"] = 8242, ["Prime"] = 8243, ["tprime"] = 8244, - ["bprime"] = 8245, ["backprime"] = 8245, + ["bprime"] = 8245, ["lsaquo"] = 8249, ["rsaquo"] = 8250, + ["OverBar"] = 8254, ["oline"] = 8254, ["caret"] = 8257, ["hybull"] = 8259, @@ -21474,6 +22847,7 @@ local character_entities = { ["bsemi"] = 8271, ["qprime"] = 8279, ["MediumSpace"] = 8287, + ["ThickSpace"] = {8287, 8202}, ["NoBreak"] = 8288, ["ApplyFunction"] = 8289, ["af"] = 8289, @@ -21482,34 +22856,34 @@ local character_entities = { ["InvisibleComma"] = 8291, ["ic"] = 8291, ["euro"] = 8364, - ["tdot"] = 8411, ["TripleDot"] = 8411, + ["tdot"] = 8411, ["DotDot"] = 8412, ["Copf"] = 8450, ["complexes"] = 8450, ["incare"] = 8453, ["gscr"] = 8458, - ["hamilt"] = 8459, ["HilbertSpace"] = 8459, ["Hscr"] = 8459, + ["hamilt"] = 8459, ["Hfr"] = 8460, ["Poincareplane"] = 8460, - ["quaternions"] = 8461, ["Hopf"] = 8461, + ["quaternions"] = 8461, ["planckh"] = 8462, - ["planck"] = 8463, ["hbar"] = 8463, - ["plankv"] = 8463, ["hslash"] = 8463, + ["planck"] = 8463, + ["plankv"] = 8463, ["Iscr"] = 8464, ["imagline"] = 8464, - ["image"] = 8465, + ["Ifr"] = 8465, ["Im"] = 8465, + ["image"] = 8465, ["imagpart"] = 8465, - ["Ifr"] = 8465, + ["Laplacetrf"] = 8466, ["Lscr"] = 8466, ["lagran"] = 8466, - ["Laplacetrf"] = 8466, ["ell"] = 8467, ["Nopf"] = 8469, ["naturals"] = 8469, @@ -21519,40 +22893,38 @@ local character_entities = { ["wp"] = 8472, ["Popf"] = 8473, ["primes"] = 8473, - ["rationals"] = 8474, ["Qopf"] = 8474, + ["rationals"] = 8474, ["Rscr"] = 8475, ["realine"] = 8475, - ["real"] = 8476, ["Re"] = 8476, - ["realpart"] = 8476, ["Rfr"] = 8476, - ["reals"] = 8477, + ["real"] = 8476, + ["realpart"] = 8476, ["Ropf"] = 8477, + ["reals"] = 8477, ["rx"] = 8478, - ["trade"] = 8482, ["TRADE"] = 8482, - ["integers"] = 8484, + ["trade"] = 8482, ["Zopf"] = 8484, - ["ohm"] = 8486, + ["integers"] = 8484, ["mho"] = 8487, ["Zfr"] = 8488, ["zeetrf"] = 8488, ["iiota"] = 8489, - ["angst"] = 8491, - ["bernou"] = 8492, ["Bernoullis"] = 8492, ["Bscr"] = 8492, - ["Cfr"] = 8493, + ["bernou"] = 8492, ["Cayleys"] = 8493, + ["Cfr"] = 8493, ["escr"] = 8495, ["Escr"] = 8496, ["expectation"] = 8496, - ["Fscr"] = 8497, ["Fouriertrf"] = 8497, - ["phmmat"] = 8499, + ["Fscr"] = 8497, ["Mellintrf"] = 8499, ["Mscr"] = 8499, + ["phmmat"] = 8499, ["order"] = 8500, ["orderof"] = 8500, ["oscr"] = 8500, @@ -21566,8 +22938,8 @@ local character_entities = { ["DifferentialD"] = 8518, ["dd"] = 8518, ["ExponentialE"] = 8519, - ["exponentiale"] = 8519, ["ee"] = 8519, + ["exponentiale"] = 8519, ["ImaginaryI"] = 8520, ["ii"] = 8520, ["frac13"] = 8531, @@ -21582,46 +22954,47 @@ local character_entities = { ["frac38"] = 8540, ["frac58"] = 8541, ["frac78"] = 8542, + ["LeftArrow"] = 8592, + ["ShortLeftArrow"] = 8592, ["larr"] = 8592, ["leftarrow"] = 8592, - ["LeftArrow"] = 8592, ["slarr"] = 8592, - ["ShortLeftArrow"] = 8592, + ["ShortUpArrow"] = 8593, + ["UpArrow"] = 8593, ["uarr"] = 8593, ["uparrow"] = 8593, - ["UpArrow"] = 8593, - ["ShortUpArrow"] = 8593, + ["RightArrow"] = 8594, + ["ShortRightArrow"] = 8594, ["rarr"] = 8594, ["rightarrow"] = 8594, - ["RightArrow"] = 8594, ["srarr"] = 8594, - ["ShortRightArrow"] = 8594, - ["darr"] = 8595, - ["downarrow"] = 8595, ["DownArrow"] = 8595, ["ShortDownArrow"] = 8595, + ["darr"] = 8595, + ["downarrow"] = 8595, + ["LeftRightArrow"] = 8596, ["harr"] = 8596, ["leftrightarrow"] = 8596, - ["LeftRightArrow"] = 8596, - ["varr"] = 8597, - ["updownarrow"] = 8597, ["UpDownArrow"] = 8597, - ["nwarr"] = 8598, + ["updownarrow"] = 8597, + ["varr"] = 8597, ["UpperLeftArrow"] = 8598, + ["nwarr"] = 8598, ["nwarrow"] = 8598, - ["nearr"] = 8599, ["UpperRightArrow"] = 8599, + ["nearr"] = 8599, ["nearrow"] = 8599, + ["LowerRightArrow"] = 8600, ["searr"] = 8600, ["searrow"] = 8600, - ["LowerRightArrow"] = 8600, + ["LowerLeftArrow"] = 8601, ["swarr"] = 8601, ["swarrow"] = 8601, - ["LowerLeftArrow"] = 8601, ["nlarr"] = 8602, ["nleftarrow"] = 8602, ["nrarr"] = 8603, ["nrightarrow"] = 8603, + ["nrarrw"] = {8605, 824}, ["rarrw"] = 8605, ["rightsquigarrow"] = 8605, ["Larr"] = 8606, @@ -21638,27 +23011,27 @@ local character_entities = { ["mapstoleft"] = 8612, ["UpTeeArrow"] = 8613, ["mapstoup"] = 8613, - ["map"] = 8614, ["RightTeeArrow"] = 8614, + ["map"] = 8614, ["mapsto"] = 8614, ["DownTeeArrow"] = 8615, ["mapstodown"] = 8615, - ["larrhk"] = 8617, ["hookleftarrow"] = 8617, - ["rarrhk"] = 8618, + ["larrhk"] = 8617, ["hookrightarrow"] = 8618, + ["rarrhk"] = 8618, ["larrlp"] = 8619, ["looparrowleft"] = 8619, - ["rarrlp"] = 8620, ["looparrowright"] = 8620, + ["rarrlp"] = 8620, ["harrw"] = 8621, ["leftrightsquigarrow"] = 8621, ["nharr"] = 8622, ["nleftrightarrow"] = 8622, - ["lsh"] = 8624, ["Lsh"] = 8624, - ["rsh"] = 8625, + ["lsh"] = 8624, ["Rsh"] = 8625, + ["rsh"] = 8625, ["ldsh"] = 8626, ["rdsh"] = 8627, ["crarr"] = 8629, @@ -21666,456 +23039,505 @@ local character_entities = { ["curvearrowleft"] = 8630, ["curarr"] = 8631, ["curvearrowright"] = 8631, - ["olarr"] = 8634, ["circlearrowleft"] = 8634, - ["orarr"] = 8635, + ["olarr"] = 8634, ["circlearrowright"] = 8635, - ["lharu"] = 8636, + ["orarr"] = 8635, ["LeftVector"] = 8636, ["leftharpoonup"] = 8636, - ["lhard"] = 8637, - ["leftharpoondown"] = 8637, + ["lharu"] = 8636, ["DownLeftVector"] = 8637, + ["leftharpoondown"] = 8637, + ["lhard"] = 8637, + ["RightUpVector"] = 8638, ["uharr"] = 8638, ["upharpoonright"] = 8638, - ["RightUpVector"] = 8638, + ["LeftUpVector"] = 8639, ["uharl"] = 8639, ["upharpoonleft"] = 8639, - ["LeftUpVector"] = 8639, - ["rharu"] = 8640, ["RightVector"] = 8640, + ["rharu"] = 8640, ["rightharpoonup"] = 8640, + ["DownRightVector"] = 8641, ["rhard"] = 8641, ["rightharpoondown"] = 8641, - ["DownRightVector"] = 8641, - ["dharr"] = 8642, ["RightDownVector"] = 8642, + ["dharr"] = 8642, ["downharpoonright"] = 8642, - ["dharl"] = 8643, ["LeftDownVector"] = 8643, + ["dharl"] = 8643, ["downharpoonleft"] = 8643, - ["rlarr"] = 8644, - ["rightleftarrows"] = 8644, ["RightArrowLeftArrow"] = 8644, - ["udarr"] = 8645, + ["rightleftarrows"] = 8644, + ["rlarr"] = 8644, ["UpArrowDownArrow"] = 8645, - ["lrarr"] = 8646, - ["leftrightarrows"] = 8646, + ["udarr"] = 8645, ["LeftArrowRightArrow"] = 8646, - ["llarr"] = 8647, + ["leftrightarrows"] = 8646, + ["lrarr"] = 8646, ["leftleftarrows"] = 8647, - ["uuarr"] = 8648, + ["llarr"] = 8647, ["upuparrows"] = 8648, - ["rrarr"] = 8649, + ["uuarr"] = 8648, ["rightrightarrows"] = 8649, + ["rrarr"] = 8649, ["ddarr"] = 8650, ["downdownarrows"] = 8650, - ["lrhar"] = 8651, ["ReverseEquilibrium"] = 8651, ["leftrightharpoons"] = 8651, - ["rlhar"] = 8652, - ["rightleftharpoons"] = 8652, + ["lrhar"] = 8651, ["Equilibrium"] = 8652, - ["nlArr"] = 8653, + ["rightleftharpoons"] = 8652, + ["rlhar"] = 8652, ["nLeftarrow"] = 8653, - ["nhArr"] = 8654, + ["nlArr"] = 8653, ["nLeftrightarrow"] = 8654, - ["nrArr"] = 8655, + ["nhArr"] = 8654, ["nRightarrow"] = 8655, - ["lArr"] = 8656, - ["Leftarrow"] = 8656, + ["nrArr"] = 8655, ["DoubleLeftArrow"] = 8656, - ["uArr"] = 8657, - ["Uparrow"] = 8657, + ["Leftarrow"] = 8656, + ["lArr"] = 8656, ["DoubleUpArrow"] = 8657, - ["rArr"] = 8658, - ["Rightarrow"] = 8658, - ["Implies"] = 8658, + ["Uparrow"] = 8657, + ["uArr"] = 8657, ["DoubleRightArrow"] = 8658, - ["dArr"] = 8659, - ["Downarrow"] = 8659, + ["Implies"] = 8658, + ["Rightarrow"] = 8658, + ["rArr"] = 8658, ["DoubleDownArrow"] = 8659, - ["hArr"] = 8660, - ["Leftrightarrow"] = 8660, + ["Downarrow"] = 8659, + ["dArr"] = 8659, ["DoubleLeftRightArrow"] = 8660, + ["Leftrightarrow"] = 8660, + ["hArr"] = 8660, ["iff"] = 8660, - ["vArr"] = 8661, - ["Updownarrow"] = 8661, ["DoubleUpDownArrow"] = 8661, + ["Updownarrow"] = 8661, + ["vArr"] = 8661, ["nwArr"] = 8662, ["neArr"] = 8663, ["seArr"] = 8664, ["swArr"] = 8665, - ["lAarr"] = 8666, ["Lleftarrow"] = 8666, - ["rAarr"] = 8667, + ["lAarr"] = 8666, ["Rrightarrow"] = 8667, + ["rAarr"] = 8667, ["zigrarr"] = 8669, - ["larrb"] = 8676, ["LeftArrowBar"] = 8676, - ["rarrb"] = 8677, + ["larrb"] = 8676, ["RightArrowBar"] = 8677, - ["duarr"] = 8693, + ["rarrb"] = 8677, ["DownArrowUpArrow"] = 8693, + ["duarr"] = 8693, ["loarr"] = 8701, ["roarr"] = 8702, ["hoarr"] = 8703, - ["forall"] = 8704, ["ForAll"] = 8704, + ["forall"] = 8704, ["comp"] = 8705, ["complement"] = 8705, - ["part"] = 8706, ["PartialD"] = 8706, - ["exist"] = 8707, + ["npart"] = {8706, 824}, + ["part"] = 8706, ["Exists"] = 8707, - ["nexist"] = 8708, + ["exist"] = 8707, ["NotExists"] = 8708, + ["nexist"] = 8708, ["nexists"] = 8708, ["empty"] = 8709, ["emptyset"] = 8709, ["emptyv"] = 8709, ["varnothing"] = 8709, - ["nabla"] = 8711, ["Del"] = 8711, - ["isin"] = 8712, - ["isinv"] = 8712, + ["nabla"] = 8711, ["Element"] = 8712, ["in"] = 8712, - ["notin"] = 8713, + ["isin"] = 8712, + ["isinv"] = 8712, ["NotElement"] = 8713, + ["notin"] = 8713, ["notinva"] = 8713, - ["niv"] = 8715, ["ReverseElement"] = 8715, - ["ni"] = 8715, ["SuchThat"] = 8715, + ["ni"] = 8715, + ["niv"] = 8715, + ["NotReverseElement"] = 8716, ["notni"] = 8716, ["notniva"] = 8716, - ["NotReverseElement"] = 8716, - ["prod"] = 8719, ["Product"] = 8719, - ["coprod"] = 8720, + ["prod"] = 8719, ["Coproduct"] = 8720, - ["sum"] = 8721, + ["coprod"] = 8720, ["Sum"] = 8721, + ["sum"] = 8721, ["minus"] = 8722, + ["MinusPlus"] = 8723, ["mnplus"] = 8723, ["mp"] = 8723, - ["MinusPlus"] = 8723, - ["plusdo"] = 8724, ["dotplus"] = 8724, - ["setmn"] = 8726, - ["setminus"] = 8726, + ["plusdo"] = 8724, ["Backslash"] = 8726, - ["ssetmn"] = 8726, + ["setminus"] = 8726, + ["setmn"] = 8726, ["smallsetminus"] = 8726, + ["ssetmn"] = 8726, ["lowast"] = 8727, - ["compfn"] = 8728, ["SmallCircle"] = 8728, - ["radic"] = 8730, + ["compfn"] = 8728, ["Sqrt"] = 8730, + ["radic"] = 8730, + ["Proportional"] = 8733, ["prop"] = 8733, ["propto"] = 8733, - ["Proportional"] = 8733, - ["vprop"] = 8733, ["varpropto"] = 8733, + ["vprop"] = 8733, ["infin"] = 8734, ["angrt"] = 8735, ["ang"] = 8736, ["angle"] = 8736, + ["nang"] = {8736, 8402}, ["angmsd"] = 8737, ["measuredangle"] = 8737, ["angsph"] = 8738, - ["mid"] = 8739, ["VerticalBar"] = 8739, - ["smid"] = 8739, + ["mid"] = 8739, ["shortmid"] = 8739, - ["nmid"] = 8740, + ["smid"] = 8739, ["NotVerticalBar"] = 8740, - ["nsmid"] = 8740, + ["nmid"] = 8740, ["nshortmid"] = 8740, + ["nsmid"] = 8740, + ["DoubleVerticalBar"] = 8741, ["par"] = 8741, ["parallel"] = 8741, - ["DoubleVerticalBar"] = 8741, - ["spar"] = 8741, ["shortparallel"] = 8741, + ["spar"] = 8741, + ["NotDoubleVerticalBar"] = 8742, ["npar"] = 8742, ["nparallel"] = 8742, - ["NotDoubleVerticalBar"] = 8742, - ["nspar"] = 8742, ["nshortparallel"] = 8742, + ["nspar"] = 8742, ["and"] = 8743, ["wedge"] = 8743, ["or"] = 8744, ["vee"] = 8744, ["cap"] = 8745, + ["caps"] = {8745, 65024}, ["cup"] = 8746, - ["int"] = 8747, + ["cups"] = {8746, 65024}, ["Integral"] = 8747, + ["int"] = 8747, ["Int"] = 8748, - ["tint"] = 8749, ["iiint"] = 8749, + ["tint"] = 8749, + ["ContourIntegral"] = 8750, ["conint"] = 8750, ["oint"] = 8750, - ["ContourIntegral"] = 8750, ["Conint"] = 8751, ["DoubleContourIntegral"] = 8751, ["Cconint"] = 8752, ["cwint"] = 8753, - ["cwconint"] = 8754, ["ClockwiseContourIntegral"] = 8754, - ["awconint"] = 8755, + ["cwconint"] = 8754, ["CounterClockwiseContourIntegral"] = 8755, + ["awconint"] = 8755, + ["Therefore"] = 8756, ["there4"] = 8756, ["therefore"] = 8756, - ["Therefore"] = 8756, + ["Because"] = 8757, ["becaus"] = 8757, ["because"] = 8757, - ["Because"] = 8757, ["ratio"] = 8758, ["Colon"] = 8759, ["Proportion"] = 8759, - ["minusd"] = 8760, ["dotminus"] = 8760, + ["minusd"] = 8760, ["mDDot"] = 8762, ["homtht"] = 8763, - ["sim"] = 8764, ["Tilde"] = 8764, - ["thksim"] = 8764, + ["nvsim"] = {8764, 8402}, + ["sim"] = 8764, ["thicksim"] = 8764, - ["bsim"] = 8765, + ["thksim"] = 8764, ["backsim"] = 8765, + ["bsim"] = 8765, + ["race"] = {8765, 817}, ["ac"] = 8766, + ["acE"] = {8766, 819}, ["mstpos"] = 8766, ["acd"] = 8767, - ["wreath"] = 8768, ["VerticalTilde"] = 8768, ["wr"] = 8768, - ["nsim"] = 8769, + ["wreath"] = 8768, ["NotTilde"] = 8769, - ["esim"] = 8770, + ["nsim"] = 8769, ["EqualTilde"] = 8770, + ["NotEqualTilde"] = {8770, 824}, ["eqsim"] = 8770, - ["sime"] = 8771, + ["esim"] = 8770, + ["nesim"] = {8770, 824}, ["TildeEqual"] = 8771, + ["sime"] = 8771, ["simeq"] = 8771, + ["NotTildeEqual"] = 8772, ["nsime"] = 8772, ["nsimeq"] = 8772, - ["NotTildeEqual"] = 8772, - ["cong"] = 8773, ["TildeFullEqual"] = 8773, + ["cong"] = 8773, ["simne"] = 8774, - ["ncong"] = 8775, ["NotTildeFullEqual"] = 8775, - ["asymp"] = 8776, - ["ap"] = 8776, + ["ncong"] = 8775, ["TildeTilde"] = 8776, + ["ap"] = 8776, ["approx"] = 8776, - ["thkap"] = 8776, + ["asymp"] = 8776, ["thickapprox"] = 8776, - ["nap"] = 8777, + ["thkap"] = 8776, ["NotTildeTilde"] = 8777, + ["nap"] = 8777, ["napprox"] = 8777, ["ape"] = 8778, ["approxeq"] = 8778, ["apid"] = 8779, - ["bcong"] = 8780, + ["napid"] = {8779, 824}, ["backcong"] = 8780, - ["asympeq"] = 8781, + ["bcong"] = 8780, ["CupCap"] = 8781, - ["bump"] = 8782, - ["HumpDownHump"] = 8782, + ["asympeq"] = 8781, + ["nvap"] = {8781, 8402}, ["Bumpeq"] = 8782, - ["bumpe"] = 8783, + ["HumpDownHump"] = 8782, + ["NotHumpDownHump"] = {8782, 824}, + ["bump"] = 8782, + ["nbump"] = {8782, 824}, ["HumpEqual"] = 8783, + ["NotHumpEqual"] = {8783, 824}, + ["bumpe"] = 8783, ["bumpeq"] = 8783, - ["esdot"] = 8784, + ["nbumpe"] = {8783, 824}, ["DotEqual"] = 8784, ["doteq"] = 8784, - ["eDot"] = 8785, + ["esdot"] = 8784, + ["nedot"] = {8784, 824}, ["doteqdot"] = 8785, + ["eDot"] = 8785, ["efDot"] = 8786, ["fallingdotseq"] = 8786, ["erDot"] = 8787, ["risingdotseq"] = 8787, + ["Assign"] = 8788, ["colone"] = 8788, ["coloneq"] = 8788, - ["Assign"] = 8788, ["ecolon"] = 8789, ["eqcolon"] = 8789, ["ecir"] = 8790, ["eqcirc"] = 8790, - ["cire"] = 8791, ["circeq"] = 8791, + ["cire"] = 8791, ["wedgeq"] = 8793, ["veeeq"] = 8794, - ["trie"] = 8796, ["triangleq"] = 8796, + ["trie"] = 8796, ["equest"] = 8799, ["questeq"] = 8799, - ["ne"] = 8800, ["NotEqual"] = 8800, - ["equiv"] = 8801, + ["ne"] = 8800, ["Congruent"] = 8801, - ["nequiv"] = 8802, + ["bnequiv"] = {8801, 8421}, + ["equiv"] = 8801, ["NotCongruent"] = 8802, + ["nequiv"] = 8802, ["le"] = 8804, ["leq"] = 8804, - ["ge"] = 8805, + ["nvle"] = {8804, 8402}, ["GreaterEqual"] = 8805, + ["ge"] = 8805, ["geq"] = 8805, - ["lE"] = 8806, + ["nvge"] = {8805, 8402}, ["LessFullEqual"] = 8806, + ["lE"] = 8806, ["leqq"] = 8806, - ["gE"] = 8807, + ["nlE"] = {8806, 824}, + ["nleqq"] = {8806, 824}, ["GreaterFullEqual"] = 8807, + ["NotGreaterFullEqual"] = {8807, 824}, + ["gE"] = 8807, ["geqq"] = 8807, + ["ngE"] = {8807, 824}, + ["ngeqq"] = {8807, 824}, ["lnE"] = 8808, ["lneqq"] = 8808, + ["lvertneqq"] = {8808, 65024}, + ["lvnE"] = {8808, 65024}, ["gnE"] = 8809, ["gneqq"] = 8809, + ["gvertneqq"] = {8809, 65024}, + ["gvnE"] = {8809, 65024}, ["Lt"] = 8810, ["NestedLessLess"] = 8810, + ["NotLessLess"] = {8810, 824}, ["ll"] = 8810, + ["nLt"] = {8810, 8402}, + ["nLtv"] = {8810, 824}, ["Gt"] = 8811, ["NestedGreaterGreater"] = 8811, + ["NotGreaterGreater"] = {8811, 824}, ["gg"] = 8811, - ["twixt"] = 8812, + ["nGt"] = {8811, 8402}, + ["nGtv"] = {8811, 824}, ["between"] = 8812, + ["twixt"] = 8812, ["NotCupCap"] = 8813, - ["nlt"] = 8814, ["NotLess"] = 8814, ["nless"] = 8814, - ["ngt"] = 8815, + ["nlt"] = 8814, ["NotGreater"] = 8815, + ["ngt"] = 8815, ["ngtr"] = 8815, - ["nle"] = 8816, ["NotLessEqual"] = 8816, + ["nle"] = 8816, ["nleq"] = 8816, - ["nge"] = 8817, ["NotGreaterEqual"] = 8817, + ["nge"] = 8817, ["ngeq"] = 8817, - ["lsim"] = 8818, ["LessTilde"] = 8818, ["lesssim"] = 8818, + ["lsim"] = 8818, + ["GreaterTilde"] = 8819, ["gsim"] = 8819, ["gtrsim"] = 8819, - ["GreaterTilde"] = 8819, - ["nlsim"] = 8820, ["NotLessTilde"] = 8820, - ["ngsim"] = 8821, + ["nlsim"] = 8820, ["NotGreaterTilde"] = 8821, - ["lg"] = 8822, - ["lessgtr"] = 8822, + ["ngsim"] = 8821, ["LessGreater"] = 8822, + ["lessgtr"] = 8822, + ["lg"] = 8822, + ["GreaterLess"] = 8823, ["gl"] = 8823, ["gtrless"] = 8823, - ["GreaterLess"] = 8823, - ["ntlg"] = 8824, ["NotLessGreater"] = 8824, - ["ntgl"] = 8825, + ["ntlg"] = 8824, ["NotGreaterLess"] = 8825, - ["pr"] = 8826, + ["ntgl"] = 8825, ["Precedes"] = 8826, + ["pr"] = 8826, ["prec"] = 8826, - ["sc"] = 8827, ["Succeeds"] = 8827, + ["sc"] = 8827, ["succ"] = 8827, - ["prcue"] = 8828, ["PrecedesSlantEqual"] = 8828, + ["prcue"] = 8828, ["preccurlyeq"] = 8828, - ["sccue"] = 8829, ["SucceedsSlantEqual"] = 8829, + ["sccue"] = 8829, ["succcurlyeq"] = 8829, - ["prsim"] = 8830, - ["precsim"] = 8830, ["PrecedesTilde"] = 8830, + ["precsim"] = 8830, + ["prsim"] = 8830, + ["NotSucceedsTilde"] = {8831, 824}, + ["SucceedsTilde"] = 8831, ["scsim"] = 8831, ["succsim"] = 8831, - ["SucceedsTilde"] = 8831, + ["NotPrecedes"] = 8832, ["npr"] = 8832, ["nprec"] = 8832, - ["NotPrecedes"] = 8832, + ["NotSucceeds"] = 8833, ["nsc"] = 8833, ["nsucc"] = 8833, - ["NotSucceeds"] = 8833, + ["NotSubset"] = {8834, 8402}, + ["nsubset"] = {8834, 8402}, ["sub"] = 8834, ["subset"] = 8834, + ["vnsub"] = {8834, 8402}, + ["NotSuperset"] = {8835, 8402}, + ["Superset"] = 8835, + ["nsupset"] = {8835, 8402}, ["sup"] = 8835, ["supset"] = 8835, - ["Superset"] = 8835, + ["vnsup"] = {8835, 8402}, ["nsub"] = 8836, ["nsup"] = 8837, - ["sube"] = 8838, ["SubsetEqual"] = 8838, + ["sube"] = 8838, ["subseteq"] = 8838, + ["SupersetEqual"] = 8839, ["supe"] = 8839, ["supseteq"] = 8839, - ["SupersetEqual"] = 8839, + ["NotSubsetEqual"] = 8840, ["nsube"] = 8840, ["nsubseteq"] = 8840, - ["NotSubsetEqual"] = 8840, + ["NotSupersetEqual"] = 8841, ["nsupe"] = 8841, ["nsupseteq"] = 8841, - ["NotSupersetEqual"] = 8841, ["subne"] = 8842, ["subsetneq"] = 8842, + ["varsubsetneq"] = {8842, 65024}, + ["vsubne"] = {8842, 65024}, ["supne"] = 8843, ["supsetneq"] = 8843, + ["varsupsetneq"] = {8843, 65024}, + ["vsupne"] = {8843, 65024}, ["cupdot"] = 8845, - ["uplus"] = 8846, ["UnionPlus"] = 8846, - ["sqsub"] = 8847, + ["uplus"] = 8846, + ["NotSquareSubset"] = {8847, 824}, ["SquareSubset"] = 8847, + ["sqsub"] = 8847, ["sqsubset"] = 8847, - ["sqsup"] = 8848, + ["NotSquareSuperset"] = {8848, 824}, ["SquareSuperset"] = 8848, + ["sqsup"] = 8848, ["sqsupset"] = 8848, - ["sqsube"] = 8849, ["SquareSubsetEqual"] = 8849, + ["sqsube"] = 8849, ["sqsubseteq"] = 8849, - ["sqsupe"] = 8850, ["SquareSupersetEqual"] = 8850, + ["sqsupe"] = 8850, ["sqsupseteq"] = 8850, - ["sqcap"] = 8851, ["SquareIntersection"] = 8851, - ["sqcup"] = 8852, + ["sqcap"] = 8851, + ["sqcaps"] = {8851, 65024}, ["SquareUnion"] = 8852, - ["oplus"] = 8853, + ["sqcup"] = 8852, + ["sqcups"] = {8852, 65024}, ["CirclePlus"] = 8853, - ["ominus"] = 8854, + ["oplus"] = 8853, ["CircleMinus"] = 8854, - ["otimes"] = 8855, + ["ominus"] = 8854, ["CircleTimes"] = 8855, + ["otimes"] = 8855, ["osol"] = 8856, - ["odot"] = 8857, ["CircleDot"] = 8857, - ["ocir"] = 8858, + ["odot"] = 8857, ["circledcirc"] = 8858, - ["oast"] = 8859, + ["ocir"] = 8858, ["circledast"] = 8859, - ["odash"] = 8861, + ["oast"] = 8859, ["circleddash"] = 8861, - ["plusb"] = 8862, + ["odash"] = 8861, ["boxplus"] = 8862, - ["minusb"] = 8863, + ["plusb"] = 8862, ["boxminus"] = 8863, - ["timesb"] = 8864, + ["minusb"] = 8863, ["boxtimes"] = 8864, - ["sdotb"] = 8865, + ["timesb"] = 8864, ["dotsquare"] = 8865, - ["vdash"] = 8866, + ["sdotb"] = 8865, ["RightTee"] = 8866, - ["dashv"] = 8867, + ["vdash"] = 8866, ["LeftTee"] = 8867, - ["top"] = 8868, + ["dashv"] = 8867, ["DownTee"] = 8868, - ["bottom"] = 8869, + ["top"] = 8868, + ["UpTee"] = 8869, ["bot"] = 8869, + ["bottom"] = 8869, ["perp"] = 8869, - ["UpTee"] = 8869, ["models"] = 8871, - ["vDash"] = 8872, ["DoubleRightTee"] = 8872, + ["vDash"] = 8872, ["Vdash"] = 8873, ["Vvdash"] = 8874, ["VDash"] = 8875, @@ -22124,22 +23546,24 @@ local character_entities = { ["nVdash"] = 8878, ["nVDash"] = 8879, ["prurel"] = 8880, - ["vltri"] = 8882, - ["vartriangleleft"] = 8882, ["LeftTriangle"] = 8882, - ["vrtri"] = 8883, - ["vartriangleright"] = 8883, + ["vartriangleleft"] = 8882, + ["vltri"] = 8882, ["RightTriangle"] = 8883, + ["vartriangleright"] = 8883, + ["vrtri"] = 8883, + ["LeftTriangleEqual"] = 8884, ["ltrie"] = 8884, + ["nvltrie"] = {8884, 8402}, ["trianglelefteq"] = 8884, - ["LeftTriangleEqual"] = 8884, + ["RightTriangleEqual"] = 8885, + ["nvrtrie"] = {8885, 8402}, ["rtrie"] = 8885, ["trianglerighteq"] = 8885, - ["RightTriangleEqual"] = 8885, ["origof"] = 8886, ["imof"] = 8887, - ["mumap"] = 8888, ["multimap"] = 8888, + ["mumap"] = 8888, ["hercon"] = 8889, ["intcal"] = 8890, ["intercal"] = 8890, @@ -22147,39 +23571,39 @@ local character_entities = { ["barvee"] = 8893, ["angrtvb"] = 8894, ["lrtri"] = 8895, - ["xwedge"] = 8896, ["Wedge"] = 8896, ["bigwedge"] = 8896, - ["xvee"] = 8897, + ["xwedge"] = 8896, ["Vee"] = 8897, ["bigvee"] = 8897, - ["xcap"] = 8898, + ["xvee"] = 8897, ["Intersection"] = 8898, ["bigcap"] = 8898, - ["xcup"] = 8899, + ["xcap"] = 8898, ["Union"] = 8899, ["bigcup"] = 8899, + ["xcup"] = 8899, + ["Diamond"] = 8900, ["diam"] = 8900, ["diamond"] = 8900, - ["Diamond"] = 8900, ["sdot"] = 8901, - ["sstarf"] = 8902, ["Star"] = 8902, - ["divonx"] = 8903, + ["sstarf"] = 8902, ["divideontimes"] = 8903, + ["divonx"] = 8903, ["bowtie"] = 8904, ["ltimes"] = 8905, ["rtimes"] = 8906, - ["lthree"] = 8907, ["leftthreetimes"] = 8907, - ["rthree"] = 8908, + ["lthree"] = 8907, ["rightthreetimes"] = 8908, - ["bsime"] = 8909, + ["rthree"] = 8908, ["backsimeq"] = 8909, - ["cuvee"] = 8910, + ["bsime"] = 8909, ["curlyvee"] = 8910, - ["cuwed"] = 8911, + ["cuvee"] = 8910, ["curlywedge"] = 8911, + ["cuwed"] = 8911, ["Sub"] = 8912, ["Subset"] = 8912, ["Sup"] = 8913, @@ -22189,49 +23613,53 @@ local character_entities = { ["fork"] = 8916, ["pitchfork"] = 8916, ["epar"] = 8917, - ["ltdot"] = 8918, ["lessdot"] = 8918, + ["ltdot"] = 8918, ["gtdot"] = 8919, ["gtrdot"] = 8919, ["Ll"] = 8920, + ["nLl"] = {8920, 824}, ["Gg"] = 8921, ["ggg"] = 8921, - ["leg"] = 8922, + ["nGg"] = {8921, 824}, ["LessEqualGreater"] = 8922, + ["leg"] = 8922, + ["lesg"] = {8922, 65024}, ["lesseqgtr"] = 8922, + ["GreaterEqualLess"] = 8923, ["gel"] = 8923, + ["gesl"] = {8923, 65024}, ["gtreqless"] = 8923, - ["GreaterEqualLess"] = 8923, ["cuepr"] = 8926, ["curlyeqprec"] = 8926, ["cuesc"] = 8927, ["curlyeqsucc"] = 8927, - ["nprcue"] = 8928, ["NotPrecedesSlantEqual"] = 8928, - ["nsccue"] = 8929, + ["nprcue"] = 8928, ["NotSucceedsSlantEqual"] = 8929, - ["nsqsube"] = 8930, + ["nsccue"] = 8929, ["NotSquareSubsetEqual"] = 8930, - ["nsqsupe"] = 8931, + ["nsqsube"] = 8930, ["NotSquareSupersetEqual"] = 8931, + ["nsqsupe"] = 8931, ["lnsim"] = 8934, ["gnsim"] = 8935, - ["prnsim"] = 8936, ["precnsim"] = 8936, + ["prnsim"] = 8936, ["scnsim"] = 8937, ["succnsim"] = 8937, + ["NotLeftTriangle"] = 8938, ["nltri"] = 8938, ["ntriangleleft"] = 8938, - ["NotLeftTriangle"] = 8938, + ["NotRightTriangle"] = 8939, ["nrtri"] = 8939, ["ntriangleright"] = 8939, - ["NotRightTriangle"] = 8939, + ["NotLeftTriangleEqual"] = 8940, ["nltrie"] = 8940, ["ntrianglelefteq"] = 8940, - ["NotLeftTriangleEqual"] = 8940, + ["NotRightTriangleEqual"] = 8941, ["nrtrie"] = 8941, ["ntrianglerighteq"] = 8941, - ["NotRightTriangleEqual"] = 8941, ["vellip"] = 8942, ["ctdot"] = 8943, ["utdot"] = 8944, @@ -22240,9 +23668,11 @@ local character_entities = { ["isinsv"] = 8947, ["isins"] = 8948, ["isindot"] = 8949, + ["notindot"] = {8949, 824}, ["notinvc"] = 8950, ["notinvb"] = 8951, ["isinE"] = 8953, + ["notinE"] = {8953, 824}, ["nisd"] = 8954, ["xnis"] = 8955, ["nis"] = 8956, @@ -22252,14 +23682,14 @@ local character_entities = { ["barwedge"] = 8965, ["Barwed"] = 8966, ["doublebarwedge"] = 8966, - ["lceil"] = 8968, ["LeftCeiling"] = 8968, - ["rceil"] = 8969, + ["lceil"] = 8968, ["RightCeiling"] = 8969, - ["lfloor"] = 8970, + ["rceil"] = 8969, ["LeftFloor"] = 8970, - ["rfloor"] = 8971, + ["lfloor"] = 8970, ["RightFloor"] = 8971, + ["rfloor"] = 8971, ["drcrop"] = 8972, ["dlcrop"] = 8973, ["urcrop"] = 8974, @@ -22291,10 +23721,10 @@ local character_entities = { ["lmoustache"] = 9136, ["rmoust"] = 9137, ["rmoustache"] = 9137, - ["tbrk"] = 9140, ["OverBracket"] = 9140, - ["bbrk"] = 9141, + ["tbrk"] = 9140, ["UnderBracket"] = 9141, + ["bbrk"] = 9141, ["bbrktbrk"] = 9142, ["OverParenthesis"] = 9180, ["UnderParenthesis"] = 9181, @@ -22303,10 +23733,10 @@ local character_entities = { ["trpezium"] = 9186, ["elinters"] = 9191, ["blank"] = 9251, - ["oS"] = 9416, ["circledS"] = 9416, - ["boxh"] = 9472, + ["oS"] = 9416, ["HorizontalLine"] = 9472, + ["boxh"] = 9472, ["boxv"] = 9474, ["boxdr"] = 9484, ["boxdl"] = 9488, @@ -22352,50 +23782,50 @@ local character_entities = { ["blk14"] = 9617, ["blk12"] = 9618, ["blk34"] = 9619, + ["Square"] = 9633, ["squ"] = 9633, ["square"] = 9633, - ["Square"] = 9633, - ["squf"] = 9642, - ["squarf"] = 9642, - ["blacksquare"] = 9642, ["FilledVerySmallSquare"] = 9642, + ["blacksquare"] = 9642, + ["squarf"] = 9642, + ["squf"] = 9642, ["EmptyVerySmallSquare"] = 9643, ["rect"] = 9645, ["marker"] = 9646, ["fltns"] = 9649, - ["xutri"] = 9651, ["bigtriangleup"] = 9651, - ["utrif"] = 9652, + ["xutri"] = 9651, ["blacktriangle"] = 9652, - ["utri"] = 9653, + ["utrif"] = 9652, ["triangle"] = 9653, - ["rtrif"] = 9656, + ["utri"] = 9653, ["blacktriangleright"] = 9656, + ["rtrif"] = 9656, ["rtri"] = 9657, ["triangleright"] = 9657, - ["xdtri"] = 9661, ["bigtriangledown"] = 9661, - ["dtrif"] = 9662, + ["xdtri"] = 9661, ["blacktriangledown"] = 9662, + ["dtrif"] = 9662, ["dtri"] = 9663, ["triangledown"] = 9663, - ["ltrif"] = 9666, ["blacktriangleleft"] = 9666, + ["ltrif"] = 9666, ["ltri"] = 9667, ["triangleleft"] = 9667, ["loz"] = 9674, ["lozenge"] = 9674, ["cir"] = 9675, ["tridot"] = 9708, - ["xcirc"] = 9711, ["bigcirc"] = 9711, + ["xcirc"] = 9711, ["ultri"] = 9720, ["urtri"] = 9721, ["lltri"] = 9722, ["EmptySmallSquare"] = 9723, ["FilledSmallSquare"] = 9724, - ["starf"] = 9733, ["bigstar"] = 9733, + ["starf"] = 9733, ["star"] = 9734, ["phone"] = 9742, ["female"] = 9792, @@ -22406,8 +23836,8 @@ local character_entities = { ["clubsuit"] = 9827, ["hearts"] = 9829, ["heartsuit"] = 9829, - ["diams"] = 9830, ["diamondsuit"] = 9830, + ["diams"] = 9830, ["sung"] = 9834, ["flat"] = 9837, ["natur"] = 9838, @@ -22422,51 +23852,53 @@ local character_entities = { ["VerticalSeparator"] = 10072, ["lbbrk"] = 10098, ["rbbrk"] = 10099, - ["lobrk"] = 10214, + ["bsolhsub"] = 10184, + ["suphsol"] = 10185, ["LeftDoubleBracket"] = 10214, - ["robrk"] = 10215, + ["lobrk"] = 10214, ["RightDoubleBracket"] = 10215, - ["lang"] = 10216, + ["robrk"] = 10215, ["LeftAngleBracket"] = 10216, + ["lang"] = 10216, ["langle"] = 10216, - ["rang"] = 10217, ["RightAngleBracket"] = 10217, + ["rang"] = 10217, ["rangle"] = 10217, ["Lang"] = 10218, ["Rang"] = 10219, ["loang"] = 10220, ["roang"] = 10221, - ["xlarr"] = 10229, - ["longleftarrow"] = 10229, ["LongLeftArrow"] = 10229, - ["xrarr"] = 10230, - ["longrightarrow"] = 10230, + ["longleftarrow"] = 10229, + ["xlarr"] = 10229, ["LongRightArrow"] = 10230, - ["xharr"] = 10231, - ["longleftrightarrow"] = 10231, + ["longrightarrow"] = 10230, + ["xrarr"] = 10230, ["LongLeftRightArrow"] = 10231, - ["xlArr"] = 10232, - ["Longleftarrow"] = 10232, + ["longleftrightarrow"] = 10231, + ["xharr"] = 10231, ["DoubleLongLeftArrow"] = 10232, - ["xrArr"] = 10233, - ["Longrightarrow"] = 10233, + ["Longleftarrow"] = 10232, + ["xlArr"] = 10232, ["DoubleLongRightArrow"] = 10233, - ["xhArr"] = 10234, - ["Longleftrightarrow"] = 10234, + ["Longrightarrow"] = 10233, + ["xrArr"] = 10233, ["DoubleLongLeftRightArrow"] = 10234, - ["xmap"] = 10236, + ["Longleftrightarrow"] = 10234, + ["xhArr"] = 10234, ["longmapsto"] = 10236, + ["xmap"] = 10236, ["dzigrarr"] = 10239, ["nvlArr"] = 10498, ["nvrArr"] = 10499, ["nvHarr"] = 10500, ["Map"] = 10501, ["lbarr"] = 10508, - ["rbarr"] = 10509, ["bkarow"] = 10509, + ["rbarr"] = 10509, ["lBarr"] = 10510, - ["rBarr"] = 10511, ["dbkarow"] = 10511, + ["rBarr"] = 10511, ["RBarr"] = 10512, ["drbkarow"] = 10512, ["DDotrahd"] = 10513, @@ -22483,16 +23915,17 @@ local character_entities = { ["rarrbfs"] = 10528, ["nwarhk"] = 10531, ["nearhk"] = 10532, - ["searhk"] = 10533, ["hksearow"] = 10533, - ["swarhk"] = 10534, + ["searhk"] = 10533, ["hkswarow"] = 10534, + ["swarhk"] = 10534, ["nwnear"] = 10535, ["nesear"] = 10536, ["toea"] = 10536, ["seswar"] = 10537, ["tosa"] = 10537, ["swnwar"] = 10538, + ["nrarrc"] = {10547, 824}, ["rarrc"] = 10547, ["cudarrr"] = 10549, ["ldca"] = 10550, @@ -22538,10 +23971,10 @@ local character_entities = { ["llhard"] = 10603, ["rharul"] = 10604, ["lrhard"] = 10605, - ["udhar"] = 10606, ["UpEquilibrium"] = 10606, - ["duhar"] = 10607, + ["udhar"] = 10606, ["ReverseUpEquilibrium"] = 10607, + ["duhar"] = 10607, ["RoundImplies"] = 10608, ["erarr"] = 10609, ["simrarr"] = 10610, @@ -22608,30 +24041,31 @@ local character_entities = { ["trisb"] = 10701, ["rtriltri"] = 10702, ["LeftTriangleBar"] = 10703, + ["NotLeftTriangleBar"] = {10703, 824}, + ["NotRightTriangleBar"] = {10704, 824}, ["RightTriangleBar"] = 10704, - ["race"] = 10714, ["iinfin"] = 10716, ["infintie"] = 10717, ["nvinfin"] = 10718, ["eparsl"] = 10723, ["smeparsl"] = 10724, ["eqvparsl"] = 10725, - ["lozf"] = 10731, ["blacklozenge"] = 10731, + ["lozf"] = 10731, ["RuleDelayed"] = 10740, ["dsol"] = 10742, - ["xodot"] = 10752, ["bigodot"] = 10752, - ["xoplus"] = 10753, + ["xodot"] = 10752, ["bigoplus"] = 10753, - ["xotime"] = 10754, + ["xoplus"] = 10753, ["bigotimes"] = 10754, - ["xuplus"] = 10756, + ["xotime"] = 10754, ["biguplus"] = 10756, - ["xsqcup"] = 10758, + ["xuplus"] = 10756, ["bigsqcup"] = 10758, - ["qint"] = 10764, + ["xsqcup"] = 10758, ["iiiint"] = 10764, + ["qint"] = 10764, ["fpartint"] = 10765, ["cirfnint"] = 10768, ["awint"] = 10769, @@ -22663,8 +24097,8 @@ local character_entities = { ["triplus"] = 10809, ["triminus"] = 10810, ["tritime"] = 10811, - ["iprod"] = 10812, ["intprod"] = 10812, + ["iprod"] = 10812, ["amalg"] = 10815, ["capdot"] = 10816, ["ncup"] = 10818, @@ -22694,27 +24128,35 @@ local character_entities = { ["sdote"] = 10854, ["simdot"] = 10858, ["congdot"] = 10861, + ["ncongdot"] = {10861, 824}, ["easter"] = 10862, ["apacir"] = 10863, ["apE"] = 10864, + ["napE"] = {10864, 824}, ["eplus"] = 10865, ["pluse"] = 10866, ["Esim"] = 10867, ["Colone"] = 10868, ["Equal"] = 10869, - ["eDDot"] = 10871, ["ddotseq"] = 10871, + ["eDDot"] = 10871, ["equivDD"] = 10872, ["ltcir"] = 10873, ["gtcir"] = 10874, ["ltquest"] = 10875, ["gtquest"] = 10876, - ["les"] = 10877, ["LessSlantEqual"] = 10877, + ["NotLessSlantEqual"] = {10877, 824}, ["leqslant"] = 10877, - ["ges"] = 10878, + ["les"] = 10877, + ["nleqslant"] = {10877, 824}, + ["nles"] = {10877, 824}, ["GreaterSlantEqual"] = 10878, + ["NotGreaterSlantEqual"] = {10878, 824}, ["geqslant"] = 10878, + ["ges"] = 10878, + ["ngeqslant"] = {10878, 824}, + ["nges"] = {10878, 824}, ["lesdot"] = 10879, ["gesdot"] = 10880, ["lesdoto"] = 10881, @@ -22758,7 +24200,9 @@ local character_entities = { ["simlE"] = 10911, ["simgE"] = 10912, ["LessLess"] = 10913, + ["NotNestedLessLess"] = {10913, 824}, ["GreaterGreater"] = 10914, + ["NotNestedGreaterGreater"] = {10914, 824}, ["glj"] = 10916, ["gla"] = 10917, ["ltcc"] = 10918, @@ -22768,26 +24212,34 @@ local character_entities = { ["smt"] = 10922, ["lat"] = 10923, ["smte"] = 10924, + ["smtes"] = {10924, 65024}, ["late"] = 10925, + ["lates"] = {10925, 65024}, ["bumpE"] = 10926, + ["NotPrecedesEqual"] = {10927, 824}, + ["PrecedesEqual"] = 10927, + ["npre"] = {10927, 824}, + ["npreceq"] = {10927, 824}, ["pre"] = 10927, ["preceq"] = 10927, - ["PrecedesEqual"] = 10927, + ["NotSucceedsEqual"] = {10928, 824}, + ["SucceedsEqual"] = 10928, + ["nsce"] = {10928, 824}, + ["nsucceq"] = {10928, 824}, ["sce"] = 10928, ["succeq"] = 10928, - ["SucceedsEqual"] = 10928, ["prE"] = 10931, ["scE"] = 10932, - ["prnE"] = 10933, ["precneqq"] = 10933, + ["prnE"] = 10933, ["scnE"] = 10934, ["succneqq"] = 10934, ["prap"] = 10935, ["precapprox"] = 10935, ["scap"] = 10936, ["succapprox"] = 10936, - ["prnap"] = 10937, ["precnapprox"] = 10937, + ["prnap"] = 10937, ["scnap"] = 10938, ["succnapprox"] = 10938, ["Pr"] = 10939, @@ -22800,16 +24252,24 @@ local character_entities = { ["supmult"] = 10946, ["subedot"] = 10947, ["supedot"] = 10948, + ["nsubE"] = {10949, 824}, + ["nsubseteqq"] = {10949, 824}, ["subE"] = 10949, ["subseteqq"] = 10949, + ["nsupE"] = {10950, 824}, + ["nsupseteqq"] = {10950, 824}, ["supE"] = 10950, ["supseteqq"] = 10950, ["subsim"] = 10951, ["supsim"] = 10952, ["subnE"] = 10955, ["subsetneqq"] = 10955, + ["varsubsetneqq"] = {10955, 65024}, + ["vsubnE"] = {10955, 65024}, ["supnE"] = 10956, ["supsetneqq"] = 10956, + ["varsupsetneqq"] = {10956, 65024}, + ["vsupnE"] = {10956, 65024}, ["csub"] = 10959, ["csup"] = 10960, ["csube"] = 10961, @@ -22838,6 +24298,7 @@ local character_entities = { ["topcir"] = 10993, ["nhpar"] = 10994, ["parsim"] = 10995, + ["nparsl"] = {11005, 8421}, ["parsl"] = 11005, ["fflig"] = 64256, ["filig"] = 64257, @@ -22988,7 +24449,11 @@ local character_entities = { % \end{markdown} % \begin{macrocode} function entities.dec_entity(s) - return unicode.utf8.char(tonumber(s)) + local n = tonumber(s) + if n == nil then + return "&#" .. s .. ";" -- fallback for unknown entities + end + return unicode.utf8.char(n) end % \end{macrocode} % \par @@ -23001,7 +24466,28 @@ end % \end{markdown} % \begin{macrocode} function entities.hex_entity(s) - return unicode.utf8.char(tonumber("0x"..s)) + local n = tonumber("0x"..s) + if n == nil then + return "&#x" .. s .. ";" -- fallback for unknown entities + end + return unicode.utf8.char(n) +end +% \end{macrocode} +% \par +% \begin{markdown} +% +% Given a captured character `x` and a string `s` of hexadecimal digits, the +% \luamdef{entities.hex_entity_with_x_char} returns the corresponding +% \acro{utf}8-encoded Unicode codepoint or fallback with the `x` character. +% +% \end{markdown} +% \begin{macrocode} +function entities.hex_entity_with_x_char(x, s) + local n = tonumber("0x"..s) + if n == nil then + return "&#" .. x .. s .. ";" -- fallback for unknown entities + end + return unicode.utf8.char(n) end % \end{macrocode} % \par @@ -23014,11 +24500,18 @@ end % \end{markdown} % \begin{macrocode} function entities.char_entity(s) - local n = character_entities[s] - if n == nil then + local code_points = character_entities[s] + if code_points == nil then return "&" .. s .. ";" end - return unicode.utf8.char(n) + if type(code_points) ~= 'table' then + code_points = {code_points} + end + local char_table = {} + for _, code_point in ipairs(code_points) do + table.insert(char_table, unicode.utf8.char(code_point)) + end + return table.concat(char_table) end % \end{macrocode} % \par @@ -23073,6 +24566,18 @@ function M.writer.new(options) % \par % \begin{markdown} % +% Define \luamdef{writer->flatten\_inlines}, which indicates whether or not the +% writer should produce raw text rather than text in the output format for +% inline elements. The \luamref{writer->flatten\_inlines} member variable is +% mutable. +% +% \end{markdown} +% \begin{macrocode} + self.flatten_inlines = false +% \end{macrocode} +% \par +% \begin{markdown} +% % Parse the \Opt{slice} option and define \luamdef{writer->slice\_begin}, % \luamdef{writer->slice\_end}, and \luamdef{writer->is\_writing}. The % \luamref{writer->is\_writing} member variable is mutable. @@ -23192,12 +24697,43 @@ function M.writer.new(options) % \par % \begin{markdown} % -% Define \luamdef{writer->hard_line_break} as the output format of a forced +% Define \luamdef{writer->paragraphsep} as the output format of a paragraph +% separator. Users can use more than one blank line to delimit two blocks to +% indicate the end of a series of blocks that make up a paragraph. This +% produces a paragraph separator instead of an interblock separator. +% +% \end{markdown} +% \begin{macrocode} + function self.paragraphsep() + if not self.is_writing then return "" end + return "\\markdownRendererParagraphSeparator\n{}" + end +% \end{macrocode} +% \par +% \begin{markdown} +% +% Define \luamdef{writer->soft_line_break} as the output format of a soft % line break. % % \end{markdown} % \begin{macrocode} - self.hard_line_break = "\\markdownRendererHardLineBreak\n{}" + self.soft_line_break = function() + if self.flatten_inlines then return "\n" end + return "\\markdownRendererSoftLineBreak\n{}" + end +% \end{macrocode} +% \par +% \begin{markdown} +% +% Define \luamdef{writer->hard_line_break} as the output format of a hard +% line break. +% +% \end{markdown} +% \begin{macrocode} + self.hard_line_break = function() + if self.flatten_inlines then return "\n" end + return "\\markdownRendererHardLineBreak\n{}" + end % \end{macrocode} % \par % \begin{markdown} @@ -23290,11 +24826,18 @@ function M.writer.new(options) % % \end{markdown} % \begin{macrocode} - local escape_typographic_text = util.escaper( + local function create_escaper(char_escapes, string_escapes) + local escape = util.escaper(char_escapes, string_escapes) + return function(s) + if self.flatten_inlines then return s end + return escape(s) + end + end + local escape_typographic_text = create_escaper( self.escaped_chars, self.escaped_strings) - local escape_programmatic_text = util.escaper( + local escape_programmatic_text = create_escaper( self.escaped_uri_chars, self.escaped_minimal_strings) - local escape_minimal = util.escaper( + local escape_minimal = create_escaper( {}, self.escaped_minimal_strings) % \end{macrocode} % \par @@ -23310,6 +24853,7 @@ function M.writer.new(options) % - \luamdef{writer->math} transforms a math span. % - \luamdef{writer->identifier} transforms an input programmatic identifier. % - \luamdef{writer->uri} transforms an input \acro{uri}. +% - \luamdef{writer->infostring} transforms a fence code infostring. % % \end{markdown} % \begin{macrocode} @@ -23319,10 +24863,12 @@ function M.writer.new(options) self.identifier = escape_minimal self.string = escape_minimal self.uri = escape_minimal + self.infostring = escape_minimal else self.identifier = escape_programmatic_text self.string = escape_typographic_text self.uri = escape_programmatic_text + self.infostring = escape_programmatic_text end % \end{macrocode} % \par @@ -23335,6 +24881,7 @@ function M.writer.new(options) % \end{markdown} % \begin{macrocode} function self.code(s, attributes) + if self.flatten_inlines then return s end local buf = {} if attributes ~= nil then table.insert(buf, @@ -23361,6 +24908,7 @@ function M.writer.new(options) % \end{markdown} % \begin{macrocode} function self.link(lab, src, tit, attributes) + if self.flatten_inlines then return lab end local buf = {} if attributes ~= nil then table.insert(buf, @@ -23389,6 +24937,7 @@ function M.writer.new(options) % \end{markdown} % \begin{macrocode} function self.image(lab, src, tit, attributes) + if self.flatten_inlines then return lab end local buf = {} if attributes ~= nil then table.insert(buf, @@ -23419,7 +24968,9 @@ function M.writer.new(options) if not self.is_writing then return "" end local buffer = {} for _,item in ipairs(items) do - buffer[#buffer + 1] = self.bulletitem(item) + if item ~= "" then + buffer[#buffer + 1] = self.bulletitem(item) + end end local contents = util.intersperse(buffer,"\n") if tight and options.tightLists then @@ -23460,8 +25011,10 @@ function M.writer.new(options) local buffer = {} local num = startnum for _,item in ipairs(items) do - buffer[#buffer + 1] = self.ordereditem(item,num) - if num ~= nil then + if item ~= "" then + buffer[#buffer + 1] = self.ordereditem(item,num) + end + if num ~= nil and item ~= "" then num = num + 1 end end @@ -23504,27 +25057,13 @@ function M.writer.new(options) % \end{markdown} % \begin{macrocode} function self.inline_html_comment(contents) + if self.flatten_inlines then return contents end return {"\\markdownRendererInlineHtmlComment{",contents,"}"} end % \end{macrocode} % \par % \begin{markdown} % -% Define \luamdef{writer->block_html_comment} as a function that will -% transform the contents of a block \acro{HTML} comment, to the output -% format, where `contents` are the contents of the \acro{HTML} comment. -% -% \end{markdown} -% \begin{macrocode} - function self.block_html_comment(contents) - if not self.is_writing then return "" end - return {"\\markdownRendererBlockHtmlCommentBegin\n",contents, - "\n\\markdownRendererBlockHtmlCommentEnd "} - end -% \end{macrocode} -% \par -% \begin{markdown} -% % Define \luamdef{writer->inline_html_tag} as a function that will % transform the contents of an opening, closing, or empty inline \acro{HTML} % tag to the output format, where `contents` are the contents of the @@ -23533,6 +25072,7 @@ function M.writer.new(options) % \end{markdown} % \begin{macrocode} function self.inline_html_tag(contents) + if self.flatten_inlines then return contents end return {"\\markdownRendererInlineHtmlTag{",self.string(contents),"}"} end % \end{macrocode} @@ -23560,6 +25100,7 @@ function M.writer.new(options) % \end{markdown} % \begin{macrocode} function self.emphasis(s) + if self.flatten_inlines then return s end return {"\\markdownRendererEmphasis{",s,"}"} end % \end{macrocode} @@ -23590,6 +25131,7 @@ function M.writer.new(options) % \end{markdown} % \begin{macrocode} function self.strong(s) + if self.flatten_inlines then return s end return {"\\markdownRendererStrongEmphasis{",s,"}"} end % \end{macrocode} @@ -23602,7 +25144,7 @@ function M.writer.new(options) % \end{markdown} % \begin{macrocode} function self.blockquote(s) - if #util.rope_to_string(s) == 0 then return "" end + if not self.is_writing then return "" end return {"\\markdownRendererBlockQuoteBegin\n",s, "\n\\markdownRendererBlockQuoteEnd "} end @@ -23649,17 +25191,24 @@ function M.writer.new(options) % % \end{markdown} % \begin{macrocode} - function self.attributes(attributes) - local expanded_attributes = {} - local key_value_regex = "([^= ]+)%s*=%s*(.*)" + local seen_identifiers = {} + local key_value_regex = "([^= ]+)%s*=%s*(.*)" + local function normalize_attributes(attributes, auto_identifiers) + -- normalize attributes + local normalized_attributes = {} + local has_explicit_identifiers = false local key, value - for _, attribute in ipairs(attributes) do - if attribute:sub(1, 1) == "#" or attribute:sub(1, 1) == "." then - table.insert(expanded_attributes, attribute) + for _, attribute in ipairs(attributes or {}) do + if attribute:sub(1, 1) == "#" then + table.insert(normalized_attributes, attribute) + has_explicit_identifiers = true + seen_identifiers[attribute:sub(2)] = true + elseif attribute:sub(1, 1) == "." then + table.insert(normalized_attributes, attribute) else key, value = attribute:match(key_value_regex) if key:lower() == "id" then - table.insert(expanded_attributes, "#" .. value) + table.insert(normalized_attributes, "#" .. value) elseif key:lower() == "class" then local classes = {} for class in value:gmatch("%S+") do @@ -23667,23 +25216,67 @@ function M.writer.new(options) end table.sort(classes) for _, class in ipairs(classes) do - table.insert(expanded_attributes, "." .. class) + table.insert(normalized_attributes, "." .. class) end else - table.insert(expanded_attributes, attribute) + table.insert(normalized_attributes, attribute) end end end - table.sort(expanded_attributes) - local buf = {} - local seen = {} - for _, attribute in ipairs(expanded_attributes) do - if seen[attribute] ~= nil then - goto continue -- prevent duplicate attributes - else - seen[attribute] = true + -- if no explicit identifiers exist, add auto identifiers + if not has_explicit_identifiers and auto_identifiers ~= nil then + local seen_auto_identifiers = {} + for _, auto_identifier in ipairs(auto_identifiers) do + if seen_auto_identifiers[auto_identifier] == nil then + seen_auto_identifiers[auto_identifier] = true + if seen_identifiers[auto_identifier] == nil then + seen_identifiers[auto_identifier] = true + table.insert(normalized_attributes, + "#" .. auto_identifier) + else + local auto_identifier_number = 1 + while true do + local numbered_auto_identifier = auto_identifier .. "-" + .. auto_identifier_number + if seen_identifiers[numbered_auto_identifier] == nil then + seen_identifiers[numbered_auto_identifier] = true + table.insert(normalized_attributes, + "#" .. numbered_auto_identifier) + break + end + auto_identifier_number = auto_identifier_number + 1 + end + end + end + end + end + + -- sort and deduplicate normalized attributes + table.sort(normalized_attributes) + local seen_normalized_attributes = {} + local deduplicated_normalized_attributes = {} + for _, attribute in ipairs(normalized_attributes) do + if seen_normalized_attributes[attribute] == nil then + seen_normalized_attributes[attribute] = true + table.insert(deduplicated_normalized_attributes, attribute) end + end + + return deduplicated_normalized_attributes + end + + function self.attributes(attributes, should_normalize_attributes) + local normalized_attributes + if should_normalize_attributes == false then + normalized_attributes = attributes + else + normalized_attributes = normalize_attributes(attributes) + end + + local buf = {} + local key, value + for _, attribute in ipairs(normalized_attributes) do if attribute:sub(1, 1) == "#" then table.insert(buf, {"\\markdownRendererAttributeIdentifier{", attribute:sub(2), "}"}) @@ -23695,7 +25288,6 @@ function M.writer.new(options) table.insert(buf, {"\\markdownRendererAttributeKeyValue{", key, "}{", value, "}"}) end - ::continue:: end return buf @@ -23715,6 +25307,19 @@ function M.writer.new(options) % \par % \begin{markdown} % +% Define \luamdef{writer->attribute\_type\_levels} as a hash table that +% maps attribute types to the number of attributes of said type in +% \luamref{writer->active\_attributes}. +% +% \end{markdown} +% \begin{macrocode} + self.attribute_type_levels = {} + setmetatable(self.attribute_type_levels, + { __index = function() return 0 end }) +% \end{macrocode} +% \par +% \begin{markdown} +% % Define \luamdef{writer->push\_attributes} and % \luamdef{writer->pop\_attributes} as functions that will add a new set % of active block-level attributes or remove the most current attributes @@ -23759,6 +25364,9 @@ function M.writer.new(options) % \begin{macrocode} function self.push_attributes(attribute_type, attributes, start_output, end_output) + local attribute_type_level = self.attribute_type_levels[attribute_type] + self.attribute_type_levels[attribute_type] = attribute_type_level + 1 + -- index attributes in a hash table for easy lookup attributes = attributes or {} for i = 1, #attributes do @@ -23776,7 +25384,6 @@ function M.writer.new(options) end if attributes["#" .. self.slice_begin_identifier] ~= nil and self.slice_begin_type == "^" then - self.is_writing = true table.insert(buf, apply_attributes()) self.is_writing = true end @@ -23812,6 +25419,8 @@ function M.writer.new(options) local attributes, _, end_output current_attribute_type, attributes, _, end_output = table.unpack( self.active_attributes[#self.active_attributes]) + local attribute_type_level = self.attribute_type_levels[current_attribute_type] + self.attribute_type_levels[current_attribute_type] = attribute_type_level - 1 if self.is_writing and end_output ~= nil then table.insert(buf, end_output) end @@ -23833,6 +25442,119 @@ function M.writer.new(options) return buf end % \end{macrocode} +% \begin{markdown} +% +% Create an auto identifier string by stripping and converting characters from string `s`. +% +% \end{markdown} +% \begin{macrocode} +local function create_auto_identifier(s) + local buffer = {} + local prev_space = false + local letter_found = false + + for _, code in utf8.codes(uni_algos.normalize.NFC(s)) do + local char = utf8.char(code) + + -- Remove everything up to the first letter. + if not letter_found then + local is_letter = unicode.utf8.match(char, "%a") + if is_letter then + letter_found = true + else + goto continue + end + end + + -- Remove all non-alphanumeric characters, except underscores, hyphens, and periods. + if not unicode.utf8.match(char, "[%w_%-%.%s]") then + goto continue + end + + -- Replace all spaces and newlines with hyphens. + if unicode.utf8.match(char, "[%s\n]") then + char = "-" + if prev_space then + goto continue + else + prev_space = true + end + else + -- Convert all alphabetic characters to lowercase. + char = unicode.utf8.lower(char) + prev_space = false + end + + table.insert(buffer, char) + + ::continue:: + end + + if prev_space then + table.remove(buffer) + end + + local identifier = #buffer == 0 and "section" or table.concat(buffer, "") + return identifier +end +% \end{macrocode} +% \begin{markdown} +% +% Create an GitHub-flavored auto identifier string by stripping and converting characters from string `s`. +% +% \end{markdown} +% \begin{macrocode} +local function create_gfm_auto_identifier(s) + local buffer = {} + local prev_space = false + local letter_found = false + + for _, code in utf8.codes(uni_algos.normalize.NFC(s)) do + local char = utf8.char(code) + + -- Remove everything up to the first non-space. + if not letter_found then + local is_letter = unicode.utf8.match(char, "%S") + if is_letter then + letter_found = true + else + goto continue + end + end + + -- Remove all non-alphanumeric characters, except underscores and hyphens. + if not unicode.utf8.match(char, "[%w_%-%s]") then + prev_space = false + goto continue + end + + -- Replace all spaces and newlines with hyphens. + if unicode.utf8.match(char, "[%s\n]") then + char = "-" + if prev_space then + goto continue + else + prev_space = true + end + else + -- Convert all alphabetic characters to lowercase. + char = unicode.utf8.lower(char) + prev_space = false + end + + table.insert(buffer, char) + + ::continue:: + end + + if prev_space then + table.remove(buffer) + end + + local identifier = #buffer == 0 and "section" or table.concat(buffer, "") + return identifier +end +% \end{macrocode} % \par % \begin{markdown} % @@ -23842,47 +25564,52 @@ function M.writer.new(options) % % \end{markdown} % \begin{macrocode} - local current_heading_level = 0 function self.heading(s, level, attributes) local buf = {} + local flat_text, inlines = table.unpack(s) -- push empty attributes for implied sections - while current_heading_level < level - 1 do + while self.attribute_type_levels["heading"] < level - 1 do table.insert(buf, self.push_attributes("heading", nil, "\\markdownRendererSectionBegin\n", "\n\\markdownRendererSectionEnd ")) - current_heading_level = current_heading_level + 1 end -- pop attributes for sections that have ended - while current_heading_level >= level do + while self.attribute_type_levels["heading"] >= level do table.insert(buf, self.pop_attributes("heading")) - current_heading_level = current_heading_level - 1 end + -- construct attributes for the new section + local auto_identifiers = {} + if self.options.autoIdentifiers then + table.insert(auto_identifiers, create_auto_identifier(flat_text)) + end + if self.options.gfmAutoIdentifiers then + table.insert(auto_identifiers, create_gfm_auto_identifier(flat_text)) + end + local normalized_attributes = normalize_attributes(attributes, auto_identifiers) + -- push attributes for the new section local start_output = {} local end_output = {} table.insert(start_output, "\\markdownRendererSectionBegin\n") - if options.headerAttributes and attributes ~= nil and #attributes > 0 then - table.insert(start_output, - "\\markdownRendererHeaderAttributeContextBegin\n") - table.insert(start_output, self.attributes(attributes)) - table.insert(end_output, - "\n\\markdownRendererHeaderAttributeContextEnd ") - end table.insert(end_output, "\n\\markdownRendererSectionEnd ") table.insert(buf, self.push_attributes("heading", - attributes, + normalized_attributes, start_output, end_output)) - current_heading_level = current_heading_level + 1 - assert(current_heading_level == level) + assert(self.attribute_type_levels["heading"] == level) + + -- render the heading and its attributes + if self.is_writing and #normalized_attributes > 0 then + table.insert(buf, "\\markdownRendererHeaderAttributeContextBegin\n") + table.insert(buf, self.attributes(normalized_attributes, false)) + end - -- produce the renderer local cmd level = level + options.shiftHeadings if level <= 1 then @@ -23901,7 +25628,11 @@ function M.writer.new(options) cmd = "" end if self.is_writing then - table.insert(buf, {cmd, "{", s, "}"}) + table.insert(buf, {cmd, "{", inlines, "}"}) + end + + if self.is_writing and #normalized_attributes > 0 then + table.insert(buf, "\\markdownRendererHeaderAttributeContextEnd ") end return buf @@ -23919,6 +25650,7 @@ function M.writer.new(options) function self.get_state() return { is_writing=self.is_writing, + flatten_inlines=self.flatten_inlines, active_attributes={table.unpack(self.active_attributes)}, } end @@ -24013,14 +25745,13 @@ parsers.tilde = P("~") parsers.backslash = P("\\") parsers.tab = P("\t") parsers.newline = P("\n") -parsers.tightblocksep = P("\001") parsers.digit = R("09") parsers.hexdigit = R("09","af","AF") parsers.letter = R("AZ","az") parsers.alphanumeric = R("AZ","az","09") parsers.keyword = parsers.letter - * parsers.alphanumeric^0 + * (parsers.alphanumeric + parsers.dash)^0 parsers.internal_punctuation = S(":;,.?") parsers.doubleasterisks = P("**") @@ -24042,8 +25773,7 @@ parsers.nonspacechar = parsers.any - parsers.spacing parsers.optionalspace = parsers.spacechar^0 parsers.normalchar = parsers.any - (V("SpecialChar") - + parsers.spacing - + parsers.tightblocksep) + + parsers.spacing) parsers.eof = -parsers.any parsers.nonindentspace = parsers.space^-3 * - parsers.spacechar parsers.indent = parsers.space^-3 * parsers.tab @@ -24064,6 +25794,767 @@ parsers.spnl = parsers.optionalspace parsers.line = parsers.linechar^0 * parsers.newline parsers.nonemptyline = parsers.line - parsers.blankline % \end{macrocode} +% \par +% \begin{markdown} +% +%#### Parsers Used for Indentation +% +% \end{markdown} +% \begin{macrocode} + +parsers.leader = parsers.space^-3 + +% \end{macrocode} +% \begin{markdown} +% +% Check if a trail exists and is non-empty in the indent table `indent_table`. +% +% \end{markdown} +% \begin{macrocode} +local function has_trail(indent_table) + return indent_table ~= nil and + indent_table.trail ~= nil and + next(indent_table.trail) ~= nil +end + +% \end{macrocode} +% \begin{markdown} +% +% Check if indent table `indent_table` has any indents. +% +% \end{markdown} +% \begin{macrocode} +local function has_indents(indent_table) + return indent_table ~= nil and + indent_table.indents ~= nil and + next(indent_table.indents) ~= nil +end + +% \end{macrocode} +% \begin{markdown} +% +% Add a trail `trail_info` to the indent table `indent_table`. +% +% \end{markdown} +% \begin{macrocode} +local function add_trail(indent_table, trail_info) + indent_table.trail = trail_info + return indent_table +end + +% \end{macrocode} +% \begin{markdown} +% +% Remove a trail `trail_info` from the indent table `indent_table`. +% +% \end{markdown} +% \begin{macrocode} +local function remove_trail(indent_table) + indent_table.trail = nil + return indent_table +end + +% \end{macrocode} +% \begin{markdown} +% +% Update the indent table `indent_table` by adding or removing a new +% indent `add`. +% +% \end{markdown} +% \begin{macrocode} +local function update_indent_table(indent_table, new_indent, add) + indent_table = remove_trail(indent_table) + + if not has_indents(indent_table) then + indent_table.indents = {} + end + + + if add then + indent_table.indents[#indent_table.indents + 1] = new_indent + else + if indent_table.indents[#indent_table.indents].name == new_indent.name then + indent_table.indents[#indent_table.indents] = nil + end + end + + return indent_table +end + +% \end{macrocode} +% \begin{markdown} +% +% Remove an indent by its name `name`. +% +% \end{markdown} +% \begin{macrocode} +local function remove_indent(name) + local function remove_indent_level(s, i, indent_table) -- luacheck: ignore s i + indent_table = update_indent_table(indent_table, {name=name}, false) + return true, indent_table + end + + return Cg(Cmt(Cb("indent_info"), remove_indent_level), "indent_info") +end + +% \end{macrocode} +% \begin{markdown} +% +% Process the spacing of a string of spaces and tabs `spacing` with preceding indent width from +% the start of the line `indent` and strip up to `left_strip_length` spaces. Return the remainder +% `remainder` and whether there is enough spaces to produce a code `is_code`. Return how many +% spaces were stripped, as well as if the minimum was met `is_minimum` and what remainder it +% left `minimum_remainder`. +% +% \end{markdown} +% \begin{macrocode} +local function process_starter_spacing(indent, spacing, minimum, left_strip_length) + left_strip_length = left_strip_length or 0 + + local count = 0 + local tab_value = 4 - (indent) % 4 + + local code_started, minimum_found = false, false + local code_start, minimum_remainder = "", "" + + local left_total_stripped = 0 + local full_remainder = "" + + if spacing ~= nil then + for i = 1, #spacing do + local character = spacing:sub(i, i) + + if character == "\t" then + count = count + tab_value + tab_value = 4 + elseif character == " " then + count = count + 1 + tab_value = 4 - (1 - tab_value) % 4 + end + + if (left_strip_length ~= 0) then + local possible_to_strip = math.min(count, left_strip_length) + count = count - possible_to_strip + left_strip_length = left_strip_length - possible_to_strip + left_total_stripped = left_total_stripped + possible_to_strip + else + full_remainder = full_remainder .. character + end + + if (minimum_found) then + minimum_remainder = minimum_remainder .. character + elseif (count >= minimum) then + minimum_found = true + minimum_remainder = minimum_remainder .. string.rep(" ", count - minimum) + end + + if (code_started) then + code_start = code_start .. character + elseif (count >= minimum + 4) then + code_started = true + code_start = code_start .. string.rep(" ", count - (minimum + 4)) + end + end + end + + local remainder + if (code_started) then + remainder = code_start + else + remainder = string.rep(" ", count - minimum) + end + + local is_minimum = count >= minimum + return { + is_code = code_started, + remainder = remainder, + left_total_stripped = left_total_stripped, + is_minimum = is_minimum, + minimum_remainder = minimum_remainder, + total_length = count, + full_remainder = full_remainder + } +end + +% \end{macrocode} +% \begin{markdown} +% +% Count the total width of all indents in the indent table `indent_table`. +% +% \end{markdown} +% \begin{macrocode} +local function count_indent_tab_level(indent_table) + local count = 0 + if not has_indents(indent_table) then + return count + end + + for i=1, #indent_table.indents do + count = count + indent_table.indents[i].length + end + return count +end + +% \end{macrocode} +% \begin{markdown} +% +% Count the total width of a delimiter `delimiter`. +% +% \end{markdown} +% \begin{macrocode} +local function total_delimiter_length(delimiter) + local count = 0 + if type(delimiter) == "string" then return #delimiter end + for _, value in pairs(delimiter) do + count = count + total_delimiter_length(value) + end + return count +end + +% \end{macrocode} +% \begin{markdown} +% +% Process the container starter `starter` of a type `indent_type`. Adjust the width of the indent +% if the delimiter is followed only by whitespaces `is_blank`. +% +% \end{markdown} +% \begin{macrocode} +local function process_starter_indent(_, _, indent_table, starter, is_blank, indent_type, breakable) + local last_trail = starter[1] + local delimiter = starter[2] + local raw_new_trail = starter[3] + + if indent_type == "bq" and not breakable then + indent_table.ignore_blockquote_blank = true + end + + if has_trail(indent_table) then + local trail = indent_table.trail + if trail.is_code then + return false + end + last_trail = trail.remainder + else + local sp = process_starter_spacing(0, last_trail, 0, 0) + + if sp.is_code then + return false + end + last_trail = sp.remainder + end + + local preceding_indentation = count_indent_tab_level(indent_table) % 4 + local last_trail_length = #last_trail + local delimiter_length = total_delimiter_length(delimiter) + + local total_indent_level = preceding_indentation + last_trail_length + delimiter_length + + local sp = {} + if not is_blank then + sp = process_starter_spacing(total_indent_level, raw_new_trail, 0, 1) + end + + local del_trail_length = sp.left_total_stripped + if is_blank then + del_trail_length = 1 + elseif not sp.is_code then + del_trail_length = del_trail_length + #sp.remainder + end + + local indent_length = last_trail_length + delimiter_length + del_trail_length + local new_indent_info = {name=indent_type, length=indent_length} + + indent_table = update_indent_table(indent_table, new_indent_info, true) + indent_table = add_trail(indent_table, {is_code=sp.is_code, remainder=sp.remainder, total_length=sp.total_length, + full_remainder=sp.full_remainder}) + + return true, indent_table +end + +% \end{macrocode} +% \begin{markdown} +% +% Return the pattern corresponding with the indent name `name`. +% +% \end{markdown} +% \begin{macrocode} +local function decode_pattern(name) + local delimeter = parsers.succeed + if name == "bq" then + delimeter = parsers.more + end + + return C(parsers.optionalspace) * C(delimeter) * C(parsers.optionalspace) * Cp() +end + +% \end{macrocode} +% \begin{markdown} +% +% Find the first blank-only indent of the indent table `indent_table` followed +% by blank-only indents. +% +% \end{markdown} +% \begin{macrocode} +local function left_blank_starter(indent_table) + local blank_starter_index + + if not has_indents(indent_table) then + return + end + + for i = #indent_table.indents,1,-1 do + local value = indent_table.indents[i] + if value.name == "li" then + blank_starter_index = i + else + break + end + end + + return blank_starter_index +end + +% \end{macrocode} +% \begin{markdown} +% +% Apply the patterns decoded from the indents of the indent table `indent_table` +% iteratively starting at position `index` of the string `s`. If the `is_optional` +% mode is selected, match as many patterns as possible, else match all or fail. +% With the option `is_blank`, the parsing behaves as optional after the position +% of a blank-only indent has been surpassed. +% +% \end{markdown} +% \begin{macrocode} +local function traverse_indent(s, i, indent_table, is_optional, is_blank) + local new_index = i + + local preceding_indentation = 0 + local current_trail = {} + + local blank_starter = left_blank_starter(indent_table) + + for index = 1,#indent_table.indents do + local value = indent_table.indents[index] + local pattern = decode_pattern(value.name) + + -- match decoded pattern + local new_indent_info = lpeg.match(Ct(pattern), s, new_index) + if new_indent_info == nil then + local blankline_end = lpeg.match(Ct(parsers.blankline * Cg(Cp(), "pos")), s, new_index) + if is_optional or not indent_table.ignore_blockquote_blank or not blankline_end then + return is_optional, new_index, current_trail + end + + return traverse_indent(s, tonumber(blankline_end.pos), indent_table, is_optional, is_blank) + end + + local raw_last_trail = new_indent_info[1] + local delimiter = new_indent_info[2] + local raw_new_trail = new_indent_info[3] + local next_index = new_indent_info[4] + + local space_only = delimiter == "" + + -- check previous trail + if not space_only and next(current_trail) == nil then + local sp = process_starter_spacing(0, raw_last_trail, 0, 0) + current_trail = {is_code=sp.is_code, remainder=sp.remainder, total_length=sp.total_length, + full_remainder=sp.full_remainder} + end + + if next(current_trail) ~= nil then + if not space_only and current_trail.is_code then + return is_optional, new_index, current_trail + end + if current_trail.internal_remainder ~= nil then + raw_last_trail = current_trail.internal_remainder + end + end + + local raw_last_trail_length = 0 + local delimiter_length = 0 + + if not space_only then + delimiter_length = #delimiter + raw_last_trail_length = #raw_last_trail + end + + local total_indent_level = preceding_indentation + raw_last_trail_length + delimiter_length + + local spacing_to_process + local minimum = 0 + local left_strip_length = 0 + + if not space_only then + spacing_to_process = raw_new_trail + left_strip_length = 1 + else + spacing_to_process = raw_last_trail + minimum = value.length + end + + local sp = process_starter_spacing(total_indent_level, spacing_to_process, minimum, left_strip_length) + + if space_only and not sp.is_minimum then + return is_optional or (is_blank and blank_starter <= index), new_index, current_trail + end + + local indent_length = raw_last_trail_length + delimiter_length + sp.left_total_stripped + + -- update info for the next pattern + if not space_only then + preceding_indentation = preceding_indentation + indent_length + else + preceding_indentation = preceding_indentation + value.length + end + + current_trail = {is_code=sp.is_code, remainder=sp.remainder, internal_remainder=sp.minimum_remainder, + total_length=sp.total_length, full_remainder=sp.full_remainder} + new_index = next_index + end + + return true, new_index, current_trail +end + +% \end{macrocode} +% \begin{markdown} +% +% Check if a code trail is expected. +% +% \end{markdown} +% \begin{macrocode} +local function check_trail(expect_code, is_code) + return (expect_code and is_code) or (not expect_code and not is_code) +end + +% \end{macrocode} +% \begin{markdown} +% +% Check if the current trail of the `indent_table` would produce code if it is expected `expect_code` +% or it would not if it is not. If there is no trail, process and check the current spacing `spacing`. +% +% \end{markdown} +% \begin{macrocode} +local function check_trail_joined(s, i, indent_table, spacing, expect_code, omit_remainder) -- luacheck: ignore s i + local is_code + local remainder + + if has_trail(indent_table) then + local trail = indent_table.trail + is_code = trail.is_code + if is_code then + remainder = trail.remainder + else + remainder = trail.full_remainder + end + else + local sp = process_starter_spacing(0, spacing, 0, 0) + is_code = sp.is_code + if is_code then + remainder = sp.remainder + else + remainder = sp.full_remainder + end + end + + local result = check_trail(expect_code, is_code) + if omit_remainder then + return result + end + return result, remainder +end + +% \end{macrocode} +% \begin{markdown} +% +% Check if the current trail of the `indent_table` is of length between `min` and `max`. +% +% \end{markdown} +% \begin{macrocode} +local function check_trail_length(s, i, indent_table, spacing, min, max) -- luacheck: ignore s i + local trail + + if has_trail(indent_table) then + trail = indent_table.trail + else + trail = process_starter_spacing(0, spacing, 0, 0) + end + + local total_length = trail.total_length + if total_length == nil then + return false + end + + return min <= total_length and total_length <= max +end + +% \end{macrocode} +% \begin{markdown} +% +% Check the indentation of the continuation line, optionally with +% the mode `is_optional` selected. Check blank line exclusively with `is_blank`. +% +% \end{markdown} +% \begin{macrocode} +local function check_continuation_indentation(s, i, indent_table, is_optional, is_blank) + if not has_indents(indent_table) then + return true + end + + local passes, new_index, current_trail = traverse_indent(s, i, indent_table, is_optional, is_blank) + + if passes then + indent_table = add_trail(indent_table, current_trail) + return new_index, indent_table + end + return false +end + +% \end{macrocode} +% \begin{markdown} +% +% Get name of the last indent from the `indent_table`. +% +% \end{markdown} +% \begin{macrocode} +local function get_last_indent_name(indent_table) + if has_indents(indent_table) then + return indent_table.indents[#indent_table.indents].name + end +end + +% \end{macrocode} +% \begin{markdown} +% +% Remove the remainder altogether if the last indent from the `indent_table` +% is blank-only. +% +% \end{markdown} +% \begin{macrocode} +local function remove_remainder_if_blank(indent_table, remainder) + if get_last_indent_name(indent_table) == "li" then + return "" + end + return remainder +end + +% \end{macrocode} +% \begin{markdown} +% +% Take the trail `trail` or create a new one from `spacing` and comapre it +% with the expected `trail_type`. On success return the index `i` and the +% remainder of the trail. +% +% \end{markdown} +% \begin{macrocode} +local function check_trail_type(s, i, trail, spacing, trail_type) -- luacheck: ignore s i + if trail == nil then + trail = process_starter_spacing(0, spacing, 0, 0) + end + + if trail_type == "non-code" then + return check_trail(false, trail.is_code) + end + if trail_type == "code" then + return check_trail(true, trail.is_code) + end + if trail_type == "full-code" then + if (trail.is_code) then + return i, trail.remainder + end + return i, "" + end + if trail_type == "full-any" then + return i, trail.internal_remainder + end +end + +% \end{macrocode} +% \begin{markdown} +% +% Stores or restores an `is_freezing` trail from indent table `indent_table`. +% +% \end{markdown} +% \begin{macrocode} +local function trail_freezing(s, i, indent_table, is_freezing) -- luacheck: ignore s i + if is_freezing then + if indent_table.is_trail_frozen then + indent_table.trail = indent_table.frozen_trail + else + indent_table.frozen_trail = indent_table.trail + indent_table.is_trail_frozen = true + end + else + indent_table.frozen_trail = nil + indent_table.is_trail_frozen = false + end + return true, indent_table +end + +% \end{macrocode} +% \begin{markdown} +% +% Check the indentation of the continuation line, optionally with +% the mode `is_optional` selected. Check blank line specifically with `is_blank`. +% Additionally, also directly check the new trail with a type `trail_type`. +% +% \end{markdown} +% \begin{macrocode} +local function check_continuation_indentation_and_trail(s, i, indent_table, is_optional, is_blank, trail_type, + reset_rem, omit_remainder) + if not has_indents(indent_table) then + local spacing, new_index = lpeg.match(C(parsers.spacechar^0) * Cp(), s, i) + local result, remainder = check_trail_type(s, i, indent_table.trail, spacing, trail_type) + if remainder == nil then + if result then + return new_index + end + return false + end + if result then + return new_index, remainder + end + return false + end + + local passes, new_index, current_trail = traverse_indent(s, i, indent_table, is_optional, is_blank) + + if passes then + local spacing + if current_trail == nil then + local newer_spacing, newer_index = lpeg.match(C(parsers.spacechar^0) * Cp(), s, i) + current_trail = process_starter_spacing(0, newer_spacing, 0, 0) + new_index = newer_index + spacing = newer_spacing + else + spacing = current_trail.remainder + end + local result, remainder = check_trail_type(s, new_index, current_trail, spacing, trail_type) + if remainder == nil or omit_remainder then + if result then + return new_index + end + return false + end + + if is_blank and reset_rem then + remainder = remove_remainder_if_blank(indent_table, remainder) + end + if result then + return new_index, remainder + end + return false + end + return false +end + +% \end{macrocode} +% \begin{markdown} +% +% The following patterns check whitespace indentation at the start of a block. +% +% \end{markdown} +% \begin{macrocode} +parsers.check_trail = Cmt(Cb("indent_info") * C(parsers.spacechar^0) * Cc(false), check_trail_joined) + +parsers.check_trail_no_rem = Cmt(Cb("indent_info") * C(parsers.spacechar^0) * Cc(false) * Cc(true), check_trail_joined) + +parsers.check_code_trail = Cmt(Cb("indent_info") * C(parsers.spacechar^0) * Cc(true), check_trail_joined) + +parsers.check_trail_length_range = function(min, max) + return Cmt(Cb("indent_info") * C(parsers.spacechar^0) * Cc(min) * Cc(max), check_trail_length) +end + +parsers.check_trail_length = function(n) + return parsers.check_trail_length_range(n, n) +end + +% \end{macrocode} +% \begin{markdown} +% +% The following patterns handle trail backup, to prevent a failing pattern to modify it before +% passing it to the next. +% +% \end{markdown} +% \begin{macrocode} +parsers.freeze_trail = Cg(Cmt(Cb("indent_info") * Cc(true), trail_freezing), "indent_info") + +parsers.unfreeze_trail = Cg(Cmt(Cb("indent_info") * Cc(false), trail_freezing), "indent_info") + +% \end{macrocode} +% \begin{markdown} +% +% The following patterns check indentation in continuation lines as defined by the container start. +% +% \end{markdown} +% \begin{macrocode} +parsers.check_minimal_indent = Cmt(Cb("indent_info") * Cc(false), check_continuation_indentation) + +parsers.check_optional_indent = Cmt(Cb("indent_info") * Cc(true), check_continuation_indentation) + +parsers.check_minimal_blank_indent = Cmt(Cb("indent_info") * Cc(false) * Cc(true), check_continuation_indentation) + +% \end{macrocode} +% \begin{markdown} +% +% The following patterns check indentation in continuation lines as defined by the container start. +% Additionally the subsequent trail is also directly checked. +% +% \end{markdown} +% \begin{macrocode} + +parsers.check_minimal_indent_and_trail = Cmt( Cb("indent_info") + * Cc(false) * Cc(false) * Cc("non-code") * Cc(true), + check_continuation_indentation_and_trail) + +parsers.check_minimal_indent_and_code_trail = Cmt( Cb("indent_info") + * Cc(false) * Cc(false) * Cc("code") * Cc(false), + check_continuation_indentation_and_trail) + +parsers.check_minimal_blank_indent_and_full_code_trail = Cmt( Cb("indent_info") + * Cc(false) * Cc(true) * Cc("full-code") * Cc(true), + check_continuation_indentation_and_trail) + +parsers.check_minimal_indent_and_any_trail = Cmt( Cb("indent_info") + * Cc(false) * Cc(false) * Cc("full-any") * Cc(true) * Cc(false), + check_continuation_indentation_and_trail) + +parsers.check_minimal_blank_indent_and_any_trail = Cmt( Cb("indent_info") + * Cc(false) * Cc(true) * Cc("full-any") * Cc(true) * Cc(false), + check_continuation_indentation_and_trail) + +parsers.check_minimal_blank_indent_and_any_trail_no_rem = Cmt( Cb("indent_info") + * Cc(false) * Cc(true) * Cc("full-any") * Cc(true) * Cc(true), + check_continuation_indentation_and_trail) + +parsers.check_optional_indent_and_any_trail = Cmt( Cb("indent_info") + * Cc(true) * Cc(false) * Cc("full-any") * Cc(true) * Cc(false), + check_continuation_indentation_and_trail) + +parsers.check_optional_blank_indent_and_any_trail = Cmt( Cb("indent_info") + * Cc(true) * Cc(true) * Cc("full-any") * Cc(true) * Cc(false), + check_continuation_indentation_and_trail) + +% \end{macrocode} +% \begin{markdown} +% +% The following patterns specify behaviour around newlines. +% +% \end{markdown} +% \begin{macrocode} + +parsers.spnlc_noexc = parsers.optionalspace + * (parsers.newline * parsers.check_minimal_indent_and_any_trail)^-1 + +parsers.spnlc = parsers.optionalspace + * (V("EndlineNoSub"))^-1 + +parsers.spnlc_sep = parsers.optionalspace * V("EndlineNoSub") + + parsers.spacechar^1 + +parsers.only_blank = parsers.spacechar^0 * (parsers.newline + parsers.eof) + +% \end{macrocode} % \begin{figure} % \hspace*{-0.1\textwidth} % \begin{minipage}{1.2\textwidth} @@ -24150,6 +26641,7 @@ parsers.chunk = parsers.line * (parsers.optionallyindentedline - parsers.blankline)^0 parsers.attribute_key_char = parsers.alphanumeric + S("-_:.") +parsers.attribute_raw_char = parsers.alphanumeric + S("-_") parsers.attribute_key = (parsers.attribute_key_char - parsers.dash - parsers.digit) * parsers.attribute_key_char^0 @@ -24161,10 +26653,16 @@ parsers.attribute_value = ( (parsers.dquote / "") * (parsers.squote / "")) + ( parsers.anyescaped - parsers.dquote - parsers.rbrace - parsers.space)^0 +parsers.attribute_identifier = parsers.attribute_key_char^1 +parsers.attribute_classname = parsers.letter + * parsers.attribute_key_char^0 +parsers.attribute_raw = parsers.attribute_raw_char^1 parsers.attribute = (parsers.dash * Cc(".unnumbered")) - + C((parsers.hash + parsers.period) - * parsers.attribute_key) + + C( parsers.hash + * parsers.attribute_identifier) + + C( parsers.period + * parsers.attribute_classname) + Cs( parsers.attribute_key * parsers.optionalspace * parsers.equal * parsers.optionalspace * parsers.attribute_value) @@ -24180,7 +26678,7 @@ parsers.attributes = parsers.lbrace parsers.raw_attribute = parsers.lbrace * parsers.optionalspace * parsers.equal - * C(parsers.attribute_key) + * C(parsers.attribute_raw) * parsers.optionalspace * parsers.rbrace @@ -24195,22 +26693,43 @@ end % \par % \begin{markdown} % -%#### Parsers Used for Markdown Lists +%#### Parsers Used for HTML Entities % % \end{markdown} % \begin{macrocode} -parsers.bulletchar = C(parsers.plus + parsers.asterisk + parsers.dash) +local function repeat_between(pattern, min, max) + return -pattern^(max + 1) * pattern^min +end -parsers.bullet = ( parsers.bulletchar * #parsers.spacing - * (parsers.tab + parsers.space^-3) - + parsers.space * parsers.bulletchar * #parsers.spacing - * (parsers.tab + parsers.space^-2) - + parsers.space * parsers.space * parsers.bulletchar - * #parsers.spacing - * (parsers.tab + parsers.space^-1) - + parsers.space * parsers.space * parsers.space - * parsers.bulletchar * #parsers.spacing - ) +parsers.hexentity = parsers.ampersand * parsers.hash * C(S("Xx")) + * C(repeat_between(parsers.hexdigit, 1, 6)) * parsers.semicolon +parsers.decentity = parsers.ampersand * parsers.hash + * C(repeat_between(parsers.digit, 1, 7)) * parsers.semicolon +parsers.tagentity = parsers.ampersand * C(parsers.alphanumeric^1) + * parsers.semicolon + +parsers.html_entities = parsers.hexentity / entities.hex_entity_with_x_char + + parsers.decentity / entities.dec_entity + + parsers.tagentity / entities.char_entity +% \end{macrocode} +% \par +% \begin{markdown} +% +%#### Parsers Used for Markdown Lists +% +% \end{markdown} +% \begin{macrocode} +parsers.bullet = function(bullet_char, interrupting) + local allowed_end + if interrupting then + allowed_end = C(parsers.spacechar^1) * #parsers.linechar + else + allowed_end = C(parsers.spacechar^1) + #(parsers.newline + parsers.eof) + end + return parsers.check_trail + * Ct(C(bullet_char) * Cc("")) + * allowed_end +end local function tickbox(interior) return parsers.optionalspace * parsers.lbracket @@ -24235,17 +26754,399 @@ local function captures_equal_length(_,i,a,b) return #a == #b and i end -parsers.closeticks = parsers.space^-1 - * Cmt(C(parsers.backtick^1) - * Cb("ticks"), captures_equal_length) +parsers.closeticks = Cmt(C(parsers.backtick^1) + * Cb("ticks"), captures_equal_length) -parsers.intickschar = (parsers.any - S(" \n\r`")) - + (parsers.newline * -parsers.blankline) - + (parsers.space - parsers.closeticks) +parsers.intickschar = (parsers.any - S("\n\r`")) + + V("NoSoftLineBreakEndline") + (parsers.backtick^1 - parsers.closeticks) -parsers.inticks = parsers.openticks * parsers.space^-1 - * C(parsers.intickschar^0) * parsers.closeticks +local function process_inticks(s) + s = s:gsub("\n", " ") + s = s:gsub("^ (.*) $", "%1") + return s +end + +parsers.inticks = parsers.openticks + * C(parsers.space^0) + * parsers.closeticks + + parsers.openticks + * Cs(Cs(parsers.intickschar^0) / process_inticks) + * parsers.closeticks + +% \end{macrocode} +% \par +% \begin{markdown} +% +%#### Parsers Used for HTML +% +% \end{markdown} +% \begin{macrocode} +-- case-insensitive match (we assume s is lowercase). must be single byte encoding +parsers.keyword_exact = function(s) + local parser = P(0) + for i=1,#s do + local c = s:sub(i,i) + local m = c .. upper(c) + parser = parser * S(m) + end + return parser +end + +parsers.special_block_keyword = + parsers.keyword_exact("pre") + + parsers.keyword_exact("script") + + parsers.keyword_exact("style") + + parsers.keyword_exact("textarea") + +parsers.block_keyword = + parsers.keyword_exact("address") + + parsers.keyword_exact("article") + + parsers.keyword_exact("aside") + + parsers.keyword_exact("base") + + parsers.keyword_exact("basefont") + + parsers.keyword_exact("blockquote") + + parsers.keyword_exact("body") + + parsers.keyword_exact("caption") + + parsers.keyword_exact("center") + + parsers.keyword_exact("col") + + parsers.keyword_exact("colgroup") + + parsers.keyword_exact("dd") + + parsers.keyword_exact("details") + + parsers.keyword_exact("dialog") + + parsers.keyword_exact("dir") + + parsers.keyword_exact("div") + + parsers.keyword_exact("dl") + + parsers.keyword_exact("dt") + + parsers.keyword_exact("fieldset") + + parsers.keyword_exact("figcaption") + + parsers.keyword_exact("figure") + + parsers.keyword_exact("footer") + + parsers.keyword_exact("form") + + parsers.keyword_exact("frame") + + parsers.keyword_exact("frameset") + + parsers.keyword_exact("h1") + + parsers.keyword_exact("h2") + + parsers.keyword_exact("h3") + + parsers.keyword_exact("h4") + + parsers.keyword_exact("h5") + + parsers.keyword_exact("h6") + + parsers.keyword_exact("head") + + parsers.keyword_exact("header") + + parsers.keyword_exact("hr") + + parsers.keyword_exact("html") + + parsers.keyword_exact("iframe") + + parsers.keyword_exact("legend") + + parsers.keyword_exact("li") + + parsers.keyword_exact("link") + + parsers.keyword_exact("main") + + parsers.keyword_exact("menu") + + parsers.keyword_exact("menuitem") + + parsers.keyword_exact("nav") + + parsers.keyword_exact("noframes") + + parsers.keyword_exact("ol") + + parsers.keyword_exact("optgroup") + + parsers.keyword_exact("option") + + parsers.keyword_exact("p") + + parsers.keyword_exact("param") + + parsers.keyword_exact("section") + + parsers.keyword_exact("source") + + parsers.keyword_exact("summary") + + parsers.keyword_exact("table") + + parsers.keyword_exact("tbody") + + parsers.keyword_exact("td") + + parsers.keyword_exact("tfoot") + + parsers.keyword_exact("th") + + parsers.keyword_exact("thead") + + parsers.keyword_exact("title") + + parsers.keyword_exact("tr") + + parsers.keyword_exact("track") + + parsers.keyword_exact("ul") + +-- end conditions +parsers.html_blankline_end_condition = parsers.linechar^0 + * ( parsers.newline + * (parsers.check_minimal_blank_indent_and_any_trail + * #parsers.blankline + + parsers.check_minimal_indent_and_any_trail) + * parsers.linechar^1)^0 + * (parsers.newline^-1 / "") + +local function remove_trailing_blank_lines(s) + return s:gsub("[\n\r]+%s*$", "") +end + +parsers.html_until_end = function(end_marker) + return Cs(Cs((parsers.newline + * (parsers.check_minimal_blank_indent_and_any_trail + * #parsers.blankline + + parsers.check_minimal_indent_and_any_trail) + + parsers.linechar - end_marker)^0 + * parsers.linechar^0 * parsers.newline^-1) + / remove_trailing_blank_lines) +end + +-- attributes +parsers.html_attribute_spacing = parsers.optionalspace + * V("NoSoftLineBreakEndline") + * parsers.optionalspace + + parsers.spacechar^1 + +parsers.html_attribute_name = (parsers.letter + parsers.colon + parsers.underscore) + * (parsers.alphanumeric + parsers.colon + parsers.underscore + + parsers.period + parsers.dash)^0 + +parsers.html_attribute_value = parsers.squote + * (parsers.linechar - parsers.squote)^0 + * parsers.squote + + parsers.dquote + * (parsers.linechar - parsers.dquote)^0 + * parsers.dquote + + ( parsers.any - parsers.spacechar - parsers.newline + - parsers.dquote - parsers.squote - parsers.backtick + - parsers.equal - parsers.less - parsers.more)^1 + +parsers.html_inline_attribute_value = parsers.squote + * (V("NoSoftLineBreakEndline") + + parsers.any + - parsers.blankline^2 + - parsers.squote)^0 + * parsers.squote + + parsers.dquote + * (V("NoSoftLineBreakEndline") + + parsers.any + - parsers.blankline^2 + - parsers.dquote)^0 + * parsers.dquote + + (parsers.any - parsers.spacechar - parsers.newline + - parsers.dquote - parsers.squote - parsers.backtick + - parsers.equal - parsers.less - parsers.more)^1 + +parsers.html_attribute_value_specification = parsers.optionalspace + * parsers.equal + * parsers.optionalspace + * parsers.html_attribute_value + +parsers.html_spnl = parsers.optionalspace + * (V("NoSoftLineBreakEndline") * parsers.optionalspace)^-1 + +parsers.html_inline_attribute_value_specification = parsers.html_spnl + * parsers.equal + * parsers.html_spnl + * parsers.html_inline_attribute_value + +parsers.html_attribute = parsers.html_attribute_spacing + * parsers.html_attribute_name + * parsers.html_inline_attribute_value_specification^-1 + +parsers.html_non_newline_attribute = parsers.spacechar^1 + * parsers.html_attribute_name + * parsers.html_attribute_value_specification^-1 + +parsers.nested_breaking_blank = parsers.newline + * parsers.check_minimal_blank_indent + * parsers.blankline + +parsers.html_comment_start = P("<!--") + +parsers.html_comment_end = P("-->") + +parsers.html_comment = Cs( parsers.html_comment_start + * parsers.html_until_end(parsers.html_comment_end)) + +parsers.html_inline_comment = (parsers.html_comment_start / "") + * -P(">") * -P("->") + * Cs((V("NoSoftLineBreakEndline") + parsers.any - P("--") + - parsers.nested_breaking_blank - parsers.html_comment_end)^0) + * (parsers.html_comment_end / "") + +parsers.html_cdatasection_start = P("<![CDATA[") + +parsers.html_cdatasection_end = P("]]>") + +parsers.html_cdatasection = Cs( parsers.html_cdatasection_start + * parsers.html_until_end(parsers.html_cdatasection_end)) + +parsers.html_inline_cdatasection = parsers.html_cdatasection_start + * Cs(V("NoSoftLineBreakEndline") + parsers.any + - parsers.nested_breaking_blank - parsers.html_cdatasection_end)^0 + * parsers.html_cdatasection_end + +parsers.html_declaration_start = P("<!") * parsers.letter + +parsers.html_declaration_end = P(">") + +parsers.html_declaration = Cs( parsers.html_declaration_start + * parsers.html_until_end(parsers.html_declaration_end)) + +parsers.html_inline_declaration = parsers.html_declaration_start + * Cs(V("NoSoftLineBreakEndline") + parsers.any + - parsers.nested_breaking_blank - parsers.html_declaration_end)^0 + * parsers.html_declaration_end + +parsers.html_instruction_start = P("<?") + +parsers.html_instruction_end = P("?>") + +parsers.html_instruction = Cs( parsers.html_instruction_start + * parsers.html_until_end(parsers.html_instruction_end)) + +parsers.html_inline_instruction = parsers.html_instruction_start + * Cs(V("NoSoftLineBreakEndline") + parsers.any + - parsers.nested_breaking_blank - parsers.html_instruction_end)^0 + * parsers.html_instruction_end + +parsers.html_blankline = parsers.newline + * parsers.optionalspace + * parsers.newline + +parsers.html_tag_start = parsers.less + +parsers.html_tag_closing_start = parsers.less + * parsers.slash + +parsers.html_tag_end = parsers.html_spnl + * parsers.more + +parsers.html_empty_tag_end = parsers.html_spnl + * parsers.slash + * parsers.more + +-- opening tags +parsers.html_any_open_inline_tag = parsers.html_tag_start + * parsers.keyword + * parsers.html_attribute^0 + * parsers.html_tag_end + +parsers.html_any_open_tag = parsers.html_tag_start + * parsers.keyword + * parsers.html_non_newline_attribute^0 + * parsers.html_tag_end + +parsers.html_open_tag = parsers.html_tag_start + * parsers.block_keyword + * parsers.html_attribute^0 + * parsers.html_tag_end + +parsers.html_open_special_tag = parsers.html_tag_start + * parsers.special_block_keyword + * parsers.html_attribute^0 + * parsers.html_tag_end + +-- incomplete tags +parsers.incomplete_tag_following = parsers.spacechar + + parsers.more + + parsers.slash * parsers.more + + #(parsers.newline + parsers.eof) + +parsers.incomplete_special_tag_following = parsers.spacechar + + parsers.more + + #(parsers.newline + parsers.eof) + +parsers.html_incomplete_open_tag = parsers.html_tag_start + * parsers.block_keyword + * parsers.incomplete_tag_following + +parsers.html_incomplete_open_special_tag = parsers.html_tag_start + * parsers.special_block_keyword + * parsers.incomplete_special_tag_following + +parsers.html_incomplete_close_tag = parsers.html_tag_closing_start + * parsers.block_keyword + * parsers.incomplete_tag_following + +parsers.html_incomplete_close_special_tag = parsers.html_tag_closing_start + * parsers.special_block_keyword + * parsers.incomplete_tag_following + +-- closing tags +parsers.html_close_tag = parsers.html_tag_closing_start + * parsers.block_keyword + * parsers.html_tag_end + +parsers.html_any_close_tag = parsers.html_tag_closing_start + * parsers.keyword + * parsers.html_tag_end + +parsers.html_close_special_tag = parsers.html_tag_closing_start + * parsers.special_block_keyword + * parsers.html_tag_end + +-- empty tags +parsers.html_any_empty_inline_tag = parsers.html_tag_start + * parsers.keyword + * parsers.html_attribute^0 + * parsers.html_empty_tag_end + +parsers.html_any_empty_tag = parsers.html_tag_start + * parsers.keyword + * parsers.html_non_newline_attribute^0 + * parsers.optionalspace + * parsers.slash + * parsers.more + +parsers.html_empty_tag = parsers.html_tag_start + * parsers.block_keyword + * parsers.html_attribute^0 + * parsers.html_empty_tag_end + +parsers.html_empty_special_tag = parsers.html_tag_start + * parsers.special_block_keyword + * parsers.html_attribute^0 + * parsers.html_empty_tag_end + +parsers.html_incomplete_blocks = parsers.html_incomplete_open_tag + + parsers.html_incomplete_open_special_tag + + parsers.html_incomplete_close_tag + +-- parse special html blocks +parsers.html_blankline_ending_special_block_opening = (parsers.html_close_special_tag + + parsers.html_empty_special_tag) + * #(parsers.optionalspace + * (parsers.newline + parsers.eof)) + +parsers.html_blankline_ending_special_block = parsers.html_blankline_ending_special_block_opening + * parsers.html_blankline_end_condition + +parsers.html_special_block_opening = parsers.html_incomplete_open_special_tag + - parsers.html_empty_special_tag + +parsers.html_closing_special_block = parsers.html_special_block_opening + * parsers.html_until_end(parsers.html_close_special_tag) + +parsers.html_special_block = parsers.html_blankline_ending_special_block + + parsers.html_closing_special_block + +-- parse html blocks +parsers.html_block_opening = parsers.html_incomplete_open_tag + + parsers.html_incomplete_close_tag + +parsers.html_block = parsers.html_block_opening + * parsers.html_blankline_end_condition + +-- parse any html blocks +parsers.html_any_block_opening = (parsers.html_any_open_tag + + parsers.html_any_close_tag + + parsers.html_any_empty_tag) + * #(parsers.optionalspace * (parsers.newline + parsers.eof)) + +parsers.html_any_block = parsers.html_any_block_opening + * parsers.html_blankline_end_condition + +parsers.html_inline_comment_full = parsers.html_comment_start + * -P(">") * -P("->") + * Cs((V("NoSoftLineBreakEndline") + parsers.any - P("--") + - parsers.nested_breaking_blank - parsers.html_comment_end)^0) + * parsers.html_comment_end + +parsers.html_inline_tags = parsers.html_inline_comment_full + + parsers.html_any_empty_inline_tag + + parsers.html_inline_instruction + + parsers.html_inline_cdatasection + + parsers.html_inline_declaration + + parsers.html_any_open_inline_tag + + parsers.html_any_close_tag + % \end{macrocode} % \par % \begin{markdown} @@ -24254,7 +27155,54 @@ parsers.inticks = parsers.openticks * parsers.space^-1 % % \end{markdown} % \begin{macrocode} -parsers.leader = parsers.space^-3 +parsers.urlchar = parsers.anyescaped + - parsers.newline + - parsers.more + +parsers.auto_link_scheme_part = parsers.alphanumeric + + parsers.plus + + parsers.period + + parsers.dash + +parsers.auto_link_scheme = parsers.letter + * parsers.auto_link_scheme_part + * parsers.auto_link_scheme_part^-30 + +parsers.absolute_uri = parsers.auto_link_scheme * parsers.colon + * (parsers.any - parsers.spacing - parsers.less - parsers.more)^0 + +parsers.printable_characters = S(".!#$%&'*+/=?^_`{|}~-") + +parsers.email_address_local_part_char = parsers.alphanumeric + + parsers.printable_characters + +parsers.email_address_local_part = parsers.email_address_local_part_char^1 + +parsers.email_address_dns_label = parsers.alphanumeric + * (parsers.alphanumeric + parsers.dash)^-62 + * B(parsers.alphanumeric) + +parsers.email_address_domain = parsers.email_address_dns_label + * (parsers.period * parsers.email_address_dns_label)^0 + +parsers.email_address = parsers.email_address_local_part + * parsers.at + * parsers.email_address_domain + +parsers.auto_link_url = parsers.less + * C(parsers.absolute_uri) + * parsers.more + +parsers.auto_link_email = parsers.less + * C(parsers.email_address) + * parsers.more + +parsers.auto_link_relative_reference = parsers.less + * C(parsers.urlchar^1) + * parsers.more + +parsers.autolink = parsers.auto_link_url + + parsers.auto_link_email -- content in balanced brackets, parentheses, or quotes: parsers.bracketed = P{ parsers.lbracket @@ -24284,183 +27232,96 @@ parsers.dquoted = P{ parsers.dquote * parsers.alphanumeric ) + V(1))^0 * parsers.dquote } --- bracketed tag for markdown links, allowing nested brackets: -parsers.tag = parsers.lbracket +parsers.link_text = parsers.lbracket * Cs((parsers.alphanumeric^1 - + parsers.bracketed - + parsers.inticks - + ( parsers.backslash / "" * parsers.rbracket - + parsers.any - - (parsers.rbracket + parsers.blankline^2)))^0) + + parsers.bracketed + + parsers.inticks + + parsers.autolink + + V("InlineHtml") + + ( parsers.backslash * parsers.backslash) + + ( parsers.backslash * (parsers.lbracket + parsers.rbracket) + + V("NoSoftLineBreakSpace") + + V("NoSoftLineBreakEndline") + + (parsers.any + - (parsers.newline + parsers.lbracket + parsers.rbracket + parsers.blankline^2))))^0) * parsers.rbracket +parsers.link_label = parsers.lbracket + * -#(parsers.sp * parsers.rbracket) + * #((parsers.any - parsers.rbracket)^-999 * parsers.rbracket) + * Cs((parsers.alphanumeric^1 + + parsers.inticks + + parsers.autolink + + V("InlineHtml") + + ( parsers.backslash * parsers.backslash) + + ( parsers.backslash * (parsers.lbracket + parsers.rbracket) + + V("NoSoftLineBreakSpace") + + V("NoSoftLineBreakEndline") + + (parsers.any + - (parsers.newline + parsers.lbracket + parsers.rbracket + parsers.blankline^2))))^1) + * parsers.rbracket + +parsers.inparens_url = P{ parsers.lparent + * ((parsers.anyescaped - (parsers.lparent + + parsers.rparent + + parsers.spacing) + ) + V(1))^0 + * parsers.rparent } + -- url for markdown links, allowing nested brackets: parsers.url = parsers.less * Cs((parsers.anyescaped + - parsers.newline + - parsers.less - parsers.more)^0) * parsers.more - + Cs((parsers.inparens + (parsers.anyescaped - - parsers.spacing - - parsers.rparent))^1) - --- quoted text, possibly with nested quotes: -parsers.title_s = parsers.squote * Cs(((parsers.anyescaped-parsers.squote) - + parsers.squoted)^0) - * parsers.squote - -parsers.title_d = parsers.dquote * Cs(((parsers.anyescaped-parsers.dquote) - + parsers.dquoted)^0) - * parsers.dquote + + -parsers.less + * Cs((parsers.inparens_url + (parsers.anyescaped + - parsers.spacing + - parsers.lparent + - parsers.rparent))^1) + +-- quoted text: +parsers.title_s = parsers.squote + * Cs((parsers.html_entities + + V("NoSoftLineBreakSpace") + + V("NoSoftLineBreakEndline") + + (parsers.anyescaped - parsers.newline - parsers.squote - parsers.blankline^2))^0) + * parsers.squote + +parsers.title_d = parsers.dquote + * Cs((parsers.html_entities + + V("NoSoftLineBreakSpace") + + V("NoSoftLineBreakEndline") + + (parsers.anyescaped - parsers.newline - parsers.dquote - parsers.blankline^2))^0) + * parsers.dquote parsers.title_p = parsers.lparent - * Cs((parsers.inparens + (parsers.anyescaped-parsers.rparent))^0) + * Cs((parsers.html_entities + + V("NoSoftLineBreakSpace") + + V("NoSoftLineBreakEndline") + + (parsers.anyescaped - parsers.newline - parsers.lparent - parsers.rparent + - parsers.blankline^2))^0) * parsers.rparent parsers.title = parsers.title_d + parsers.title_s + parsers.title_p parsers.optionaltitle - = parsers.spnl * parsers.title * parsers.spacechar^0 + = parsers.spnlc * parsers.title * parsers.spacechar^0 + Cc("") -parsers.indirect_link - = parsers.tag - * ( C(parsers.spnl) * parsers.tag - + Cc(nil) * Cc(nil) -- always produce exactly two captures - ) - -parsers.indirect_image - = parsers.exclamation * parsers.indirect_link % \end{macrocode} % \par % \begin{markdown} % -%#### Parsers Used for HTML -% -% \end{markdown} -% \begin{macrocode} --- case-insensitive match (we assume s is lowercase). must be single byte encoding -parsers.keyword_exact = function(s) - local parser = P(0) - for i=1,#s do - local c = s:sub(i,i) - local m = c .. upper(c) - parser = parser * S(m) - end - return parser -end - -parsers.block_keyword = - parsers.keyword_exact("address") + parsers.keyword_exact("blockquote") + - parsers.keyword_exact("center") + parsers.keyword_exact("del") + - parsers.keyword_exact("dir") + parsers.keyword_exact("div") + - parsers.keyword_exact("p") + parsers.keyword_exact("pre") + - parsers.keyword_exact("li") + parsers.keyword_exact("ol") + - parsers.keyword_exact("ul") + parsers.keyword_exact("dl") + - parsers.keyword_exact("dd") + parsers.keyword_exact("form") + - parsers.keyword_exact("fieldset") + parsers.keyword_exact("isindex") + - parsers.keyword_exact("ins") + parsers.keyword_exact("menu") + - parsers.keyword_exact("noframes") + parsers.keyword_exact("frameset") + - parsers.keyword_exact("h1") + parsers.keyword_exact("h2") + - parsers.keyword_exact("h3") + parsers.keyword_exact("h4") + - parsers.keyword_exact("h5") + parsers.keyword_exact("h6") + - parsers.keyword_exact("hr") + parsers.keyword_exact("script") + - parsers.keyword_exact("noscript") + parsers.keyword_exact("table") + - parsers.keyword_exact("tbody") + parsers.keyword_exact("tfoot") + - parsers.keyword_exact("thead") + parsers.keyword_exact("th") + - parsers.keyword_exact("td") + parsers.keyword_exact("tr") - --- There is no reason to support bad html, so we expect quoted attributes -parsers.htmlattributevalue - = parsers.squote * (parsers.any - (parsers.blankline - + parsers.squote))^0 - * parsers.squote - + parsers.dquote * (parsers.any - (parsers.blankline - + parsers.dquote))^0 - * parsers.dquote - -parsers.htmlattribute = parsers.spacing^1 - * (parsers.alphanumeric + S("_-"))^1 - * parsers.sp * parsers.equal * parsers.sp - * parsers.htmlattributevalue - -parsers.htmlcomment = P("<!--") - * parsers.optionalspace - * Cs((parsers.any - parsers.optionalspace * P("-->"))^0) - * parsers.optionalspace - * P("-->") - -parsers.htmlinstruction = P("<?") * (parsers.any - P("?>"))^0 * P("?>") - -parsers.openelt_any = parsers.less * parsers.keyword * parsers.htmlattribute^0 - * parsers.sp * parsers.more - -parsers.openelt_exact = function(s) - return parsers.less * parsers.sp * parsers.keyword_exact(s) - * parsers.htmlattribute^0 * parsers.sp * parsers.more -end - -parsers.openelt_block = parsers.sp * parsers.block_keyword - * parsers.htmlattribute^0 * parsers.sp * parsers.more - -parsers.closeelt_any = parsers.less * parsers.sp * parsers.slash - * parsers.keyword * parsers.sp * parsers.more - -parsers.closeelt_exact = function(s) - return parsers.less * parsers.sp * parsers.slash * parsers.keyword_exact(s) - * parsers.sp * parsers.more -end - -parsers.emptyelt_any = parsers.less * parsers.sp * parsers.keyword - * parsers.htmlattribute^0 * parsers.sp * parsers.slash - * parsers.more - -parsers.emptyelt_block = parsers.less * parsers.sp * parsers.block_keyword - * parsers.htmlattribute^0 * parsers.sp * parsers.slash - * parsers.more - -parsers.displaytext = (parsers.any - parsers.less)^1 - --- return content between two matched HTML tags -parsers.in_matched = function(s) - return { parsers.openelt_exact(s) - * (V(1) + parsers.displaytext - + (parsers.less - parsers.closeelt_exact(s)))^0 - * parsers.closeelt_exact(s) } -end - -local function parse_matched_tags(s,pos) - local t = string.lower(lpeg.match(C(parsers.keyword),s,pos)) - return lpeg.match(parsers.in_matched(t),s,pos-1) -end - -parsers.in_matched_block_tags = parsers.less - * Cmt(#parsers.openelt_block, parse_matched_tags) - -% \end{macrocode} -% \par -% \begin{markdown} -% -%#### Parsers Used for HTML Entities -% -% \end{markdown} -% \begin{macrocode} -parsers.hexentity = parsers.ampersand * parsers.hash * S("Xx") - * C(parsers.hexdigit^1) * parsers.semicolon -parsers.decentity = parsers.ampersand * parsers.hash - * C(parsers.digit^1) * parsers.semicolon -parsers.tagentity = parsers.ampersand * C(parsers.alphanumeric^1) - * parsers.semicolon -% \end{macrocode} -% \par -% \begin{markdown} -% -%#### Helpers for Link Reference Definitions +%#### Helpers for Links and Link Reference Definitions % % \end{markdown} % \begin{macrocode} -- parse a reference definition: [foo]: /bar "title" -parsers.define_reference_parser = parsers.leader * parsers.tag * parsers.colon - * parsers.spacechar^0 * parsers.url - * parsers.optionaltitle +parsers.define_reference_parser = (parsers.check_trail / "") * parsers.link_label * parsers.colon + * parsers.spnlc * parsers.url + * ( parsers.spnlc_sep * parsers.title * parsers.only_blank + + Cc("") * parsers.only_blank) % \end{macrocode} % \par % \begin{markdown} @@ -24470,7 +27331,6 @@ parsers.define_reference_parser = parsers.leader * parsers.tag * parsers.colon % \end{markdown} % \begin{macrocode} parsers.Inline = V("Inline") -parsers.IndentedInline = V("IndentedInline") -- parse many p between starter and ender parsers.between = function(p, starter, ender) @@ -24478,26 +27338,6 @@ parsers.between = function(p, starter, ender) return (starter * #parsers.nonspacechar * Ct(p * (p - ender2)^0) * ender2) end -parsers.urlchar = parsers.anyescaped - - parsers.newline - - parsers.more - -parsers.auto_link_url = parsers.less - * C( parsers.alphanumeric^1 * P("://") - * parsers.urlchar^1) - * parsers.more - -parsers.auto_link_email - = parsers.less - * C((parsers.alphanumeric + S("-._+"))^1 - * P("@") * parsers.urlchar^1) - * parsers.more - -parsers.auto_link_relative_reference - = parsers.less - * C(parsers.urlchar^1) - * parsers.more - % \end{macrocode} % \par % \begin{markdown} @@ -24507,10 +27347,13 @@ parsers.auto_link_relative_reference % \end{markdown} % \begin{macrocode} parsers.lineof = function(c) - return (parsers.leader * (P(c) * parsers.optionalspace)^3 - * (parsers.newline * parsers.blankline^1 - + parsers.newline^-1 * parsers.eof)) + return (parsers.check_trail_no_rem * (P(c) * parsers.optionalspace)^3 + * (parsers.newline + parsers.eof)) end + +parsers.thematic_break_lines = parsers.lineof(parsers.asterisk) + + parsers.lineof(parsers.dash) + + parsers.lineof(parsers.underscore) % \end{macrocode} % \par % \begin{markdown} @@ -24524,11 +27367,21 @@ parsers.heading_start = #parsers.hash * C(parsers.hash^-6) * -parsers.hash / length -- parse setext header ending and return level -parsers.heading_level = parsers.equal^1 * Cc(1) + parsers.dash^1 * Cc(2) +parsers.heading_level = parsers.nonindentspace * parsers.equal^1 * parsers.optionalspace * #parsers.newline * Cc(1) + + parsers.nonindentspace * parsers.dash^1 * parsers.optionalspace * #parsers.newline * Cc(2) local function strip_atx_end(s) - return s:gsub("[#%s]*\n$","") + return s:gsub("%s+#*%s*\n$","") end + +parsers.atx_heading = parsers.check_trail_no_rem + * Cg(parsers.heading_start, "level") + * (C( parsers.optionalspace + * parsers.hash^0 + * parsers.optionalspace + * parsers.newline) + + parsers.spacechar^1 + * C(parsers.line)) % \end{macrocode} % \par % \begin{markdown} @@ -24610,7 +27463,7 @@ function M.reader.new(writer, options) tag = util.rope_to_string(tag) tag = tag:gsub("[ \n\r\t]+", " ") tag = tag:gsub("^ ", ""):gsub(" $", "") - tag = uni_case.casefold(tag, true, false) + tag = uni_algos.case.casefold(tag, true, false) return tag end % \end{macrocode} @@ -24733,11 +27586,6 @@ function M.reader.new(writer, options) return parsers.inlines end, false) - self.create_parser("parse_inlines_no_link", - function() - return parsers.inlines_no_link - end, false) - self.create_parser("parse_inlines_no_inline_note", function() return parsers.inlines_no_inline_note @@ -24752,6 +27600,134 @@ function M.reader.new(writer, options) function() return parsers.inlines_nbsp end, false) + self.create_parser("parse_inlines_no_link_or_emphasis", + function() + return parsers.inlines_no_link_or_emphasis + end, false) +% \end{macrocode} +% \par +% \begin{markdown} +% +%#### Parsers Used for Indentation (local) +% +% The following patterns represent basic building blocks of indented content. +% +% \end{markdown} +% \begin{macrocode} + parsers.minimally_indented_blankline = parsers.check_minimal_indent * (parsers.blankline / "") + + parsers.minimally_indented_block = parsers.check_minimal_indent * V("Block") + + parsers.minimally_indented_block_or_paragraph = parsers.check_minimal_indent * V("BlockOrParagraph") + + parsers.minimally_indented_paragraph = parsers.check_minimal_indent * V("Paragraph") + + parsers.minimally_indented_plain = parsers.check_minimal_indent * V("Plain") + + parsers.minimally_indented_par_or_plain = parsers.minimally_indented_paragraph + + parsers.minimally_indented_plain + + parsers.minimally_indented_par_or_plain_no_blank = parsers.minimally_indented_par_or_plain + - parsers.minimally_indented_blankline + + parsers.minimally_indented_ref = parsers.check_minimal_indent * V("Reference") + + parsers.minimally_indented_blank = parsers.check_minimal_indent * V("Blank") + + parsers.conditionally_indented_blankline = parsers.check_minimal_blank_indent * (parsers.blankline / "") + + parsers.minimally_indented_ref_or_block = parsers.minimally_indented_ref + + parsers.minimally_indented_block + - parsers.minimally_indented_blankline + + parsers.minimally_indented_ref_or_block_or_par = parsers.minimally_indented_ref + + parsers.minimally_indented_block_or_paragraph + - parsers.minimally_indented_blankline + +% \end{macrocode} +% \begin{markdown} +% +% The following pattern parses the properly indented content that follows the initial container start. +% +% \end{markdown} +% \begin{macrocode} + + parsers.separator_loop = function(separated_block, paragraph, block_separator, paragraph_separator) + return separated_block + + block_separator + * paragraph + * separated_block + + paragraph_separator + * paragraph + end + + parsers.create_loop_body_pair = function(separated_block, paragraph, block_separator, paragraph_separator) + return { + block = parsers.separator_loop(separated_block, paragraph, block_separator, block_separator), + par = parsers.separator_loop(separated_block, paragraph, block_separator, paragraph_separator) + } + end + + parsers.block_sep_group = function(blank) + return blank^0 * parsers.eof + + ( blank^2 / writer.paragraphsep + + blank^0 / writer.interblocksep + ) + end + + parsers.par_sep_group = function(blank) + return blank^0 * parsers.eof + + blank^0 / writer.paragraphsep + end + + parsers.sep_group_no_output = function(blank) + return blank^0 * parsers.eof + + blank^0 + end + + parsers.content_blank = parsers.minimally_indented_blankline + + parsers.ref_or_block_separated = parsers.sep_group_no_output(parsers.content_blank) + * ( parsers.minimally_indented_ref + - parsers.content_blank) + + parsers.block_sep_group(parsers.content_blank) + * ( parsers.minimally_indented_block + - parsers.content_blank) + + parsers.loop_body_pair = + parsers.create_loop_body_pair(parsers.ref_or_block_separated, + parsers.minimally_indented_par_or_plain_no_blank, + parsers.block_sep_group(parsers.content_blank), + parsers.par_sep_group(parsers.content_blank)) + + parsers.content_loop = ( V("Block") + * parsers.loop_body_pair.block^0 + + (V("Paragraph") + V("Plain")) + * parsers.ref_or_block_separated + * parsers.loop_body_pair.block^0 + + (V("Paragraph") + V("Plain")) + * parsers.loop_body_pair.par^0) + * parsers.content_blank^0 + + parsers.indented_content = function() + return Ct( (V("Reference") + (parsers.blankline / "")) + * parsers.content_blank^0 + * parsers.check_minimal_indent + * parsers.content_loop + + (V("Reference") + (parsers.blankline / "")) + * parsers.content_blank^0 + + parsers.content_loop) + end + + parsers.add_indent = function(pattern, name, breakable) + return Cg(Cmt( Cb("indent_info") + * Ct(pattern) + * (#parsers.linechar * Cc(false) + Cc(true)) -- check if starter is blank + * Cc(name) + * Cc(breakable), + process_starter_indent), "indent_info") + end + % \end{macrocode} % \par % \begin{markdown} @@ -24766,18 +27742,28 @@ function M.reader.new(writer, options) parsers.dig = parsers.digit end - parsers.enumerator = C(parsers.dig^3 * parsers.period) * #parsers.spacing - + C(parsers.dig^2 * parsers.period) * #parsers.spacing - * (parsers.tab + parsers.space^1) - + C(parsers.dig * parsers.period) * #parsers.spacing - * (parsers.tab + parsers.space^-2) - + parsers.space * C(parsers.dig^2 * parsers.period) - * #parsers.spacing - + parsers.space * C(parsers.dig * parsers.period) - * #parsers.spacing - * (parsers.tab + parsers.space^-1) - + parsers.space * parsers.space * C(parsers.dig^1 - * parsers.period) * #parsers.spacing + parsers.enumerator = function(delimiter_type, interrupting) + local delimiter_range + local allowed_end + if interrupting then + delimiter_range = P("1") + allowed_end = C(parsers.spacechar^1) * #parsers.linechar + else + delimiter_range = parsers.dig * parsers.dig^-8 + allowed_end = C(parsers.spacechar^1) + #(parsers.newline + parsers.eof) + end + + return parsers.check_trail + * Ct(C(delimiter_range) * C(delimiter_type)) + * allowed_end + end + + parsers.starter = parsers.bullet(parsers.dash) + + parsers.bullet(parsers.asterisk) + + parsers.bullet(parsers.plus) + + parsers.enumerator(parsers.period) + + parsers.enumerator(parsers.rparent) + % \end{macrocode} % \par % \begin{markdown} @@ -24786,20 +27772,369 @@ function M.reader.new(writer, options) % % \end{markdown} % \begin{macrocode} - -- strip off leading > and indents, and run through blocks - parsers.blockquote_body = ((parsers.leader * parsers.more * parsers.space^-1)/"" - * parsers.linechar^0 * parsers.newline)^1 - * (-V("BlockquoteExceptions") * parsers.linechar^1 - * parsers.newline)^0 + parsers.blockquote_start = parsers.check_trail * C(parsers.more) * C(parsers.spacechar^0) + + parsers.blockquote_body = parsers.add_indent(parsers.blockquote_start, "bq", true) + * parsers.indented_content() + * remove_indent("bq") if not options.breakableBlockquotes then - parsers.blockquote_body = parsers.blockquote_body - * (parsers.blankline^0 / "") + parsers.blockquote_body = parsers.add_indent(parsers.blockquote_start, "bq", false) + * parsers.indented_content() + * remove_indent("bq") end % \end{macrocode} % \par % \begin{markdown} % +%#### Helpers for Emphasis and Strong Emphasis (local) +% +% Parse the content of a table `content_part` with links, images and emphasis disabled. +% +% \end{markdown} +% \begin{macrocode} + local function parse_content_part(content_part) + local rope = util.rope_to_string(content_part) + local parsed = self.parser_functions.parse_inlines_no_link_or_emphasis(rope) + parsed.indent_info = nil + return parsed + end + +% \end{macrocode} +% \begin{markdown} +% +% Collect the content between the `opening_index` and `closing_index` in the delimiter table `t`. +% +% \end{markdown} +% \begin{macrocode} + local function collect_emphasis_content(t, opening_index, closing_index) + local content = {} + + local content_part = {} + for i = opening_index, closing_index do + local value = t[i] + + if value.rendered ~= nil then + content[#content + 1] = parse_content_part(content_part) + content_part = {} + content[#content + 1] = value.rendered + value.rendered = nil + else + if value.type == "delimiter" and value.element == "emphasis" then + if value.is_active then + content_part[#content_part + 1] = string.rep(value.character, value.current_count) + end + else + content_part[#content_part + 1] = value.content + end + value.content = '' + value.is_active = false + end + end + + if next(content_part) ~= nil then + content[#content + 1] = parse_content_part(content_part) + end + + return content + end + +% \end{macrocode} +% \begin{markdown} +% +% Render content between the `opening_index` and `closing_index` in the delimiter table `t` +% as emphasis. +% +% \end{markdown} +% \begin{macrocode} + local function fill_emph(t, opening_index, closing_index) + local content = collect_emphasis_content(t, opening_index + 1, closing_index - 1) + t[opening_index + 1].is_active = true + t[opening_index + 1].rendered = writer.emphasis(content) + end + +% \end{macrocode} +% \begin{markdown} +% +% Render content between the `opening_index` and `closing_index` in the delimiter table `t` +% as strong emphasis. +% +% \end{markdown} +% \begin{macrocode} + local function fill_strong(t, opening_index, closing_index) + local content = collect_emphasis_content(t, opening_index + 1, closing_index - 1) + t[opening_index + 1].is_active = true + t[opening_index + 1].rendered = writer.strong(content) + end + +% \end{macrocode} +% \begin{markdown} +% +% Check whether the opening delimiter `opening_delimiter` and closing delimiter `closing_delimiter` break +% rule three together. +% +% \end{markdown} +% \begin{macrocode} + local function breaks_three_rule(opening_delimiter, closing_delimiter) + return (opening_delimiter.is_closing or closing_delimiter.is_opening) and + ((opening_delimiter.original_count + closing_delimiter.original_count) % 3 == 0) and + (opening_delimiter.original_count % 3 ~= 0 or closing_delimiter.original_count % 3 ~= 0) + end + +% \end{macrocode} +% \begin{markdown} +% +% Look for the first potential emphasis opener in the delimiter table `t` in the range from +% `bottom_index` to `latest_index` that has the same character `character` as the closing +% delimiter `closing_delimiter`. +% +% \end{markdown} +% \begin{macrocode} + local function find_emphasis_opener(t, bottom_index, latest_index, character, closing_delimiter) + for i = latest_index, bottom_index, -1 do + local value = t[i] + if value.is_active and + value.is_opening and + value.type == "delimiter" and + value.element == "emphasis" and + (value.character == character) and + (value.current_count > 0) then + if not breaks_three_rule(value, closing_delimiter) then + return i + end + end + end + end + +% \end{macrocode} +% \begin{markdown} +% +% Iterate over the delimiters in the delimiter table `t`, producing emphasis or strong emphasis macros. +% +% \end{markdown} +% \begin{macrocode} + local function process_emphasis(t, opening_index, closing_index) + for i = opening_index, closing_index do + local value = t[i] + if value.type == "delimiter" and value.element == "emphasis" then + local delimiter_length = string.len(value.content) + value.character = string.sub(value.content, 1, 1) + value.current_count = delimiter_length + value.original_count = delimiter_length + end + end + + local openers_bottom = { + ['*'] = { + [true] = {opening_index, opening_index, opening_index}, + [false] = {opening_index, opening_index, opening_index} + }, + ['_'] = { + [true] = {opening_index, opening_index, opening_index}, + [false] = {opening_index, opening_index, opening_index} + } + } + + local current_position = opening_index + local max_position = closing_index + + while current_position <= max_position do + local value = t[current_position] + + if value.type ~= "delimiter" or + value.element ~= "emphasis" or + not value.is_active or + not value.is_closing or + (value.current_count <= 0) then + current_position = current_position + 1 + goto continue + end + + local character = value.character + local is_opening = value.is_opening + local closing_length_modulo_three = value.original_count % 3 + + local current_openers_bottom = openers_bottom[character][is_opening][closing_length_modulo_three + 1] + + local opener_position = find_emphasis_opener(t, current_openers_bottom, current_position - 1, character, value) + + if (opener_position == nil) then + openers_bottom[character][is_opening][closing_length_modulo_three + 1] = current_position + current_position = current_position + 1 + goto continue + end + + local opening_delimiter = t[opener_position] + + local current_opening_count = opening_delimiter.current_count + local current_closing_count = t[current_position].current_count + + if (current_opening_count >= 2) and (current_closing_count >= 2) then + opening_delimiter.current_count = current_opening_count - 2 + t[current_position].current_count = current_closing_count - 2 + fill_strong(t, opener_position, current_position) + else + opening_delimiter.current_count = current_opening_count - 1 + t[current_position].current_count = current_closing_count - 1 + fill_emph(t, opener_position, current_position) + end + + ::continue:: + end + end + + local cont = lpeg.R("\128\191") -- continuation byte + +% \end{macrocode} +% \begin{markdown} +% +% Match a UTF-8 character of byte length `n`. +% +% \end{markdown} +% \begin{macrocode} + local function utf8_by_byte_count(n) + if (n == 1) then + return lpeg.R("\0\127") + end + if (n == 2) then + return lpeg.R("\194\223") * cont + end + if (n == 3) then + return lpeg.R("\224\239") * cont * cont + end + if (n == 4) then + return lpeg.R("\240\244") * cont * cont * cont + end + end + +% \end{macrocode} +% \begin{markdown} +% +% Check if a there is a character of a type `chartype` between the start position `start_pos` +% and end position `end_pos` in a string `s` relative to current index `i`. +% +% \end{markdown} +% \begin{macrocode} + local function check_unicode_type(s, i, start_pos, end_pos, chartype) + local c + local char_length + for pos = start_pos, end_pos, 1 do + if (start_pos < 0) then + char_length = -pos + else + char_length = pos + 1 + end + c = lpeg.match({ C(utf8_by_byte_count(char_length)) },s,i+pos) + if (c ~= nil) and (unicode.utf8.match(c, chartype)) then + return i + end + end + end + + local function check_preceding_unicode_punctuation(s, i) + return check_unicode_type(s, i, -4, -1, "%p") + end + + local function check_preceding_unicode_whitespace(s, i) + return check_unicode_type(s, i, -4, -1, "%s") + end + + local function check_following_unicode_punctuation(s, i) + return check_unicode_type(s, i, 0, 3, "%p") + end + + local function check_following_unicode_whitespace(s, i) + return check_unicode_type(s, i, 0, 3, "%s") + end + + parsers.unicode_preceding_punctuation = B(parsers.escapable) + + Cmt(parsers.succeed, check_preceding_unicode_punctuation) + + parsers.unicode_preceding_whitespace = Cmt(parsers.succeed, check_preceding_unicode_whitespace) + + parsers.unicode_following_punctuation = #parsers.escapable + + Cmt(parsers.succeed, check_following_unicode_punctuation) + + parsers.unicode_following_whitespace = Cmt(parsers.succeed, check_following_unicode_whitespace) + + parsers.delimiter_run = function(character) + return (B(parsers.backslash * character) + -B(character)) + * character^1 + * -#character + end + + parsers.left_flanking_delimiter_run = function(character) + return (B( parsers.any) + * (parsers.unicode_preceding_punctuation + parsers.unicode_preceding_whitespace) + + -B(parsers.any)) + * parsers.delimiter_run(character) + * parsers.unicode_following_punctuation + + parsers.delimiter_run(character) + * -#(parsers.unicode_following_punctuation + parsers.unicode_following_whitespace + + parsers.eof) + end + + parsers.right_flanking_delimiter_run = function(character) + return parsers.unicode_preceding_punctuation + * parsers.delimiter_run(character) + * (parsers.unicode_following_punctuation + parsers.unicode_following_whitespace + + parsers.eof) + + (B(parsers.any) + * -(parsers.unicode_preceding_punctuation + parsers.unicode_preceding_whitespace)) + * parsers.delimiter_run(character) + end + + if options.underscores then + parsers.emph_start = parsers.left_flanking_delimiter_run(parsers.asterisk) + + (-#parsers.right_flanking_delimiter_run(parsers.underscore) + + (parsers.unicode_preceding_punctuation + * #parsers.right_flanking_delimiter_run(parsers.underscore))) + * parsers.left_flanking_delimiter_run(parsers.underscore) + + parsers.emph_end = parsers.right_flanking_delimiter_run(parsers.asterisk) + + (-#parsers.left_flanking_delimiter_run(parsers.underscore) + + #(parsers.left_flanking_delimiter_run(parsers.underscore) + * parsers.unicode_following_punctuation)) + * parsers.right_flanking_delimiter_run(parsers.underscore) + else + parsers.emph_start = parsers.left_flanking_delimiter_run(parsers.asterisk) + + parsers.emph_end = parsers.right_flanking_delimiter_run(parsers.asterisk) + end + + parsers.emph_capturing_open_and_close = #parsers.emph_start * #parsers.emph_end + * Ct( Cg(Cc("delimiter"), "type") + * Cg(Cc("emphasis"), "element") + * Cg(C(parsers.emph_start), "content") + * Cg(Cc(true), "is_opening") + * Cg(Cc(true), "is_closing")) + + parsers.emph_capturing_open = Ct( Cg(Cc("delimiter"), "type") + * Cg(Cc("emphasis"), "element") + * Cg(C(parsers.emph_start), "content") + * Cg(Cc(true), "is_opening") + * Cg(Cc(false), "is_closing")) + + parsers.emph_capturing_close = Ct( Cg(Cc("delimiter"), "type") + * Cg(Cc("emphasis"), "element") + * Cg(C(parsers.emph_end), "content") + * Cg(Cc(false), "is_opening") + * Cg(Cc(true), "is_closing")) + + parsers.emph_open_or_close = parsers.emph_capturing_open_and_close + + parsers.emph_capturing_open + + parsers.emph_capturing_close + + parsers.emph_open = parsers.emph_capturing_open_and_close + + parsers.emph_capturing_open + + parsers.emph_close = parsers.emph_capturing_open_and_close + + parsers.emph_capturing_close + +% \end{macrocode} +% \par +% \begin{markdown} +% %#### Helpers for Links and Link Reference Definitions (local) % % \end{markdown} @@ -24818,14 +28153,16 @@ function M.reader.new(writer, options) % % \end{markdown} % \begin{macrocode} - function self.register_link(tag, url, title, + function self.register_link(_, tag, url, title, attributes) - tag = self.normalize_tag(tag) - references[tag] = { - url = url, - title = title, - attributes = attributes, - } + local normalized_tag = self.normalize_tag(tag) + if references[normalized_tag] == nil then + references[normalized_tag] = { + url = url, + title = title, + attributes = attributes + } + end return "" end @@ -24834,103 +28171,505 @@ function M.reader.new(writer, options) % \begin{markdown} % % The \luamdef{reader->lookup_reference} method looks up a -% reference with link label `tag`. When the reference exists -% the method returns a link. The attributes of a link are -% produced by merging the attributes of the link reference -% and the optional `attributes`. Otherwise, the method returns a -% two-tuple of `nil` and fallback text constructed from the -% link text `label` and the optional spaces `sps` between the -% link text and the link label. -% -% \end{markdown} -% \begin{macrocode} - function self.lookup_reference(label, sps, tag, - attributes) - local tagpart - if not tag then - tag = label - tagpart = "" - elseif tag == "" then - tag = label - tagpart = "[]" - else - tagpart = { - "[", - self.parser_functions.parse_inlines(tag), - "]" - } - end - if sps then - tagpart = {sps, tagpart} +% reference with link label `tag`. +% +% \end{markdown} +% \begin{macrocode} + function self.lookup_reference(tag) + return references[self.normalize_tag(tag)] + end + + parsers.title_s_direct_ref = parsers.squote + * Cs((parsers.html_entities + + (parsers.anyescaped - parsers.squote - parsers.blankline^2))^0) + * parsers.squote + + parsers.title_d_direct_ref = parsers.dquote + * Cs((parsers.html_entities + + (parsers.anyescaped - parsers.dquote - parsers.blankline^2))^0) + * parsers.dquote + + parsers.title_p_direct_ref = parsers.lparent + * Cs((parsers.html_entities + + (parsers.anyescaped - parsers.lparent - parsers.rparent - parsers.blankline^2))^0) + * parsers.rparent + + parsers.title_direct_ref = parsers.title_s_direct_ref + + parsers.title_d_direct_ref + + parsers.title_p_direct_ref + + parsers.inline_direct_ref_inside = parsers.lparent * parsers.spnl + * Cg(parsers.url + Cc(""), "url") + * parsers.spnl + * Cg(parsers.title_direct_ref + Cc(""), "title") + * parsers.spnl * parsers.rparent + + parsers.inline_direct_ref = parsers.lparent * parsers.spnlc + * Cg(parsers.url + Cc(""), "url") + * parsers.spnlc + * Cg(parsers.title + Cc(""), "title") + * parsers.spnlc * parsers.rparent + + parsers.empty_link = parsers.lbracket + * parsers.rbracket + + parsers.inline_link = parsers.link_text + * parsers.inline_direct_ref + + parsers.full_link = parsers.link_text + * parsers.link_label + + parsers.shortcut_link = parsers.link_label + * -(parsers.empty_link + parsers.link_label) + + parsers.collapsed_link = parsers.link_label + * parsers.empty_link + + parsers.image_opening = #(parsers.exclamation * parsers.inline_link) + * Cg(Cc("inline"), "link_type") + + #(parsers.exclamation * parsers.full_link) + * Cg(Cc("full"), "link_type") + + #(parsers.exclamation * parsers.collapsed_link) + * Cg(Cc("collapsed"), "link_type") + + #(parsers.exclamation * parsers.shortcut_link) + * Cg(Cc("shortcut"), "link_type") + + #(parsers.exclamation * parsers.empty_link) + * Cg(Cc("empty"), "link_type") + + parsers.link_opening = #parsers.inline_link + * Cg(Cc("inline"), "link_type") + + #parsers.full_link + * Cg(Cc("full"), "link_type") + + #parsers.collapsed_link + * Cg(Cc("collapsed"), "link_type") + + #parsers.shortcut_link + * Cg(Cc("shortcut"), "link_type") + + #parsers.empty_link + * Cg(Cc("empty_link"), "link_type") + + #parsers.link_text + * Cg(Cc("link_text"), "link_type") + + parsers.link_image_opening = Ct( Cg(Cc("delimiter"), "type") + * Cg(Cc(true), "is_opening") + * Cg(Cc(false), "is_closing") + * ( Cg(Cc("image"), "element") + * parsers.image_opening + * Cg(parsers.exclamation * parsers.lbracket, "content") + + Cg(Cc("link"), "element") + * parsers.link_opening + * Cg(parsers.lbracket, "content"))) + + parsers.link_image_closing = Ct( Cg(Cc("delimiter"), "type") + * Cg(Cc("link"), "element") + * Cg(Cc(false), "is_opening") + * Cg(Cc(true), "is_closing") + * ( Cg(Cc(true), "is_direct") + * Cg(parsers.rbracket * #parsers.inline_direct_ref, "content") + + Cg(Cc(false), "is_direct") + * Cg(parsers.rbracket, "content"))) + + parsers.link_image_open_or_close = parsers.link_image_opening + + parsers.link_image_closing + + if options.html then + parsers.link_emph_precedence = parsers.inticks + + parsers.autolink + + parsers.html_inline_tags + else + parsers.link_emph_precedence = parsers.inticks + + parsers.autolink + end + + parsers.link_and_emph_endline = parsers.newline + * ((parsers.check_minimal_indent + * -V("EndlineExceptions") + + parsers.check_optional_indent + * -V("EndlineExceptions") + * -parsers.starter) / "") + * parsers.spacechar^0 / "\n" + + parsers.link_and_emph_content = Ct( Cg(Cc("content"), "type") + * Cg(Cs(( parsers.link_emph_precedence + + parsers.backslash * parsers.any + + parsers.link_and_emph_endline + + (parsers.linechar + - parsers.blankline^2 + - parsers.link_image_open_or_close + - parsers.emph_open_or_close))^0), "content")) + + parsers.link_and_emph_table = (parsers.link_image_opening + parsers.emph_open) + * parsers.link_and_emph_content + * ((parsers.link_image_open_or_close + parsers.emph_open_or_close) + * parsers.link_and_emph_content)^1 + +% \end{macrocode} +% \begin{markdown} +% +% Collect the content between the `opening_index` and `closing_index` in the delimiter table `t`. +% +% \end{markdown} +% \begin{macrocode} + local function collect_link_content(t, opening_index, closing_index) + local content = {} + for i = opening_index, closing_index do + content[#content + 1] = t[i].content end - tag = self.normalize_tag(tag) - local r = references[tag] - if r then - local merged_attributes = {} - for _, attribute in ipairs(r.attributes or {}) do - table.insert(merged_attributes, attribute) + return util.rope_to_string(content) + end + +% \end{macrocode} +% \begin{markdown} +% +% Look for the closest potential link opener in the delimiter table `t` in the range from +% `bottom_index` to `latest_index`. +% +% \end{markdown} +% \begin{macrocode} + local function find_link_opener(t, bottom_index, latest_index) + for i = latest_index, bottom_index, -1 do + local value = t[i] + if value.type == "delimiter" and + value.is_opening and + (value.element == "link" or value.element == "image") + and not value.removed then + if value.is_active then + return i + end + value.removed = true + return nil end - for _, attribute in ipairs(attributes or {}) do - table.insert(merged_attributes, attribute) + end + end + +% \end{macrocode} +% \begin{markdown} +% +% Find the position of a delimiter that closes a full link after an an index `latest_index` +% in the delimiter table `t`. +% +% \end{markdown} +% \begin{macrocode} + local function find_next_link_closing_index(t, latest_index) + for i = latest_index, #t do + local value = t[i] + if value.is_closing and + value.element == "link" and + not value.removed then + return i end - if #merged_attributes == 0 then - merged_attributes = nil + end + end + +% \end{macrocode} +% \begin{markdown} +% +% Disable all preceding opening link delimiters by marking them inactive with the `is_active` property +% to prevent links within links. Images within links are allowed. +% +% \end{markdown} +% \begin{macrocode} + local function disable_previous_link_openers(t, opening_index) + if t[opening_index].element == "image" then + return + end + + for i = opening_index, 1, -1 do + local value = t[i] + if value.is_active and + value.type == "delimiter" and + value.is_opening and + value.element == "link" then + value.is_active = false end - return { - url = r.url, - title = r.title, - attributes = merged_attributes, - } - else - return nil, { - "[", - self.parser_functions.parse_inlines(label), - "]", - tagpart - } end end - -- lookup link reference and return a link, if the reference is found, - -- or a bracketed label otherwise. - local function indirect_link(label, sps, tag) - return writer.defer_call(function() - local r,fallback = self.lookup_reference(label, sps, tag) - if r then - return writer.link( - self.parser_functions.parse_inlines_no_link(label), - r.url, r.title) - else - return fallback +% \end{macrocode} +% \begin{markdown} +% +% Disable the delimiters between the `opening_index` and `closing_index` in the delimiter table `t` +% by marking them inactive with the `is_active` property. +% +% \end{markdown} +% \begin{macrocode} + local function disable_range(t, opening_index, closing_index) + for i = opening_index, closing_index do + local value = t[i] + if value.is_active then + value.is_active = false + if value.type == "delimiter" then + value.removed = true + end end - end) + end end - -- lookup image reference and return an image, if the reference is found, - -- or a bracketed label otherwise. - local function indirect_image(label, sps, tag) - return writer.defer_call(function() - local r,fallback = self.lookup_reference(label, sps, tag) - if r then - return writer.image(writer.string(label), r.url, r.title) - else - return {"!", fallback} +% \end{macrocode} +% \begin{markdown} +% +% Clear the parsed content between the `opening_index` and `closing_index` in the delimiter table `t`. +% +% \end{markdown} +% \begin{macrocode} + local function delete_parsed_content_in_range(t, opening_index, closing_index) + for i = opening_index, closing_index do + t[i].rendered = nil + end + end + +% \end{macrocode} +% \begin{markdown} +% +% Clear the content between the `opening_index` and `closing_index` in the delimiter table `t`. +% +% \end{markdown} +% \begin{macrocode} + local function empty_content_in_range(t, opening_index, closing_index) + for i = opening_index, closing_index do + t[i].content = '' + end + end + +% \end{macrocode} +% \begin{markdown} +% +% Join the attributes from the link reference definition `reference_attributes` with the link's own +% attributes `own_attributes`. +% +% \end{markdown} +% \begin{macrocode} + local function join_attributes(reference_attributes, own_attributes) + local merged_attributes = {} + for _, attribute in ipairs(reference_attributes or {}) do + table.insert(merged_attributes, attribute) + end + for _, attribute in ipairs(own_attributes or {}) do + table.insert(merged_attributes, attribute) + end + if next(merged_attributes) == nil then + merged_attributes = nil + end + return merged_attributes + end + +% \end{macrocode} +% \begin{markdown} +% +% Parse content between two delimiters in the delimiter table `t`. Produce the respective link and image +% macros. +% +% \end{markdown} +% \begin{macrocode} + local function render_link_or_image(t, opening_index, closing_index, content_end_index, reference) + process_emphasis(t, opening_index, content_end_index) + local mapped = collect_emphasis_content(t, opening_index + 1, content_end_index - 1) + + local rendered = {} + if (t[opening_index].element == "link") then + rendered = writer.link(mapped, reference.url, reference.title, reference.attributes) + end + + if (t[opening_index].element == "image") then + rendered = writer.image(mapped, reference.url, reference.title, reference.attributes) + end + + t[opening_index].rendered = rendered + delete_parsed_content_in_range(t, opening_index + 1, closing_index) + empty_content_in_range(t, opening_index, closing_index) + disable_previous_link_openers(t, opening_index) + disable_range(t, opening_index, closing_index) + end + +% \end{macrocode} +% \begin{markdown} +% +% Match the link destination of an inline link at index `closing_index` in table `t` +% when `match_reference` is true. Additionally, match attributes when the option +% \Opt{linkAttributes} is enabled. +% +% \end{markdown} +% \begin{macrocode} + local function resolve_inline_following_content(t, closing_index, match_reference, match_link_attributes) + local content = "" + for i = closing_index + 1, #t do + content = content .. t[i].content + end + + local matching_content = parsers.succeed + + if match_reference then + matching_content = matching_content * parsers.inline_direct_ref_inside + end + + if match_link_attributes then + matching_content = matching_content * Cg(Ct(parsers.attributes^-1), "attributes") + end + + local matched = lpeg.match(Ct(matching_content * Cg(Cp(), "end_position")), content) + + local matched_count = matched.end_position - 1 + for i = closing_index + 1, #t do + local value = t[i] + + local chars_left = matched_count + matched_count = matched_count - #value.content + + if matched_count <= 0 then + value.content = value.content:sub(chars_left + 1) + break end - end) + + value.content = '' + value.is_active = false + end + + local attributes = matched.attributes + if attributes == nil or next(attributes) == nil then + attributes = nil + end + + return { + url = matched.url or "", + title = matched.title or "", + attributes = attributes + } + end + +% \end{macrocode} +% \begin{markdown} +% +% Resolve an inline link [a](b "c") from the delimiters at `opening_index` and `closing_index` +% within a delimiter table `t`. Here, compared to other types of links, no reference definition is needed. +% +% \end{markdown} +% \begin{macrocode} + local function resolve_inline_link(t, opening_index, closing_index) + local inline_content = resolve_inline_following_content(t, closing_index, true, t.match_link_attributes) + render_link_or_image(t, opening_index, closing_index, closing_index, inline_content) + end + +% \end{macrocode} +% \begin{markdown} +% +% Resolve a shortcut link [a] from the delimiters at `opening_index` and `closing_index` within a delimiter table `t`. +% Continue if a tag `a` is not found in the references. +% +% \end{markdown} +% \begin{macrocode} + local function resolve_shortcut_link(t, opening_index, closing_index) + local content = collect_link_content(t, opening_index + 1, closing_index - 1) + local r = self.lookup_reference(content) + + if r then + local inline_content = resolve_inline_following_content(t, closing_index, false, t.match_link_attributes) + r.attributes = join_attributes(r.attributes, inline_content.attributes) + render_link_or_image(t, opening_index, closing_index, closing_index, r) + end + end + +% \end{macrocode} +% \begin{markdown} +% +% Resolve a full link [a][b] from the delimiters at `opening_index` and `closing_index` within a delimiter table `t`. +% Continue if a tag `b` is not found in the references. +% +% \end{markdown} +% \begin{macrocode} + local function resolve_full_link(t, opening_index, closing_index) + local next_link_closing_index = find_next_link_closing_index(t, closing_index + 4) + local next_link_content = collect_link_content(t, closing_index + 3, next_link_closing_index - 1) + local r = self.lookup_reference(next_link_content) + + if r then + local inline_content = resolve_inline_following_content(t, next_link_closing_index, false, + t.match_link_attributes) + r.attributes = join_attributes(r.attributes, inline_content.attributes) + render_link_or_image(t, opening_index, next_link_closing_index, closing_index, r) + end + end + +% \end{macrocode} +% \begin{markdown} +% +% Resolve a collapsed link [a][] from the delimiters at `opening_index` and `closing_index` +% within a delimiter table `t`. +% Continue if a tag `a` is not found in the references. +% +% \end{markdown} +% \begin{macrocode} + local function resolve_collapsed_link(t, opening_index, closing_index) + local next_link_closing_index = find_next_link_closing_index(t, closing_index + 4) + local content = collect_link_content(t, opening_index + 1, closing_index - 1) + local r = self.lookup_reference(content) + + if r then + local inline_content = resolve_inline_following_content(t, closing_index, false, t.match_link_attributes) + r.attributes = join_attributes(r.attributes, inline_content.attributes) + render_link_or_image(t, opening_index, next_link_closing_index, closing_index, r) + end end - parsers.direct_link_tail = parsers.spnl - * parsers.lparent - * (parsers.url + Cc("")) -- link can be empty [foo]() - * parsers.optionaltitle - * parsers.rparent +% \end{macrocode} +% \begin{markdown} +% +% Parse a table of link and emphasis delimiters `t`. +% First, iterate over the link delimiters and produce either link or image macros. +% Then run `process_emphasis` over the entire delimiter table, resolving emphasis and strong +% emphasis and parsing any content outside of closed delimiters. +% +% \end{markdown} +% \begin{macrocode} + local function process_links_and_emphasis(t) + for _,value in ipairs(t) do + value.is_active = true + end + + for i,value in ipairs(t) do + if not value.is_closing or + value.type ~= "delimiter" or + not (value.element == "link" or value.element == "image") then + goto continue + end + + local opener_position = find_link_opener(t, 1, i - 1) + if (opener_position == nil) then + goto continue + end + + local opening_delimiter = t[opener_position] + opening_delimiter.removed = true + + local link_type = opening_delimiter.link_type + + if (link_type == "inline") then + resolve_inline_link(t, opener_position, i) + end + if (link_type == "shortcut") then + resolve_shortcut_link(t, opener_position, i) + end + if (link_type == "full") then + resolve_full_link(t, opener_position, i) + end + if (link_type == "collapsed") then + resolve_collapsed_link(t, opener_position, i) + end + + ::continue:: + end - parsers.direct_link = (parsers.tag / self.parser_functions.parse_inlines_no_link) - * parsers.direct_link_tail + t[#t].content = t[#t].content:gsub("%s*$","") + + process_emphasis(t, 1, #t) + local final_result = collect_emphasis_content(t, 1, #t) + return final_result + end + + function self.defer_link_and_emphasis_processing(delimiter_table) + return writer.defer_call(function() + return process_links_and_emphasis(delimiter_table) + end) + end - parsers.direct_image = parsers.exclamation - * (parsers.tag / self.parser_functions.parse_inlines) - * parsers.direct_link_tail % \end{macrocode} % \par % \begin{markdown} @@ -24942,7 +28681,7 @@ function M.reader.new(writer, options) parsers.Str = (parsers.normalchar * (parsers.normalchar + parsers.at)^0) / writer.string - parsers.Symbol = (V("SpecialChar") - parsers.tightblocksep) + parsers.Symbol = (parsers.backtick^1 + V("SpecialChar")) / writer.string parsers.Ellipsis = P("...") / writer.ellipsis @@ -24954,83 +28693,112 @@ function M.reader.new(writer, options) if options.blankBeforeBlockquote then parsers.bqstart = parsers.fail else - parsers.bqstart = parsers.more + parsers.bqstart = parsers.blockquote_start end if options.blankBeforeHeading then parsers.headerstart = parsers.fail else - parsers.headerstart = parsers.hash - + (parsers.line * (parsers.equal^1 + parsers.dash^1) - * parsers.optionalspace * parsers.newline) + parsers.headerstart = parsers.atx_heading + end + + if options.blankBeforeList then + parsers.interrupting_bullets = parsers.fail + parsers.interrupting_enumerators = parsers.fail + else + parsers.interrupting_bullets = parsers.bullet(parsers.dash, true) + + parsers.bullet(parsers.asterisk, true) + + parsers.bullet(parsers.plus, true) + + parsers.interrupting_enumerators = parsers.enumerator(parsers.period, true) + + parsers.enumerator(parsers.rparent, true) + end + + if options.html then + parsers.html_interrupting = parsers.check_trail + * ( parsers.html_incomplete_open_tag + + parsers.html_incomplete_close_tag + + parsers.html_incomplete_open_special_tag + + parsers.html_comment_start + + parsers.html_cdatasection_start + + parsers.html_declaration_start + + parsers.html_instruction_start + - parsers.html_close_special_tag + - parsers.html_empty_special_tag) + else + parsers.html_interrupting = parsers.fail end parsers.EndlineExceptions = parsers.blankline -- paragraph break - + parsers.tightblocksep -- nested list + parsers.eof -- end of document + parsers.bqstart + + parsers.thematic_break_lines + + parsers.interrupting_bullets + + parsers.interrupting_enumerators + parsers.headerstart + + parsers.html_interrupting - parsers.Endline = parsers.newline - * -V("EndlineExceptions") - * parsers.spacechar^0 - / (options.hardLineBreaks and writer.hard_line_break - or writer.space) + parsers.NoSoftLineBreakEndlineExceptions = parsers.EndlineExceptions + parsers.endline = parsers.newline + * (parsers.check_minimal_indent + * -V("EndlineExceptions") + + parsers.check_optional_indent + * -V("EndlineExceptions") + * -parsers.starter) + * parsers.spacechar^0 + + parsers.Endline = parsers.endline + / writer.soft_line_break + + parsers.EndlineNoSub = parsers.endline + + parsers.NoSoftLineBreakEndline + = parsers.newline + * (parsers.check_minimal_indent + * -V("NoSoftLineBreakEndlineExceptions") + + parsers.check_optional_indent + * -V("NoSoftLineBreakEndlineExceptions") + * -parsers.starter) + * parsers.spacechar^0 + / writer.space + + parsers.EndlineBreak = parsers.backslash * parsers.Endline + / writer.hard_line_break + parsers.OptionalIndent = parsers.spacechar^1 / writer.space - parsers.Space = parsers.spacechar^2 * parsers.Endline / writer.hard_line_break - + parsers.spacechar^1 * parsers.Endline^-1 * parsers.eof / "" + parsers.Space = parsers.spacechar^2 * parsers.Endline + / writer.hard_line_break + + parsers.spacechar^1 * parsers.Endline^-1 * parsers.eof / self.expandtabs + + parsers.spacechar^1 * parsers.Endline + / writer.soft_line_break + + parsers.spacechar^1 * -parsers.newline / self.expandtabs + + parsers.NoSoftLineBreakSpace + = parsers.spacechar^2 * parsers.Endline + / writer.hard_line_break + + parsers.spacechar^1 * parsers.Endline^-1 * parsers.eof / self.expandtabs + parsers.spacechar^1 * parsers.Endline - * parsers.optionalspace - / (options.hardLineBreaks - and writer.hard_line_break - or writer.space) - + parsers.spacechar^1 * parsers.optionalspace - / writer.space + / writer.soft_line_break + + parsers.spacechar^1 * -parsers.newline / self.expandtabs parsers.NonbreakingEndline - = parsers.newline - * -V("EndlineExceptions") - * parsers.spacechar^0 - / (options.hardLineBreaks and writer.hard_line_break - or writer.nbsp) + = parsers.endline + / writer.soft_line_break parsers.NonbreakingSpace - = parsers.spacechar^2 * parsers.Endline / writer.hard_line_break + = parsers.spacechar^2 * parsers.Endline + / writer.hard_line_break + parsers.spacechar^1 * parsers.Endline^-1 * parsers.eof / "" + parsers.spacechar^1 * parsers.Endline * parsers.optionalspace - / (options.hardLineBreaks - and writer.hard_line_break - or writer.nbsp) + / writer.soft_line_break + parsers.spacechar^1 * parsers.optionalspace / writer.nbsp - if options.underscores then - parsers.Strong = ( parsers.between(parsers.Inline, parsers.doubleasterisks, - parsers.doubleasterisks) - + parsers.between(parsers.Inline, parsers.doubleunderscores, - parsers.doubleunderscores) - ) / writer.strong - - parsers.Emph = ( parsers.between(parsers.Inline, parsers.asterisk, - parsers.asterisk) - + parsers.between(parsers.Inline, parsers.underscore, - parsers.underscore) - ) / writer.emphasis - else - parsers.Strong = ( parsers.between(parsers.Inline, parsers.doubleasterisks, - parsers.doubleasterisks) - ) / writer.strong - - parsers.Emph = ( parsers.between(parsers.Inline, parsers.asterisk, - parsers.asterisk) - ) / writer.emphasis - end - % \end{macrocode} % \par % \begin{markdown} @@ -25046,7 +28814,6 @@ function self.auto_link_url(url, attributes) return writer.link(writer.escape(url), url, nil, attributes) end - % \end{macrocode} % \par % \begin{markdown} @@ -25074,39 +28841,21 @@ end = parsers.auto_link_relative_reference / self.auto_link_url - parsers.DirectLink = parsers.direct_link - / writer.link - - parsers.IndirectLink = parsers.indirect_link - / indirect_link - - -- parse a link or image (direct or indirect) - parsers.Link = parsers.DirectLink + parsers.IndirectLink - - parsers.DirectImage = parsers.direct_image - / writer.image - - parsers.IndirectImage = parsers.indirect_image - / indirect_image - - parsers.Image = parsers.DirectImage + parsers.IndirectImage - - -- avoid parsing long strings of * or _ as emph/strong - parsers.UlOrStarLine = parsers.asterisk^4 + parsers.underscore^4 - / writer.string + parsers.LinkAndEmph = Ct(parsers.link_and_emph_table) + / self.defer_link_and_emphasis_processing parsers.EscapedChar = parsers.backslash * C(parsers.escapable) / writer.string - parsers.InlineHtml = parsers.emptyelt_any / writer.inline_html_tag - + (parsers.htmlcomment / self.parser_functions.parse_inlines_no_html) - / writer.inline_html_comment - + parsers.htmlinstruction - + parsers.openelt_any / writer.inline_html_tag - + parsers.closeelt_any / writer.inline_html_tag + parsers.InlineHtml = Cs(parsers.html_inline_comment) / writer.inline_html_comment + + Cs(parsers.html_any_empty_inline_tag + + parsers.html_inline_instruction + + parsers.html_inline_cdatasection + + parsers.html_inline_declaration + + parsers.html_any_open_inline_tag + + parsers.html_any_close_tag) + / writer.inline_html_tag - parsers.HtmlEntity = parsers.hexentity / entities.hex_entity / writer.string - + parsers.decentity / entities.dec_entity / writer.string - + parsers.tagentity / entities.char_entity / writer.string + parsers.HtmlEntity = parsers.html_entities / writer.string % \end{macrocode} % \par % \begin{markdown} @@ -25115,40 +28864,40 @@ end % % \end{markdown} % \begin{macrocode} - parsers.DisplayHtml = (parsers.htmlcomment / self.parser_functions.parse_blocks_nested) - / writer.block_html_comment - + parsers.emptyelt_block / writer.block_html_element - + parsers.openelt_exact("hr") / writer.block_html_element - + parsers.in_matched_block_tags / writer.block_html_element - + parsers.htmlinstruction + parsers.DisplayHtml = Cs(parsers.check_trail + * ( parsers.html_comment + + parsers.html_special_block + + parsers.html_block + + parsers.html_any_block + + parsers.html_instruction + + parsers.html_cdatasection + + parsers.html_declaration)) + / writer.block_html_element - parsers.Verbatim = Cs( (parsers.blanklines - * ((parsers.indentedline - parsers.blankline))^1)^1 - ) / self.expandtabs / writer.verbatim + parsers.indented_non_blank_line = parsers.indentedline - parsers.blankline - parsers.BlockquoteExceptions = parsers.leader * parsers.more - + parsers.blankline + parsers.Verbatim = Cs( + parsers.check_code_trail + * (parsers.line - parsers.blankline) + * ((parsers.check_minimal_blank_indent_and_full_code_trail * parsers.blankline)^0 + * ((parsers.check_minimal_indent / "") * parsers.check_code_trail + * (parsers.line - parsers.blankline))^1)^0 + ) / self.expandtabs / writer.verbatim - parsers.Blockquote = Cs(parsers.blockquote_body^1) - / self.parser_functions.parse_blocks_nested + parsers.Blockquote = parsers.blockquote_body / writer.blockquote - parsers.ThematicBreak = ( parsers.lineof(parsers.asterisk) - + parsers.lineof(parsers.dash) - + parsers.lineof(parsers.underscore) - ) / writer.thematic_break + parsers.ThematicBreak = parsers.thematic_break_lines + / writer.thematic_break parsers.Reference = parsers.define_reference_parser - * parsers.blankline^1 / self.register_link - parsers.Paragraph = parsers.nonindentspace * Ct(parsers.Inline^1) - * ( parsers.newline - * ( parsers.blankline^1 - + #V("EndlineExceptions") - ) - + parsers.eof) - / writer.paragraph + parsers.Paragraph = parsers.freeze_trail + * (Ct((parsers.Inline)^1) + * (parsers.newline + parsers.eof) + * parsers.unfreeze_trail + / writer.paragraph) parsers.Plain = parsers.nonindentspace * Ct(parsers.Inline^1) / writer.plain @@ -25160,7 +28909,6 @@ end % % \end{markdown} % \begin{macrocode} - parsers.starter = parsers.bullet + parsers.enumerator if options.taskLists then parsers.tickbox = ( parsers.ticked_box @@ -25171,43 +28919,117 @@ end parsers.tickbox = parsers.fail end - -- we use \001 as a separator between a tight list item and a - -- nested list under it. - parsers.NestedList = Cs((parsers.optionallyindentedline - - parsers.starter)^1) - / function(a) return "\001"..a end - - parsers.ListBlockLine = parsers.optionallyindentedline - - parsers.blankline - (parsers.indent^-1 - * parsers.starter) - - parsers.ListBlock = parsers.line * parsers.ListBlockLine^0 - - parsers.ListContinuationBlock = parsers.blanklines * (parsers.indent / "") - * parsers.ListBlock + parsers.list_blank = parsers.conditionally_indented_blankline + + parsers.ref_or_block_list_separated = parsers.sep_group_no_output(parsers.list_blank) + * parsers.minimally_indented_ref + + parsers.block_sep_group(parsers.list_blank) + * parsers.minimally_indented_block + + parsers.ref_or_block_non_separated = parsers.minimally_indented_ref + + (parsers.succeed / writer.interblocksep) + * parsers.minimally_indented_block + - parsers.minimally_indented_blankline + + parsers.tight_list_loop_body_pair = + parsers.create_loop_body_pair(parsers.ref_or_block_non_separated, + parsers.minimally_indented_par_or_plain_no_blank, + (parsers.succeed / writer.interblocksep), + (parsers.succeed / writer.paragraphsep)) + + parsers.loose_list_loop_body_pair = + parsers.create_loop_body_pair(parsers.ref_or_block_list_separated, + parsers.minimally_indented_par_or_plain, + parsers.block_sep_group(parsers.list_blank), + parsers.par_sep_group(parsers.list_blank)) + + parsers.tight_list_content_loop = V("Block") + * parsers.tight_list_loop_body_pair.block^0 + + (V("Paragraph") + V("Plain")) + * parsers.ref_or_block_non_separated + * parsers.tight_list_loop_body_pair.block^0 + + (V("Paragraph") + V("Plain")) + * parsers.tight_list_loop_body_pair.par^0 + + parsers.loose_list_content_loop = V("Block") + * parsers.loose_list_loop_body_pair.block^0 + + (V("Paragraph") + V("Plain")) + * parsers.ref_or_block_list_separated + * parsers.loose_list_loop_body_pair.block^0 + + (V("Paragraph") + V("Plain")) + * parsers.loose_list_loop_body_pair.par^0 + + parsers.list_item_tightness_condition = -#( parsers.list_blank^0 + * parsers.minimally_indented_ref_or_block_or_par) + * remove_indent("li") + + remove_indent("li") + * parsers.fail + + parsers.indented_content_tight = Ct( (parsers.blankline / "") + * #parsers.list_blank + * remove_indent("li") + + ( (V("Reference") + (parsers.blankline / "")) + * parsers.check_minimal_indent + * parsers.tight_list_content_loop + + (V("Reference") + (parsers.blankline / "")) + + (parsers.tickbox^-1 / writer.escape) + * parsers.tight_list_content_loop + ) + * parsers.list_item_tightness_condition + ) + + parsers.indented_content_loose = Ct( (parsers.blankline / "") + * #parsers.list_blank + + ( (V("Reference") + (parsers.blankline / "")) + * parsers.check_minimal_indent + * parsers.loose_list_content_loop + + (V("Reference") + (parsers.blankline / "")) + + (parsers.tickbox^-1 / writer.escape) + * parsers.loose_list_content_loop + ) + ) parsers.TightListItem = function(starter) - return -parsers.ThematicBreak - * (Cs(starter / "" * parsers.tickbox^-1 * parsers.ListBlock * parsers.NestedList^-1) - / self.parser_functions.parse_blocks_nested) - * -(parsers.blanklines * parsers.indent) + return -parsers.ThematicBreak + * parsers.add_indent(starter, "li") + * parsers.indented_content_tight end parsers.LooseListItem = function(starter) - return -parsers.ThematicBreak - * Cs( starter / "" * parsers.tickbox^-1 * parsers.ListBlock * Cc("\n") - * (parsers.NestedList + parsers.ListContinuationBlock^0) - * (parsers.blanklines / "\n\n") - ) / self.parser_functions.parse_blocks_nested + return -parsers.ThematicBreak + * parsers.add_indent(starter, "li") + * parsers.indented_content_loose + * remove_indent("li") end - parsers.BulletList = ( Ct(parsers.TightListItem(parsers.bullet)^1) * Cc(true) - * parsers.skipblanklines * -parsers.bullet - + Ct(parsers.LooseListItem(parsers.bullet)^1) * Cc(false) - * parsers.skipblanklines ) - / writer.bulletlist + parsers.BulletListOfType = function(bullet_type) + local bullet = parsers.bullet(bullet_type) + return ( Ct( parsers.TightListItem(bullet) + * ( (parsers.check_minimal_indent / "") + * parsers.TightListItem(bullet) + )^0 + ) + * Cc(true) + * -#( (parsers.list_blank^0 / "") + * parsers.check_minimal_indent + * (bullet - parsers.ThematicBreak) + ) + + Ct( parsers.LooseListItem(bullet) + * ( (parsers.list_blank^0 / "") + * (parsers.check_minimal_indent / "") + * parsers.LooseListItem(bullet) + )^0 + ) + * Cc(false) + ) / writer.bulletlist + end + + parsers.BulletList = parsers.BulletListOfType(parsers.dash) + + parsers.BulletListOfType(parsers.asterisk) + + parsers.BulletListOfType(parsers.plus) - local function ordered_list(items,tight,startnum) + local function ordered_list(items,tight,starter) + local startnum = starter[2][1] if options.startNumber then startnum = tonumber(startnum) or 1 -- fallback for '#' if startnum ~= nil then @@ -25219,14 +29041,23 @@ end return writer.orderedlist(items,tight,startnum) end - parsers.OrderedList = Cg(parsers.enumerator, "listtype") * - ( Ct(parsers.TightListItem(Cb("listtype")) - * parsers.TightListItem(parsers.enumerator)^0) - * Cc(true) * parsers.skipblanklines * -parsers.enumerator - + Ct(parsers.LooseListItem(Cb("listtype")) - * parsers.LooseListItem(parsers.enumerator)^0) - * Cc(false) * parsers.skipblanklines - ) * Cb("listtype") / ordered_list + parsers.OrderedListOfType = function(delimiter_type) + local enumerator = parsers.enumerator(delimiter_type) + return Cg(enumerator, "listtype") + * (Ct( parsers.TightListItem(Cb("listtype")) + * ((parsers.check_minimal_indent / "") * parsers.TightListItem(enumerator))^0) + * Cc(true) + * -#((parsers.list_blank^0 / "") + * parsers.check_minimal_indent * enumerator) + + Ct( parsers.LooseListItem(Cb("listtype")) + * ((parsers.list_blank^0 / "") + * (parsers.check_minimal_indent / "") * parsers.LooseListItem(enumerator))^0) + * Cc(false) + ) * Ct(Cb("listtype")) / ordered_list + end + + parsers.OrderedList = parsers.OrderedListOfType(parsers.period) + + parsers.OrderedListOfType(parsers.rparent) % \end{macrocode} % \par % \begin{markdown} @@ -25237,7 +29068,6 @@ end % \begin{macrocode} parsers.Blank = parsers.blankline / "" + V("Reference") - + (parsers.tightblocksep / "\n") % \end{macrocode} % \par % \begin{markdown} @@ -25246,22 +29076,45 @@ end % % \end{markdown} % \begin{macrocode} + function parsers.parse_heading_text(s) + local inlines = self.parser_functions.parse_inlines(s) + local flatten_inlines = self.writer.flatten_inlines + self.writer.flatten_inlines = true + local flat_text = self.parser_functions.parse_inlines(s) + flat_text = util.rope_to_string(flat_text) + self.writer.flatten_inlines = flatten_inlines + return {flat_text, inlines} + end + -- parse atx header - parsers.AtxHeading = Cg(parsers.heading_start, "level") - * parsers.optionalspace - * (C(parsers.line) + parsers.AtxHeading = parsers.check_trail_no_rem + * Cg(parsers.heading_start, "level") + * ((C( parsers.optionalspace + * parsers.hash^0 + * parsers.optionalspace + * parsers.newline) + + parsers.spacechar^1 + * C(parsers.line)) / strip_atx_end - / self.parser_functions.parse_inlines) + / parsers.parse_heading_text) * Cb("level") / writer.heading - parsers.SetextHeading = #(parsers.line * S("=-")) - * Ct(parsers.linechar^1 - / self.parser_functions.parse_inlines) - * parsers.newline - * parsers.heading_level - * parsers.optionalspace + parsers.heading_line = parsers.linechar^1 + - parsers.thematic_break_lines + + parsers.heading_text = parsers.heading_line + * ((V("Endline") / "\n") * (parsers.heading_line - parsers.heading_level))^0 + * parsers.newline^-1 + + parsers.SetextHeading = parsers.freeze_trail * parsers.check_trail_no_rem + * #(parsers.heading_text + * parsers.check_minimal_indent * parsers.check_trail * parsers.heading_level) + * Cs(parsers.heading_text) + / parsers.parse_heading_text + * parsers.check_minimal_indent_and_trail * parsers.heading_level * parsers.newline + * parsers.unfreeze_trail / writer.heading parsers.Heading = parsers.AtxHeading + parsers.SetextHeading @@ -25364,12 +29217,42 @@ end Blocks = V("InitializeState") * ( V("ExpectedJekyllData") - * (V("Blank")^0 / writer.interblocksep))^-1 + * (V("Blank")^0 / writer.interblocksep) + )^-1 * V("Blank")^0 - * V("Block")^-1 - * ( V("Blank")^0 / writer.interblocksep - * V("Block"))^0 - * V("Blank")^0 * parsers.eof, +% \end{macrocode} +% \par +% \begin{markdown} +% +% Only create interblock separators between pairs of blocks that are not +% both paragraphs. Between a pair of paragraphs, any number of blank +% lines will always produce a paragraph separator. +% +% \end{markdown} +% \begin{macrocode} + * ( V("Block") + * ( V("Blank")^0 * parsers.eof + + ( V("Blank")^2 / writer.paragraphsep + + V("Blank")^0 / writer.interblocksep + ) + ) + + ( V("Paragraph") + V("Plain") ) + * ( V("Blank")^0 * parsers.eof + + ( V("Blank")^2 / writer.paragraphsep + + V("Blank")^0 / writer.interblocksep + ) + ) + * V("Block") + * ( V("Blank")^0 * parsers.eof + + ( V("Blank")^2 / writer.paragraphsep + + V("Blank")^0 / writer.interblocksep + ) + ) + + ( V("Paragraph") + V("Plain") ) + * ( V("Blank")^0 * parsers.eof + + V("Blank")^0 / writer.paragraphsep + ) + )^0, ExpectedJekyllData = parsers.fail, @@ -25381,23 +29264,25 @@ end ThematicBreak = parsers.ThematicBreak, BulletList = parsers.BulletList, OrderedList = parsers.OrderedList, - Heading = parsers.Heading, DisplayHtml = parsers.DisplayHtml, + Heading = parsers.Heading, Paragraph = parsers.Paragraph, Plain = parsers.Plain, EndlineExceptions = parsers.EndlineExceptions, - BlockquoteExceptions = parsers.BlockquoteExceptions, + NoSoftLineBreakEndlineExceptions + = parsers.NoSoftLineBreakEndlineExceptions, Str = parsers.Str, Space = parsers.Space, + NoSoftLineBreakSpace = parsers.NoSoftLineBreakSpace, OptionalIndent = parsers.OptionalIndent, Endline = parsers.Endline, - UlOrStarLine = parsers.UlOrStarLine, - Strong = parsers.Strong, - Emph = parsers.Emph, - Link = parsers.Link, - Image = parsers.Image, + EndlineNoSub = parsers.EndlineNoSub, + NoSoftLineBreakEndline + = parsers.NoSoftLineBreakEndline, + EndlineBreak = parsers.EndlineBreak, + LinkAndEmph = parsers.LinkAndEmph, Code = parsers.Code, AutoLinkUrl = parsers.AutoLinkUrl, AutoLinkEmail = parsers.AutoLinkEmail, @@ -25500,13 +29385,26 @@ end % \end{markdown} % \begin{macrocode} self.initialize_named_group = function(name, value) + local pattern = Ct("") + if value ~= nil then + pattern = pattern / value + end syntax.InitializeState = syntax.InitializeState - * Cg(Ct("") / value, name) + * Cg(pattern, name) end % \end{macrocode} % \par % \begin{markdown} % +% Add a named group for indentation. +% +% \end{markdown} +% \begin{macrocode} + self.initialize_named_group("indent_info") +% \end{macrocode} +% \par +% \begin{markdown} +% % Apply syntax extensions. % % \end{markdown} @@ -25578,20 +29476,6 @@ end % \par % \begin{markdown} % -% Duplicate the `Inline` rule as `IndentedInline` with the right-hand-side -% terminal symbol `Space` replaced with `OptionalIndent`. -% -% \end{markdown} -% \begin{macrocode} - walkable_syntax["IndentedInline"] = util.table_copy( - walkable_syntax["Inline"]) - self.insert_pattern( - "IndentedInline instead of Space", - "OptionalIndent") -% \end{macrocode} -% \par -% \begin{markdown} -% % Materialize \luamref{walkable_syntax} and merge it into \luamref{syntax} to % produce the complete \acro{peg} grammar of markdown. Whenever a rule exists % in both \luamref{walkable_syntax} and \luamref{syntax}, the rule from @@ -25607,7 +29491,7 @@ end % \begin{markdown} % % Although the interface of the \luamref{reader->insert_pattern} method does -% document this (see Section <#sec:lua-user-extensions>), we allow the +% not document this (see Section <#sec:lua-user-extensions>), we allow the % \luamref{reader->insert_pattern} and \luamref{reader->update_rule} % methods to insert not just \acro{peg} patterns, but also rule names that % reference the \acro{peg} grammar of Markdown. @@ -25667,6 +29551,15 @@ end syntax.AutoLinkRelativeReference = parsers.fail end + if options.contentLevel == "inline" then + syntax[1] = "Inlines" + syntax.Inlines = V("InitializeState") + * parsers.Inline^0 + * ( parsers.spacing^0 + * parsers.eof / "") + syntax.Space = parsers.Space + parsers.blankline / writer.space + end + local blocks_nested_t = util.table_copy(syntax) blocks_nested_t.ExpectedJekyllData = parsers.fail parsers.blocks_nested = Ct(blocks_nested_t) @@ -25681,10 +29574,6 @@ end * parsers.eof / "") parsers.inlines = Ct(inlines_t) - local inlines_no_link_t = util.table_copy(inlines_t) - inlines_no_link_t.Link = parsers.fail - parsers.inlines_no_link = Ct(inlines_no_link_t) - local inlines_no_inline_note_t = util.table_copy(inlines_t) inlines_no_inline_note_t.InlineNote = parsers.fail parsers.inlines_no_inline_note = Ct(inlines_no_inline_note_t) @@ -25699,6 +29588,11 @@ end inlines_nbsp_t.Endline = parsers.NonbreakingEndline inlines_nbsp_t.Space = parsers.NonbreakingSpace parsers.inlines_nbsp = Ct(inlines_nbsp_t) + + local inlines_no_link_or_emphasis_t = util.table_copy(inlines_t) + inlines_no_link_or_emphasis_t.LinkAndEmph = parsers.fail + inlines_no_link_or_emphasis_t.EndlineExceptions = parsers.EndlineExceptions - parsers.eof + parsers.inlines_no_link_or_emphasis = Ct(inlines_no_link_or_emphasis_t) % \end{macrocode} % \par % \begin{markdown} @@ -25831,6 +29725,7 @@ M.extensions.bracketed_spans = function() % \end{markdown} % \begin{macrocode} function self.span(s, attr) + if self.flatten_inlines then return s end return {"\\markdownRendererBracketedSpanAttributeContextBegin", self.attributes(attr), s, @@ -25840,13 +29735,25 @@ M.extensions.bracketed_spans = function() local parsers = self.parsers local writer = self.writer - local Span = parsers.between(parsers.Inline, - parsers.lbracket, - parsers.rbracket) + local span_label = parsers.lbracket + * (Cs((parsers.alphanumeric^1 + + parsers.inticks + + parsers.autolink + + V("InlineHtml") + + ( parsers.backslash * parsers.backslash) + + ( parsers.backslash * (parsers.lbracket + parsers.rbracket) + + V("Space") + V("Endline") + + (parsers.any + - (parsers.newline + parsers.lbracket + parsers.rbracket + + parsers.blankline^2))))^1) + / self.parser_functions.parse_inlines) + * parsers.rbracket + + local Span = span_label * Ct(parsers.attributes) / writer.span - self.insert_pattern("Inline after Emph", + self.insert_pattern("Inline before LinkAndEmph", Span, "Span") end } @@ -25888,11 +29795,24 @@ M.extensions.citations = function(citation_nbsps) % \end{markdown} % \begin{macrocode} function self.citations(text_cites, cites) - local buffer = {"\\markdownRenderer", text_cites and "TextCite" or "Cite", - "{", #cites, "}"} - for _,cite in ipairs(cites) do - buffer[#buffer+1] = {cite.suppress_author and "-" or "+", "{", - cite.prenote or "", "}{", cite.postnote or "", "}{", cite.name, "}"} + local buffer = {} + if self.flatten_inlines then + for _,cite in ipairs(cites) do + if cite.prenote then + table.insert(buffer, {cite.prenote, " "}) + end + table.insert(buffer, cite.name) + if cite.postnote then + table.insert(buffer, {" ", cite.postnote}) + end + end + else + table.insert(buffer, {"\\markdownRenderer", text_cites and "TextCite" or "Cite", + "{", #cites, "}"}) + for _,cite in ipairs(cites) do + table.insert(buffer, {cite.suppress_author and "-" or "+", "{", + cite.prenote or "", "}{", cite.postnote or "", "}{", cite.name, "}"}) + end end return buffer end @@ -25919,42 +29839,51 @@ M.extensions.citations = function(citation_nbsps) = Cs((parsers.alphanumeric^1 + parsers.bracketed + parsers.inticks + + parsers.autolink + + V("InlineHtml") + + V("Space") + V("Endline") + (parsers.anyescaped - - (parsers.rbracket + parsers.blankline^2)) + - (parsers.newline + parsers.rbracket + parsers.blankline^2)) - (parsers.spnl * parsers.dash^-1 * parsers.at))^0) local citation_body_postnote = Cs((parsers.alphanumeric^1 + parsers.bracketed + parsers.inticks + + parsers.autolink + + V("InlineHtml") + + V("Space") + V("Endline") + (parsers.anyescaped - - (parsers.rbracket + parsers.semicolon + - (parsers.newline + parsers.rbracket + parsers.semicolon + parsers.blankline^2)) - (parsers.spnl * parsers.rbracket))^0) local citation_body_chunk = citation_body_prenote - * parsers.spnl * citation_name + * parsers.spnlc * citation_name * (parsers.internal_punctuation - parsers.semicolon)^-1 - * parsers.spnl * citation_body_postnote + * parsers.spnlc * citation_body_postnote local citation_body = citation_body_chunk - * (parsers.semicolon * parsers.spnl + * (parsers.semicolon * parsers.spnlc * citation_body_chunk)^0 local citation_headless_body_postnote = Cs((parsers.alphanumeric^1 + parsers.bracketed + parsers.inticks + + parsers.autolink + + V("InlineHtml") + + V("Space") + V("Endline") + (parsers.anyescaped - - (parsers.rbracket + parsers.at + - (parsers.newline + parsers.rbracket + parsers.at + parsers.semicolon + parsers.blankline^2)) - (parsers.spnl * parsers.rbracket))^0) local citation_headless_body = citation_headless_body_postnote - * (parsers.sp * parsers.semicolon * parsers.spnl + * (parsers.sp * parsers.semicolon * parsers.spnlc * citation_body_chunk)^0 local citations @@ -25983,10 +29912,10 @@ M.extensions.citations = function(citation_nbsps) end local TextCitations - = Ct((parsers.spnl + = Ct((parsers.spnlc * Cc("") * citation_name - * ((parsers.spnl + * ((parsers.spnlc * parsers.lbracket * citation_headless_body * parsers.rbracket) + Cc("")))^1) @@ -25995,7 +29924,7 @@ M.extensions.citations = function(citation_nbsps) end local ParenthesizedCitations - = Ct((parsers.spnl + = Ct((parsers.spnlc * parsers.lbracket * citation_body * parsers.rbracket)^1) @@ -26005,7 +29934,7 @@ M.extensions.citations = function(citation_nbsps) local Citations = TextCitations + ParenthesizedCitations - self.insert_pattern("Inline after Emph", + self.insert_pattern("Inline before LinkAndEmph", Citations, "Citations") self.add_special_character("@") @@ -26018,7 +29947,7 @@ end % %#### Content Blocks % -% The \luamdef{extensions.content_blocks} function implements the iA\,Writer +% The \luamdef{extensions.content_blocks} function implements the iA\\,Writer % content blocks syntax extension. The `language_map` parameter specifies % the filename of the \acro{JSON} file that maps filename extensions to % programming language names. @@ -26068,7 +29997,7 @@ M.extensions.content_blocks = function(language_map) % \begin{markdown} % % Define \luamdef{writer->contentblock} as a function that will transform an -% input iA\,Writer content block to the output format, where `src` +% input iA\\,Writer content block to the output format, where `src` % corresponds to the \acro{uri} prefix, `suf` to the \acro{uri} extension, % `type` to the type of the content block (`localfile` or `onlineimage`), % and `tit` to the title of the content block. @@ -26135,6 +30064,7 @@ M.extensions.content_blocks = function(language_map) = (parsers.less * Cs((parsers.anyescaped - parsers.more + - parsers.spacing - #(parsers.period * onlineimagesuffix * parsers.more @@ -26167,7 +30097,7 @@ M.extensions.content_blocks = function(language_map) * Cc("localfile") local ContentBlock - = parsers.leader + = parsers.check_trail_no_rem * (localfilepath + onlineimageurl) * contentblock_tail / writer.contentblock @@ -26234,18 +30164,26 @@ M.extensions.definition_lists = function(tight_lists) local defstartchar = S("~:") - local defstart = ( defstartchar * #parsers.spacing + local defstart = parsers.check_trail_length(0) * defstartchar * #parsers.spacing * (parsers.tab + parsers.space^-3) - + parsers.space * defstartchar * #parsers.spacing + + parsers.check_trail_length(1) * defstartchar * #parsers.spacing * (parsers.tab + parsers.space^-2) - + parsers.space * parsers.space * defstartchar - * #parsers.spacing + + parsers.check_trail_length(2) * defstartchar * #parsers.spacing * (parsers.tab + parsers.space^-1) - + parsers.space * parsers.space * parsers.space - * defstartchar * #parsers.spacing - ) + + parsers.check_trail_length(3) * defstartchar * #parsers.spacing + + local indented_line = (parsers.check_minimal_indent / "") * parsers.check_code_trail * parsers.line - local dlchunk = Cs(parsers.line * (parsers.indentedline - parsers.blankline)^0) + local blank = parsers.check_minimal_blank_indent_and_any_trail * parsers.optionalspace * parsers.newline + + local dlchunk = Cs(parsers.line * (indented_line - blank)^0) + + local indented_blocks = function(bl) + return Cs( bl + * (blank^1 * (parsers.check_minimal_indent / "") + * parsers.check_code_trail * -parsers.blankline * bl)^0 + * (blank^1 + parsers.eof)) + end local function definition_list_item(term, defs, _) return { term = self.parser_functions.parse_inlines(term), @@ -26253,22 +30191,22 @@ M.extensions.definition_lists = function(tight_lists) end local DefinitionListItemLoose - = C(parsers.line) * parsers.skipblanklines - * Ct((defstart - * parsers.indented_blocks(dlchunk) - / self.parser_functions.parse_blocks_nested)^1) + = C(parsers.line) * blank^0 + * Ct((parsers.check_minimal_indent * (defstart + * indented_blocks(dlchunk) + / self.parser_functions.parse_blocks_nested))^1) * Cc(false) / definition_list_item local DefinitionListItemTight = C(parsers.line) - * Ct((defstart * dlchunk - / self.parser_functions.parse_blocks_nested)^1) + * Ct((parsers.check_minimal_indent * (defstart * dlchunk + / self.parser_functions.parse_blocks_nested))^1) * Cc(true) / definition_list_item local DefinitionList = ( Ct(DefinitionListItemLoose^1) * Cc(false) + Ct(DefinitionListItemTight^1) - * (parsers.skipblanklines + * (blank^0 * -DefinitionListItemLoose * Cc(true)) ) / writer.definitionlist @@ -26322,8 +30260,10 @@ M.extensions.fancy_lists = function() local buffer = {} local num = startnum for _,item in ipairs(items) do - buffer[#buffer + 1] = self.fancyitem(item,num) - if num ~= nil then + if item ~= "" then + buffer[#buffer + 1] = self.fancyitem(item,num) + end + if num ~= nil and item ~= "" then num = num + 1 end end @@ -26361,52 +30301,101 @@ M.extensions.fancy_lists = function() local options = self.options local writer = self.writer - local label = parsers.dig + parsers.letter - local numdelim = parsers.period + parsers.rparent - local enumerator = C(label^3 * numdelim) * #parsers.spacing - + C(label^2 * numdelim) * #parsers.spacing - * (parsers.tab + parsers.space^1) - + C(label * numdelim) * #parsers.spacing - * (parsers.tab + parsers.space^-2) - + parsers.space * C(label^2 * numdelim) - * #parsers.spacing - + parsers.space * C(label * numdelim) - * #parsers.spacing - * (parsers.tab + parsers.space^-1) - + parsers.space * parsers.space * C(label^1 - * numdelim) * #parsers.spacing - local starter = parsers.bullet + enumerator + local function combine_markers_and_delims(markers, delims) + local markers_table = {} + for _,marker in ipairs(markers) do + local start_marker + local continuation_marker + if type(marker) == "table" then + start_marker = marker[1] + continuation_marker = marker[2] + else + start_marker = marker + continuation_marker = marker + end + for _,delim in ipairs(delims) do + table.insert(markers_table, {start_marker, continuation_marker, delim}) + end + end + return markers_table + end + + local function join_table_with_func(func, markers_table) + local pattern = func(table.unpack(markers_table[1])) + for i = 2, #markers_table do + pattern = pattern + func(table.unpack(markers_table[i])) + end + return pattern + end - local NestedList = Cs((parsers.optionallyindentedline - - starter)^1) - / function(a) return "\001"..a end + local lowercase_letter_marker = R("az") + local uppercase_letter_marker = R("AZ") - local ListBlockLine = parsers.optionallyindentedline - - parsers.blankline - (parsers.indent^-1 - * starter) + local roman_marker = function(chars) + local m, d, c = P(chars[1]), P(chars[2]), P(chars[3]) + local l, x, v, i = P(chars[4]), P(chars[5]), P(chars[6]), P(chars[7]) + return m^-3 + * (c*m + c*d + d^-1 * c^-3) + * (x*c + x*l + l^-1 * x^-3) + * (i*x + i*v + v^-1 * i^-3) + end + + local lowercase_roman_marker = roman_marker({"m", "d", "c", "l", "x", "v", "i"}) + local uppercase_roman_marker = roman_marker({"M", "D", "C", "L", "X", "V", "I"}) + + local lowercase_opening_roman_marker = P("i") + local uppercase_opening_roman_marker = P("I") + + local digit_marker = parsers.dig * parsers.dig^-8 - local ListBlock = parsers.line * ListBlockLine^0 + local markers = { + {lowercase_opening_roman_marker, lowercase_roman_marker}, + {uppercase_opening_roman_marker, uppercase_roman_marker}, + lowercase_letter_marker, + uppercase_letter_marker, + lowercase_roman_marker, + uppercase_roman_marker, + digit_marker + } + + local delims = { + parsers.period, + parsers.rparent + } - local ListContinuationBlock = parsers.blanklines * (parsers.indent / "") - * ListBlock + local markers_table = combine_markers_and_delims(markers, delims) + + local function enumerator(start_marker, _, delimiter_type, interrupting) + local delimiter_range + local allowed_end + if interrupting then + delimiter_range = P("1") + allowed_end = C(parsers.spacechar^1) * #parsers.linechar + else + delimiter_range = start_marker + allowed_end = C(parsers.spacechar^1) + #(parsers.newline + parsers.eof) + end + + return parsers.check_trail + * Ct(C(delimiter_range) * C(delimiter_type)) + * allowed_end + end + + local starter = join_table_with_func(enumerator, markers_table) local TightListItem = function(starter) - return -parsers.ThematicBreak - * (Cs(starter / "" * parsers.tickbox^-1 * ListBlock * NestedList^-1) - / self.parser_functions.parse_blocks_nested) - * -(parsers.blanklines * parsers.indent) + return parsers.add_indent(starter, "li") + * parsers.indented_content_tight end local LooseListItem = function(starter) - return -parsers.ThematicBreak - * Cs( starter / "" * parsers.tickbox^-1 * ListBlock * Cc("\n") - * (NestedList + ListContinuationBlock^0) - * (parsers.blanklines / "\n\n") - ) / self.parser_functions.parse_blocks_nested + return parsers.add_indent(starter, "li") + * parsers.indented_content_loose + * remove_indent("li") end local function roman2number(roman) - local romans = { ["L"] = 50, ["X"] = 10, ["V"] = 5, ["I"] = 1 } + local romans = { ["M"] = 1000, ["D"] = 500, ["C"] = 100, ["L"] = 50, ["X"] = 10, ["V"] = 5, ["I"] = 1 } local numeral = 0 local i = 1 @@ -26425,8 +30414,7 @@ M.extensions.fancy_lists = function() return numeral end - local function sniffstyle(itemprefix) - local numstr, delimend = itemprefix:match("^([A-Za-z0-9]*)([.)]*)") + local function sniffstyle(numstr, delimend) local numdelim if delimend == ")" then numdelim = "OneParen" @@ -26435,46 +30423,72 @@ M.extensions.fancy_lists = function() else numdelim = "Default" end - numstr = numstr or itemprefix local num - num = numstr:match("^([IVXL]+)") + num = numstr:match("^([I])$") if num then return roman2number(num), "UpperRoman", numdelim end - num = numstr:match("^([ivxl]+)") + num = numstr:match("^([i])$") if num then return roman2number(string.upper(num)), "LowerRoman", numdelim end - num = numstr:match("^([A-Z])") + num = numstr:match("^([A-Z])$") if num then return string.byte(num) - string.byte("A") + 1, "UpperAlpha", numdelim end - num = numstr:match("^([a-z])") + num = numstr:match("^([a-z])$") if num then return string.byte(num) - string.byte("a") + 1, "LowerAlpha", numdelim end + num = numstr:match("^([IVXLCDM]+)") + if num then + return roman2number(num), "UpperRoman", numdelim + end + num = numstr:match("^([ivxlcdm]+)") + if num then + return roman2number(string.upper(num)), "LowerRoman", numdelim + end return math.floor(tonumber(numstr) or 1), "Decimal", numdelim end local function fancylist(items,tight,start) - local startnum, numstyle, numdelim = sniffstyle(start) + local startnum, numstyle, numdelim = sniffstyle(start[2][1], start[2][2]) return writer.fancylist(items,tight, - options.startNumber and startnum, + options.startNumber and startnum or 1, numstyle or "Decimal", numdelim or "Default") end - local FancyList = Cg(enumerator, "listtype") * - ( Ct(TightListItem(Cb("listtype")) - * TightListItem(enumerator)^0) - * Cc(true) * parsers.skipblanklines * -enumerator - + Ct(LooseListItem(Cb("listtype")) - * LooseListItem(enumerator)^0) - * Cc(false) * parsers.skipblanklines - ) * Cb("listtype") / fancylist + local FancyListOfType = function(start_marker, continuation_marker, delimiter_type) + local enumerator_start = enumerator(start_marker, continuation_marker, delimiter_type) + local enumerator_cont = enumerator(continuation_marker, continuation_marker, delimiter_type) + return Cg(enumerator_start, "listtype") + * (Ct( TightListItem(Cb("listtype")) + * ((parsers.check_minimal_indent / "") * TightListItem(enumerator_cont))^0) + * Cc(true) + * -#((parsers.conditionally_indented_blankline^0 / "") + * parsers.check_minimal_indent * enumerator_cont) + + Ct( LooseListItem(Cb("listtype")) + * ((parsers.conditionally_indented_blankline^0 / "") + * (parsers.check_minimal_indent / "") * LooseListItem(enumerator_cont))^0) + * Cc(false) + ) * Ct(Cb("listtype")) / fancylist + end + + local FancyList = join_table_with_func(FancyListOfType, markers_table) + + local Endline = parsers.newline + * (parsers.check_minimal_indent + * -parsers.EndlineExceptions + + parsers.check_optional_indent + * -parsers.EndlineExceptions + * -starter) + * parsers.spacechar^0 + / writer.soft_line_break self.update_rule("OrderedList", FancyList) + self.update_rule("Endline", Endline) end } end @@ -26523,7 +30537,7 @@ M.extensions.fenced_code = function(blank_before_code_fence, end local name = util.cache_verbatim(options.cacheDir, s) table.insert(buf, {"\\markdownRendererInputFencedCode{", - name,"}{",self.string(i),"}"}) + name,"}{",self.string(i),"}{",self.infostring(i),"}"}) if attr ~= nil then table.insert(buf, "\\markdownRendererFencedCodeAttributeContextEnd") end @@ -26556,18 +30570,52 @@ M.extensions.fenced_code = function(blank_before_code_fence, return #a >= #b and i end - local tilde_infostring - = C((parsers.linechar - - (parsers.spacechar^1 * parsers.newline))^0) + local function strip_enclosing_whitespaces(str) + return str:gsub("^%s*(.-)%s*$", "%1") + end + + local tilde_infostring = Cs(Cs((V("HtmlEntity") + + parsers.anyescaped + - parsers.newline)^0) + / strip_enclosing_whitespaces) - local backtick_infostring - = C((parsers.linechar - - (parsers.backtick - + parsers.spacechar^1 * parsers.newline))^0) + local backtick_infostring = Cs(Cs((V("HtmlEntity") + + (-#(parsers.backslash * parsers.backtick) * parsers.anyescaped) + - parsers.newline + - parsers.backtick)^0) + / strip_enclosing_whitespaces) local fenceindent + + local function has_trail(indent_table) + return indent_table ~= nil and + indent_table.trail ~= nil and + next(indent_table.trail) ~= nil + end + + local function has_indents(indent_table) + return indent_table ~= nil and + indent_table.indents ~= nil and + next(indent_table.indents) ~= nil + end + + local function get_last_indent_name(indent_table) + if has_indents(indent_table) then + return indent_table.indents[#indent_table.indents].name + end + end + + local function count_fenced_start_indent(_, _, indent_table, trail) + local last_indent_name = get_last_indent_name(indent_table) + fenceindent = 0 + if last_indent_name ~= "li" then + fenceindent = #trail + end + return true + end + local fencehead = function(char, infostring) - return C(parsers.nonindentspace) / function(s) fenceindent = #s end + return Cmt(Cb("indent_info") * parsers.check_trail, count_fenced_start_indent) * Cg(char^3, "fencelength") * parsers.optionalspace * infostring @@ -26575,52 +30623,69 @@ M.extensions.fenced_code = function(blank_before_code_fence, end local fencetail = function(char) - return parsers.nonindentspace + return parsers.check_trail_no_rem * Cmt(C(char^3) * Cb("fencelength"), captures_geq_length) * parsers.optionalspace * (parsers.newline + parsers.eof) + parsers.eof end - local fencedline = function(char) - return C(parsers.line - fencetail(char)) - / function(s) - local i = 1 - local remaining = fenceindent - while true do - local c = s:sub(i, i) - if c == " " and remaining > 0 then - remaining = remaining - 1 - i = i + 1 - elseif c == "\t" and remaining > 3 then - remaining = remaining - 4 - i = i + 1 - else - break - end - end - return s:sub(i) - end + local function process_fenced_line(s, i, indent_table, line_content, is_blank) -- luacheck: ignore s i + local remainder = "" + if has_trail(indent_table) then + remainder = indent_table.trail.internal_remainder + end + + if is_blank and get_last_indent_name(indent_table) == "li" then + remainder = "" + end + + local str = remainder .. line_content + local index = 1 + local remaining = fenceindent + + while true do + local c = str:sub(index, index) + if c == " " and remaining > 0 then + remaining = remaining - 1 + index = index + 1 + elseif c == "\t" and remaining > 3 then + remaining = remaining - 4 + index = index + 1 + else + break + end + end + + return true, str:sub(index) end + local fencedline = function(char) + return Cmt(Cb("indent_info") * C(parsers.line - fencetail(char)) * Cc(false), process_fenced_line) + end + + local blankfencedline = Cmt(Cb("indent_info") * C(parsers.blankline) * Cc(true), process_fenced_line) + local TildeFencedCode = fencehead(parsers.tilde, tilde_infostring) - * Cs(fencedline(parsers.tilde)^0) - * fencetail(parsers.tilde) + * Cs(((parsers.check_minimal_blank_indent / "") * blankfencedline + + (parsers.check_minimal_indent / "") * fencedline(parsers.tilde))^0) + * ((parsers.check_minimal_indent / "") * fencetail(parsers.tilde) + parsers.succeed) local BacktickFencedCode = fencehead(parsers.backtick, backtick_infostring) - * Cs(fencedline(parsers.backtick)^0) - * fencetail(parsers.backtick) + * Cs(((parsers.check_minimal_blank_indent / "") * blankfencedline + + (parsers.check_minimal_indent / "") * fencedline(parsers.backtick))^0) + * ((parsers.check_minimal_indent / "") * fencetail(parsers.backtick) + parsers.succeed) - local infostring_with_attributes - = Ct(C((parsers.linechar - - ( parsers.optionalspace - * parsers.attributes))^0) - * parsers.optionalspace - * Ct(parsers.attributes)) + local infostring_with_attributes + = Ct(C((parsers.linechar + - ( parsers.optionalspace + * parsers.attributes))^0) + * parsers.optionalspace + * Ct(parsers.attributes)) local FencedCode - = (TildeFencedCode + BacktickFencedCode) + = ((TildeFencedCode + BacktickFencedCode) / function(infostring, code) local expanded_code = self.expandtabs(code) @@ -26641,7 +30706,7 @@ M.extensions.fenced_code = function(blank_before_code_fence, end end return writer.fencedCode(expanded_code, infostring, attr) - end + end) self.insert_pattern("Block after Verbatim", FencedCode, "FencedCode") @@ -26695,7 +30760,7 @@ M.extensions.fenced_divs = function(blank_before_div_fence) function self.div_begin(attributes) local start_output = {"\\markdownRendererFencedDivAttributeContextBegin\n", self.attributes(attributes)} - local end_output = {"\n\\markdownRendererFencedDivAttributeContextEnd "} + local end_output = {"\\markdownRendererFencedDivAttributeContextEnd "} return self.push_attributes("div", attributes, start_output, end_output) end % \end{macrocode} @@ -26760,6 +30825,31 @@ M.extensions.fenced_divs = function(blank_before_div_fence) return Cg( Cmt(Cb("div_level"), update_div_level) , "div_level") end + + local non_fenced_div_block = parsers.check_minimal_indent * V("Block") + - parsers.check_minimal_indent_and_trail * fenced_div_end + + local non_fenced_div_paragraph = parsers.check_minimal_indent * V("Paragraph") + - parsers.check_minimal_indent_and_trail * fenced_div_end + + local blank = parsers.minimally_indented_blank + + local block_separated = parsers.block_sep_group(blank) + * non_fenced_div_block + + local loop_body_pair = parsers.create_loop_body_pair(block_separated, + non_fenced_div_paragraph, + parsers.block_sep_group(blank), + parsers.par_sep_group(blank)) + + local content_loop = ( non_fenced_div_block + * loop_body_pair.block^0 + + non_fenced_div_paragraph + * block_separated + * loop_body_pair.block^0 + + non_fenced_div_paragraph + * loop_body_pair.par^0) + * blank^0 local FencedDiv = fenced_div_begin / function (infostring) @@ -26772,14 +30862,9 @@ M.extensions.fenced_divs = function(blank_before_div_fence) / writer.div_begin * increment_div_level(1) * parsers.skipblanklines - * Ct( (V("Block") - fenced_div_end)^-1 - * ( parsers.blanklines - / function() - return writer.interblocksep - end - * (V("Block") - fenced_div_end))^0) - * parsers.skipblanklines - * fenced_div_end * increment_div_level(-1) + * Ct(content_loop) + * parsers.minimally_indented_blank^0 + * parsers.check_minimal_indent_and_trail * fenced_div_end * increment_div_level(-1) * (Cc("") / writer.div_end) self.insert_pattern("Block after Verbatim", @@ -26791,8 +30876,9 @@ M.extensions.fenced_divs = function(blank_before_div_fence) % \par % \begin{markdown} % -% Patch blockquotes, so that they allow the end of a fenced div immediately -% afterwards. +% If the `blank_before_div_fence` parameter is `false`, we will have the +% closing div at the beginning of a line break the current paragraph if +% we are currently nested in a div. % % \end{markdown} % \begin{macrocode} @@ -26804,23 +30890,6 @@ M.extensions.fenced_divs = function(blank_before_div_fence) local is_inside_div = Cmt(Cb("div_level"), check_div_level) local fencestart = is_inside_div * fenced_div_end - self.update_rule("BlockquoteExceptions", function(previous_pattern) - if previous_pattern == nil then - previous_pattern = parsers.BlockquoteExceptions - end - return previous_pattern + fencestart - end) - -% \end{macrocode} -% \par -% \begin{markdown} -% -% If the `blank_before_div_fence` parameter is `false`, we will have the -% closing div at the beginning of a line break the current paragraph if -% we are currently nested in a div. -% -% \end{markdown} -% \begin{macrocode} if not blank_before_div_fence then self.update_rule("EndlineExceptions", function(previous_pattern) if previous_pattern == nil then @@ -26850,47 +30919,58 @@ M.extensions.header_attributes = function() local parsers = self.parsers local writer = self.writer + local function strip_atx_end(s) + return s:gsub("%s+#*%s*$","") + end + local AtxHeading = Cg(parsers.heading_start, "level") * parsers.optionalspace * (C(((parsers.linechar - - ((parsers.hash^1 + - (parsers.attributes * parsers.optionalspace - * parsers.attributes^-1 - + parsers.attributes) - * parsers.optionalspace - * parsers.newline)) - * (parsers.linechar - - parsers.hash - - parsers.lbrace)^0)^1) - / self.parser_functions.parse_inlines) + * parsers.newline)) + * (parsers.linechar + - parsers.lbrace)^0)^1) + / strip_atx_end + / parsers.parse_heading_text) * Cg(Ct(parsers.newline - + (parsers.hash^1 + + (parsers.attributes * parsers.optionalspace - * parsers.attributes^-1 - + parsers.attributes) - * parsers.optionalspace - * parsers.newline), "attributes") + * parsers.newline)), "attributes") * Cb("level") * Cb("attributes") / writer.heading - local SetextHeading = #(parsers.line * S("=-")) - * (C(((parsers.linechar - - (parsers.attributes - * parsers.optionalspace - * parsers.newline)) - * (parsers.linechar - - parsers.lbrace)^0)^1) - / self.parser_functions.parse_inlines) - * Cg(Ct(parsers.newline - + (parsers.attributes - * parsers.optionalspace - * parsers.newline)), "attributes") - * parsers.heading_level - * Cb("attributes") - * parsers.optionalspace - * parsers.newline - / writer.heading + local function strip_trailing_spaces(s) + return s:gsub("%s*$","") + end + + local heading_line = (parsers.linechar + - (parsers.attributes + * parsers.optionalspace + * parsers.newline))^1 + - parsers.thematic_break_lines + + local heading_text = heading_line + * ((V("Endline") / "\n") * (heading_line - parsers.heading_level))^0 + * parsers.newline^-1 + + local SetextHeading = parsers.freeze_trail * parsers.check_trail_no_rem + * #(heading_text + * (parsers.attributes + * parsers.optionalspace + * parsers.newline)^-1 + * parsers.check_minimal_indent * parsers.check_trail * parsers.heading_level) + * Cs(heading_text) / strip_trailing_spaces + / parsers.parse_heading_text + * Cg(Ct((parsers.attributes + * parsers.optionalspace + * parsers.newline)^-1), "attributes") + * parsers.check_minimal_indent_and_trail * parsers.heading_level + * Cb("attributes") + * parsers.newline + * parsers.unfreeze_trail + / writer.heading local Heading = AtxHeading + SetextHeading self.update_rule("Heading", Heading) @@ -26985,6 +31065,48 @@ end % \end{macrocode} % \begin{markdown} % +%#### Marked text +% +% The \luamdef{extensions.mark} function implements the Pandoc mark syntax +% extension. +% +% \end{markdown} +% \begin{macrocode} +M.extensions.mark = function() + return { + name = "built-in mark syntax extension", + extend_writer = function(self) +% \end{macrocode} +% \par +% \begin{markdown} +% +% Define \luamdef{writer->mark} as a function that will transform an input +% marked text `s` to the output format. +% +% \end{markdown} +% \begin{macrocode} + function self.mark(s) + if self.flatten_inlines then return s end + return {"\\markdownRendererMark{", s, "}"} + end + end, extend_reader = function(self) + local parsers = self.parsers + local writer = self.writer + + local doubleequals = P("==") + + local Mark = parsers.between(V("Inline"), doubleequals, doubleequals) + / function (inlines) return writer.mark(inlines) end + + self.add_special_character("=") + self.insert_pattern("Inline before LinkAndEmph", + Mark, "Mark") + end + } +end +% \end{macrocode} +% \begin{markdown} +% %#### Link Attributes % % The \luamdef{extensions.link_attributes} function implements the Pandoc @@ -26998,7 +31120,6 @@ M.extensions.link_attributes = function() extend_writer = function() end, extend_reader = function(self) local parsers = self.parsers - local writer = self.writer local options = self.options % \end{macrocode} @@ -27008,13 +31129,15 @@ M.extensions.link_attributes = function() % % \end{markdown} % \begin{macrocode} - - local define_reference_parser = parsers.define_reference_parser - * ( parsers.spnl - * Ct(parsers.attributes))^-1 + local define_reference_parser = (parsers.check_trail / "") * parsers.link_label * parsers.colon + * parsers.spnlc * parsers.url + * ( parsers.spnlc_sep * parsers.title * (parsers.spnlc * Ct(parsers.attributes)) + * parsers.only_blank + + parsers.spnlc_sep * parsers.title * parsers.only_blank + + Cc("") * (parsers.spnlc * Ct(parsers.attributes)) * parsers.only_blank + + Cc("") * parsers.only_blank) local ReferenceWithAttributes = define_reference_parser - * parsers.blankline^1 / self.register_link self.update_rule("Reference", ReferenceWithAttributes) @@ -27027,93 +31150,10 @@ M.extensions.link_attributes = function() % \end{markdown} % \begin{macrocode} - local function indirect_link(label, sps, tag, - attribute_text, - attributes) - return writer.defer_call(function() - local r, fallback = self.lookup_reference(label, sps, tag, - attributes) - if r then - return writer.link( - self.parser_functions.parse_inlines_no_link(label), - r.url, r.title, r.attributes) - else - local buf = {fallback} - if attributes then - table.insert(buf, writer.string(attribute_text)) - end - return buf - end - end) - end - - local DirectLinkWithAttributes = parsers.direct_link - * (Ct(parsers.attributes))^-1 - / writer.link - - local IndirectLinkWithAttributes = parsers.indirect_link - * (C(Ct(parsers.attributes)))^-1 - / indirect_link + local LinkWithAttributesAndEmph = Ct(parsers.link_and_emph_table * Cg(Cc(true), "match_link_attributes")) + / self.defer_link_and_emphasis_processing - local LinkWithAttributes = DirectLinkWithAttributes - + IndirectLinkWithAttributes - -% \end{macrocode} -% \begin{markdown} -% -% Here, we directly update the `Link` grammar rule to keep the -% method \luamref{reader->parser_functions.parse_inlines_no_link} -% aware of `LinkWithAttributes` and prevent nested links. -% -% If we used \luamref{reader->insert_pattern} instead of -% \luamref{reader->update_rule}, this correspondence would have -% been lost and link text would be able to contain nested links. -% -% \end{markdown} -% \begin{macrocode} - self.update_rule("Link", LinkWithAttributes) - -% \end{macrocode} -% \begin{markdown} -% -% The following patterns define direct and indirect images with attributes. -% -% \end{markdown} -% \begin{macrocode} - - local function indirect_image(label, sps, tag, - attribute_text, - attributes) - return writer.defer_call(function() - local r, fallback = self.lookup_reference(label, sps, tag, - attributes) - if r then - return writer.image(writer.string(label), - r.url, r.title, r.attributes) - else - local buf = {"!", fallback} - if attributes then - table.insert(buf, writer.string(attribute_text)) - end - return buf - end - end) - end - - local DirectImageWithAttributes = parsers.direct_image - * Ct(parsers.attributes) - / writer.image - - local IndirectImageWithAttributes = parsers.indirect_image - * C(Ct(parsers.attributes)) - / indirect_image - - local ImageWithAttributes = DirectImageWithAttributes - + IndirectImageWithAttributes - - self.insert_pattern("Inline before Image", - ImageWithAttributes, - "ImageWithAttributes") + self.update_rule("LinkAndEmph", LinkWithAttributesAndEmph) % \end{macrocode} % \begin{markdown} @@ -27122,7 +31162,6 @@ M.extensions.link_attributes = function() % % \end{markdown} % \begin{macrocode} - local AutoLinkUrlWithAttributes = parsers.auto_link_url * Ct(parsers.attributes) @@ -27186,6 +31225,7 @@ M.extensions.notes = function(notes, inline_notes) % \end{markdown} % \begin{macrocode} function self.note(s) + if self.flatten_inlines then return "" end return {"\\markdownRendererNote{",s,"}"} end end, extend_reader = function(self) @@ -27195,10 +31235,10 @@ M.extensions.notes = function(notes, inline_notes) if inline_notes then local InlineNote = parsers.circumflex - * (parsers.tag / self.parser_functions.parse_inlines_no_inline_note) + * (parsers.link_label / self.parser_functions.parse_inlines_no_inline_note) / writer.note - self.insert_pattern("Inline after Emph", + self.insert_pattern("Inline after LinkAndEmph", InlineNote, "InlineNote") end if notes then @@ -27208,7 +31248,7 @@ M.extensions.notes = function(notes, inline_notes) local RawNoteRef = #(parsers.lbracket * parsers.circumflex) - * parsers.tag / strip_first_char + * parsers.link_label / strip_first_char local rawnotes = {} @@ -27227,21 +31267,36 @@ M.extensions.notes = function(notes, inline_notes) end local function register_note(ref,rawnote) - rawnotes[self.normalize_tag(ref)] = rawnote + local normalized_tag = self.normalize_tag(ref) + if rawnotes[normalized_tag] == nil then + rawnotes[normalized_tag] = rawnote + end return "" end local NoteRef = RawNoteRef / lookup_note + local optionally_indented_line = parsers.check_optional_indent_and_any_trail * parsers.line + + local blank = parsers.check_optional_blank_indent_and_any_trail * parsers.optionalspace * parsers.newline + + local chunk = Cs(parsers.line * (optionally_indented_line - blank)^0) + + local indented_blocks = function(bl) + return Cs( bl + * (blank^1 * (parsers.check_optional_indent / "") + * parsers.check_code_trail * -parsers.blankline * bl)^0) + end + local NoteBlock - = parsers.leader * RawNoteRef * parsers.colon - * parsers.spnl * parsers.indented_blocks(parsers.chunk) + = parsers.check_trail_no_rem * RawNoteRef * parsers.colon + * parsers.spnlc * indented_blocks(chunk) / register_note - local Blank = NoteBlock + parsers.Blank - self.update_rule("Blank", Blank) + local Reference = NoteBlock + parsers.Reference - self.insert_pattern("Inline after Emph", + self.update_rule("Reference", Reference) + self.insert_pattern("Inline before LinkAndEmph", NoteRef, "NoteRef") end @@ -27257,11 +31312,13 @@ end % The \luamdef{extensions.pipe_table} function implements the \acro{PHP} % Markdown table syntax extension (also known as pipe tables in Pandoc). When % the `table_captions` parameter is `true`, the function also implements the -% Pandoc table caption syntax extension for table captions. +% Pandoc table caption syntax extension for table captions. When the +% `table_attributes` parameter is also `true`, the function also +% allows attributes to be attached to the (possibly empty) table captions. % % \end{markdown} % \begin{macrocode} -M.extensions.pipe_tables = function(table_captions) +M.extensions.pipe_tables = function(table_captions, table_attributes) local function make_pipe_table_rectangular(rows) local num_columns = #rows[2] @@ -27329,10 +31386,18 @@ M.extensions.pipe_tables = function(table_captions) % % \end{markdown} % \begin{macrocode} - function self.table(rows, caption) + function self.table(rows, caption, attributes) if not self.is_writing then return "" end - local buffer = {"\\markdownRendererTable{", - caption or "", "}{", #rows - 1, "}{", #rows[1], "}"} + local buffer = {} + if attributes ~= nil then + table.insert(buffer, + "\\markdownRendererTableAttributeContextBegin\n") + table.insert(buffer, self.attributes(attributes)) + end + table.insert(buffer, + {"\\markdownRendererTable{", + caption or "", "}{", #rows - 1, "}{", + #rows[1], "}"}) local temp = rows[2] -- put alignments on the first row rows[2] = rows[1] rows[1] = temp @@ -27349,6 +31414,10 @@ M.extensions.pipe_tables = function(table_captions) end table.insert(buffer, "}") end + if attributes ~= nil then + table.insert(buffer, + "\\markdownRendererTableAttributeContextEnd{}") + end return buffer end end, extend_reader = function(self) @@ -27378,31 +31447,65 @@ M.extensions.pipe_tables = function(table_captions) , table_hline_separator , table_hline_column) - local table_caption_beginning = parsers.skipblanklines - * parsers.nonindentspace + local table_caption_beginning = (parsers.check_minimal_blank_indent_and_any_trail_no_rem + * parsers.optionalspace * parsers.newline)^0 + * parsers.check_minimal_indent_and_trail * (P("Table")^-1 * parsers.colon) * parsers.optionalspace + local function strip_trailing_spaces(s) + return s:gsub("%s*$","") + end + local table_row = pipe_table_row(true , (C((parsers.linechar - parsers.pipe)^1) + / strip_trailing_spaces / self.parser_functions.parse_inlines) , parsers.pipe , (C((parsers.linechar - parsers.pipe)^0) + / strip_trailing_spaces / self.parser_functions.parse_inlines)) local table_caption if table_captions then table_caption = #table_caption_beginning * table_caption_beginning - * Ct(parsers.IndentedInline^1) - * parsers.newline + if table_attributes then + table_caption = table_caption + * (C(((( parsers.linechar + - (parsers.attributes + * parsers.optionalspace + * parsers.newline + * -#( parsers.optionalspace + * parsers.linechar))) + + ( parsers.newline + * #( parsers.optionalspace + * parsers.linechar) + * C(parsers.optionalspace) / writer.space)) + * (parsers.linechar + - parsers.lbrace)^0)^1) + / self.parser_functions.parse_inlines) + * (parsers.newline + + ( Ct(parsers.attributes) + * parsers.optionalspace + * parsers.newline)) + else + table_caption = table_caption + * C(( parsers.linechar + + ( parsers.newline + * #( parsers.optionalspace + * parsers.linechar) + * C(parsers.optionalspace) / writer.space))^1) + / self.parser_functions.parse_inlines + * parsers.newline + end else table_caption = parsers.fail end - local PipeTable = Ct(table_row * parsers.newline - * table_hline - * (parsers.newline * table_row)^0) + local PipeTable = Ct(table_row * parsers.newline * (parsers.check_minimal_indent_and_trail / {}) + * table_hline * parsers.newline + * ((parsers.check_minimal_indent / {}) * table_row * parsers.newline)^0) / make_pipe_table_rectangular * table_caption^-1 / writer.table @@ -27439,6 +31542,7 @@ M.extensions.raw_inline = function() % \begin{macrocode} function self.rawInline(s, attr) if not self.is_writing then return "" end + if self.flatten_inlines then return s end local name = util.cache_verbatim(options.cacheDir, s) return {"\\markdownRendererInputRawInline{", name,"}{", self.string(attr),"}"} @@ -27479,6 +31583,7 @@ M.extensions.strike_through = function() % \end{markdown} % \begin{macrocode} function self.strike_through(s) + if self.flatten_inlines then return s end return {"\\markdownRendererStrikeThrough{",s,"}"} end end, extend_reader = function(self) @@ -27490,7 +31595,7 @@ M.extensions.strike_through = function() parsers.doubletildes) ) / writer.strike_through - self.insert_pattern("Inline after Emph", + self.insert_pattern("Inline after LinkAndEmph", StrikeThrough, "StrikeThrough") self.add_special_character("~") @@ -27521,6 +31626,7 @@ M.extensions.subscripts = function() % \end{markdown} % \begin{macrocode} function self.subscript(s) + if self.flatten_inlines then return s end return {"\\markdownRendererSubscript{",s,"}"} end end, extend_reader = function(self) @@ -27531,7 +31637,7 @@ M.extensions.subscripts = function() parsers.between(parsers.Str, parsers.tilde, parsers.tilde) ) / writer.subscript - self.insert_pattern("Inline after Emph", + self.insert_pattern("Inline after LinkAndEmph", Subscript, "Subscript") self.add_special_character("~") @@ -27562,6 +31668,7 @@ M.extensions.superscripts = function() % \end{markdown} % \begin{macrocode} function self.superscript(s) + if self.flatten_inlines then return s end return {"\\markdownRendererSuperscript{",s,"}"} end end, extend_reader = function(self) @@ -27572,7 +31679,7 @@ M.extensions.superscripts = function() parsers.between(parsers.Str, parsers.circumflex, parsers.circumflex) ) / writer.superscript - self.insert_pattern("Inline after Emph", + self.insert_pattern("Inline after LinkAndEmph", Superscript, "Superscript") self.add_special_character("^") @@ -27582,7 +31689,7 @@ end % \end{macrocode} % \begin{markdown} % -%#### Tex Math +%#### \TeX{} Math % % The \luamdef{extensions.tex_math} function implements the Pandoc math % syntax extensions. @@ -27605,7 +31712,7 @@ M.extensions.tex_math = function(tex_math_dollars, % \end{markdown} % \begin{macrocode} function self.display_math(s) - if not self.is_writing then return "" end + if self.flatten_inlines then return s end return {"\\markdownRendererDisplayMath{",self.math(s),"}"} end % \end{macrocode} @@ -27617,7 +31724,7 @@ M.extensions.tex_math = function(tex_math_dollars, % \end{markdown} % \begin{macrocode} function self.inline_math(s) - if not self.is_writing then return "" end + if self.flatten_inlines then return s end return {"\\markdownRendererInlineMath{",self.math(s),"}"} end end, extend_reader = function(self) @@ -27625,11 +31732,19 @@ M.extensions.tex_math = function(tex_math_dollars, local writer = self.writer local function between(p, starter, ender) - return (starter * C(p * (p - ender)^0) * ender) + return (starter * Cs(p * (p - ender)^0) * ender) + end + + local function strip_preceding_whitespaces(str) + return str:gsub("^%s*(.-)$", "%1") end local allowed_before_closing = B( parsers.backslash * parsers.any - + parsers.any * (parsers.nonspacechar - parsers.backslash)) + + parsers.any * (parsers.any - parsers.backslash)) + + local allowed_before_closing_no_space = B( parsers.backslash * parsers.any + + parsers.any * (parsers.nonspacechar - parsers.backslash)) + % \end{macrocode} % \begin{markdown} % @@ -27637,19 +31752,20 @@ M.extensions.tex_math = function(tex_math_dollars, % % \end{markdown} % \begin{macrocode} - local dollar_math_content = parsers.backslash^-1 - * parsers.any + local dollar_math_content = (parsers.newline * (parsers.check_optional_indent / "") + + parsers.backslash^-1 + * parsers.linechar) - parsers.blankline^2 - parsers.dollar local inline_math_opening_dollars = parsers.dollar * #(parsers.nonspacechar) - local inline_math_closing_dollars = allowed_before_closing + local inline_math_closing_dollars = allowed_before_closing_no_space * parsers.dollar * -#(parsers.digit) - local inline_math_dollars = between(C( dollar_math_content), + local inline_math_dollars = between(Cs( dollar_math_content), inline_math_opening_dollars, inline_math_closing_dollars) @@ -27659,7 +31775,7 @@ M.extensions.tex_math = function(tex_math_dollars, local display_math_closing_dollars = parsers.dollar * parsers.dollar - local display_math_dollars = between(C( dollar_math_content), + local display_math_dollars = between(Cs( dollar_math_content), display_math_opening_dollars, display_math_closing_dollars) % \end{macrocode} @@ -27670,7 +31786,8 @@ M.extensions.tex_math = function(tex_math_dollars, % % \end{markdown} % \begin{macrocode} - local backslash_math_content = parsers.any + local backslash_math_content = (parsers.newline * (parsers.check_optional_indent / "") + + parsers.linechar) - parsers.blankline^2 % \end{macrocode} % \begin{markdown} @@ -27683,29 +31800,32 @@ M.extensions.tex_math = function(tex_math_dollars, local inline_math_opening_double = parsers.backslash * parsers.backslash * parsers.lparent - * #(parsers.nonspacechar) local inline_math_closing_double = allowed_before_closing + * parsers.spacechar^0 * parsers.backslash * parsers.backslash * parsers.rparent - local inline_math_double = between(C( backslash_math_content), + local inline_math_double = between(Cs( backslash_math_content), inline_math_opening_double, inline_math_closing_double) + / strip_preceding_whitespaces local display_math_opening_double = parsers.backslash * parsers.backslash * parsers.lbracket local display_math_closing_double = allowed_before_closing + * parsers.spacechar^0 * parsers.backslash * parsers.backslash * parsers.rbracket - local display_math_double = between(C( backslash_math_content), + local display_math_double = between(Cs( backslash_math_content), display_math_opening_double, display_math_closing_double) + / strip_preceding_whitespaces % \end{macrocode} % \begin{markdown} % @@ -27716,26 +31836,29 @@ M.extensions.tex_math = function(tex_math_dollars, % \begin{macrocode} local inline_math_opening_single = parsers.backslash * parsers.lparent - * #(parsers.nonspacechar) local inline_math_closing_single = allowed_before_closing + * parsers.spacechar^0 * parsers.backslash * parsers.rparent - local inline_math_single = between(C( backslash_math_content), + local inline_math_single = between(Cs( backslash_math_content), inline_math_opening_single, inline_math_closing_single) + / strip_preceding_whitespaces local display_math_opening_single = parsers.backslash * parsers.lbracket local display_math_closing_single = allowed_before_closing + * parsers.spacechar^0 * parsers.backslash * parsers.rbracket - local display_math_single = between(C( backslash_math_content), + local display_math_single = between(Cs( backslash_math_content), display_math_opening_single, display_math_closing_single) + / strip_preceding_whitespaces local display_math = parsers.fail @@ -27759,7 +31882,7 @@ M.extensions.tex_math = function(tex_math_dollars, local TexMath = display_math / writer.display_math + inline_math / writer.inline_math - self.insert_pattern("Inline after Emph", + self.insert_pattern("Inline after LinkAndEmph", TexMath, "TexMath") if tex_math_dollars then @@ -27961,6 +32084,30 @@ function M.new(options) % \par % \begin{markdown} % +% If the singleton cache contains a conversion function for the same `options`, +% reuse it. +% +% \end{markdown} +% \begin{macrocode} + if options.singletonCache and singletonCache.convert then + for k, v in pairs(defaultOptions) do + if type(v) == "table" then + for i = 1, math.max(#singletonCache.options[k], #options[k]) do + if singletonCache.options[k][i] ~= options[k][i] then + goto miss + end + end + elseif singletonCache.options[k] ~= options[k] then + goto miss + end + end + return singletonCache.convert + end + ::miss:: +% \end{macrocode} +% \par +% \begin{markdown} +% % Apply built-in syntax extensions based on `options`. % % \end{markdown} @@ -28026,9 +32173,14 @@ function M.new(options) table.insert(extensions, line_block_extension) end + if options.mark then + local mark_extension = M.extensions.mark() + table.insert(extensions, mark_extension) + end + if options.pipeTables then local pipe_tables_extension = M.extensions.pipe_tables( - options.tableCaptions) + options.tableCaptions, options.tableAttributes) table.insert(extensions, pipe_tables_extension) end @@ -28062,19 +32214,9 @@ function M.new(options) table.insert(extensions, tex_math_extension) end -% \end{macrocode} -% \begin{markdown} -% -% The footnotes and inlineFootnotes option has been deprecated and will be -% removed in Markdown 3.0.0. -% -% \end{markdown} -% \begin{macrocode} - if options.footnotes or options.inlineFootnotes or - options.notes or options.inlineNotes then + if options.notes or options.inlineNotes then local notes_extension = M.extensions.notes( - options.footnotes or options.notes, - options.inlineFootnotes or options.inlineNotes) + options.notes, options.inlineNotes) table.insert(extensions, notes_extension) end @@ -28187,14 +32329,51 @@ function M.new(options) % \par % \begin{markdown} % -% Produce and return a conversion function from markdown to plain \TeX. +% Produce a conversion function from markdown to plain \TeX. % % \end{markdown} % \begin{macrocode} local writer = M.writer.new(options) local reader = M.reader.new(writer, options) local convert = reader.finalize_grammar(extensions) - +% \end{macrocode} +% \par +% \begin{markdown} +% +% Force garbage collection to reclaim memory for temporary +% objects created in \luamref{writer.new}, \luamref{reader.new}, +% and \luamref{reader->finalize_grammar}. +% +% \end{markdown} +% \begin{macrocode} + collectgarbage("collect") +% \end{macrocode} +% \par +% \begin{markdown} +% +% Update the singleton cache. +% +% \end{markdown} +% \begin{macrocode} + if options.singletonCache then + local singletonCacheOptions = {} + for k, v in pairs(options) do + singletonCacheOptions[k] = v + end + setmetatable(singletonCacheOptions, + { __index = function (_, key) + return defaultOptions[key] end }) + singletonCache.options = singletonCacheOptions + singletonCache.convert = convert + end +% \end{macrocode} +% \par +% \begin{markdown} +% +% Return the conversion function from markdown to plain \TeX. +% +% \end{markdown} +% \begin{macrocode} return convert end @@ -28246,9 +32425,8 @@ local kpse (function() local should_initialize = package.loaded.kpse == nil or tex.initialize ~= nil - local ran_ok - ran_ok, kpse = pcall(require, "kpse") - if ran_ok and should_initialize then + kpse = require("kpse") + if should_initialize then kpse.set_program_name("luatex") end end)() @@ -28276,6 +32454,14 @@ else assert(io.write(output)) end % \end{macrocode} +% \begin{markdown} +% Remove the `options.cacheDir` directory if it is empty. +% \end{markdown} +% \begin{macrocode} +if options.cacheDir then + lfs.rmdir(options["cacheDir"]) +end +% \end{macrocode} % \iffalse %</lua-cli> %<*tex> @@ -28295,23 +32481,218 @@ end % % \end{markdown} % \begin{macrocode} -\ifx\markdownInfo\undefined - \def\markdownInfo#1{% - \immediate\write-1{(l.\the\inputlineno) markdown.tex info: #1.}}% -\fi -\ifx\markdownWarning\undefined - \def\markdownWarning#1{% - \immediate\write16{(l.\the\inputlineno) markdown.tex warning: #1}}% -\fi -\ifx\markdownError\undefined - \def\markdownError#1#2{% - \errhelp{#2.}% - \errmessage{(l.\the\inputlineno) markdown.tex error: #1}}% -\fi +\ExplSyntaxOn +\cs_if_free:NT + \markdownInfo + { + \cs_new:Npn + \markdownInfo #1 + { + \msg_info:nne + { markdown } + { generic-message } + { #1 } + } + } +\cs_if_free:NT + \markdownWarning + { + \cs_new:Npn + \markdownWarning #1 + { + \msg_warning:nne + { markdown } + { generic-message } + { #1 } + } + } +\cs_if_free:NT + \markdownError + { + \cs_new:Npn + \markdownError #1 #2 + { + \msg_error:nnee + { markdown } + { generic-message-with-help-text } + { #1 } + { #2 } + } + } +\msg_new:nnn + { markdown } + { generic-message } + { #1 } +\msg_new:nnnn + { markdown } + { generic-message-with-help-text } + { #1 } + { #2 } +\cs_generate_variant:Nn + \msg_info:nnn + { nne } +\cs_generate_variant:Nn + \msg_warning:nnn + { nne } +\cs_generate_variant:Nn + \msg_error:nnnn + { nnee } +\ExplSyntaxOff +% \end{macrocode} +% \par +% \begin{markdown} +% +%### Themes {#themes-implementation} +% +% This section implements the theme-loading mechanism and the built-in themes +% provided with the Markdown package. Futhermore, this section also implements +% the built-in plain \TeX{} themes provided with the Markdown package. +% +% \end{markdown} +% \begin{macrocode} +\ExplSyntaxOn +\prop_new:N \g_@@_plain_tex_loaded_themes_linenos_prop +\cs_new:Nn + \@@_plain_tex_load_theme:nn + { + \prop_get:NnNTF + \g_@@_plain_tex_loaded_themes_linenos_prop + { #1 } + \l_tmpa_tl + { + \msg_warning:nnnV + { markdown } + { repeatedly-loaded-plain-tex-theme } + { #1 } + \l_tmpa_tl + } + { + \msg_info:nnn + { markdown } + { loading-plain-tex-theme } + { #1 } + \prop_gput:Nnx + \g_@@_plain_tex_loaded_themes_linenos_prop + { #1 } + { \tex_the:D \tex_inputlineno:D } + \file_input:n + { markdown theme #2 } + } + } +\msg_new:nnn + { markdown } + { loading-plain-tex-theme } + { Loading~plain~TeX~Markdown~theme~#1 } +\msg_new:nnn + { markdown } + { repeatedly-loaded-plain-tex-theme } + { + Plain~TeX~Markdown~theme~#1~was~previously~ + loaded~on~line~#2,~not~loading~it~again + } +\cs_generate_variant:Nn + \prop_gput:Nnn + { Nnx } +\cs_gset_eq:NN + \@@_load_theme:nn + \@@_plain_tex_load_theme:nn +\cs_generate_variant:Nn + \@@_load_theme:nn + { nV } +% \end{macrocode} +% \begin{markdown} +% +% Developers can use the \mref{markdownLoadPlainTeXTheme} macro to load a +% corresponding plain \TeX{} theme from within themes for higher-level \TeX{} +% formats such as \LaTeX{} and \Hologo{ConTeXt}. +% +% \end{markdown} +% \begin{macrocode} +\cs_new:Npn + \markdownLoadPlainTeXTheme + { +% \end{macrocode} +% \begin{markdown} +% +% First, we extract the name of the current theme from the +% \mref{g_@@_current_theme_tl} macro. +% +% \end{markdown} +% \begin{macrocode} + \tl_set:NV + \l_tmpa_tl + \g_@@_current_theme_tl + \tl_reverse:N + \l_tmpa_tl + \tl_set:Ne + \l_tmpb_tl + { + \tl_tail:V + \l_tmpa_tl + } + \tl_reverse:N + \l_tmpb_tl +% \end{macrocode} +% \begin{markdown} +% +% Next, we munge the theme name. +% +% \end{markdown} +% \begin{macrocode} + \str_set:NV + \l_tmpa_str + \l_tmpb_tl + \str_replace_all:Nnn + \l_tmpa_str + { / } + { _ } +% \end{macrocode} +% \begin{markdown} +% +% Finally, we load the plain \TeX{} theme. +% +% \end{markdown} +% \begin{macrocode} + \@@_plain_tex_load_theme:VV + \l_tmpb_tl + \l_tmpa_str + } +\cs_generate_variant:Nn + \tl_set:Nn + { Ne } +\cs_generate_variant:Nn + \@@_plain_tex_load_theme:nn + { VV } +\ExplSyntaxOff % \end{macrocode} +% \iffalse +%</tex> +%<*themes-witiko-tilde> +% \fi % \par % \begin{markdown} % +% The `witiko/tilde` theme redefines the tilde token renderer prototype, +% so that it expands to a non-breaking space: +% +% \end{markdown} +% \begin{macrocode} +\markdownSetup { + rendererPrototypes = { + tilde = {~}, + }, +} +% \end{macrocode} +% \iffalse +%</themes-witiko-tilde> +%<*themes-witiko-markdown-defaults-tex> +% \fi +% \begin{markdown} +% +% The `witiko/markdown/defaults` plain \TeX{} theme provides default +% definitions for token renderer prototypes. See Section +% <#sec:tex-token-renderer-prototypes> for the actual definitions. +% %### Token Renderer Prototypes {#tex-token-renderer-prototypes} % % The following definitions should be considered placeholder. @@ -28319,7 +32700,10 @@ end % \end{markdown} % \begin{macrocode} \def\markdownRendererInterblockSeparatorPrototype{\par}% +\def\markdownRendererParagraphSeparatorPrototype{% + \markdownRendererInterblockSeparator}% \def\markdownRendererHardLineBreakPrototype{\hfil\break}% +\def\markdownRendererSoftLineBreakPrototype{ }% \let\markdownRendererEllipsisPrototype\dots \def\markdownRendererNbspPrototype{~}% \def\markdownRendererLeftBracePrototype{\char`\{}% @@ -28340,7 +32724,7 @@ end \def\markdownRendererContentBlockOnlineImagePrototype{% \markdownRendererImage}% \def\markdownRendererContentBlockCodePrototype#1#2#3#4#5{% - \markdownRendererInputFencedCode{#3}{#2}}% + \markdownRendererInputFencedCode{#3}{#2}{#2}}% \def\markdownRendererImagePrototype#1#2#3#4{#2}% \def\markdownRendererUlBeginPrototype{}% \def\markdownRendererUlBeginTightPrototype{}% @@ -28378,7 +32762,7 @@ end \def\markdownRendererLineBlockEndPrototype{\endgroup}% \def\markdownRendererInputVerbatimPrototype#1{% \par{\tt\input#1\relax{}}\par}% -\def\markdownRendererInputFencedCodePrototype#1#2{% +\def\markdownRendererInputFencedCodePrototype#1#2#3{% \markdownRendererInputVerbatim{#1}}% \def\markdownRendererHeadingOnePrototype#1{#1}% \def\markdownRendererHeadingTwoPrototype#1{#1}% @@ -28431,12 +32815,7 @@ end \markdownRendererHeaderAttributeContextEndPrototype \cs_gset:Npn \markdownRendererReplacementCharacterPrototype - { - % TODO: Replace with `\codepoint_generate:nn` in TeX Live 2023 - \sys_if_engine_pdftex:TF - { ^^ef^^bf^^bd } - { ^^^^fffd } - } + { \codepoint_str_generate:n { fffd } } \ExplSyntaxOff \def\markdownRendererSectionBeginPrototype{}% \def\markdownRendererSectionEndPrototype{}% @@ -28492,7 +32871,7 @@ end % \par % \begin{markdown} % -%#### YAML Metadata Renderer Prototypes {#expl3-yaml-metadata-implementation} +%#### YAML Metadata Renderer Prototypes % % To keep track of the current type of structure we inhabit when we are % traversing a \acro{yaml} document, we will maintain the @@ -28727,6 +33106,35 @@ end } \ExplSyntaxOff % \end{macrocode} +% \iffalse +%</themes-witiko-markdown-defaults-tex> +%<*tex> +% \fi +% \begin{markdown} +% +% If plain \TeX{} is the top layer, we load the `witiko/markdown/defaults` +% plain \TeX{} theme with the default definitions for token renderer +% prototypes unless the option `noDefaults` has been enabled (see Section +% <#sec:plain>). +% +% \end{markdown} +% \begin{macrocode} +\ExplSyntaxOn +\str_if_eq:VVT + \c_@@_top_layer_tl + \c_@@_option_layer_plain_tex_tl + { + \ExplSyntaxOff + \@@_if_option:nF + { noDefaults } + { + \@@_setup:n + {theme = witiko/markdown/defaults} + } + \ExplSyntaxOn + } +\ExplSyntaxOff +% \end{macrocode} % \begin{markdown} % %### Lua Snippets @@ -28849,56 +33257,25 @@ end % \par % \begin{markdown} % -%### Buffering Markdown Input {#buffering} -% -% The -% \mdef{markdownIfOption}`{`\meta{name}`}{`\meta{iftrue}`}{`\meta{iffalse}`}` -% macro is provided for testing, whether the value of -% `\markdownOption`\meta{name} is `true`. If the value is `true`, then -% \meta{iftrue} is expanded, otherwise \meta{iffalse} is expanded. +% The \mdef{markdownCleanup} macro contains the Lua code that is executed +% after any conversion from markdown to plain \TeX{}. % % \end{markdown} % \begin{macrocode} -\ExplSyntaxOn -\prg_new_conditional:Nnn - \@@_if_option:n - { TF, T, F } - { - \@@_get_option_type:nN - { #1 } - \l_tmpa_tl - \str_if_eq:NNF - \l_tmpa_tl - \c_@@_option_type_boolean_tl - { - \msg_error:nnxx - { markdown } - { expected-boolean-option } - { #1 } - { \l_tmpa_tl } - } - \@@_get_option_value:nN - { #1 } - \l_tmpa_tl - \str_if_eq:NNTF - \l_tmpa_tl - \c_@@_option_value_true_tl - { \prg_return_true: } - { \prg_return_false: } - } -\msg_new:nnn - { markdown } - { expected-boolean-option } - { - Option~#1~has~type~#2,~ - but~a~boolean~was~expected. - } -\let\markdownIfOption=\@@_if_option:nTF -\ExplSyntaxOff +\def\markdownCleanup{% +% \end{macrocode} +% \begin{markdown} +% Remove the `options.cacheDir` directory if it is empty. +% \end{markdown} +% \begin{macrocode} + lfs.rmdir(cacheDir) +}% % \end{macrocode} % \par % \begin{markdown} % +%### Buffering Markdown Input {#buffering} +% % The macros \mdef{markdownInputFileStream} and \mdef{markdownOutputFileStream} % contain the number of the input and output file streams that will be used for % the IO operations of the package. @@ -29080,266 +33457,64 @@ end % \par % \begin{markdown} % -% The following two sections of the implementation have been deprecated and -% will be removed in Markdown 3.0.0. The code that corresponds to -% \mref{markdownMode} value of `3` will be the only implementation. +% Use the \pkg{lt3luabridge} library to define the \mdef{markdownLuaExecute} +% macro, which takes in a Lua scripts and expands to the standard output +% produced by its execution. % % \end{markdown} % \begin{macrocode} \ExplSyntaxOn -\int_compare:nT - { \markdownMode = 3 } +\cs_new:Npn + \markdownLuaExecute + #1 { - \markdownInfo{Using~mode~3:~The~lt3luabridge~package} - \file_input:n { lt3luabridge.tex } - \cs_new:Npn - \markdownLuaExecute - { \luabridgeExecute } + \int_compare:nNnT + { \g_luabridge_method_int } + = + { \c_luabridge_method_shell_int } + { + \sys_if_shell_unrestricted:F + { + \sys_if_shell:TF + { + \msg_error:nn + { markdown } + { restricted-shell-access } + } + { + \msg_error:nn + { markdown } + { disabled-shell-access } + } + } + } + \luabridge_now:e + { #1 } + } +\cs_generate_variant:Nn + \msg_new:nnnn + { nnnV } +\tl_set:Nn + \l_tmpa_tl + { + You~may~need~to~run~TeX~with~the~--shell-escape~or~the~ + --enable-write18~flag,~or~write~shell_escape=t~in~the~ + texmf.cnf~file. } +\msg_new:nnnV + { markdown } + { restricted-shell-access } + { Shell~escape~is~restricted } + \l_tmpa_tl +\msg_new:nnnV + { markdown } + { disabled-shell-access } + { Shell~escape~is~disabled } + \l_tmpa_tl \ExplSyntaxOff % \end{macrocode} % \begin{markdown} % -%### Lua Shell Escape Bridge {#lua-bridge} -% -% The following \TeX{} code is intended for \TeX{} engines that do not provide -% direct access to Lua, but expose the shell of the operating system. This -% corresponds to the \mref{markdownMode} values of `0` and `1`. -% -% The \mref{markdownLuaExecute} macro defined here and in Section -% <#sec:direct-lua> are meant to be indistinguishable to the remaining code. -% -% The package assumes that although the user is not using the Lua\TeX{} engine, -% their \TeX{} distribution contains it, and uses shell access to produce and -% execute Lua scripts using the \TeX{}Lua interpreter~[@luatex21, Section -% 4.1.1]. -% -% \end{markdown} -% \begin{macrocode} -\ifnum\markdownMode<2\relax -\ifnum\markdownMode=0\relax - \markdownWarning{Using mode 0: Shell escape via write18 - (deprecated, to be removed in Markdown 3.0.0)}% -\else - \markdownWarning{Using mode 1: Shell escape via os.execute - (deprecated, to be removed in Markdown 3.0.0)}% -\fi -% \end{macrocode} -% \par -% \begin{markdown} -% -% The \mdef{markdownExecuteShellEscape} macro contains the numeric value indicating -% whether the shell access is enabled (`1`), disabled (`0`), or restricted -% (`2`). -% -% Inherit the value of the `\pdfshellescape` (Lua\TeX{}, \Hologo{pdfTeX}) -% or the `\shellescape` (\Hologo{XeTeX}) commands. If neither of these -% commands is defined and Lua is available, attempt to access the -% `status.shell_escape` configuration item. -% -% If you cannot detect, whether the shell access is enabled, act as if it were. -% -% \end{markdown} -% \begin{macrocode} -\ifx\pdfshellescape\undefined - \ifx\shellescape\undefined - \ifnum\markdownMode=0\relax - \def\markdownExecuteShellEscape{1}% - \else - \def\markdownExecuteShellEscape{% - \directlua{tex.sprint(status.shell_escape or "1")}}% - \fi - \else - \let\markdownExecuteShellEscape\shellescape - \fi -\else - \let\markdownExecuteShellEscape\pdfshellescape -\fi -% \end{macrocode} -% \par -% \begin{markdown} -% -% The \mdef{markdownExecuteDirect} macro executes the code it has received as -% its first argument by writing it to the output file stream 18, if Lua is -% unavailable, or by using the Lua `os.execute` method otherwise. -% -% \end{markdown} -% \begin{macrocode} -\ifnum\markdownMode=0\relax - \def\markdownExecuteDirect#1{\immediate\write18{#1}}% -\else - \def\markdownExecuteDirect#1{% - \directlua{os.execute("\luaescapestring{#1}")}}% -\fi -% \end{macrocode} -% \par -% \begin{markdown} -% -% The \mdef{markdownExecute} macro is a wrapper on top of -% \mref{markdownExecuteDirect} that checks the value of -% \mref{markdownExecuteShellEscape} and prints an error message if the shell is -% inaccessible. -% -% \end{markdown} -% \begin{macrocode} -\def\markdownExecute#1{% - \ifnum\markdownExecuteShellEscape=1\relax - \markdownExecuteDirect{#1}% - \else - \markdownError{I can not access the shell}{Either run the TeX - compiler with the --shell-escape or the --enable-write18 flag, - or set shell_escape=t in the texmf.cnf file}% - \fi}% -% \end{macrocode} -% \par -% \begin{markdown} -% -% The \mdef{markdownLuaExecute} macro executes the Lua code it has received as -% its first argument. The Lua code may not directly interact with the \TeX{} -% engine, but it can use the `print` function in the same manner it -% would use the `tex.print` method. -% -% \end{markdown} -% \begin{macrocode} -\begingroup -% \end{macrocode} -% \begin{markdown} -% Swap the category code of the backslash symbol and the pipe symbol, so that -% we may use the backslash symbol freely inside the Lua code. -% \end{markdown} -% \begin{macrocode} - \catcode`|=0% - \catcode`\\=12% - |gdef|markdownLuaExecute#1{% -% \end{macrocode} -% \begin{markdown} -% Create the file \Opt{helperScriptFileName} and fill it with the -% input Lua code prepended with \pkg{kpathsea} initialization, so that Lua -% modules from the \TeX{} distribution are available. -% \end{markdown} -% \begin{macrocode} - |immediate|openout|markdownOutputFileStream=% - |markdownOptionHelperScriptFileName - |markdownInfo{Writing a helper Lua script to the file - "|markdownOptionHelperScriptFileName"}% - |immediate|write|markdownOutputFileStream{% - local ran_ok, error = pcall(function() -% \end{macrocode} -% \begin{markdown} -% -% If \pkg{Kpathsea} has not been loaded before or if Lua\TeX{} has not yet -% been initialized, configure \pkg{Kpathsea} on top of loading it. -% -% \end{markdown} -% \begin{macrocode} - local kpse - (function() - local should_initialize = package.loaded.kpse == nil - or tex.initialize - local ran_ok - ran_ok, kpse = pcall(require, "kpse") - if ran_ok and should_initialize then - kpse.set_program_name("luatex") - end - end)() - #1 - end) -% \end{macrocode} -% \begin{markdown} -% If there was an error, use the file \Opt{errorTempFileName} to -% store the error message. -% \end{markdown} -% \begin{macrocode} - if not ran_ok then - local file = io.open("% - |markdownOptionOutputDir - /|markdownOptionErrorTempFileName", "w") - if file then - file:write(error .. "\n") - file:close() - end - print('\\markdownError{An error was encountered while executing - Lua code}{For further clues, examine the file - "|markdownOptionOutputDir - /|markdownOptionErrorTempFileName"}') - end}% - |immediate|closeout|markdownOutputFileStream -% \end{macrocode} -% \begin{markdown} -% Execute the generated \Opt{helperScriptFileName} Lua script using -% the \TeX{}Lua binary and store the output in the -% \Opt{outputTempFileName} file. -% \end{markdown} -% \begin{macrocode} - |markdownInfo{Executing a helper Lua script from the file - "|markdownOptionHelperScriptFileName" and storing the result in the - file "|markdownOptionOutputTempFileName"}% - |markdownExecute{texlua "|markdownOptionOutputDir - /|markdownOptionHelperScriptFileName" > % - "|markdownOptionOutputDir - /|markdownOptionOutputTempFileName"}% -% \end{macrocode} -% \begin{markdown} -% `\input` the generated \Opt{outputTempFileName} file. -% \end{markdown} -% \begin{macrocode} - |input|markdownOptionOutputTempFileName|relax}% -|endgroup -% \end{macrocode} -% \par -% \begin{markdown} -% -%### Direct Lua Access {#direct-lua} -% -% The following \TeX{} code is intended for \TeX{} engines that provide -% direct access to Lua (Lua\TeX{}). The macro \mref{markdownLuaExecute} defined -% here and in Section <#sec:lua-bridge> are meant to be indistinguishable to -% the remaining code. This corresponds to the \mref{markdownMode} value of `2`. -% -% \end{markdown} -% \begin{macrocode} -\fi -\ifnum\markdownMode=2\relax - \markdownWarning{Using mode 2: Direct Lua access - (deprecated, to be removed in Markdown 3.0.0)}% -% \end{macrocode} -% \par -% \begin{markdown} -% -% The direct Lua access version of the \mref{markdownLuaExecute} macro is defined -% in terms of the `\directlua` primitive. The `print` function is set as -% an alias to the `tex.print` method in order to mimic the behaviour of the -% \mref{markdownLuaExecute} definition from Section <#sec:lua-bridge>, -% -% \end{markdown} -% \begin{macrocode} -\begingroup -% \end{macrocode} -% \begin{markdown} -% Swap the category code of the backslash symbol and the pipe symbol, so that -% we may use the backslash symbol freely inside the Lua code. -% \end{markdown} -% \begin{macrocode} - \catcode`|=0% - \catcode`\\=12% - |gdef|markdownLuaExecute#1{% - |directlua{% - local function print(input) - local output = {} - for line in input:gmatch("[^\r\n]+") do - table.insert(output, line) - end - tex.print(output) - end - #1 - }% - }% -|endgroup -\fi -% \end{macrocode} -% \par -% \begin{markdown} -% %### Typesetting Markdown % % The \mref{markdownInput} macro uses an implementation of the @@ -29413,20 +33588,15 @@ end [[Could not open file "&1" for reading]]) local input = assert(file:read("*a")) assert(file:close()) + print(convert(input)) + |markdownCleanup}% % \end{macrocode} % \begin{markdown} -% Since the Lua converter expects \acro{unix} line endings, normalize the -% input. Also add a line ending at the end of the file in case the input file -% has none. -% \end{markdown} -% \begin{macrocode} - print(convert(input))}% -% \end{macrocode} -% \begin{markdown} -% In case we were finalizing the frozen cache, increment \Opt{frozenCacheCounter}. +% If we are finalizing the frozen cache, increment \Opt{frozenCacheCounter}. % \end{markdown} % \begin{macrocode} - |global|advance|markdownOptionFrozenCacheCounter by 1|relax + |markdownIfOption{finalizeCache}{% + |global|advance|markdownOptionFrozenCacheCounter by 1|relax}{}% }% |endgroup }% @@ -29472,19 +33642,6 @@ end % \par % \begin{markdown} % -% Use reflection to define the `renderers` and `rendererPrototypes` keys of -% \mref{markdownSetup} as well as the keys that correspond to Lua options. -% -% \end{markdown} -% \begin{macrocode} -\ExplSyntaxOn -\@@_latex_define_renderers: -\@@_latex_define_renderer_prototypes: -\ExplSyntaxOff -% \end{macrocode} -% \par -% \begin{markdown} -% %### Logging Facilities % The \LaTeX{} implementation redefines the plain \TeX{} logging macros (see % Section <#sec:tex-interface-logging>) to use the \LaTeX{} `\PackageInfo`, @@ -29519,13 +33676,125 @@ end % % \end{markdown} % \begin{macrocode} -\renewenvironment{markdown}{% - \markdownReadAndConvert@markdown{}}{% - \markdownEnd}% -\renewenvironment{markdown*}[1]{% - \markdownSetup{#1}% - \markdownReadAndConvert@markdown*}{% - \markdownEnd}% +\ExplSyntaxOn +\renewenvironment + { markdown } + { +% \end{macrocode} +% \markdownBegin +% +% In our implementation of the \envmref{markdown} \LaTeX{} environment, we +% want to distinguish between the following two cases: +% ``` tex +% \begin{markdown} [smartEllipses] \begin{markdown} +% \% This is an optional argument ^ [smartEllipses] +% \% ... \% ^ This is link +% \end{markdown} \end{markdown} +% ``````` +% +% Therefore, we cannot use the built-in \LaTeX{} support for environments +% with optional arguments or packages such as \pkg{xparse}. Instead, we +% must read the optional argument manually and prevent reading past the +% end of a line. +% +% To prevent reading past the end of a line when looking for the optional +% argument of the \envmref{markdown} \LaTeX{} environment and accidentally +% tokenizing markdown text, we change the category code of carriage return +% (`\r`, ASCII character 13 in decimal) from 5 (end of line). +% +% While any category code other than 5 (end of line) would work, we switch to +% the category 13 (active), which is also used by the +% \mref{markdownReadAndConvert} macro. This is necessary if we read until the +% end of a line, because then the carriage return character will be +% produced by \TeX{} via the `\endlinechar` plain \TeX{} macro and it needs +% to have the correct category code, so that \mref{markdownReadAndConvert} +% processes it correctly. +% +% \markdownEnd +% \begin{macrocode} + \group_begin: + \char_set_catcode_active:n { 13 } +% \end{macrocode} +% \begin{markdown} +% +% To prevent doubling the hash signs (`#`, ASCII code 35 in decimal), we switch +% its category from 6 (parameter) to 12 (letter). +% +% \end{markdown} +% \begin{macrocode} + \char_set_catcode_letter:n { 35 } +% \end{macrocode} +% \begin{markdown} +% +% After we have matched the opening `[` that begins the optional argument, +% we accept carriage returns as well. +% +% \end{markdown} +% \begin{macrocode} + \peek_regex_replace_once:nnF + { \ *\[([^]]*)\][^\r]* } + { +% \end{macrocode} +% \begin{markdown} +% +% After we have matched the optional argument, we switch back the category +% code of carriage returns and hash signs and we retokenize the content. This +% will cause single new lines to produce a space token and multiple new lines +% to produce `\par` tokens. Furthermore, this will cause hash signs followed +% by a number to be recognized as parameter numbers, which is necessary when +% we use the optional argument to redefine token renderers and token renderer +% prototypes. +% +% \end{markdown} +% \begin{macrocode} + \c { group_end: } + \c { tl_set_rescan:Nnn } \c { l_tmpa_tl } { } { \1 } +% \end{macrocode} +% \begin{markdown} +% +% Then, we pass the retokenized content to the \mref{markdownSetup} macro. +% +% \end{markdown} +% \begin{macrocode} + \c { @@_setup:V } \c { l_tmpa_tl } +% \end{macrocode} +% \begin{markdown} +% +% Finally, regardless of whether or not we have matched the optional argument, +% we let the \mref{markdownReadAndConvert} macro process the rest of the +% \LaTeX{} environment. +% +% \end{markdown} +% \begin{macrocode} + \c { markdownReadAndConvert@markdown } { } + } + { + \group_end: + \markdownReadAndConvert@markdown { } + } + } + { \markdownEnd } +\renewenvironment + { markdown* } + [ 1 ] + { + \msg_warning:nnn + { markdown } + { latex-markdown-star-deprecated } + { #1 } + \@@_setup:n + { #1 } + \markdownReadAndConvert@markdown * + } + { \markdownEnd } +\msg_new:nnn + { markdown } + { latex-markdown-star-deprecated } + { + The~markdown*~LaTeX~environment~has~been~deprecated~and~will~ + be~removed~in~the~next~major~version~of~the~Markdown~package. + } +\ExplSyntaxOff \begingroup % \end{macrocode} % \begin{markdown} @@ -29546,51 +33815,147 @@ end % \par % \begin{markdown} % -%#### \LaTeX{} Themes {#latex-themes-implementation} +%### Options +% The supplied package options are processed using the \mref{markdownSetup} macro. +% +% \end{markdown} +% \begin{macrocode} +\DeclareOption*{% + \expandafter\markdownSetup\expandafter{\CurrentOption}}% +\ProcessOptions\relax +% \end{macrocode} +% \begin{markdown} +% +%### Themes {#latex-themes-implementation} % -% This section implements the theme-loading mechanism and the example themes -% provided with the Markdown package. +% This section overrides the plain \TeX{} implementation of the theme-loading +% mechanism from Section <#sec:themes-implementation>. Futhermore, this section +% also implements the built-in \LaTeX{} themes provided with the Markdown package. % % \end{markdown} % \begin{macrocode} \ExplSyntaxOn +\cs_gset:Nn + \@@_load_theme:nn + { % \end{macrocode} +% \par % \begin{markdown} % -% To keep track of our current place when packages themes have been nested, -% we will maintain the \mdef{g_\@\@_latex_themes_seq} stack of theme names. +% If the Markdown package has already been loaded, determine whether +% a file named `markdowntheme`\meta{munged theme name}`.sty` exists +% and whether we are still in the preamble. % % \end{markdown} % \begin{macrocode} -\newcommand\markdownLaTeXThemeName{} -\seq_new:N \g_@@_latex_themes_seq -\seq_gput_right:NV - \g_@@_latex_themes_seq - \markdownLaTeXThemeName -\newcommand\markdownLaTeXThemeLoad[2]{ - \def\@tempa{% - \def\markdownLaTeXThemeName{#2} - \seq_gput_right:NV - \g_@@_latex_themes_seq - \markdownLaTeXThemeName - \RequirePackage{#1} - \seq_pop_right:NN - \g_@@_latex_themes_seq - \l_tmpa_tl - \seq_get_right:NN - \g_@@_latex_themes_seq - \l_tmpa_tl - \exp_args:NNV - \def - \markdownLaTeXThemeName - \l_tmpa_tl} - \ifmarkdownLaTeXLoaded - \@tempa - \else - \exp_args:No + \ifmarkdownLaTeXLoaded + \ifx\@onlypreamble\@notprerr +% \end{macrocode} +% \begin{markdown} +% +% If both conditions are true does, end with an error, since we cannot load +% \LaTeX{} themes after the preamble. Otherwise, try loading a plain \TeX{} +% theme instead. +% +% \end{markdown} +% \begin{macrocode} + \file_if_exist:nTF + { markdown theme #2.sty } + { + \msg_error:nnn + { markdown } + { latex-theme-after-preamble } + { #1 } + } + { + \@@_plain_tex_load_theme:nn + { #1 } + { #2 } + } + \else +% \end{macrocode} +% \begin{markdown} +% +% If the Markdown package has already been loaded but we are still in the +% preamble, load a \LaTeX{} theme if it exists or load a plain \TeX{} theme +% otherwise. +% +% \end{markdown} +% \begin{macrocode} + \file_if_exist:nTF + { markdown theme #2.sty } + { + \msg_info:nnn + { markdown } + { loading-latex-theme } + { #1 } + \RequirePackage + { markdown theme #2 } + } + { + \@@_plain_tex_load_theme:nn + { #1 } + { #2 } + } + \fi + \else +% \end{macrocode} +% \begin{markdown} +% +% If the Markdown package has not yet been loaded, postpone the loading until +% the Markdown package has finished loading. +% +% \end{markdown} +% \begin{macrocode} + \msg_info:nnn + { markdown } + { theme-loading-postponed } + { #1 } \AtEndOfPackage - { \@tempa } - \fi} + { + \@@_load_theme:nn + { #1 } + { #2 } + } + \fi + } +\msg_new:nnn + { markdown } + { theme-loading-postponed } + { + Postponing~loading~Markdown~theme~#1~until~ + Markdown~package~has~finished~loading + } +\msg_new:nnn + { markdown } + { loading-latex-theme } + { Loading~LaTeX~Markdown~theme~#1 } +\cs_generate_variant:Nn + \msg_new:nnnn + { nnVV } +\tl_set:Nn + \l_tmpa_tl + { Cannot~load~LaTeX~Markdown~theme~#1~after~ } +\tl_put_right:NV + \l_tmpa_tl + \c_backslash_str +\tl_put_right:Nn + \l_tmpa_tl + { begin{document} } +\tl_set:Nn + \l_tmpb_tl + { Load~Markdown~theme~#1~before~ } +\tl_put_right:NV + \l_tmpb_tl + \c_backslash_str +\tl_put_right:Nn + \l_tmpb_tl + { begin{document} } +\msg_new:nnVV + { markdown } + { latex-theme-after-preamble } + \l_tmpa_tl + \l_tmpb_tl \ExplSyntaxOff % \end{macrocode} % \begin{markdown} @@ -29600,7 +33965,7 @@ end % \end{markdown} % \iffalse %</latex> -%<*latex-themes-witiko-dot> +%<*themes-witiko-dot> % \fi % \begin{macrocode} \markdownSetup{fencedCode}% @@ -29632,7 +33997,7 @@ end % % \end{markdown} % \begin{macrocode} -\renewcommand\markdownRendererInputFencedCodePrototype[2]{% +\renewcommand\markdownRendererInputFencedCodePrototype[3]{% \def\next##1 ##2\relax{% \ifthenelse{\equal{##1}{dot}}{% \markdownIfOption{frozenCache}{}{% @@ -29659,14 +34024,14 @@ end % \end{markdown} % \begin{macrocode} }{% - \markdown@witiko@dot@oldRendererInputFencedCodePrototype{#1}{#2}% + \markdown@witiko@dot@oldRendererInputFencedCodePrototype{#1}{#2}{#3}% }% }% \next#2 \relax}% % \end{macrocode} % \iffalse -%</latex-themes-witiko-dot> -%<*latex-themes-witiko-graphicx-http> +%</themes-witiko-dot> +%<*themes-witiko-graphicx-http> % \fi % \par % \begin{markdown} @@ -29802,152 +34167,32 @@ end \endgroup % \end{macrocode} % \iffalse -%</latex-themes-witiko-graphicx-http> -%<*latex-themes-witiko-tilde> +%</themes-witiko-graphicx-http> +%<*themes-witiko-markdown-defaults-latex> % \fi % \par % \begin{markdown} % -% The `witiko/tilde` theme redefines the tilde token renderer prototype, -% so that it expands to a non-breaking space: +% The `witiko/markdown/defaults` \LaTeX{} theme provides default definitions +% for token renderer prototypes. First, the \LaTeX{} theme loads the plain +% \TeX{} theme with the default definitions for plain \TeX{}: % % \end{markdown} % \begin{macrocode} -\renewcommand\markdownRendererTildePrototype{~}% +\markdownLoadPlainTeXTheme % \end{macrocode} -% \iffalse -%</latex-themes-witiko-tilde> -%<*latex> -% \fi % \par % \begin{markdown} % -%### Options -% The supplied package options are processed using the \mref{markdownSetup} macro. -% -% \end{markdown} -% \begin{macrocode} -\DeclareOption*{% - \expandafter\markdownSetup\expandafter{\CurrentOption}}% -\ProcessOptions\relax -% \end{macrocode} -% \begin{markdown} -% -% After processing the options, activate the `jekyllDataRenderes`, `renderers`, -% `rendererPrototypes`, and `code` keys. -% -% \end{markdown} -% \begin{macrocode} -\ExplSyntaxOn -\keys_define:nn - { markdown/latex-options } - { - renderers .code:n = { - \keys_set:nn - { markdown/latex-options/renderers } - { #1 } - }, - } -\@@_with_various_cases:nn - { rendererPrototypes } - { - \keys_define:nn - { markdown/latex-options } - { - #1 .code:n = { - \keys_set:nn - { markdown/latex-options/renderer-prototypes } - { ##1 } - }, - } - } -% \end{macrocode} -% \begin{markdown} -% -% The `code` key is used to immediately expand and execute code, which can be -% especially useful in \LaTeX{} snippets. -% -% \end{markdown} -% \begin{macrocode} -\keys_define:nn - { markdown/latex-options } - { - code .code:n = { #1 }, - } -% \end{macrocode} -% \begin{markdown} -% -% The `jekyllDataRenderers` key can be used as a syntactic sugar for setting -% the `markdown/jekyllData` key--values (see Section <#sec:expl3yamlmetadata>) -% without using the expl3 language. -% -% \end{markdown} -% \begin{macrocode} -\@@_with_various_cases:nn - { jekyllDataRenderers } - { - \keys_define:nn - { markdown/latex-options } - { - #1 .code:n = { - \tl_set:Nn - \l_tmpa_tl - { ##1 } -% \end{macrocode} -% \begin{markdown} -% -% To ensure that keys containing forward slashes get passed correctly, we -% replace all forward slashes in the input with backslash tokens with category -% code letter and then undo the replacement. This means that if any unbraced -% backslash tokens with category code letter exist in the input, they will be -% replaced with forward slashes. However, this should be extremely rare. -% -% \end{markdown} -% \begin{macrocode} - \tl_replace_all:NnV - \l_tmpa_tl - { / } - \c_backslash_str - \keys_set:nV - { markdown/latex-options/jekyll-data-renderers } - \l_tmpa_tl - }, - } - } -\keys_define:nn - { markdown/latex-options/jekyll-data-renderers } - { - unknown .code:n = { - \tl_set_eq:NN - \l_tmpa_tl - \l_keys_key_str - \tl_replace_all:NVn - \l_tmpa_tl - \c_backslash_str - { / } - \tl_put_right:Nn - \l_tmpa_tl - { - .code:n = { #1 } - } - \keys_define:nV - { markdown/jekyllData } - \l_tmpa_tl - } - } -\cs_generate_variant:Nn - \keys_define:nn - { nV } -\ExplSyntaxOff -% \end{macrocode} -% \par -% \begin{markdown} +% Next, the \LaTeX{} theme overrides some of the plain \TeX{} definitions. +% See Section <#sec:latex-token-renderer-prototypes> for the actual +% definitions. % -%### Token Renderer Prototypes {#latex-default-renderer-prototypes} +%### Token Renderer Prototypes {#latex-token-renderer-prototypes} % -% The following configuration should be considered placeholder. If the `plain` -% package option has been enabled (see Section <#sec:latexplain>), none of -% it will take effect. +% The following configuration should be considered placeholder. If the option +% `plain` has been enabled (see Section <#sec:plain>), none of the definitions +% will take effect. % \end{markdown} % \begin{macrocode} \markdownIfOption{plain}{\iffalse}{\iftrue} @@ -29991,7 +34236,7 @@ end { LowerRoman } { \int_to_roman:n { #2 } } { UpperRoman } { \int_to_Roman:n { #2 } } { LowerAlpha } { \int_to_alph:n { #2 } } - { UpperAlpha } { \int_to_alph:n { #2 } } + { UpperAlpha } { \int_to_Alph:n { #2 } } } } \cs_new:Nn @@ -30057,16 +34302,26 @@ end \tl_set:Nn \l_@@_latex_fancy_list_item_label_delimiter_style_tl { #2 } - \tl_set:Nn - \l_tmpa_tl - { \begin{enumerate}[ } - \tl_put_right:Nx - \l_tmpa_tl - { \@@_latex_paralist_style:nn { #1 } { #2 } } - \tl_put_right:Nn + \@@_if_option:nTF + { startNumber } + { + \tl_set:Nn + \l_tmpa_tl + { \begin{enumerate} } + } + { + \tl_set:Nn + \l_tmpa_tl + { \begin{enumerate}[ } + \tl_put_right:Nx + \l_tmpa_tl + { \@@_latex_paralist_style:nn { #1 } { #2 } } + \tl_put_right:Nn + \l_tmpa_tl + { ] } + } + \tl_use:N \l_tmpa_tl - { ] } - \l_tmpa_tl }, fancyOlEnd = { \end{enumerate} @@ -30104,15 +34359,27 @@ end { \plpartopsep=\partopsep \pltopsep=\topsep - \begin{compactenum}[ } - \tl_put_right:Nx - \l_tmpa_tl - { \@@_latex_paralist_style:nn { #1 } { #2 } } - \tl_put_right:Nn + \@@_if_option:nTF + { startNumber } + { + \tl_put_right:Nn + \l_tmpa_tl + { \begin{compactenum} } + } + { + \tl_put_right:Nn + \l_tmpa_tl + { \begin{compactenum}[ } + \tl_put_right:Nx + \l_tmpa_tl + { \@@_latex_paralist_style:nn { #1 } { #2 } } + \tl_put_right:Nn + \l_tmpa_tl + { ] } + } + \tl_use:N \l_tmpa_tl - { ] } - \l_tmpa_tl }, fancyOlEndTight = { \end{compactenum} @@ -30250,7 +34517,7 @@ end image = {% \begin{figure}% \begin{center}% - \includegraphics{#3}% + \includegraphics[alt={#1}]{#3}% \end{center}% \ifx\empty#4\empty\else \caption{#4}% @@ -30267,43 +34534,44 @@ end dlEnd = {\end{description}}, emphasis = {\emph{#1}}, tickedBox = {$\boxtimes$}, - halfTickedBox = {$\boxdot$}, + halfTickedBox = {$\boxdot$}}} % \end{macrocode} % \par % \begin{markdown} % -% If identifier attributes appear at the beginning of a section, we make the -% next heading produce the `\label` macro. +% If identifier attributes appear at the beginning of a section, we make them +% produce the `\label` macro. % % \end{markdown} % \begin{macrocode} - headerAttributeContextBegin = {% - \markdownSetup{ - rendererPrototypes = { - attributeIdentifier = {% - \begingroup - \def\next####1{% - \def####1########1{% - \endgroup - ####1{########1}% - \label{##1}% - }% - }% - \next\markdownRendererHeadingOne - \next\markdownRendererHeadingTwo - \next\markdownRendererHeadingThree - \next\markdownRendererHeadingFour - \next\markdownRendererHeadingFive - \next\markdownRendererHeadingSix - }, - }, - }% +\ExplSyntaxOn +\seq_new:N \l_@@_header_identifiers_seq +\markdownSetup{ + rendererPrototypes = { + headerAttributeContextBegin = { + \seq_clear:N \l_@@_header_identifiers_seq + \markdownSetup + { + renderers = { + attributeIdentifier = { + \seq_put_right:Nn + \l_@@_header_identifiers_seq + { ##1 } + }, + }, + } + }, + headerAttributeContextEnd = { + \seq_map_inline:Nn + \l_@@_header_identifiers_seq + { \label { ##1 } } + }, }, - headerAttributeContextEnd = {}, +} +\ExplSyntaxOff +\markdownSetup{rendererPrototypes={ superscript = {\textsuperscript{#1}}, subscript = {\textsubscript{#1}}, - displayMath = {\begin{displaymath}#1\end{displaymath}}, - inlineMath = {\begin{math}#1\end{math}}, blockQuoteBegin = {\begin{quotation}}, blockQuoteEnd = {\end{quotation}}, inputVerbatim = {\VerbatimInput{#1}}, @@ -30322,7 +34590,7 @@ end \RequirePackage{ltxcmds} \ExplSyntaxOn \cs_gset:Npn - \markdownRendererInputFencedCodePrototype#1#2 + \markdownRendererInputFencedCodePrototype#1#2#3 { \tl_if_empty:nTF { #2 } @@ -30382,7 +34650,7 @@ end % % \end{markdown} % \begin{macrocode} - { \markdownRendererInputFencedCode{#1}{} } + { \markdownRendererInputFencedCode{#1}{}{} } } } } @@ -30419,16 +34687,16 @@ end headingOne = {\section{#1}}, headingTwo = {\subsection{#1}}, headingThree = {\subsubsection{#1}}, - headingFour = {\paragraph{#1}\leavevmode}, - headingFive = {\subparagraph{#1}\leavevmode}}} + headingFour = {\paragraph{#1}}, + headingFive = {\subparagraph{#1}}}} }{% \markdownSetup{rendererPrototypes = { headingOne = {\chapter{#1}}, headingTwo = {\section{#1}}, headingThree = {\subsection{#1}}, headingFour = {\subsubsection{#1}}, - headingFive = {\paragraph{#1}\leavevmode}, - headingSix = {\subparagraph{#1}\leavevmode}}} + headingFive = {\paragraph{#1}}, + headingSix = {\subparagraph{#1}}}} }% % \end{macrocode} % \par @@ -30966,25 +35234,10 @@ end % % \end{markdown} % \begin{macrocode} -% TODO: Remove the command definition in TeX Live 2021. -\providecommand\IfFormatAtLeastTF{\@ifl@t@r\fmtversion} \markdownSetup{ rendererPrototypes = { jekyllDataEnd = { -% TODO: Remove the else branch in TeX Live 2021. - \IfFormatAtLeastTF - { 2020-10-01 } - { \AddToHook{begindocument/end}{\maketitle} } - { - \ifx\@onlypreamble\@notprerr - % We are in the document - \maketitle - \else - % We are in the preamble - \RequirePackage{etoolbox} - \AfterEndPreamble{\maketitle} - \fi - } + \AddToHook{begindocument/end}{\maketitle} }, }, } @@ -31013,18 +35266,18 @@ end % \par % \begin{markdown} % -%#### Strike-Through -% If the \Opt{strikeThrough} option is enabled, we will load the -% \pkg{soulutf8} package and use it to implement strike-throughs. +%#### Marked Text +% If the \Opt{mark} option is enabled, we will load the \pkg{soulutf8} package +% and use it to implement marked text. % % \end{markdown} % \begin{macrocode} -\markdownIfOption{strikeThrough}{% +\markdownIfOption{mark}{% \RequirePackage{soulutf8}% \markdownSetup{ rendererPrototypes = { - strikeThrough = {% - \st{#1}% + mark = {% + \hl{#1}% }, } } @@ -31120,8 +35373,12 @@ end } } \ExplSyntaxOff -\fi % Closes `\markdownIfOption{Plain}{\iffalse}{iftrue}` +\fi % Closes `\markdownIfOption{plain}{\iffalse}{\iftrue}` % \end{macrocode} +% \iffalse +%</themes-witiko-markdown-defaults-latex> +%<*latex> +% \fi % \par % \begin{markdown} % @@ -31186,7 +35443,7 @@ end % \begin{markdown} % %### Typesetting Markdown -% The \mref{inputmarkdown} is defined to accept an optional argument with +% The \mref{inputmarkdown} macro is defined to accept an optional argument with % options recognized by the \Hologo{ConTeXt} interface (see Section % <#sec:context-options>). % @@ -31198,7 +35455,7 @@ end \long\def\doinputmarkdown[#1]#2{% \begingroup \iffirstargument - \setupmarkdown{#1}% + \setupmarkdown[#1]% \fi \markdownInput{#2}% \endgroup}% @@ -31220,48 +35477,108 @@ end % % \end{markdown} % \begin{macrocode} -\ifx\startluacode\undefined % MkII - \begingroup - \catcode`\|=0% - \catcode`\\=12% - |gdef|startmarkdown{% - |markdownReadAndConvert{\stopmarkdown}% - {|stopmarkdown}}% - |gdef|stopmarkdown{% - |markdownEnd}% - |endgroup -\else % MkIV - \startluacode - document.markdown_buffering = false - local function preserve_trailing_spaces(line) - if document.markdown_buffering then - line = line:gsub("[ \t][ \t]$", "\t\t") - end - return line +\startluacode + document.markdown_buffering = false + local function preserve_trailing_spaces(line) + if document.markdown_buffering then + line = line:gsub("[ \t][ \t]$", "\t\t") end - resolvers.installinputlinehandler(preserve_trailing_spaces) - \stopluacode - \begingroup - \catcode`\|=0% - \catcode`\\=12% - |gdef|startmarkdown{% - |ctxlua{document.markdown_buffering = true}% - |markdownReadAndConvert{\stopmarkdown}% - {|stopmarkdown}}% - |gdef|stopmarkdown{% - |ctxlua{document.markdown_buffering = false}% - |markdownEnd}% - |endgroup -\fi + return line + end + resolvers.installinputlinehandler(preserve_trailing_spaces) +\stopluacode +\begingroup + \catcode`\|=0% + \catcode`\\=12% + |gdef|startmarkdown{% + |ctxlua{document.markdown_buffering = true}% + |markdownReadAndConvert{\stopmarkdown}% + {|stopmarkdown}}% + |gdef|stopmarkdown{% + |ctxlua{document.markdown_buffering = false}% + |markdownEnd}% +|endgroup % \end{macrocode} % \par % \begin{markdown} % -%### Token Renderer Prototypes -% The following configuration should be considered placeholder. +%### Themes {#context-themes-implementation} +% +% This section overrides the plain \TeX{} implementation of the theme-loading +% mechanism from Section <#sec:themes-implementation>. Futhermore, this section +% also implements the built-in \Hologo{ConTeXt} themes provided with the +% Markdown package. +% +% \end{markdown} +% \begin{macrocode} +\ExplSyntaxOn +\cs_gset:Nn + \@@_load_theme:nn + { +% \end{macrocode} +% \par +% \begin{markdown} +% +% Determine whether a file named `t-markdowntheme`\meta{munged theme +% name}`.tex` exists. If it does, load it. Otherwise, try loading a plain +% \TeX{} theme instead. % % \end{markdown} % \begin{macrocode} + \file_if_exist:nTF + { t - markdown theme #2.tex } + { + \msg_info:nnn + { markdown } + { loading-context-theme } + { #1 } + \usemodule + [ t ] + [ markdown theme #2 ] + } + { + \@@_plain_tex_load_theme:nn + { #1 } + { #2 } + } + } +\msg_new:nnn + { markdown } + { loading-context-theme } + { Loading~ConTeXt~Markdown~theme~#1 } +\ExplSyntaxOff +% \end{macrocode} +% \iffalse +%</context> +%<*themes-witiko-markdown-defaults-context> +% \fi +% \par +% \begin{markdown} +% +% The `witiko/markdown/defaults` \Hologo{ConTeXt} theme provides default +% definitions for token renderer prototypes. First, the \Hologo{ConTeXt} theme +% loads the plain \TeX{} theme with the default definitions for plain \TeX{}: +% +% \end{markdown} +% \begin{macrocode} +\markdownLoadPlainTeXTheme +% \end{macrocode} +% \par +% \begin{markdown} +% +% Next, the \Hologo{ConTeXt} theme overrides some of the plain \TeX{} definitions. +% See Section <#sec:context-token-renderer-prototypes> for the actual +% definitions. +% +%### Token Renderer Prototypes {#context-token-renderer-prototypes} +% +% The following configuration should be considered placeholder. If the option +% `plain` has been enabled (see Section <#sec:plain>), none of the definitions +% will take effect. +% +% \end{markdown} +% \begin{macrocode} +\markdownIfOption{plain}{\iffalse}{\iftrue} \def\markdownRendererHardLineBreakPrototype{\blank}% \def\markdownRendererLeftBracePrototype{\textbraceleft}% \def\markdownRendererRightBracePrototype{\textbraceright}% @@ -31356,7 +35673,7 @@ end % \begin{macrocode} \ExplSyntaxOn \cs_gset:Npn - \markdownRendererInputFencedCodePrototype#1#2 + \markdownRendererInputFencedCodePrototype#1#2#3 { \tl_if_empty:nTF { #2 } @@ -31416,7 +35733,6 @@ end \def\markdownRendererSuperscriptPrototype#1{\high{#1}} \def\markdownRendererSubscriptPrototype#1{\low{#1}} \def\markdownRendererDisplayMathPrototype#1{\startformula#1\stopformula}% -\def\markdownRendererInlineMathPrototype#1{$#1$}% % \end{macrocode} % \par % \begin{markdown} @@ -31544,8 +35860,30 @@ end \cs_gset_eq:NN \markdownRendererInputRawBlockPrototype \markdownRendererInputRawInlinePrototype +\fi % Closes `\markdownIfOption{plain}{\iffalse}{\iftrue}` \ExplSyntaxOff -\stopmodule\protect +\stopmodule +\protect +% \end{macrocode} +% \iffalse +%</themes-witiko-markdown-defaults-context> +%<*context> +% \fi +% \par +% \begin{markdown} +% +% At the end of the \Hologo{ConTeXt} module, we load the +% `witiko/markdown/defaults` \Hologo{ConTeXt} theme with the default +% definitions for token renderer prototypes unless the option `noDefaults` +% has been enabled (see Section <#sec:plain>). +% +% \end{markdown} +% \begin{macrocode} +\markdownIfOption{noDefaults}{}{ + \setupmarkdown[theme=witiko/markdown/defaults] +} +\stopmodule +\protect % \end{macrocode} % \iffalse %</context> diff --git a/Master/texmf-dist/source/generic/markdown/markdown.ins b/Master/texmf-dist/source/generic/markdown/markdown.ins index de2f350d50f..8e777f4217c 100644 --- a/Master/texmf-dist/source/generic/markdown/markdown.ins +++ b/Master/texmf-dist/source/generic/markdown/markdown.ins @@ -9,10 +9,13 @@ \file{markdown.tex}{\from{markdown.dtx}{tex}} \file{markdown.sty}{\from{markdown.dtx}{latex}} \file{t-markdown.tex}{\from{markdown.dtx}{context}} - \file{markdownthemewitiko_dot.sty}{\from{markdown.dtx}{latex-themes-witiko-dot}} - \file{markdownthemewitiko_graphicx_http.sty}{\from{markdown.dtx}{latex-themes-witiko-graphicx-http}} - \file{markdownthemewitiko_tilde.sty}{\from{markdown.dtx}{latex-themes-witiko-tilde}} - \file{markdownthemewitiko_markdown_techdoc.sty}{\from{markdown.dtx}{latex-themes-witiko-markdown-techdoc}} + \file{markdownthemewitiko_dot.sty}{\from{markdown.dtx}{themes-witiko-dot}} + \file{markdownthemewitiko_graphicx_http.sty}{\from{markdown.dtx}{themes-witiko-graphicx-http}} + \file{markdownthemewitiko_tilde.tex}{\from{markdown.dtx}{themes-witiko-tilde}} + \file{markdownthemewitiko_markdown_defaults.tex}{\from{markdown.dtx}{themes-witiko-markdown-defaults-tex}} + \file{markdownthemewitiko_markdown_defaults.sty}{\from{markdown.dtx}{themes-witiko-markdown-defaults-latex}} + \file{t-markdownthemewitiko_markdown_defaults.tex}{\from{markdown.dtx}{themes-witiko-markdown-defaults-context}} + \file{markdownthemewitiko_markdown_techdoc.sty}{\from{markdown.dtx}{themes-witiko-markdown-techdoc}} \usepreamble\empty \usepostamble\empty \file{markdown.md}{\from{markdown.dtx}{manual}} |