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 +361/−0
- README.md +6/−0
- pandoc-lua-engine.cabal +145/−0
- src/Text/Pandoc/Lua.hs +48/−0
- src/Text/Pandoc/Lua/Custom.hs +163/−0
- src/Text/Pandoc/Lua/Filter.hs +81/−0
- src/Text/Pandoc/Lua/Global.hs +74/−0
- src/Text/Pandoc/Lua/Init.hs +217/−0
- src/Text/Pandoc/Lua/Marshal/Chunks.hs +145/−0
- src/Text/Pandoc/Lua/Marshal/CommonState.hs +68/−0
- src/Text/Pandoc/Lua/Marshal/Context.hs +73/−0
- src/Text/Pandoc/Lua/Marshal/Format.hs +134/−0
- src/Text/Pandoc/Lua/Marshal/PandocError.hs +53/−0
- src/Text/Pandoc/Lua/Marshal/ReaderOptions.hs +135/−0
- src/Text/Pandoc/Lua/Marshal/Reference.hs +96/−0
- src/Text/Pandoc/Lua/Marshal/Sources.hs +56/−0
- src/Text/Pandoc/Lua/Marshal/Template.hs +43/−0
- src/Text/Pandoc/Lua/Marshal/WriterOptions.hs +246/−0
- src/Text/Pandoc/Lua/Module/CLI.hs +64/−0
- src/Text/Pandoc/Lua/Module/Format.hs +75/−0
- src/Text/Pandoc/Lua/Module/MediaBag.hs +174/−0
- src/Text/Pandoc/Lua/Module/Pandoc.hs +317/−0
- src/Text/Pandoc/Lua/Module/Scaffolding.hs +52/−0
- src/Text/Pandoc/Lua/Module/Structure.hs +192/−0
- src/Text/Pandoc/Lua/Module/System.hs +41/−0
- src/Text/Pandoc/Lua/Module/Template.hs +103/−0
- src/Text/Pandoc/Lua/Module/Types.hs +42/−0
- src/Text/Pandoc/Lua/Module/Utils.hs +239/−0
- src/Text/Pandoc/Lua/Orphans.hs +115/−0
- src/Text/Pandoc/Lua/PandocLua.hs +100/−0
- src/Text/Pandoc/Lua/Writer/Classic.hs +255/−0
- src/Text/Pandoc/Lua/Writer/Scaffolding.hs +311/−0
- test/Tests/Lua.hs +246/−0
- test/Tests/Lua/Module.hs +45/−0
- test/Tests/Lua/Reader.hs +36/−0
- test/Tests/Lua/Writer.hs +94/−0
- test/bytestring-reader.lua +7/−0
- test/bytestring.bin binary
- test/bytestring.lua +7/−0
- test/extensions.lua +12/−0
- test/lua/attr-test.lua +6/−0
- test/lua/block-count.lua +11/−0
- test/lua/blocks-filter.lua +8/−0
- test/lua/hello-world-doc.lua +10/−0
- test/lua/implicit-doc-filter.lua +6/−0
- test/lua/inlines-filter.lua +19/−0
- test/lua/markdown-reader.lua +12/−0
- test/lua/math.lua +10/−0
- test/lua/meta.lua +6/−0
- test/lua/metatable-catch-all.lua +20/−0
- test/lua/module/globals.lua +108/−0
- test/lua/module/pandoc-format.lua +50/−0
- test/lua/module/pandoc-list.lua +160/−0
- test/lua/module/pandoc-mediabag.lua +72/−0
- test/lua/module/pandoc-path.lua +44/−0
- test/lua/module/pandoc-structure.lua +139/−0
- test/lua/module/pandoc-template.lua +92/−0
- test/lua/module/pandoc-types.lua +86/−0
- test/lua/module/pandoc-utils.lua +333/−0
- test/lua/module/pandoc.lua +423/−0
- test/lua/module/partial.test +0/−0
- test/lua/module/tiny.epub binary
- test/lua/plain-to-para.lua +6/−0
- test/lua/require-file.lua +2/−0
- test/lua/script-name.lua +3/−0
- test/lua/single-to-double-quoted.lua +10/−0
- test/lua/smallcaps-title.lua +12/−0
- test/lua/smart-constructors.lua +10/−0
- test/lua/strmacro.lua +11/−0
- test/lua/undiv.lua +3/−0
- test/lua/uppercase-header.lua +9/−0
- test/sample.lua +369/−0
- test/tables.custom +200/−0
- test/tables.native +964/−0
- test/test-pandoc-lua-engine.hs +18/−0
- test/testsuite.native +2238/−0
- test/writer-template.lua +7/−0
- test/writer-template.out.txt +3/−0
- test/writer.custom +782/−0
+ 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 '<'+ elseif x == '>' then+ return '>'+ elseif x == '&' then+ return '&'+ elseif in_attribute and x == '"' then+ return '"'+ elseif in_attribute and x == "'" then+ return '''+ 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 '‘' .. s .. '’'+end++function DoubleQuoted(s)+ return '“' .. s .. '”'+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 .. '">↩</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+> 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: \$ \\ \> \[ \{</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><div>+ foo+</div></code></pre>++<p>As should this:</p>++<pre><code><div>foo</div></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><!-- Comment --></code></pre>++<p>Just plain comment, with trailing spaces on the line:</p>++<!-- foo -->++<p>Code:</p>++<pre><code><hr /></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>></code>, <code>$</code>, <code>\</code>, <code>\$</code>, <code><html></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>“Hello,” said the spider. “‘Shelob’ is my name.”</p>++<p>‘A’, ‘B’, and ‘C’ are letters.</p>++<p>‘Oak,’ ‘elm,’ and ‘beech’ are names of trees.+So is ‘pine.’</p>++<p>‘He said, “I want to go.”’ Were you alive in the+70’s?</p>++<p>Here is some quoted ‘<code>code</code>’ and a “<a href="http://example.com/?foo=1&bar=2" title="">quoted link</a>”.</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 “lot” 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&T has an ampersand in their name.</p>++<p>AT&T is another way to write it.</p>++<p>This & that.</p>++<p>4 < 5.</p>++<p>6 > 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: ></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 "quotes" 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 "quotes" inside">bar</a>.</p>++<p>Foo <a href="/url/" title="Title with "quote" inside">biz</a>.</p>++<h2 id="with-ampersands">With ampersands</h2>++<p>Here’s a <a href="http://example.com/?foo=1&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&T">AT&T</a>.</p>++<p>Here’s an <a href="/script?foo=1&bar=2" title="">inline link</a>.</p>++<p>Here’s an <a href="/script?foo=1&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&bar=2" title="" class="uri">http://example.com/?foo=1&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><http://example.com/></code></p>++<pre><code>or here: <http://example.com/></code></pre>++<hr/>++<h1 id="images">Images</h1>++<p>From “Voyage dans la Lune” 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">↩</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> { <code> }</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">↩</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">↩</a></p></li>+<li id="fn4"><p>In quote. <a href="#fnref4">↩</a></p></li>+<li id="fn5"><p>In list. <a href="#fnref5">↩</a></p></li>+</ol>