packages feed

pandoc-lua-engine (empty) → 0.1

raw patch · 79 files changed

+10933/−0 lines, 79 filesdep +SHAdep +basedep +bytestringbinary-added

Dependencies added: SHA, base, bytestring, citeproc, containers, data-default, directory, doclayout, doctemplates, exceptions, filepath, hslua, hslua-aeson, hslua-core, hslua-module-doclayout, hslua-module-path, hslua-module-system, hslua-module-text, hslua-module-version, hslua-module-zip, lpeg, mtl, pandoc, pandoc-lua-engine, pandoc-lua-marshal, pandoc-types, parsec, tasty, tasty-golden, tasty-hunit, tasty-lua, text

Files

+ COPYING.md view
@@ -0,0 +1,361 @@+### GNU GENERAL PUBLIC LICENSE++Version 2, June 1991++    Copyright (C) 1989, 1991 Free Software Foundation, Inc.  +    51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA++    Everyone is permitted to copy and distribute verbatim copies+    of this license document, but changing it is not allowed.++### Preamble++The licenses for most software are designed to take away your freedom+to share and change it. By contrast, the GNU General Public License is+intended to guarantee your freedom to share and change free+software--to make sure the software is free for all its users. This+General Public License applies to most of the Free Software+Foundation's software and to any other program whose authors commit to+using it. (Some other Free Software Foundation software is covered by+the GNU Lesser General Public License instead.) You can apply it to+your programs, too.++When we speak of free software, we are referring to freedom, not+price. Our General Public Licenses are designed to make sure that you+have the freedom to distribute copies of free software (and charge for+this service if you wish), that you receive source code or can get it+if you want it, that you can change the software or use pieces of it+in new free programs; and that you know you can do these things.++To protect your rights, we need to make restrictions that forbid+anyone to deny you these rights or to ask you to surrender the rights.+These restrictions translate to certain responsibilities for you if+you distribute copies of the software, or if you modify it.++For example, if you distribute copies of such a program, whether+gratis or for a fee, you must give the recipients all the rights that+you have. You must make sure that they, too, receive or can get the+source code. And you must show them these terms so they know their+rights.++We protect your rights with two steps: (1) copyright the software, and+(2) offer you this license which gives you legal permission to copy,+distribute and/or modify the software.++Also, for each author's protection and ours, we want to make certain+that everyone understands that there is no warranty for this free+software. If the software is modified by someone else and passed on,+we want its recipients to know that what they have is not the+original, so that any problems introduced by others will not reflect+on the original authors' reputations.++Finally, any free program is threatened constantly by software+patents. We wish to avoid the danger that redistributors of a free+program will individually obtain patent licenses, in effect making the+program proprietary. To prevent this, we have made it clear that any+patent must be licensed for everyone's free use or not licensed at+all.++The precise terms and conditions for copying, distribution and+modification follow.++### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION++**0.** This License applies to any program or other work which+contains a notice placed by the copyright holder saying it may be+distributed under the terms of this General Public License. The+"Program", below, refers to any such program or work, and a "work+based on the Program" means either the Program or any derivative work+under copyright law: that is to say, a work containing the Program or+a portion of it, either verbatim or with modifications and/or+translated into another language. (Hereinafter, translation is+included without limitation in the term "modification".) Each licensee+is addressed as "you".++Activities other than copying, distribution and modification are not+covered by this License; they are outside its scope. The act of+running the Program is not restricted, and the output from the Program+is covered only if its contents constitute a work based on the Program+(independent of having been made by running the Program). Whether that+is true depends on what the Program does.++**1.** You may copy and distribute verbatim copies of the Program's+source code as you receive it, in any medium, provided that you+conspicuously and appropriately publish on each copy an appropriate+copyright notice and disclaimer of warranty; keep intact all the+notices that refer to this License and to the absence of any warranty;+and give any other recipients of the Program a copy of this License+along with the Program.++You may charge a fee for the physical act of transferring a copy, and+you may at your option offer warranty protection in exchange for a+fee.++**2.** You may modify your copy or copies of the Program or any+portion of it, thus forming a work based on the Program, and copy and+distribute such modifications or work under the terms of Section 1+above, provided that you also meet all of these conditions:++  +**a)** You must cause the modified files to carry prominent notices+stating that you changed the files and the date of any change.++  +**b)** You must cause any work that you distribute or publish, that in+whole or in part contains or is derived from the Program or any part+thereof, to be licensed as a whole at no charge to all third parties+under the terms of this License.++  +**c)** If the modified program normally reads commands interactively+when run, you must cause it, when started running for such interactive+use in the most ordinary way, to print or display an announcement+including an appropriate copyright notice and a notice that there is+no warranty (or else, saying that you provide a warranty) and that+users may redistribute the program under these conditions, and telling+the user how to view a copy of this License. (Exception: if the+Program itself is interactive but does not normally print such an+announcement, your work based on the Program is not required to print+an announcement.)++These requirements apply to the modified work as a whole. If+identifiable sections of that work are not derived from the Program,+and can be reasonably considered independent and separate works in+themselves, then this License, and its terms, do not apply to those+sections when you distribute them as separate works. But when you+distribute the same sections as part of a whole which is a work based+on the Program, the distribution of the whole must be on the terms of+this License, whose permissions for other licensees extend to the+entire whole, and thus to each and every part regardless of who wrote+it.++Thus, it is not the intent of this section to claim rights or contest+your rights to work written entirely by you; rather, the intent is to+exercise the right to control the distribution of derivative or+collective works based on the Program.++In addition, mere aggregation of another work not based on the Program+with the Program (or with a work based on the Program) on a volume of+a storage or distribution medium does not bring the other work under+the scope of this License.++**3.** You may copy and distribute the Program (or a work based on it,+under Section 2) in object code or executable form under the terms of+Sections 1 and 2 above provided that you also do one of the following:++  +**a)** Accompany it with the complete corresponding machine-readable+source code, which must be distributed under the terms of Sections 1+and 2 above on a medium customarily used for software interchange; or,++  +**b)** Accompany it with a written offer, valid for at least three+years, to give any third party, for a charge no more than your cost of+physically performing source distribution, a complete machine-readable+copy of the corresponding source code, to be distributed under the+terms of Sections 1 and 2 above on a medium customarily used for+software interchange; or,++  +**c)** Accompany it with the information you received as to the offer+to distribute corresponding source code. (This alternative is allowed+only for noncommercial distribution and only if you received the+program in object code or executable form with such an offer, in+accord with Subsection b above.)++The source code for a work means the preferred form of the work for+making modifications to it. For an executable work, complete source+code means all the source code for all modules it contains, plus any+associated interface definition files, plus the scripts used to+control compilation and installation of the executable. However, as a+special exception, the source code distributed need not include+anything that is normally distributed (in either source or binary+form) with the major components (compiler, kernel, and so on) of the+operating system on which the executable runs, unless that component+itself accompanies the executable.++If distribution of executable or object code is made by offering+access to copy from a designated place, then offering equivalent+access to copy the source code from the same place counts as+distribution of the source code, even though third parties are not+compelled to copy the source along with the object code.++**4.** You may not copy, modify, sublicense, or distribute the Program+except as expressly provided under this License. Any attempt otherwise+to copy, modify, sublicense or distribute the Program is void, and+will automatically terminate your rights under this License. However,+parties who have received copies, or rights, from you under this+License will not have their licenses terminated so long as such+parties remain in full compliance.++**5.** You are not required to accept this License, since you have not+signed it. However, nothing else grants you permission to modify or+distribute the Program or its derivative works. These actions are+prohibited by law if you do not accept this License. Therefore, by+modifying or distributing the Program (or any work based on the+Program), you indicate your acceptance of this License to do so, and+all its terms and conditions for copying, distributing or modifying+the Program or works based on it.++**6.** Each time you redistribute the Program (or any work based on+the Program), the recipient automatically receives a license from the+original licensor to copy, distribute or modify the Program subject to+these terms and conditions. You may not impose any further+restrictions on the recipients' exercise of the rights granted herein.+You are not responsible for enforcing compliance by third parties to+this License.++**7.** If, as a consequence of a court judgment or allegation of+patent infringement or for any other reason (not limited to patent+issues), conditions are imposed on you (whether by court order,+agreement or otherwise) that contradict the conditions of this+License, they do not excuse you from the conditions of this License.+If you cannot distribute so as to satisfy simultaneously your+obligations under this License and any other pertinent obligations,+then as a consequence you may not distribute the Program at all. For+example, if a patent license would not permit royalty-free+redistribution of the Program by all those who receive copies directly+or indirectly through you, then the only way you could satisfy both it+and this License would be to refrain entirely from distribution of the+Program.++If any portion of this section is held invalid or unenforceable under+any particular circumstance, the balance of the section is intended to+apply and the section as a whole is intended to apply in other+circumstances.++It is not the purpose of this section to induce you to infringe any+patents or other property right claims or to contest validity of any+such claims; this section has the sole purpose of protecting the+integrity of the free software distribution system, which is+implemented by public license practices. Many people have made+generous contributions to the wide range of software distributed+through that system in reliance on consistent application of that+system; it is up to the author/donor to decide if he or she is willing+to distribute software through any other system and a licensee cannot+impose that choice.++This section is intended to make thoroughly clear what is believed to+be a consequence of the rest of this License.++**8.** If the distribution and/or use of the Program is restricted in+certain countries either by patents or by copyrighted interfaces, the+original copyright holder who places the Program under this License+may add an explicit geographical distribution limitation excluding+those countries, so that distribution is permitted only in or among+countries not thus excluded. In such case, this License incorporates+the limitation as if written in the body of this License.++**9.** The Free Software Foundation may publish revised and/or new+versions of the General Public License from time to time. Such new+versions will be similar in spirit to the present version, but may+differ in detail to address new problems or concerns.++Each version is given a distinguishing version number. If the Program+specifies a version number of this License which applies to it and+"any later version", you have the option of following the terms and+conditions either of that version or of any later version published by+the Free Software Foundation. If the Program does not specify a+version number of this License, you may choose any version ever+published by the Free Software Foundation.++**10.** If you wish to incorporate parts of the Program into other+free programs whose distribution conditions are different, write to+the author to ask for permission. For software which is copyrighted by+the Free Software Foundation, write to the Free Software Foundation;+we sometimes make exceptions for this. Our decision will be guided by+the two goals of preserving the free status of all derivatives of our+free software and of promoting the sharing and reuse of software+generally.++**NO WARRANTY**++**11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO+WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR+OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE+PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.++**12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY+AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE+PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A+FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH+DAMAGES.++### END OF TERMS AND CONDITIONS++### How to Apply These Terms to Your New Programs++If you develop a new program, and you want it to be of the greatest+possible use to the public, the best way to achieve this is to make it+free software which everyone can redistribute and change under these+terms.++To do so, attach the following notices to the program. It is safest to+attach them to the start of each source file to most effectively+convey the exclusion of warranty; and each file should have at least+the "copyright" line and a pointer to where the full notice is found.++    one line to give the program's name and an idea of what it does.+    Copyright (C) yyyy  name of author++    This program is free software; you can redistribute it and/or+    modify it under the terms of the GNU General Public License+    as published by the Free Software Foundation; either version 2+    of the License, or (at your option) any later version.++    This program is distributed in the hope that it will be useful,+    but WITHOUT ANY WARRANTY; without even the implied warranty of+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+    GNU General Public License for more details.++    You should have received a copy of the GNU General Public License+    along with this program; if not, write to the Free Software+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.++Also add information on how to contact you by electronic and paper+mail.++If the program is interactive, make it output a short notice like this+when it starts in an interactive mode:++    Gnomovision version 69, Copyright (C) year name of author+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details+    type `show w'.  This is free software, and you are welcome+    to redistribute it under certain conditions; type `show c' +    for details.++The hypothetical commands \`show w' and \`show c' should show the+appropriate parts of the General Public License. Of course, the+commands you use may be called something other than \`show w' and+\`show c'; they could even be mouse-clicks or menu items--whatever+suits your program.++You should also get your employer (if you work as a programmer) or+your school, if any, to sign a "copyright disclaimer" for the program,+if necessary. Here is a sample; alter the names:++    Yoyodyne, Inc., hereby disclaims all copyright+    interest in the program `Gnomovision'+    (which makes passes at compilers) written +    by James Hacker.++    signature of Ty Coon, 1 April 1989+    Ty Coon, President of Vice++This General Public License does not permit incorporating your program+into proprietary programs. If your program is a subroutine library,+you may consider it more useful to permit linking proprietary+applications with the library. If this is what you want to do, use the+[GNU Lesser General Public+License](https://www.gnu.org/licenses/lgpl.html) instead of this+License.
+ README.md view
@@ -0,0 +1,6 @@+# pandoc-lua-engine++This package provides a Lua pandoc scripting engine based. It+allows to write filters, custom readers, and custom writers in+Lua.+
+ pandoc-lua-engine.cabal view
@@ -0,0 +1,145 @@+cabal-version:       2.4+name:                pandoc-lua-engine+version:             0.1+build-type:          Simple+license:             GPL-2.0-or-later+license-file:        COPYING.md+copyright:           © 2006-2022 John MacFarlane, 2017-2022 Albert Krewinkel+author:              John MacFarlane, Albert Krewinkel+maintainer:          Albert Krewinkel <albert@hslua.org>+bug-reports:         https://github.com/jgm/pandoc/issues+homepage:            https://pandoc.org+category:            Text+tested-with:         GHC == 8.6.5+                   , GHC == 8.8.4+                   , GHC == 8.10.7+                   , GHC == 9.0.2+                   , GHC == 9.2.5+                   , GHC == 9.4.4+synopsis:            Lua engine to power custom pandoc conversions+description:         This package provides a pandoc scripting engine based on+                     Lua.+extra-source-files:  README.md+                   , test/bytestring.bin+                   , test/bytestring.lua+                   , test/bytestring-reader.lua+                   , test/extensions.lua+                   , test/lua/*.lua+                   , test/lua/module/*.lua+                   , test/lua/module/partial.test+                   , test/lua/module/tiny.epub+                   , test/sample.lua+                   , test/tables.custom+                   , test/tables.native+                   , test/testsuite.native+                   , test/writer.custom+                   , test/writer-template.lua+                   , test/writer-template.out.txt++source-repository head+  type:                git+  location:            https://github.com/jgm/pandoc.git+  subdir:              pandoc-lua-engine++common common-options+  default-language:    Haskell2010+  build-depends:       base              >= 4.12 && < 5+  ghc-options:         -Wall -fno-warn-unused-do-bind+                       -Wincomplete-record-updates+                       -Wnoncanonical-monad-instances+                       -Wcpp-undef+                       -Wincomplete-uni-patterns+                       -Widentities+                       -Wpartial-fields+                       -Wmissing-export-lists+                       -Wmissing-signatures+                       -fhide-source-paths++  if impl(ghc >= 8.10)+    ghc-options:         -Wunused-packages++  if impl(ghc >= 9.0)+    ghc-options:         -Winvalid-haddock++library+  import:              common-options+  hs-source-dirs:      src+  exposed-modules:     Text.Pandoc.Lua+  other-modules:       Text.Pandoc.Lua.Custom+                     , Text.Pandoc.Lua.Filter+                     , Text.Pandoc.Lua.Global+                     , Text.Pandoc.Lua.Init+                     , Text.Pandoc.Lua.Marshal.Chunks+                     , Text.Pandoc.Lua.Marshal.CommonState+                     , Text.Pandoc.Lua.Marshal.Context+                     , Text.Pandoc.Lua.Marshal.Format+                     , Text.Pandoc.Lua.Marshal.PandocError+                     , Text.Pandoc.Lua.Marshal.ReaderOptions+                     , Text.Pandoc.Lua.Marshal.Reference+                     , Text.Pandoc.Lua.Marshal.Sources+                     , Text.Pandoc.Lua.Marshal.Template+                     , Text.Pandoc.Lua.Marshal.WriterOptions+                     , Text.Pandoc.Lua.Module.CLI+                     , Text.Pandoc.Lua.Module.Format+                     , Text.Pandoc.Lua.Module.MediaBag+                     , Text.Pandoc.Lua.Module.Pandoc+                     , Text.Pandoc.Lua.Module.Scaffolding+                     , Text.Pandoc.Lua.Module.Structure+                     , Text.Pandoc.Lua.Module.System+                     , Text.Pandoc.Lua.Module.Template+                     , Text.Pandoc.Lua.Module.Types+                     , Text.Pandoc.Lua.Module.Utils+                     , Text.Pandoc.Lua.Orphans+                     , Text.Pandoc.Lua.PandocLua+                     , Text.Pandoc.Lua.Writer.Classic+                     , Text.Pandoc.Lua.Writer.Scaffolding++  build-depends:       SHA                   >= 1.6     && < 1.7+                     , bytestring            >= 0.9     && < 0.12+                     , citeproc              >= 0.8     && < 0.9+                     , containers            >= 0.6.0.1 && < 0.7+                     , data-default          >= 0.4     && < 0.8+                     , doclayout             >= 0.4     && < 0.5+                     , doctemplates          >= 0.11    && < 0.12+                     , exceptions            >= 0.8     && < 0.11+                     , hslua                 >= 2.2.1   && < 2.3+                     , hslua-aeson           >= 2.2.1   && < 2.3+                     , hslua-core            >= 2.2.1   && < 2.3+                     , hslua-module-doclayout>= 1.0.4   && < 1.1+                     , hslua-module-path     >= 1.0.3   && < 1.1+                     , hslua-module-system   >= 1.0     && < 1.1+                     , hslua-module-text     >= 1.0.3   && < 1.1+                     , hslua-module-version  >= 1.0.3   && < 1.1+                     , hslua-module-zip      >= 1.0.0   && < 1.1+                     , lpeg                  >= 1.0.1   && < 1.1+                     , mtl                   >= 2.2     && < 2.4+                     , pandoc                >= 3.0     && < 3.1+                     , pandoc-lua-marshal    >= 0.2     && < 0.3+                     , pandoc-types          >= 1.22    && < 1.24+                     , parsec                >= 3.1     && < 3.2+                     , text                  >= 1.1.1   && < 2.1+++test-suite test-pandoc-lua-engine+  import:              common-options+  type:                exitcode-stdio-1.0+  main-is:             test-pandoc-lua-engine.hs+  hs-source-dirs:      test+  build-depends:       pandoc-lua-engine+                     , bytestring+                     , directory+                     , data-default+                     , exceptions            >= 0.8     && < 0.11+                     , filepath+                     , hslua                 >= 2.1     && < 2.3+                     , pandoc+                     , pandoc-types          >= 1.22    && < 1.24+                     , tasty+                     , tasty-golden+                     , tasty-hunit+                     , tasty-lua             >= 1.0     && < 1.1+                     , text                  >= 1.1.1   && < 2.1+  other-modules:       Tests.Lua+                     , Tests.Lua.Module+                     , Tests.Lua.Reader+                     , Tests.Lua.Writer
+ src/Text/Pandoc/Lua.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications  #-}+{- |+   Module      : Text.Pandoc.Lua+   Copyright   : Copyright © 2017-2023 Albert Krewinkel+   License     : GNU GPL, version 2 or above++   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>+   Stability   : alpha++Running pandoc Lua filters.+-}+module Text.Pandoc.Lua+  ( -- * High-level functions+    applyFilter+  , loadCustom+  -- * Low-level functions+  , Global(..)+  , setGlobals+  , runLua+  , runLuaNoEnv+  -- * Engine+  , getEngine+  ) where++import Control.Monad.IO.Class (MonadIO (liftIO))+import HsLua.Core (getglobal, openlibs, run, top, tostring)+import Text.Pandoc.Error (PandocError)+import Text.Pandoc.Lua.Filter (applyFilter)+import Text.Pandoc.Lua.Global (Global (..), setGlobals)+import Text.Pandoc.Lua.Init (runLua, runLuaNoEnv)+import Text.Pandoc.Lua.Custom (loadCustom)+import Text.Pandoc.Lua.Orphans ()+import Text.Pandoc.Scripting (ScriptingEngine (..))+import qualified Text.Pandoc.UTF8 as UTF8++-- | Constructs the Lua scripting engine.+getEngine :: MonadIO m => m ScriptingEngine+getEngine = do+  versionName <- liftIO . run @PandocError $ do+    openlibs+    getglobal "_VERSION"+    tostring top+  pure $ ScriptingEngine+    { engineName = maybe "Lua (unknown version)" UTF8.toText versionName+    , engineApplyFilter = applyFilter+    , engineLoadCustom = loadCustom+    }
+ src/Text/Pandoc/Lua/Custom.hs view
@@ -0,0 +1,163 @@+{-# LANGUAGE LambdaCase          #-}+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE TypeApplications    #-}+{-# LANGUAGE TupleSections       #-}+{- |+   Module      : Text.Pandoc.Lua.Custom+   Copyright   : © 2021-2023 Albert Krewinkel, John MacFarlane+   License     : GPL-2.0-or-later+   Maintainer  : Albert Krewinkel <albert+pandoc@tarleb.com>++Supports custom parsers written in Lua which produce a Pandoc AST.+-}+module Text.Pandoc.Lua.Custom ( loadCustom ) where+import Control.Exception+import Control.Monad ((<=<), (<$!>))+import Control.Monad.IO.Class (MonadIO)+import Data.Maybe (fromMaybe)+import HsLua as Lua hiding (Operation (Div))+import HsLua.Core.Run (GCManagedState, newGCManagedState, withGCManagedState)+import Text.Pandoc.Class (PandocMonad, findFileWithDataFallback)+import Text.Pandoc.Error (PandocError)+import Text.Pandoc.Lua.Global (Global (..), setGlobals)+import Text.Pandoc.Lua.Init (runLuaWith)+import Text.Pandoc.Lua.Marshal.Format (peekExtensionsConfig)+import Text.Pandoc.Lua.Marshal.Pandoc (peekPandoc)+import Text.Pandoc.Lua.Marshal.WriterOptions (pushWriterOptions)+import Text.Pandoc.Readers (Reader (..))+import Text.Pandoc.Sources (ToSources(..))+import Text.Pandoc.Scripting (CustomComponents (..))+import Text.Pandoc.Writers (Writer (..))+import qualified Text.Pandoc.Lua.Writer.Classic as Classic++-- | Convert custom markup to Pandoc.+loadCustom :: (PandocMonad m, MonadIO m)+           => FilePath -> m (CustomComponents m)+loadCustom luaFile = do+  luaState <- liftIO newGCManagedState+  luaFile' <- fromMaybe luaFile <$>+              findFileWithDataFallback "custom"  luaFile+  either throw pure <=< runLuaWith luaState $ do+    let globals = [ PANDOC_SCRIPT_FILE luaFile ]+    setGlobals globals+    dofileTrace luaFile' >>= \case+      OK -> pure ()+      _  -> throwErrorAsException++    mextsConf <- rawgetglobal "Extensions" >>= \case+      TypeNil      -> pure Nothing+      TypeFunction -> Just <$!> do+        callTrace 0 1+        forcePeek $ peekExtensionsConfig top `lastly` pop 1+      _            -> Just <$!> do+        forcePeek $ peekExtensionsConfig top `lastly` pop 1++    mtemplate <- rawgetglobal "Template" >>= \case+      TypeNil   -> pure Nothing+      TypeFunction -> Just <$!> do+        callTrace 0 1+        forcePeek $ peekText top `lastly` pop 1+      _ -> Just <$!> do+        forcePeek $ peekText top `lastly` pop 1++    mreader <- rawgetglobal "Reader" >>= \case+      TypeNil -> do+        pop 1+        rawgetglobal "ByteStringReader" >>= \case+          TypeNil -> pure Nothing+          _ -> do+            setfield registryindex readerField+            pure . Just $ byteStringReader luaState+      _ -> do+        setfield registryindex readerField+        pure . Just $ textReader luaState++    mwriter <- rawgetglobal "Writer" >>= \case+      TypeNil -> rawgetglobal "ByteStringWriter" >>= \case+        TypeNil -> do+          -- Neither `Writer` nor `BinaryWriter` are defined. Check for+          -- "Doc"; if present, use the file as a classic writer.+          docType <- rawgetglobal "Doc"+          pop 3  -- remove nils/value of "Writer", "ByteStringWriter", "Doc"+          pure $+            if docType /= TypeFunction+            then Nothing+            else Just . TextWriter $ \opts doc ->+              liftIO $ withGCManagedState luaState $+              Classic.runCustom @PandocError opts doc+        _ -> Just <$!> do+          -- Binary writer. Writer function is on top of the stack.+          setfield registryindex writerField+          pure $ ByteStringWriter $ \opts doc ->+            -- Call writer with document and writer options as arguments.+            liftIO $ withGCManagedState luaState $ do+              getfield registryindex writerField+              push doc+              pushWriterOptions opts+              callTrace 2 1+              forcePeek @PandocError $ peekLazyByteString top+      _ -> Just <$!> do+        -- New-type text writer. Writer function is on top of the stack.+        setfield registryindex writerField+        pure $ TextWriter $ \opts doc ->+          liftIO $ withGCManagedState luaState $ do+            getfield registryindex writerField+            push doc+            pushWriterOptions opts+            callTrace 2 1+            forcePeek @PandocError $ peekText top++    pure $ CustomComponents+      { customReader = mreader+      , customWriter = mwriter+      , customTemplate = mtemplate+      , customExtensions = mextsConf+      }++-- | "Raw", non-metatable lookup of a key in the global table.+--+-- Most classic writers contain code that throws an error if a global+-- is not present. This would break our check for the existence of a+-- "Writer" function. We resort to raw access for that reason, but+-- could also catch the error instead.+--+-- TODO: This function ensures the proper behavior of legacy custom+-- writers. It should be replaced with 'getglobal' in the future.+rawgetglobal :: LuaError e => Name -> LuaE e Lua.Type+rawgetglobal x = do+  pushglobaltable+  pushName x+  rawget (nth 2) <* remove (nth 2) -- remove global table++-- | Name under which the reader function is stored in the registry.+readerField :: Name+readerField = "Pandoc Reader function"++-- | Name under which the writer function is stored in the registry.+writerField :: Name+writerField = "Pandoc Writer function"++-- | Runs a Lua action in a continueable environment.+inLua :: MonadIO m => GCManagedState -> LuaE PandocError a -> m a+inLua st = liftIO . withGCManagedState @PandocError st++-- | Returns the ByteStringReader function+byteStringReader :: MonadIO m => GCManagedState -> Reader m+byteStringReader st = ByteStringReader $ \ropts input -> inLua st $ do+  getfield registryindex readerField+  push input+  push ropts+  pcallTrace 2 1 >>= \case+    OK -> forcePeek $ peekPandoc top+    _ -> throwErrorAsException++-- | Returns the TextReader function+textReader :: MonadIO m => GCManagedState -> Reader m+textReader st = TextReader $ \ropts srcs -> inLua st $ do+  let input = toSources srcs+  getfield registryindex readerField+  push input+  push ropts+  pcallTrace 2 1 >>= \case+    OK -> forcePeek $ peekPandoc top+    _ -> throwErrorAsException
+ src/Text/Pandoc/Lua/Filter.hs view
@@ -0,0 +1,81 @@+{-# LANGUAGE FlexibleContexts     #-}+{-# LANGUAGE IncoherentInstances  #-}+{-# LANGUAGE OverloadedStrings    #-}+{-# LANGUAGE ScopedTypeVariables  #-}+{- |+Module      : Text.Pandoc.Lua.Filter+Copyright   : © 2012-2023 John MacFarlane,+              © 2017-2023 Albert Krewinkel+License     : GNU GPL, version 2 or above+Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>+Stability   : alpha++Types and functions for running Lua filters.+-}+module Text.Pandoc.Lua.Filter+  ( applyFilter+  ) where+import Control.Monad ((>=>), (<$!>))+import HsLua as Lua+import Text.Pandoc.Definition+import Text.Pandoc.Filter (Environment (..))+import Text.Pandoc.Lua.Marshal.AST+import Text.Pandoc.Lua.Marshal.Filter+import Text.Pandoc.Lua.Global (Global (..), setGlobals)+import Text.Pandoc.Lua.Init (runLua)+import Text.Pandoc.Lua.PandocLua ()+import Control.Exception (throw)+import qualified Data.Text as T+import Text.Pandoc.Class (PandocMonad)+import Control.Monad.Trans (MonadIO)+import Text.Pandoc.Error (PandocError (PandocFilterError, PandocLuaError))++-- | Transform document using the filter defined in the given file.+runFilterFile :: FilePath -> Pandoc -> LuaE PandocError Pandoc+runFilterFile filterPath doc = do+  oldtop <- gettop+  stat <- dofileTrace filterPath+  if stat /= Lua.OK+    then throwErrorAsException+    else do+      newtop <- gettop+      -- Use the returned filters, or the implicitly defined global+      -- filter if nothing was returned.+      luaFilters <- forcePeek $+        if newtop - oldtop >= 1+        then peekList peekFilter top+        else (:[]) <$!> (liftLua pushglobaltable *> peekFilter top)+      settop oldtop+      runAll luaFilters doc++runAll :: [Filter] -> Pandoc -> LuaE PandocError Pandoc+runAll = foldr ((>=>) . applyFully) return++-- | Run the Lua filter in @filterPath@ for a transformation to the+-- target format (first element in args). Pandoc uses Lua init files to+-- setup the Lua interpreter.+applyFilter :: (PandocMonad m, MonadIO m)+            => Environment+            -> [String]+            -> FilePath+            -> Pandoc+            -> m Pandoc+applyFilter fenv args fp doc = do+  let globals = [ FORMAT $ case args of+                    x:_ -> T.pack x+                    _   -> ""+                , PANDOC_READER_OPTIONS (envReaderOptions fenv)+                , PANDOC_WRITER_OPTIONS (envWriterOptions fenv)+                , PANDOC_SCRIPT_FILE fp+                ]+  runLua >=> forceResult fp $ do+    setGlobals globals+    runFilterFile fp doc++forceResult :: (PandocMonad m, MonadIO m)+            => FilePath -> Either PandocError Pandoc -> m Pandoc+forceResult fp eitherResult = case eitherResult of+  Right x  -> return x+  Left err -> throw . PandocFilterError (T.pack fp) $ case err of+    PandocLuaError msg -> msg+    _                  -> T.pack $ show err
+ src/Text/Pandoc/Lua/Global.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE OverloadedStrings  #-}+{- |+   Module      : Text.Pandoc.Lua+   Copyright   : Copyright © 2017-2023 Albert Krewinkel+   License     : GNU GPL, version 2 or above++   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>+   Stability   : alpha++Pandoc's Lua globals.+-}+module Text.Pandoc.Lua.Global+  ( Global (..)+  , setGlobals+  ) where++import HsLua as Lua+import HsLua.Module.Version (pushVersion)+import Text.Pandoc.Class (CommonState)+import Text.Pandoc.Definition (Pandoc, pandocTypesVersion)+import Text.Pandoc.Error (PandocError)+import Text.Pandoc.Lua.Marshal.CommonState (pushCommonState)+import Text.Pandoc.Lua.Marshal.Pandoc (pushPandoc)+import Text.Pandoc.Lua.Marshal.ReaderOptions (pushReaderOptionsReadonly)+import Text.Pandoc.Lua.Marshal.WriterOptions (pushWriterOptions)+import Text.Pandoc.Lua.PandocLua ()+import Text.Pandoc.Options (ReaderOptions, WriterOptions)+import Text.Pandoc.Version (pandocVersion)++import qualified Data.Text as Text++-- | Permissible global Lua variables.+data Global =+    FORMAT Text.Text+  | PANDOC_API_VERSION+  | PANDOC_DOCUMENT Pandoc+  | PANDOC_READER_OPTIONS ReaderOptions+  | PANDOC_WRITER_OPTIONS WriterOptions+  | PANDOC_SCRIPT_FILE FilePath+  | PANDOC_STATE CommonState+  | PANDOC_VERSION+  -- Cannot derive instance of Data because of CommonState++-- | Set all given globals.+setGlobals :: [Global] -> LuaE PandocError ()+setGlobals = mapM_ setGlobal++setGlobal :: Global -> LuaE PandocError ()+setGlobal global = case global of+  -- This could be simplified if Global was an instance of Data.+  FORMAT format -> do+    Lua.pushText format+    Lua.setglobal "FORMAT"+  PANDOC_API_VERSION -> do+    pushVersion pandocTypesVersion+    Lua.setglobal "PANDOC_API_VERSION"+  PANDOC_DOCUMENT doc -> do+    pushPandoc doc+    Lua.setglobal "PANDOC_DOCUMENT"+  PANDOC_READER_OPTIONS ropts -> do+    pushReaderOptionsReadonly ropts+    Lua.setglobal "PANDOC_READER_OPTIONS"+  PANDOC_WRITER_OPTIONS wopts -> do+    pushWriterOptions wopts+    Lua.setglobal "PANDOC_WRITER_OPTIONS"+  PANDOC_SCRIPT_FILE filePath -> do+    Lua.pushString filePath+    Lua.setglobal "PANDOC_SCRIPT_FILE"+  PANDOC_STATE commonState -> do+    pushCommonState commonState+    Lua.setglobal "PANDOC_STATE"+  PANDOC_VERSION              -> do+    pushVersion pandocVersion+    Lua.setglobal "PANDOC_VERSION"
+ src/Text/Pandoc/Lua/Init.hs view
@@ -0,0 +1,217 @@+{-# LANGUAGE LambdaCase        #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes        #-}+{- |+   Module      : Text.Pandoc.Lua+   Copyright   : Copyright © 2017-2023 Albert Krewinkel+   License     : GNU GPL, version 2 or above++   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>+   Stability   : alpha++Functions to initialize the Lua interpreter.+-}+module Text.Pandoc.Lua.Init+  ( runLua+  , runLuaNoEnv+  , runLuaWith+  ) where++import Control.Monad (forM, forM_, when)+import Control.Monad.Catch (throwM, try)+import Control.Monad.Trans (MonadIO (..))+import Data.Maybe (catMaybes)+import HsLua as Lua hiding (status, try)+import HsLua.Core.Run as Lua+import Text.Pandoc.Class (PandocMonad (..))+import Text.Pandoc.Data (readDataFile)+import Text.Pandoc.Error (PandocError (PandocLuaError))+import Text.Pandoc.Lua.Global (Global (..), setGlobals)+import Text.Pandoc.Lua.Marshal.List (newListMetatable, pushListModule)+import Text.Pandoc.Lua.PandocLua (PandocLua (..), liftPandocLua)+import qualified Data.ByteString.Char8 as Char8+import qualified Data.Text as T+import qualified Lua.LPeg as LPeg+import qualified HsLua.Aeson+import qualified HsLua.Module.DocLayout as Module.Layout+import qualified HsLua.Module.Path as Module.Path+import qualified HsLua.Module.Text as Module.Text+import qualified HsLua.Module.Zip as Module.Zip+import qualified Text.Pandoc.Lua.Module.CLI as Pandoc.CLI+import qualified Text.Pandoc.Lua.Module.Format as Pandoc.Format+import qualified Text.Pandoc.Lua.Module.MediaBag as Pandoc.MediaBag+import qualified Text.Pandoc.Lua.Module.Pandoc as Module.Pandoc+import qualified Text.Pandoc.Lua.Module.Scaffolding as Pandoc.Scaffolding+import qualified Text.Pandoc.Lua.Module.Structure as Pandoc.Structure+import qualified Text.Pandoc.Lua.Module.System as Pandoc.System+import qualified Text.Pandoc.Lua.Module.Template as Pandoc.Template+import qualified Text.Pandoc.Lua.Module.Types as Pandoc.Types+import qualified Text.Pandoc.Lua.Module.Utils as Pandoc.Utils++-- | Run the Lua interpreter, using pandoc's default way of environment+-- initialization.+runLua :: (PandocMonad m, MonadIO m)+       => LuaE PandocError a -> m (Either PandocError a)+runLua action = do+  runPandocLuaWith Lua.run . try $ do+    initLuaState+    liftPandocLua action++runLuaWith :: (PandocMonad m, MonadIO m)+           => GCManagedState -> LuaE PandocError a -> m (Either PandocError a)+runLuaWith luaState action = do+  runPandocLuaWith (withGCManagedState luaState) . try $ do+    initLuaState+    liftPandocLua action++-- | Like 'runLua', but ignores all environment variables like @LUA_PATH@.+runLuaNoEnv :: (PandocMonad m, MonadIO m)+            => LuaE PandocError a -> m (Either PandocError a)+runLuaNoEnv action = do+  runPandocLuaWith Lua.run . try $ do+    liftPandocLua $ do+      -- This is undocumented, but works -- the code is adapted from the+      -- `lua.c` sources for the default interpreter.+      Lua.pushboolean True+      Lua.setfield Lua.registryindex "LUA_NOENV"+    initLuaState+    liftPandocLua action++-- | Modules that are loaded at startup and assigned to fields in the+-- pandoc module.+--+-- Note that @pandoc.List@ is not included here for technical reasons;+-- it must be handled separately.+loadedModules :: [Module PandocError]+loadedModules =+  [ Pandoc.CLI.documentedModule+  , Pandoc.Format.documentedModule+  , Pandoc.MediaBag.documentedModule+  , Pandoc.Scaffolding.documentedModule+  , Pandoc.Structure.documentedModule+  , Pandoc.System.documentedModule+  , Pandoc.Template.documentedModule+  , Pandoc.Types.documentedModule+  , Pandoc.Utils.documentedModule+  , Module.Layout.documentedModule { moduleName = "pandoc.layout" }+  , Module.Path.documentedModule { moduleName = "pandoc.path" }+  , Module.Text.documentedModule+  , Module.Zip.documentedModule { moduleName = "pandoc.zip" }+  ]++-- | Initialize the lua state with all required values+initLuaState :: PandocLua ()+initLuaState = do+  liftPandocLua Lua.openlibs+  initJsonMetatable+  initPandocModule+  installLpegSearcher+  setGlobalModules+  loadInitScript "init.lua"+ where+  initPandocModule :: PandocLua ()+  initPandocModule = liftPandocLua $ do+    -- Push module table+    registerModule Module.Pandoc.documentedModule+    -- load modules and add them to the `pandoc` module table.+    forM_ loadedModules $ \mdl -> do+      registerModule mdl+      let fieldname (Name mdlname) = Name .+            maybe mdlname snd . Char8.uncons . snd $+            Char8.break (== '.') mdlname+      Lua.setfield (nth 2) (fieldname $ moduleName mdl)+    -- pandoc.List is low-level and must be opened differently.+    requirehs "pandoc.List" (const pushListModule)+    setfield (nth 2) "List"+    -- assign module to global variable+    Lua.setglobal "pandoc"++  loadInitScript :: FilePath -> PandocLua ()+  loadInitScript scriptFile = do+    script <- readDataFile scriptFile+    status <- liftPandocLua $ Lua.dostring script+    when (status /= Lua.OK) . liftPandocLua $ do+      err <- popException+      let prefix = "Couldn't load '" <> T.pack scriptFile <> "':\n"+      throwM . PandocLuaError . (prefix <>) $ case err of+        PandocLuaError msg -> msg+        _                  -> T.pack $ show err++  setGlobalModules :: PandocLua ()+  setGlobalModules = liftPandocLua $ do+    let globalModules =+          [ ("lpeg", LPeg.luaopen_lpeg_ptr)  -- must be loaded first+          , ("re", LPeg.luaopen_re_ptr)      -- re depends on lpeg+          ]+    loadedBuiltInModules <- fmap catMaybes . forM globalModules $+      \(pkgname, luaopen) -> do+        Lua.pushcfunction luaopen+        usedBuiltIn <- Lua.pcall 0 1 Nothing >>= \case+          OK -> do               -- all good, loading succeeded+            -- register as loaded module so later modules can rely on this+            Lua.getfield Lua.registryindex Lua.loaded+            Lua.pushvalue (Lua.nth 2)+            Lua.setfield (Lua.nth 2) pkgname+            Lua.pop 1  -- pop _LOADED+            return True+          _  -> do               -- built-in library failed, load system lib+            Lua.pop 1  -- ignore error message+            -- Try loading via the normal package loading mechanism.+            Lua.getglobal "require"+            Lua.pushName pkgname+            Lua.call 1 1  -- Throws an exception if loading failed again!+            return False++        -- Module on top of stack. Register as global+        Lua.setglobal pkgname+        return $ if usedBuiltIn then Just pkgname else Nothing++    -- Remove module entry from _LOADED table in registry if we used a+    -- built-in library. This ensures that later calls to @require@ will+    -- prefer the shared library, if any.+    forM_ loadedBuiltInModules $ \pkgname -> do+      Lua.getfield Lua.registryindex Lua.loaded+      Lua.pushnil+      Lua.setfield (Lua.nth 2) pkgname+      Lua.pop 1  -- registry++  installLpegSearcher :: PandocLua ()+  installLpegSearcher = liftPandocLua $ do+    Lua.getglobal' "package.searchers"+    Lua.pushHaskellFunction $ Lua.state >>= liftIO . LPeg.lpeg_searcher+    Lua.rawseti (Lua.nth 2) . (+1) . fromIntegral =<< Lua.rawlen (Lua.nth 2)+    Lua.pop 1  -- remove 'package.searchers' from stack++-- | Setup the metatable that's assigned to Lua tables that were created+-- from/via JSON arrays.+initJsonMetatable :: PandocLua ()+initJsonMetatable = liftPandocLua $ do+  newListMetatable HsLua.Aeson.jsonarray (pure ())++-- | Evaluate a @'PandocLua'@ computation, running all contained Lua+-- operations.+runPandocLuaWith :: (PandocMonad m, MonadIO m)+                 => (forall b. LuaE PandocError b -> IO b)+                 -> PandocLua a+                 -> m a+runPandocLuaWith runner pLua = do+  origState <- getCommonState+  globals <- defaultGlobals+  (result, newState) <- liftIO . runner . unPandocLua $ do+    putCommonState origState+    liftPandocLua $ setGlobals globals+    r <- pLua+    c <- getCommonState+    return (r, c)+  putCommonState newState+  return result++-- | Global variables which should always be set.+defaultGlobals :: PandocMonad m => m [Global]+defaultGlobals = do+  commonState <- getCommonState+  return+    [ PANDOC_API_VERSION+    , PANDOC_STATE commonState+    , PANDOC_VERSION+    ]
+ src/Text/Pandoc/Lua/Marshal/Chunks.hs view
@@ -0,0 +1,145 @@+{-# LANGUAGE LambdaCase           #-}+{-# LANGUAGE OverloadedStrings    #-}+{- |+   Module      : Text.Pandoc.Lua.Marshal.Chunks+   Copyright   : © 2022 Albert Krewinkel+   License     : GPL-2.0-or-later+   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>++Marshaling chunks, i.e., pandoc subdocuments.+-}+module Text.Pandoc.Lua.Marshal.Chunks+  ( peekChunk+  , pushChunk+  , peekChunkedDoc+  , pushChunkedDoc+  ) where++import Control.Monad ((<$!>))+import Data.Tree (Tree (..))+import HsLua+import Text.Pandoc.Chunks (Chunk (..), ChunkedDoc (..), SecInfo (..))+import Text.Pandoc.Lua.Marshal.AST++-- | Retrieves a 'Chunk' from the Lua stack.+peekChunk :: LuaError e => Peeker e Chunk+peekChunk = peekUD typeChunk++-- | Pushes a 'Chunk' to the top of the Lua stack.+pushChunk :: LuaError e => Pusher e Chunk+pushChunk = pushUD typeChunk++typeChunk :: LuaError e => DocumentedType e Chunk+typeChunk = deftype "pandoc.Chunk"+  [ operation Tostring $ lambda+    ### liftPure show+    <#> udparam typeChunk "chunk" "chunk to print in native format"+    =#> functionResult pushString "string" "Haskell representation"+  ]+  [ property "heading"+    "heading text"+    (pushInlines, chunkHeading)+    (peekInlinesFuzzy, \chunk inlns -> chunk{ chunkHeading = inlns })++  , property "id"+    "identifier"+    (pushText, chunkId)+    (peekText, \chunk ident -> chunk{ chunkId = ident })++  , property "level"+    "level of topmost heading in chunk"+    (pushIntegral, chunkLevel)+    (peekIntegral, \chunk level -> chunk{ chunkLevel = level })++  , property "number"+    "chunk number"+    (pushIntegral, chunkNumber)+    (peekIntegral, \chunk number -> chunk{ chunkNumber = number })++  , property "section_number"+    "hierarchical section number"+    (pushMaybe pushText, chunkSectionNumber)+    (peekMaybe peekText, \chunk secnum -> chunk{ chunkSectionNumber = secnum })++  , property "path"+    "target filepath for this chunk"+    (pushString, chunkPath)+    (peekString, \chunk path -> chunk{ chunkPath = path })++  , property "up"+    "link to the enclosing section chunk, if any"+    (pushMaybe pushChunk, chunkUp)+    (peekMaybe peekChunk, \chunk up -> chunk{ chunkUp = up })++  , property "prev"+    "link to the previous section, if any"+    (pushMaybe pushChunk, chunkPrev)+    (peekMaybe peekChunk, \chunk prev -> chunk{ chunkPrev = prev })++  , property "next"+    "link to the next section, if any"+    (pushMaybe pushChunk, chunkNext)+    (peekMaybe peekChunk, \chunk next' -> chunk{ chunkNext = next' })++  , property "unlisted"+    ( "whether the section in this chunk should be listed in the TOC" <>+      "even if the chunk has no section number" )+    (pushBool, chunkUnlisted)+    (peekBool, \chunk unlisted -> chunk { chunkUnlisted = unlisted })++  , property "contents"+    "the chunk's block contents"+    (pushBlocks, chunkContents)+    (peekBlocksFuzzy, \chunk blks -> chunk{ chunkContents = blks })+  ]++-- | Retrieves a 'ChunkedDoc' from the Lua stack.+peekChunkedDoc :: LuaError e => Peeker e ChunkedDoc+peekChunkedDoc = peekUD typeChunkedDoc++-- | Pushes a 'ChunkedDoc to the top of the Lua stack.+pushChunkedDoc :: LuaError e => Pusher e ChunkedDoc+pushChunkedDoc = pushUD typeChunkedDoc++-- | Lua type for 'ChunkedDoc' values.+typeChunkedDoc :: LuaError e => DocumentedType e ChunkedDoc+typeChunkedDoc = deftype "pandoc.ChunkedDoc"+  []+  [ readonly "chunks"+    "list of chunks that make up the document"+    (pushList pushChunk, chunkedChunks)++  , readonly "meta"+    "the document's metadata"+    (pushMeta, chunkedMeta)++  , readonly "toc"+    "table of contents information"+    (pushTocTree, chunkedTOC)+  ]++-- | Pushes a TOC tree to the stack. The resulting object is a list with+-- the top-level entry placed at index @0@ and all subentries as the+-- rest of the list.+pushTocTree :: LuaError e => Pusher e (Tree SecInfo)+pushTocTree (Node secInfo subSecInfo) = do+  pushList pushTocTree subSecInfo+  pushSecInfo secInfo+  rawseti (nth 2) 0++pushSecInfo :: LuaError e => Pusher e SecInfo+pushSecInfo = pushAsTable+  [ ("title"  , pushInlines . secTitle)+  , ("number" , maybe pushnil pushText . secNumber)+  , ("id"     , pushText . secId)+  , ("path"   , pushText . secPath)+  , ("level"  , pushIntegral . secLevel)+  ]++peekMaybe :: LuaError e => Peeker e a -> Peeker e (Maybe a)+peekMaybe p idx = liftLua (isnoneornil idx) >>= \case+  True  -> pure Nothing+  False -> Just <$!> p idx++pushMaybe :: LuaError e => Pusher e a -> Pusher e (Maybe a)+pushMaybe = maybe pushnil
+ src/Text/Pandoc/Lua/Marshal/CommonState.hs view
@@ -0,0 +1,68 @@+{-# LANGUAGE OverloadedStrings    #-}+{- |+   Module      : Text.Pandoc.Lua.Marshal.CommonState+   Copyright   : © 2012-2023 John MacFarlane+                 © 2017-2023 Albert Krewinkel+   License     : GNU GPL, version 2 or above+   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>+   Stability   : alpha++Instances to marshal (push) and unmarshal (peek) the common state.+-}+module Text.Pandoc.Lua.Marshal.CommonState+  ( typeCommonState+  , peekCommonState+  , pushCommonState+  ) where++import HsLua+import Text.Pandoc.Class (CommonState (..))+import Text.Pandoc.Logging (LogMessage, showLogMessage)+import Text.Pandoc.Lua.Marshal.List (pushPandocList)++-- | Lua type used for the @CommonState@ object.+typeCommonState :: LuaError e => DocumentedType e CommonState+typeCommonState = deftype "pandoc CommonState" []+  [ readonly "input_files" "input files passed to pandoc"+      (pushPandocList pushString, stInputFiles)++  , readonly "output_file" "the file to which pandoc will write"+      (maybe pushnil pushString, stOutputFile)++  , readonly "log" "list of log messages"+      (pushPandocList (pushUD typeLogMessage), stLog)++  , readonly "request_headers" "headers to add for HTTP requests"+      (pushPandocList (pushPair pushText pushText), stRequestHeaders)++  , readonly "resource_path"+      "path to search for resources like included images"+      (pushPandocList pushString, stResourcePath)++  , readonly "source_url" "absolute URL + dir of 1st source file"+      (maybe pushnil pushText, stSourceURL)++  , readonly "user_data_dir" "directory to search for data files"+      (maybe pushnil pushString, stUserDataDir)++  , readonly "trace" "controls whether tracing messages are issued"+      (pushBool, stTrace)++  , readonly "verbosity" "verbosity level"+      (pushString . show, stVerbosity)+  ]++peekCommonState :: LuaError e => Peeker e CommonState+peekCommonState = peekUD typeCommonState++pushCommonState :: LuaError e => Pusher e CommonState+pushCommonState = pushUD typeCommonState++typeLogMessage :: LuaError e => DocumentedType e LogMessage+typeLogMessage = deftype "pandoc LogMessage"+  [ operation Index $ defun "__tostring"+      ### liftPure showLogMessage+      <#> udparam typeLogMessage "msg" "object"+      =#> functionResult pushText "string" "stringified log message"+  ]+  mempty -- no members
+ src/Text/Pandoc/Lua/Marshal/Context.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE FlexibleInstances    #-}+{-# LANGUAGE LambdaCase           #-}+{-# LANGUAGE OverloadedStrings    #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+{- |+   Module      : Text.Pandoc.Lua.Marshaling.Context+   Copyright   : © 2012-2023 John MacFarlane+                 © 2017-2023 Albert Krewinkel+   License     : GNU GPL, version 2 or above++   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>+   Stability   : alpha++Marshaling instance for doctemplates Context and its components.+-}+module Text.Pandoc.Lua.Marshal.Context+  ( peekContext+  , pushContext+  ) where++import Control.Monad (when, (<$!>))+import Data.Text (Text)+import HsLua as Lua+import HsLua.Module.DocLayout (peekDoc, pushDoc)+import Text.DocTemplates (Context(..), Val(..))++instance Pushable (Context Text) where+  push = pushContext++instance Pushable (Val Text) where+  push = pushVal++-- | Retrieves a template context from the Lua stack.+peekContext :: LuaError e => Peeker e (Context Text)+peekContext idx = Context <$!> peekMap peekText peekVal idx++-- | Pushes a template context to the Lua stack.+pushContext :: LuaError e => Pusher e (Context Text)+pushContext ctx = do+  pushMap pushText pushVal $ unContext ctx+  created <- Lua.newmetatable "pandoc Context"+  when created $ do+    pushName "__concat"+    pushHaskellFunction $ do+      c1 <- forcePeek $ peekContext (nthBottom 1)+      c2 <- forcePeek $ peekContext (nthBottom 2)+      pushContext (c1 <> c2)+      return 1+    rawset (nth 3)+  setmetatable (nth 2)++pushVal :: LuaError e => Pusher e (Val Text)+pushVal = \case+  NullVal     -> Lua.pushnil+  BoolVal b   -> Lua.pushBool b+  MapVal ctx  -> pushContext ctx+  ListVal xs  -> pushList pushVal xs+  SimpleVal d -> pushDoc d++peekVal :: LuaError e => Peeker e (Val Text)+peekVal idx = liftLua (ltype idx) >>= \case+  TypeNil      -> pure NullVal+  TypeBoolean  -> BoolVal <$!> peekBool idx+  TypeNumber   -> SimpleVal <$!> peekDoc idx+  TypeString   -> SimpleVal <$!> peekDoc idx+  TypeTable    -> do+    len <- liftLua $ Lua.rawlen idx+    if len <= 0+      then MapVal <$!> peekContext idx+      else ListVal <$!> peekList peekVal idx+  TypeUserdata -> SimpleVal <$!> peekDoc idx+  _ -> failPeek =<<+       typeMismatchMessage "Doc, string, boolean, table, or nil" idx
+ src/Text/Pandoc/Lua/Marshal/Format.hs view
@@ -0,0 +1,134 @@+{-# LANGUAGE LambdaCase           #-}+{-# LANGUAGE OverloadedStrings    #-}+{-# LANGUAGE TupleSections        #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+{- |+   Module      : Text.Pandoc.Lua.Marshaling.Format+   Copyright   : © 2022-2023 Albert Krewinkel+   License     : GPL-2.0-or-later+   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>++Marshaling functions and instance for format related types, including+'Extensions' and 'ExtensionConfig'.+-}+module Text.Pandoc.Lua.Marshal.Format+  ( peekExtensions+  , pushExtensions+  , peekExtensionsConfig+  , pushExtensionsConfig+  , peekFlavoredFormat+  ) where++import Control.Applicative ((<|>))+import Control.Monad ((<$!>))+import Data.Maybe (fromMaybe)+import HsLua+import Text.Pandoc.Error (PandocError (..))+import Text.Pandoc.Extensions+  ( Extension, Extensions, extensionsFromList, extensionsToList+  , getDefaultExtensions, readExtension, showExtension )+import Text.Pandoc.Format+  ( ExtensionsConfig (..), ExtensionsDiff (..), FlavoredFormat (..)+  , diffExtensions, parseFlavoredFormat)+import Text.Pandoc.Lua.PandocLua (PandocLua (unPandocLua))++-- | Retrieves a single 'Extension' from the Lua stack.+peekExtension :: LuaError e => Peeker e Extension+peekExtension idx = do+  extString <- peekString idx+  return $ readExtension extString+{-# INLINE peekExtension #-}++-- | Pushes an individual 'Extension' to the Lua stack.+pushExtension :: LuaError e => Pusher e Extension+pushExtension = pushText . showExtension+{-# INLINE pushExtension #-}++-- | Retrieves an 'Extensions' set from the Lua stack.+peekExtensions :: LuaError e => Peeker e Extensions+peekExtensions = fmap extensionsFromList . peekList peekExtension+{-# INLINE peekExtensions #-}++-- | Pushes a set of 'Extensions' to the top of the Lua stack.+pushExtensions :: LuaError e => Pusher e Extensions+pushExtensions = pushViaJSON+{-# INLINE pushExtensions #-}++instance Peekable Extensions where+  safepeek = peekExtensions++instance Pushable Extensions where+  push = pushExtensions++-- | Retrieves an 'ExtensionsConfig' value from the Lua stack.+peekExtensionsConfig :: LuaError e => Peeker e ExtensionsConfig+peekExtensionsConfig idx = do+  diff <- peekExtensionsDiff idx+  return $ ExtensionsConfig+    { extsDefault   = extsToEnable diff+    , extsSupported = extsToEnable diff <> extsToDisable diff+    }++-- | Pushes an 'ExtensionsConfig' value as a table with that maps+-- extensions to their default enabled/disabled status.+pushExtensionsConfig :: LuaError e => Pusher e ExtensionsConfig+pushExtensionsConfig (ExtensionsConfig def supported) =+  pushKeyValuePairs pushExtension pushBool $+    map (,False) (extensionsToList supported) +++    map (,True)  (extensionsToList def)++instance Peekable ExtensionsConfig where+  safepeek = peekExtensionsConfig++peekExtensionsDiff :: LuaError e => Peeker e ExtensionsDiff+peekExtensionsDiff = typeChecked "table" istable $ \idx ->+      (do+          en <- peekFieldRaw (emptyOr (fmap Just . peekExtensions)) "enable" idx+          di <- peekFieldRaw (emptyOr (fmap Just . peekExtensions)) "disable" idx+          if (en, di) == (Nothing, Nothing)+            then failPeek "At least on of  'enable' and 'disable' must be set"+            else return $+                 ExtensionsDiff (fromMaybe mempty en) (fromMaybe mempty di))+  <|> -- two lists of extensions; the first is list assumed to contain those+      -- extensions to be enabled+      (uncurry ExtensionsDiff <$!> peekPair peekExtensions peekExtensions idx)+  <|> (do+          let+          exts <- peekKeyValuePairs peekExtension peekEnabled idx+          let enabled  = extensionsFromList . map fst $ filter snd exts+          let disabled = extensionsFromList . map fst $ filter (not . snd) exts+          return $ ExtensionsDiff enabled disabled)++-- | Retrieves the activation status of an extension. True or the string+-- @'enable'@ for activated, False or 'disable' for disabled.+peekEnabled :: LuaError e => Peeker e Bool+peekEnabled idx' = liftLua (ltype idx') >>= \case+  TypeBoolean -> peekBool idx'+  TypeString  -> peekText idx' >>= \case+                   "disable" -> pure False+                   "enable"  -> pure True+                   _         -> failPeek "expected 'disable' or 'enable'"+  _ -> failPeek "expected boolean or string"++-- | Retrieves a flavored format from the Lua stack.+peekFlavoredFormat :: Peeker PandocError FlavoredFormat+peekFlavoredFormat idx = retrieving "flavored format" $+  liftLua (ltype idx) >>= \case+  TypeString -> peekText idx >>= liftLua . unPandocLua . parseFlavoredFormat+  TypeTable -> do+    let diffFor format idx' = peekExtensionsDiff idx' <|>+          (getDefaultExtensions format `diffExtensions`) <$>+          (typeChecked "table" istable peekExtensions idx')+    format   <- peekFieldRaw peekText "format" idx+    extsDiff <- peekFieldRaw (emptyOr (diffFor format)) "extensions" idx+    return (FlavoredFormat format extsDiff)+  _ -> failPeek =<< typeMismatchMessage "string or table" idx++-- | Returns 'mempty' if the given stack index is @nil@, and the result+-- of the peeker otherwise.+emptyOr :: Monoid a => Peeker e a -> Peeker e a+emptyOr p idx = do+  nil <- liftLua (isnil idx)+  if nil+    then pure mempty+    else p idx
+ src/Text/Pandoc/Lua/Marshal/PandocError.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE LambdaCase           #-}+{-# LANGUAGE OverloadedStrings    #-}+{-# LANGUAGE ScopedTypeVariables  #-}+{- |+   Module      : Text.Pandoc.Lua.Marshal.PandocError+   Copyright   : © 2020-2023 Albert Krewinkel+   License     : GNU GPL, version 2 or above++   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>+   Stability   : alpha++Marshal of @'PandocError'@ values.+-}+module Text.Pandoc.Lua.Marshal.PandocError+  ( peekPandocError+  , pushPandocError+  , typePandocError+  )+  where++import HsLua (LuaError, Peeker, Pusher, liftLua, pushText)+import HsLua.Packaging+import Text.Pandoc.Error (PandocError (PandocLuaError), renderError)++import qualified HsLua as Lua+import qualified Text.Pandoc.UTF8 as UTF8++-- | Lua userdata type definition for PandocError.+typePandocError :: LuaError e => DocumentedType e PandocError+typePandocError = deftype "PandocError"+  [ operation Tostring $ defun "__tostring"+    ### liftPure (\case+                     PandocLuaError e -> e+                     err              -> renderError err)+    <#> udparam typePandocError "obj" "PandocError object"+    =#> functionResult pushText "string" "string representation of error."+  ]+  mempty -- no members++-- | Peek a @'PandocError'@ element to the Lua stack.+pushPandocError :: LuaError e => Pusher e PandocError+pushPandocError = pushUD typePandocError++-- | Retrieve a @'PandocError'@ from the Lua stack.+peekPandocError :: LuaError e => Peeker e PandocError+peekPandocError idx = Lua.retrieving "PandocError" $+  liftLua (Lua.ltype idx) >>= \case+    Lua.TypeUserdata -> peekUD typePandocError idx+    _ -> do+      msg <- liftLua $ do+        Lua.pushvalue idx+        Lua.state >>= \l -> Lua.liftIO (Lua.popErrorMessage l)+      return $ PandocLuaError (UTF8.toText msg)
+ src/Text/Pandoc/Lua/Marshal/ReaderOptions.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE CPP                  #-}+{-# LANGUAGE LambdaCase           #-}+{-# LANGUAGE OverloadedStrings    #-}+{-# LANGUAGE ScopedTypeVariables  #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+{- |+   Module      : Text.Pandoc.Lua.Marshaling.ReaderOptions+   Copyright   : © 2012-2023 John MacFarlane+                 © 2017-2023 Albert Krewinkel+   License     : GNU GPL, version 2 or above++   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>+   Stability   : alpha++Marshaling instance for ReaderOptions and its components.+-}+module Text.Pandoc.Lua.Marshal.ReaderOptions+  ( peekReaderOptions+  , pushReaderOptions+  , pushReaderOptionsReadonly+  ) where++import Data.Default (def)+import HsLua as Lua+import Text.Pandoc.Lua.Marshal.Format (peekExtensions, pushExtensions)+import Text.Pandoc.Lua.Marshal.List (pushPandocList)+import Text.Pandoc.Options (ReaderOptions (..))++--+-- Reader Options+--++-- | Retrieve a ReaderOptions value, either from a normal ReaderOptions+-- value, from a read-only object, or from a table with the same+-- keys as a ReaderOptions object.+peekReaderOptions :: LuaError e => Peeker e ReaderOptions+peekReaderOptions = retrieving "ReaderOptions" . \idx ->+  liftLua (ltype idx) >>= \case+    TypeUserdata -> choice [ peekUD typeReaderOptions+                           , peekUD typeReaderOptionsReadonly+                           ]+                           idx+    TypeTable    -> peekReaderOptionsTable idx+    _            -> failPeek =<<+                    typeMismatchMessage "ReaderOptions userdata or table" idx++-- | Pushes a ReaderOptions value as userdata object.+pushReaderOptions :: LuaError e => Pusher e ReaderOptions+pushReaderOptions = pushUD typeReaderOptions++-- | Pushes a ReaderOptions object, but makes it read-only.+pushReaderOptionsReadonly :: LuaError e => Pusher e ReaderOptions+pushReaderOptionsReadonly = pushUD typeReaderOptionsReadonly++-- | ReaderOptions object type for read-only values.+typeReaderOptionsReadonly :: LuaError e => DocumentedType e ReaderOptions+typeReaderOptionsReadonly = deftype "ReaderOptions (read-only)"+  [ operation Tostring $ lambda+    ### liftPure show+    <#> udparam typeReaderOptions "opts" "options to print in native format"+    =#> functionResult pushString "string" "Haskell representation"+  , operation Newindex $ lambda+    ### (failLua "This ReaderOptions value is read-only.")+    =?> "Throws an error when called, i.e., an assignment is made."+  ]+  readerOptionsMembers++-- | 'ReaderOptions' object type.+typeReaderOptions :: LuaError e => DocumentedType e ReaderOptions+typeReaderOptions = deftype "ReaderOptions"+  [ operation Tostring $ lambda+    ### liftPure show+    <#> udparam typeReaderOptions "opts" "options to print in native format"+    =#> functionResult pushString "string" "Haskell representation"+  ]+  readerOptionsMembers++-- | Member properties of 'ReaderOptions' Lua values.+readerOptionsMembers :: LuaError e+                     => [Member e (DocumentedFunction e) ReaderOptions]+readerOptionsMembers =+  [ property "abbreviations" ""+      (pushSet pushText, readerAbbreviations)+      (peekSet peekText, \opts x -> opts{ readerAbbreviations = x })+  , property "columns" ""+      (pushIntegral, readerColumns)+      (peekIntegral, \opts x -> opts{ readerColumns = x })+  , property "default_image_extension" ""+      (pushText, readerDefaultImageExtension)+      (peekText, \opts x -> opts{ readerDefaultImageExtension = x })+  , property "extensions" ""+      (pushExtensions, readerExtensions)+      (peekExtensions, \opts x -> opts{ readerExtensions = x })+  , property "indented_code_classes" ""+      (pushPandocList pushText, readerIndentedCodeClasses)+      (peekList peekText, \opts x -> opts{ readerIndentedCodeClasses = x })+  , property "standalone" ""+      (pushBool, readerStandalone)+      (peekBool, \opts x -> opts{ readerStandalone = x })+  , property "strip_comments" ""+      (pushBool, readerStripComments)+      (peekBool, \opts x -> opts{ readerStripComments = x })+  , property "tab_stop" ""+      (pushIntegral, readerTabStop)+      (peekIntegral, \opts x -> opts{ readerTabStop = x })+  , property "track_changes" ""+      (pushViaJSON, readerTrackChanges)+      (choice [peekRead, peekViaJSON], \opts x -> opts{ readerTrackChanges = x })+  ]++-- | Retrieves a 'ReaderOptions' object from a table on the stack, using+-- the default values for all missing fields.+--+-- Internally, this pushes the default reader options, sets each+-- key/value pair of the table in the userdata value, then retrieves the+-- object again. This will update all fields and complain about unknown+-- keys.+peekReaderOptionsTable :: LuaError e => Peeker e ReaderOptions+peekReaderOptionsTable idx = retrieving "ReaderOptions (table)" $ do+  liftLua $ do+    absidx <- absindex idx+    pushUD typeReaderOptions def+    let setFields = do+          next absidx >>= \case+            False -> return () -- all fields were copied+            True -> do+              pushvalue (nth 2) *> insert (nth 2)+              settable (nth 4) -- set in userdata object+              setFields+    pushnil -- first key+    setFields+  peekUD typeReaderOptions top `lastly` pop 1++instance Pushable ReaderOptions where+  push = pushReaderOptions
+ src/Text/Pandoc/Lua/Marshal/Reference.hs view
@@ -0,0 +1,96 @@+{-# LANGUAGE LambdaCase           #-}+{-# LANGUAGE OverloadedStrings    #-}+{-# LANGUAGE ScopedTypeVariables  #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+{- |+   Module      : Text.Pandoc.Lua.Marshaling.ReaderOptions+   Copyright   : © 2012-2023 John MacFarlane+                 © 2017-2023 Albert Krewinkel+   License     : GNU GPL, version 2 or above++   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>+   Stability   : alpha++Marshal citeproc 'Reference' values.+-}+module Text.Pandoc.Lua.Marshal.Reference+  ( pushReference+  ) where++import Citeproc.Types+  ( Date (..), DateParts (..), ItemId (..), Name (..), Reference (..)+  , Val (..), Variable, fromVariable+  )+import Control.Monad (forM_)+import HsLua hiding (Name, Reference, pushName, peekName)+import Text.Pandoc.Builder (Inlines, toList)+import Text.Pandoc.Lua.Marshal.Inline (pushInlines)+import Text.Pandoc.Lua.Marshal.List (pushPandocList)++import qualified Data.Map as Map++-- | Pushes a ReaderOptions value as userdata object.+pushReference :: LuaError e => Pusher e (Reference Inlines)+pushReference reference = do+  pushAsTable [ ("id", pushItemId . referenceId)+              , ("type", pushText . referenceType)+              ]+              reference+  forM_ (Map.toList $ referenceVariables reference) $ \(var, val) -> do+    pushVariable var+    pushVal val+    rawset (nth 3)++-- | Pushes an 'ItemId' as a string.+pushItemId :: Pusher e ItemId+pushItemId = pushText . unItemId++-- | Pushes a person's 'Name' as a table.+pushName :: LuaError e => Pusher e Name+pushName = pushAsTable+  [ ("family"                , pushTextOrNil . nameFamily)+  , ("given"                 , pushTextOrNil . nameGiven)+  , ("dropping-particle"     , pushTextOrNil . nameDroppingParticle)+  , ("non-dropping-particle" , pushTextOrNil . nameNonDroppingParticle)+  , ("suffix"                , pushTextOrNil . nameSuffix)+  , ("literal"               , pushTextOrNil . nameLiteral)+  , ("comma-suffix"          , pushBoolOrNil . nameCommaSuffix)+  , ("static-ordering"       , pushBoolOrNil . nameStaticOrdering)+  ]+  where+    pushTextOrNil = \case+      Nothing -> pushnil+      Just xs -> pushText xs++-- | Pushes a boolean, but uses @nil@ instead of @false@; table fields+-- are not set unless the value is true.+pushBoolOrNil :: Pusher e Bool+pushBoolOrNil = \case+  False -> pushnil+  True  -> pushBool True++-- | Pushes a 'Variable' as string.+pushVariable :: Pusher e Variable+pushVariable = pushText . fromVariable++-- | Pushes a 'Val', i.e., a variable value.+pushVal :: LuaError e => Pusher e (Val Inlines)+pushVal = \case+  TextVal t -> pushText t+  FancyVal inlns -> pushInlines $ toList inlns+  NumVal i       -> pushIntegral i+  NamesVal names -> pushPandocList pushName names+  DateVal date   -> pushDate date+  _              -> pushText mempty++-- | Pushes a 'Date' as table.+pushDate :: LuaError e => Pusher e Date+pushDate = pushAsTable+  [ ("date-parts", pushPandocList pushDateParts . dateParts)+  , ("circa", pushBoolOrNil . dateCirca)+  , ("season", maybe pushnil pushIntegral . dateSeason)+  , ("literal", maybe pushnil pushText . dateLiteral)+  ]+ where+   -- date parts are lists of Int values+   pushDateParts (DateParts dp) = pushPandocList pushIntegral dp
+ src/Text/Pandoc/Lua/Marshal/Sources.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE LambdaCase           #-}+{-# LANGUAGE OverloadedStrings    #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+{- |+Module      : Text.Pandoc.Lua.Marshaling.Sources+Copyright   : © 2021-2023 Albert Krewinkel+License     : GNU GPL, version 2 or above+Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>++Marshal 'Sources'.+-}+module Text.Pandoc.Lua.Marshal.Sources+  ( peekSources+  , pushSources+  ) where++import Control.Monad ((<$!>))+import Data.Text (Text)+import HsLua as Lua+import Text.Pandoc.Lua.Marshal.List (newListMetatable)+import Text.Pandoc.Sources (Sources (..), toSources)+import Text.Parsec (SourcePos, sourceName)++-- | Pushes the 'Sources' as a list of lazy Lua objects.+pushSources :: LuaError e => Pusher e Sources+pushSources (Sources srcs) = do+  pushList (pushUD typeSource) srcs+  newListMetatable "pandoc Sources" $ do+    pushName "__tostring"+    pushHaskellFunction $ do+      sources <- forcePeek $ peekList (peekUD typeSource) (nthBottom 1)+      pushText . mconcat $ map snd sources+      return 1+    rawset (nth 3)+  setmetatable (nth 2)++-- | Retrieves sources from the stack.+peekSources :: LuaError e => Peeker e Sources+peekSources idx = liftLua (ltype idx) >>= \case+  TypeString -> toSources <$!> peekText idx+  TypeTable  -> Sources <$!> peekList (peekUD typeSource) idx+  _          -> Sources . (:[]) <$!> peekUD typeSource idx++-- | Source object type.+typeSource :: LuaError e => DocumentedType e (SourcePos, Text)+typeSource = deftype "pandoc input source"+  [ operation Tostring $ lambda+    ### liftPure snd+    <#> udparam typeSource "srcs" "Source to print in native format"+    =#> functionResult pushText "string" "Haskell representation"+  ]+  [ readonly "name" "source name"+      (pushString, sourceName . fst)+  , readonly "text" "source text"+      (pushText, snd)+  ]
+ src/Text/Pandoc/Lua/Marshal/Template.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE LambdaCase           #-}+{-# LANGUAGE OverloadedStrings    #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+{- |+Module      : Text.Pandoc.Lua.Marshal.Template+Copyright   : © 2021-2023 Albert Krewinkel+License     : GNU GPL, version 2 or above+Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>++Marshal 'Template' 'Text'.+-}+module Text.Pandoc.Lua.Marshal.Template+  ( pushTemplate+  , peekTemplate+  , typeTemplate+  ) where++import Data.Text (Text)+import HsLua as Lua+import HsLua.Core.Utf8 as Lua+import Text.Pandoc.Error (PandocError)+import Text.Pandoc.Lua.PandocLua (unPandocLua)+import Text.Pandoc.Templates (Template, compileTemplate, runWithDefaultPartials)++-- | Pushes a 'Template' as a an opaque userdata value.+pushTemplate :: LuaError e => Pusher e (Template Text)+pushTemplate = pushUD typeTemplate++-- | Retrieves a 'Template' 'Text' value from the stack.+peekTemplate :: Peeker PandocError (Template Text)+peekTemplate idx = liftLua (ltype idx) >>= \case+  TypeString -> do+    let path = "templates/default.custom"+    let liftPM = liftLua . unPandocLua+    tmpl <- peekText idx+    liftPM (runWithDefaultPartials (compileTemplate path tmpl)) >>= \case+      Left e  -> failPeek (Lua.fromString e)+      Right t -> pure t+  _ -> peekUD typeTemplate idx++-- | Template object type.+typeTemplate :: LuaError e => DocumentedType e (Template Text)+typeTemplate = deftype "pandoc Template" [] []
+ src/Text/Pandoc/Lua/Marshal/WriterOptions.hs view
@@ -0,0 +1,246 @@+{-# LANGUAGE CPP                  #-}+{-# LANGUAGE LambdaCase           #-}+{-# LANGUAGE OverloadedStrings    #-}+{-# LANGUAGE ScopedTypeVariables  #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+{- |+   Module      : Text.Pandoc.Lua.Marshaling.WriterOptions+   Copyright   : © 2021-2023 Albert Krewinkel, John MacFarlane+   License     : GNU GPL, version 2 or above++   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>+   Stability   : alpha++Marshaling instance for WriterOptions and its components.+-}+module Text.Pandoc.Lua.Marshal.WriterOptions+  ( peekWriterOptions+  , pushWriterOptions+  ) where++import Control.Applicative (optional)+import Data.Default (def)+import HsLua as Lua+import Text.Pandoc.Error (PandocError)+import Text.Pandoc.Lua.Marshal.Context (peekContext, pushContext)+import Text.Pandoc.Lua.Marshal.Format (peekExtensions, pushExtensions)+import Text.Pandoc.Lua.Marshal.List (pushPandocList)+import Text.Pandoc.Lua.Marshal.Template (peekTemplate, pushTemplate)+import Text.Pandoc.Options (WriterOptions (..))++--+-- Writer Options+--++-- | Retrieve a WriterOptions value, either from a normal WriterOptions+-- value, from a read-only object, or from a table with the same+-- keys as a WriterOptions object.+peekWriterOptions :: Peeker PandocError WriterOptions+peekWriterOptions = retrieving "WriterOptions" . \idx ->+  liftLua (ltype idx) >>= \case+    TypeUserdata -> peekUD typeWriterOptions idx+    TypeTable    -> peekWriterOptionsTable idx+    _            -> failPeek =<<+                    typeMismatchMessage "WriterOptions userdata or table" idx++-- | Pushes a WriterOptions value as userdata object.+pushWriterOptions :: Pusher PandocError WriterOptions+pushWriterOptions = pushUD typeWriterOptions++-- | 'WriterOptions' object type.+typeWriterOptions :: DocumentedType PandocError WriterOptions+typeWriterOptions = deftype "WriterOptions"+  [ operation Tostring $ lambda+    ### liftPure show+    <#> udparam typeWriterOptions "opts" "options to print in native format"+    =#> functionResult pushString "string" "Haskell representation"+  ]+  [ property "cite_method"+    "How to print cites"+    (pushViaJSON, writerCiteMethod)+    (peekViaJSON, \opts x -> opts{ writerCiteMethod = x })++  , property "columns"+    "Characters in a line (for text wrapping)"+    (pushIntegral, writerColumns)+    (peekIntegral, \opts x -> opts{ writerColumns = x })++  , property "dpi"+    "DPI for pixel to/from inch/cm conversions"+    (pushIntegral, writerDpi)+    (peekIntegral, \opts x -> opts{ writerDpi = x })++  , property "email_obfuscation"+    "How to obfuscate emails"+    (pushViaJSON, writerEmailObfuscation)+    (peekViaJSON, \opts x -> opts{ writerEmailObfuscation = x })++  , property "split_level"+    "Level at which EPUB or chunked HTML documents are split into files"+    (pushIntegral, writerSplitLevel)+    (peekIntegral, \opts x -> opts{ writerSplitLevel = x })++  , property "epub_chapter_level"+    "Deprecated synonym for split_level"+    (pushIntegral, writerSplitLevel)+    (peekIntegral, \opts x -> opts{ writerSplitLevel = x })++  , property "epub_fonts"+    "Paths to fonts to embed"+    (pushPandocList pushString, writerEpubFonts)+    (peekList peekString, \opts x -> opts{ writerEpubFonts = x })++  , property "epub_metadata"+    "Metadata to include in EPUB"+    (maybe pushnil pushText, writerEpubMetadata)+    (optional . peekText, \opts x -> opts{ writerEpubMetadata = x })++  , property "epub_subdirectory"+    "Subdir for epub in OCF"+    (pushText, writerEpubSubdirectory)+    (peekText, \opts x -> opts{ writerEpubSubdirectory = x })++  , property "extensions"+    "Markdown extensions that can be used"+    (pushExtensions, writerExtensions)+    (peekExtensions, \opts x -> opts{ writerExtensions = x })++  , property "highlight_style"+    "Style to use for highlighting (nil = no highlighting)"+    (maybe pushnil pushViaJSON, writerHighlightStyle)+    (optional . peekViaJSON, \opts x -> opts{ writerHighlightStyle = x })++  , property "html_math_method"+    "How to print math in HTML"+    (pushViaJSON, writerHTMLMathMethod)+    (peekViaJSON, \opts x -> opts{ writerHTMLMathMethod = x })++  , property "html_q_tags"+    "Use @<q>@ tags for quotes in HTML"+    (pushBool, writerHtmlQTags)+    (peekBool, \opts x -> opts{ writerHtmlQTags = x })++  , property "identifier_prefix"+    "Prefix for section & note ids in HTML and for footnote marks in markdown"+    (pushText, writerIdentifierPrefix)+    (peekText, \opts x -> opts{ writerIdentifierPrefix = x })++  , property "incremental"+    "True if lists should be incremental"+    (pushBool, writerIncremental)+    (peekBool, \opts x -> opts{ writerIncremental = x })++  , property "listings"+    "Use listings package for code"+    (pushBool, writerListings)+    (peekBool, \opts x -> opts{ writerListings = x })++  , property "number_offset"+    "Starting number for section, subsection, ..."+    (pushPandocList pushIntegral, writerNumberOffset)+    (peekList peekIntegral, \opts x -> opts{ writerNumberOffset = x })++  , property "number_sections"+    "Number sections in LaTeX"+    (pushBool, writerNumberSections)+    (peekBool, \opts x -> opts{ writerNumberSections = x })++  , property "prefer_ascii"+    "Prefer ASCII representations of characters when possible"+    (pushBool, writerPreferAscii)+    (peekBool, \opts x -> opts{ writerPreferAscii = x })++  , property "reference_doc"+    "Path to reference document if specified"+    (maybe pushnil pushString, writerReferenceDoc)+    (optional . peekString, \opts x -> opts{ writerReferenceDoc = x })++  , property "reference_links"+    "Use reference links in writing markdown, rst"+    (pushBool, writerReferenceLinks)+    (peekBool, \opts x -> opts{ writerReferenceLinks = x })++  , property "reference_location"+    "Location of footnotes and references for writing markdown"+    (pushViaJSON, writerReferenceLocation)+    (peekViaJSON, \opts x -> opts{ writerReferenceLocation = x })++  , property "section_divs"+    "Put sections in div tags in HTML"+    (pushBool, writerSectionDivs)+    (peekBool, \opts x -> opts{ writerSectionDivs = x })++  , property "setext_headers"+    "Use setext headers for levels 1-2 in markdown"+    (pushBool, writerSetextHeaders)+    (peekBool, \opts x -> opts{ writerSetextHeaders = x })++  , property "slide_level"+    "Force header level of slides"+    (maybe pushnil pushIntegral, writerSlideLevel)+    (optional . peekIntegral, \opts x -> opts{ writerSlideLevel = x })++  -- , property "syntax_map" "Syntax highlighting definition"+  --   (pushViaJSON, writerSyntaxMap)+  --   (peekViaJSON, \opts x -> opts{ writerSyntaxMap = x })+    -- :: SyntaxMap++  , property "tab_stop"+    "Tabstop for conversion btw spaces and tabs"+    (pushIntegral, writerTabStop)+    (peekIntegral, \opts x -> opts{ writerTabStop = x })++  , property "table_of_contents"+    "Include table of contents"+    (pushBool, writerTableOfContents)+    (peekBool, \opts x -> opts{ writerTableOfContents = x })++  , property "template"+    "Template to use"+    (maybe pushnil pushTemplate, writerTemplate)+    (optional . peekTemplate, \opts x -> opts{ writerTemplate = x })+    -- :: Maybe (Template Text)++  , property "toc_depth"+    "Number of levels to include in TOC"+    (pushIntegral, writerTOCDepth)+    (peekIntegral, \opts x -> opts{ writerTOCDepth = x })++  , property "top_level_division"+    "Type of top-level divisions"+    (pushViaJSON, writerTopLevelDivision)+    (peekViaJSON, \opts x -> opts{ writerTopLevelDivision = x })++  , property "variables"+    "Variables to set in template"+    (pushContext, writerVariables)+    (peekContext, \opts x -> opts{ writerVariables = x })++  , property "wrap_text"+    "Option for wrapping text"+    (pushViaJSON, writerWrapText)+    (peekViaJSON, \opts x -> opts{ writerWrapText = x })+  ]++-- | Retrieves a 'WriterOptions' object from a table on the stack, using+-- the default values for all missing fields.+--+-- Internally, this pushes the default writer options, sets each+-- key/value pair of the table in the userdata value, then retrieves the+-- object again. This will update all fields and complain about unknown+-- keys.+peekWriterOptionsTable :: Peeker PandocError WriterOptions+peekWriterOptionsTable idx = retrieving "WriterOptions (table)" $ do+  liftLua $ do+    absidx <- absindex idx+    pushUD typeWriterOptions def+    let setFields = do+          next absidx >>= \case+            False -> return () -- all fields were copied+            True -> do+              pushvalue (nth 2) *> insert (nth 2)+              settable (nth 4) -- set in userdata object+              setFields+    pushnil -- first key+    setFields+  peekUD typeWriterOptions top `lastly` pop 1
+ src/Text/Pandoc/Lua/Module/CLI.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE LambdaCase        #-}+{-# LANGUAGE OverloadedStrings #-}+{- |+   Module      : Text.Pandoc.Lua.Module.CLI+   Copyright   : © 2022-2023 Albert Krewinkel+   License     : GPL-2.0-or-later+   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>++Command line helpers+-}+module Text.Pandoc.Lua.Module.CLI+  ( documentedModule+  ) where++import Control.Applicative ((<|>))+import HsLua ( Field (..), Module (..), (###), (<#>), (=#>), (#?)+             , defun, failLua, functionResult, liftIO, parameter, pop+             , pushViaJSON, rawgeti, top)+import HsLua.Marshalling (lastly, liftLua, peekList, peekString)+import Text.Pandoc.App (defaultOpts, options, parseOptionsFromArgs)+import Text.Pandoc.Error (PandocError)+import Text.Pandoc.Lua.PandocLua ()+import qualified Data.Text as T++-- | Push the pandoc.types module on the Lua stack.+documentedModule :: Module PandocError+documentedModule = Module+  { moduleName = "pandoc.cli"+  , moduleDescription =+      "Command line options and argument parsing."+  , moduleFields =+      [ Field+        { fieldName = "default_options"+        , fieldDescription = "Default CLI options, using a JSON-like " <>+          "representation."+        , fieldPushValue = pushViaJSON defaultOpts+        }+      ]+  , moduleFunctions =+      [ defun "parse_options"+        ### parseOptions+        <#> parameter peekArgs "{string,...}" "args"+              "list of command line arguments"+        =#> functionResult pushViaJSON "table"+              "parsed options, using their JSON-like representation."+        #? T.unlines+           [ "Parses command line arguments into pandoc options."+           , "Typically this function will be used in stand-alone pandoc Lua"+           , "scripts, taking the list of arguments from the global `arg`."+           ]+      ]+  , moduleOperations = []+  }+ where+  peekArgs idx =+    (,)+    <$> (liftLua (rawgeti idx 0) *> (peekString top <|> pure "") `lastly` pop 1)+    <*> peekList peekString idx++  parseOptions (prg, args) =+    liftIO (parseOptionsFromArgs options defaultOpts prg args) >>=+    \case+      Left e     -> failLua $ "Cannot process info option: " ++ show e+      Right opts -> pure opts
+ src/Text/Pandoc/Lua/Module/Format.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE OverloadedStrings #-}+{- |+   Module      : Text.Pandoc.Lua.Module.Format+   Copyright   : © 2022-2023 Albert Krewinkel+   License     : GPL-2.0-or-later+   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>++Lua module to handle pandoc templates.+-}+module Text.Pandoc.Lua.Module.Format+  ( documentedModule+  ) where++import HsLua+import Text.Pandoc.Error (PandocError)+import Text.Pandoc.Extensions (getAllExtensions, getDefaultExtensions)+import Text.Pandoc.Format (getExtensionsConfig)+import Text.Pandoc.Lua.Marshal.Format (pushExtensions, pushExtensionsConfig)+import Text.Pandoc.Lua.PandocLua ()++import qualified Data.Text as T++-- | The "pandoc.format" module.+documentedModule :: Module PandocError+documentedModule = Module+  { moduleName = "pandoc.format"+  , moduleDescription = T.unlines+    [ "Pandoc formats and their extensions."+    ]+  , moduleFields = []+  , moduleOperations = []+  , moduleFunctions = functions+  }++-- | Extension module functions.+functions :: [DocumentedFunction PandocError]+functions =+  [ defun "default_extensions"+     ### liftPure getDefaultExtensions+     <#> parameter peekText "string" "format" "format name"+     =#> functionResult pushExtensions "FormatExtensions"+           "default extensions enabled for `format`"+     #? T.unlines+        [ "Returns the list of default extensions of the given format; this"+        , "function does not check if the format is supported, it will return"+        , "a fallback list of extensions even for unknown formats."+        ]++  , defun "all_extensions"+     ### liftPure getAllExtensions+     <#> parameter peekText "string" "format" "format name"+     =#> functionResult pushExtensions "FormatExtensions"+           "all extensions supported for `format`"+     #? T.unlines+        [ "Returns the list of all valid extensions for a format."+        , "No distinction is made between input and output; an extension"+        , "can have an effect when reading a format but not when"+        , "writing it, or *vice versa*."+        ]++  , defun "extensions"+     ### liftPure getExtensionsConfig+     <#> textParam "format" "format identifier"+     =#> functionResult pushExtensionsConfig "table" "extensions config"+     #? T.unlines+        [ "Returns the extension configuration for the given format."+        , "The configuration is represented as a table with all supported"+        , "extensions as keys and their default status as value, with"+        , "`true` indicating that the extension is enabled by default,"+        , "while `false` marks a supported extension that's disabled."+        , ""+        , "This function can be used to assign a value to the `Extensions`"+        , "global in custom readers and writers."+        ]+  ]
+ src/Text/Pandoc/Lua/Module/MediaBag.hs view
@@ -0,0 +1,174 @@+{-# LANGUAGE LambdaCase        #-}+{-# LANGUAGE OverloadedStrings #-}+{- |+   Module      : Text.Pandoc.Lua.Module.MediaBag+   Copyright   : Copyright © 2017-2023 Albert Krewinkel+   License     : GNU GPL, version 2 or above+   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>++The Lua module @pandoc.mediabag@.+-}+module Text.Pandoc.Lua.Module.MediaBag+  ( documentedModule+  ) where++import Prelude hiding (lookup)+import Data.Maybe (fromMaybe)+import Data.Version (makeVersion)+import HsLua ( LuaE, DocumentedFunction, Module (..)+             , (<#>), (###), (=#>), (=?>), (#?), defun, functionResult+             , opt, parameter, since, stringParam, textParam)+import Text.Pandoc.Class ( CommonState (..), fetchItem, fillMediaBag+                         , getMediaBag, modifyCommonState, setMediaBag)+import Text.Pandoc.Class.IO (writeMedia)+import Text.Pandoc.Error (PandocError)+import Text.Pandoc.Lua.Marshal.Pandoc (peekPandoc, pushPandoc)+import Text.Pandoc.Lua.Marshal.List (pushPandocList)+import Text.Pandoc.Lua.Orphans ()+import Text.Pandoc.Lua.PandocLua (unPandocLua)+import Text.Pandoc.MIME (MimeType)++import qualified Data.ByteString.Lazy as BL+import qualified Data.Text as T+import qualified HsLua as Lua+import qualified Text.Pandoc.MediaBag as MB++--+-- MediaBag submodule+--+documentedModule :: Module PandocError+documentedModule = Module+  { moduleName = "pandoc.mediabag"+  , moduleDescription = "mediabag access"+  , moduleFields = []+  , moduleFunctions =+      [ delete+      , empty+      , fetch+      , fill+      , insert+      , items+      , list+      , lookup+      , write+      ]+  , moduleOperations = []+  }++-- | Delete a single item from the media bag.+delete :: DocumentedFunction PandocError+delete = defun "delete"+  ### (\fp -> unPandocLua $ modifyCommonState+              (\st -> st { stMediaBag = MB.deleteMedia fp (stMediaBag st) }))+  <#> stringParam "filepath" "filename of item to delete"+  =#> []+++-- | Delete all items from the media bag.+empty :: DocumentedFunction PandocError+empty = defun "empty"+  ### unPandocLua (modifyCommonState (\st -> st { stMediaBag = mempty }))+  =#> []++-- | Fill the mediabag with all images in the document that aren't+-- present yet.+fill :: DocumentedFunction PandocError+fill = defun "fill"+  ### unPandocLua . fillMediaBag+  <#> parameter peekPandoc "Pandoc" "doc"+        "document from which to fill the mediabag"+  =#> functionResult pushPandoc "Pandoc" "modified document"+  #? ("Fills the mediabag with the images in the given document.\n" <>+      "An image that cannot be retrieved will be replaced with a Span\n" <>+      "of class \"image\" that contains the image description.\n" <>+      "" <>+      "Images for which the mediabag already contains an item will\n" <>+      "not be processed again.")++-- | Insert a new item into the media bag.+insert :: DocumentedFunction PandocError+insert = defun "insert"+  ### (\fp mmime contents -> unPandocLua $ do+          mb <- getMediaBag+          setMediaBag $ MB.insertMedia fp mmime contents mb+          return (Lua.NumResults 0))+  <#> stringParam "filepath" "item file path"+  <#> opt (textParam "mimetype" "the item's MIME type")+  <#> parameter Lua.peekLazyByteString "string" "contents" "binary contents"+  =#> []++-- | Returns iterator values to be used with a Lua @for@ loop.+items :: DocumentedFunction PandocError+items = defun "items"+  ### (do+          mb <-unPandocLua getMediaBag+          let pushItem (fp, mimetype, contents) = do+                Lua.pushString fp+                Lua.pushText mimetype+                Lua.pushByteString $ BL.toStrict contents+                return (Lua.NumResults 3)+          Lua.pushIterator pushItem (MB.mediaItems mb))+  =?> "Iterator triple"++-- | Function to lookup a value in the mediabag.+lookup :: DocumentedFunction PandocError+lookup = defun "lookup"+  ### (\fp -> unPandocLua (MB.lookupMedia fp <$> getMediaBag) >>= \case+          Nothing   -> 1 <$ Lua.pushnil+          Just item -> 2 <$ do+            Lua.pushText $ MB.mediaMimeType item+            Lua.pushLazyByteString $ MB.mediaContents item)+  <#> stringParam "filepath" "path of item to lookup"+  =?> "MIME type and contents"++-- | Function listing all mediabag items.+list :: DocumentedFunction PandocError+list = defun "list"+  ### (unPandocLua (MB.mediaDirectory <$> getMediaBag))+  =#> functionResult (pushPandocList pushEntry) "table" "list of entry triples"+ where+  pushEntry :: (FilePath, MimeType, Int) -> LuaE PandocError ()+  pushEntry (fp, mimeType, contentLength) = do+    Lua.newtable+    Lua.pushName "path"   *> Lua.pushString fp              *> Lua.rawset (-3)+    Lua.pushName "type"   *> Lua.pushText mimeType          *> Lua.rawset (-3)+    Lua.pushName "length" *> Lua.pushIntegral contentLength *> Lua.rawset (-3)++-- | Lua function to retrieve a new item.+fetch :: DocumentedFunction PandocError+fetch = defun "fetch"+  ### (\src -> do+          (bs, mimeType) <- unPandocLua $ fetchItem src+          Lua.pushText $ fromMaybe "" mimeType+          Lua.pushByteString bs+          return 2)+  <#> textParam "src" "URI to fetch"+  =?> "Returns two string values: the fetched contents and the mimetype."++-- | Extract the mediabag or just a single entry.+write :: DocumentedFunction PandocError+write = defun "write"+  ### (\dir mfp -> do+          mb <- unPandocLua getMediaBag+          case mfp of+            Nothing -> unPandocLua $ mapM_ (writeMedia dir) (MB.mediaItems mb)+            Just fp -> do+              case MB.lookupMedia fp mb of+                Nothing   -> Lua.failLua ("Resource not in mediabag: " <> fp)+                Just item -> unPandocLua $ do+                  let triple = ( MB.mediaPath item+                               , MB.mediaMimeType item+                               , MB.mediaContents item+                               )+                  writeMedia dir triple)+  <#> stringParam "dir" "path of the target directory"+  <#> opt (stringParam "fp" "canonical name (relative path) of resource")+  =#> []+  #? T.unlines+     [ "Writes the contents of  mediabag to the given target directory. If"+     , "`fp` is given, then only the resource with the given name will be"+     , "extracted. Omitting that parameter means that the whole mediabag"+     , "gets extracted. An error is thrown if `fp` is given but cannot be"+     , "found in the mediabag."+     ]+  `since` makeVersion [3, 0]
+ src/Text/Pandoc/Lua/Module/Pandoc.hs view
@@ -0,0 +1,317 @@+{-# LANGUAGE LambdaCase        #-}+{-# LANGUAGE FlexibleContexts  #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications  #-}+{- |+   Module      : Text.Pandoc.Lua.Module.Pandoc+   Copyright   : Copyright © 2017-2023 Albert Krewinkel+   License     : GNU GPL, version 2 or above++   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>+   Stability   : alpha++Pandoc module for lua.+-}+module Text.Pandoc.Lua.Module.Pandoc+  ( documentedModule+  ) where++import Prelude hiding (read)+import Control.Applicative ((<|>))+import Control.Monad (forM_, when)+import Control.Monad.Catch (catch, throwM)+import Data.Data (Data, dataTypeConstrs, dataTypeOf, showConstr)+import Data.Default (Default (..))+import Data.Maybe (fromMaybe)+import Data.Proxy (Proxy (Proxy))+import HsLua+import System.Exit (ExitCode (..))+import Text.Pandoc.Definition+import Text.Pandoc.Error (PandocError (..))+import Text.Pandoc.Format (parseFlavoredFormat)+import Text.Pandoc.Lua.Orphans ()+import Text.Pandoc.Lua.Marshal.AST+import Text.Pandoc.Lua.Marshal.Format (peekFlavoredFormat)+import Text.Pandoc.Lua.Marshal.Filter (peekFilter)+import Text.Pandoc.Lua.Marshal.ReaderOptions ( peekReaderOptions+                                             , pushReaderOptions)+import Text.Pandoc.Lua.Marshal.Sources (peekSources)+import Text.Pandoc.Lua.Marshal.WriterOptions ( peekWriterOptions+                                             , pushWriterOptions)+import Text.Pandoc.Lua.Module.Utils (sha1)+import Text.Pandoc.Lua.PandocLua (PandocLua (unPandocLua))+import Text.Pandoc.Lua.Writer.Classic (runCustom)+import Text.Pandoc.Options ( ReaderOptions (readerExtensions)+                           , WriterOptions (writerExtensions) )+import Text.Pandoc.Process (pipeProcess)+import Text.Pandoc.Readers (Reader (..), getReader, readers)+import Text.Pandoc.Sources (toSources)+import Text.Pandoc.Writers (Writer (..), getWriter, writers)++import qualified HsLua as Lua+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString.Lazy.Char8 as BSL+import qualified Data.Set as Set+import qualified Data.Text as T+import qualified Text.Pandoc.UTF8 as UTF8++documentedModule :: Module PandocError+documentedModule = Module+  { moduleName = "pandoc"+  , moduleDescription = T.unlines+    [ "Lua functions for pandoc scripts; includes constructors for"+    , "document elements, functions to parse text in a given"+    , "format, and functions to filter and modify a subtree."+    ]+  , moduleFields = readersField : writersField :+                   stringConstants ++ [inlineField, blockField]+  , moduleOperations = []+  , moduleFunctions = mconcat+      [ functions+      , otherConstructors+      , blockConstructors+      , inlineConstructors+      , metaValueConstructors+      ]+  }++-- | Set of input formats accepted by @read@.+readersField :: Field PandocError+readersField = Field+  { fieldName = "readers"+  , fieldDescription = T.unlines+    [ "Set of formats that pandoc can parse. All keys in this table can"+    , "be used as the `format` value in `pandoc.read`."+    ]+  , fieldPushValue = pushSet pushText $+                     Set.fromList (map fst (readers @PandocLua))+  }++-- | Set of input formats accepted by @write@.+writersField :: Field PandocError+writersField = Field+  { fieldName = "writers"+  , fieldDescription = T.unlines+    [ "Set of formats that pandoc can generate. All keys in this table"+    , "can be used as the `format` value in `pandoc.write`."+    ]+  , fieldPushValue = pushSet pushText $+                     Set.fromList (map fst (writers @PandocLua))+  }++-- | Inline table field+inlineField :: Field PandocError+inlineField = Field+  { fieldName = "Inline"+  , fieldDescription = "Inline constructors, nested under 'constructors'."+  -- the nesting happens for historical reasons and should probably be+  -- changed.+  , fieldPushValue = pushWithConstructorsSubtable inlineConstructors+  }++-- | @Block@ module field+blockField :: Field PandocError+blockField = Field+  { fieldName = "Block"+  , fieldDescription = "Inline constructors, nested under 'constructors'."+  -- the nesting happens for historical reasons and should probably be+  -- changed.+  , fieldPushValue = pushWithConstructorsSubtable blockConstructors+  }++pushWithConstructorsSubtable :: [DocumentedFunction PandocError]+                             -> LuaE PandocError ()+pushWithConstructorsSubtable constructors = do+  newtable -- Field table+  newtable -- constructor table+  pushName "constructor" *> pushvalue (nth 2) *> rawset (nth 4)+  forM_ constructors $ \fn -> do+    pushName (functionName fn)+    pushDocumentedFunction fn+    rawset (nth 3)+  pop 1 -- pop constructor table++otherConstructors :: [DocumentedFunction PandocError]+otherConstructors =+  [ mkPandoc+  , mkMeta+  , mkAttr+  , mkAttributeList+  , mkBlocks+  , mkCitation+  , mkCell+  , mkRow+  , mkTableHead+  , mkTableFoot+  , mkInlines+  , mkListAttributes+  , mkSimpleTable++  , defun "ReaderOptions"+    ### liftPure id+    <#> parameter peekReaderOptions "ReaderOptions|table" "opts" "reader options"+    =#> functionResult pushReaderOptions "ReaderOptions" "new object"+    #? "Creates a new ReaderOptions value."++  , defun "WriterOptions"+    ### liftPure id+    <#> parameter peekWriterOptions "WriterOptions|table" "opts"+          "writer options"+    =#> functionResult pushWriterOptions "WriterOptions" "new object"+    #? "Creates a new WriterOptions value."+  ]++stringConstants :: [Field e]+stringConstants =+  let constrs :: forall a. Data a => Proxy a -> [String]+      constrs _ = map showConstr . dataTypeConstrs . dataTypeOf @a $ undefined+      nullaryConstructors = mconcat+        [ constrs (Proxy @ListNumberStyle)+        , constrs (Proxy @ListNumberDelim)+        , constrs (Proxy @QuoteType)+        , constrs (Proxy @MathType)+        , constrs (Proxy @Alignment)+        , constrs (Proxy @CitationMode)+        ]+      toField s = Field+        { fieldName = T.pack s+        , fieldDescription = T.pack s+        , fieldPushValue = pushString s+        }+  in map toField nullaryConstructors++functions :: [DocumentedFunction PandocError]+functions =+  [ defun "pipe"+    ### (\command args input -> do+            (ec, output) <- Lua.liftIO $ pipeProcess Nothing command args input+                            `catch` (throwM . PandocIOError "pipe")+            case ec of+              ExitSuccess -> 1 <$ Lua.pushLazyByteString output+              ExitFailure n -> do+                pushPipeError (PipeError (T.pack command) n output)+                Lua.error)+    <#> parameter peekString "string" "command" "path to executable"+    <#> parameter (peekList peekString) "{string,...}" "args"+          "list of arguments"+    <#> parameter peekLazyByteString "string" "input"+          "input passed to process via stdin"+    =?> "output string, or error triple"++  , defun "read"+    ### (\content mformatspec mreaderOptions -> unPandocLua $ do+            flvrd <- maybe (parseFlavoredFormat "markdown") pure mformatspec+            let readerOpts = fromMaybe def mreaderOptions+            getReader flvrd >>= \case+              (TextReader r, es)       ->+                 r readerOpts{readerExtensions = es}+                   (case content of+                      Left bs       -> toSources $ UTF8.toText bs+                      Right sources -> sources)+              (ByteStringReader r, es) ->+                 case content of+                   Left bs -> r readerOpts{readerExtensions = es}+                                (BSL.fromStrict bs)+                   Right _ -> throwM $ PandocLuaError+                              "Cannot use bytestring reader with Sources")+    <#> parameter (\idx -> (Left  <$> peekByteString idx)+                       <|> (Right <$> peekSources idx))+          "string|Sources" "content" "text to parse"+    <#> opt (parameter peekFlavoredFormat "string|table"+                       "formatspec" "format and extensions")+    <#> opt (parameter peekReaderOptions "ReaderOptions" "reader_options"+             "reader options")+    =#> functionResult pushPandoc "Pandoc" "result document"++  , sha1++  , defun "walk_block"+    ### walkElement+    <#> parameter peekBlockFuzzy "Block" "block" "element to traverse"+    <#> parameter peekFilter "Filter" "lua_filter" "filter functions"+    =#> functionResult pushBlock "Block" "modified Block"++  , defun "walk_inline"+    ### walkElement+    <#> parameter peekInlineFuzzy "Inline" "inline" "element to traverse"+    <#> parameter peekFilter "Filter" "lua_filter" "filter functions"+    =#> functionResult pushInline "Inline" "modified Inline"++  , defun "write"+    ### (\doc mformatspec mwriterOpts -> unPandocLua $ do+            flvrd <- maybe (parseFlavoredFormat "markdown") pure mformatspec+            let writerOpts = fromMaybe def mwriterOpts+            getWriter flvrd >>= \case+              (TextWriter w, es)      -> Right <$>+                w writerOpts{ writerExtensions = es } doc+              (ByteStringWriter w, es) -> Left <$>+                w writerOpts{ writerExtensions = es } doc)+    <#> parameter peekPandoc "Pandoc" "doc" "document to convert"+    <#> opt (parameter peekFlavoredFormat "string|table"+                       "formatspec" "format and extensions")+    <#> opt (parameter peekWriterOptions "WriterOptions" "writer_options"+              "writer options")+    =#> functionResult (either pushLazyByteString pushText) "string"+          "result document"++  , defun "write_classic"+    ### (\doc mwopts -> runCustom (fromMaybe def mwopts) doc)+    <#> parameter peekPandoc "Pandoc" "doc" "document to convert"+    <#> opt (parameter peekWriterOptions "WriterOptions" "writer_options"+              "writer options")+    =#> functionResult pushText "string" "rendered document"+    #? (T.unlines+       [ "Runs a classic custom Lua writer, using the functions defined"+       , "in the current environment."+       ])+  ]+ where+  walkElement x f =+        walkInlineSplicing f x+    >>= walkInlinesStraight f+    >>= walkBlockSplicing f+    >>= walkBlocksStraight f++data PipeError = PipeError+  { pipeErrorCommand :: T.Text+  , pipeErrorCode :: Int+  , pipeErrorOutput :: BL.ByteString+  }++peekPipeError :: LuaError e => StackIndex -> LuaE e PipeError+peekPipeError idx =+  PipeError+  <$> (Lua.getfield idx "command"    *> Lua.peek (-1) <* Lua.pop 1)+  <*> (Lua.getfield idx "error_code" *> Lua.peek (-1) <* Lua.pop 1)+  <*> (Lua.getfield idx "output"     *> Lua.peek (-1) <* Lua.pop 1)++pushPipeError :: LuaError e => Pusher e PipeError+pushPipeError pipeErr = do+  pushAsTable [ ("command"    , pushText . pipeErrorCommand)+              , ("error_code" , pushIntegral . pipeErrorCode)+              , ("output"     , pushLazyByteString . pipeErrorOutput)+              ] pipeErr+  pushPipeErrorMetaTable+  Lua.setmetatable (nth 2)+    where+      pushPipeErrorMetaTable :: LuaError e => LuaE e ()+      pushPipeErrorMetaTable = do+        v <- Lua.newmetatable "pandoc pipe error"+        when v $ do+          pushName "__tostring"+          pushHaskellFunction pipeErrorMessage+          rawset (nth 3)++      pipeErrorMessage :: LuaError e => LuaE e NumResults+      pipeErrorMessage = do+        (PipeError cmd errorCode output) <- peekPipeError (nthBottom 1)+        pushByteString . BSL.toStrict . BSL.concat $+          [ BSL.pack "Error running "+          , BSL.pack $ T.unpack cmd+          , BSL.pack " (error code "+          , BSL.pack $ show errorCode+          , BSL.pack "): "+          , if output == mempty then BSL.pack "<no output>" else output+          ]+        return (NumResults 1)
+ src/Text/Pandoc/Lua/Module/Scaffolding.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE OverloadedStrings #-}+{- |+   Module      : Text.Pandoc.Lua.Module.Scaffolding+   Copyright   : Copyright © 2022-2023 Albert Krewinkel, John MacFarlane+   License     : GNU GPL, version 2 or above+   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>++Scaffolding for custom Writers.+-}+module Text.Pandoc.Lua.Module.Scaffolding+  ( documentedModule+  ) where++import HsLua+import Text.Pandoc.Error (PandocError)+import Text.Pandoc.Lua.Writer.Scaffolding (pushWriterScaffolding)+import qualified Data.Text as T++-- | The "pandoc.template" module.+documentedModule :: Module PandocError+documentedModule = Module+  { moduleName = "pandoc.scaffolding"+  , moduleDescription = T.unlines+    [ "Scaffolding for custom writers."+    ]+  , moduleFields = [writerScaffolding]+  , moduleOperations = []+  , moduleFunctions = []+  }++-- | Template module functions.+writerScaffolding :: Field PandocError+writerScaffolding = Field+  { fieldName = "Writer"+  , fieldDescription = T.unlines+    [ "An object to be used as a `Writer` function; the construct handles"+    , "most of the boilerplate, expecting only render functions for all"+    , "AST elements"+    ]+  , fieldPushValue = do+      pushWriterScaffolding+      -- pretend that it's a submodule so we get better error messages+      getfield registryindex loaded+      pushvalue (nth 2)+      setfield (nth 2) (submod "Writer")+      -- same for fields "Block" and "Inline"+      getfield (nth 2) "Inline" *> setfield (nth 2) (submod "Writer.Inline")+      getfield (nth 2) "Block" *> setfield (nth 2) (submod "Writer.Block")++      pop 1 -- remove "LOADED_TABLE"+  }+ where submod x = moduleName documentedModule <> "." <> x
+ src/Text/Pandoc/Lua/Module/Structure.hs view
@@ -0,0 +1,192 @@+{-# LANGUAGE LambdaCase        #-}+{-# LANGUAGE OverloadedStrings #-}+{- |+   Module      : Text.Pandoc.Lua.Module.Structure+   Copyright   : © 2023 Albert Krewinkel+   License     : GPL-2.0-or-later+   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>++Command line helpers+-}+module Text.Pandoc.Lua.Module.Structure+  ( documentedModule+  ) where++import Control.Applicative ((<|>), optional)+import Data.Default (Default (..))+import Data.Maybe (fromMaybe)+import HsLua ( DocumentedFunction, LuaError, Module (..), Peeker+             , (###), (<#>), (=#>), (#?)+             , defun, functionResult, getfield, isnil, lastly, liftLua+             , opt, liftPure, parameter , peekBool, peekIntegral+             , peekFieldRaw, peekText, pop, pushIntegral, top )+import Text.Pandoc.Chunks ( ChunkedDoc (..), PathTemplate (..)+                          , tocToList, splitIntoChunks )+import Text.Pandoc.Definition (Pandoc (..), Block)+import Text.Pandoc.Error (PandocError)+import Text.Pandoc.Lua.PandocLua ()+import Text.Pandoc.Lua.Marshal.AST ( peekBlocksFuzzy, peekPandoc+                                   , pushBlock, pushBlocks )+import Text.Pandoc.Lua.Marshal.Chunks+import Text.Pandoc.Lua.Marshal.WriterOptions ( peekWriterOptions )+import Text.Pandoc.Options (WriterOptions (writerTOCDepth,+                                           writerNumberSections))+import Text.Pandoc.Slides (getSlideLevel, prepSlides)+import Text.Pandoc.Writers.Shared (toTableOfContents)+import qualified Data.Text as T+import qualified Text.Pandoc.Shared as Shared++-- | Push the pandoc.structure module on the Lua stack.+documentedModule :: Module PandocError+documentedModule = Module+  { moduleName = "pandoc.structure"+  , moduleDescription =+    "Access to the higher-level document structure, including" <>+    "hierarchical sections and the table of contents."+  , moduleFields = []+  , moduleFunctions =+      [ make_sections+      , slide_level+      , split_into_chunks+      , table_of_contents+      ]+  , moduleOperations = []+  }++make_sections :: LuaError e => DocumentedFunction e+make_sections = defun "make_sections"+  ### (\blks mopts ->+         let (numSects, baseLevel, mslideLevel) =+               fromMaybe (defNumSec, Nothing, Nothing) mopts+             blks' = case mslideLevel of+                       Just l | l <= 0 -> prepSlides (getSlideLevel blks) blks+                       Just sl -> prepSlides sl blks+                       Nothing -> blks+         in pure $ Shared.makeSections numSects baseLevel blks')+  <#> parameter peekBodyBlocks "Blocks" "blocks" "document blocks to process"+  <#> opt (parameter peekOpts "table" "opts" "options")+  =#> functionResult pushBlocks "list of Blocks"+        "processed blocks"+  #? T.unlines+     [ "Puts [Blocks] into a hierarchical structure: a list of sections"+     , "(each a Div with class \"section\" and first element a Header)."+     , ""+     , "The optional `opts` argument can be a table; two settings are"+     , "recognized: If `number_sections` is true, a `number` attribute"+     , "containing the section number will be added to each `Header`. If"+     , "`base_level` is an integer, then `Header` levels will be"+     , "reorganized so that there are no gaps, with numbering levels"+     , "shifted by the given value. Finally, an integer `slide_level`"+     , "value triggers the creation of slides at that heading level."+     , ""+     , "Note that a [WriterOptions][] object can be passed as the opts"+     , "table; this will set the `number_section` and `slide_level` values"+     , "to those defined on the command line."+     ]+  where+    defNumSec = False+    peekOpts idx = do+      numberSections <- fromMaybe defNumSec <$> do+        liftLua $ getfield idx "number_sections"+        optional (peekBool top `lastly` pop 1)+      baseLevel <- do+        liftLua $ getfield idx "base_level"+        optional (peekIntegral top `lastly` pop 1)+      slideLevel <- do+        liftLua $ getfield idx "slide_level"+        optional (peekIntegral top `lastly` pop 1)+      return (numberSections, baseLevel, slideLevel)++slide_level :: LuaError e => DocumentedFunction e+slide_level = defun "slide_level"+  ### liftPure getSlideLevel+  <#> parameter peekBodyBlocks "Pandoc|Blocks" "blocks" "document body"+  =#> functionResult pushIntegral "integer" "slide level"+  #? T.unlines+  [ "Find level of header that starts slides (defined as the least"+  , "header level that occurs before a non-header/non-hrule in the"+  , "blocks)."+  ]++-- | Split 'Pandoc' into 'Chunk's.+split_into_chunks :: LuaError e => DocumentedFunction e+split_into_chunks = defun "split_into_chunks"+  ### (\doc mopts -> pure $+          let defOpts = (defPathTmpl, defNumSects, Nothing, defLvl)+              (pathTempl, numberSect, mbBaseLevel, chunkLevel) =+                fromMaybe defOpts mopts+          in splitIntoChunks pathTempl numberSect mbBaseLevel chunkLevel doc)+  <#> parameter peekPandoc "Pandoc" "doc" "document to split"+  <#> opt (parameter peekSplitOpts "table" "opts" optionsDescr)+  =#> functionResult pushChunkedDoc "ChunkedDoc" ""+  #? T.unlines+     [ "Converts a `Pandoc` document into a `ChunkedDoc`." ]+ where+  defPathTmpl = PathTemplate "chunk-%n"+  defNumSects = False+  defLvl = 1+  peekSplitOpts idx = (,,,)+    <$> peekFieldRaw ((fmap PathTemplate . peekText) `orDefault` defPathTmpl)+                     "path_template" idx+    <*> peekFieldRaw (peekBool `orDefault` defNumSects) "number_sections" idx+    <*> peekFieldRaw (optional . peekIntegral) "base_heading_level" idx+    <*> peekFieldRaw (peekIntegral `orDefault` defLvl) "chunk_level" idx+  orDefault p defaultValue idx' = liftLua (isnil idx') >>= \case+    True  -> pure defaultValue+    False -> p idx'+  optionsDescr = T.unlines+    [ "The following options are supported:"+    , ""+    , "    `path_template`"+    , "    :   template used to generate the chunks' filepaths"+    , "        `%n` will be replaced with the chunk number (padded with"+    , "        leading 0s to 3 digits), `%s` with the section number of"+    , "        the heading, `%h` with the (stringified) heading text,"+    , "        `%i` with the section identifier. For example,"+    , "        `\"section-%s-%i.html\"` might be resolved to"+    , "        `\"section-1.2-introduction.html\"`."+    , ""+    , "        Default is `\"chunk-%n\"` (string)"+    , ""+    , "    `number_sections`"+    , "    :   whether sections should be numbered; default is `false`"+    , "        (boolean)"+    , ""+    , "    `chunk_level`"+    , "    :   The heading level the document should be split into"+    , "        chunks. The default is to split at the top-level, i.e.,"+    , "        `1`. (integer)"+    , ""+    , "    `base_heading_level`"+    , "    :   The base level to be used for numbering. Default is `nil`"+    , "        (integer|nil)"+    ]++-- | Generate a table of contents.+table_of_contents :: DocumentedFunction PandocError+table_of_contents = defun "table_of_contents"+  ### (\tocSource mwriterOpts -> pure $+          let writerOpts = fromMaybe def mwriterOpts+          in case tocSource of+               Left blks  -> toTableOfContents writerOpts blks+               Right tree -> tocToList (writerNumberSections writerOpts)+                                       (writerTOCDepth writerOpts) tree+      )+  <#> parameter peekTocSource "Blocks|Pandoc|ChunkedDoc" "toc_source"+        "list of command line arguments"+  <#> opt (parameter peekWriterOptions "WriterOptions" "opts" "options")+  =#> functionResult pushBlock "Block"+        "Table of contents as a BulletList object"+  #? T.unlines+     [ "Generates a table of contents for the given object." ]+ where+  peekTocSource idx =+    (Left <$> peekBodyBlocks idx) <|>+    (Right . chunkedTOC <$> peekChunkedDoc idx)++-- | Retrieves the body blocks of a 'Pandoc' object or from a list of+-- blocks.+peekBodyBlocks :: LuaError e => Peeker e [Block]+peekBodyBlocks idx =+  ((\(Pandoc _ blks) -> blks) <$> peekPandoc idx) <|>+  peekBlocksFuzzy idx
+ src/Text/Pandoc/Lua/Module/System.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{- |+   Module      : Text.Pandoc.Lua.Module.System+   Copyright   : © 2019-2023 Albert Krewinkel+   License     : GNU GPL, version 2 or above++   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>+   Stability   : alpha++Pandoc's system Lua module.+-}+module Text.Pandoc.Lua.Module.System+  ( documentedModule+  ) where++import HsLua+import HsLua.Module.System+  (arch, env, getwd, ls, mkdir, os, rmdir, with_env, with_tmpdir, with_wd)++-- | Push the pandoc.system module on the Lua stack.+documentedModule :: LuaError e => Module e+documentedModule = Module+  { moduleName = "pandoc.system"+  , moduleDescription = "system functions"+  , moduleFields =+      [ arch+      , os+      ]+  , moduleFunctions =+      [ setName "environment" env+      , setName "get_working_directory" getwd+      , setName "list_directory" ls+      , setName "make_directory" mkdir+      , setName "remove_directory" rmdir+      , setName "with_environment" with_env+      , setName "with_temporary_directory" with_tmpdir+      , setName "with_working_directory" with_wd+      ]+  , moduleOperations = []+  }
+ src/Text/Pandoc/Lua/Module/Template.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE OverloadedStrings #-}+{- |+   Module      : Text.Pandoc.Lua.Module.Template+   Copyright   : Copyright © 2022-2023 Albert Krewinkel, John MacFarlane+   License     : GNU GPL, version 2 or above+   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>++Lua module to handle pandoc templates.+-}+module Text.Pandoc.Lua.Module.Template+  ( documentedModule+  ) where++import HsLua+import HsLua.Module.DocLayout (peekDoc, pushDoc)+import Text.Pandoc.Error (PandocError)+import Text.Pandoc.Lua.Marshal.AST (peekMeta, pushBlocks, pushInlines)+import Text.Pandoc.Lua.Marshal.Context (peekContext, pushContext)+import Text.Pandoc.Lua.Marshal.Template (peekTemplate, pushTemplate)+import Text.Pandoc.Lua.PandocLua (PandocLua (unPandocLua), liftPandocLua)+import Text.Pandoc.Writers.Shared (metaToContext')+import Text.Pandoc.Templates+  ( compileTemplate, getDefaultTemplate, renderTemplate+  , runWithPartials, runWithDefaultPartials )++import qualified Data.Text as T++-- | The "pandoc.template" module.+documentedModule :: Module PandocError+documentedModule = Module+  { moduleName = "pandoc.template"+  , moduleDescription = T.unlines+    [ "Lua functions for pandoc templates."+    ]+  , moduleFields = []+  , moduleOperations = []+  , moduleFunctions = functions+  }++-- | Template module functions.+functions :: [DocumentedFunction PandocError]+functions =+  [ defun "apply"+     ### liftPure2 renderTemplate+     <#> parameter peekTemplate "Template" "template" "template to apply"+     <#> parameter peekContext "table" "context" "variable values"+     =#> functionResult pushDoc "Doc" "rendered template"+     #? T.unlines+     [ "Applies a context with variable assignments to a template,"+     , "returning the rendered template. The `context` parameter must be a"+     , "table with variable names as keys and [Doc], string, boolean, or"+     , "table as values, where the table can be either be a list of the"+     , "aforementioned types, or a nested context."+     ]++  , defun "compile"+     ### (\template mfilepath -> unPandocLua $+           case mfilepath of+             Just fp -> runWithPartials (compileTemplate fp template)+             Nothing -> runWithDefaultPartials+                        (compileTemplate "templates/default" template))+     <#> parameter peekText "string" "template" "template string"+     <#> opt (stringParam "templ_path" "template path")+     =#> functionResult (either failLua pushTemplate) "pandoc Template"+           "compiled template"++  , defun "default"+     ### (\mformat -> unPandocLua $ do+           let getFORMAT = liftPandocLua $ do+                 getglobal "FORMAT"+                 forcePeek $ peekText top `lastly` pop 1+           format <- maybe getFORMAT pure mformat+           getDefaultTemplate format)+     <#> opt (textParam "writer"+              "writer for which the template should be returned.")+     =#> functionResult pushText "string"+           "string representation of the writer's default template"++  , defun "meta_to_context"+     ### (\meta blockWriterIdx inlineWriterIdx -> unPandocLua $ do+             let blockWriter blks = liftPandocLua $ do+                   pushvalue blockWriterIdx+                   pushBlocks blks+                   callTrace 1 1+                   forcePeek $ peekDoc top+             let inlineWriter blks = liftPandocLua $ do+                   pushvalue inlineWriterIdx+                   pushInlines blks+                   callTrace 1 1+                   forcePeek $ peekDoc top+             metaToContext' blockWriter inlineWriter meta)+     <#> parameter peekMeta "Meta" "meta" "document metadata"+     <#> parameter pure "function" "blocks_writer"+           "converter from Blocks to Doc values"+     <#> parameter pure "function" "inlines_writer"+           "converter from Inlines to Doc values"+     =#> functionResult pushContext "table" "template context"+     #? T.unlines+     [ "Creates template context from the document's [Meta]{#type-meta}"+     , "data, using the given functions to convert [Blocks] and [Inlines]"+     , "to [Doc] values."+     ]+  ]
+ src/Text/Pandoc/Lua/Module/Types.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE OverloadedStrings #-}+{- |+   Module      : Text.Pandoc.Lua.Module.Types+   Copyright   : © 2019-2023 Albert Krewinkel+   License     : GNU GPL, version 2 or above++   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>+   Stability   : alpha++Pandoc data type constructors.+-}+module Text.Pandoc.Lua.Module.Types+  ( documentedModule+  ) where++import HsLua ( Module (..), (###), (<#>), (=#>)+             , defun, functionResult, parameter)+import HsLua.Module.Version (peekVersionFuzzy, pushVersion)+import Text.Pandoc.Error (PandocError)+import Text.Pandoc.Lua.PandocLua ()++-- | Push the pandoc.types module on the Lua stack.+documentedModule :: Module PandocError+documentedModule = Module+  { moduleName = "pandoc.types"+  , moduleDescription =+      "Constructors for types that are not part of the pandoc AST."+  , moduleFields = []+  , moduleFunctions =+      [ defun "Version"+        ### return+        <#> parameter peekVersionFuzzy "string|integer|{integer,...}|Version"+              "version_specifier"+              (mconcat [ "either a version string like `'2.7.3'`, "+                       , "a single integer like `2`, "+                       , "list of integers like `{2,7,3}`, "+                       , "or a Version object"+                       ])+        =#> functionResult pushVersion "Version" "A new Version object."+      ]+  , moduleOperations = []+  }
+ src/Text/Pandoc/Lua/Module/Utils.hs view
@@ -0,0 +1,239 @@+{-# LANGUAGE LambdaCase          #-}+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications    #-}+{- |+   Module      : Text.Pandoc.Lua.Module.Utils+   Copyright   : Copyright © 2017-2023 Albert Krewinkel+   License     : GNU GPL, version 2 or above++   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>+   Stability   : alpha++Utility module for Lua, exposing internal helper functions.+-}+module Text.Pandoc.Lua.Module.Utils+  ( documentedModule+  , sha1+  ) where++import Control.Applicative ((<|>))+import Control.Monad ((<$!>))+import Data.Data (showConstr, toConstr)+import Data.Default (def)+import Data.Maybe (fromMaybe)+import Data.Version (Version)+import HsLua as Lua+import HsLua.Module.Version (peekVersionFuzzy, pushVersion)+import Text.Pandoc.Citeproc (getReferences, processCitations)+import Text.Pandoc.Definition+import Text.Pandoc.Error (PandocError)+import Text.Pandoc.Filter (applyJSONFilter)+import Text.Pandoc.Lua.Marshal.AST+import Text.Pandoc.Lua.Marshal.Reference+import Text.Pandoc.Lua.PandocLua (PandocLua (unPandocLua))++import qualified Data.Digest.Pure.SHA as SHA+import qualified Data.ByteString.Lazy as BSL+import qualified Data.Map as Map+import qualified Data.Text as T+import qualified Text.Pandoc.Builder as B+import qualified Text.Pandoc.Shared as Shared+import qualified Text.Pandoc.UTF8 as UTF8+import qualified Text.Pandoc.Writers.Shared as Shared++-- | Push the "pandoc.utils" module to the Lua stack.+documentedModule :: Module PandocError+documentedModule = Module+  { moduleName = "pandoc.utils"+  , moduleDescription = "pandoc utility functions"+  , moduleFields = []+  , moduleOperations = []+  , moduleFunctions =+    [ defun "blocks_to_inlines"+      ### (\blks mSep -> do+              let sep = maybe Shared.defaultBlocksSeparator B.fromList mSep+              return $ B.toList (Shared.blocksToInlinesWithSep sep blks))+      <#> parameter (peekList peekBlock) "list of blocks"+            "blocks" ""+      <#> opt (parameter (peekList peekInline) "Inlines" "sep" "")+      =#> functionResult pushInlines "list of inlines" ""++    , defun "citeproc"+      ### unPandocLua . processCitations+      <#> parameter peekPandoc "Pandoc" "doc" "document"+      =#> functionResult pushPandoc "Pandoc" "processed document"+      #?  T.unwords+          [ "Process the citations in the file, replacing them with "+          , "rendered citations and adding a bibliography. "+          , "See the manual section on citation rendering for details."+          ]++    , defun "equals"+      ### equal+      <#> parameter pure "AST element" "elem1" ""+      <#> parameter pure "AST element" "elem2" ""+      =#> functionResult pushBool "boolean" "true iff elem1 == elem2"++    , defun "make_sections"+      ### liftPure3 Shared.makeSections+      <#> parameter peekBool "boolean" "numbering" "add header numbers"+      <#> parameter (\i -> (Nothing <$ peekNil i) <|> (Just <$!> peekIntegral i))+                    "integer or nil" "baselevel" ""+      <#> parameter (peekList peekBlock) "list of blocks"+            "blocks" "document blocks to process"+      =#> functionResult pushBlocks "list of Blocks"+            "processes blocks"++    , defun "normalize_date"+      ### liftPure Shared.normalizeDate+      <#> parameter peekText "string" "date" "the date string"+      =#> functionResult (maybe pushnil pushText) "string or nil"+            "normalized date, or nil if normalization failed."+      #? T.unwords+      [ "Parse a date and convert (if possible) to \"YYYY-MM-DD\" format. We"+      , "limit years to the range 1601-9999 (ISO 8601 accepts greater than"+      , "or equal to 1583, but MS Word only accepts dates starting 1601)."+      , "Returns nil instead of a string if the conversion failed."+      ]++    , sha1++    , defun "Version"+      ### liftPure (id @Version)+      <#> parameter peekVersionFuzzy+            "version string, list of integers, or integer"+            "v" "version description"+      =#> functionResult pushVersion "Version" "new Version object"+      #? "Creates a Version object."++    , defun "references"+      ### (unPandocLua . getReferences Nothing)+      <#> parameter peekPandoc "Pandoc" "doc" "document"+      =#> functionResult (pushPandocList pushReference) "table"+            "lift of references"+      #? mconcat+         [ "Get references defined inline in the metadata and via an external "+         , "bibliography.  Only references that are actually cited in the "+         , "document (either with a genuine citation or with `nocite`) are "+         , "returned. URL variables are converted to links."+         ]++    , defun "run_json_filter"+      ### (\doc filterPath margs -> do+              args <- case margs of+                        Just xs -> return xs+                        Nothing -> do+                          Lua.getglobal "FORMAT"+                          (forcePeek ((:[]) <$!> peekString top) <* pop 1)+              applyJSONFilter def args filterPath doc+          )+      <#> parameter peekPandoc "Pandoc" "doc" "input document"+      <#> parameter peekString "filepath" "filter_path" "path to filter"+      <#> opt (parameter (peekList peekString) "list of strings"+               "args" "arguments to pass to the filter")+      =#> functionResult pushPandoc "Pandoc" "filtered document"++    , defun "stringify"+      ### stringify+      <#> parameter pure "AST element" "elem" "some pandoc AST element"+      =#> functionResult pushText "string" "stringified element"++    , defun "from_simple_table"+      ### from_simple_table+      <#> parameter peekSimpleTable "SimpleTable" "simple_tbl" ""+      =?> "Simple table"++    , defun "to_roman_numeral"+      ### liftPure Shared.toRomanNumeral+      <#> parameter (peekIntegral @Int) "integer" "n" "number smaller than 4000"+      =#> functionResult pushText "string" "roman numeral"+      #? "Converts a number < 4000 to uppercase roman numeral."++    , defun "to_simple_table"+      ### to_simple_table+      <#> parameter peekTable "Block" "tbl" "a table"+      =#> functionResult pushSimpleTable "SimpleTable" "SimpleTable object"+      #? "Converts a table into an old/simple table."++    , defun "type"+      ### (\idx -> getmetafield idx "__name" >>= \case+              TypeString -> fromMaybe mempty <$> tostring top+              _ -> ltype idx >>= typename)+      <#> parameter pure "any" "object" ""+      =#> functionResult pushByteString "string" "type of the given value"+    #? ("Pandoc-friendly version of Lua's default `type` function, " <>+        "returning the type of a value. If the argument has a " <>+        "string-valued metafield `__name`, then it gives that string. " <>+        "Otherwise it behaves just like the normal `type` function.")+    ]+  }++-- | Documented Lua function to compute the hash of a string.+sha1 :: DocumentedFunction e+sha1 = defun "sha1"+  ### liftPure (SHA.showDigest . SHA.sha1)+  <#> parameter (fmap BSL.fromStrict . peekByteString) "string" "input" ""+  =#> functionResult pushString "string" "hexadecimal hash value"+  #? "Compute the hash of the given string value."+++-- | Convert pandoc structure to a string with formatting removed.+-- Footnotes are skipped (since we don't want their contents in link+-- labels).+stringify :: LuaError e => StackIndex -> LuaE e T.Text+stringify idx = forcePeek . retrieving "stringifyable element" $+  choice+  [ (fmap Shared.stringify . peekPandoc)+  , (fmap Shared.stringify . peekInline)+  , (fmap Shared.stringify . peekBlock)+  , (fmap Shared.stringify . peekCitation)+  , (fmap stringifyMetaValue . peekMetaValue)+  , (fmap (const "") . peekAttr)+  , (fmap (const "") . peekListAttributes)+  ] idx+ where+  stringifyMetaValue :: MetaValue -> T.Text+  stringifyMetaValue mv = case mv of+    MetaBool b   -> T.toLower $ T.pack (show b)+    MetaString s -> s+    MetaList xs  -> mconcat $ map stringifyMetaValue xs+    MetaMap m    -> mconcat $ map (stringifyMetaValue . snd) (Map.toList m)+    _            -> Shared.stringify mv++-- | Converts an old/simple table into a normal table block element.+from_simple_table :: SimpleTable -> LuaE PandocError NumResults+from_simple_table (SimpleTable capt aligns widths head' body) = do+  pushBlock $ Table+    nullAttr+    (Caption Nothing [Plain capt | not (null capt)])+    (zipWith (\a w -> (a, toColWidth w)) aligns widths)+    (TableHead nullAttr [blockListToRow head' | not (null head') ])+    [TableBody nullAttr 0 [] $ map blockListToRow body | not (null body)]+    (TableFoot nullAttr [])+  return (NumResults 1)+  where+    blockListToRow :: [[Block]] -> Row+    blockListToRow = Row nullAttr . map (B.simpleCell . B.fromList)++    toColWidth :: Double -> ColWidth+    toColWidth 0 = ColWidthDefault+    toColWidth w = ColWidth w++-- | Converts a table into an old/simple table.+to_simple_table :: Block -> LuaE PandocError SimpleTable+to_simple_table = \case+  Table _attr caption specs thead tbodies tfoot -> do+    let (capt, aligns, widths, headers, rows) =+          Shared.toLegacyTable caption specs thead tbodies tfoot+    return $ SimpleTable capt aligns widths headers rows+  blk -> Lua.failLua $ mconcat+         [ "Expected Table, got ", showConstr (toConstr blk), "." ]++peekTable :: LuaError e => Peeker e Block+peekTable idx = peekBlock idx >>= \case+  t@(Table {}) -> return t+  b -> Lua.failPeek $ mconcat+       [ "Expected Table, got "+       , UTF8.fromString $ showConstr (toConstr b)+       , "." ]
+ src/Text/Pandoc/Lua/Orphans.hs view
@@ -0,0 +1,115 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE FlexibleInstances    #-}+{- |+   Module      : Text.Pandoc.Lua.Orphans+   Copyright   : © 2012-2023 John MacFarlane+                 © 2017-2023 Albert Krewinkel+   License     : GNU GPL, version 2 or above++   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>+   Stability   : alpha++Orphan instances for Lua's Pushable and Peekable type classes.+-}+module Text.Pandoc.Lua.Orphans () where++import Data.Version (Version)+import HsLua+import HsLua.Module.Version (peekVersionFuzzy)+import Text.Pandoc.Definition+import Text.Pandoc.Lua.Marshal.AST+import Text.Pandoc.Lua.Marshal.CommonState ()+import Text.Pandoc.Lua.Marshal.Context ()+import Text.Pandoc.Lua.Marshal.PandocError()+import Text.Pandoc.Lua.Marshal.ReaderOptions ()+import Text.Pandoc.Lua.Marshal.Sources (pushSources)+import Text.Pandoc.Sources (Sources)++instance Pushable Pandoc where+  push = pushPandoc++instance Pushable Meta where+  push = pushMeta++instance Pushable MetaValue where+  push = pushMetaValue++instance Pushable Block where+  push = pushBlock++instance {-# OVERLAPPING #-} Pushable [Block] where+  push = pushBlocks++instance Pushable Alignment where+  push = pushString . show++instance Pushable CitationMode where+  push = pushCitationMode++instance Pushable Format where+  push = pushFormat++instance Pushable ListNumberDelim where+  push = pushString . show++instance Pushable ListNumberStyle where+  push = pushString . show++instance Pushable MathType where+  push = pushMathType++instance Pushable QuoteType where+  push = pushQuoteType++instance Pushable Cell where+  push = pushCell++instance Pushable Inline where+  push = pushInline++instance {-# OVERLAPPING #-} Pushable [Inline] where+  push = pushInlines++instance Pushable Citation where+  push = pushCitation++instance Pushable Row where+  push = pushRow++instance Pushable TableBody where+  push = pushTableBody++instance Pushable TableFoot where+  push = pushTableFoot++instance Pushable TableHead where+  push = pushTableHead++-- These instances exist only for testing. It's a hack to avoid making+-- the marshalling modules public.+instance Peekable Inline where+  safepeek = peekInline++instance Peekable Block where+  safepeek = peekBlock++instance Peekable Cell where+  safepeek = peekCell++instance Peekable Meta where+  safepeek = peekMeta++instance Peekable Pandoc where+  safepeek = peekPandoc++instance Peekable Row where+  safepeek = peekRow++instance Peekable Version where+  safepeek = peekVersionFuzzy++instance {-# OVERLAPPING #-} Peekable Attr where+  safepeek = peekAttr++instance Pushable Sources where+  push = pushSources
+ src/Text/Pandoc/Lua/PandocLua.hs view
@@ -0,0 +1,100 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+{- |+   Module      : Text.Pandoc.Lua.PandocLua+   Copyright   : © 2020-2023 Albert Krewinkel+   License     : GPL-2.0-or-later+   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>++PandocMonad instance which allows execution of Lua operations and which+uses Lua to handle state.+-}+module Text.Pandoc.Lua.PandocLua+  ( PandocLua (..)+  , liftPandocLua+  ) where++import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow)+import Control.Monad.Except (MonadError (catchError, throwError))+import Control.Monad.IO.Class (MonadIO)+import HsLua as Lua+import Text.Pandoc.Class (PandocMonad (..))+import Text.Pandoc.Error (PandocError (..))+import Text.Pandoc.Lua.Marshal.CommonState (peekCommonState, pushCommonState)+import Text.Pandoc.Lua.Marshal.PandocError (peekPandocError, pushPandocError)++import qualified Control.Monad.Catch as Catch+import qualified Data.Text as T+import qualified Text.Pandoc.Class.IO as IO++-- | Type providing access to both, pandoc and Lua operations.+newtype PandocLua a = PandocLua { unPandocLua :: LuaE PandocError a }+  deriving+    ( Applicative+    , Functor+    , Monad+    , MonadCatch+    , MonadIO+    , MonadMask+    , MonadThrow+    )++-- | Lift a @'Lua'@ operation into the @'PandocLua'@ type.+liftPandocLua :: LuaE PandocError a -> PandocLua a+liftPandocLua = PandocLua++instance {-# OVERLAPPING #-} Exposable PandocError (PandocLua NumResults) where+  partialApply _narg = liftLua . unPandocLua++instance Pushable a => Exposable PandocError (PandocLua a) where+  partialApply _narg x = 1 <$ (liftLua (unPandocLua x >>= Lua.push))++instance MonadError PandocError PandocLua where+  catchError = Catch.catch+  throwError = Catch.throwM++instance PandocMonad PandocLua where+  lookupEnv = IO.lookupEnv+  getCurrentTime = IO.getCurrentTime+  getCurrentTimeZone = IO.getCurrentTimeZone+  newStdGen = IO.newStdGen+  newUniqueHash = IO.newUniqueHash++  openURL = IO.openURL++  readFileLazy = IO.readFileLazy+  readFileStrict = IO.readFileStrict+  readStdinStrict = IO.readStdinStrict++  glob = IO.glob+  fileExists = IO.fileExists+  getDataFileName = IO.getDataFileName+  getModificationTime = IO.getModificationTime++  getCommonState = PandocLua $ do+    Lua.getglobal "PANDOC_STATE"+    forcePeek $ peekCommonState Lua.top+  putCommonState cst = PandocLua $ do+    pushCommonState cst+    Lua.setglobal "PANDOC_STATE"++  logOutput = IO.logOutput++-- | Retrieve a @'PandocError'@ from the Lua stack.+popPandocError :: LuaE PandocError PandocError+popPandocError = do+  errResult <- runPeek $ peekPandocError top `lastly` pop 1+  case resultToEither errResult of+    Right x  -> return x+    Left err -> return $ PandocLuaError (T.pack err)++-- | Conversions between Lua errors and 'PandocError' exceptions.+instance LuaError PandocError where+  popException = popPandocError+  pushException = pushPandocError+  luaException = PandocLuaError . T.pack
+ src/Text/Pandoc/Lua/Writer/Classic.hs view
@@ -0,0 +1,255 @@+{-# LANGUAGE CPP                 #-}+{-# LANGUAGE FlexibleContexts    #-}+{-# LANGUAGE FlexibleInstances   #-}+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications    #-}+{- |+   Module      : Text.Pandoc.Lua.Writer.Classic+   Copyright   : Copyright (C) 2012-2023 John MacFarlane+   License     : GNU GPL, version 2 or above++   Maintainer  : John MacFarlane <jgm@berkeley.edu>+   Stability   : alpha+   Portability : portable++Conversion of Pandoc documents using a \"classic\" custom Lua writer.+-}+module Text.Pandoc.Lua.Writer.Classic+  ( runCustom+  ) where+import Control.Applicative (optional)+import Control.Arrow ((***))+import Data.List (intersperse)+import Data.Maybe (fromMaybe)+import qualified Data.Text as T+import Data.Text (Text, pack)+import HsLua as Lua hiding (Operation (Div))+#if !MIN_VERSION_hslua(2,2,0)+import HsLua.Aeson (peekViaJSON)+#endif+import Text.DocLayout (literal, render)+import Text.DocTemplates (Context)+import Text.Pandoc.Definition+import Text.Pandoc.Lua.Marshal.Attr (pushAttributeList)+import Text.Pandoc.Lua.Orphans ()+import Text.Pandoc.Options+import Text.Pandoc.Templates (renderTemplate)+import Text.Pandoc.Writers.Shared++-- | List of key-value pairs that is pushed to Lua as AttributeList+-- userdata.+newtype AttributeList = AttributeList [(Text, Text)]+instance Pushable AttributeList where+  push (AttributeList kvs) = pushAttributeList kvs++attrToMap :: Attr -> AttributeList+attrToMap (id',classes,keyvals) = AttributeList+    $ ("id", id')+    : ("class", T.unwords classes)+    : keyvals++newtype Stringify a = Stringify a++instance Pushable (Stringify Format) where+  push (Stringify (Format f)) = Lua.push (T.toLower f)++instance Pushable (Stringify [Inline]) where+  push (Stringify ils) = Lua.push =<< inlineListToCustom ils++instance Pushable (Stringify [Block]) where+  push (Stringify blks) = Lua.push =<< blockListToCustom blks++instance Pushable (Stringify MetaValue) where+  push (Stringify (MetaMap m))       = Lua.push (fmap Stringify m)+  push (Stringify (MetaList xs))     = Lua.push (map Stringify xs)+  push (Stringify (MetaBool x))      = Lua.push x+  push (Stringify (MetaString s))    = Lua.push s+  push (Stringify (MetaInlines ils)) = Lua.push (Stringify ils)+  push (Stringify (MetaBlocks bs))   = Lua.push (Stringify bs)++instance Pushable (Stringify Citation) where+  push (Stringify cit) = pushAsTable+    [ ("citationId", push . citationId)+    , ("citationPrefix",  push . Stringify . citationPrefix)+    , ("citationSuffix",  push . Stringify . citationSuffix)+    , ("citationMode",    push . citationMode)+    , ("citationNoteNum", push . citationNoteNum)+    , ("citationHash",    push . citationHash)+    ]+    cit++-- | Key-value pair, pushed as a table with @a@ as the only key and @v@ as the+-- associated value.+newtype KeyValue a b = KeyValue (a, b)++instance (Pushable a, Pushable b) => Pushable (KeyValue a b) where+  push (KeyValue (k, v)) = do+    Lua.newtable+    Lua.push k+    Lua.push v+    Lua.rawset (Lua.nth 3)++-- | Convert Pandoc to custom markup using a classic Lua writer.+runCustom :: LuaError e+          => WriterOptions+          -> Pandoc+          -> LuaE e Text+runCustom opts doc@(Pandoc meta _) = do+  (body, context) <- docToCustom opts doc+  -- convert metavalues to a template context (variables)+  metaContext <- metaToContext opts+                   (fmap (literal . pack) . blockListToCustom)+                   (fmap (literal . pack) . inlineListToCustom)+                   meta+  -- merge contexts from metadata and variables+  let renderContext = context <> metaContext+  return $ case writerTemplate opts of+    Nothing  -> body+    Just tpl -> render Nothing $+                renderTemplate tpl $ setField "body" body renderContext++-- | Converts a Pandoc value to custom markup using a classic Lua writer.+docToCustom :: forall e. LuaError e+            => WriterOptions -> Pandoc -> LuaE e (Text, Context Text)+docToCustom opts (Pandoc (Meta metamap) blocks) = do+  body <- blockListToCustom blocks+  -- invoke doesn't work with multiple return values, so we have to call+  -- `Doc` manually.+  Lua.getglobal "Doc"                 -- function+  push body                           -- argument 1+  push (fmap Stringify  metamap)      -- argument 2+  push (writerVariables opts)         -- argument 3+  call 3 2+  rendered  <- peek (nth 2)       -- first return value+  context <- forcePeek . optional $ peekViaJSON top  -- snd return value+  return (rendered, fromMaybe mempty context)+++-- | Convert Pandoc block element to Custom.+blockToCustom :: forall e. LuaError e+              => Block         -- ^ Block element+              -> LuaE e String++blockToCustom (Plain inlines) = invoke "Plain" (Stringify inlines)++blockToCustom (Para [Image attr txt (src,tit)]) =+  invoke "CaptionedImage" src tit (Stringify txt) (attrToMap attr)++blockToCustom (Para inlines) = invoke "Para" (Stringify inlines)++blockToCustom (LineBlock linesList) =+  invoke "LineBlock" (map (Stringify) linesList)++blockToCustom (RawBlock format str) =+  invoke "RawBlock" (Stringify format) str++blockToCustom HorizontalRule = invoke "HorizontalRule"++blockToCustom (Header level attr inlines) =+  invoke "Header" level (Stringify inlines) (attrToMap attr)++blockToCustom (CodeBlock attr str) =+  invoke "CodeBlock" str (attrToMap attr)++blockToCustom (BlockQuote blocks) =+  invoke "BlockQuote" (Stringify blocks)++blockToCustom (Figure attr (Caption _ cbody) content) =+  invoke "Figure"+    (Stringify cbody)+    (Stringify content)+    (attrToMap attr)++blockToCustom (Table _ blkCapt specs thead tbody tfoot) =+  let (capt, aligns, widths, headers, rows) = toLegacyTable blkCapt specs thead tbody tfoot+      aligns' = map show aligns+      capt' = Stringify capt+      headers' = map (Stringify) headers+      rows' = map (map (Stringify)) rows+  in invoke "Table" capt' aligns' widths headers' rows'++blockToCustom (BulletList items) =+  invoke "BulletList" (map (Stringify) items)++blockToCustom (OrderedList (num,sty,delim) items) =+  invoke "OrderedList" (map (Stringify) items) num (show sty) (show delim)++blockToCustom (DefinitionList items) =+  invoke "DefinitionList"+               (map (KeyValue . (Stringify *** map (Stringify))) items)++blockToCustom (Div attr items) =+  invoke "Div" (Stringify items) (attrToMap attr)++-- | Convert list of Pandoc block elements to Custom.+blockListToCustom :: forall e. LuaError e+                  => [Block]       -- ^ List of block elements+                  -> LuaE e String+blockListToCustom xs = do+  blocksep <- invoke "Blocksep"+  bs <- mapM blockToCustom xs+  return $ mconcat $ intersperse blocksep bs++-- | Convert list of Pandoc inline elements to Custom.+inlineListToCustom :: forall e. LuaError e => [Inline] -> LuaE e String+inlineListToCustom lst = do+  xs <- mapM (inlineToCustom @e) lst+  return $ mconcat xs++-- | Convert Pandoc inline element to Custom.+inlineToCustom :: forall e. LuaError e => Inline -> LuaE e String++inlineToCustom (Str str) = invoke "Str" str++inlineToCustom Space = invoke "Space"++inlineToCustom SoftBreak = invoke "SoftBreak"++inlineToCustom (Emph lst) = invoke "Emph" (Stringify lst)++inlineToCustom (Underline lst) = invoke "Underline" (Stringify lst)++inlineToCustom (Strong lst) = invoke "Strong" (Stringify lst)++inlineToCustom (Strikeout lst) = invoke "Strikeout" (Stringify lst)++inlineToCustom (Superscript lst) = invoke "Superscript" (Stringify lst)++inlineToCustom (Subscript lst) = invoke "Subscript" (Stringify lst)++inlineToCustom (SmallCaps lst) = invoke "SmallCaps" (Stringify lst)++inlineToCustom (Quoted SingleQuote lst) =+  invoke "SingleQuoted" (Stringify lst)++inlineToCustom (Quoted DoubleQuote lst) =+  invoke "DoubleQuoted" (Stringify lst)++inlineToCustom (Cite cs lst) =+  invoke "Cite" (Stringify lst) (map (Stringify) cs)++inlineToCustom (Code attr str) =+  invoke "Code" str (attrToMap attr)++inlineToCustom (Math DisplayMath str) =+  invoke "DisplayMath" str++inlineToCustom (Math InlineMath str) =+  invoke "InlineMath" str++inlineToCustom (RawInline format str) =+  invoke "RawInline" (Stringify format) str++inlineToCustom LineBreak = invoke "LineBreak"++inlineToCustom (Link attr txt (src,tit)) =+  invoke "Link" (Stringify txt) src tit (attrToMap attr)++inlineToCustom (Image attr alt (src,tit)) =+  invoke "Image" (Stringify alt) src tit (attrToMap attr)++inlineToCustom (Note contents) = invoke "Note" (Stringify contents)++inlineToCustom (Span attr items) =+  invoke "Span" (Stringify items) (attrToMap attr)
+ src/Text/Pandoc/Lua/Writer/Scaffolding.hs view
@@ -0,0 +1,311 @@+{-# LANGUAGE LambdaCase          #-}+{-# LANGUAGE OverloadedStrings   #-}+{- |+   Module      : Text.Pandoc.Lua.Writer.Scaffolding+   Copyright   : © 2022-2023 Albert Krewinkel+   License     : GPL-2.0-or-later+   Maintainer  : Albert Krewinkel <pandoc@tarleb.com>++Conversion of Pandoc documents using a custom Lua writer.+-}+module Text.Pandoc.Lua.Writer.Scaffolding+  ( pushWriterScaffolding+  ) where++import Control.Monad ((<$!>), void)+import Data.ByteString (ByteString)+import Data.Data (dataTypeConstrs, dataTypeOf, showConstr, toConstr)+import Data.Default (def)+import Data.List (intersperse)+import Data.Maybe (fromMaybe)+import Data.Text (Text)+import Data.String (IsString (fromString))+import HsLua+import HsLua.Module.DocLayout (peekDoc, pushDoc)+import Text.DocLayout (Doc, blankline, render)+import Text.DocTemplates (Context)+import Text.Pandoc.Definition+import Text.Pandoc.Error (PandocError (..))+import Text.Pandoc.Options (WriterOptions (..), WrapOption(..))+import Text.Pandoc.Lua.PandocLua ()+import Text.Pandoc.Lua.Marshal.AST+import Text.Pandoc.Lua.Marshal.Context (peekContext)+import Text.Pandoc.Lua.Marshal.WriterOptions ( peekWriterOptions+                                             , pushWriterOptions)+import Text.Pandoc.Templates (renderTemplate)+import Text.Pandoc.Writers.Shared (metaToContext, setField)+import qualified Data.Text as T+import qualified Text.Pandoc.UTF8 as UTF8++-- | Convert Pandoc to custom markup.+pushWriterScaffolding :: LuaE PandocError NumResults+pushWriterScaffolding = do+  newtable+    *> pushWriterMT *> setmetatable (nth 2)+  writer <- toWriterTable top+  addField "Blocks"  $ pushDocumentedFunction (blocksFn writer)+  addField "Inlines" $ pushDocumentedFunction (inlinesFn writer)+  addField "Block"   $ newtable *> pushBlockMT  writer *> setmetatable (nth 2)+  addField "Inline"  $ newtable *> pushInlineMT writer *> setmetatable (nth 2)+  addField "Pandoc"  $ pushDocumentedFunction $ lambda+    ### (\(Pandoc _ blks) -> do+            pushWriterTable writer+            getfield' top "Blocks"+            pushBlocks blks+            callTrace 1 1+            pure (NumResults 1))+    <#> parameter peekPandoc "Pandoc" "doc" ""+    =?> "rendered doc"+  freeWriter writer+  return 1+ where+  blocksFn w = lambda+    ### (\blocks msep -> blockListToCustom w msep blocks)+    <#> parameter peekBlocks "Blocks" "blocks" ""+    <#> opt (parameter peekDocFuzzy "Doc" "sep" "")+    =#> functionResult pushDoc "Doc" ""+  inlinesFn w = lambda+    ### inlineListToCustom w+    <#> parameter peekInlines "Inlines" "inlines" ""+    =#> functionResult pushDoc "Doc" ""+  pushBlockMT writer = do+    newtable+    addField "__call" $ pushDocumentedFunction $ lambda+      ### blockToCustom+      <#> parameter peekWriter "table" "writer" ""+      <#> parameter peekBlockFuzzy "Block" "block" ""+      =#> functionResult pushDoc "Doc" "rendered blocks"+    addField "__index" $+      -- lookup missing fields in the main Writer table+      pushWriterTable writer+  pushInlineMT writer = do+    newtable+    addField "__call" $ pushDocumentedFunction $ lambda+      ### inlineToCustom+      <#> parameter peekWriter "table" "writer" ""+      <#> parameter peekInlineFuzzy "Inline" "inline" ""+      =#> functionResult pushDoc "Doc" "rendered inline"+    addField "__index" $ do+      -- lookup missing fields in the main Writer table+      pushWriterTable writer++pushWriterMT :: LuaE PandocError ()+pushWriterMT = do+  newtable+  addField "__call" $ pushDocumentedFunction $ lambda+    ### (\writer doc mopts -> runWriter writer doc mopts)+    <#> parameter peekWriter "table" "writer" ""+    <#> parameter peekPandoc "Pandoc" "doc" ""+    <#> opt (parameter peekWriterOptions "WriterOptions" "opts" "")+    =#> functionResult pushText "string" "rendered document"+  addField "__index" . pushDocumentedFunction $ lambda+    ### (\_writer key -> handleMissingField key)+    <#> parameter pure "table"  "writer" ""+    <#> parameter (liftLua . tostring') "string" "key" ""+    =#> functionResult (const pushnil) "string" ""+++addField :: LuaError e => Name -> LuaE e a -> LuaE e ()+addField name action = do+  pushName name+  action+  rawset (nth 3)++getfield' :: LuaError e => StackIndex -> Name -> LuaE e HsLua.Type+getfield' idx name = do+  aidx <- absindex idx+  pushName name+  rawget aidx >>= \case+    TypeNil -> pop 1 *> getfield aidx name+    ty      -> pure ty++-- | A writer table is just an absolute stack index.+newtype WriterTable = WriterTable Reference++toWriterTable :: LuaError e => StackIndex -> LuaE e WriterTable+toWriterTable idx = WriterTable <$!> do+  pushvalue idx+  ref registryindex++peekWriter :: LuaError e => Peeker e WriterTable+peekWriter = liftLua . toWriterTable++pushWriterTable :: LuaError e => Pusher e WriterTable+pushWriterTable (WriterTable wref) = void $ getref registryindex wref++writerOptionsField :: Name+writerOptionsField = "Pandoc Writer WriterOptions"++freeWriter :: WriterTable -> LuaE e ()+freeWriter (WriterTable wref) = unref registryindex wref++pushOpts :: LuaE PandocError ()+pushOpts = void $ getfield' registryindex writerOptionsField++runWriter :: WriterTable -> Pandoc -> Maybe WriterOptions+          -> LuaE PandocError Text+runWriter writer doc@(Pandoc meta _blks) mopts = do+  let opts = fromMaybe def mopts+  pushWriterOptions opts *>+    setfield registryindex writerOptionsField++  (body, mcontext) <- runPeek (pandocToCustom writer doc) >>= force . \case+    Failure msg contexts -> Failure (cleanupTrace msg) contexts+    s -> s++  -- convert metavalues to a template context (variables)+  defaultContext <- metaToContext opts+                    (blockListToCustom writer Nothing)+                    (inlineListToCustom writer)+                    meta+  let context = setField "body" body+              $ fromMaybe defaultContext mcontext++  let colwidth = if writerWrapText opts == WrapAuto+                    then Just $ writerColumns opts+                    else Nothing++  return $ render colwidth $+    case writerTemplate opts of+       Nothing  -> body+       Just tpl -> renderTemplate tpl context++-- | Keep exactly one traceback and clean it up. This wouldn't be+-- necessary if the @pcallTrace@ function would do nothing whenever the+-- error already included a trace, but that would require some bigger+-- changes; removing the additional traces in this post-process step is+-- much easier (for now).+cleanupTrace :: ByteString -> ByteString+cleanupTrace msg = UTF8.fromText . T.intercalate "\n" $+  let tmsg = T.lines $ UTF8.toText msg+      traceStart = (== "stack traceback:")+  in case break traceStart tmsg of+        (x, t:traces) -> (x <>) . (t:) $+                         let (firstTrace, rest) = break traceStart traces+                             isPeekContext = ("\twhile " `T.isPrefixOf`)+                             isUnknownCFn = (== "\t[C]: in ?")+                         in filter (not . isUnknownCFn) firstTrace <>+                            filter isPeekContext rest+        _ -> tmsg++-- | Pushes the field in the writer table.+getWriterField :: LuaError e+               => WriterTable -> Name -> LuaE e HsLua.Type+getWriterField writer name = do+  pushWriterTable writer+  getfield' top name <* remove (nth 2)++-- | Looks up @Writer.subtable.field@; tries @Writer.field@ as a fallback if the+-- subtable field is @nil@.+getNestedWriterField :: LuaError e+                     => WriterTable -> Name -> Name -> LuaE e HsLua.Type+getNestedWriterField writer subtable field = do+  pushWriterTable writer+  getfield' top subtable >>= \case+    TypeNil -> TypeNil <$ remove (nth 2) -- remove Writer table+    _       -> getfield' top field+               -- remove Writer and subtable+               <* remove (nth 3) <* remove (nth 2)++pandocToCustom :: WriterTable -> Pandoc+               -> Peek PandocError (Doc Text, Maybe (Context Text))+pandocToCustom writer doc = withContext "rendering Pandoc" $ do+  callStatus <- liftLua $ do+    getWriterField writer "Pandoc"+    pushPandoc doc+    pushOpts+    pcallTrace 2 2+  case callStatus of+    OK -> ((,) <$> peekDocFuzzy (nth 2) <*> orNil peekContext top)+          `lastly` pop 2+    _  -> failPeek =<< liftLua (tostring' top)++blockToCustom :: WriterTable -> Block -> LuaE PandocError (Doc Text)+blockToCustom writer blk = forcePeek $ renderBlock writer blk++renderBlock :: WriterTable -> Block -> Peek PandocError (Doc Text)+renderBlock writer blk = do+  let constrName = fromString . showConstr . toConstr $ blk+  withContext ("rendering Block `" <> constrName <> "`") $+    liftLua (getNestedWriterField writer "Block" constrName) >>= \case+      TypeNil -> failPeek =<< typeMismatchMessage "function or Doc" top+      _       -> callOrDoc (pushBlock blk)++inlineToCustom :: WriterTable -> Inline -> LuaE PandocError (Doc Text)+inlineToCustom writer inln = forcePeek $ renderInline writer inln++renderInline :: WriterTable -> Inline -> Peek PandocError (Doc Text)+renderInline writer inln = do+  let constrName = fromString . showConstr . toConstr $ inln+  withContext ("rendering Inline `" <> constrName <> "`") $ do+    liftLua (getNestedWriterField writer "Inline" constrName) >>= \case+      TypeNil -> failPeek =<< typeMismatchMessage "function or Doc" top+      _       -> callOrDoc (pushInline inln)++-- | If the value at the top of the stack can be called as a function,+-- then push the element and writer options to the stack and call it;+-- otherwise treat it as a plain Doc value+callOrDoc :: LuaE PandocError ()+          -> Peek PandocError (Doc Text)+callOrDoc pushElement = do+  liftLua (ltype top) >>= \case+    TypeFunction -> peekCall+    _            ->+      liftLua (getmetafield top "__call") >>= \case+        TypeNil -> peekDocFuzzy top+        _       -> liftLua (pop 1) *> peekCall+ where+   peekCall :: Peek PandocError (Doc Text)+   peekCall =+     liftLua (pushElement *> pushOpts *> pcallTrace 2 1) >>= \case+       OK -> peekDocFuzzy top+       _  -> failPeek =<< liftLua (tostring' top)++blockListToCustom :: WriterTable -> Maybe (Doc Text) -> [Block]+                  -> LuaE PandocError (Doc Text)+blockListToCustom writer msep blocks = forcePeek $+  renderBlockList writer msep blocks++inlineListToCustom :: WriterTable -> [Inline] -> LuaE PandocError (Doc Text)+inlineListToCustom writer inlines = forcePeek $+  renderInlineList writer inlines++renderBlockList :: WriterTable -> Maybe (Doc Text) -> [Block]+                -> Peek PandocError (Doc Text)+renderBlockList writer msep blocks = withContext "rendering Blocks" $ do+  let addSeps = intersperse $ fromMaybe blankline msep+  mconcat . addSeps <$> mapM (renderBlock writer) blocks++renderInlineList :: WriterTable -> [Inline] -> Peek PandocError (Doc Text)+renderInlineList writer inlines = withContext "rendering Inlines" $ do+  mconcat <$> mapM (renderInline writer) inlines++orNil :: Peeker e a -> Peeker e (Maybe a)+orNil p idx = liftLua (ltype idx) >>= \case+  TypeNil  -> pure Nothing+  TypeNone -> pure Nothing+  _        -> Just <$> p idx++peekDocFuzzy :: LuaError e => Peeker e (Doc Text)+peekDocFuzzy idx = liftLua (ltype idx) >>= \case+  TypeTable -> mconcat <$!> peekList peekDoc idx+  _         -> peekDoc idx++handleMissingField :: LuaError e => ByteString -> LuaE e ()+handleMissingField key' =+  let key = UTF8.toString key'+      blockNames  = map (fromString . show) . dataTypeConstrs . dataTypeOf+                      $ HorizontalRule+      inlineNames = map (fromString . show) . dataTypeConstrs . dataTypeOf+                      $ Space+      mtypeName = case () of+       _ | key `elem` blockNames  -> Just "Block"+       _ | key `elem` inlineNames -> Just "Inline"+       _                          -> Nothing+  in case mtypeName of+       Just typeName  -> failLua $+                         "No render function for " <> typeName <> " value " <>+                         "'" <> key <> "';\ndefine a function `Writer." <>+                         typeName <> "." <> key <> "` that returns " <>+                         "a string or Doc."+       _ -> pure ()
+ test/Tests/Lua.hs view
@@ -0,0 +1,246 @@+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications    #-}+{- |+   Module      : Tests.Lua+   Copyright   : © 2017-2023 Albert Krewinkel+   License     : GNU GPL, version 2 or above++   Maintainer  : Albert Krewinkel <albert@zeitkraut.de>+   Stability   : alpha+   Portability : portable++Unit and integration tests for pandoc's Lua subsystem.+-}+module Tests.Lua ( runLuaTest, tests ) where++import HsLua as Lua hiding (Operation (Div), error)+import System.FilePath ((</>))+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.HUnit ((@=?), Assertion, HasCallStack, assertEqual, testCase)+import Text.Pandoc.Arbitrary ()+import Text.Pandoc.Builder (bulletList, definitionList, displayMath, divWith,+                            doc, doubleQuoted, emph, header, lineBlock,+                            linebreak, math, orderedList, para, plain, rawBlock,+                            singleQuoted, space, str, strong,+                            HasMeta (setMeta))+import Text.Pandoc.Class (runIOorExplode, setUserDataDir)+import Text.Pandoc.Definition (Attr, Block (BlockQuote, Div, Para), Pandoc,+                               Inline (Emph, Str), pandocTypesVersion)+import Text.Pandoc.Error (PandocError (PandocLuaError))+import Text.Pandoc.Lua (Global (..), applyFilter, runLua, setGlobals)+import Text.Pandoc.Options (def)+import Text.Pandoc.Version (pandocVersionText)++import qualified Control.Monad.Catch as Catch+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE++tests :: [TestTree]+tests =+  [ testCase "macro expansion via filter" $+    assertFilterConversion "a '{{helloworld}}' string is expanded"+      "strmacro.lua"+      (doc . para $ str "{{helloworld}}")+      (doc . para . emph $ str "Hello, World")++  , testCase "convert all plains to paras" $+    assertFilterConversion "plains become para"+      "plain-to-para.lua"+      (doc $ bulletList [plain (str "alfa"), plain (str "bravo")])+      (doc $ bulletList [para (str "alfa"), para (str "bravo")])++  , testCase "convert display math to inline math" $+    assertFilterConversion "display math becomes inline math"+      "math.lua"+      (doc $ para (displayMath "5+5"))+      (doc $ para (math "5+5"))++  , testCase "make hello world document" $+    assertFilterConversion "Document contains 'Hello, World!'"+      "hello-world-doc.lua"+      (doc . para $ str "Hey!" <> linebreak <> str "What's up?")+      (doc . para $ str "Hello," <> space <> str "World!")++  , testCase "implicit doc filter" $+    assertFilterConversion "Document contains 'Hello, World!'"+      "implicit-doc-filter.lua"+      (doc . plain $ linebreak)+      (doc . para $ str "Hello," <> space <> str "World!")++  , testCase "parse raw markdown blocks" $+    assertFilterConversion "raw markdown block is converted"+      "markdown-reader.lua"+      (doc $ rawBlock "markdown" "*charly* **delta**")+      (doc . para $ emph "charly" <> space <> strong "delta")++  , testCase "allow shorthand functions for quote types" $+    assertFilterConversion "single quoted becomes double quoted string"+      "single-to-double-quoted.lua"+      (doc . para . singleQuoted $ str "simple")+      (doc . para . doubleQuoted $ str "simple")++  , testCase "Count inlines via metatable catch-all" $+    assertFilterConversion "filtering with metatable catch-all failed"+      "metatable-catch-all.lua"+      (doc . para $ "four words, three spaces")+      (doc . para $ str "7")++  , testCase "Count blocks via Block-specific catch-all" $+    assertFilterConversion "filtering with Block catch-all failed"+      "block-count.lua"+      (doc $ para "one" <> para "two")+      (doc $ para "2")++  , testCase "Smart constructors" $+    assertFilterConversion "smart constructors returned a wrong result"+      "smart-constructors.lua"+      (doc $ para "")+      (doc $ mconcat+       [ bulletList [para "Hello", para "World"]+       , definitionList [("foo", [para "placeholder"])]+       , lineBlock ["Moin", "Welt"]+       , orderedList [plain "one", plain "two"]+       ])++  , testCase "Convert header upper case" $+    assertFilterConversion "converting header to upper case failed"+      "uppercase-header.lua"+      (doc $ header 1 "les états-unis" <> para "text")+      (doc $ header 1 "LES ÉTATS-UNIS" <> para "text")++  , testCase "Attribute lists are convenient to use" $+    let kv_before = [("one", "1"), ("two", "2"), ("three", "3")]+        kv_after  = [("one", "eins"), ("three", "3"), ("five", "5")]+    in assertFilterConversion "Attr doesn't behave as expected"+      "attr-test.lua"+      (doc $ divWith ("", [], kv_before) (para "nil"))+      (doc $ divWith ("", [], kv_after) (para "nil"))++  , testCase "Filter list of inlines" $+      assertFilterConversion "List of inlines"+      "inlines-filter.lua"+      (doc $ para ("Hello," <> linebreak <> "World! Wassup?"))+      (doc $ para "Hello, World! Wassup?")++  , testCase "Filter list of blocks" $+      assertFilterConversion "List of blocks"+      "blocks-filter.lua"+      (doc $ para "one." <> para "two." <> para "three.")+      (doc $ plain "3")++  , testCase "Filter Meta" $+    let setMetaBefore = setMeta "old" ("old" :: T.Text)+                      . setMeta "bool" False+        setMetaAfter  = setMeta "new" ("new" :: T.Text)+                      . setMeta "bool" True+    in assertFilterConversion "Meta filtering"+      "meta.lua"+      (setMetaBefore . doc $ mempty)+      (setMetaAfter . doc $ mempty)++  , testCase "Script filename is set" $+    assertFilterConversion "unexpected script name"+      "script-name.lua"+      (doc $ para "ignored")+      (doc $ para (str $ T.pack $ "lua" </> "script-name.lua"))++  , testCase "Pandoc version is set" . runLuaTest $ do+      Lua.getglobal "PANDOC_VERSION"+      Lua.liftIO .+        assertEqual "pandoc version is wrong" (TE.encodeUtf8 pandocVersionText)+        =<< Lua.tostring' Lua.top++  , testCase "Pandoc types version is set" . runLuaTest $ do+      Lua.getglobal "PANDOC_API_VERSION"+      Lua.liftIO . assertEqual "pandoc-types version is wrong" pandocTypesVersion+        =<< Lua.peek Lua.top++  , testCase "require file" $+    assertFilterConversion "requiring file failed"+      "require-file.lua"+      (doc $ para "ignored")+      (doc $ para (str . T.pack $ "lua" </> "require-file.lua"))++  , testCase "Allow singleton inline in constructors" . runLuaTest $ do+      Lua.liftIO . assertEqual "Not the expected Emph"+        (Emph [Str "test"]) =<< do+        Lua.OK <- Lua.dostring "return pandoc.Emph"+        Lua.push @Inline (Str "test")+        Lua.call 1 1+        Lua.peek @Inline top+      Lua.liftIO . assertEqual "Unexpected element"+        (Para [Str "test"]) =<< do+        Lua.getglobal' "pandoc.Para"+        Lua.pushString "test"+        Lua.call 1 1+        Lua.peek @Block top+      Lua.liftIO . assertEqual "Unexptected element"+        (BlockQuote [Para [Str "foo"]]) =<< (+        do+          Lua.getglobal' "pandoc.BlockQuote"+          Lua.push (Para [Str "foo"])+          _ <- Lua.call 1 1+          Lua.peek @Block Lua.top+        )++  , testCase "Elements with Attr have `attr` accessor" . runLuaTest $ do+      Lua.push (Div ("hi", ["moin"], [])+                [Para [Str "ignored"]])+      Lua.getfield Lua.top "attr"+      Lua.liftIO . assertEqual "no accessor" (("hi", ["moin"], []) :: Attr)+        =<< Lua.peek @Attr Lua.top++  , testCase "module `pandoc.system` is present" . runLuaTest $ do+      Lua.getglobal' "pandoc.system"+      ty <- Lua.ltype Lua.top+      Lua.liftIO $ assertEqual "module should be a table" Lua.TypeTable ty++  , testGroup "global modules"+    [ testCase "module 'lpeg' is loaded into a global" . runLuaTest $ do+        s <- Lua.dostring "assert(type(lpeg)=='table')"+        Lua.liftIO $ Lua.OK @=? s++    , testCase "module 're' is loaded into a global" . runLuaTest $ do+        s <- Lua.dostring "assert(type(re)=='table')"+        Lua.liftIO $ Lua.OK @=? s++    , testCase "module 'lpeg' is available via `require`" . runLuaTest $ do+        s <- Lua.dostring+              "package.path = ''; package.cpath = ''; require 'lpeg'"+        Lua.liftIO $ Lua.OK @=? s++    , testCase "module 're' is available via `require`" . runLuaTest $ do+        s <- Lua.dostring+               "package.path = ''; package.cpath = ''; require 're'"+        Lua.liftIO $ Lua.OK @=? s+    ]++  , testCase "informative error messages" . runLuaTest $ do+      Lua.pushboolean True+      -- Lua.newtable+      eitherPandoc <- Catch.try (peek @Pandoc Lua.top)+      case eitherPandoc of+        Left (PandocLuaError msg) -> do+          let expectedMsg = "Pandoc expected, got boolean\n"+                <> "\twhile retrieving Pandoc"+          Lua.liftIO $ assertEqual "unexpected error message" expectedMsg msg+        Left e -> error ("Expected a Lua error, but got " <> show e)+        Right _ -> error "Getting a Pandoc element from a bool should fail."+  ]++assertFilterConversion :: String -> FilePath -> Pandoc -> Pandoc -> Assertion+assertFilterConversion msg filterPath docIn expectedDoc = do+  actualDoc <- runIOorExplode $ do+    setUserDataDir (Just "../data")+    applyFilter def ["HTML"] ("lua" </> filterPath) docIn+  assertEqual msg expectedDoc actualDoc++runLuaTest :: HasCallStack => Lua.LuaE PandocError a -> IO a+runLuaTest op = runIOorExplode $ do+  res <- runLua $ do+    setGlobals [ PANDOC_WRITER_OPTIONS def ]+    op+  case res of+    Left e -> error (show e)+    Right x -> return x
+ test/Tests/Lua/Module.hs view
@@ -0,0 +1,45 @@+{- |+Module      : Tests.Lua.Module+Copyright   : © 2019-2023 Albert Krewinkel+License     : GNU GPL, version 2 or above++Maintainer  : Albert Krewinkel <albert@zeitkraut.de>+Stability   : alpha+Portability : portable++Lua module tests+-}+module Tests.Lua.Module (tests) where++import System.FilePath ((</>))+import Test.Tasty (TestName, TestTree)+import Test.Tasty.Lua (testLuaFile)++import Tests.Lua (runLuaTest)++tests :: [TestTree]+tests =+  [ testPandocLua "pandoc"+                  ("lua" </> "module" </> "pandoc.lua")+  , testPandocLua "pandoc.List"+                  ("lua" </> "module" </> "pandoc-list.lua")+  , testPandocLua "pandoc.format"+                  ("lua" </> "module" </> "pandoc-format.lua")+  , testPandocLua "pandoc.mediabag"+                  ("lua" </> "module" </> "pandoc-mediabag.lua")+  , testPandocLua "pandoc.path"+                  ("lua" </> "module" </> "pandoc-path.lua")+  , testPandocLua "pandoc.structure"+                  ("lua" </> "module" </> "pandoc-structure.lua")+  , testPandocLua "pandoc.template"+                  ("lua" </> "module" </> "pandoc-template.lua")+  , testPandocLua "pandoc.types"+                  ("lua" </> "module" </> "pandoc-types.lua")+  , testPandocLua "pandoc.utils"+                  ("lua" </> "module" </> "pandoc-utils.lua")+  , testPandocLua "globals"+                  ("lua" </> "module" </> "globals.lua")+  ]++testPandocLua :: TestName -> FilePath -> TestTree+testPandocLua = testLuaFile runLuaTest
+ test/Tests/Lua/Reader.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE LambdaCase        #-}+{- |+Module      : Tests.Lua.Reader+Copyright   : © 2022-2023 Albert Krewinkel+License     : GPL-2.0-or-later+Maintainer  : Albert Krewinkel <pandoc@tarleb.com>++Tests for custom Lua readers.+-}+module Tests.Lua.Reader (tests) where++import Control.Arrow ((>>>))+import Data.Char (chr)+import Data.Default (Default (def))+import Text.Pandoc.Class (runIOorExplode)+import Text.Pandoc.Lua (loadCustom)+import Text.Pandoc.Readers (Reader (ByteStringReader))+import Text.Pandoc.Scripting (customReader)+import Test.Tasty (TestTree)+import Test.Tasty.HUnit ((@?=), testCase)++import qualified Data.ByteString.Lazy as BL+import qualified Data.Text as T+import qualified Text.Pandoc.Builder as B++tests :: [TestTree]+tests =+  [ testCase "read binary to code block" $ do+    input <- BL.readFile "bytestring.bin"+    doc <- runIOorExplode $+      loadCustom "bytestring-reader.lua" >>= (customReader >>> \case+        Just (ByteStringReader f) -> f def input+        _                         -> error "Expected a bytestring reader")+    let bytes = mconcat $ map (B.str . T.singleton . chr) [0..255]+    doc @?= B.doc (B.plain bytes)+  ]
+ test/Tests/Lua/Writer.hs view
@@ -0,0 +1,94 @@+{-# LANGUAGE LambdaCase        #-}+{-# LANGUAGE OverloadedStrings #-}+{- |+Module      : Tests.Lua.Writer+Copyright   : © 2019-2023 Albert Krewinkel+License     : GNU GPL, version 2 or above+Maintainer  : Albert Krewinkel <albert@zeitkraut.de>++Tests for custom Lua writers.+-}+module Tests.Lua.Writer (tests) where++import Data.Default (Default (def))+import Data.Maybe (fromMaybe)+import Text.Pandoc.Class (runIOorExplode, readFileStrict)+import Text.Pandoc.Extensions (Extension (..), extensionsFromList)+import Text.Pandoc.Format (ExtensionsDiff (..), FlavoredFormat (..),+                           applyExtensionsDiff)+import Text.Pandoc.Lua (loadCustom)+import Text.Pandoc.Options (WriterOptions (..))+import Text.Pandoc.Readers (readNative)+import Text.Pandoc.Scripting (CustomComponents (..))+import Text.Pandoc.Writers (Writer (ByteStringWriter, TextWriter))+import Test.Tasty (TestTree)+import Test.Tasty.Golden (goldenVsString)+import Test.Tasty.HUnit (testCase, (@?=))++import qualified Data.ByteString.Lazy as BL+import qualified Text.Pandoc.Builder as B+import qualified Text.Pandoc.UTF8 as UTF8++tests :: [TestTree]+tests =+  [ goldenVsString "default testsuite"+    "writer.custom"+    (runIOorExplode $ do+        source <- UTF8.toText <$> readFileStrict "testsuite.native"+        doc <- readNative def source+        txt <- customWriter <$> loadCustom "sample.lua" >>= \case+          Just (TextWriter f) -> f def doc+          _                   -> error "Expected a text writer"+        pure $ BL.fromStrict (UTF8.fromText txt))++  , goldenVsString "tables testsuite"+    "tables.custom"+    (runIOorExplode $ do+        source <- UTF8.toText <$> readFileStrict "tables.native"+        doc <- readNative def source+        txt <- writeCustom "sample.lua" >>= \case+          (TextWriter f, _, _) -> f def doc+          _            -> error "Expected a text writer"+        pure $ BL.fromStrict (UTF8.fromText txt))++  , goldenVsString "bytestring writer"+    "bytestring.bin"+    (runIOorExplode $+        writeCustom "bytestring.lua" >>= \case+          (ByteStringWriter f, _, _) -> f def mempty+          _                       -> error "Expected a bytestring writer")++  , goldenVsString "template"+    "writer-template.out.txt"+    (runIOorExplode $ do+        (_, _, template) <- writeCustom "writer-template.lua"+        pure . BL.fromStrict . UTF8.fromText $ fromMaybe "" template)++  , testCase "preset extensions" $ do+      let format = FlavoredFormat "extensions.lua" mempty+      result <- runIOorExplode $ writeCustom "extensions.lua" >>= \case+          (TextWriter write, extsConf, _) -> do+            exts <- applyExtensionsDiff extsConf format+            write def{writerExtensions = exts} (B.doc mempty)+          _                        -> error "Expected a text writer"+      result @?= "smart extension is enabled;\ncitations extension is disabled\n"+  , testCase "modified extensions" $ do+      let ediff = ExtensionsDiff+            { extsToEnable = extensionsFromList [Ext_citations]+            , extsToDisable = mempty+            }+      let format = FlavoredFormat "extensions.lua" ediff+      result <- runIOorExplode $ writeCustom "extensions.lua" >>= \case+          (TextWriter write, extsConf, _) -> do+            exts <- applyExtensionsDiff extsConf format+            write def{writerExtensions = exts} (B.doc mempty)+          _                        -> error "Expected a text writer"+      result @?= "smart extension is enabled;\ncitations extension is enabled\n"+  ]+ where+  writeCustom fp = do+    components <- loadCustom fp+    let exts = fromMaybe mempty (customExtensions components)+    case customWriter components of+      Nothing -> error "Expected a writer to be defined"+      Just w  -> return (w, exts, customTemplate components)
+ test/bytestring-reader.lua view
@@ -0,0 +1,7 @@+function ByteStringReader (input, opts)+  local chars = pandoc.List{}+  for i = 1, #input do+    chars:insert(utf8.char(input:byte(i,i)))+  end+  return pandoc.Pandoc(pandoc.Plain(pandoc.Str(table.concat(chars))))+end
+ test/bytestring.bin view

binary file changed (absent → 256 bytes)

+ test/bytestring.lua view
@@ -0,0 +1,7 @@+function ByteStringWriter (doc, opts)+  local buffer = {}+  for i=0, 255 do+    table.insert(buffer, string.char(i))+  end+  return table.concat(buffer, '')+end
+ test/extensions.lua view
@@ -0,0 +1,12 @@+function Writer (doc, opts)+  local output = 'smart extension is %s;\ncitations extension is %s\n'+  local status = function (ext)+    return opts.extensions:includes(ext) and 'enabled' or 'disabled'+  end+  return output:format(status('smart'), status('citations'))+end++Extensions = {+  smart = true,+  citations = false,+}
+ test/lua/attr-test.lua view
@@ -0,0 +1,6 @@+function Div (div)+  div.attributes.five = ("%d"):format(div.attributes.two + div.attributes.three)+  div.attributes.two = nil+  div.attributes.one = "eins"+  return div+end
+ test/lua/block-count.lua view
@@ -0,0 +1,11 @@+local num_blocks = 0++function Block(el)+  num_blocks = num_blocks + 1+end++function Pandoc(blocks, meta)+  return pandoc.Pandoc {+    pandoc.Para{pandoc.Str(num_blocks)}+  }+end
+ test/lua/blocks-filter.lua view
@@ -0,0 +1,8 @@+function Blocks (blks)+  -- verify that this looks like a `pandoc.List`+  if not blks.find or not blks.map or not blks.filter then+    error("table doesn't seem to be an instance of pandoc.List")+  end+  -- return plain block containing the number of elements in the list+  return {pandoc.Plain {pandoc.Str(tostring(#blks))}}+end
+ test/lua/hello-world-doc.lua view
@@ -0,0 +1,10 @@+return {+  {+    Pandoc = function(doc)+      local meta = {}+      local hello = { pandoc.Str "Hello,", pandoc.Space(), pandoc.Str "World!" }+      local blocks = { pandoc.Para(hello) }+      return pandoc.Pandoc(blocks, meta)+    end+  }+}
+ test/lua/implicit-doc-filter.lua view
@@ -0,0 +1,6 @@+function Pandoc (doc)+  local meta = {}+  local hello = { pandoc.Str "Hello,", pandoc.Space(), pandoc.Str "World!" }+  local blocks = { pandoc.Para(hello) }+  return pandoc.Pandoc(blocks, meta)+end
+ test/lua/inlines-filter.lua view
@@ -0,0 +1,19 @@+function isWorldAfterSpace (fst, snd)+  return fst and fst.t == 'LineBreak'+   and snd and snd.t == 'Str' and snd.text == 'World!'+end++function Inlines (inlns)+  -- verify that this looks like a `pandoc.List`+  if not inlns.find or not inlns.map or not inlns.filter then+    error("table doesn't seem to be an instance of pandoc.List")+  end++  -- Remove spaces before string "World"+  for i = #inlns-1,1,-1 do+    if isWorldAfterSpace(inlns[i], inlns[i+1]) then+      inlns[i] = pandoc.Space()+    end+  end+  return inlns+end
+ test/lua/markdown-reader.lua view
@@ -0,0 +1,12 @@+return {+  {+    RawBlock = function (elem)+      if elem.format == "markdown" then+        local pd = pandoc.read(elem.text, "markdown")+        return pd.blocks[1]+      else+        return elem+      end+    end,+  }+}
+ test/lua/math.lua view
@@ -0,0 +1,10 @@+return {+  {+    Math = function (elem)+      if elem.mathtype == "DisplayMath" then+        elem.mathtype = "InlineMath"+      end+      return elem+    end,+  }+}
+ test/lua/meta.lua view
@@ -0,0 +1,6 @@+function Meta (meta)+  meta.old = nil+  meta.new = "new"+  meta.bool = (meta.bool == false)+  return meta+end
+ test/lua/metatable-catch-all.lua view
@@ -0,0 +1,20 @@+local num_inlines = 0++function catch_all(el)+  if el.tag and pandoc.Inline.constructor[el.tag] then+    num_inlines = num_inlines + 1+  end+end++function Pandoc(blocks, meta)+  return pandoc.Pandoc {+    pandoc.Para{pandoc.Str(num_inlines)}+  }+end++return {+  setmetatable(+    {Pandoc = Pandoc},+    {__index = function(_) return catch_all end}+  )+}
+ test/lua/module/globals.lua view
@@ -0,0 +1,108 @@+local tasty = require 'tasty'++local test = tasty.test_case+local group = tasty.test_group+local assert = tasty.assert++-- These tests exist mainly to catch changes to the JSON representation of+-- WriterOptions and its components. UPDATE THE DOCS if anything changes.+return {+  group 'PANDOC_WRITER_OPTIONS' {+    test('cite_method', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.cite_method), 'string')+    end),+    test('columns', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.columns), 'number')+    end),+    test('dpi', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.dpi), 'number')+    end),+    test('email_obfuscation', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.email_obfuscation), 'string')+    end),+    test('split_level', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.split_level), 'number')+    end),+    test('epub_fonts', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.epub_fonts), 'table')+    end),+    test('epub_metadata', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.epub_metadata), 'nil')+    end),+    test('epub_subdirectory', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.epub_subdirectory), 'string')+    end),+    test('extensions', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.extensions), 'table')+      for _, v in ipairs(PANDOC_WRITER_OPTIONS.extensions) do+        assert.are_equal(type(v), 'string')+      end+    end),+    test('highlight_style', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.highlight_style), 'table')+    end),+    test('html_math_method', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.html_math_method), 'string')+    end),+    test('html_q_tags', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.html_q_tags), 'boolean')+    end),+    test('identifier_prefix', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.identifier_prefix), 'string')+    end),+    test('incremental', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.incremental), 'boolean')+    end),+    test('listings', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.listings), 'boolean')+    end),+    test('number_offset', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.number_offset), 'table')+      for _, v in ipairs(PANDOC_WRITER_OPTIONS.number_offset) do+        assert.are_equal(type(v), 'number')+      end+    end),+    test('number_sections', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.number_sections), 'boolean')+    end),+    test('prefer_ascii', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.prefer_ascii), 'boolean')+    end),+    test('reference_doc', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.reference_doc), 'nil')+    end),+    test('reference_links', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.reference_links), 'boolean')+    end),+    test('reference_location', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.reference_location), 'string')+    end),+    test('section_divs', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.section_divs), 'boolean')+    end),+    test('setext_headers', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.setext_headers), 'boolean')+    end),+    test('slide_level', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.slide_level), 'nil')+    end),+    test('tab_stop', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.tab_stop), 'number')+    end),+    test('table_of_contents', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.table_of_contents), 'boolean')+    end),+    test('toc_depth', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.toc_depth), 'number')+    end),+    test('top_level_division', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.top_level_division), 'string')+    end),+    test('variables', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.variables), 'table')+    end),+    test('wrap_text', function ()+      assert.are_equal(type(PANDOC_WRITER_OPTIONS.wrap_text), 'string')+    end),+  }+}
+ test/lua/module/pandoc-format.lua view
@@ -0,0 +1,50 @@+local tasty = require 'tasty'++local test = tasty.test_case+local group = tasty.test_group+local assert = tasty.assert++local format = require 'pandoc.format'++return {+  group 'default_extensions' {+    test('docx', function ()+      local docx_default_exts = {+        'auto_identifiers',+      }+      assert.are_same(format.default_extensions('docx'), docx_default_exts)+    end),+  },++  group 'all_extensions' {+    test('docx', function ()+      local docx_default_exts = {+        'ascii_identifiers',+        'auto_identifiers',+        'citations',+        'east_asian_line_breaks',+        'empty_paragraphs',+        'gfm_auto_identifiers',+        'native_numbering',+        'styles',+      }+      assert.are_same(format.all_extensions('docx'), docx_default_exts)+    end),+  },++  group 'extensions' {+    test('org', function ()+      local org_default_exts = {+        ascii_identifiers = false,+        auto_identifiers = true,+        citations = true,+        east_asian_line_breaks = false,+        fancy_lists = false,+        gfm_auto_identifiers = false,+        smart = false,+        task_lists = true,+      }+      assert.are_same(format.extensions 'org', org_default_exts)+    end),+  },+}
+ test/lua/module/pandoc-list.lua view
@@ -0,0 +1,160 @@+local tasty = require 'tasty'+local List = require 'pandoc.List'++local assert = tasty.assert+local test = tasty.test_case+local group = tasty.test_group++return {+  group 'List as function' {+    test('equivalent to List:new', function (x)+      local new = List:new {'ramen'}+      local list = List {'ramen'}+      assert.are_same(new, list)+      assert.are_equal(getmetatable(new), getmetatable(list))+    end)+  },++  group 'clone' {+    test('changing the clone does not affect original', function ()+      local orig = List:new {23, 42}+      local copy = orig:clone()+      copy[1] = 5+      assert.are_same({23, 42}, orig)+      assert.are_same({5, 42}, copy)+    end),+    test('result is a list', function ()+      local orig = List:new {23, 42}+      assert.are_equal(List, getmetatable(orig:clone()))+    end),+  },++  group 'extend' {+    test('extends list with other list', function ()+      local primes = List:new {2, 3, 5, 7}+      primes:extend {11, 13, 17}+      assert.are_same({2, 3, 5, 7, 11, 13, 17}, primes)+    end)+  },++  group 'filter' {+    test('keep elements for which property is truthy', function ()+      local is_small_prime = function (x)+        return List.includes({2, 3, 5, 7}, x)+      end+      local numbers = List:new {4, 7, 2, 9, 5, 11}+      assert.are_same({7, 2, 5}, numbers:filter(is_small_prime))+    end),+  },++  group 'find' {+    test('returns element and index if found', function ()+      local list = List:new {5, 23, 71}+      local elem, idx = list:find(71)+      assert.are_same(71, elem)+      assert.are_same(3, idx)+    end),+    test('respects start index', function ()+      local list = List:new {19, 23, 29, 71}+      assert.are_equal(23, list:find(23, 1))+      assert.are_equal(23, list:find(23, 2))+      assert.is_nil(list:find(23, 3))+    end),+    test('returns nil if element not found', function ()+      assert.is_nil((List:new {18, 20, 22, 0, 24}):find('0'))+    end),+  },++  group 'find_if' {+    test('returns element and index if found', function ()+      local perm_prime = List:new {2, 3, 5, 7, 11, 13, 17, 31, 37, 71}+      local elem, idx = perm_prime:find_if(function (x) return x >= 10 end)+      assert.are_same(11, elem)+      assert.are_same(5, idx)+    end),+    test('returns nil if element not found', function ()+      local is_null = function (n) return List.includes({23,35,46,59}, n) end+      assert.is_nil((List:new {18, 20, 22, 24, 27}):find_if(is_null))+    end),+  },++  group 'includes' {+    test('finds elements in list', function ()+      local lst = List:new {'one', 'two', 'three'}+      assert.is_truthy(lst:includes('one'))+      assert.is_truthy(lst:includes('two'))+      assert.is_truthy(lst:includes('three'))+      assert.is_falsy(lst:includes('four'))+    end)+  },++  group 'insert' {+    test('insert value at end of list.', function ()+      local count_norsk = List {'en', 'to', 'tre'}+      count_norsk:insert('fire')+      assert.are_same({'en', 'to', 'tre', 'fire'}, count_norsk)+    end),+    test('insert value in the middle of list.', function ()+      local count_norsk = List {'fem', 'syv'}+      count_norsk:insert(2, 'seks')+      assert.are_same({'fem', 'seks', 'syv'}, count_norsk)+    end)+  },++  group 'map' {+    test('applies function to elements', function ()+      local primes = List:new {2, 3, 5, 7}+      local squares = primes:map(function (x) return x^2 end)+      assert.are_same({4, 9, 25, 49}, squares)+    end),+    test('leaves original list unchanged', function ()+      local primes = List:new {2, 3, 5, 7}+      local squares = primes:map(function (x) return x^2 end)+      assert.are_same({2, 3, 5, 7}, primes)+    end)+  },++  group 'new' {+    test('make table usable as list', function ()+      local test = List:new{1, 1, 2, 3, 5}+      assert.are_same(+        {1, 1, 4, 9, 25},+        test:map(function (x) return x^2 end)+      )+    end),+    test('return empty list if no argument is given', function ()+       assert.are_same({}, List:new())+    end),+    test('metatable of result is pandoc.List', function ()+      local test = List:new{5}+      assert.are_equal(List, getmetatable(test))+    end)+  },++  group 'remove' {+    test('remove value at end of list.', function ()+      local understand = List {'jeg', 'forstår', 'ikke'}+      local norsk_not = understand:remove()+      assert.are_same({'jeg', 'forstår'}, understand)+      assert.are_equal('ikke', norsk_not)+    end),+    test('remove value at beginning of list.', function ()+      local count_norsk = List {'en', 'to', 'tre'}+      count_norsk:remove(1)+      assert.are_same({'to', 'tre'}, count_norsk)+    end)+  },++  group 'sort' {+    test('sort numeric list', function ()+      local numbers = List {71, 5, -1, 42, 23, 0, 1}+      numbers:sort()+      assert.are_same({-1, 0, 1, 5, 23, 42, 71}, numbers)+    end),+    test('reverse-sort numeric', function ()+      local numbers = List {71, 5, -1, 42, 23, 0, 1}+      numbers:sort(function (x, y) return x > y end)+      assert.are_same({71, 42, 23, 5, 1, 0, -1}, numbers)+    end)+  },+}
+ test/lua/module/pandoc-mediabag.lua view
@@ -0,0 +1,72 @@+local tasty = require 'tasty'++local test = tasty.test_case+local group = tasty.test_group+local assert = tasty.assert++local mediabag = require 'pandoc.mediabag'++return {+  group 'insert' {+    test('insert adds an item to the mediabag', function ()+      local fp = "media/hello.txt"+      local mt = "text/plain"+      local contents = "Hello, World!"+      assert.are_same(mediabag.list(), {})+      mediabag.insert(fp, mt, contents)+      assert.are_same(+        mediabag.list(),+        {{['path'] = fp, ['type'] = mt, ['length'] = 13}}+      )+      mediabag.empty() -- clean up+    end),+    test('is idempotent', function ()+      local fp = "media/hello.txt"+      local mt = "text/plain"+      local contents = "Hello, World!"+      mediabag.insert(fp, mt, contents)+      mediabag.insert(fp, mt, contents)+      assert.are_same(+        mediabag.list(),+        {{['path'] = fp, ['type'] = mt, ['length'] = 13}}+      )+      mediabag.empty() -- clean up+    end),+  },++  group 'delete' {+    test('removes an item', function ()+      assert.are_same(mediabag.list(), {})+      mediabag.insert('test.html', 'text/html', '<aside>Who cares?</aside>')+      mediabag.insert('test.css', 'text/plain', 'aside { color: red; }')+      assert.are_equal(#mediabag.list(), 2)+      mediabag.delete('test.html')+      assert.are_same(+        mediabag.list(),+        {{['path'] = 'test.css', ['type'] = 'text/plain', ['length'] = 21}}+      )+      mediabag.empty() -- clean up+    end),+  },++  group 'items' {+    test('iterates over all items', function ()+      local input_items = {+        ['test.html'] = {'text/html', '<aside>Really?</aside>'},+        ['test.css'] = {'text/plain', 'aside { color: red; }'},+        ['test.js'] = {'application/javascript', 'alert("HI MOM!")'}+      }+      -- fill mediabag+      for name, v in pairs(input_items) do+        mediabag.insert(name, v[1], v[2])+      end++      local seen_items = {}+      for fp, mt, c in mediabag.items() do+        seen_items[fp] = {mt, c}+      end+      assert.are_same(seen_items, input_items)+      mediabag.empty() -- clean up+    end)+  }+}
+ test/lua/module/pandoc-path.lua view
@@ -0,0 +1,44 @@+local tasty = require 'tasty'+local path = require 'pandoc.path'++local assert = tasty.assert+local test = tasty.test_case+local group = tasty.test_group++return {+  group 'path separator' {+    test('is string', function ()+      assert.are_same(type(path.separator), 'string')+    end),+    test('is slash or backslash', function ()+      assert.is_truthy(path.separator:match '^[/\\]$')+    end),+  },+  group 'search path separator' {+    test('is string', function ()+      assert.are_same(type(path.search_path_separator), 'string')+    end),+    test('is colon or semicolon', function ()+      assert.is_truthy(path.search_path_separator:match '^[:;]$')+    end)+  },+  group 'module' {+    test('check function existence', function ()+      local functions = {+        'directory',+        'filename',+        'is_absolute',+        'is_relative',+        'join',+        'make_relative',+        'normalize',+        'split',+        'split_extension',+        'split_search_path',+      }+      for _, f in ipairs(functions) do+        assert.are_equal(type(path[f]), 'function')+      end+    end)+  }+}
+ test/lua/module/pandoc-structure.lua view
@@ -0,0 +1,139 @@+local tasty = require 'tasty'+local structure = require 'pandoc.structure'+local path = require 'pandoc.path'+local system = require 'pandoc.system'++local assert = tasty.assert+local test = tasty.test_case+local group = tasty.test_group++return {+  test('is table', function ()+    assert.are_equal(type(structure), 'table')+  end),++  group 'make_sections' {+    test('sanity check', function ()+      local blks = {+        pandoc.Header(1, {pandoc.Str 'First'}),+        pandoc.Header(2, {pandoc.Str 'Second'}),+        pandoc.Header(2, {pandoc.Str 'Third'}),+      }+      local opts = PANDOC_WRITER_OPTIONS+      local hblks = structure.make_sections(blks, opts)+      assert.are_equal('Div', hblks[1].t)+      assert.are_equal('Header', hblks[1].content[1].t)+    end),+    test('respects number_sections', function ()+      local blks = {+        pandoc.Header(1, {pandoc.Str 'First'}),+        pandoc.Para 'Vestibulum convallis, lorem a tempus semper.'+      }+      local hblks = structure.make_sections(blks, {number_sections = true})+      assert.are_equal('Div', hblks[1].t)+      assert.are_equal('Header', hblks[1].content[1].t)+      assert.are_equal('1', hblks[1].content[1].attributes['number'])+    end),+    test('respects base_level', function ()+      local blks = {+        pandoc.Header(1, {pandoc.Str 'First'}),+        pandoc.Para 'Curabitur lacinia pulvinar nibh.',+        pandoc.Header(3, {pandoc.Str 'First'}), -- Skipping level 2+      }+      local opts = {+        number_sections = true,+        base_level = 1+      }+      local hblks = structure.make_sections(blks, opts)+      assert.are_equal('Div', hblks[1].t)+      assert.are_equal('Header', hblks[1].content[1].t)+      assert.are_equal('1',   hblks[1].content[1].attributes['number'])+      assert.are_equal('1.1', hblks[1].content[3].attributes['number'])+    end)+  },++  group 'split_into_chunks' {+    test('is function', function ()+      assert.are_equal(type(structure.split_into_chunks), 'function')+    end),+    test('returns a chunked doc', function ()+      assert.are_equal(+        pandoc.utils.type(structure.split_into_chunks(pandoc.Pandoc{})),+        'pandoc.ChunkedDoc'+      )+    end),+  },++  group 'table_of_contents' {+    test('is function', function ()+      assert.are_equal(type(structure.table_of_contents), 'function')+    end),+    test('returns a bullet list', function ()+      assert.are_equal(+        pandoc.utils.type(structure.table_of_contents{}),+        'Block'+      )+      assert.are_equal(+        structure.table_of_contents{}.t,+        'BulletList'+      )+    end),+    test('returns a toc for a list of blocks', function ()+      local body = pandoc.Blocks{+        pandoc.Header(1, 'First'),+        pandoc.Para('A sentence placed below the first structure.'),+        pandoc.Header(2, 'Subsection'),+        pandoc.Para('Mauris ac felis vel velit tristique imperdiet.'),+        pandoc.Header(1, 'Second'),+        pandoc.Para('Integer placerat tristique nisl.')+      }+      assert.are_equal(+        structure.table_of_contents(body),+        pandoc.BulletList{+          {pandoc.Plain('First'),+           pandoc.BulletList{{pandoc.Plain 'Subsection'}}+          },+          {pandoc.Plain('Second')}+        }+      )+    end),+    test('returns a toc for a chunked doc', function ()+      local doc = pandoc.Pandoc {+        pandoc.Header(1, 'First', {id='first'}),+        pandoc.Para('A sentence placed below the first structure.'),+        pandoc.Header(2, 'Subsection', {id='subsection'}),+        pandoc.Para('Mauris ac felis vel velit tristique imperdiet.'),+        pandoc.Header(1, 'Second', {id='second'}),+        pandoc.Para('Integer placerat tristique nisl.')+      }+      local chunked = structure.split_into_chunks(doc, {chunk_level = 2})+      assert.are_equal(+        structure.table_of_contents(chunked),+        pandoc.BulletList{+          {pandoc.Plain({pandoc.Link('First', 'chunk-001', '', {id='toc-first'})}),+           pandoc.BulletList{{pandoc.Plain({pandoc.Link('Subsection', 'chunk-002', '', {id='toc-subsection'})})}}+          },+          {pandoc.Plain({pandoc.Link('Second', 'chunk-003', '', {id='toc-second'})})}+        }+      )+    end),+    test('respects toc-depth option', function ()+      local doc = pandoc.Pandoc {+        pandoc.Header(1, 'First', {id='first'}),+        pandoc.Para('A sentence placed below the first structure.'),+        pandoc.Header(2, 'Subsection', {id='subsection'}),+        pandoc.Para('Mauris ac felis vel velit tristique imperdiet.'),+        pandoc.Header(1, 'Second', {id='second'}),+        pandoc.Para('Integer placerat tristique nisl.')+      }+      local chunked = structure.split_into_chunks(doc)+      assert.are_equal(+        structure.table_of_contents(chunked, {toc_depth = 1}),+        pandoc.BulletList{+          {pandoc.Plain({pandoc.Link('First', 'chunk-001', '', {id='toc-first'})})},+          {pandoc.Plain({pandoc.Link('Second', 'chunk-002', '', {id='toc-second'})})}+        }+      )+    end),+  },+}
+ test/lua/module/pandoc-template.lua view
@@ -0,0 +1,92 @@+local tasty = require 'tasty'+local template = require 'pandoc.template'++local assert = tasty.assert+local test = tasty.test_case+local group = tasty.test_group++return {+  test('is table', function ()+    assert.are_equal(type(template), 'table')+  end),+  group 'default' {+    test('is function', function ()+      assert.are_equal(type(template.default), 'function')+    end),+    test('returns a string for known format', function ()+      assert.are_equal(+        pandoc.utils.type(template.default 'json'),+        'string'+      )+      assert.are_equal(+        pandoc.utils.type(template.default 'markdown'),+        'string'+      )+    end),+    test('fails on unknown format', function ()+      local success, msg = pcall(function ()+          return pandoc.utils.type(template.default 'nosuchformat')+      end)+      assert.is_falsy(success)+    end),+  },+  group 'compile' {+    test('is function', function ()+      assert.are_equal(type(template.compile), 'function')+    end),+    test('returns a Template', function ()+      assert.are_equal(+        pandoc.utils.type(template.compile('$title$')),+        'pandoc Template'+      )+    end),+    test('returns a Template', function ()+      local templ_path = pandoc.path.join{'lua', 'module', 'default.test'}+      assert.are_equal(+        pandoc.utils.type(template.compile('${ partial() }', templ_path)),+        'pandoc Template'+      )+    end),+    test('fails if template has non-existing partial', function ()+      assert.error_matches(+        function () return template.compile('${ nosuchpartial() }') end,+        'Could not find data file'+      )+    end),+    test('works with default template that uses partials', function ()+      local jats_template = template.default 'jats'+      assert.are_equal(type(jats_template), 'string')+      assert.are_equal(+        pandoc.utils.type(template.compile(jats_template)),+        'pandoc Template'+      )+    end),+  },+  group 'apply' {+    test('is function', function ()+      assert.are_equal(type(template.apply), 'function')+    end),+    test('returns a Doc value', function ()+      local tmpl = template.compile('placeholder')+      assert.are_equal(+        pandoc.utils.type(template.apply(tmpl, {})),+        'Doc'+      )+    end),+    test('applies the given context', function ()+      local tmpl = template.compile('song: $title$')+      local context = {title = 'Along Comes Mary'}+      assert.are_equal(+        template.apply(tmpl, context):render(),+        'song: Along Comes Mary'+      )+    end),+    test('accepts string as template', function ()+      local context = {number = '2'}+      assert.are_equal(+        template.apply('Song $number$', context):render(),+        'Song 2'+      )+    end)+  },+}
+ test/lua/module/pandoc-types.lua view
@@ -0,0 +1,86 @@+local tasty = require 'tasty'+local types = require 'pandoc.types'+local Version = types.Version++local assert = tasty.assert+local test = tasty.test_case+local group = tasty.test_group++return {+  group 'Version' {++    group 'constructor' {+      test('has type `userdata`', function ()+        assert.are_same(type(Version {2}), 'userdata')+      end),+      test('accepts list of integers', function ()+        assert.are_same(type(Version {2, 7, 3}), 'userdata')+      end),+      test('accepts a single integer', function ()+        assert.are_same(Version(5), Version {5})+      end),+      test('accepts version as string', function ()+        assert.are_same(+          Version '4.45.1',+          Version {4, 45, 1}+        )+      end),+      test('non-version string is rejected', function ()+        local success, msg = pcall(function () Version '11friends' end)+        assert.is_falsy(success)+        assert.is_truthy(tostring(msg):match('11friends'))+      end)+    },++    group 'comparison' {+      test('smaller (equal) than', function ()+        assert.is_truthy(Version {2, 58, 3} < Version {2, 58, 4})+        assert.is_falsy(Version {2, 60, 1} < Version {2, 59, 2})+        assert.is_truthy(Version {0, 14, 3} < Version {0, 14, 3, 1})+        assert.is_truthy(Version {3, 58, 3} <= Version {4})+        assert.is_truthy(Version {0, 14, 3} <= Version {0, 14, 3, 1})+      end),+      test('larger (equal) than', function ()+        assert.is_truthy(Version{2,58,3} > Version {2, 57, 4})+        assert.is_truthy(Version{2,58,3} > Version {2, 58, 2})+        assert.is_truthy(Version {0, 8} >= Version {0, 8})+        assert.is_falsy(Version {0, 8} >= Version {0, 8, 2})+      end),+      test('equality', function ()+        assert.is_truthy(Version '8.8', Version {8, 8})+      end),+      test('second argument can be a version string', function ()+        assert.is_truthy(Version '8' < '9.1')+        assert.is_falsy(Version '8.8' < '8.7')+      end),+    },++    group 'conversion to string' {+      test('converting from and to string is a noop', function ()+        local version_string = '1.19.4'+        assert.are_equal(tostring(Version(version_string)), version_string)+      end)+    },++    group 'convenience functions' {+      test('throws error if version is too old', function ()+        local actual = Version {2, 8}+        local expected = Version {2, 9}+        assert.error_matches(+          function () actual:must_be_at_least(expected) end,+          'expected version 2.9 or newer, got 2.8'+        )+      end),+      test('does nothing if expected version is older than actual', function ()+        local actual = Version '2.9'+        local expected = Version '2.8'+        actual:must_be_at_least(expected)+      end),+      test('does nothing if expected version equals to actual', function ()+        local actual = Version '2.8'+        local expected = Version '2.8'+        actual:must_be_at_least(expected)+      end)+    }+  }+}
+ test/lua/module/pandoc-utils.lua view
@@ -0,0 +1,333 @@+local tasty = require 'tasty'+local utils = require 'pandoc.utils'++local assert = tasty.assert+local test = tasty.test_case+local group = tasty.test_group++return {+  group 'blocks_to_inlines' {+    test('default separator', function ()+      local blocks = {+        pandoc.Para { pandoc.Str 'Paragraph1' },+        pandoc.Para { pandoc.Emph { pandoc.Str 'Paragraph2' } }+      }+      local expected = {+        pandoc.Str 'Paragraph1',+        pandoc.LineBreak(),+        pandoc.Emph { pandoc.Str 'Paragraph2' }+      }+      assert.are_same(+        expected,+        utils.blocks_to_inlines(blocks)+      )+    end),+    test('custom separator', function ()+      local blocks = {+        pandoc.Para{ pandoc.Str 'Paragraph1' },+        pandoc.Para{ pandoc.Emph 'Paragraph2' }+      }+      local expected = {+        pandoc.Str 'Paragraph1',+        pandoc.LineBreak(),+        pandoc.Emph { pandoc.Str 'Paragraph2' }+      }+      assert.are_same(+        expected,+        utils.blocks_to_inlines(blocks, { pandoc.LineBreak() })+      )+    end)+  },++  group 'equals' {+    test('compares Pandoc elements', function ()+      assert.is_truthy(+        utils.equals(pandoc.Pandoc{'foo'}, pandoc.Pandoc{'foo'})+      )+    end),+    test('compares Block elements', function ()+      assert.is_truthy(+        utils.equals(pandoc.Plain{'foo'}, pandoc.Plain{'foo'})+      )+      assert.is_falsy(+        utils.equals(pandoc.Para{'foo'}, pandoc.Plain{'foo'})+      )+    end),+    test('compares Inline elements', function ()+      assert.is_truthy(+        utils.equals(pandoc.Emph{'foo'}, pandoc.Emph{'foo'})+      )+      assert.is_falsy(+        utils.equals(pandoc.Emph{'foo'}, pandoc.Strong{'foo'})+      )+    end),+    test('compares Inline with Block elements', function ()+      assert.is_falsy(+        utils.equals(pandoc.Emph{'foo'}, pandoc.Plain{'foo'})+      )+      assert.is_falsy(+        utils.equals(pandoc.Para{'foo'}, pandoc.Strong{'foo'})+      )+    end),+    test('compares Pandoc with Block elements', function ()+      assert.is_falsy(+        utils.equals(pandoc.Pandoc{'foo'}, pandoc.Plain{'foo'})+      )+      assert.is_falsy(+        utils.equals(pandoc.Para{'foo'}, pandoc.Pandoc{'foo'})+      )+    end),+  },++  group 'make_sections' {+    test('sanity check', function ()+      local blks = {+        pandoc.Header(1, {pandoc.Str 'First'}),+        pandoc.Header(2, {pandoc.Str 'Second'}),+        pandoc.Header(2, {pandoc.Str 'Third'}),+      }+      local hblks = utils.make_sections(true, 1, blks)+      assert.are_equal('Div', hblks[1].t)+      assert.are_equal('Header', hblks[1].content[1].t)+      assert.are_equal('1', hblks[1].content[1].attributes['number'])+    end)+  },++  group 'normalize_date' {+    test('09 Nov 1989', function ()+      assert.are_equal('1989-11-09', utils.normalize_date '09 Nov 1989')+    end),+    test('12/31/2017', function ()+      assert.are_equal('2017-12-31', utils.normalize_date '12/31/2017')+    end),+  },++  group 'references' {+    test('gets references from doc', function ()+      local ref = {+        ['author'] = {+          {given = 'Max', family = 'Mustermann'}+        },+        ['container-title'] = pandoc.Inlines('JOSS'),+        ['id']     = 'test',+        ['issued'] = {['date-parts'] = {{2021}}},+        ['title']  = pandoc.Inlines{+          pandoc.Quoted('DoubleQuote', 'Interesting'),+          pandoc.Space(),+          'work'+        },+        ['type']   = 'article-journal',+      }+      local nocite = pandoc.Cite(+        '@test',+        {pandoc.Citation('test', 'NormalCitation')}+      )+      local doc = pandoc.Pandoc({}, {nocite = nocite, references = {ref}})+      assert.are_same({ref}, pandoc.utils.references(doc))+    end)+  },++  group 'sha1' {+    test('hashing', function ()+      local ref_hash = '0a0a9f2a6772942557ab5355d76af442f8f65e01'+      assert.are_equal(ref_hash, utils.sha1 'Hello, World!')+    end)+  },++  group 'stringify' {+    test('Inline', function ()+      local inline = pandoc.Emph{+        pandoc.Str 'Cogito',+        pandoc.Space(),+        pandoc.Str 'ergo',+        pandoc.Space(),+        pandoc.Str 'sum.',+      }+      assert.are_equal('Cogito ergo sum.', utils.stringify(inline))+    end),+    test('Block', function ()+      local block = pandoc.Para{+        pandoc.Str 'Make',+        pandoc.Space(),+        pandoc.Str 'it',+        pandoc.Space(),+        pandoc.Str 'so.',+      }+      assert.are_equal('Make it so.', utils.stringify(block))+    end),+    test('boolean', function ()+      assert.are_equal('true', utils.stringify(true))+      assert.are_equal('false', utils.stringify(false))+    end),+    test('number', function ()+      assert.are_equal('5', utils.stringify(5))+      assert.are_equal('23.23', utils.stringify(23.23))+    end),+    test('Attr', function ()+      local attr = pandoc.Attr('foo', {'bar'}, {a = 'b'})+      assert.are_equal('', utils.stringify(attr))+    end),+    test('List', function ()+      local list = pandoc.List{pandoc.Str 'a', pandoc.Blocks('b')}+      assert.are_equal('ab', utils.stringify(list))+    end),+    test('Blocks', function ()+      local blocks = pandoc.Blocks{pandoc.Para 'a', pandoc.Header(1, 'b')}+      assert.are_equal('ab', utils.stringify(blocks))+    end),+    test('Inlines', function ()+      local inlines = pandoc.Inlines{pandoc.Str 'a', pandoc.Subscript('b')}+      assert.are_equal('ab', utils.stringify(inlines))+    end),+    test('Meta', function ()+      local meta = pandoc.Meta{+        a = pandoc.Inlines 'funny and ',+        b = 'good movie',+        c = pandoc.List{pandoc.Inlines{pandoc.Str '!'}}+      }+      assert.are_equal('funny and good movie!', utils.stringify(meta))+    end),+  },++  group 'to_roman_numeral' {+    test('convertes number', function ()+      assert.are_equal('MDCCCLXXXVIII', utils.to_roman_numeral(1888))+    end),+    test('fails on non-convertible argument', function ()+      assert.is_falsy(pcall(utils.to_roman_numeral, 'not a number'))+    end)+  },++  group 'type' {+    test('nil', function ()+      assert.are_equal(utils.type(nil), 'nil')+    end),+    test('boolean', function ()+      assert.are_equal(utils.type(true), 'boolean')+      assert.are_equal(utils.type(false), 'boolean')+    end),+    test('number', function ()+      assert.are_equal(utils.type(5), 'number')+      assert.are_equal(utils.type(-3.02), 'number')+    end),+    test('string', function ()+      assert.are_equal(utils.type(''), 'string')+      assert.are_equal(utils.type('asdf'), 'string')+    end),+    test('plain table', function ()+      assert.are_equal(utils.type({}), 'table')+    end),+    test('List', function ()+      assert.are_equal(utils.type(pandoc.List{}), 'List')+    end),+    test('Inline', function ()+      assert.are_equal(utils.type(pandoc.Str 'a'), 'Inline')+      assert.are_equal(utils.type(pandoc.Emph 'emphasized'), 'Inline')+    end),+    test('Inlines', function ()+      assert.are_equal(utils.type(pandoc.Inlines{pandoc.Str 'a'}), 'Inlines')+      assert.are_equal(utils.type(pandoc.Inlines{pandoc.Emph 'b'}), 'Inlines')+    end),+    test('Blocks', function ()+      assert.are_equal(utils.type(pandoc.Para 'a'), 'Block')+      assert.are_equal(utils.type(pandoc.CodeBlock 'true'), 'Block')+    end),+    test('Inlines', function ()+      assert.are_equal(utils.type(pandoc.Blocks{'a'}), 'Blocks')+      assert.are_equal(utils.type(pandoc.Blocks{pandoc.CodeBlock 'b'}), 'Blocks')+    end),+  },++  group 'to_simple_table' {+    test('convertes Table', function ()+      function simple_cell (blocks)+        return {+          attr = pandoc.Attr(),+          alignment = "AlignDefault",+          contents = blocks,+          col_span = 1,+          row_span = 1,+        }+      end+      local tbl = pandoc.Table(+        {long = {pandoc.Plain {+                   pandoc.Str "the", pandoc.Space(), pandoc.Str "caption"}}},+        {{pandoc.AlignDefault, nil}},+        pandoc.TableHead{pandoc.Row{simple_cell{pandoc.Plain "head1"}}},+        {{+            attr = pandoc.Attr(),+            body = {pandoc.Row{simple_cell{pandoc.Plain "cell1"}}},+            head = {},+            row_head_columns = 0+        }},+        pandoc.TableFoot(),+        pandoc.Attr()+      )+      local stbl = utils.to_simple_table(tbl)+      assert.are_equal('SimpleTable', stbl.t)+      assert.are_equal('head1', utils.stringify(stbl.headers[1]))+      assert.are_equal('cell1', utils.stringify(stbl.rows[1][1]))+      assert.are_equal('the caption', utils.stringify(pandoc.Span(stbl.caption)))+    end),+    test('fails on para', function ()+      assert.is_falsy(pcall(utils.to_simple_table, pandoc.Para "nope"))+    end),+  },+  group 'from_simple_table' {+    test('converts SimpleTable to Table', function ()+      local caption = {pandoc.Str "Overview"}+      local aligns = {pandoc.AlignDefault, pandoc.AlignDefault}+      local widths = {0, 0} -- let pandoc determine col widths+      local headers = {+        {pandoc.Plain "Language"},+        {pandoc.Plain "Typing"}+      }+      local rows = {+        {{pandoc.Plain "Haskell"}, {pandoc.Plain "static"}},+        {{pandoc.Plain "Lua"}, {pandoc.Plain "Dynamic"}},+      }+      local simple_table = pandoc.SimpleTable(+        caption,+        aligns,+        widths,+        headers,+        rows+      )+      local tbl = utils.from_simple_table(simple_table)+      assert.are_equal("Table", tbl.t)+      assert.are_same(+        {pandoc.Plain(caption)},+        tbl.caption.long+      )+      -- reversible+      assert.are_same(simple_table, utils.to_simple_table(tbl))+    end),+    test('empty caption', function ()+      local simple_table = pandoc.SimpleTable(+        {},+        {pandoc.AlignDefault},+        {0},+        {{pandoc.Plain 'a'}},+        {{{pandoc.Plain 'b'}}}+      )+      local tbl = utils.from_simple_table(simple_table)+      assert.are_equal(+        pandoc.Blocks{},+        tbl.caption.long+      )+      assert.is_nil(tbl.caption.short)+    end),+    test('empty body', function ()+      local simple_table = pandoc.SimpleTable(+        pandoc.Inlines('a nice caption'),+        {pandoc.AlignDefault},+        {0},+        {{pandoc.Plain 'a'}},+        {}+      )+      local tbl = utils.from_simple_table(simple_table)+      tbl.bodies:map(print)+      assert.are_same(pandoc.List(), tbl.bodies)+    end),+  }+}
+ test/lua/module/pandoc.lua view
@@ -0,0 +1,423 @@+local tasty = require 'tasty'++local test = tasty.test_case+local group = tasty.test_group+local assert = tasty.assert++function os_is_windows ()+  return package.config:sub(1,1) == '\\'+end++-- Constructor behavior is tested in the hslua-pandoc-types module, so+-- we just make sure the functions are present.+return {+  group 'Constructors' {+    group 'Misc' {+      test('pandoc.Attr is a function', function ()+        assert.are_equal(type(pandoc.Attr), 'function')+      end),+      test('pandoc.AttributeList is a function', function ()+        assert.are_equal(type(pandoc.AttributeList), 'function')+      end),+      test('pandoc.Blocks is a function', function ()+        assert.are_equal(type(pandoc.Blocks), 'function')+      end),+      test('pandoc.Citation is a function', function ()+        assert.are_equal(type(pandoc.Citation), 'function')+      end),+      test('pandoc.Inlines is a function', function ()+        assert.are_equal(type(pandoc.Inlines), 'function')+      end),+      test('pandoc.SimpleTable is a function', function ()+        assert.are_equal(type(pandoc.SimpleTable), 'function')+      end),+      test('pandoc.Meta is a function', function ()+        assert.are_equal(type(pandoc.Meta), 'function')+      end),+      test('pandoc.Pandoc is a function', function ()+        assert.are_equal(type(pandoc.Pandoc), 'function')+      end),+    },+    group "Inline elements" {+      test('pandoc.AttributeList is a function', function ()+        assert.are_equal(type(pandoc.Cite), 'function')+      end),+      test('pandoc.AttributeList is a function', function ()+        assert.are_equal(type(pandoc.Code), 'function')+      end),+      test('pandoc.Emph is a function', function ()+        assert.are_equal(type(pandoc.Emph), 'function')+      end),+      test('pandoc.Image is a function', function ()+        assert.are_equal(type(pandoc.Image), 'function')+      end),+      test('pandoc.Link is a function', function ()+        assert.are_equal(type(pandoc.Link), 'function')+      end),+      test('pandoc.Math is a function', function ()+        assert.are_equal(type(pandoc.Math), 'function')+      end),+      test('pandoc.Note is a function', function ()+        assert.are_equal(type(pandoc.Note), 'function')+      end),+      test('pandoc.Quoted is a function', function ()+        assert.are_equal(type(pandoc.Quoted), 'function')+      end),+      test('pandoc.SmallCaps is a function', function ()+        assert.are_equal(type(pandoc.SmallCaps), 'function')+      end),+      test('pandoc.SoftBreak is a function', function ()+        assert.are_equal(type(pandoc.SoftBreak), 'function')+      end),+      test('pandoc.Span is a function', function ()+        assert.are_equal(type(pandoc.Span), 'function')+      end),+      test('pandoc.Str is a function', function ()+        assert.are_equal(type(pandoc.Str), 'function')+      end),+      test('pandoc.Strikeout is a function', function ()+      assert.are_equal(type(pandoc.Strikeout), 'function')+      end),+      test('pandoc.Strong is a function', function ()+        assert.are_equal(type(pandoc.Strong), 'function')+      end),+      test('pandoc.Subscript is a function', function ()+        assert.are_equal(type(pandoc.Subscript), 'function')+      end),+      test('pandoc.Superscript is a function', function ()+        assert.are_equal(type(pandoc.Superscript), 'function')+      end),+      test('pandoc.Underline is a function', function ()+        assert.are_equal(type(pandoc.Underline), 'function')+      end),+    },+    group "Block elements" {+      test('pandoc.BlockQuote is a function', function ()+        assert.are_equal(type(pandoc.BlockQuote), 'function')+      end),+      test('pandoc.BulletList is a function', function ()+        assert.are_equal(type(pandoc.BulletList), 'function')+      end),+      test('pandoc.CodeBlock is a function', function ()+        assert.are_equal(type(pandoc.CodeBlock), 'function')+      end),+      test('pandoc.DefinitionList is a function', function ()+        assert.are_equal(type(pandoc.DefinitionList), 'function')+      end),+      test('pandoc.Div is a function', function ()+        assert.are_equal(type(pandoc.Div), 'function')+      end),+      test('pandoc.Header is a function', function ()+        assert.are_equal(type(pandoc.Header), 'function')+      end),+      test('pandoc.LineBlock is a function', function ()+        assert.are_equal(type(pandoc.LineBlock), 'function')+      end),+      test('pandoc.Figure is a function', function ()+        assert.are_equal(type(pandoc.Figure), 'function')+      end),+      test('pandoc.OrderedList is a function', function ()+        assert.are_equal(type(pandoc.OrderedList), 'function')+      end),+      test('pandoc.Para is a function', function ()+        assert.are_equal(type(pandoc.Para), 'function')+      end),+      test('pandoc.Plain is a function', function ()+        assert.are_equal(type(pandoc.Plain), 'function')+      end),+      test('pandoc.RawBlock is a function', function ()+        assert.are_equal(type(pandoc.Plain), 'function')+      end),+      test('pandoc.Table is a function', function ()+        assert.are_equal(type(pandoc.Table), 'function')+      end),+    }+  },+  group 'MetaValue elements' {+    test('MetaList elements behave like lists', function ()+      local metalist = pandoc.MetaList{}+      assert.are_equal(type(metalist.insert), 'function')+      assert.are_equal(type(metalist.remove), 'function')+    end),+    test('`tag` is an alias for `t``', function ()+      assert.are_equal((pandoc.MetaList{}).tag, (pandoc.MetaList{}).t)+      assert.are_equal((pandoc.MetaMap{}).tag, (pandoc.MetaMap{}).t)+      assert.are_equal((pandoc.MetaInlines{}).tag, (pandoc.MetaInlines{}).t)+      assert.are_equal((pandoc.MetaBlocks{}).tag, (pandoc.MetaBlocks{}).t)+    end),+  },+  group 'Meta' {+    test('inline list is treated as MetaInlines', function ()+      local meta = pandoc.Pandoc({}, {test = {pandoc.Emph 'check'}}).meta+      assert.are_same(meta.test, {pandoc.Emph{pandoc.Str 'check'}})+    end),+    test('inline element is treated as MetaInlines singleton', function ()+      local meta = pandoc.Pandoc({}, {test = pandoc.Emph 'check'}).meta+      assert.are_same(meta.test, {pandoc.Emph{pandoc.Str 'check'}})+    end),+    test('block list is treated as MetaBlocks', function ()+      local meta = pandoc.Pandoc({}, {test = {pandoc.Plain 'check'}}).meta+      assert.are_same(meta.test, {pandoc.Plain{pandoc.Str 'check'}})+    end),+    test('block element is treated as MetaBlocks singleton', function ()+      local meta = pandoc.Pandoc({}, {test = pandoc.Plain 'check'}).meta+      assert.are_same(meta.test, {pandoc.Plain{pandoc.Str 'check'}})+    end),+  },+  group 'Other types' {+    group 'ReaderOptions' {+      test('returns a userdata value', function ()+        local opts = pandoc.ReaderOptions {}+        assert.are_equal(type(opts), 'userdata')+      end),+      test('can construct from table', function ()+        local opts = pandoc.ReaderOptions {columns = 66}+        assert.are_equal(opts.columns, 66)+      end),+      test('can construct from other ReaderOptions value', function ()+        local orig = pandoc.ReaderOptions{columns = 65}+        local copy = pandoc.ReaderOptions(orig)+        for k, v in pairs(orig) do+          assert.are_same(copy[k], v)+        end+        assert.are_equal(copy.columns, 65)+      end),+    },+  },++  group 'clone' {+    test('clones Attr', function ()+      local attr = pandoc.Attr('test', {'my-class'}, {foo = 'bar'})+      local cloned = attr:clone()+      attr.identifier = ''+      attr.classes = {}+      attr.attributes = {}+      assert.are_same(cloned.identifier, 'test')+      assert.are_same(cloned.classes, {'my-class'})+      assert.are_same(cloned.attributes.foo, 'bar')+    end),+    test('clones ListAttributes', function ()+      local la = pandoc.ListAttributes(2, pandoc.DefaultStyle, pandoc.Period)+      local cloned = la:clone()+      la.start = 9+      assert.are_same(cloned.start, 2)+    end),+    test('clones Para', function ()+      local para = pandoc.Para {pandoc.Str 'Hello'}+      local cloned = para:clone()+      para.content[1].text = 'bye'+      assert.are_same(cloned, pandoc.Para {pandoc.Str 'Hello'})+    end),+    test('clones Str', function ()+      local str = pandoc.Str 'Hello'+      local cloned = str:clone()+      str.text = 'bye'+      assert.are_same(cloned.text, 'Hello')+    end),+    test('clones Citation', function ()+      local cite = pandoc.Citation('leibniz', pandoc.AuthorInText)+      local cloned = cite:clone()+      cite.id = 'newton'+      assert.are_same(cloned.id, 'leibniz')+      assert.are_same(cite.id, 'newton')+      assert.are_same(cite.mode, cloned.mode)+    end),+  },++  group 'pipe' {+    test('external string processing', function ()+      if os_is_windows() then+        local pipe_result = pandoc.pipe('find', {'hi'}, 'hi')+        assert.are_equal('hi', pipe_result:match '%a+')+      else+        local pipe_result = pandoc.pipe('tr', {'a', 'b'}, 'abc')+        assert.are_equal('bbc', pipe_result:match '%a+')+      end+    end),+    test('failing pipe', function ()+      if os_is_windows() then+        local success, err = pcall(pandoc.pipe, 'find', {'/a'}, 'hi')+        assert.is_falsy(success)+        assert.are_equal('find', err.command)+        assert.is_truthy(err.error_code ~= 0)+      else+        local success, err = pcall(pandoc.pipe, 'false', {}, 'abc')+        assert.is_falsy(success)+        assert.are_equal('false', err.command)+        assert.are_equal(1, err.error_code)+        assert.are_equal('', err.output)+      end+    end)+  },++  group 'read' {+    test('Markdown', function ()+      local valid_markdown = '*Hello*, World!\n'+      local expected = pandoc.Pandoc({+          pandoc.Para {+            pandoc.Emph { pandoc.Str 'Hello' },+            pandoc.Str ',',+            pandoc.Space(),+            pandoc.Str 'World!'+          }+      })+      assert.are_same(expected, pandoc.read(valid_markdown))+    end),+    test('unsupported extension', function ()+      assert.error_matches(+        function () pandoc.read('foo', 'gfm+empty_paragraphs') end,+        'The extension empty_paragraphs is not supported for gfm'+      )+    end),+    test('read with other indented code classes', function()+      local indented_code = '    return true'+      local expected = pandoc.Pandoc({+          pandoc.CodeBlock('return true', {class='foo'})+      })+      assert.are_same(+        expected,+        pandoc.read(indented_code, 'markdown', {indented_code_classes={'foo'}})+      )+    end),+    test('can read epub', function ()+      local epub = io.open('lua/module/tiny.epub', 'rb')+      local blocks = pandoc.read(epub:read'a', 'epub').blocks+      assert.are_equal(+        blocks[#blocks],+        pandoc.Para { pandoc.Emph 'EPUB' }+      )+    end),+    test('failing read', function ()+      assert.error_matches(+        function () pandoc.read('foo', 'nosuchreader') end,+        'Unknown input format nosuchreader'+      )+    end),+    group 'extensions' {+      test('string spec', function ()+        local doc = pandoc.read('"vice versa"', 'markdown-smart')+        assert.are_equal(doc, pandoc.Pandoc{pandoc.Para '"vice versa"'})+      end),+      test('unsupported extension', function ()+        assert.error_matches(+          function () pandoc.read('foo', 'gfm+empty_paragraphs') end,+          'The extension empty_paragraphs is not supported for gfm'+        )+      end),+      test('unknown extension', function ()+        local format_spec = { format = 'markdown', extensions = {'nope'}}+        assert.error_matches(+          function () pandoc.read('x', format_spec) end,+          'The extension nope is not supported for markdown'+        )+      end),+      test('fails on invalid extension', function ()+        local format_spec = { format = 'markdown', extensions = {'nope'}}+        assert.error_matches(+          function () pandoc.read('nu-uh', format_spec) end,+          'The extension nope is not supported for markdown'+        )+      end),+    },+  },++  group 'walk_block' {+    test('block walking order', function ()+     local acc = {}+     local nested_nums = pandoc.Div {+       pandoc.Para{pandoc.Str'1'},+       pandoc.Div{+         pandoc.Para{pandoc.Str'2'},+         pandoc.Para{pandoc.Str'3'}+       },+       pandoc.Para{pandoc.Str'4'}+     }+     pandoc.walk_block(+       nested_nums,+       {Para = function (p) table.insert(acc, p.content[1].text) end}+     )+     assert.are_equal('1234', table.concat(acc))+    end)+  },++  group 'walk_inline' {+    test('inline walking order', function ()+      local acc = {}+      local nested_nums = pandoc.Span {+        pandoc.Str'1',+        pandoc.Emph {+          pandoc.Str'2',+          pandoc.Str'3'+        },+        pandoc.Str'4'+      }+      pandoc.walk_inline(+        nested_nums,+        {Str = function (s) table.insert(acc, s.text) end}+      )+      assert.are_equal('1234', table.concat(acc))+    end)+  },++  group 'write' {+    test('string spec', function ()+      local doc = pandoc.Pandoc{pandoc.Quoted('DoubleQuote', 'vice versa')}+      local plain = pandoc.write(doc, 'plain+smart')+      assert.are_equal(plain, '"vice versa"\n')+    end),+    test('table format spec with extensions list', function ()+      local doc = pandoc.Pandoc{pandoc.Quoted('DoubleQuote', 'vice versa')}+      local format_spec = { format = 'plain', extensions = {'smart'}}+      local plain = pandoc.write(doc, format_spec)+      assert.are_equal(plain, '"vice versa"\n')+    end),+    test('table format spec with `enable`/`disable` diff', function ()+      local diff = {+        enable = {'smart'}+      }+      local doc = pandoc.Pandoc{pandoc.Quoted('DoubleQuote', 'vice versa')}+      local format_spec = { format = 'plain', extensions = diff}+      local plain = pandoc.write(doc, format_spec)+      assert.are_equal(plain, '"vice versa"\n')+    end),+    test('table format spec with set-like diff', function ()+      local diff = {+        smart = true,+        auto_identifiers = false+      }+      local doc = pandoc.Pandoc{pandoc.Quoted('DoubleQuote', 'vice versa')}+      local format_spec = { format = 'plain', extensions = diff}+      local plain = pandoc.write(doc, format_spec)+      assert.are_equal(plain, '"vice versa"\n')+    end),+    test('fails on invalid extension', function ()+      local doc = pandoc.Pandoc{'nope'}+      local format_spec = { format = 'plain', extensions = {'nope'}}+      assert.error_matches(+        function () pandoc.write(doc, format_spec) end,+        'The extension nope is not supported for plain'+      )+    end),+  },++  group 'Marshal' {+    group 'Inlines' {+      test('Strings are broken into words', function ()+        assert.are_equal(+          pandoc.Emph 'Nice, init?',+          pandoc.Emph{pandoc.Str 'Nice,', pandoc.Space(), pandoc.Str 'init?'}+        )+      end)+    },+    group 'Blocks' {+      test('Strings are broken into words and wrapped in Plain', function ()+        assert.are_equal(+          pandoc.Div{+            pandoc.Plain{pandoc.Str 'Nice,', pandoc.Space(), pandoc.Str 'init?'}+          },+          pandoc.Div{'Nice, init?'}+        )+      end)+    }+  }+}
+ test/lua/module/partial.test view
+ test/lua/module/tiny.epub view

binary file changed (absent → 3097 bytes)

+ test/lua/plain-to-para.lua view
@@ -0,0 +1,6 @@+return {+  { Plain = function (elem)+      return pandoc.Para(elem.content)+  end,+  }+}
+ test/lua/require-file.lua view
@@ -0,0 +1,2 @@+package.path = package.path .. ';lua/?.lua'+require 'script-name'
+ test/lua/script-name.lua view
@@ -0,0 +1,3 @@+function Para (_)+  return pandoc.Para{pandoc.Str(PANDOC_SCRIPT_FILE)}+end
+ test/lua/single-to-double-quoted.lua view
@@ -0,0 +1,10 @@+return {+  {+    Quoted = function (elem)+      if elem.quotetype == "SingleQuote" then+        elem.quotetype = "DoubleQuote"+      end+      return elem+    end,+  }+}
+ test/lua/smallcaps-title.lua view
@@ -0,0 +1,12 @@+return {+  {+    Meta = function(meta)+      -- The call to `MetaInlines` is redundant and used for testing purposes+      -- only. The explicit use of a MetaValue constructor is only useful when+      -- used with an empty table: `MetaInlines{}` is read differently than+      -- `MetaBlocks{}`.+      meta.title = pandoc.MetaInlines{pandoc.SmallCaps(meta.title)}+      return meta+    end+  }+}
+ test/lua/smart-constructors.lua view
@@ -0,0 +1,10 @@+-- Test that constructors are "smart" in that they autoconvert+-- types where sensible.+function Para (_)+  return {+    pandoc.BulletList{pandoc.Para "Hello", pandoc.Para "World"},+    pandoc.DefinitionList{{"foo", pandoc.Para "placeholder"}},+    pandoc.LineBlock{"Moin", "Welt"},+    pandoc.OrderedList{pandoc.Plain{pandoc.Str "one"}, pandoc.Plain "two"}+  }+end
+ test/lua/strmacro.lua view
@@ -0,0 +1,11 @@+return {+  {+    Str = function (elem)+      if elem.text == "{{helloworld}}" then+        return pandoc.Emph {pandoc.Str "Hello, World"}+      else+        return elem+      end+    end,+  }+}
+ test/lua/undiv.lua view
@@ -0,0 +1,3 @@+function Div(el)+  return el.content+end
+ test/lua/uppercase-header.lua view
@@ -0,0 +1,9 @@+local text = require 'text'++local function str_to_uppercase (s)+  return pandoc.Str(text.upper(s.text))+end++function Header (el)+  return pandoc.walk_block(el, {Str = str_to_uppercase})+end
+ test/sample.lua view
@@ -0,0 +1,369 @@+-- This is a sample custom writer for pandoc.  It produces output+-- that is very similar to that of pandoc's HTML writer.+-- There is one new feature: code blocks marked with class 'dot'+-- are piped through graphviz and images are included in the HTML+-- output using 'data:' URLs. The image format can be controlled+-- via the `image_format` metadata field.+--+-- Invoke with: pandoc -t sample.lua+--+-- Note:  you need not have lua installed on your system to use this+-- custom writer.  However, if you do have lua installed, you can+-- use it to test changes to the script.  'lua sample.lua' will+-- produce informative error messages if your code contains+-- syntax errors.++function Writer (doc, opts)+  PANDOC_DOCUMENT = doc+  PANDOC_WRITER_OPTIONS = opts+  loadfile(PANDOC_SCRIPT_FILE)()+  return pandoc.write_classic(doc, opts)+end++local pipe = pandoc.pipe+local stringify = (require 'pandoc.utils').stringify++-- Choose the image format based on the value of the+-- `image_format` environment variable.+local image_format = os.getenv 'image_format' or 'png'+local image_mime_type = ({+    jpeg = 'image/jpeg',+    jpg = 'image/jpeg',+    gif = 'image/gif',+    png = 'image/png',+    svg = 'image/svg+xml',+  })[image_format]+  or error('unsupported image format `' .. image_format .. '`')++-- Character escaping+local function escape(s, in_attribute)+  return s:gsub('[<>&"\']',+    function(x)+      if x == '<' then+        return '&lt;'+      elseif x == '>' then+        return '&gt;'+      elseif x == '&' then+        return '&amp;'+      elseif in_attribute and x == '"' then+        return '&quot;'+      elseif in_attribute and x == "'" then+        return '&#39;'+      else+        return x+      end+    end)+end++-- Helper function to convert an attributes table into+-- a string that can be put into HTML tags.+local function attributes(attr)+  local attr_table = {}+  for x,y in pairs(attr) do+    if y and y ~= '' then+      table.insert(attr_table, ' ' .. x .. '="' .. escape(y,true) .. '"')+    end+  end+  return table.concat(attr_table)+end++-- Table to store footnotes, so they can be included at the end.+local notes = {}++-- Blocksep is used to separate block elements.+function Blocksep()+  return '\n\n'+end++-- This function is called once for the whole document. Parameters:+-- body is a string, metadata is a table, variables is a table.+-- This gives you a fragment.  You could use the metadata table to+-- fill variables in a custom lua template.  Or, pass `--template=...`+-- to pandoc, and pandoc will do the template processing as usual.+function Doc(body, metadata, variables)+  local buffer = {}+  local function add(s)+    table.insert(buffer, s)+  end+  add(body)+  if #notes > 0 then+    add('<ol class="footnotes">')+    for _,note in pairs(notes) do+      add(note)+    end+    add('</ol>')+  end+  return table.concat(buffer,'\n') .. '\n'+end++-- The functions that follow render corresponding pandoc elements.+-- s is always a string, attr is always a table of attributes, and+-- items is always an array of strings (the items in a list).+-- Comments indicate the types of other variables.++function Str(s)+  return escape(s)+end++function Space()+  return ' '+end++function SoftBreak()+  return '\n'+end++function LineBreak()+  return '<br/>'+end++function Emph(s)+  return '<em>' .. s .. '</em>'+end++function Strong(s)+  return '<strong>' .. s .. '</strong>'+end++function Subscript(s)+  return '<sub>' .. s .. '</sub>'+end++function Superscript(s)+  return '<sup>' .. s .. '</sup>'+end++function SmallCaps(s)+  return '<span style="font-variant: small-caps;">' .. s .. '</span>'+end++function Strikeout(s)+  return '<del>' .. s .. '</del>'+end++function Link(s, tgt, tit, attr)+  return '<a href="' .. escape(tgt,true) .. '" title="' ..+         escape(tit,true) .. '"' .. attributes(attr) .. '>' .. s .. '</a>'+end++function Image(s, src, tit, attr)+  return '<img src="' .. escape(src,true) .. '" title="' ..+         escape(tit,true) .. '"/>'+end++function Code(s, attr)+  return '<code' .. attributes(attr) .. '>' .. escape(s) .. '</code>'+end++function InlineMath(s)+  return '\\(' .. escape(s) .. '\\)'+end++function DisplayMath(s)+  return '\\[' .. escape(s) .. '\\]'+end++function SingleQuoted(s)+  return '&lsquo;' .. s .. '&rsquo;'+end++function DoubleQuoted(s)+  return '&ldquo;' .. s .. '&rdquo;'+end++function Note(s)+  local num = #notes + 1+  -- insert the back reference right before the final closing tag.+  s = string.gsub(s,+          '(.*)</', '%1 <a href="#fnref' .. num ..  '">&#8617;</a></')+  -- add a list item with the note to the note table.+  table.insert(notes, '<li id="fn' .. num .. '">' .. s .. '</li>')+  -- return the footnote reference, linked to the note.+  return '<a id="fnref' .. num .. '" href="#fn' .. num ..+            '"><sup>' .. num .. '</sup></a>'+end++function Span(s, attr)+  return '<span' .. attributes(attr) .. '>' .. s .. '</span>'+end++function RawInline(format, str)+  if format == 'html' then+    return str+  else+    return ''+  end+end++function Cite(s, cs)+  local ids = {}+  for _,cit in ipairs(cs) do+    table.insert(ids, cit.citationId)+  end+  return '<span class="cite" data-citation-ids="' .. table.concat(ids, ',') ..+    '">' .. s .. '</span>'+end++function Plain(s)+  return s+end++function Para(s)+  return '<p>' .. s .. '</p>'+end++-- lev is an integer, the header level.+function Header(lev, s, attr)+  return '<h' .. lev .. attributes(attr) ..  '>' .. s .. '</h' .. lev .. '>'+end++function BlockQuote(s)+  return '<blockquote>\n' .. s .. '\n</blockquote>'+end++function HorizontalRule()+  return "<hr/>"+end++function LineBlock(ls)+  return '<div style="white-space: pre-line;">' .. table.concat(ls, '\n') ..+         '</div>'+end++function CodeBlock(s, attr)+  -- If code block has class 'dot', pipe the contents through dot+  -- and base64, and include the base64-encoded png as a data: URL.+  if attr.class and string.match(' ' .. attr.class .. ' ',' dot ') then+    local img = pipe('base64', {}, pipe('dot', {'-T' .. image_format}, s))+    return '<img src="data:' .. image_mime_type .. ';base64,' .. img .. '"/>'+  -- otherwise treat as code (one could pipe through a highlighter)+  else+    return '<pre><code' .. attributes(attr) .. '>' .. escape(s) ..+           '</code></pre>'+  end+end++function BulletList(items)+  local buffer = {}+  for _, item in pairs(items) do+    table.insert(buffer, '<li>' .. item .. '</li>')+  end+  return '<ul>\n' .. table.concat(buffer, '\n') .. '\n</ul>'+end++function OrderedList(items)+  local buffer = {}+  for _, item in pairs(items) do+    table.insert(buffer, '<li>' .. item .. '</li>')+  end+  return '<ol>\n' .. table.concat(buffer, '\n') .. '\n</ol>'+end++function DefinitionList(items)+  local buffer = {}+  for _,item in pairs(items) do+    local k, v = next(item)+    table.insert(buffer, '<dt>' .. k .. '</dt>\n<dd>' ..+                   table.concat(v, '</dd>\n<dd>') .. '</dd>')+  end+  return '<dl>\n' .. table.concat(buffer, '\n') .. '\n</dl>'+end++-- Convert pandoc alignment to something HTML can use.+-- align is AlignLeft, AlignRight, AlignCenter, or AlignDefault.+local function html_align(align)+  if align == 'AlignLeft' then+    return 'left'+  elseif align == 'AlignRight' then+    return 'right'+  elseif align == 'AlignCenter' then+    return 'center'+  else+    return 'left'+  end+end++function CaptionedImage(src, tit, caption, attr)+  if #caption == 0 then+    return '<p><img src="' .. escape(src,true) .. '" id="' .. attr.id ..+      '"/></p>'+  else+    local ecaption = escape(caption)+    return '<figure>\n<img src="' .. escape(src,true) ..+        '" id="' .. attr.id .. '" alt="' .. ecaption  .. '"/>' ..+        '<figcaption>' .. ecaption .. '</figcaption>\n</figure>'+  end+end++function Figure(caption, contents, attr)+  return '<figure' .. attributes(attr) .. '>\n' .. contents ..+    '\n<figcaption>' .. caption .. '</figcaption>\n' ..+    '</figure>'+end++-- Caption is a string, aligns is an array of strings,+-- widths is an array of floats, headers is an array of+-- strings, rows is an array of arrays of strings.+function Table(caption, aligns, widths, headers, rows)+  local buffer = {}+  local function add(s)+    table.insert(buffer, s)+  end+  add('<table>')+  if caption ~= '' then+    add('<caption>' .. escape(caption) .. '</caption>')+  end+  if widths and widths[1] ~= 0 then+    for _, w in pairs(widths) do+      add('<col width="' .. string.format('%.0f%%', w * 100) .. '" />')+    end+  end+  local header_row = {}+  local empty_header = true+  for i, h in pairs(headers) do+    local align = html_align(aligns[i])+    table.insert(header_row,'<th align="' .. align .. '">' .. h .. '</th>')+    empty_header = empty_header and h == ''+  end+  if not empty_header then+    add('<tr class="header">')+    for _,h in pairs(header_row) do+      add(h)+    end+    add('</tr>')+  end+  local class = 'even'+  for _, row in pairs(rows) do+    class = (class == 'even' and 'odd') or 'even'+    add('<tr class="' .. class .. '">')+    for i,c in pairs(row) do+      add('<td align="' .. html_align(aligns[i]) .. '">' .. c .. '</td>')+    end+    add('</tr>')+  end+  add('</table>')+  return table.concat(buffer,'\n')+end++function RawBlock(format, str)+  if format == 'html' then+    return str+  else+    return ''+  end+end++function Div(s, attr)+  return '<div' .. attributes(attr) .. '>\n' .. s .. '</div>'+end++-- The following code will produce runtime warnings when you haven't defined+-- all of the functions you need for the custom writer, so it's useful+-- to include when you're working on a writer.+local meta = {}+meta.__index =+  function(_, key)+    io.stderr:write(string.format("WARNING: Undefined function '%s'\n",key))+    return function() return '' end+  end+setmetatable(_G, meta)
+ test/tables.custom view
@@ -0,0 +1,200 @@+<p>Simple table with caption:</p>++<table>+<caption>Demonstration of simple table syntax.</caption>+<tr class="header">+<th align="right">Right</th>+<th align="left">Left</th>+<th align="center">Center</th>+<th align="left">Default</th>+</tr>+<tr class="odd">+<td align="right">12</td>+<td align="left">12</td>+<td align="center">12</td>+<td align="left">12</td>+</tr>+<tr class="even">+<td align="right">123</td>+<td align="left">123</td>+<td align="center">123</td>+<td align="left">123</td>+</tr>+<tr class="odd">+<td align="right">1</td>+<td align="left">1</td>+<td align="center">1</td>+<td align="left">1</td>+</tr>+</table>++<p>Simple table without caption:</p>++<table>+<tr class="header">+<th align="right">Right</th>+<th align="left">Left</th>+<th align="center">Center</th>+<th align="left">Default</th>+</tr>+<tr class="odd">+<td align="right">12</td>+<td align="left">12</td>+<td align="center">12</td>+<td align="left">12</td>+</tr>+<tr class="even">+<td align="right">123</td>+<td align="left">123</td>+<td align="center">123</td>+<td align="left">123</td>+</tr>+<tr class="odd">+<td align="right">1</td>+<td align="left">1</td>+<td align="center">1</td>+<td align="left">1</td>+</tr>+</table>++<p>Simple table indented two spaces:</p>++<table>+<caption>Demonstration of simple table syntax.</caption>+<tr class="header">+<th align="right">Right</th>+<th align="left">Left</th>+<th align="center">Center</th>+<th align="left">Default</th>+</tr>+<tr class="odd">+<td align="right">12</td>+<td align="left">12</td>+<td align="center">12</td>+<td align="left">12</td>+</tr>+<tr class="even">+<td align="right">123</td>+<td align="left">123</td>+<td align="center">123</td>+<td align="left">123</td>+</tr>+<tr class="odd">+<td align="right">1</td>+<td align="left">1</td>+<td align="center">1</td>+<td align="left">1</td>+</tr>+</table>++<p>Multiline table with caption:</p>++<table>+<caption>Here’s the caption.+It may span multiple lines.</caption>+<col width="15%" />+<col width="14%" />+<col width="16%" />+<col width="35%" />+<tr class="header">+<th align="center">Centered+Header</th>+<th align="left">Left+Aligned</th>+<th align="right">Right+Aligned</th>+<th align="left">Default aligned</th>+</tr>+<tr class="odd">+<td align="center">First</td>+<td align="left">row</td>+<td align="right">12.0</td>+<td align="left">Example of a row that spans+multiple lines.</td>+</tr>+<tr class="even">+<td align="center">Second</td>+<td align="left">row</td>+<td align="right">5.0</td>+<td align="left">Here’s another one. Note+the blank line between rows.</td>+</tr>+</table>++<p>Multiline table without caption:</p>++<table>+<col width="15%" />+<col width="14%" />+<col width="16%" />+<col width="35%" />+<tr class="header">+<th align="center">Centered+Header</th>+<th align="left">Left+Aligned</th>+<th align="right">Right+Aligned</th>+<th align="left">Default aligned</th>+</tr>+<tr class="odd">+<td align="center">First</td>+<td align="left">row</td>+<td align="right">12.0</td>+<td align="left">Example of a row that spans+multiple lines.</td>+</tr>+<tr class="even">+<td align="center">Second</td>+<td align="left">row</td>+<td align="right">5.0</td>+<td align="left">Here’s another one. Note+the blank line between rows.</td>+</tr>+</table>++<p>Table without column headers:</p>++<table>+<tr class="odd">+<td align="right">12</td>+<td align="left">12</td>+<td align="center">12</td>+<td align="right">12</td>+</tr>+<tr class="even">+<td align="right">123</td>+<td align="left">123</td>+<td align="center">123</td>+<td align="right">123</td>+</tr>+<tr class="odd">+<td align="right">1</td>+<td align="left">1</td>+<td align="center">1</td>+<td align="right">1</td>+</tr>+</table>++<p>Multiline table without column headers:</p>++<table>+<col width="15%" />+<col width="14%" />+<col width="16%" />+<col width="35%" />+<tr class="odd">+<td align="center">First</td>+<td align="left">row</td>+<td align="right">12.0</td>+<td align="left">Example of a row that spans+multiple lines.</td>+</tr>+<tr class="even">+<td align="center">Second</td>+<td align="left">row</td>+<td align="right">5.0</td>+<td align="left">Here’s another one. Note+the blank line between rows.</td>+</tr>+</table>
+ test/tables.native view
@@ -0,0 +1,964 @@+[ Para+    [ Str "Simple"+    , Space+    , Str "table"+    , Space+    , Str "with"+    , Space+    , Str "caption:"+    ]+, Table+    ( "" , [] , [] )+    (Caption+       Nothing+       [ Plain+           [ Str "Demonstration"+           , Space+           , Str "of"+           , Space+           , Str "simple"+           , Space+           , Str "table"+           , Space+           , Str "syntax."+           ]+       ])+    [ ( AlignRight , ColWidthDefault )+    , ( AlignLeft , ColWidthDefault )+    , ( AlignCenter , ColWidthDefault )+    , ( AlignDefault , ColWidthDefault )+    ]+    (TableHead+       ( "" , [] , [] )+       [ Row+           ( "" , [] , [] )+           [ Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Right" ] ]+           , Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Left" ] ]+           , Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Center" ] ]+           , Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Default" ] ]+           ]+       ])+    [ TableBody+        ( "" , [] , [] )+        (RowHeadColumns 0)+        []+        [ Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12" ] ]+            ]+        , Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "123" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "123" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "123" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "123" ] ]+            ]+        , Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "1" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "1" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "1" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "1" ] ]+            ]+        ]+    ]+    (TableFoot ( "" , [] , [] ) [])+, Para+    [ Str "Simple"+    , Space+    , Str "table"+    , Space+    , Str "without"+    , Space+    , Str "caption:"+    ]+, Table+    ( "" , [] , [] )+    (Caption Nothing [])+    [ ( AlignRight , ColWidthDefault )+    , ( AlignLeft , ColWidthDefault )+    , ( AlignCenter , ColWidthDefault )+    , ( AlignDefault , ColWidthDefault )+    ]+    (TableHead+       ( "" , [] , [] )+       [ Row+           ( "" , [] , [] )+           [ Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Right" ] ]+           , Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Left" ] ]+           , Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Center" ] ]+           , Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Default" ] ]+           ]+       ])+    [ TableBody+        ( "" , [] , [] )+        (RowHeadColumns 0)+        []+        [ Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12" ] ]+            ]+        , Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "123" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "123" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "123" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "123" ] ]+            ]+        , Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "1" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "1" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "1" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "1" ] ]+            ]+        ]+    ]+    (TableFoot ( "" , [] , [] ) [])+, Para+    [ Str "Simple"+    , Space+    , Str "table"+    , Space+    , Str "indented"+    , Space+    , Str "two"+    , Space+    , Str "spaces:"+    ]+, Table+    ( "" , [] , [] )+    (Caption+       Nothing+       [ Plain+           [ Str "Demonstration"+           , Space+           , Str "of"+           , Space+           , Str "simple"+           , Space+           , Str "table"+           , Space+           , Str "syntax."+           ]+       ])+    [ ( AlignRight , ColWidthDefault )+    , ( AlignLeft , ColWidthDefault )+    , ( AlignCenter , ColWidthDefault )+    , ( AlignDefault , ColWidthDefault )+    ]+    (TableHead+       ( "" , [] , [] )+       [ Row+           ( "" , [] , [] )+           [ Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Right" ] ]+           , Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Left" ] ]+           , Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Center" ] ]+           , Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Default" ] ]+           ]+       ])+    [ TableBody+        ( "" , [] , [] )+        (RowHeadColumns 0)+        []+        [ Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12" ] ]+            ]+        , Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "123" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "123" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "123" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "123" ] ]+            ]+        , Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "1" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "1" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "1" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "1" ] ]+            ]+        ]+    ]+    (TableFoot ( "" , [] , [] ) [])+, Para+    [ Str "Multiline"+    , Space+    , Str "table"+    , Space+    , Str "with"+    , Space+    , Str "caption:"+    ]+, Table+    ( "" , [] , [] )+    (Caption+       Nothing+       [ Plain+           [ Str "Here\8217s"+           , Space+           , Str "the"+           , Space+           , Str "caption."+           , SoftBreak+           , Str "It"+           , Space+           , Str "may"+           , Space+           , Str "span"+           , Space+           , Str "multiple"+           , Space+           , Str "lines."+           ]+       ])+    [ ( AlignCenter , ColWidth 0.15 )+    , ( AlignLeft , ColWidth 0.1375 )+    , ( AlignRight , ColWidth 0.1625 )+    , ( AlignLeft , ColWidth 0.35 )+    ]+    (TableHead+       ( "" , [] , [] )+       [ Row+           ( "" , [] , [] )+           [ Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Centered" , SoftBreak , Str "Header" ] ]+           , Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Left" , SoftBreak , Str "Aligned" ] ]+           , Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Right" , SoftBreak , Str "Aligned" ] ]+           , Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Default" , Space , Str "aligned" ] ]+           ]+       ])+    [ TableBody+        ( "" , [] , [] )+        (RowHeadColumns 0)+        []+        [ Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "First" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "row" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12.0" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain+                    [ Str "Example"+                    , Space+                    , Str "of"+                    , Space+                    , Str "a"+                    , Space+                    , Str "row"+                    , Space+                    , Str "that"+                    , Space+                    , Str "spans"+                    , SoftBreak+                    , Str "multiple"+                    , Space+                    , Str "lines."+                    ]+                ]+            ]+        , Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "Second" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "row" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "5.0" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain+                    [ Str "Here\8217s"+                    , Space+                    , Str "another"+                    , Space+                    , Str "one."+                    , Space+                    , Str "Note"+                    , SoftBreak+                    , Str "the"+                    , Space+                    , Str "blank"+                    , Space+                    , Str "line"+                    , Space+                    , Str "between"+                    , Space+                    , Str "rows."+                    ]+                ]+            ]+        ]+    ]+    (TableFoot ( "" , [] , [] ) [])+, Para+    [ Str "Multiline"+    , Space+    , Str "table"+    , Space+    , Str "without"+    , Space+    , Str "caption:"+    ]+, Table+    ( "" , [] , [] )+    (Caption Nothing [])+    [ ( AlignCenter , ColWidth 0.15 )+    , ( AlignLeft , ColWidth 0.1375 )+    , ( AlignRight , ColWidth 0.1625 )+    , ( AlignLeft , ColWidth 0.35 )+    ]+    (TableHead+       ( "" , [] , [] )+       [ Row+           ( "" , [] , [] )+           [ Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Centered" , SoftBreak , Str "Header" ] ]+           , Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Left" , SoftBreak , Str "Aligned" ] ]+           , Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Right" , SoftBreak , Str "Aligned" ] ]+           , Cell+               ( "" , [] , [] )+               AlignDefault+               (RowSpan 1)+               (ColSpan 1)+               [ Plain [ Str "Default" , Space , Str "aligned" ] ]+           ]+       ])+    [ TableBody+        ( "" , [] , [] )+        (RowHeadColumns 0)+        []+        [ Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "First" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "row" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12.0" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain+                    [ Str "Example"+                    , Space+                    , Str "of"+                    , Space+                    , Str "a"+                    , Space+                    , Str "row"+                    , Space+                    , Str "that"+                    , Space+                    , Str "spans"+                    , SoftBreak+                    , Str "multiple"+                    , Space+                    , Str "lines."+                    ]+                ]+            ]+        , Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "Second" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "row" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "5.0" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain+                    [ Str "Here\8217s"+                    , Space+                    , Str "another"+                    , Space+                    , Str "one."+                    , Space+                    , Str "Note"+                    , SoftBreak+                    , Str "the"+                    , Space+                    , Str "blank"+                    , Space+                    , Str "line"+                    , Space+                    , Str "between"+                    , Space+                    , Str "rows."+                    ]+                ]+            ]+        ]+    ]+    (TableFoot ( "" , [] , [] ) [])+, Para+    [ Str "Table"+    , Space+    , Str "without"+    , Space+    , Str "column"+    , Space+    , Str "headers:"+    ]+, Table+    ( "" , [] , [] )+    (Caption Nothing [])+    [ ( AlignRight , ColWidthDefault )+    , ( AlignLeft , ColWidthDefault )+    , ( AlignCenter , ColWidthDefault )+    , ( AlignRight , ColWidthDefault )+    ]+    (TableHead ( "" , [] , [] ) [])+    [ TableBody+        ( "" , [] , [] )+        (RowHeadColumns 0)+        []+        [ Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12" ] ]+            ]+        , Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "123" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "123" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "123" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "123" ] ]+            ]+        , Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "1" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "1" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "1" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "1" ] ]+            ]+        ]+    ]+    (TableFoot ( "" , [] , [] ) [])+, Para+    [ Str "Multiline"+    , Space+    , Str "table"+    , Space+    , Str "without"+    , Space+    , Str "column"+    , Space+    , Str "headers:"+    ]+, Table+    ( "" , [] , [] )+    (Caption Nothing [])+    [ ( AlignCenter , ColWidth 0.15 )+    , ( AlignLeft , ColWidth 0.1375 )+    , ( AlignRight , ColWidth 0.1625 )+    , ( AlignDefault , ColWidth 0.35 )+    ]+    (TableHead ( "" , [] , [] ) [])+    [ TableBody+        ( "" , [] , [] )+        (RowHeadColumns 0)+        []+        [ Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "First" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "row" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "12.0" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain+                    [ Str "Example"+                    , Space+                    , Str "of"+                    , Space+                    , Str "a"+                    , Space+                    , Str "row"+                    , Space+                    , Str "that"+                    , Space+                    , Str "spans"+                    , SoftBreak+                    , Str "multiple"+                    , Space+                    , Str "lines."+                    ]+                ]+            ]+        , Row+            ( "" , [] , [] )+            [ Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "Second" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "row" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain [ Str "5.0" ] ]+            , Cell+                ( "" , [] , [] )+                AlignDefault+                (RowSpan 1)+                (ColSpan 1)+                [ Plain+                    [ Str "Here\8217s"+                    , Space+                    , Str "another"+                    , Space+                    , Str "one."+                    , Space+                    , Str "Note"+                    , SoftBreak+                    , Str "the"+                    , Space+                    , Str "blank"+                    , Space+                    , Str "line"+                    , Space+                    , Str "between"+                    , Space+                    , Str "rows."+                    ]+                ]+            ]+        ]+    ]+    (TableFoot ( "" , [] , [] ) [])+]
+ test/test-pandoc-lua-engine.hs view
@@ -0,0 +1,18 @@+module Main (main) where+import Test.Tasty (TestTree, defaultMain, testGroup)+import qualified Tests.Lua+import qualified Tests.Lua.Module+import qualified Tests.Lua.Reader+import qualified Tests.Lua.Writer+import System.Directory (withCurrentDirectory)++main :: IO ()+main = withCurrentDirectory "test" $ defaultMain tests++tests :: TestTree+tests = testGroup "pandoc Lua engine"+  [ testGroup "Lua filters" Tests.Lua.tests+  , testGroup "Lua modules" Tests.Lua.Module.tests+  , testGroup "Custom writers" Tests.Lua.Writer.tests+  , testGroup "Custom readers" Tests.Lua.Reader.tests+  ]
+ test/testsuite.native view
@@ -0,0 +1,2238 @@+Pandoc+  Meta+    { unMeta =+        fromList+          [ ( "author"+            , MetaList+                [ MetaInlines [ Str "John" , Space , Str "MacFarlane" ]+                , MetaInlines [ Str "Anonymous" ]+                ]+            )+          , ( "date"+            , MetaInlines+                [ Str "July" , Space , Str "17," , Space , Str "2006" ]+            )+          , ( "title"+            , MetaInlines+                [ Str "Pandoc"+                , Space+                , Str "Test"+                , Space+                , Str "Suite"+                ]+            )+          ]+    }+  [ Para+      [ Str "This"+      , Space+      , Str "is"+      , Space+      , Str "a"+      , Space+      , Str "set"+      , Space+      , Str "of"+      , Space+      , Str "tests"+      , Space+      , Str "for"+      , Space+      , Str "pandoc."+      , Space+      , Str "Most"+      , Space+      , Str "of"+      , Space+      , Str "them"+      , Space+      , Str "are"+      , Space+      , Str "adapted"+      , Space+      , Str "from"+      , SoftBreak+      , Str "John"+      , Space+      , Str "Gruber\8217s"+      , Space+      , Str "markdown"+      , Space+      , Str "test"+      , Space+      , Str "suite."+      ]+  , HorizontalRule+  , Header 1 ( "headers" , [] , [] ) [ Str "Headers" ]+  , Header+      2+      ( "level-2-with-an-embedded-link" , [] , [] )+      [ Str "Level"+      , Space+      , Str "2"+      , Space+      , Str "with"+      , Space+      , Str "an"+      , Space+      , Link+          ( "" , [] , [] )+          [ Str "embedded" , Space , Str "link" ]+          ( "/url" , "" )+      ]+  , Header+      3+      ( "level-3-with-emphasis" , [] , [] )+      [ Str "Level"+      , Space+      , Str "3"+      , Space+      , Str "with"+      , Space+      , Emph [ Str "emphasis" ]+      ]+  , Header+      4 ( "level-4" , [] , [] ) [ Str "Level" , Space , Str "4" ]+  , Header+      5 ( "level-5" , [] , [] ) [ Str "Level" , Space , Str "5" ]+  , Header+      1 ( "level-1" , [] , [] ) [ Str "Level" , Space , Str "1" ]+  , Header+      2+      ( "level-2-with-emphasis" , [] , [] )+      [ Str "Level"+      , Space+      , Str "2"+      , Space+      , Str "with"+      , Space+      , Emph [ Str "emphasis" ]+      ]+  , Header+      3 ( "level-3" , [] , [] ) [ Str "Level" , Space , Str "3" ]+  , Para+      [ Str "with"+      , Space+      , Str "no"+      , Space+      , Str "blank"+      , Space+      , Str "line"+      ]+  , Header+      2 ( "level-2" , [] , [] ) [ Str "Level" , Space , Str "2" ]+  , Para+      [ Str "with"+      , Space+      , Str "no"+      , Space+      , Str "blank"+      , Space+      , Str "line"+      ]+  , HorizontalRule+  , Header 1 ( "paragraphs" , [] , [] ) [ Str "Paragraphs" ]+  , Para+      [ Str "Here\8217s"+      , Space+      , Str "a"+      , Space+      , Str "regular"+      , Space+      , Str "paragraph."+      ]+  , Para+      [ Str "In"+      , Space+      , Str "Markdown"+      , Space+      , Str "1.0.0"+      , Space+      , Str "and"+      , Space+      , Str "earlier."+      , Space+      , Str "Version"+      , SoftBreak+      , Str "8."+      , Space+      , Str "This"+      , Space+      , Str "line"+      , Space+      , Str "turns"+      , Space+      , Str "into"+      , Space+      , Str "a"+      , Space+      , Str "list"+      , Space+      , Str "item."+      , SoftBreak+      , Str "Because"+      , Space+      , Str "a"+      , Space+      , Str "hard-wrapped"+      , Space+      , Str "line"+      , Space+      , Str "in"+      , Space+      , Str "the"+      , SoftBreak+      , Str "middle"+      , Space+      , Str "of"+      , Space+      , Str "a"+      , Space+      , Str "paragraph"+      , Space+      , Str "looked"+      , Space+      , Str "like"+      , Space+      , Str "a"+      , SoftBreak+      , Str "list"+      , Space+      , Str "item."+      ]+  , Para+      [ Str "Here\8217s"+      , Space+      , Str "one"+      , Space+      , Str "with"+      , Space+      , Str "a"+      , Space+      , Str "bullet."+      , SoftBreak+      , Str "*"+      , Space+      , Str "criminey."+      ]+  , Para+      [ Str "There"+      , Space+      , Str "should"+      , Space+      , Str "be"+      , Space+      , Str "a"+      , Space+      , Str "hard"+      , Space+      , Str "line"+      , Space+      , Str "break"+      , LineBreak+      , Str "here."+      ]+  , HorizontalRule+  , Header+      1+      ( "block-quotes" , [] , [] )+      [ Str "Block" , Space , Str "Quotes" ]+  , Para [ Str "E-mail" , Space , Str "style:" ]+  , BlockQuote+      [ Para+          [ Str "This"+          , Space+          , Str "is"+          , Space+          , Str "a"+          , Space+          , Str "block"+          , Space+          , Str "quote."+          , SoftBreak+          , Str "It"+          , Space+          , Str "is"+          , Space+          , Str "pretty"+          , Space+          , Str "short."+          ]+      ]+  , BlockQuote+      [ Para+          [ Str "Code"+          , Space+          , Str "in"+          , Space+          , Str "a"+          , Space+          , Str "block"+          , Space+          , Str "quote:"+          ]+      , CodeBlock+          ( "" , [] , [] ) "sub status {\n    print \"working\";\n}"+      , Para [ Str "A" , Space , Str "list:" ]+      , OrderedList+          ( 1 , Decimal , Period )+          [ [ Plain [ Str "item" , Space , Str "one" ] ]+          , [ Plain [ Str "item" , Space , Str "two" ] ]+          ]+      , Para+          [ Str "Nested"+          , Space+          , Str "block"+          , Space+          , Str "quotes:"+          ]+      , BlockQuote [ Para [ Str "nested" ] ]+      , BlockQuote [ Para [ Str "nested" ] ]+      ]+  , Para+      [ Str "This"+      , Space+      , Str "should"+      , Space+      , Str "not"+      , Space+      , Str "be"+      , Space+      , Str "a"+      , Space+      , Str "block"+      , Space+      , Str "quote:"+      , Space+      , Str "2"+      , SoftBreak+      , Str ">"+      , Space+      , Str "1."+      ]+  , Para+      [ Str "And"+      , Space+      , Str "a"+      , Space+      , Str "following"+      , Space+      , Str "paragraph."+      ]+  , HorizontalRule+  , Header+      1+      ( "code-blocks" , [] , [] )+      [ Str "Code" , Space , Str "Blocks" ]+  , Para [ Str "Code:" ]+  , CodeBlock+      ( "" , [] , [] )+      "---- (should be four hyphens)\n\nsub status {\n    print \"working\";\n}\n\nthis code block is indented by one tab"+  , Para [ Str "And:" ]+  , CodeBlock+      ( "" , [] , [] )+      "    this code block is indented by two tabs\n\nThese should not be escaped:  \\$ \\\\ \\> \\[ \\{"+  , HorizontalRule+  , Header 1 ( "lists" , [] , [] ) [ Str "Lists" ]+  , Header 2 ( "unordered" , [] , [] ) [ Str "Unordered" ]+  , Para [ Str "Asterisks" , Space , Str "tight:" ]+  , BulletList+      [ [ Plain [ Str "asterisk" , Space , Str "1" ] ]+      , [ Plain [ Str "asterisk" , Space , Str "2" ] ]+      , [ Plain [ Str "asterisk" , Space , Str "3" ] ]+      ]+  , Para [ Str "Asterisks" , Space , Str "loose:" ]+  , BulletList+      [ [ Para [ Str "asterisk" , Space , Str "1" ] ]+      , [ Para [ Str "asterisk" , Space , Str "2" ] ]+      , [ Para [ Str "asterisk" , Space , Str "3" ] ]+      ]+  , Para [ Str "Pluses" , Space , Str "tight:" ]+  , BulletList+      [ [ Plain [ Str "Plus" , Space , Str "1" ] ]+      , [ Plain [ Str "Plus" , Space , Str "2" ] ]+      , [ Plain [ Str "Plus" , Space , Str "3" ] ]+      ]+  , Para [ Str "Pluses" , Space , Str "loose:" ]+  , BulletList+      [ [ Para [ Str "Plus" , Space , Str "1" ] ]+      , [ Para [ Str "Plus" , Space , Str "2" ] ]+      , [ Para [ Str "Plus" , Space , Str "3" ] ]+      ]+  , Para [ Str "Minuses" , Space , Str "tight:" ]+  , BulletList+      [ [ Plain [ Str "Minus" , Space , Str "1" ] ]+      , [ Plain [ Str "Minus" , Space , Str "2" ] ]+      , [ Plain [ Str "Minus" , Space , Str "3" ] ]+      ]+  , Para [ Str "Minuses" , Space , Str "loose:" ]+  , BulletList+      [ [ Para [ Str "Minus" , Space , Str "1" ] ]+      , [ Para [ Str "Minus" , Space , Str "2" ] ]+      , [ Para [ Str "Minus" , Space , Str "3" ] ]+      ]+  , Header 2 ( "ordered" , [] , [] ) [ Str "Ordered" ]+  , Para [ Str "Tight:" ]+  , OrderedList+      ( 1 , Decimal , Period )+      [ [ Plain [ Str "First" ] ]+      , [ Plain [ Str "Second" ] ]+      , [ Plain [ Str "Third" ] ]+      ]+  , Para [ Str "and:" ]+  , OrderedList+      ( 1 , Decimal , Period )+      [ [ Plain [ Str "One" ] ]+      , [ Plain [ Str "Two" ] ]+      , [ Plain [ Str "Three" ] ]+      ]+  , Para+      [ Str "Loose" , Space , Str "using" , Space , Str "tabs:" ]+  , OrderedList+      ( 1 , Decimal , Period )+      [ [ Para [ Str "First" ] ]+      , [ Para [ Str "Second" ] ]+      , [ Para [ Str "Third" ] ]+      ]+  , Para+      [ Str "and" , Space , Str "using" , Space , Str "spaces:" ]+  , OrderedList+      ( 1 , Decimal , Period )+      [ [ Para [ Str "One" ] ]+      , [ Para [ Str "Two" ] ]+      , [ Para [ Str "Three" ] ]+      ]+  , Para [ Str "Multiple" , Space , Str "paragraphs:" ]+  , OrderedList+      ( 1 , Decimal , Period )+      [ [ Para+            [ Str "Item"+            , Space+            , Str "1,"+            , Space+            , Str "graf"+            , Space+            , Str "one."+            ]+        , Para+            [ Str "Item"+            , Space+            , Str "1."+            , Space+            , Str "graf"+            , Space+            , Str "two."+            , Space+            , Str "The"+            , Space+            , Str "quick"+            , Space+            , Str "brown"+            , Space+            , Str "fox"+            , Space+            , Str "jumped"+            , Space+            , Str "over"+            , Space+            , Str "the"+            , Space+            , Str "lazy"+            , Space+            , Str "dog\8217s"+            , SoftBreak+            , Str "back."+            ]+        ]+      , [ Para [ Str "Item" , Space , Str "2." ] ]+      , [ Para [ Str "Item" , Space , Str "3." ] ]+      ]+  , Header 2 ( "nested" , [] , [] ) [ Str "Nested" ]+  , BulletList+      [ [ Plain [ Str "Tab" ]+        , BulletList+            [ [ Plain [ Str "Tab" ]+              , BulletList [ [ Plain [ Str "Tab" ] ] ]+              ]+            ]+        ]+      ]+  , Para [ Str "Here\8217s" , Space , Str "another:" ]+  , OrderedList+      ( 1 , Decimal , Period )+      [ [ Plain [ Str "First" ] ]+      , [ Plain [ Str "Second:" ]+        , BulletList+            [ [ Plain [ Str "Fee" ] ]+            , [ Plain [ Str "Fie" ] ]+            , [ Plain [ Str "Foe" ] ]+            ]+        ]+      , [ Plain [ Str "Third" ] ]+      ]+  , Para+      [ Str "Same"+      , Space+      , Str "thing"+      , Space+      , Str "but"+      , Space+      , Str "with"+      , Space+      , Str "paragraphs:"+      ]+  , OrderedList+      ( 1 , Decimal , Period )+      [ [ Para [ Str "First" ] ]+      , [ Para [ Str "Second:" ]+        , BulletList+            [ [ Plain [ Str "Fee" ] ]+            , [ Plain [ Str "Fie" ] ]+            , [ Plain [ Str "Foe" ] ]+            ]+        ]+      , [ Para [ Str "Third" ] ]+      ]+  , Header+      2+      ( "tabs-and-spaces" , [] , [] )+      [ Str "Tabs" , Space , Str "and" , Space , Str "spaces" ]+  , BulletList+      [ [ Para+            [ Str "this"+            , Space+            , Str "is"+            , Space+            , Str "a"+            , Space+            , Str "list"+            , Space+            , Str "item"+            , SoftBreak+            , Str "indented"+            , Space+            , Str "with"+            , Space+            , Str "tabs"+            ]+        ]+      , [ Para+            [ Str "this"+            , Space+            , Str "is"+            , Space+            , Str "a"+            , Space+            , Str "list"+            , Space+            , Str "item"+            , SoftBreak+            , Str "indented"+            , Space+            , Str "with"+            , Space+            , Str "spaces"+            ]+        , BulletList+            [ [ Para+                  [ Str "this"+                  , Space+                  , Str "is"+                  , Space+                  , Str "an"+                  , Space+                  , Str "example"+                  , Space+                  , Str "list"+                  , Space+                  , Str "item"+                  , SoftBreak+                  , Str "indented"+                  , Space+                  , Str "with"+                  , Space+                  , Str "tabs"+                  ]+              ]+            , [ Para+                  [ Str "this"+                  , Space+                  , Str "is"+                  , Space+                  , Str "an"+                  , Space+                  , Str "example"+                  , Space+                  , Str "list"+                  , Space+                  , Str "item"+                  , SoftBreak+                  , Str "indented"+                  , Space+                  , Str "with"+                  , Space+                  , Str "spaces"+                  ]+              ]+            ]+        ]+      ]+  , Header+      2+      ( "fancy-list-markers" , [] , [] )+      [ Str "Fancy" , Space , Str "list" , Space , Str "markers" ]+  , OrderedList+      ( 2 , Decimal , TwoParens )+      [ [ Para+            [ Str "begins" , Space , Str "with" , Space , Str "2" ]+        ]+      , [ Para [ Str "and" , Space , Str "now" , Space , Str "3" ]+        , Para+            [ Str "with"+            , Space+            , Str "a"+            , Space+            , Str "continuation"+            ]+        , OrderedList+            ( 4 , LowerRoman , Period )+            [ [ Plain+                  [ Str "sublist"+                  , Space+                  , Str "with"+                  , Space+                  , Str "roman"+                  , Space+                  , Str "numerals,"+                  , SoftBreak+                  , Str "starting"+                  , Space+                  , Str "with"+                  , Space+                  , Str "4"+                  ]+              ]+            , [ Plain [ Str "more" , Space , Str "items" ]+              , OrderedList+                  ( 1 , UpperAlpha , TwoParens )+                  [ [ Plain [ Str "a" , Space , Str "subsublist" ] ]+                  , [ Plain [ Str "a" , Space , Str "subsublist" ] ]+                  ]+              ]+            ]+        ]+      ]+  , Para [ Str "Nesting:" ]+  , OrderedList+      ( 1 , UpperAlpha , Period )+      [ [ Plain [ Str "Upper" , Space , Str "Alpha" ]+        , OrderedList+            ( 1 , UpperRoman , Period )+            [ [ Plain [ Str "Upper" , Space , Str "Roman." ]+              , OrderedList+                  ( 6 , Decimal , TwoParens )+                  [ [ Plain+                        [ Str "Decimal"+                        , Space+                        , Str "start"+                        , Space+                        , Str "with"+                        , Space+                        , Str "6"+                        ]+                    , OrderedList+                        ( 3 , LowerAlpha , OneParen )+                        [ [ Plain+                              [ Str "Lower"+                              , Space+                              , Str "alpha"+                              , Space+                              , Str "with"+                              , Space+                              , Str "paren"+                              ]+                          ]+                        ]+                    ]+                  ]+              ]+            ]+        ]+      ]+  , Para [ Str "Autonumbering:" ]+  , OrderedList+      ( 1 , DefaultStyle , DefaultDelim )+      [ [ Plain [ Str "Autonumber." ] ]+      , [ Plain [ Str "More." ]+        , OrderedList+            ( 1 , DefaultStyle , DefaultDelim )+            [ [ Plain [ Str "Nested." ] ] ]+        ]+      ]+  , Para+      [ Str "Should"+      , Space+      , Str "not"+      , Space+      , Str "be"+      , Space+      , Str "a"+      , Space+      , Str "list"+      , Space+      , Str "item:"+      ]+  , Para [ Str "M.A.\160\&2007" ]+  , Para [ Str "B." , Space , Str "Williams" ]+  , HorizontalRule+  , Header+      1+      ( "definition-lists" , [] , [] )+      [ Str "Definition" , Space , Str "Lists" ]+  , Para+      [ Str "Tight"+      , Space+      , Str "using"+      , Space+      , Str "spaces:"+      ]+  , DefinitionList+      [ ( [ Str "apple" ]+        , [ [ Plain [ Str "red" , Space , Str "fruit" ] ] ]+        )+      , ( [ Str "orange" ]+        , [ [ Plain [ Str "orange" , Space , Str "fruit" ] ] ]+        )+      , ( [ Str "banana" ]+        , [ [ Plain [ Str "yellow" , Space , Str "fruit" ] ] ]+        )+      ]+  , Para+      [ Str "Tight" , Space , Str "using" , Space , Str "tabs:" ]+  , DefinitionList+      [ ( [ Str "apple" ]+        , [ [ Plain [ Str "red" , Space , Str "fruit" ] ] ]+        )+      , ( [ Str "orange" ]+        , [ [ Plain [ Str "orange" , Space , Str "fruit" ] ] ]+        )+      , ( [ Str "banana" ]+        , [ [ Plain [ Str "yellow" , Space , Str "fruit" ] ] ]+        )+      ]+  , Para [ Str "Loose:" ]+  , DefinitionList+      [ ( [ Str "apple" ]+        , [ [ Para [ Str "red" , Space , Str "fruit" ] ] ]+        )+      , ( [ Str "orange" ]+        , [ [ Para [ Str "orange" , Space , Str "fruit" ] ] ]+        )+      , ( [ Str "banana" ]+        , [ [ Para [ Str "yellow" , Space , Str "fruit" ] ] ]+        )+      ]+  , Para+      [ Str "Multiple"+      , Space+      , Str "blocks"+      , Space+      , Str "with"+      , Space+      , Str "italics:"+      ]+  , DefinitionList+      [ ( [ Emph [ Str "apple" ] ]+        , [ [ Para [ Str "red" , Space , Str "fruit" ]+            , Para+                [ Str "contains"+                , Space+                , Str "seeds,"+                , SoftBreak+                , Str "crisp,"+                , Space+                , Str "pleasant"+                , Space+                , Str "to"+                , Space+                , Str "taste"+                ]+            ]+          ]+        )+      , ( [ Emph [ Str "orange" ] ]+        , [ [ Para [ Str "orange" , Space , Str "fruit" ]+            , CodeBlock ( "" , [] , [] ) "{ orange code block }"+            , BlockQuote+                [ Para+                    [ Str "orange"+                    , Space+                    , Str "block"+                    , Space+                    , Str "quote"+                    ]+                ]+            ]+          ]+        )+      ]+  , Para+      [ Str "Multiple"+      , Space+      , Str "definitions,"+      , Space+      , Str "tight:"+      ]+  , DefinitionList+      [ ( [ Str "apple" ]+        , [ [ Plain [ Str "red" , Space , Str "fruit" ] ]+          , [ Plain [ Str "computer" ] ]+          ]+        )+      , ( [ Str "orange" ]+        , [ [ Plain [ Str "orange" , Space , Str "fruit" ] ]+          , [ Plain [ Str "bank" ] ]+          ]+        )+      ]+  , Para+      [ Str "Multiple"+      , Space+      , Str "definitions,"+      , Space+      , Str "loose:"+      ]+  , DefinitionList+      [ ( [ Str "apple" ]+        , [ [ Para [ Str "red" , Space , Str "fruit" ] ]+          , [ Para [ Str "computer" ] ]+          ]+        )+      , ( [ Str "orange" ]+        , [ [ Para [ Str "orange" , Space , Str "fruit" ] ]+          , [ Para [ Str "bank" ] ]+          ]+        )+      ]+  , Para+      [ Str "Blank"+      , Space+      , Str "line"+      , Space+      , Str "after"+      , Space+      , Str "term,"+      , Space+      , Str "indented"+      , Space+      , Str "marker,"+      , Space+      , Str "alternate"+      , Space+      , Str "markers:"+      ]+  , DefinitionList+      [ ( [ Str "apple" ]+        , [ [ Para [ Str "red" , Space , Str "fruit" ] ]+          , [ Para [ Str "computer" ] ]+          ]+        )+      , ( [ Str "orange" ]+        , [ [ Para [ Str "orange" , Space , Str "fruit" ]+            , OrderedList+                ( 1 , Decimal , Period )+                [ [ Plain [ Str "sublist" ] ]+                , [ Plain [ Str "sublist" ] ]+                ]+            ]+          ]+        )+      ]+  , Header+      1+      ( "html-blocks" , [] , [] )+      [ Str "HTML" , Space , Str "Blocks" ]+  , Para+      [ Str "Simple"+      , Space+      , Str "block"+      , Space+      , Str "on"+      , Space+      , Str "one"+      , Space+      , Str "line:"+      ]+  , Div ( "" , [] , [] ) [ Plain [ Str "foo" ] ]+  , Para+      [ Str "And"+      , Space+      , Str "nested"+      , Space+      , Str "without"+      , Space+      , Str "indentation:"+      ]+  , Div+      ( "" , [] , [] )+      [ Div+          ( "" , [] , [] )+          [ Div ( "" , [] , [] ) [ Para [ Str "foo" ] ] ]+      , Div ( "" , [] , [] ) [ Plain [ Str "bar" ] ]+      ]+  , Para+      [ Str "Interpreted"+      , Space+      , Str "markdown"+      , Space+      , Str "in"+      , Space+      , Str "a"+      , Space+      , Str "table:"+      ]+  , RawBlock (Format "html") "<table>"+  , RawBlock (Format "html") "<tr>"+  , RawBlock (Format "html") "<td>"+  , Plain+      [ Str "This"+      , Space+      , Str "is"+      , Space+      , Emph [ Str "emphasized" ]+      ]+  , RawBlock (Format "html") "</td>"+  , RawBlock (Format "html") "<td>"+  , Plain+      [ Str "And"+      , Space+      , Str "this"+      , Space+      , Str "is"+      , Space+      , Strong [ Str "strong" ]+      ]+  , RawBlock (Format "html") "</td>"+  , RawBlock (Format "html") "</tr>"+  , RawBlock (Format "html") "</table>"+  , RawBlock+      (Format "html")+      "<script type=\"text/javascript\">document.write('This *should not* be interpreted as markdown');</script>"+  , Para+      [ Str "Here\8217s"+      , Space+      , Str "a"+      , Space+      , Str "simple"+      , Space+      , Str "block:"+      ]+  , Div ( "" , [] , [] ) [ Para [ Str "foo" ] ]+  , Para+      [ Str "This"+      , Space+      , Str "should"+      , Space+      , Str "be"+      , Space+      , Str "a"+      , Space+      , Str "code"+      , Space+      , Str "block,"+      , Space+      , Str "though:"+      ]+  , CodeBlock ( "" , [] , [] ) "<div>\n    foo\n</div>"+  , Para+      [ Str "As" , Space , Str "should" , Space , Str "this:" ]+  , CodeBlock ( "" , [] , [] ) "<div>foo</div>"+  , Para [ Str "Now," , Space , Str "nested:" ]+  , Div+      ( "" , [] , [] )+      [ Div+          ( "" , [] , [] )+          [ Div ( "" , [] , [] ) [ Plain [ Str "foo" ] ] ]+      ]+  , Para+      [ Str "This"+      , Space+      , Str "should"+      , Space+      , Str "just"+      , Space+      , Str "be"+      , Space+      , Str "an"+      , Space+      , Str "HTML"+      , Space+      , Str "comment:"+      ]+  , RawBlock (Format "html") "<!-- Comment -->"+  , Para [ Str "Multiline:" ]+  , RawBlock (Format "html") "<!--\nBlah\nBlah\n-->"+  , RawBlock+      (Format "html") "<!--\n    This is another comment.\n-->"+  , Para [ Str "Code" , Space , Str "block:" ]+  , CodeBlock ( "" , [] , [] ) "<!-- Comment -->"+  , Para+      [ Str "Just"+      , Space+      , Str "plain"+      , Space+      , Str "comment,"+      , Space+      , Str "with"+      , Space+      , Str "trailing"+      , Space+      , Str "spaces"+      , Space+      , Str "on"+      , Space+      , Str "the"+      , Space+      , Str "line:"+      ]+  , RawBlock (Format "html") "<!-- foo -->"+  , Para [ Str "Code:" ]+  , CodeBlock ( "" , [] , [] ) "<hr />"+  , Para [ Str "Hr\8217s:" ]+  , RawBlock (Format "html") "<hr>"+  , RawBlock (Format "html") "<hr />"+  , RawBlock (Format "html") "<hr />"+  , RawBlock (Format "html") "<hr>"+  , RawBlock (Format "html") "<hr />"+  , RawBlock (Format "html") "<hr />"+  , RawBlock (Format "html") "<hr class=\"foo\" id=\"bar\" />"+  , RawBlock (Format "html") "<hr class=\"foo\" id=\"bar\" />"+  , RawBlock (Format "html") "<hr class=\"foo\" id=\"bar\">"+  , HorizontalRule+  , Header+      1+      ( "inline-markup" , [] , [] )+      [ Str "Inline" , Space , Str "Markup" ]+  , Para+      [ Str "This"+      , Space+      , Str "is"+      , Space+      , Emph [ Str "emphasized" ]+      , Str ","+      , Space+      , Str "and"+      , Space+      , Str "so"+      , Space+      , Emph [ Str "is" , Space , Str "this" ]+      , Str "."+      ]+  , Para+      [ Str "This"+      , Space+      , Str "is"+      , Space+      , Strong [ Str "strong" ]+      , Str ","+      , Space+      , Str "and"+      , Space+      , Str "so"+      , Space+      , Strong [ Str "is" , Space , Str "this" ]+      , Str "."+      ]+  , Para+      [ Str "An"+      , Space+      , Emph+          [ Link+              ( "" , [] , [] )+              [ Str "emphasized" , Space , Str "link" ]+              ( "/url" , "" )+          ]+      , Str "."+      ]+  , Para+      [ Strong+          [ Emph+              [ Str "This"+              , Space+              , Str "is"+              , Space+              , Str "strong"+              , Space+              , Str "and"+              , Space+              , Str "em."+              ]+          ]+      ]+  , Para+      [ Str "So"+      , Space+      , Str "is"+      , Space+      , Strong [ Emph [ Str "this" ] ]+      , Space+      , Str "word."+      ]+  , Para+      [ Strong+          [ Emph+              [ Str "This"+              , Space+              , Str "is"+              , Space+              , Str "strong"+              , Space+              , Str "and"+              , Space+              , Str "em."+              ]+          ]+      ]+  , Para+      [ Str "So"+      , Space+      , Str "is"+      , Space+      , Strong [ Emph [ Str "this" ] ]+      , Space+      , Str "word."+      ]+  , Para+      [ Str "This"+      , Space+      , Str "is"+      , Space+      , Str "code:"+      , Space+      , Code ( "" , [] , [] ) ">"+      , Str ","+      , Space+      , Code ( "" , [] , [] ) "$"+      , Str ","+      , Space+      , Code ( "" , [] , [] ) "\\"+      , Str ","+      , Space+      , Code ( "" , [] , [] ) "\\$"+      , Str ","+      , Space+      , Code ( "" , [] , [] ) "<html>"+      , Str "."+      ]+  , Para+      [ Strikeout+          [ Str "This"+          , Space+          , Str "is"+          , Space+          , Emph [ Str "strikeout" ]+          , Str "."+          ]+      ]+  , Para+      [ Str "Superscripts:"+      , Space+      , Str "a"+      , Superscript [ Str "bc" ]+      , Str "d"+      , Space+      , Str "a"+      , Superscript [ Emph [ Str "hello" ] ]+      , Space+      , Str "a"+      , Superscript [ Str "hello\160there" ]+      , Str "."+      ]+  , Para+      [ Str "Subscripts:"+      , Space+      , Str "H"+      , Subscript [ Str "2" ]+      , Str "O,"+      , Space+      , Str "H"+      , Subscript [ Str "23" ]+      , Str "O,"+      , Space+      , Str "H"+      , Subscript [ Str "many\160of\160them" ]+      , Str "O."+      ]+  , Para+      [ Str "These"+      , Space+      , Str "should"+      , Space+      , Str "not"+      , Space+      , Str "be"+      , Space+      , Str "superscripts"+      , Space+      , Str "or"+      , Space+      , Str "subscripts,"+      , SoftBreak+      , Str "because"+      , Space+      , Str "of"+      , Space+      , Str "the"+      , Space+      , Str "unescaped"+      , Space+      , Str "spaces:"+      , Space+      , Str "a^b"+      , Space+      , Str "c^d,"+      , Space+      , Str "a~b"+      , Space+      , Str "c~d."+      ]+  , HorizontalRule+  , Header+      1+      ( "smart-quotes-ellipses-dashes" , [] , [] )+      [ Str "Smart"+      , Space+      , Str "quotes,"+      , Space+      , Str "ellipses,"+      , Space+      , Str "dashes"+      ]+  , Para+      [ Quoted DoubleQuote [ Str "Hello," ]+      , Space+      , Str "said"+      , Space+      , Str "the"+      , Space+      , Str "spider."+      , Space+      , Quoted+          DoubleQuote+          [ Quoted SingleQuote [ Str "Shelob" ]+          , Space+          , Str "is"+          , Space+          , Str "my"+          , Space+          , Str "name."+          ]+      ]+  , Para+      [ Quoted SingleQuote [ Str "A" ]+      , Str ","+      , Space+      , Quoted SingleQuote [ Str "B" ]+      , Str ","+      , Space+      , Str "and"+      , Space+      , Quoted SingleQuote [ Str "C" ]+      , Space+      , Str "are"+      , Space+      , Str "letters."+      ]+  , Para+      [ Quoted SingleQuote [ Str "Oak," ]+      , Space+      , Quoted SingleQuote [ Str "elm," ]+      , Space+      , Str "and"+      , Space+      , Quoted SingleQuote [ Str "beech" ]+      , Space+      , Str "are"+      , Space+      , Str "names"+      , Space+      , Str "of"+      , Space+      , Str "trees."+      , SoftBreak+      , Str "So"+      , Space+      , Str "is"+      , Space+      , Quoted SingleQuote [ Str "pine." ]+      ]+  , Para+      [ Quoted+          SingleQuote+          [ Str "He"+          , Space+          , Str "said,"+          , Space+          , Quoted+              DoubleQuote+              [ Str "I"+              , Space+              , Str "want"+              , Space+              , Str "to"+              , Space+              , Str "go."+              ]+          ]+      , Space+      , Str "Were"+      , Space+      , Str "you"+      , Space+      , Str "alive"+      , Space+      , Str "in"+      , Space+      , Str "the"+      , SoftBreak+      , Str "70\8217s?"+      ]+  , Para+      [ Str "Here"+      , Space+      , Str "is"+      , Space+      , Str "some"+      , Space+      , Str "quoted"+      , Space+      , Quoted SingleQuote [ Code ( "" , [] , [] ) "code" ]+      , Space+      , Str "and"+      , Space+      , Str "a"+      , Space+      , Quoted+          DoubleQuote+          [ Link+              ( "" , [] , [] )+              [ Str "quoted" , Space , Str "link" ]+              ( "http://example.com/?foo=1&bar=2" , "" )+          ]+      , Str "."+      ]+  , Para+      [ Str "Some"+      , Space+      , Str "dashes:"+      , Space+      , Str "one\8212two"+      , Space+      , Str "\8212"+      , Space+      , Str "three\8212four"+      , Space+      , Str "\8212"+      , Space+      , Str "five."+      ]+  , Para+      [ Str "Dashes"+      , Space+      , Str "between"+      , Space+      , Str "numbers:"+      , Space+      , Str "5\8211\&7,"+      , Space+      , Str "255\8211\&66,"+      , Space+      , Str "1987\8211\&1999."+      ]+  , Para [ Str "Ellipses\8230and\8230and\8230." ]+  , HorizontalRule+  , Header 1 ( "latex" , [] , [] ) [ Str "LaTeX" ]+  , BulletList+      [ [ Plain+            [ RawInline (Format "tex") "\\cite[22-23]{smith.1899}" ]+        ]+      , [ Plain [ Math InlineMath "2+2=4" ] ]+      , [ Plain [ Math InlineMath "x \\in y" ] ]+      , [ Plain [ Math InlineMath "\\alpha \\wedge \\omega" ] ]+      , [ Plain [ Math InlineMath "223" ] ]+      , [ Plain [ Math InlineMath "p" , Str "-Tree" ] ]+      , [ Plain+            [ Str "Here\8217s"+            , Space+            , Str "some"+            , Space+            , Str "display"+            , Space+            , Str "math:"+            , SoftBreak+            , Math+                DisplayMath+                "\\frac{d}{dx}f(x)=\\lim_{h\\to 0}\\frac{f(x+h)-f(x)}{h}"+            ]+        ]+      , [ Plain+            [ Str "Here\8217s"+            , Space+            , Str "one"+            , Space+            , Str "that"+            , Space+            , Str "has"+            , Space+            , Str "a"+            , Space+            , Str "line"+            , Space+            , Str "break"+            , Space+            , Str "in"+            , Space+            , Str "it:"+            , Space+            , Math InlineMath "\\alpha + \\omega \\times x^2"+            , Str "."+            ]+        ]+      ]+  , Para+      [ Str "These"+      , Space+      , Str "shouldn\8217t"+      , Space+      , Str "be"+      , Space+      , Str "math:"+      ]+  , BulletList+      [ [ Plain+            [ Str "To"+            , Space+            , Str "get"+            , Space+            , Str "the"+            , Space+            , Str "famous"+            , Space+            , Str "equation,"+            , Space+            , Str "write"+            , Space+            , Code ( "" , [] , [] ) "$e = mc^2$"+            , Str "."+            ]+        ]+      , [ Plain+            [ Str "$22,000"+            , Space+            , Str "is"+            , Space+            , Str "a"+            , Space+            , Emph [ Str "lot" ]+            , Space+            , Str "of"+            , Space+            , Str "money."+            , Space+            , Str "So"+            , Space+            , Str "is"+            , Space+            , Str "$34,000."+            , SoftBreak+            , Str "(It"+            , Space+            , Str "worked"+            , Space+            , Str "if"+            , Space+            , Quoted DoubleQuote [ Str "lot" ]+            , Space+            , Str "is"+            , Space+            , Str "emphasized.)"+            ]+        ]+      , [ Plain+            [ Str "Shoes"+            , Space+            , Str "($20)"+            , Space+            , Str "and"+            , Space+            , Str "socks"+            , Space+            , Str "($5)."+            ]+        ]+      , [ Plain+            [ Str "Escaped"+            , Space+            , Code ( "" , [] , [] ) "$"+            , Str ":"+            , Space+            , Str "$73"+            , Space+            , Emph+                [ Str "this"+                , Space+                , Str "should"+                , Space+                , Str "be"+                , Space+                , Str "emphasized"+                ]+            , Space+            , Str "23$."+            ]+        ]+      ]+  , Para+      [ Str "Here\8217s"+      , Space+      , Str "a"+      , Space+      , Str "LaTeX"+      , Space+      , Str "table:"+      ]+  , RawBlock+      (Format "tex")+      "\\begin{tabular}{|l|l|}\\hline\nAnimal & Number \\\\ \\hline\nDog    & 2      \\\\\nCat    & 1      \\\\ \\hline\n\\end{tabular}"+  , HorizontalRule+  , Header+      1+      ( "special-characters" , [] , [] )+      [ Str "Special" , Space , Str "Characters" ]+  , Para+      [ Str "Here"+      , Space+      , Str "is"+      , Space+      , Str "some"+      , Space+      , Str "unicode:"+      ]+  , BulletList+      [ [ Plain+            [ Str "I" , Space , Str "hat:" , Space , Str "\206" ]+        ]+      , [ Plain+            [ Str "o" , Space , Str "umlaut:" , Space , Str "\246" ]+        ]+      , [ Plain [ Str "section:" , Space , Str "\167" ] ]+      , [ Plain+            [ Str "set"+            , Space+            , Str "membership:"+            , Space+            , Str "\8712"+            ]+        ]+      , [ Plain [ Str "copyright:" , Space , Str "\169" ] ]+      ]+  , Para+      [ Str "AT&T"+      , Space+      , Str "has"+      , Space+      , Str "an"+      , Space+      , Str "ampersand"+      , Space+      , Str "in"+      , Space+      , Str "their"+      , Space+      , Str "name."+      ]+  , Para+      [ Str "AT&T"+      , Space+      , Str "is"+      , Space+      , Str "another"+      , Space+      , Str "way"+      , Space+      , Str "to"+      , Space+      , Str "write"+      , Space+      , Str "it."+      ]+  , Para+      [ Str "This" , Space , Str "&" , Space , Str "that." ]+  , Para [ Str "4" , Space , Str "<" , Space , Str "5." ]+  , Para [ Str "6" , Space , Str ">" , Space , Str "5." ]+  , Para [ Str "Backslash:" , Space , Str "\\" ]+  , Para [ Str "Backtick:" , Space , Str "`" ]+  , Para [ Str "Asterisk:" , Space , Str "*" ]+  , Para [ Str "Underscore:" , Space , Str "_" ]+  , Para+      [ Str "Left" , Space , Str "brace:" , Space , Str "{" ]+  , Para+      [ Str "Right" , Space , Str "brace:" , Space , Str "}" ]+  , Para+      [ Str "Left" , Space , Str "bracket:" , Space , Str "[" ]+  , Para+      [ Str "Right" , Space , Str "bracket:" , Space , Str "]" ]+  , Para+      [ Str "Left" , Space , Str "paren:" , Space , Str "(" ]+  , Para+      [ Str "Right" , Space , Str "paren:" , Space , Str ")" ]+  , Para [ Str "Greater-than:" , Space , Str ">" ]+  , Para [ Str "Hash:" , Space , Str "#" ]+  , Para [ Str "Period:" , Space , Str "." ]+  , Para [ Str "Bang:" , Space , Str "!" ]+  , Para [ Str "Plus:" , Space , Str "+" ]+  , Para [ Str "Minus:" , Space , Str "-" ]+  , HorizontalRule+  , Header 1 ( "links" , [] , [] ) [ Str "Links" ]+  , Header 2 ( "explicit" , [] , [] ) [ Str "Explicit" ]+  , Para+      [ Str "Just"+      , Space+      , Str "a"+      , Space+      , Link ( "" , [] , [] ) [ Str "URL" ] ( "/url/" , "" )+      , Str "."+      ]+  , Para+      [ Link+          ( "" , [] , [] )+          [ Str "URL" , Space , Str "and" , Space , Str "title" ]+          ( "/url/" , "title" )+      , Str "."+      ]+  , Para+      [ Link+          ( "" , [] , [] )+          [ Str "URL" , Space , Str "and" , Space , Str "title" ]+          ( "/url/" , "title preceded by two spaces" )+      , Str "."+      ]+  , Para+      [ Link+          ( "" , [] , [] )+          [ Str "URL" , Space , Str "and" , Space , Str "title" ]+          ( "/url/" , "title preceded by a tab" )+      , Str "."+      ]+  , Para+      [ Link+          ( "" , [] , [] )+          [ Str "URL" , Space , Str "and" , Space , Str "title" ]+          ( "/url/" , "title with \"quotes\" in it" )+      ]+  , Para+      [ Link+          ( "" , [] , [] )+          [ Str "URL" , Space , Str "and" , Space , Str "title" ]+          ( "/url/" , "title with single quotes" )+      ]+  , Para+      [ Link+          ( "" , [] , [] )+          [ Str "with_underscore" ]+          ( "/url/with_underscore" , "" )+      ]+  , Para+      [ Link+          ( "" , [] , [] )+          [ Str "Email" , Space , Str "link" ]+          ( "mailto:nobody@nowhere.net" , "" )+      ]+  , Para+      [ Link ( "" , [] , [] ) [ Str "Empty" ] ( "" , "" )+      , Str "."+      ]+  , Header 2 ( "reference" , [] , [] ) [ Str "Reference" ]+  , Para+      [ Str "Foo"+      , Space+      , Link ( "" , [] , [] ) [ Str "bar" ] ( "/url/" , "" )+      , Str "."+      ]+  , Para+      [ Str "With"+      , Space+      , Link+          ( "" , [] , [] )+          [ Str "embedded" , Space , Str "[brackets]" ]+          ( "/url/" , "" )+      , Str "."+      ]+  , Para+      [ Link ( "" , [] , [] ) [ Str "b" ] ( "/url/" , "" )+      , Space+      , Str "by"+      , Space+      , Str "itself"+      , Space+      , Str "should"+      , Space+      , Str "be"+      , Space+      , Str "a"+      , Space+      , Str "link."+      ]+  , Para+      [ Str "Indented"+      , Space+      , Link ( "" , [] , [] ) [ Str "once" ] ( "/url" , "" )+      , Str "."+      ]+  , Para+      [ Str "Indented"+      , Space+      , Link ( "" , [] , [] ) [ Str "twice" ] ( "/url" , "" )+      , Str "."+      ]+  , Para+      [ Str "Indented"+      , Space+      , Link ( "" , [] , [] ) [ Str "thrice" ] ( "/url" , "" )+      , Str "."+      ]+  , Para+      [ Str "This"+      , Space+      , Str "should"+      , Space+      , Str "[not][]"+      , Space+      , Str "be"+      , Space+      , Str "a"+      , Space+      , Str "link."+      ]+  , CodeBlock ( "" , [] , [] ) "[not]: /url"+  , Para+      [ Str "Foo"+      , Space+      , Link+          ( "" , [] , [] )+          [ Str "bar" ]+          ( "/url/" , "Title with \"quotes\" inside" )+      , Str "."+      ]+  , Para+      [ Str "Foo"+      , Space+      , Link+          ( "" , [] , [] )+          [ Str "biz" ]+          ( "/url/" , "Title with \"quote\" inside" )+      , Str "."+      ]+  , Header+      2+      ( "with-ampersands" , [] , [] )+      [ Str "With" , Space , Str "ampersands" ]+  , Para+      [ Str "Here\8217s"+      , Space+      , Str "a"+      , Space+      , Link+          ( "" , [] , [] )+          [ Str "link"+          , Space+          , Str "with"+          , Space+          , Str "an"+          , Space+          , Str "ampersand"+          , Space+          , Str "in"+          , Space+          , Str "the"+          , Space+          , Str "URL"+          ]+          ( "http://example.com/?foo=1&bar=2" , "" )+      , Str "."+      ]+  , Para+      [ Str "Here\8217s"+      , Space+      , Str "a"+      , Space+      , Str "link"+      , Space+      , Str "with"+      , Space+      , Str "an"+      , Space+      , Str "amersand"+      , Space+      , Str "in"+      , Space+      , Str "the"+      , Space+      , Str "link"+      , Space+      , Str "text:"+      , Space+      , Link+          ( "" , [] , [] )+          [ Str "AT&T" ]+          ( "http://att.com/" , "AT&T" )+      , Str "."+      ]+  , Para+      [ Str "Here\8217s"+      , Space+      , Str "an"+      , Space+      , Link+          ( "" , [] , [] )+          [ Str "inline" , Space , Str "link" ]+          ( "/script?foo=1&bar=2" , "" )+      , Str "."+      ]+  , Para+      [ Str "Here\8217s"+      , Space+      , Str "an"+      , Space+      , Link+          ( "" , [] , [] )+          [ Str "inline"+          , Space+          , Str "link"+          , Space+          , Str "in"+          , Space+          , Str "pointy"+          , Space+          , Str "braces"+          ]+          ( "/script?foo=1&bar=2" , "" )+      , Str "."+      ]+  , Header 2 ( "autolinks" , [] , [] ) [ Str "Autolinks" ]+  , Para+      [ Str "With"+      , Space+      , Str "an"+      , Space+      , Str "ampersand:"+      , Space+      , Link+          ( "" , [ "uri" ] , [] )+          [ Str "http://example.com/?foo=1&bar=2" ]+          ( "http://example.com/?foo=1&bar=2" , "" )+      ]+  , BulletList+      [ [ Plain+            [ Str "In" , Space , Str "a" , Space , Str "list?" ]+        ]+      , [ Plain+            [ Link+                ( "" , [ "uri" ] , [] )+                [ Str "http://example.com/" ]+                ( "http://example.com/" , "" )+            ]+        ]+      , [ Plain [ Str "It" , Space , Str "should." ] ]+      ]+  , Para+      [ Str "An"+      , Space+      , Str "e-mail"+      , Space+      , Str "address:"+      , Space+      , Link+          ( "" , [ "email" ] , [] )+          [ Str "nobody@nowhere.net" ]+          ( "mailto:nobody@nowhere.net" , "" )+      ]+  , BlockQuote+      [ Para+          [ Str "Blockquoted:"+          , Space+          , Link+              ( "" , [ "uri" ] , [] )+              [ Str "http://example.com/" ]+              ( "http://example.com/" , "" )+          ]+      ]+  , Para+      [ Str "Auto-links"+      , Space+      , Str "should"+      , Space+      , Str "not"+      , Space+      , Str "occur"+      , Space+      , Str "here:"+      , Space+      , Code ( "" , [] , [] ) "<http://example.com/>"+      ]+  , CodeBlock+      ( "" , [] , [] ) "or here: <http://example.com/>"+  , HorizontalRule+  , Header 1 ( "images" , [] , [] ) [ Str "Images" ]+  , Para+      [ Str "From"+      , Space+      , Quoted+          DoubleQuote+          [ Str "Voyage"+          , Space+          , Str "dans"+          , Space+          , Str "la"+          , Space+          , Str "Lune"+          ]+      , Space+      , Str "by"+      , Space+      , Str "Georges"+      , Space+      , Str "Melies"+      , Space+      , Str "(1902):"+      ]+  , Figure+      ( "" , [] , [] )+      (Caption Nothing [ Plain [ Str "lalune" ] ])+      [ Plain+          [ Image+              ( "" , [] , [] )+              [ Str "lalune" ]+              ( "lalune.jpg" , "Voyage dans la Lune" )+          ]+      ]+  , Para+      [ Str "Here"+      , Space+      , Str "is"+      , Space+      , Str "a"+      , Space+      , Str "movie"+      , Space+      , Image+          ( "" , [] , [] ) [ Str "movie" ] ( "movie.jpg" , "" )+      , Space+      , Str "icon."+      ]+  , HorizontalRule+  , Header 1 ( "footnotes" , [] , [] ) [ Str "Footnotes" ]+  , Para+      [ Str "Here"+      , Space+      , Str "is"+      , Space+      , Str "a"+      , Space+      , Str "footnote"+      , Space+      , Str "reference,"+      , Note+          [ Para+              [ Str "Here"+              , Space+              , Str "is"+              , Space+              , Str "the"+              , Space+              , Str "footnote."+              , Space+              , Str "It"+              , Space+              , Str "can"+              , Space+              , Str "go"+              , Space+              , Str "anywhere"+              , Space+              , Str "after"+              , Space+              , Str "the"+              , Space+              , Str "footnote"+              , SoftBreak+              , Str "reference."+              , Space+              , Str "It"+              , Space+              , Str "need"+              , Space+              , Str "not"+              , Space+              , Str "be"+              , Space+              , Str "placed"+              , Space+              , Str "at"+              , Space+              , Str "the"+              , Space+              , Str "end"+              , Space+              , Str "of"+              , Space+              , Str "the"+              , Space+              , Str "document."+              ]+          ]+      , Space+      , Str "and"+      , Space+      , Str "another."+      , Note+          [ Para+              [ Str "Here\8217s"+              , Space+              , Str "the"+              , Space+              , Str "long"+              , Space+              , Str "note."+              , Space+              , Str "This"+              , Space+              , Str "one"+              , Space+              , Str "contains"+              , Space+              , Str "multiple"+              , SoftBreak+              , Str "blocks."+              ]+          , Para+              [ Str "Subsequent"+              , Space+              , Str "blocks"+              , Space+              , Str "are"+              , Space+              , Str "indented"+              , Space+              , Str "to"+              , Space+              , Str "show"+              , Space+              , Str "that"+              , Space+              , Str "they"+              , Space+              , Str "belong"+              , Space+              , Str "to"+              , Space+              , Str "the"+              , SoftBreak+              , Str "footnote"+              , Space+              , Str "(as"+              , Space+              , Str "with"+              , Space+              , Str "list"+              , Space+              , Str "items)."+              ]+          , CodeBlock ( "" , [] , [] ) "  { <code> }"+          , Para+              [ Str "If"+              , Space+              , Str "you"+              , Space+              , Str "want,"+              , Space+              , Str "you"+              , Space+              , Str "can"+              , Space+              , Str "indent"+              , Space+              , Str "every"+              , Space+              , Str "line,"+              , Space+              , Str "but"+              , Space+              , Str "you"+              , Space+              , Str "can"+              , Space+              , Str "also"+              , Space+              , Str "be"+              , SoftBreak+              , Str "lazy"+              , Space+              , Str "and"+              , Space+              , Str "just"+              , Space+              , Str "indent"+              , Space+              , Str "the"+              , Space+              , Str "first"+              , Space+              , Str "line"+              , Space+              , Str "of"+              , Space+              , Str "each"+              , Space+              , Str "block."+              ]+          ]+      , SoftBreak+      , Str "This"+      , Space+      , Str "should"+      , Space+      , Emph [ Str "not" ]+      , Space+      , Str "be"+      , Space+      , Str "a"+      , Space+      , Str "footnote"+      , Space+      , Str "reference,"+      , Space+      , Str "because"+      , Space+      , Str "it"+      , SoftBreak+      , Str "contains"+      , Space+      , Str "a"+      , Space+      , Str "space.[^my"+      , Space+      , Str "note]"+      , Space+      , Str "Here"+      , Space+      , Str "is"+      , Space+      , Str "an"+      , Space+      , Str "inline"+      , Space+      , Str "note."+      , Note+          [ Para+              [ Str "This"+              , SoftBreak+              , Str "is"+              , Space+              , Emph [ Str "easier" ]+              , Space+              , Str "to"+              , Space+              , Str "type."+              , Space+              , Str "Inline"+              , Space+              , Str "notes"+              , Space+              , Str "may"+              , Space+              , Str "contain"+              , SoftBreak+              , Link+                  ( "" , [] , [] )+                  [ Str "links" ]+                  ( "http://google.com" , "" )+              , Space+              , Str "and"+              , Space+              , Code ( "" , [] , [] ) "]"+              , Space+              , Str "verbatim"+              , Space+              , Str "characters,"+              , SoftBreak+              , Str "as"+              , Space+              , Str "well"+              , Space+              , Str "as"+              , Space+              , Str "[bracketed"+              , Space+              , Str "text]."+              ]+          ]+      ]+  , BlockQuote+      [ Para+          [ Str "Notes"+          , Space+          , Str "can"+          , Space+          , Str "go"+          , Space+          , Str "in"+          , Space+          , Str "quotes."+          , Note [ Para [ Str "In" , Space , Str "quote." ] ]+          ]+      ]+  , OrderedList+      ( 1 , Decimal , Period )+      [ [ Plain+            [ Str "And"+            , Space+            , Str "in"+            , Space+            , Str "list"+            , Space+            , Str "items."+            , Note [ Para [ Str "In" , Space , Str "list." ] ]+            ]+        ]+      ]+  , Para+      [ Str "This"+      , Space+      , Str "paragraph"+      , Space+      , Str "should"+      , Space+      , Str "not"+      , Space+      , Str "be"+      , Space+      , Str "part"+      , Space+      , Str "of"+      , Space+      , Str "the"+      , Space+      , Str "note,"+      , Space+      , Str "as"+      , Space+      , Str "it"+      , Space+      , Str "is"+      , Space+      , Str "not"+      , Space+      , Str "indented."+      ]+  ]
+ test/writer-template.lua view
@@ -0,0 +1,7 @@+function Writer (doc, opts)+  return pandoc.write(doc, 'gfm', opts)+end++function Template ()+  return '<!-- start -->\n$body$\n<!-- stop -->\n'+end
+ test/writer-template.out.txt view
@@ -0,0 +1,3 @@+<!-- start -->+$body$+<!-- stop -->
+ test/writer.custom view
@@ -0,0 +1,782 @@+<p>This is a set of tests for pandoc. Most of them are adapted from+John Gruber’s markdown test suite.</p>++<hr/>++<h1 id="headers">Headers</h1>++<h2 id="level-2-with-an-embedded-link">Level 2 with an <a href="/url" title="">embedded link</a></h2>++<h3 id="level-3-with-emphasis">Level 3 with <em>emphasis</em></h3>++<h4 id="level-4">Level 4</h4>++<h5 id="level-5">Level 5</h5>++<h1 id="level-1">Level 1</h1>++<h2 id="level-2-with-emphasis">Level 2 with <em>emphasis</em></h2>++<h3 id="level-3">Level 3</h3>++<p>with no blank line</p>++<h2 id="level-2">Level 2</h2>++<p>with no blank line</p>++<hr/>++<h1 id="paragraphs">Paragraphs</h1>++<p>Here’s a regular paragraph.</p>++<p>In Markdown 1.0.0 and earlier. Version+8. This line turns into a list item.+Because a hard-wrapped line in the+middle of a paragraph looked like a+list item.</p>++<p>Here’s one with a bullet.+* criminey.</p>++<p>There should be a hard line break<br/>here.</p>++<hr/>++<h1 id="block-quotes">Block Quotes</h1>++<p>E-mail style:</p>++<blockquote>+<p>This is a block quote.+It is pretty short.</p>+</blockquote>++<blockquote>+<p>Code in a block quote:</p>++<pre><code>sub status {+    print "working";+}</code></pre>++<p>A list:</p>++<ol>+<li>item one</li>+<li>item two</li>+</ol>++<p>Nested block quotes:</p>++<blockquote>+<p>nested</p>+</blockquote>++<blockquote>+<p>nested</p>+</blockquote>+</blockquote>++<p>This should not be a block quote: 2+&gt; 1.</p>++<p>And a following paragraph.</p>++<hr/>++<h1 id="code-blocks">Code Blocks</h1>++<p>Code:</p>++<pre><code>---- (should be four hyphens)++sub status {+    print "working";+}++this code block is indented by one tab</code></pre>++<p>And:</p>++<pre><code>    this code block is indented by two tabs++These should not be escaped:  \$ \\ \&gt; \[ \{</code></pre>++<hr/>++<h1 id="lists">Lists</h1>++<h2 id="unordered">Unordered</h2>++<p>Asterisks tight:</p>++<ul>+<li>asterisk 1</li>+<li>asterisk 2</li>+<li>asterisk 3</li>+</ul>++<p>Asterisks loose:</p>++<ul>+<li><p>asterisk 1</p></li>+<li><p>asterisk 2</p></li>+<li><p>asterisk 3</p></li>+</ul>++<p>Pluses tight:</p>++<ul>+<li>Plus 1</li>+<li>Plus 2</li>+<li>Plus 3</li>+</ul>++<p>Pluses loose:</p>++<ul>+<li><p>Plus 1</p></li>+<li><p>Plus 2</p></li>+<li><p>Plus 3</p></li>+</ul>++<p>Minuses tight:</p>++<ul>+<li>Minus 1</li>+<li>Minus 2</li>+<li>Minus 3</li>+</ul>++<p>Minuses loose:</p>++<ul>+<li><p>Minus 1</p></li>+<li><p>Minus 2</p></li>+<li><p>Minus 3</p></li>+</ul>++<h2 id="ordered">Ordered</h2>++<p>Tight:</p>++<ol>+<li>First</li>+<li>Second</li>+<li>Third</li>+</ol>++<p>and:</p>++<ol>+<li>One</li>+<li>Two</li>+<li>Three</li>+</ol>++<p>Loose using tabs:</p>++<ol>+<li><p>First</p></li>+<li><p>Second</p></li>+<li><p>Third</p></li>+</ol>++<p>and using spaces:</p>++<ol>+<li><p>One</p></li>+<li><p>Two</p></li>+<li><p>Three</p></li>+</ol>++<p>Multiple paragraphs:</p>++<ol>+<li><p>Item 1, graf one.</p>++<p>Item 1. graf two. The quick brown fox jumped over the lazy dog’s+back.</p></li>+<li><p>Item 2.</p></li>+<li><p>Item 3.</p></li>+</ol>++<h2 id="nested">Nested</h2>++<ul>+<li>Tab++<ul>+<li>Tab++<ul>+<li>Tab</li>+</ul></li>+</ul></li>+</ul>++<p>Here’s another:</p>++<ol>+<li>First</li>+<li>Second:++<ul>+<li>Fee</li>+<li>Fie</li>+<li>Foe</li>+</ul></li>+<li>Third</li>+</ol>++<p>Same thing but with paragraphs:</p>++<ol>+<li><p>First</p></li>+<li><p>Second:</p>++<ul>+<li>Fee</li>+<li>Fie</li>+<li>Foe</li>+</ul></li>+<li><p>Third</p></li>+</ol>++<h2 id="tabs-and-spaces">Tabs and spaces</h2>++<ul>+<li><p>this is a list item+indented with tabs</p></li>+<li><p>this is a list item+indented with spaces</p>++<ul>+<li><p>this is an example list item+indented with tabs</p></li>+<li><p>this is an example list item+indented with spaces</p></li>+</ul></li>+</ul>++<h2 id="fancy-list-markers">Fancy list markers</h2>++<ol>+<li><p>begins with 2</p></li>+<li><p>and now 3</p>++<p>with a continuation</p>++<ol>+<li>sublist with roman numerals,+starting with 4</li>+<li>more items++<ol>+<li>a subsublist</li>+<li>a subsublist</li>+</ol></li>+</ol></li>+</ol>++<p>Nesting:</p>++<ol>+<li>Upper Alpha++<ol>+<li>Upper Roman.++<ol>+<li>Decimal start with 6++<ol>+<li>Lower alpha with paren</li>+</ol></li>+</ol></li>+</ol></li>+</ol>++<p>Autonumbering:</p>++<ol>+<li>Autonumber.</li>+<li>More.++<ol>+<li>Nested.</li>+</ol></li>+</ol>++<p>Should not be a list item:</p>++<p>M.A. 2007</p>++<p>B. Williams</p>++<hr/>++<h1 id="definition-lists">Definition Lists</h1>++<p>Tight using spaces:</p>++<dl>+<dt>apple</dt>+<dd>red fruit</dd>+<dt>orange</dt>+<dd>orange fruit</dd>+<dt>banana</dt>+<dd>yellow fruit</dd>+</dl>++<p>Tight using tabs:</p>++<dl>+<dt>apple</dt>+<dd>red fruit</dd>+<dt>orange</dt>+<dd>orange fruit</dd>+<dt>banana</dt>+<dd>yellow fruit</dd>+</dl>++<p>Loose:</p>++<dl>+<dt>apple</dt>+<dd><p>red fruit</p></dd>+<dt>orange</dt>+<dd><p>orange fruit</p></dd>+<dt>banana</dt>+<dd><p>yellow fruit</p></dd>+</dl>++<p>Multiple blocks with italics:</p>++<dl>+<dt><em>apple</em></dt>+<dd><p>red fruit</p>++<p>contains seeds,+crisp, pleasant to taste</p></dd>+<dt><em>orange</em></dt>+<dd><p>orange fruit</p>++<pre><code>{ orange code block }</code></pre>++<blockquote>+<p>orange block quote</p>+</blockquote></dd>+</dl>++<p>Multiple definitions, tight:</p>++<dl>+<dt>apple</dt>+<dd>red fruit</dd>+<dd>computer</dd>+<dt>orange</dt>+<dd>orange fruit</dd>+<dd>bank</dd>+</dl>++<p>Multiple definitions, loose:</p>++<dl>+<dt>apple</dt>+<dd><p>red fruit</p></dd>+<dd><p>computer</p></dd>+<dt>orange</dt>+<dd><p>orange fruit</p></dd>+<dd><p>bank</p></dd>+</dl>++<p>Blank line after term, indented marker, alternate markers:</p>++<dl>+<dt>apple</dt>+<dd><p>red fruit</p></dd>+<dd><p>computer</p></dd>+<dt>orange</dt>+<dd><p>orange fruit</p>++<ol>+<li>sublist</li>+<li>sublist</li>+</ol></dd>+</dl>++<h1 id="html-blocks">HTML Blocks</h1>++<p>Simple block on one line:</p>++<div>+foo</div>++<p>And nested without indentation:</p>++<div>+<div>+<div>+<p>foo</p></div></div>++<div>+bar</div></div>++<p>Interpreted markdown in a table:</p>++<table>++<tr>++<td>++This is <em>emphasized</em>++</td>++<td>++And this is <strong>strong</strong>++</td>++</tr>++</table>++<script type="text/javascript">document.write('This *should not* be interpreted as markdown');</script>++<p>Here’s a simple block:</p>++<div>+<p>foo</p></div>++<p>This should be a code block, though:</p>++<pre><code>&lt;div&gt;+    foo+&lt;/div&gt;</code></pre>++<p>As should this:</p>++<pre><code>&lt;div&gt;foo&lt;/div&gt;</code></pre>++<p>Now, nested:</p>++<div>+<div>+<div>+foo</div></div></div>++<p>This should just be an HTML comment:</p>++<!-- Comment -->++<p>Multiline:</p>++<!--+Blah+Blah+-->++<!--+    This is another comment.+-->++<p>Code block:</p>++<pre><code>&lt;!-- Comment --&gt;</code></pre>++<p>Just plain comment, with trailing spaces on the line:</p>++<!-- foo -->++<p>Code:</p>++<pre><code>&lt;hr /&gt;</code></pre>++<p>Hr’s:</p>++<hr>++<hr />++<hr />++<hr>++<hr />++<hr />++<hr class="foo" id="bar" />++<hr class="foo" id="bar" />++<hr class="foo" id="bar">++<hr/>++<h1 id="inline-markup">Inline Markup</h1>++<p>This is <em>emphasized</em>, and so <em>is this</em>.</p>++<p>This is <strong>strong</strong>, and so <strong>is this</strong>.</p>++<p>An <em><a href="/url" title="">emphasized link</a></em>.</p>++<p><strong><em>This is strong and em.</em></strong></p>++<p>So is <strong><em>this</em></strong> word.</p>++<p><strong><em>This is strong and em.</em></strong></p>++<p>So is <strong><em>this</em></strong> word.</p>++<p>This is code: <code>&gt;</code>, <code>$</code>, <code>\</code>, <code>\$</code>, <code>&lt;html&gt;</code>.</p>++<p><del>This is <em>strikeout</em>.</del></p>++<p>Superscripts: a<sup>bc</sup>d a<sup><em>hello</em></sup> a<sup>hello there</sup>.</p>++<p>Subscripts: H<sub>2</sub>O, H<sub>23</sub>O, H<sub>many of them</sub>O.</p>++<p>These should not be superscripts or subscripts,+because of the unescaped spaces: a^b c^d, a~b c~d.</p>++<hr/>++<h1 id="smart-quotes-ellipses-dashes">Smart quotes, ellipses, dashes</h1>++<p>&ldquo;Hello,&rdquo; said the spider. &ldquo;&lsquo;Shelob&rsquo; is my name.&rdquo;</p>++<p>&lsquo;A&rsquo;, &lsquo;B&rsquo;, and &lsquo;C&rsquo; are letters.</p>++<p>&lsquo;Oak,&rsquo; &lsquo;elm,&rsquo; and &lsquo;beech&rsquo; are names of trees.+So is &lsquo;pine.&rsquo;</p>++<p>&lsquo;He said, &ldquo;I want to go.&rdquo;&rsquo; Were you alive in the+70’s?</p>++<p>Here is some quoted &lsquo;<code>code</code>&rsquo; and a &ldquo;<a href="http://example.com/?foo=1&amp;bar=2" title="">quoted link</a>&rdquo;.</p>++<p>Some dashes: one—two — three—four — five.</p>++<p>Dashes between numbers: 5–7, 255–66, 1987–1999.</p>++<p>Ellipses…and…and….</p>++<hr/>++<h1 id="latex">LaTeX</h1>++<ul>+<li></li>+<li>\(2+2=4\)</li>+<li>\(x \in y\)</li>+<li>\(\alpha \wedge \omega\)</li>+<li>\(223\)</li>+<li>\(p\)-Tree</li>+<li>Here’s some display math:+\[\frac{d}{dx}f(x)=\lim_{h\to 0}\frac{f(x+h)-f(x)}{h}\]</li>+<li>Here’s one that has a line break in it: \(\alpha + \omega \times x^2\).</li>+</ul>++<p>These shouldn’t be math:</p>++<ul>+<li>To get the famous equation, write <code>$e = mc^2$</code>.</li>+<li>$22,000 is a <em>lot</em> of money. So is $34,000.+(It worked if &ldquo;lot&rdquo; is emphasized.)</li>+<li>Shoes ($20) and socks ($5).</li>+<li>Escaped <code>$</code>: $73 <em>this should be emphasized</em> 23$.</li>+</ul>++<p>Here’s a LaTeX table:</p>++++<hr/>++<h1 id="special-characters">Special Characters</h1>++<p>Here is some unicode:</p>++<ul>+<li>I hat: Î</li>+<li>o umlaut: ö</li>+<li>section: §</li>+<li>set membership: ∈</li>+<li>copyright: ©</li>+</ul>++<p>AT&amp;T has an ampersand in their name.</p>++<p>AT&amp;T is another way to write it.</p>++<p>This &amp; that.</p>++<p>4 &lt; 5.</p>++<p>6 &gt; 5.</p>++<p>Backslash: \</p>++<p>Backtick: `</p>++<p>Asterisk: *</p>++<p>Underscore: _</p>++<p>Left brace: {</p>++<p>Right brace: }</p>++<p>Left bracket: [</p>++<p>Right bracket: ]</p>++<p>Left paren: (</p>++<p>Right paren: )</p>++<p>Greater-than: &gt;</p>++<p>Hash: #</p>++<p>Period: .</p>++<p>Bang: !</p>++<p>Plus: +</p>++<p>Minus: -</p>++<hr/>++<h1 id="links">Links</h1>++<h2 id="explicit">Explicit</h2>++<p>Just a <a href="/url/" title="">URL</a>.</p>++<p><a href="/url/" title="title">URL and title</a>.</p>++<p><a href="/url/" title="title preceded by two spaces">URL and title</a>.</p>++<p><a href="/url/" title="title preceded by a tab">URL and title</a>.</p>++<p><a href="/url/" title="title with &quot;quotes&quot; in it">URL and title</a></p>++<p><a href="/url/" title="title with single quotes">URL and title</a></p>++<p><a href="/url/with_underscore" title="">with_underscore</a></p>++<p><a href="mailto:nobody@nowhere.net" title="">Email link</a></p>++<p><a href="" title="">Empty</a>.</p>++<h2 id="reference">Reference</h2>++<p>Foo <a href="/url/" title="">bar</a>.</p>++<p>With <a href="/url/" title="">embedded [brackets]</a>.</p>++<p><a href="/url/" title="">b</a> by itself should be a link.</p>++<p>Indented <a href="/url" title="">once</a>.</p>++<p>Indented <a href="/url" title="">twice</a>.</p>++<p>Indented <a href="/url" title="">thrice</a>.</p>++<p>This should [not][] be a link.</p>++<pre><code>[not]: /url</code></pre>++<p>Foo <a href="/url/" title="Title with &quot;quotes&quot; inside">bar</a>.</p>++<p>Foo <a href="/url/" title="Title with &quot;quote&quot; inside">biz</a>.</p>++<h2 id="with-ampersands">With ampersands</h2>++<p>Here’s a <a href="http://example.com/?foo=1&amp;bar=2" title="">link with an ampersand in the URL</a>.</p>++<p>Here’s a link with an amersand in the link text: <a href="http://att.com/" title="AT&amp;T">AT&amp;T</a>.</p>++<p>Here’s an <a href="/script?foo=1&amp;bar=2" title="">inline link</a>.</p>++<p>Here’s an <a href="/script?foo=1&amp;bar=2" title="">inline link in pointy braces</a>.</p>++<h2 id="autolinks">Autolinks</h2>++<p>With an ampersand: <a href="http://example.com/?foo=1&amp;bar=2" title="" class="uri">http://example.com/?foo=1&amp;bar=2</a></p>++<ul>+<li>In a list?</li>+<li><a href="http://example.com/" title="" class="uri">http://example.com/</a></li>+<li>It should.</li>+</ul>++<p>An e-mail address: <a href="mailto:nobody@nowhere.net" title="" class="email">nobody@nowhere.net</a></p>++<blockquote>+<p>Blockquoted: <a href="http://example.com/" title="" class="uri">http://example.com/</a></p>+</blockquote>++<p>Auto-links should not occur here: <code>&lt;http://example.com/&gt;</code></p>++<pre><code>or here: &lt;http://example.com/&gt;</code></pre>++<hr/>++<h1 id="images">Images</h1>++<p>From &ldquo;Voyage dans la Lune&rdquo; by Georges Melies (1902):</p>++<figure>+<img src="lalune.jpg" title="Voyage dans la Lune"/>+<figcaption>lalune</figcaption>+</figure>++<p>Here is a movie <img src="movie.jpg" title=""/> icon.</p>++<hr/>++<h1 id="footnotes">Footnotes</h1>++<p>Here is a footnote reference,<a id="fnref1" href="#fn1"><sup>1</sup></a> and another.<a id="fnref2" href="#fn2"><sup>2</sup></a>+This should <em>not</em> be a footnote reference, because it+contains a space.[^my note] Here is an inline note.<a id="fnref3" href="#fn3"><sup>3</sup></a></p>++<blockquote>+<p>Notes can go in quotes.<a id="fnref4" href="#fn4"><sup>4</sup></a></p>+</blockquote>++<ol>+<li>And in list items.<a id="fnref5" href="#fn5"><sup>5</sup></a></li>+</ol>++<p>This paragraph should not be part of the note, as it is not indented.</p>+<ol class="footnotes">+<li id="fn1"><p>Here is the footnote. It can go anywhere after the footnote+reference. It need not be placed at the end of the document. <a href="#fnref1">&#8617;</a></p></li>+<li id="fn2"><p>Here’s the long note. This one contains multiple+blocks.</p>++<p>Subsequent blocks are indented to show that they belong to the+footnote (as with list items).</p>++<pre><code>  { &lt;code&gt; }</code></pre>++<p>If you want, you can indent every line, but you can also be+lazy and just indent the first line of each block. <a href="#fnref2">&#8617;</a></p></li>+<li id="fn3"><p>This+is <em>easier</em> to type. Inline notes may contain+<a href="http://google.com" title="">links</a> and <code>]</code> verbatim characters,+as well as [bracketed text]. <a href="#fnref3">&#8617;</a></p></li>+<li id="fn4"><p>In quote. <a href="#fnref4">&#8617;</a></p></li>+<li id="fn5"><p>In list. <a href="#fnref5">&#8617;</a></p></li>+</ol>