From 31129fee86af51e4989f313151b95e90045a579d Mon Sep 17 00:00:00 2001 From: farvardin Date: Sun, 15 Feb 2026 21:50:23 +0100 Subject: [PATCH 01/11] adding again txt2tags writer after sync from upstream --- MANUAL.txt | 1 + data/templates/default.t2t | 19 +++++++++++++++++++ pandoc.cabal | 4 ++++ src/Text/Pandoc/Writers.hs | 2 ++ test/Tests/Old.hs | 3 ++- 5 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 data/templates/default.t2t diff --git a/MANUAL.txt b/MANUAL.txt index c97586d0bbff..08d3e9152f7b 100644 --- a/MANUAL.txt +++ b/MANUAL.txt @@ -363,6 +363,7 @@ header when requesting a document from a URL: - `rtf` ([Rich Text Format]) - `texinfo` ([GNU Texinfo]) - `textile` ([Textile]) + - `t2t` ([txt2tags]) - `slideous` ([Slideous] HTML and JavaScript slide show) - `slidy` ([Slidy] HTML and JavaScript slide show) - `dzslides` ([DZSlides] HTML5 + JavaScript slide show) diff --git a/data/templates/default.t2t b/data/templates/default.t2t new file mode 100644 index 000000000000..23b302b4f76a --- /dev/null +++ b/data/templates/default.t2t @@ -0,0 +1,19 @@ +$pagetitle$ +$author-meta$ +$date-meta$ + + + +$for(include-before)$ +$include-before$ + +$endfor$ +$if(toc)$ +__TOC__ + +$endif$ +$body$ +$for(include-after)$ + +$include-after$ +$endfor$ diff --git a/pandoc.cabal b/pandoc.cabal index 3640dc43cd24..49199782ff92 100644 --- a/pandoc.cabal +++ b/pandoc.cabal @@ -93,6 +93,7 @@ data-files: data/templates/default.asciidoc data/templates/default.haddock data/templates/default.textile + data/templates/default.t2t data/templates/default.org data/templates/default.epub2 data/templates/default.epub3 @@ -344,6 +345,7 @@ extra-source-files: test/tables.typst test/tables.rst test/tables.rtf + test/tables.t2t test/tables.txt test/tables.fb2 test/tables.muse @@ -387,6 +389,7 @@ extra-source-files: test/writer.rst test/writer.icml test/writer.rtf + test/writer.t2t test/writer.tei test/writer.texinfo test/writer.fb2 @@ -668,6 +671,7 @@ library Text.Pandoc.Writers.Org, Text.Pandoc.Writers.AsciiDoc, Text.Pandoc.Writers.Textile, + Text.Pandoc.Writers.Txt2Tags, Text.Pandoc.Writers.MediaWiki, Text.Pandoc.Writers.DokuWiki, Text.Pandoc.Writers.XWiki, diff --git a/src/Text/Pandoc/Writers.hs b/src/Text/Pandoc/Writers.hs index e74ea4f00353..9224648fbb46 100644 --- a/src/Text/Pandoc/Writers.hs +++ b/src/Text/Pandoc/Writers.hs @@ -135,6 +135,7 @@ import Text.Pandoc.Writers.RTF import Text.Pandoc.Writers.TEI import Text.Pandoc.Writers.Texinfo import Text.Pandoc.Writers.Textile +import Text.Pandoc.Writers.Txt2Tags import Text.Pandoc.Writers.Typst import Text.Pandoc.Writers.XML import Text.Pandoc.Writers.XWiki @@ -202,6 +203,7 @@ writers = [ ,("xwiki" , TextWriter writeXWiki) ,("zimwiki" , TextWriter writeZimWiki) ,("textile" , TextWriter writeTextile) + ,("t2t" , TextWriter writeTxt2Tags) ,("typst" , TextWriter writeTypst) ,("rtf" , TextWriter writeRTF) ,("org" , TextWriter writeOrg) diff --git a/test/Tests/Old.hs b/test/Tests/Old.hs index 6e83ea6735cf..26455350a5e6 100644 --- a/test/Tests/Old.hs +++ b/test/Tests/Old.hs @@ -173,7 +173,8 @@ tests pandocPath = "haddock-reader.haddock" "haddock-reader.native" ] , testGroup "txt2tags" - [ test' "reader" ["-r", "t2t", "-w", "native", "-s"] + [ testGroup "writer" $ writerTests' "t2t" + , test' "reader" ["-r", "t2t", "-w", "native", "-s"] "txt2tags.t2t" "txt2tags.native" ] , testGroup "epub" [ test' "features" ["-r", "epub", "-w", "native", "-s"] From d27f051f7257f2c02116a11b8b1d53e9eab463e0 Mon Sep 17 00:00:00 2001 From: farvardin Date: Sun, 15 Feb 2026 21:51:57 +0100 Subject: [PATCH 02/11] adding t2t files --- src/Text/Pandoc/Writers/Txt2Tags.hs | 511 ++++++++++++++++++++++ test/tables.t2t | 46 ++ test/writer.t2t | 647 +++++++++++++++++++++++++++ test/writer.txt2tags | 653 ++++++++++++++++++++++++++++ 4 files changed, 1857 insertions(+) create mode 100644 src/Text/Pandoc/Writers/Txt2Tags.hs create mode 100644 test/tables.t2t create mode 100644 test/writer.t2t create mode 100644 test/writer.txt2tags diff --git a/src/Text/Pandoc/Writers/Txt2Tags.hs b/src/Text/Pandoc/Writers/Txt2Tags.hs new file mode 100644 index 000000000000..1b2040fd4a0d --- /dev/null +++ b/src/Text/Pandoc/Writers/Txt2Tags.hs @@ -0,0 +1,511 @@ +{-# LANGUAGE OverloadedStrings #-} +{- | + Module : Text.Pandoc.Writers.Txt2Tags + Copyright : Copyright (C) 2020 Eric Forgeot, based on John MacFarlane dokuwiki writer + License : GNU GPL, version 2 or above + + Maintainer : Eric Forgeot - https://github.com/farvardin/ + Stability : alpha + Portability : portable + +Conversion of 'Pandoc' documents to Txt2Tags markup. + +Txt2Tags: +-} + +{- + It works for most of the syntax. But some improvements or fix can be made: + - On lists (2 extra lines to terminate a list) + - On the 3 lines header at the begining of a file + - Definition lists are broken + - Tables could be improved + - Some formats make better results than others (html is ok, md is somehow broken) + - code related to only dokuwiki could be removed +-} + +module Text.Pandoc.Writers.Txt2Tags ( writeTxt2Tags ) where +import Control.Monad (zipWithM) +import Control.Monad.Reader (ReaderT, asks, local, runReaderT) +import Control.Monad.State.Strict (StateT, evalStateT) +import Data.Default (Default (..)) +import Data.List (intersect, transpose) +import Data.Text (Text) +import qualified Data.Text as T +import Text.Pandoc.Class.PandocMonad (PandocMonad, report) +import Text.Pandoc.Definition +import Text.Pandoc.ImageSize +import Text.Pandoc.Logging +import Text.Pandoc.Options (WrapOption (..), WriterOptions (writerTableOfContents, writerTemplate, writerWrapText)) +import Text.Pandoc.Shared (camelCaseToHyphenated, escapeURI, isURI, linesToPara, + removeFormatting, trimr, tshow) +import Text.Pandoc.Templates (renderTemplate) +import Text.DocLayout (render, literal) +import Text.Pandoc.Writers.Shared (defField, metaToContext, toLegacyTable) + +data WriterState = WriterState { + } + +data WriterEnvironment = WriterEnvironment { + stIndent :: Text -- Indent after the marker at the beginning of list items + , stUseTags :: Bool -- True if we should use HTML tags because we're in a complex list + , stBackSlashLB :: Bool -- True if we should produce formatted strings with newlines (as in a table cell) + } + +instance Default WriterState where + def = WriterState {} + +instance Default WriterEnvironment where + def = WriterEnvironment { stIndent = "" + , stUseTags = False + , stBackSlashLB = False } + +type Txt2Tags m = ReaderT WriterEnvironment (StateT WriterState m) + +-- | Convert Pandoc to Txt2Tags. +writeTxt2Tags :: PandocMonad m => WriterOptions -> Pandoc -> m Text +writeTxt2Tags opts document = + runTxt2Tags (pandocToTxt2Tags opts document) + +runTxt2Tags :: PandocMonad m => Txt2Tags m a -> m a +runTxt2Tags = flip evalStateT def . flip runReaderT def + +-- | Return Txt2Tags representation of document. +pandocToTxt2Tags :: PandocMonad m + => WriterOptions -> Pandoc -> Txt2Tags m Text +pandocToTxt2Tags opts (Pandoc meta blocks) = do + metadata <- metaToContext opts + (fmap (literal . trimr) . blockListToTxt2Tags opts) + (fmap (literal . trimr) . inlineListToTxt2Tags opts) + meta + body <- blockListToTxt2Tags opts blocks + let context = defField "body" body + $ defField "toc" (writerTableOfContents opts) metadata + return $ + case writerTemplate opts of + Nothing -> body + Just tpl -> render Nothing $ renderTemplate tpl context + +-- | Escape special characters for Txt2Tags. +escapeString :: Text -> Text +escapeString = T.replace "__" "%%__%%" . + T.replace "**" "%%**%%" . + T.replace "//" "%%//%%" + +-- | Convert Pandoc block element to Txt2Tags. +blockToTxt2Tags :: PandocMonad m + => WriterOptions -- ^ Options + -> Block -- ^ Block element + -> Txt2Tags m Text + +blockToTxt2Tags _ Null = return "" + +blockToTxt2Tags opts (Div _attrs bs) = do + contents <- blockListToTxt2Tags opts bs + return $ contents <> "\n\n" + +blockToTxt2Tags opts (Plain inlines) = + inlineListToTxt2Tags opts inlines + +-- title beginning with fig: indicates that the image is a figure +-- Txt2Tags doesn't support captions - so combine together alt and caption into alt +blockToTxt2Tags opts (Para [Image attr txt (src,tgt)]) + | Just tit <- T.stripPrefix "fig:" tgt + = do + capt <- if null txt + then return "" + else (" " <>) `fmap` inlineListToTxt2Tags opts txt + let opt = if null txt + then "" + else "|" <> if T.null tit then capt else tit <> capt + return $ "{{" <> src <> imageDims opts attr <> opt <> "}}\n" + +blockToTxt2Tags opts (Para inlines) = do + indent <- asks stIndent + useTags <- asks stUseTags + contents <- inlineListToTxt2Tags opts inlines + return $ if useTags + then "

" <> contents <> "

" + else contents <> if T.null indent then "\n" else "" + +blockToTxt2Tags opts (LineBlock lns) = + blockToTxt2Tags opts $ linesToPara lns + +blockToTxt2Tags _ b@(RawBlock f str) + | f == Format "Txt2Tags" = return str + -- See https://www.Txt2Tags.org/wiki:syntax + -- use uppercase HTML tag for block-level content: + | f == Format "html" = return $ "\n" <> str <> "\n" + | otherwise = "" <$ + report (BlockNotRendered b) + +blockToTxt2Tags _ HorizontalRule = return "\n---------------------\n" + +blockToTxt2Tags opts (Header level _ inlines) = do + -- emphasis, links etc. not allowed in headers, apparently, + -- so we remove formatting: + contents <- inlineListToTxt2Tags opts $ removeFormatting inlines + let eqs = T.replicate level "=" + return $ eqs <> " " <> contents <> " " <> eqs <> "\n" + +blockToTxt2Tags _ (CodeBlock (_,classes,_) str) = do + let at = classes `intersect` ["actionscript", "ada", "apache", "applescript", "asm", "asp", + "autoit", "bash", "blitzbasic", "bnf", "c", "c_mac", "caddcl", "cadlisp", "cfdg", "cfm", + "cpp", "cpp-qt", "csharp", "css", "d", "delphi", "diff", "div", "dos", "eiffel", "fortran", + "freebasic", "gml", "groovy", "html4strict", "idl", "ini", "inno", "io", "java", "java5", + "javascript", "latex", "lisp", "lua", "matlab", "mirc", "mpasm", "mysql", "nsis", "objc", + "ocaml", "ocaml-brief", "oobas", "oracle8", "pascal", "perl", "php", "php-brief", "plsql", + "python", "qbasic", "rails", "reg", "robots", "ruby", "sas", "scheme", "sdlbasic", + "smalltalk", "smarty", "sql", "tcl", "thinbasic", "tsql", "vb", "vbnet", "vhdl", + "visualfoxpro", "winbatch", "xml", "xpp", "z80"] + return $ "```" <> + (case at of + [] -> "\n" + (x:_) -> " " <> x <> "\n") <> str <> "\n```" + +blockToTxt2Tags opts (BlockQuote blocks) = do + contents <- blockListToTxt2Tags opts blocks + if isSimpleBlockQuote blocks + then return $ T.unlines $ map ("\t" <>) $ T.lines contents + else return $ "```\n" <> contents <> "```" + +blockToTxt2Tags opts (Table _ blkCapt specs thead tbody tfoot) = do + let (capt, aligns, _, headers, rows) = toLegacyTable blkCapt specs thead tbody tfoot + captionDoc <- if null capt + then return "" + else do + c <- inlineListToTxt2Tags opts capt + return $ "" <> c <> "\n" + headers' <- if all null headers + then return [] + else zipWithM (tableItemToTxt2Tags opts) aligns headers + rows' <- mapM (zipWithM (tableItemToTxt2Tags opts) aligns) rows + let widths = map (maximum . map T.length) $ transpose (headers':rows') + let padTo (width, al) s = + case width - T.length s of + x | x > 0 -> + if al == AlignLeft || al == AlignDefault + then s <> T.replicate x " " + else if al == AlignRight + then T.replicate x " " <> s + else T.replicate (x `div` 2) " " <> + s <> T.replicate (x - x `div` 2) " " + | otherwise -> s + let renderRow sep cells = sep <> + T.intercalate sep (zipWith padTo (zip widths aligns) cells) <> sep + return $ captionDoc <> + (if null headers' then "" else renderRow "|" headers' <> "\n") <> + T.unlines (map (renderRow "|") rows') + +blockToTxt2Tags opts x@(BulletList items) = do + oldUseTags <- asks stUseTags + indent <- asks stIndent + backSlash <- asks stBackSlashLB + let useTags = oldUseTags || not (isSimpleList x) + if useTags + then do + contents <- local (\s -> s { stUseTags = True }) + (mapM (listItemToTxt2Tags opts) items) + return $ "```\n" <> vcat contents <> "```\n" + else do + contents <- local (\s -> s { stIndent = stIndent s <> " " + , stBackSlashLB = backSlash}) + (mapM (listItemToTxt2Tags opts) items) + return $ vcat contents <> if T.null indent then "\n" else "" + +blockToTxt2Tags opts x@(OrderedList attribs items) = do + oldUseTags <- asks stUseTags + indent <- asks stIndent + backSlash <- asks stBackSlashLB + let useTags = oldUseTags || not (isSimpleList x) + if useTags + then do + contents <- local (\s -> s { stUseTags = True }) + (mapM (orderedListItemToTxt2Tags opts) items) + return $ "+ " <> listAttribsToString attribs <> ">\n" <> vcat contents <> "\n" + else do + contents <- local (\s -> s { stIndent = stIndent s <> " " + , stBackSlashLB = backSlash}) + (mapM (orderedListItemToTxt2Tags opts) items) + return $ vcat contents <> if T.null indent then "\n" else "" + +-- TODO Need to decide how to make definition lists work on Txt2Tags - I don't think there +-- is a specific representation of them. +-- TODO This creates double '; ; ' if there is a bullet or ordered list inside a definition list +blockToTxt2Tags opts x@(DefinitionList items) = do + oldUseTags <- asks stUseTags + indent <- asks stIndent + backSlash <- asks stBackSlashLB + let useTags = oldUseTags || not (isSimpleList x) + if useTags + then do + contents <- local (\s -> s { stUseTags = True }) + (mapM (definitionListItemToTxt2Tags opts) items) + return $ ": \n" <> vcat contents <> "\n" + else do + contents <- local (\s -> s { stIndent = stIndent s <> " " + , stBackSlashLB = backSlash}) + (mapM (definitionListItemToTxt2Tags opts) items) + return $ vcat contents <> if T.null indent then "\n" else "" + +-- Auxiliary functions for lists: + +-- | Convert ordered list attributes to HTML attribute string +listAttribsToString :: ListAttributes -> Text +listAttribsToString (startnum, numstyle, _) = + let numstyle' = camelCaseToHyphenated $ tshow numstyle + in (if startnum /= 1 + then " start=\"" <> tshow startnum <> "\"" + else "") <> + (if numstyle /= DefaultStyle + then " style=\"list-style-type: " <> numstyle' <> ";\"" + else "") + +-- | Convert bullet list item (list of blocks) to Txt2Tags. +listItemToTxt2Tags :: PandocMonad m + => WriterOptions -> [Block] -> Txt2Tags m Text +listItemToTxt2Tags opts items = do + useTags <- asks stUseTags + if useTags + then do + contents <- blockListToTxt2Tags opts items + return $ "
  • " <> contents <> "
  • " + else do + bs <- mapM (blockToTxt2Tags opts) items + let contents = case items of + [_, CodeBlock _ _] -> T.concat bs + _ -> vcat bs + indent <- asks stIndent + backSlash <- asks stBackSlashLB + let indent' = if backSlash then T.drop 2 indent else indent + return $ indent' <> "- " <> contents + +-- | Convert ordered list item (list of blocks) to Txt2Tags. +-- | TODO Emiminate dreadful duplication of text from listItemToTxt2Tags +orderedListItemToTxt2Tags :: PandocMonad m => WriterOptions -> [Block] -> Txt2Tags m Text +orderedListItemToTxt2Tags opts items = do + contents <- blockListToTxt2Tags opts items + useTags <- asks stUseTags + if useTags + then return $ "
  • " <> contents <> "
  • " + else do + indent <- asks stIndent + backSlash <- asks stBackSlashLB + let indent' = if backSlash then T.drop 2 indent else indent + return $ indent' <> "+ " <> contents + +-- | Convert definition list item (label, list of blocks) to Txt2Tags. +definitionListItemToTxt2Tags :: PandocMonad m + => WriterOptions + -> ([Inline],[[Block]]) + -> Txt2Tags m Text +definitionListItemToTxt2Tags opts (label, items) = do + labelText <- inlineListToTxt2Tags opts label + contents <- mapM (blockListToTxt2Tags opts) items + useTags <- asks stUseTags + if useTags + then return $ "
    " <> labelText <> "
    \n" <> + T.intercalate "\n" (map (\d -> "
    " <> d <> "
    ") contents) + else do + indent <- asks stIndent + backSlash <- asks stBackSlashLB + let indent' = if backSlash then T.drop 2 indent else indent + return $ indent' <> "* **" <> labelText <> "** " <> T.concat contents + +-- | True if the list can be handled by simple wiki markup, False if HTML tags will be needed. +isSimpleList :: Block -> Bool +isSimpleList x = + case x of + BulletList items -> all isSimpleListItem items + OrderedList (1, _, _) items -> all isSimpleListItem items + DefinitionList items -> all (all isSimpleListItem . snd) items + _ -> False + +-- | True if list item can be handled with the simple wiki syntax. False if +-- HTML tags will be needed. +isSimpleListItem :: [Block] -> Bool +isSimpleListItem [] = True +isSimpleListItem [x, CodeBlock{}] | isPlainOrPara x = True +isSimpleListItem (x:ys) | isPlainOrPara x = all isSimpleList ys +isSimpleListItem _ = False + +isPlainOrPara :: Block -> Bool +isPlainOrPara (Plain _) = True +isPlainOrPara (Para _) = True +isPlainOrPara _ = False + +isSimpleBlockQuote :: [Block] -> Bool +isSimpleBlockQuote bs = all isPlainOrPara bs + +-- | Concatenates strings with line breaks between them. +vcat :: [Text] -> Text +vcat = T.intercalate "\n" + +-- | For each string in the input list, convert all newlines to +-- Txt2Tags escaped newlines. Then concat the list using double linebreaks. +backSlashLineBreaks :: [Text] -> Text +backSlashLineBreaks ls = vcatBackSlash $ map (T.pack . escape . T.unpack) ls + where + vcatBackSlash = T.intercalate "\\\\ \\\\ " -- simulate paragraphs. + escape ['\n'] = "" -- remove trailing newlines + escape ('\n':cs) = "\\\\ " <> escape cs + escape (c:cs) = c : escape cs + escape [] = [] + +-- Auxiliary functions for tables: + +tableItemToTxt2Tags :: PandocMonad m + => WriterOptions + -> Alignment + -> [Block] + -> Txt2Tags m Text +tableItemToTxt2Tags opts align' item = do + let mkcell x = (if align' == AlignRight || align' == AlignCenter + then " " + else "") <> x <> + (if align' == AlignLeft || align' == AlignCenter + then " " + else "") + contents <- local (\s -> s { stBackSlashLB = True }) $ + blockListToTxt2Tags opts item + return $ mkcell contents + +-- | Convert list of Pandoc block elements to Txt2Tags. +blockListToTxt2Tags :: PandocMonad m + => WriterOptions -- ^ Options + -> [Block] -- ^ List of block elements + -> Txt2Tags m Text +blockListToTxt2Tags opts blocks = do + backSlash <- asks stBackSlashLB + let blocks' = consolidateRawBlocks blocks + if backSlash + then backSlashLineBreaks <$> mapM (blockToTxt2Tags opts) blocks' + else vcat <$> mapM (blockToTxt2Tags opts) blocks' + +consolidateRawBlocks :: [Block] -> [Block] +consolidateRawBlocks [] = [] +consolidateRawBlocks (RawBlock f1 b1 : RawBlock f2 b2 : xs) + | f1 == f2 = consolidateRawBlocks (RawBlock f1 (b1 <> "\n" <> b2) : xs) +consolidateRawBlocks (x:xs) = x : consolidateRawBlocks xs + +-- | Convert list of Pandoc inline elements to Txt2Tags. +inlineListToTxt2Tags :: PandocMonad m + => WriterOptions -> [Inline] -> Txt2Tags m Text +inlineListToTxt2Tags opts lst = + T.concat <$> mapM (inlineToTxt2Tags opts) lst + +-- | Convert Pandoc inline element to Txt2Tags. +inlineToTxt2Tags :: PandocMonad m + => WriterOptions -> Inline -> Txt2Tags m Text + +inlineToTxt2Tags opts (Span _attrs ils) = + inlineListToTxt2Tags opts ils + +inlineToTxt2Tags opts (Emph lst) = do + contents <- inlineListToTxt2Tags opts lst + return $ "//" <> contents <> "//" + +inlineToTxt2Tags opts (Underline lst) = do + contents <- inlineListToTxt2Tags opts lst + return $ "__" <> contents <> "__" + +inlineToTxt2Tags opts (Strong lst) = do + contents <- inlineListToTxt2Tags opts lst + return $ "**" <> contents <> "**" + +inlineToTxt2Tags opts (Strikeout lst) = do + contents <- inlineListToTxt2Tags opts lst + return $ "--" <> contents <> "--" + +inlineToTxt2Tags opts (Superscript lst) = do + contents <- inlineListToTxt2Tags opts lst + return $ "" <> contents <> "" + +inlineToTxt2Tags opts (Subscript lst) = do + contents <- inlineListToTxt2Tags opts lst + return $ "" <> contents <> "" + +inlineToTxt2Tags opts (SmallCaps lst) = inlineListToTxt2Tags opts lst + +inlineToTxt2Tags opts (Quoted SingleQuote lst) = do + contents <- inlineListToTxt2Tags opts lst + return $ "\8216" <> contents <> "\8217" + +inlineToTxt2Tags opts (Quoted DoubleQuote lst) = do + contents <- inlineListToTxt2Tags opts lst + return $ "\8220" <> contents <> "\8221" + +inlineToTxt2Tags opts (Cite _ lst) = inlineListToTxt2Tags opts lst + +inlineToTxt2Tags _ (Code _ str) = + -- In Txt2Tags, text surrounded by '' is really just a font statement, i.e. , + -- and so other formatting can be present inside. + -- However, in pandoc, and markdown, inlined code doesn't contain formatting. + -- So I have opted for using %% to disable all formatting inside inline code blocks. + -- This gives the best results when converting from other formats to Txt2Tags, even if + -- the resultand code is a little ugly, for short strings that don't contain formatting + -- characters. + -- It does mean that if pandoc could ever read Txt2Tags, and so round-trip the format, + -- any formatting inside inlined code blocks would be lost, or presented incorrectly. + return $ "''%%" <> str <> "%%''" + +inlineToTxt2Tags _ (Str str) = return $ escapeString str + +inlineToTxt2Tags _ (Math mathType str) = return $ delim <> str <> delim + -- note: str should NOT be escaped + where delim = case mathType of + DisplayMath -> "$$" + InlineMath -> "$" + +inlineToTxt2Tags _ il@(RawInline f str) + | f == Format "Txt2Tags" = return str + | f == Format "html" = return $ "" <> str <> "" + | otherwise = "" <$ report (InlineNotRendered il) + +inlineToTxt2Tags _ LineBreak = do + backSlash <- asks stBackSlashLB + return $ if backSlash + then "\n" + else "\\\\\n" + +inlineToTxt2Tags opts SoftBreak = + case writerWrapText opts of + WrapNone -> return " " + WrapAuto -> return " " + WrapPreserve -> return "\n" + +inlineToTxt2Tags _ Space = return " " + +inlineToTxt2Tags opts (Link _ txt (src, _)) = do + label <- inlineListToTxt2Tags opts txt + case txt of + [Str s] | "mailto:" `T.isPrefixOf` src -> return $ "<" <> s <> ">" + | escapeURI s == src -> return src + _ -> if isURI src + then return $ "[" <> label <> " " <> src <> "]" + else return $ "[" <> label <> " " <> src' <> "]" + where src' = case T.uncons src of + Just ('/',xs) -> xs -- with leading / it's a + _ -> src -- link to a help page +inlineToTxt2Tags opts (Image attr alt (source, tit)) = do + alt' <- inlineListToTxt2Tags opts alt + let txt = case (tit, alt) of + ("", []) -> "" + ("", _ ) -> "|" <> alt' + (_ , _ ) -> "|" <> tit + return $ "[" <> source <> imageDims opts attr <> txt <> "]" + +inlineToTxt2Tags opts (Note contents) = do + contents' <- blockListToTxt2Tags opts contents + return $ "((" <> contents' <> "))" + -- note - may not work for notes with multiple blocks + +imageDims :: WriterOptions -> Attr -> Text +imageDims opts attr = go (toPx $ dimension Width attr) (toPx $ dimension Height attr) + where + toPx = fmap (showInPixel opts) . checkPct + checkPct (Just (Percent _)) = Nothing + checkPct maybeDim = maybeDim + go (Just w) Nothing = "?" <> w + go (Just w) (Just h) = "?" <> w <> "x" <> h + go Nothing (Just h) = "?0x" <> h + go Nothing Nothing = "" diff --git a/test/tables.t2t b/test/tables.t2t new file mode 100644 index 000000000000..9d5d36f045f4 --- /dev/null +++ b/test/tables.t2t @@ -0,0 +1,46 @@ +Simple table with caption: + +Demonstration of simple table syntax. +| Right|Left | Center |Default| +| 12|12 | 12 |12 | +| 123|123 | 123 |123 | +| 1|1 | 1 |1 | + +Simple table without caption: + +| Right|Left | Center |Default| +| 12|12 | 12 |12 | +| 123|123 | 123 |123 | +| 1|1 | 1 |1 | + +Simple table indented two spaces: + +Demonstration of simple table syntax. +| Right|Left | Center |Default| +| 12|12 | 12 |12 | +| 123|123 | 123 |123 | +| 1|1 | 1 |1 | + +Multiline table with caption: + +Here’s the caption. It may span multiple lines. +| Centered Header |Left Aligned | Right Aligned|Default aligned | +| First |row | 12.0|Example of a row that spans multiple lines. | +| Second |row | 5.0|Here’s another one. Note the blank line between rows. | + +Multiline table without caption: + +| Centered Header |Left Aligned | Right Aligned|Default aligned | +| First |row | 12.0|Example of a row that spans multiple lines. | +| Second |row | 5.0|Here’s another one. Note the blank line between rows. | + +Table without column headers: + +| 12|12 | 12 | 12| +| 123|123 | 123 | 123| +| 1|1 | 1 | 1| + +Multiline table without column headers: + +| First |row | 12.0|Example of a row that spans multiple lines. | +| Second |row | 5.0|Here’s another one. Note the blank line between rows.| diff --git a/test/writer.t2t b/test/writer.t2t new file mode 100644 index 000000000000..f23c0ab477ae --- /dev/null +++ b/test/writer.t2t @@ -0,0 +1,647 @@ +This is a set of tests for pandoc. Most of them are adapted from John Gruber’s markdown test suite. + + +--------------------- + += Headers = + +== Level 2 with an embedded link == + +=== Level 3 with emphasis === + +==== Level 4 ==== + +===== Level 5 ===== + += Level 1 = + +== Level 2 with emphasis == + +=== Level 3 === + +with no blank line + +== Level 2 == + +with no blank line + + +--------------------- + += Paragraphs = + +Here’s a regular paragraph. + +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. + +Here’s one with a bullet. * criminey. + +There should be a hard line break\\ +here. + + +--------------------- + += Block Quotes = + +E-mail style: + + This is a block quote. It is pretty short. + +``` +Code in a block quote: + +``` +sub status { + print "working"; +} +``` +A list: + + + item one + + item two + +Nested block quotes: + + nested + + nested +``` +This should not be a block quote: 2 > 1. + +And a following paragraph. + + +--------------------- + += Code Blocks = + +Code: + +``` +---- (should be four hyphens) + +sub status { + print "working"; +} + +this code block is indented by one tab +``` +And: + +``` + this code block is indented by two tabs + +These should not be escaped: \$ \\ \> \[ \{ +``` + +--------------------- + += Lists = + +== Unordered == + +Asterisks tight: + + - asterisk 1 + - asterisk 2 + - asterisk 3 + +Asterisks loose: + + - asterisk 1 + - asterisk 2 + - asterisk 3 + +Pluses tight: + + - Plus 1 + - Plus 2 + - Plus 3 + +Pluses loose: + + - Plus 1 + - Plus 2 + - Plus 3 + +Minuses tight: + + - Minus 1 + - Minus 2 + - Minus 3 + +Minuses loose: + + - Minus 1 + - Minus 2 + - Minus 3 + +== Ordered == + +Tight: + + + First + + Second + + Third + +and: + + + One + + Two + + Three + +Loose using tabs: + + + First + + Second + + Third + +and using spaces: + + + One + + Two + + Three + +Multiple paragraphs: + ++ style="list-style-type: decimal;"> +
  • Item 1, graf one.

    +

    Item 1. graf two. The quick brown fox jumped over the lazy dog’s back.

  • +
  • Item 2.

  • +
  • Item 3.

  • + +== Nested == + + - Tab + - Tab + - Tab + +Here’s another: + + + First + + Second: + - Fee + - Fie + - Foe + + Third + +Same thing but with paragraphs: + + + First + + Second: + - Fee + - Fie + - Foe + + Third + +== Tabs and spaces == + + - this is a list item indented with tabs + - this is a list item indented with spaces + - this is an example list item indented with tabs + - this is an example list item indented with spaces + +== Fancy list markers == + ++ start="2" style="list-style-type: decimal;"> +
  • begins with 2

  • +
  • and now 3

    +

    with a continuation

    ++ start="4" style="list-style-type: lower-roman;"> +
  • sublist with roman numerals, starting with 4
  • +
  • more items ++ style="list-style-type: upper-alpha;"> +
  • a subsublist
  • +
  • a subsublist
  • + + + +Nesting: + ++ style="list-style-type: upper-alpha;"> +
  • Upper Alpha ++ style="list-style-type: upper-roman;"> +
  • Upper Roman. ++ start="6" style="list-style-type: decimal;"> +
  • Decimal start with 6 ++ start="3" style="list-style-type: lower-alpha;"> +
  • Lower alpha with paren
  • + + + + +Autonumbering: + + + Autonumber. + + More. + + Nested. + +Should not be a list item: + +M.A. 2007 + +B. Williams + + +--------------------- + += Definition Lists = + +Tight using spaces: + + * **apple** red fruit + * **orange** orange fruit + * **banana** yellow fruit + +Tight using tabs: + + * **apple** red fruit + * **orange** orange fruit + * **banana** yellow fruit + +Loose: + + * **apple** red fruit + * **orange** orange fruit + * **banana** yellow fruit + +Multiple blocks with italics: + +: +
    //apple//
    +

    red fruit

    +

    contains seeds, crisp, pleasant to taste

    +
    //orange//
    +

    orange fruit

    +``` +{ orange code block } +``` +

    orange block quote

    +
    + +Multiple definitions, tight: + + * **apple** red fruitcomputer + * **orange** orange fruitbank + +Multiple definitions, loose: + + * **apple** red fruitcomputer + * **orange** orange fruitbank + +Blank line after term, indented marker, alternate markers: + + * **apple** red fruitcomputer + * **orange** orange fruit + + sublist + + sublist + += HTML Blocks = + +Simple block on one line: + +foo + + +And nested without indentation: + +foo + + + + + +bar + + + + +Interpreted markdown in a table: + + + + + + + +
    + +This is //emphasized// + + + +And this is **strong** + +
    + + +Here’s a simple block: + +foo + + + +This should be a code block, though: + +``` +
    + foo +
    +``` +As should this: + +``` +
    foo
    +``` +Now, nested: + +foo + + + + + + +This should just be an HTML comment: + + + + +Multiline: + + + + + +Code block: + +``` + +``` +Just plain comment, with trailing spaces on the line: + + + + +Code: + +``` +
    +``` +Hr’s: + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +--------------------- + += Inline Markup = + +This is //emphasized//, and so //is this//. + +This is **strong**, and so **is this**. + +An //[emphasized link url]//. + +**//This is strong and em.//** + +So is **//this//** word. + +**//This is strong and em.//** + +So is **//this//** word. + +This is code: ''%%>%%'', ''%%$%%'', ''%%\%%'', ''%%\$%%'', ''%%%%''. + +--This is //strikeout//.-- + +Superscripts: abcd a//hello// ahello there. + +Subscripts: H2O, H23O, Hmany of themO. + +These should not be superscripts or subscripts, because of the unescaped spaces: a^b c^d, a~b c~d. + + +--------------------- + += Smart quotes, ellipses, dashes = + +“Hello,” said the spider. “‘Shelob’ is my name.” + +‘A’, ‘B’, and ‘C’ are letters. + +‘Oak,’ ‘elm,’ and ‘beech’ are names of trees. So is ‘pine.’ + +‘He said, “I want to go.”’ Were you alive in the 70’s? + +Here is some quoted ‘''%%code%%''’ and a “[quoted link http://example.com/?foo=1&bar=2]”. + +Some dashes: one—two — three—four — five. + +Dashes between numbers: 5–7, 255–66, 1987–1999. + +Ellipses…and…and…. + + +--------------------- + += LaTeX = + + - + - $2+2=4$ + - $x \in y$ + - $\alpha \wedge \omega$ + - $223$ + - $p$-Tree + - Here’s some display math: $$\frac{d}{dx}f(x)=\lim_{h\to 0}\frac{f(x+h)-f(x)}{h}$$ + - Here’s one that has a line break in it: $\alpha + \omega \times x^2$. + +These shouldn’t be math: + + - To get the famous equation, write ''%%$e = mc^2$%%''. + - $22,000 is a //lot// of money. So is $34,000. (It worked if “lot” is emphasized.) + - Shoes ($20) and socks ($5). + - Escaped ''%%$%%'': $73 //this should be emphasized// 23$. + +Here’s a LaTeX table: + + + +--------------------- + += Special Characters = + +Here is some unicode: + + - I hat: Î + - o umlaut: ö + - section: § + - set membership: ∈ + - copyright: © + +AT&T has an ampersand in their name. + +AT&T is another way to write it. + +This & that. + +4 < 5. + +6 > 5. + +Backslash: \ + +Backtick: ` + +Asterisk: * + +Underscore: _ + +Left brace: { + +Right brace: } + +Left bracket: [ + +Right bracket: ] + +Left paren: ( + +Right paren: ) + +Greater-than: > + +Hash: # + +Period: . + +Bang: ! + +Plus: + + +Minus: - + + +--------------------- + += Links = + +== Explicit == + +Just a [URL url/]. + +[URL and title url/]. + +[URL and title url/]. + +[URL and title url/]. + +[URL and title url/] + +[URL and title url/] + +[with_underscore url/with_underscore] + +[Email link mailto:nobody@nowhere.net] + +[Empty ]. + +== Reference == + +Foo [bar url/]. + +With [embedded [brackets] url/]. + +[b url/] by itself should be a link. + +Indented [once url]. + +Indented [twice url]. + +Indented [thrice url]. + +This should [not][] be a link. + +``` +[not]: /url +``` +Foo [bar url/]. + +Foo [biz url/]. + +== With ampersands == + +Here’s a [link with an ampersand in the URL http://example.com/?foo=1&bar=2]. + +Here’s a link with an amersand in the link text: [AT&T http://att.com/]. + +Here’s an [inline link script?foo=1&bar=2]. + +Here’s an [inline link in pointy braces script?foo=1&bar=2]. + +== Autolinks == + +With an ampersand: http://example.com/?foo=1&bar=2 + + - In a list? + - http://example.com/ + - It should. + +An e-mail address: + + Blockquoted: http://example.com/ + +Auto-links should not occur here: ''%%%%'' + +``` +or here: +``` + +--------------------- + += Images = + +From “Voyage dans la Lune” by Georges Melies (1902): + +{{lalune.jpg|Voyage dans la Lune lalune}} + +Here is a movie [movie.jpg|movie] icon. + + +--------------------- + += Footnotes = + +Here is a footnote reference,((Here is the footnote. It can go anywhere after the footnote reference. It need not be placed at the end of the document. +)) and another.((Here’s the long note. This one contains multiple blocks. + +Subsequent blocks are indented to show that they belong to the footnote (as with list items). + +``` + { } +``` +If you want, you can indent every line, but you can also be lazy and just indent the first line of each block. +)) This should //not// be a footnote reference, because it contains a space.[^my note] Here is an inline note.((This is //easier// to type. Inline notes may contain [links http://google.com] and ''%%]%%'' verbatim characters, as well as [bracketed text]. +)) + + Notes can go in quotes.((In quote. + )) + + + And in list items.((In list.)) + +This paragraph should not be part of the note, as it is not indented. diff --git a/test/writer.txt2tags b/test/writer.txt2tags new file mode 100644 index 000000000000..714ffa4e7227 --- /dev/null +++ b/test/writer.txt2tags @@ -0,0 +1,653 @@ + + + + + + +This is a set of tests for pandoc. Most of them are adapted from John Gruber’s markdown test suite. + + +--------------------- + += Headers = + +== Level 2 with an embedded link == + +=== Level 3 with emphasis === + +==== Level 4 ==== + +===== Level 5 ===== + += Level 1 = + +== Level 2 with emphasis == + +=== Level 3 === + +with no blank line + +== Level 2 == + +with no blank line + + +--------------------- + += Paragraphs = + +Here’s a regular paragraph. + +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. + +Here’s one with a bullet. * criminey. + +There should be a hard line break\\ +here. + + +--------------------- + += Block Quotes = + +E-mail style: + +> This is a block quote. It is pretty short. + +
    +Code in a block quote: + +``` +sub status { + print "working"; +} +``` +A list: + + + item one + + item two + +Nested block quotes: + +> nested + +> nested +
    +This should not be a block quote: 2 > 1. + +And a following paragraph. + + +--------------------- + += Code Blocks = + +Code: + +``` +---- (should be four hyphens) + +sub status { + print "working"; +} + +this code block is indented by one tab +``` +And: + +``` + this code block is indented by two tabs + +These should not be escaped: \$ \\ \> \[ \{ +``` + +--------------------- + += Lists = + +== Unordered == + +Asterisks tight: + + - asterisk 1 + - asterisk 2 + - asterisk 3 + +Asterisks loose: + + - asterisk 1 + - asterisk 2 + - asterisk 3 + +Pluses tight: + + - Plus 1 + - Plus 2 + - Plus 3 + +Pluses loose: + + - Plus 1 + - Plus 2 + - Plus 3 + +Minuses tight: + + - Minus 1 + - Minus 2 + - Minus 3 + +Minuses loose: + + - Minus 1 + - Minus 2 + - Minus 3 + +== Ordered == + +Tight: + + + First + + Second + + Third + +and: + + + One + + Two + + Three + +Loose using tabs: + + + First + + Second + + Third + +and using spaces: + + + One + + Two + + Three + +Multiple paragraphs: + +
      +
    1. Item 1, graf one.

      +

      Item 1. graf two. The quick brown fox jumped over the lazy dog’s back.

    2. +
    3. Item 2.

    4. +
    5. Item 3.

    + +== Nested == + + - Tab + - Tab + - Tab + +Here’s another: + + + First + + Second: + - Fee + - Fie + - Foe + + Third + +Same thing but with paragraphs: + + + First + + Second: + - Fee + - Fie + - Foe + + Third + +== Tabs and spaces == + + - this is a list item indented with tabs + - this is a list item indented with spaces + - this is an example list item indented with tabs + - this is an example list item indented with spaces + +== Fancy list markers == + +
      +
    1. begins with 2

    2. +
    3. and now 3

      +

      with a continuation

      +
        +
      1. sublist with roman numerals, starting with 4
      2. +
      3. more items +
          +
        1. a subsublist
        2. +
        3. a subsublist
        +
      +
    + +Nesting: + +
      +
    1. Upper Alpha +
        +
      1. Upper Roman. +
          +
        1. Decimal start with 6 +
            +
          1. Lower alpha with paren
          +
        +
      +
    + +Autonumbering: + + + Autonumber. + + More. + + Nested. + +Should not be a list item: + +M.A. 2007 + +B. Williams + + +--------------------- + += Definition Lists = + +Tight using spaces: + + * **apple** red fruit + * **orange** orange fruit + * **banana** yellow fruit + +Tight using tabs: + + * **apple** red fruit + * **orange** orange fruit + * **banana** yellow fruit + +Loose: + + * **apple** red fruit + * **orange** orange fruit + * **banana** yellow fruit + +Multiple blocks with italics: + +
    +
    //apple//
    +

    red fruit

    +

    contains seeds, crisp, pleasant to taste

    +
    //orange//
    +

    orange fruit

    +``` +{ orange code block } +``` +>

    orange block quote

    +
    + +Multiple definitions, tight: + + * **apple** red fruitcomputer + * **orange** orange fruitbank + +Multiple definitions, loose: + + * **apple** red fruitcomputer + * **orange** orange fruitbank + +Blank line after term, indented marker, alternate markers: + + * **apple** red fruitcomputer + * **orange** orange fruit + + sublist + + sublist + += HTML Blocks = + +Simple block on one line: + +foo + + +And nested without indentation: + +foo + + + + + +bar + + + + +Interpreted markdown in a table: + + + + + + + +
    + +This is //emphasized// + + + +And this is **strong** + +
    + + +Here’s a simple block: + +foo + + + +This should be a code block, though: + +``` +
    + foo +
    +``` +As should this: + +``` +
    foo
    +``` +Now, nested: + +foo + + + + + + +This should just be an HTML comment: + + + + +Multiline: + + + + + +Code block: + +``` + +``` +Just plain comment, with trailing spaces on the line: + + + + +Code: + +``` +
    +``` +Hr’s: + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +--------------------- + += Inline Markup = + +This is //emphasized//, and so //is this//. + +This is **strong**, and so **is this**. + +An //[emphasized link url]//. + +**//This is strong and em.//** + +So is **//this//** word. + +**//This is strong and em.//** + +So is **//this//** word. + +This is code: ''%%>%%'', ''%%$%%'', ''%%\%%'', ''%%\$%%'', ''%%%%''. + +--This is //strikeout//.-- + +Superscripts: abcd a//hello// ahello there. + +Subscripts: H2O, H23O, Hmany of themO. + +These should not be superscripts or subscripts, because of the unescaped spaces: a^b c^d, a~b c~d. + + +--------------------- + += Smart quotes, ellipses, dashes = + +“Hello,” said the spider. “‘Shelob’ is my name.” + +‘A’, ‘B’, and ‘C’ are letters. + +‘Oak,’ ‘elm,’ and ‘beech’ are names of trees. So is ‘pine.’ + +‘He said, “I want to go.”’ Were you alive in the 70’s? + +Here is some quoted ‘''%%code%%''’ and a “[quoted link http://example.com/?foo=1&bar=2]”. + +Some dashes: one—two — three—four — five. + +Dashes between numbers: 5–7, 255–66, 1987–1999. + +Ellipses…and…and…. + + +--------------------- + += LaTeX = + + - + - $2+2=4$ + - $x \in y$ + - $\alpha \wedge \omega$ + - $223$ + - $p$-Tree + - Here’s some display math: $$\frac{d}{dx}f(x)=\lim_{h\to 0}\frac{f(x+h)-f(x)}{h}$$ + - Here’s one that has a line break in it: $\alpha + \omega \times x^2$. + +These shouldn’t be math: + + - To get the famous equation, write ''%%$e = mc^2$%%''. + - $22,000 is a //lot// of money. So is $34,000. (It worked if “lot” is emphasized.) + - Shoes ($20) and socks ($5). + - Escaped ''%%$%%'': $73 //this should be emphasized// 23$. + +Here’s a LaTeX table: + + + +--------------------- + += Special Characters = + +Here is some unicode: + + - I hat: Î + - o umlaut: ö + - section: § + - set membership: ∈ + - copyright: © + +AT&T has an ampersand in their name. + +AT&T is another way to write it. + +This & that. + +4 < 5. + +6 > 5. + +Backslash: \ + +Backtick: ` + +Asterisk: * + +Underscore: _ + +Left brace: { + +Right brace: } + +Left bracket: [ + +Right bracket: ] + +Left paren: ( + +Right paren: ) + +Greater-than: > + +Hash: # + +Period: . + +Bang: ! + +Plus: + + +Minus: - + + +--------------------- + += Links = + +== Explicit == + +Just a [URL url/]. + +[URL and title url/]. + +[URL and title url/]. + +[URL and title url/]. + +[URL and title url/] + +[URL and title url/] + +[with_underscore url/with_underscore] + +[Email link mailto:nobody@nowhere.net] + +[Empty ]. + +== Reference == + +Foo [bar url/]. + +With [embedded [brackets] url/]. + +[b url/] by itself should be a link. + +Indented [once url]. + +Indented [twice url]. + +Indented [thrice url]. + +This should [not][] be a link. + +``` +[not]: /url +``` +Foo [bar url/]. + +Foo [biz url/]. + +== With ampersands == + +Here’s a [link with an ampersand in the URL http://example.com/?foo=1&bar=2]. + +Here’s a link with an amersand in the link text: [AT&T http://att.com/]. + +Here’s an [inline link script?foo=1&bar=2]. + +Here’s an [inline link in pointy braces script?foo=1&bar=2]. + +== Autolinks == + +With an ampersand: http://example.com/?foo=1&bar=2 + + - In a list? + - http://example.com/ + - It should. + +An e-mail address: + +> Blockquoted: http://example.com/ + +Auto-links should not occur here: ''%%%%'' + +``` +or here: +``` + +--------------------- + += Images = + +From “Voyage dans la Lune” by Georges Melies (1902): + +{{lalune.jpg|Voyage dans la Lune lalune}} + +Here is a movie [movie.jpg|movie] icon. + + +--------------------- + += Footnotes = + +Here is a footnote reference,((Here is the footnote. It can go anywhere after the footnote reference. It need not be placed at the end of the document. +)) and another.((Here’s the long note. This one contains multiple blocks. + +Subsequent blocks are indented to show that they belong to the footnote (as with list items). + +``` + { } +``` +If you want, you can indent every line, but you can also be lazy and just indent the first line of each block. +)) This should //not// be a footnote reference, because it contains a space.[^my note] Here is an inline note.((This is //easier// to type. Inline notes may contain [links http://google.com] and ''%%]%%'' verbatim characters, as well as [bracketed text]. +)) + +> Notes can go in quotes.((In quote. +> )) + + + And in list items.((In list.)) + +This paragraph should not be part of the note, as it is not indented. From 4a00e28abbc055d2c68115c7fa47ed7cc7269ba9 Mon Sep 17 00:00:00 2001 From: farvardin Date: Sun, 15 Feb 2026 23:49:23 +0100 Subject: [PATCH 03/11] updating src/Text/Pandoc/Writers/Txt2Tags.hs to follow latest dokuwiki version --- src/Text/Pandoc/Writers/Txt2Tags.hs | 298 +++++++++++++--------------- 1 file changed, 143 insertions(+), 155 deletions(-) diff --git a/src/Text/Pandoc/Writers/Txt2Tags.hs b/src/Text/Pandoc/Writers/Txt2Tags.hs index 1b2040fd4a0d..facf66d9f740 100644 --- a/src/Text/Pandoc/Writers/Txt2Tags.hs +++ b/src/Text/Pandoc/Writers/Txt2Tags.hs @@ -1,16 +1,16 @@ {-# LANGUAGE OverloadedStrings #-} {- | Module : Text.Pandoc.Writers.Txt2Tags - Copyright : Copyright (C) 2020 Eric Forgeot, based on John MacFarlane dokuwiki writer + Copyright : Copyright (C) 2008-2024 Eric Forgeot, based on John MacFarlane DokuWiki writer License : GNU GPL, version 2 or above - Maintainer : Eric Forgeot - https://github.com/farvardin/ + Maintainer : Clare Macrae Stability : alpha Portability : portable Conversion of 'Pandoc' documents to Txt2Tags markup. -Txt2Tags: +Txt2Tags: -} {- @@ -20,7 +20,8 @@ Txt2Tags: - Definition lists are broken - Tables could be improved - Some formats make better results than others (html is ok, md is somehow broken) - - code related to only dokuwiki could be removed + - code related to dokuwiki only could be removed + - a test case should be made -} module Text.Pandoc.Writers.Txt2Tags ( writeTxt2Tags ) where @@ -28,27 +29,32 @@ import Control.Monad (zipWithM) import Control.Monad.Reader (ReaderT, asks, local, runReaderT) import Control.Monad.State.Strict (StateT, evalStateT) import Data.Default (Default (..)) -import Data.List (intersect, transpose) +import Data.List (transpose) +import Data.List.NonEmpty (nonEmpty) import Data.Text (Text) import qualified Data.Text as T import Text.Pandoc.Class.PandocMonad (PandocMonad, report) import Text.Pandoc.Definition +import Text.Pandoc.Extensions import Text.Pandoc.ImageSize import Text.Pandoc.Logging -import Text.Pandoc.Options (WrapOption (..), WriterOptions (writerTableOfContents, writerTemplate, writerWrapText)) -import Text.Pandoc.Shared (camelCaseToHyphenated, escapeURI, isURI, linesToPara, - removeFormatting, trimr, tshow) +import Text.Pandoc.Options (WrapOption (..), WriterOptions (writerTableOfContents, + writerTemplate, writerWrapText), isEnabled) +import Text.Pandoc.Shared (figureDiv, linesToPara, removeFormatting, trimr) +import Text.Pandoc.URI (escapeURI, isURI) import Text.Pandoc.Templates (renderTemplate) import Text.DocLayout (render, literal) import Text.Pandoc.Writers.Shared (defField, metaToContext, toLegacyTable) +import Data.Maybe (fromMaybe) +import qualified Data.Map as M data WriterState = WriterState { } data WriterEnvironment = WriterEnvironment { stIndent :: Text -- Indent after the marker at the beginning of list items - , stUseTags :: Bool -- True if we should use HTML tags because we're in a complex list , stBackSlashLB :: Bool -- True if we should produce formatted strings with newlines (as in a table cell) + , stBlockQuoteLevel :: Int -- Block quote level } instance Default WriterState where @@ -56,8 +62,8 @@ instance Default WriterState where instance Default WriterEnvironment where def = WriterEnvironment { stIndent = "" - , stUseTags = False - , stBackSlashLB = False } + , stBackSlashLB = False + , stBlockQuoteLevel = 0 } type Txt2Tags m = ReaderT WriterEnvironment (StateT WriterState m) @@ -97,76 +103,60 @@ blockToTxt2Tags :: PandocMonad m -> Block -- ^ Block element -> Txt2Tags m Text -blockToTxt2Tags _ Null = return "" - blockToTxt2Tags opts (Div _attrs bs) = do contents <- blockListToTxt2Tags opts bs - return $ contents <> "\n\n" + indent <- asks stIndent + return $ contents <> if T.null indent then "\n\n" else "" blockToTxt2Tags opts (Plain inlines) = inlineListToTxt2Tags opts inlines --- title beginning with fig: indicates that the image is a figure --- Txt2Tags doesn't support captions - so combine together alt and caption into alt -blockToTxt2Tags opts (Para [Image attr txt (src,tgt)]) - | Just tit <- T.stripPrefix "fig:" tgt - = do - capt <- if null txt - then return "" - else (" " <>) `fmap` inlineListToTxt2Tags opts txt - let opt = if null txt - then "" - else "|" <> if T.null tit then capt else tit <> capt - return $ "{{" <> src <> imageDims opts attr <> opt <> "}}\n" - blockToTxt2Tags opts (Para inlines) = do + bqLevel <- asks stBlockQuoteLevel + let bqPrefix = case bqLevel of + 0 -> "" + n -> T.replicate n ">" <> " " indent <- asks stIndent - useTags <- asks stUseTags contents <- inlineListToTxt2Tags opts inlines - return $ if useTags - then "

    " <> contents <> "

    " - else contents <> if T.null indent then "\n" else "" + return $ bqPrefix <> contents <> if T.null indent then "\n" else "" blockToTxt2Tags opts (LineBlock lns) = blockToTxt2Tags opts $ linesToPara lns -blockToTxt2Tags _ b@(RawBlock f str) +blockToTxt2Tags opts b@(RawBlock f str) | f == Format "Txt2Tags" = return str -- See https://www.Txt2Tags.org/wiki:syntax -- use uppercase HTML tag for block-level content: - | f == Format "html" = return $ "\n" <> str <> "\n" + | f == Format "html" + , isEnabled Ext_raw_html opts = return $ "\n" <> str <> "\n" | otherwise = "" <$ report (BlockNotRendered b) -blockToTxt2Tags _ HorizontalRule = return "\n---------------------\n" +blockToTxt2Tags _ HorizontalRule = return "\n---------------\n" blockToTxt2Tags opts (Header level _ inlines) = do -- emphasis, links etc. not allowed in headers, apparently, -- so we remove formatting: contents <- inlineListToTxt2Tags opts $ removeFormatting inlines - let eqs = T.replicate level "=" + let eqs = T.replicate level "=" return $ eqs <> " " <> contents <> " " <> eqs <> "\n" blockToTxt2Tags _ (CodeBlock (_,classes,_) str) = do - let at = classes `intersect` ["actionscript", "ada", "apache", "applescript", "asm", "asp", - "autoit", "bash", "blitzbasic", "bnf", "c", "c_mac", "caddcl", "cadlisp", "cfdg", "cfm", - "cpp", "cpp-qt", "csharp", "css", "d", "delphi", "diff", "div", "dos", "eiffel", "fortran", - "freebasic", "gml", "groovy", "html4strict", "idl", "ini", "inno", "io", "java", "java5", - "javascript", "latex", "lisp", "lua", "matlab", "mirc", "mpasm", "mysql", "nsis", "objc", - "ocaml", "ocaml-brief", "oobas", "oracle8", "pascal", "perl", "php", "php-brief", "plsql", - "python", "qbasic", "rails", "reg", "robots", "ruby", "sas", "scheme", "sdlbasic", - "smalltalk", "smarty", "sql", "tcl", "thinbasic", "tsql", "vb", "vbnet", "vhdl", - "visualfoxpro", "winbatch", "xml", "xpp", "z80"] - return $ "```" <> - (case at of - [] -> "\n" - (x:_) -> " " <> x <> "\n") <> str <> "\n```" - -blockToTxt2Tags opts (BlockQuote blocks) = do - contents <- blockListToTxt2Tags opts blocks - if isSimpleBlockQuote blocks - then return $ T.unlines $ map ("\t" <>) $ T.lines contents - else return $ "```\n" <> contents <> "```" + bqLevel <- asks stBlockQuoteLevel + let bqPrefix = case bqLevel of + 0 -> "" + n -> T.replicate n ">" <> " " + return $ bqPrefix <> + " + (case classes of + [] -> "" + (x:_) -> " " <> fromMaybe x (M.lookup x languageNames)) <> + ">\n" <> str <> + (if "\n" `T.isSuffixOf` str then "" else "\n") <> "
    \n" + +blockToTxt2Tags opts (BlockQuote blocks) = + local (\st -> st{ stBlockQuoteLevel = stBlockQuoteLevel st + 1 }) + (blockListToTxt2Tags opts blocks) blockToTxt2Tags opts (Table _ blkCapt specs thead tbody tfoot) = do let (capt, aligns, _, headers, rows) = toLegacyTable blkCapt specs thead tbody tfoot @@ -179,7 +169,8 @@ blockToTxt2Tags opts (Table _ blkCapt specs thead tbody tfoot) = do then return [] else zipWithM (tableItemToTxt2Tags opts) aligns headers rows' <- mapM (zipWithM (tableItemToTxt2Tags opts) aligns) rows - let widths = map (maximum . map T.length) $ transpose (headers':rows') + let widths = map (maybe 0 maximum . nonEmpty . map T.length) + $ transpose (headers':rows') let padTo (width, al) s = case width - T.length s of x | x > 0 -> @@ -196,102 +187,77 @@ blockToTxt2Tags opts (Table _ blkCapt specs thead tbody tfoot) = do (if null headers' then "" else renderRow "|" headers' <> "\n") <> T.unlines (map (renderRow "|") rows') -blockToTxt2Tags opts x@(BulletList items) = do - oldUseTags <- asks stUseTags +blockToTxt2Tags opts (BulletList items) = do indent <- asks stIndent backSlash <- asks stBackSlashLB - let useTags = oldUseTags || not (isSimpleList x) - if useTags - then do - contents <- local (\s -> s { stUseTags = True }) - (mapM (listItemToTxt2Tags opts) items) - return $ "```\n" <> vcat contents <> "```\n" - else do - contents <- local (\s -> s { stIndent = stIndent s <> " " - , stBackSlashLB = backSlash}) + contents <- local (\s -> s { stIndent = stIndent s <> " " + , stBackSlashLB = backSlash}) (mapM (listItemToTxt2Tags opts) items) - return $ vcat contents <> if T.null indent then "\n" else "" + return $ vcat contents <> if T.null indent then "\n" else "" -blockToTxt2Tags opts x@(OrderedList attribs items) = do - oldUseTags <- asks stUseTags +blockToTxt2Tags opts (OrderedList _attribs items) = do indent <- asks stIndent backSlash <- asks stBackSlashLB - let useTags = oldUseTags || not (isSimpleList x) - if useTags - then do - contents <- local (\s -> s { stUseTags = True }) - (mapM (orderedListItemToTxt2Tags opts) items) - return $ "+ " <> listAttribsToString attribs <> ">\n" <> vcat contents <> "\n" - else do - contents <- local (\s -> s { stIndent = stIndent s <> " " - , stBackSlashLB = backSlash}) - (mapM (orderedListItemToTxt2Tags opts) items) - return $ vcat contents <> if T.null indent then "\n" else "" + contents <- local (\s -> s { stIndent = stIndent s <> " " + , stBackSlashLB = backSlash}) + (mapM (orderedListItemToTxt2Tags opts) items) + return $ vcat contents <> if T.null indent then "\n" else "" + +blockToTxt2Tags opts (Figure attr capt body) = + blockToTxt2Tags opts $ figureDiv attr capt body -- TODO Need to decide how to make definition lists work on Txt2Tags - I don't think there -- is a specific representation of them. -- TODO This creates double '; ; ' if there is a bullet or ordered list inside a definition list -blockToTxt2Tags opts x@(DefinitionList items) = do - oldUseTags <- asks stUseTags +blockToTxt2Tags opts (DefinitionList items) = do indent <- asks stIndent backSlash <- asks stBackSlashLB - let useTags = oldUseTags || not (isSimpleList x) - if useTags - then do - contents <- local (\s -> s { stUseTags = True }) - (mapM (definitionListItemToTxt2Tags opts) items) - return $ ": \n" <> vcat contents <> "\n" - else do - contents <- local (\s -> s { stIndent = stIndent s <> " " - , stBackSlashLB = backSlash}) - (mapM (definitionListItemToTxt2Tags opts) items) - return $ vcat contents <> if T.null indent then "\n" else "" + contents <- local (\s -> s { stIndent = stIndent s <> " " + , stBackSlashLB = backSlash}) + (mapM (definitionListItemToTxt2Tags opts) items) + return $ vcat contents <> if T.null indent then "\n" else "" -- Auxiliary functions for lists: --- | Convert ordered list attributes to HTML attribute string -listAttribsToString :: ListAttributes -> Text -listAttribsToString (startnum, numstyle, _) = - let numstyle' = camelCaseToHyphenated $ tshow numstyle - in (if startnum /= 1 - then " start=\"" <> tshow startnum <> "\"" - else "") <> - (if numstyle /= DefaultStyle - then " style=\"list-style-type: " <> numstyle' <> ";\"" - else "") - -- | Convert bullet list item (list of blocks) to Txt2Tags. listItemToTxt2Tags :: PandocMonad m => WriterOptions -> [Block] -> Txt2Tags m Text listItemToTxt2Tags opts items = do - useTags <- asks stUseTags - if useTags - then do - contents <- blockListToTxt2Tags opts items - return $ "
  • " <> contents <> "
  • " - else do - bs <- mapM (blockToTxt2Tags opts) items - let contents = case items of - [_, CodeBlock _ _] -> T.concat bs - _ -> vcat bs - indent <- asks stIndent - backSlash <- asks stBackSlashLB - let indent' = if backSlash then T.drop 2 indent else indent - return $ indent' <> "- " <> contents + bqLevel <- asks stBlockQuoteLevel + let bqPrefix = case bqLevel of + 0 -> "" + n -> T.replicate n ">" <> " " + let useWrap = not (isSimpleListItem items) + bs <- mapM (blockToTxt2Tags opts) items + let contents = case items of + [_, CodeBlock _ _] -> T.concat bs + _ -> vcat bs + indent <- asks stIndent + backSlash <- asks stBackSlashLB + let indent' = if backSlash then T.drop 2 indent else indent + return $ bqPrefix <> indent' <> "- " <> + if useWrap + then "\n" <> contents <> "\n" + else contents -- | Convert ordered list item (list of blocks) to Txt2Tags. -- | TODO Emiminate dreadful duplication of text from listItemToTxt2Tags orderedListItemToTxt2Tags :: PandocMonad m => WriterOptions -> [Block] -> Txt2Tags m Text orderedListItemToTxt2Tags opts items = do - contents <- blockListToTxt2Tags opts items - useTags <- asks stUseTags - if useTags - then return $ "
  • " <> contents <> "
  • " - else do - indent <- asks stIndent - backSlash <- asks stBackSlashLB - let indent' = if backSlash then T.drop 2 indent else indent - return $ indent' <> "+ " <> contents + bqLevel <- asks stBlockQuoteLevel + let bqPrefix = case bqLevel of + 0 -> "" + n -> T.replicate n ">" <> " " + let useWrap = not (isSimpleListItem items) + contents <- local (\st -> st{ stBlockQuoteLevel = 0 }) + (blockListToTxt2Tags opts items) + indent <- asks stIndent + backSlash <- asks stBackSlashLB + let indent' = if backSlash then T.drop 2 indent else indent + return $ bqPrefix <> indent' <> "+ " <> + if useWrap + then "\n" <> contents <> "\n" + else contents -- | Convert definition list item (label, list of blocks) to Txt2Tags. definitionListItemToTxt2Tags :: PandocMonad m @@ -299,43 +265,46 @@ definitionListItemToTxt2Tags :: PandocMonad m -> ([Inline],[[Block]]) -> Txt2Tags m Text definitionListItemToTxt2Tags opts (label, items) = do + let useWrap = not (all isSimpleListItem items) + bqLevel <- asks stBlockQuoteLevel + let bqPrefix = case bqLevel of + 0 -> "" + n -> T.replicate n ">" <> " " labelText <- inlineListToTxt2Tags opts label - contents <- mapM (blockListToTxt2Tags opts) items - useTags <- asks stUseTags - if useTags - then return $ "
    " <> labelText <> "
    \n" <> - T.intercalate "\n" (map (\d -> "
    " <> d <> "
    ") contents) - else do - indent <- asks stIndent - backSlash <- asks stBackSlashLB - let indent' = if backSlash then T.drop 2 indent else indent - return $ indent' <> "* **" <> labelText <> "** " <> T.concat contents - --- | True if the list can be handled by simple wiki markup, False if HTML tags will be needed. -isSimpleList :: Block -> Bool -isSimpleList x = - case x of - BulletList items -> all isSimpleListItem items - OrderedList (1, _, _) items -> all isSimpleListItem items - DefinitionList items -> all (all isSimpleListItem . snd) items - _ -> False + contents <- local (\st -> st{ stBlockQuoteLevel = 0 }) + (mapM (blockListToTxt2Tags opts) items) + indent <- asks stIndent + backSlash <- asks stBackSlashLB + let indent' = if backSlash then T.drop 2 indent else indent + return $ bqPrefix <> indent' <> "* **" <> labelText <> "** " <> + if useWrap + then "\n" <> vcat contents <> "\n" + else T.intercalate "; " contents -- | True if list item can be handled with the simple wiki syntax. False if --- HTML tags will be needed. +-- WRAP tags will be needed. isSimpleListItem :: [Block] -> Bool isSimpleListItem [] = True isSimpleListItem [x, CodeBlock{}] | isPlainOrPara x = True +isSimpleListItem (Div _ bs : ys) = -- see #8920 + isSimpleListItem bs && all isSimpleList ys isSimpleListItem (x:ys) | isPlainOrPara x = all isSimpleList ys isSimpleListItem _ = False +--- | True if the list can be handled by simple wiki markup, False if HTML tags will be needed. + +isSimpleList :: Block -> Bool +isSimpleList x = + case x of + BulletList items -> all isSimpleListItem items + OrderedList (1, _, _) items -> all isSimpleListItem items + DefinitionList items -> all (all isSimpleListItem . snd) items + _ -> False isPlainOrPara :: Block -> Bool isPlainOrPara (Plain _) = True isPlainOrPara (Para _) = True isPlainOrPara _ = False -isSimpleBlockQuote :: [Block] -> Bool -isSimpleBlockQuote bs = all isPlainOrPara bs - -- | Concatenates strings with line breaks between them. vcat :: [Text] -> Text vcat = T.intercalate "\n" @@ -365,8 +334,9 @@ tableItemToTxt2Tags opts align' item = do (if align' == AlignLeft || align' == AlignCenter then " " else "") - contents <- local (\s -> s { stBackSlashLB = True }) $ - blockListToTxt2Tags opts item + contents <- local (\s -> s { stBackSlashLB = True + , stBlockQuoteLevel = 0 }) $ + blockListToTxt2Tags opts item return $ mkcell contents -- | Convert list of Pandoc block elements to Txt2Tags. @@ -456,9 +426,10 @@ inlineToTxt2Tags _ (Math mathType str) = return $ delim <> str <> delim DisplayMath -> "$$" InlineMath -> "$" -inlineToTxt2Tags _ il@(RawInline f str) +inlineToTxt2Tags opts il@(RawInline f str) | f == Format "Txt2Tags" = return str - | f == Format "html" = return $ "" <> str <> "" + | f == Format "html" + , isEnabled Ext_raw_html opts = return $ "" <> str <> "" | otherwise = "" <$ report (InlineNotRendered il) inlineToTxt2Tags _ LineBreak = do @@ -495,7 +466,8 @@ inlineToTxt2Tags opts (Image attr alt (source, tit)) = do return $ "[" <> source <> imageDims opts attr <> txt <> "]" inlineToTxt2Tags opts (Note contents) = do - contents' <- blockListToTxt2Tags opts contents + contents' <- local (\st -> st{ stBlockQuoteLevel = 0 }) + (blockListToTxt2Tags opts contents) return $ "((" <> contents' <> "))" -- note - may not work for notes with multiple blocks @@ -509,3 +481,19 @@ imageDims opts attr = go (toPx $ dimension Width attr) (toPx $ dimension Height go (Just w) (Just h) = "?" <> w <> "x" <> h go Nothing (Just h) = "?0x" <> h go Nothing Nothing = "" + +languageNames :: M.Map Text Text +languageNames = M.fromList + [("cs", "csharp") + ,("coffee", "cofeescript") + ,("commonlisp", "lisp") + ,("gcc", "c") + ,("html", "html5") + ,("makefile", "make") + ,("objectivec", "objc") + ,("r", "rsplus") + ,("sqlmysql", "mysql") + ,("sqlpostgresql", "postgresql") + ,("sci", "scilab") + ,("xorg", "xorgconf") + ] From 3d611d626c09e068d8ed2091d6bcd919d3f7b242 Mon Sep 17 00:00:00 2001 From: farvardin Date: Sun, 15 Feb 2026 23:54:59 +0100 Subject: [PATCH 04/11] disable ci --- .github/workflows/ci.yml | 245 --------------------- .github/workflows/commit-validation-pr.yml | 32 --- 2 files changed, 277 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b6fa5eddfd88..e69de29bb2d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,245 +0,0 @@ -name: CI tests - -on: - push: - branches: - - 'main' - paths-ignore: - - 'doc/*.md' - - 'MANUAL.txt' - - '*.md' - - '.cirrus.yml' - - 'RELEASE_CHECKLIST' - - 'BUGS' - - 'README.template' - - 'hie.yaml' - - '*.nix' - - 'tools/**' - - 'linux/**' - - 'macos/**' - - 'windows/**' - - 'man/**' - pull_request: - paths-ignore: - - 'doc/*.md' - - 'MANUAL.txt' - - '*.md' - - 'RELEASE_CHECKLIST' - - 'BUGS' - - 'README.template' - - 'hie.yaml' - - '*.nix' - - 'tools/**' - - 'linux/**' - - 'macos/**' - - 'windows/**' - - 'man/**' - -permissions: - contents: read - -jobs: - linux: - - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - versions: - # NOTE: Update `CONTRIBUTING.md` when changing the minimum version - - ghc: '9.6' - cabal: 'latest' - cabalopts: '--ghc-option=-Werror' - - ghc: '9.8' - cabal: 'latest' - cabalopts: '--ghc-option=-Werror' - - ghc: '9.10' - cabal: 'latest' - cabalopts: '--ghc-option=-Werror' - - ghc: '9.12' - cabal: 'latest' - cabalopts: '--ghc-option=-Werror' - - steps: - - uses: actions/checkout@v6 - - # needed by memory - - name: Install numa - run: sudo apt-get install libnuma-dev - - - name: Workaround runner image issue - # https://github.com/actions/runner-images/issues/7061 - run: sudo chown -R $USER /usr/local/.ghcup - - - name: Install cabal/ghc - run: | - ghcup install ghc --set ${{ matrix.versions.ghc }} - ghcup install cabal --set ${{ matrix.versions.cabal }} - - # declare/restore cached things - - - name: Cache cabal global package db - id: cabal-global - uses: actions/cache@v5 - with: - path: | - ~/.local/state/cabal - key: ${{ runner.os }}-${{ matrix.versions.ghc }}-${{ matrix.versions.cabal }}-cabal-global-${{ secrets.CACHE_VERSION }} - - - name: Cache cabal work - id: cabal-local - uses: actions/cache@v5 - with: - path: | - dist-newstyle - key: ${{ runner.os }}-${{ matrix.versions.ghc }}-${{ matrix.versions.cabal }}-cabal-local-${{ secrets.CACHE_VERSION }} - - - name: Update cabal - run: | - cabal update - - - name: Build and test - run: | - cabal build ${{ matrix.versions.cabalopts }} --enable-tests --disable-optimization all - cabal test ${{ matrix.versions.cabalopts }} --disable-optimization all - - linux-stack: - - runs-on: ubuntu-latest - strategy: - fail-fast: true - steps: - - uses: actions/checkout@v6 - - # needed by memory - - name: Install numa - run: sudo apt-get install libnuma-dev - - # declare/restore cached things - - name: Cache stack global package db - id: stack-global - uses: actions/cache@v5 - with: - path: | - ~/.stack - key: ${{ runner.os }}-${{ matrix.versions.ghc }}-${{ matrix.versions.cabal }}-stack-global-${{ secrets.CACHE_VERSION }} - - - name: Cache stack work - id: stack-local - uses: actions/cache@v5 - with: - path: | - .stack-work - key: ${{ runner.os }}-${{ matrix.versions.ghc }}-${{ matrix.versions.cabal }}-stack-local-${{ secrets.CACHE_VERSION }} - - - name: Build and test - run: | - stack test --fast - - windows: - - runs-on: windows-2022 - - steps: - - uses: actions/checkout@v6 - - - name: Install ghc - run: | - ghcup install ghc --set 9.10 - ghcup install cabal --set latest - - # declare/restore cached things - - - name: Cache cabal global package db - id: cabal-global - uses: actions/cache@v5 - with: - path: | - C:\cabal\store - key: ${{ runner.os }}-appdata-cabal-${{ hashFiles('cabal.project') }}-${{ secrets.CACHE_VERSION }} - - - name: Cache cabal work - id: cabal-local - uses: actions/cache@v5 - with: - path: | - dist-newstyle - key: ${{ runner.os }}-stack-work-${{ hashFiles('cabal.project') }}-${{ secrets.CACHE_VERSION }} - - - name: Build and test - run: | - cabal update - cabal test --ghc-option=-Werror -fhttp --enable-tests --disable-optimization --ghc-options=-Werror all - -# The nix build ran out of resources. Not sure how to fix. -# -# nix-wasm: -# -# runs-on: ubuntu-latest -# strategy: -# fail-fast: true -# steps: -# - uses: actions/checkout@v6 -# # Remove unneeded cruft in runner so we have room for Nix: -# - uses: wimpysworld/nothing-but-nix@v9 -# with: -# hatchet-protocol: 'rampage' -# - uses: DeterminateSystems/nix-installer-action@main -# - uses: DeterminateSystems/magic-nix-cache-action@main -# - name: Cache cabal work -# id: cabal-local -# uses: actions/cache@v5 -# with: -# path: | -# dist-newstyle -# key: ${{ runner.os }}-nix-cabal-local-${{ secrets.CACHE_VERSION }} -# - run: | -# nix develop --command bash -c "make pandoc.wasm" - -# We no longer run the macos tests, to make CI faster. -# macos: - -# runs-on: macos-12 -# strategy: -# fail-fast: true -# matrix: -# versions: -# - ghc: '8.8.4' -# cabal: '3.2' - -# steps: -# - uses: actions/checkout@v6 - -# - name: Install cabal/ghc -# run: | -# ghcup install ghc --set ${{ matrix.versions.ghc }} -# ghcup install cabal ${{ matrix.versions.cabal }} - -# # declare/restore cached things - -# - name: Cache cabal global package db -# id: cabal-global -# uses: actions/cache@v5 -# with: -# path: | -# ~/.cabal -# key: ${{ runner.os }}-${{ matrix.versions.ghc }}-${{ matrix.versions.cabal }}-cabal-global-${{ secrets.CACHE_VERSION }} - -# - name: Cache cabal work -# id: cabal-local -# uses: actions/cache@v5 -# with: -# path: | -# dist-newstyle -# key: ${{ runner.os }}-${{ matrix.versions.ghc }}-${{ matrix.versions.cabal }}-cabal-local-${{ secrets.CACHE_VERSION }} - -# - name: Install dependencies -# run: | -# cabal v2-update -# cabal v2-build --dependencies-only --enable-tests --disable-optimization -# - name: Build and test -# run: | -# cabal v2-build --enable-tests --disable-optimization 2>&1 | tee build.log -# # fail if warnings in local build -# ! grep -q ": *[Ww]arning:" build.log || exit 1 -# cabal v2-test --disable-optimization --test-option=--hide-successes --test-option=--ansi-tricks=false diff --git a/.github/workflows/commit-validation-pr.yml b/.github/workflows/commit-validation-pr.yml index b8aab60d3356..e69de29bb2d1 100644 --- a/.github/workflows/commit-validation-pr.yml +++ b/.github/workflows/commit-validation-pr.yml @@ -1,32 +0,0 @@ -name: commit-validation-pr -on: [pull_request] - -permissions: - contents: read - -jobs: - check-commit-msg-length: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - name: Check commit message length - run: | - git log ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} | ( - longlines=0 - while IFS='' read -r line; do - if [ "${#line}" -gt 78 ] ; then - if echo "$line" | grep -q '^\s*https://\S*\s*$'; then - echo "Ignoring long line with URL." - else - echo "Overlong line: ${line}" >&2 - if echo "$line" | grep -q 'https://'; then - echo "Put a long URL on a line by itself." - fi - longlines=$(( longlines + 1 )) - fi - fi - done - [ "${longlines}" -eq 0 ] - ) From 0d7b179030c1fe64768b5a431383987aece1bdee Mon Sep 17 00:00:00 2001 From: farvardin Date: Mon, 16 Feb 2026 08:42:36 +0100 Subject: [PATCH 05/11] adding basic instructions --- test-t2t.sh | 1 + updating_fork.txt | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100755 test-t2t.sh create mode 100644 updating_fork.txt diff --git a/test-t2t.sh b/test-t2t.sh new file mode 100755 index 000000000000..77b588722a10 --- /dev/null +++ b/test-t2t.sh @@ -0,0 +1 @@ +pandoc -f html -t t2t test/writer.html5 diff --git a/updating_fork.txt b/updating_fork.txt new file mode 100644 index 000000000000..b067dcfe36d3 --- /dev/null +++ b/updating_fork.txt @@ -0,0 +1,22 @@ +== 2022 == + +git pull firstrepo master +git push origin +cabal install --overwrite-policy=always + +cabal build + +cabal install -fembed_data_files + +== 2026 == + +--cabal update-- + +stack setup +stack upgrade +stack setup +stack build +stack install + +copy to ~/.local/bin/pandoc + From 837205a279fd620a521ef16628eafb4cd98d0cd6 Mon Sep 17 00:00:00 2001 From: luginf Date: Fri, 10 Apr 2026 10:58:49 +0200 Subject: [PATCH 06/11] Txt2Tags writer: fix syntax, add tests - Fix inline code to use `` `` `` `` instead of `''%%...%%''` - Fix raw block/inline format name to lowercase `txt2tags` - Fix HTML raw passthrough syntax (`"""\n...\n"""` for blocks, `""..."""` for inline) - Remove unused `stBlockQuoteLevel` field; blockquotes render as plain content - Fix `escapeString` to also escape `--` and ` `` ` - Fix definition list: use `: **term**` with indented content instead of DokuWiki `* **term**` - Remove leftover DokuWiki-specific `languageNames` map and `isSimpleList` helpers - Add `Tests.Writers.Txt2Tags` with unit tests covering inline formatting, escaping, headers, code blocks, lists, definition lists, blockquotes, horizontal rules, tables, links, images, and raw blocks Co-Authored-By: Claude Sonnet 4.6 --- pandoc.cabal | 1 + src/Text/Pandoc/Writers/Txt2Tags.hs | 265 +++++++++------------------- test/Tests/Writers/Txt2Tags.hs | 124 +++++++++++++ test/test-pandoc.hs | 2 + 4 files changed, 208 insertions(+), 184 deletions(-) create mode 100644 test/Tests/Writers/Txt2Tags.hs diff --git a/pandoc.cabal b/pandoc.cabal index 627e4ed368ef..1ee2a381afbd 100644 --- a/pandoc.cabal +++ b/pandoc.cabal @@ -922,6 +922,7 @@ test-suite test-pandoc Tests.Writers.Ms Tests.Writers.AnnotatedTable Tests.Writers.BBCode + Tests.Writers.Txt2Tags benchmark benchmark-pandoc import: common-executable diff --git a/src/Text/Pandoc/Writers/Txt2Tags.hs b/src/Text/Pandoc/Writers/Txt2Tags.hs index facf66d9f740..9b0c869c21c8 100644 --- a/src/Text/Pandoc/Writers/Txt2Tags.hs +++ b/src/Text/Pandoc/Writers/Txt2Tags.hs @@ -13,17 +13,6 @@ Conversion of 'Pandoc' documents to Txt2Tags markup. Txt2Tags: -} -{- - It works for most of the syntax. But some improvements or fix can be made: - - On lists (2 extra lines to terminate a list) - - On the 3 lines header at the begining of a file - - Definition lists are broken - - Tables could be improved - - Some formats make better results than others (html is ok, md is somehow broken) - - code related to dokuwiki only could be removed - - a test case should be made --} - module Text.Pandoc.Writers.Txt2Tags ( writeTxt2Tags ) where import Control.Monad (zipWithM) import Control.Monad.Reader (ReaderT, asks, local, runReaderT) @@ -45,25 +34,21 @@ import Text.Pandoc.URI (escapeURI, isURI) import Text.Pandoc.Templates (renderTemplate) import Text.DocLayout (render, literal) import Text.Pandoc.Writers.Shared (defField, metaToContext, toLegacyTable) -import Data.Maybe (fromMaybe) -import qualified Data.Map as M data WriterState = WriterState { } data WriterEnvironment = WriterEnvironment { - stIndent :: Text -- Indent after the marker at the beginning of list items - , stBackSlashLB :: Bool -- True if we should produce formatted strings with newlines (as in a table cell) - , stBlockQuoteLevel :: Int -- Block quote level + stIndent :: Text -- Indentation prefix for the current list nesting level + , stBackSlashLB :: Bool -- True inside table cells (use \\ for line breaks) } instance Default WriterState where def = WriterState {} instance Default WriterEnvironment where - def = WriterEnvironment { stIndent = "" - , stBackSlashLB = False - , stBlockQuoteLevel = 0 } + def = WriterEnvironment { stIndent = "" + , stBackSlashLB = False } type Txt2Tags m = ReaderT WriterEnvironment (StateT WriterState m) @@ -92,8 +77,11 @@ pandocToTxt2Tags opts (Pandoc meta blocks) = do Just tpl -> render Nothing $ renderTemplate tpl context -- | Escape special characters for Txt2Tags. +-- The %%text%% syntax disables Txt2Tags formatting interpretation. escapeString :: Text -> Text -escapeString = T.replace "__" "%%__%%" . +escapeString = T.replace "``" "%%``%%" . + T.replace "--" "%%--%%" . + T.replace "__" "%%__%%" . T.replace "**" "%%**%%" . T.replace "//" "%%//%%" @@ -106,57 +94,42 @@ blockToTxt2Tags :: PandocMonad m blockToTxt2Tags opts (Div _attrs bs) = do contents <- blockListToTxt2Tags opts bs indent <- asks stIndent - return $ contents <> if T.null indent then "\n\n" else "" + return $ contents <> if T.null indent then "\n" else "" blockToTxt2Tags opts (Plain inlines) = inlineListToTxt2Tags opts inlines blockToTxt2Tags opts (Para inlines) = do - bqLevel <- asks stBlockQuoteLevel - let bqPrefix = case bqLevel of - 0 -> "" - n -> T.replicate n ">" <> " " indent <- asks stIndent contents <- inlineListToTxt2Tags opts inlines - return $ bqPrefix <> contents <> if T.null indent then "\n" else "" + return $ contents <> if T.null indent then "\n" else "" blockToTxt2Tags opts (LineBlock lns) = blockToTxt2Tags opts $ linesToPara lns blockToTxt2Tags opts b@(RawBlock f str) - | f == Format "Txt2Tags" = return str - -- See https://www.Txt2Tags.org/wiki:syntax - -- use uppercase HTML tag for block-level content: + | f == Format "txt2tags" = return str + -- Use the Txt2Tags raw area syntax (""") for block-level HTML pass-through: | f == Format "html" - , isEnabled Ext_raw_html opts = return $ "\n" <> str <> "\n" - | otherwise = "" <$ - report (BlockNotRendered b) + , isEnabled Ext_raw_html opts = return $ "\"\"\"\n" <> str <> "\n\"\"\"\n" + | otherwise = "" <$ report (BlockNotRendered b) -blockToTxt2Tags _ HorizontalRule = return "\n---------------\n" +blockToTxt2Tags _ HorizontalRule = return "\n---\n" blockToTxt2Tags opts (Header level _ inlines) = do - -- emphasis, links etc. not allowed in headers, apparently, - -- so we remove formatting: + -- Formatting is not allowed in headers, so strip it contents <- inlineListToTxt2Tags opts $ removeFormatting inlines - let eqs = T.replicate level "=" + let eqs = T.replicate level "=" return $ eqs <> " " <> contents <> " " <> eqs <> "\n" -blockToTxt2Tags _ (CodeBlock (_,classes,_) str) = do - bqLevel <- asks stBlockQuoteLevel - let bqPrefix = case bqLevel of - 0 -> "" - n -> T.replicate n ">" <> " " - return $ bqPrefix <> - " - (case classes of - [] -> "" - (x:_) -> " " <> fromMaybe x (M.lookup x languageNames)) <> - ">\n" <> str <> - (if "\n" `T.isSuffixOf` str then "" else "\n") <> "
    \n" +-- | Txt2Tags verbatim area uses ``` delimiters. +blockToTxt2Tags _ (CodeBlock _ str) = + return $ "```\n" <> str <> + (if "\n" `T.isSuffixOf` str then "" else "\n") <> "```\n" +-- | Txt2Tags has no blockquote syntax; render content without special markup. blockToTxt2Tags opts (BlockQuote blocks) = - local (\st -> st{ stBlockQuoteLevel = stBlockQuoteLevel st + 1 }) - (blockListToTxt2Tags opts blocks) + blockListToTxt2Tags opts blocks blockToTxt2Tags opts (Table _ blkCapt specs thead tbody tfoot) = do let (capt, aligns, _, headers, rows) = toLegacyTable blkCapt specs thead tbody tfoot @@ -164,7 +137,7 @@ blockToTxt2Tags opts (Table _ blkCapt specs thead tbody tfoot) = do then return "" else do c <- inlineListToTxt2Tags opts capt - return $ "" <> c <> "\n" + return $ c <> "\n" headers' <- if all null headers then return [] else zipWithM (tableItemToTxt2Tags opts) aligns headers @@ -181,39 +154,30 @@ blockToTxt2Tags opts (Table _ blkCapt specs thead tbody tfoot) = do else T.replicate (x `div` 2) " " <> s <> T.replicate (x - x `div` 2) " " | otherwise -> s - let renderRow sep cells = sep <> - T.intercalate sep (zipWith padTo (zip widths aligns) cells) <> sep + let renderRow sep cells = + sep <> T.intercalate sep (zipWith padTo (zip widths aligns) cells) <> sep return $ captionDoc <> (if null headers' then "" else renderRow "|" headers' <> "\n") <> T.unlines (map (renderRow "|") rows') blockToTxt2Tags opts (BulletList items) = do indent <- asks stIndent - backSlash <- asks stBackSlashLB - contents <- local (\s -> s { stIndent = stIndent s <> " " - , stBackSlashLB = backSlash}) - (mapM (listItemToTxt2Tags opts) items) + contents <- local (\s -> s { stIndent = stIndent s <> " " }) + (mapM (listItemToTxt2Tags opts) items) return $ vcat contents <> if T.null indent then "\n" else "" blockToTxt2Tags opts (OrderedList _attribs items) = do indent <- asks stIndent - backSlash <- asks stBackSlashLB - contents <- local (\s -> s { stIndent = stIndent s <> " " - , stBackSlashLB = backSlash}) + contents <- local (\s -> s { stIndent = stIndent s <> " " }) (mapM (orderedListItemToTxt2Tags opts) items) return $ vcat contents <> if T.null indent then "\n" else "" blockToTxt2Tags opts (Figure attr capt body) = blockToTxt2Tags opts $ figureDiv attr capt body --- TODO Need to decide how to make definition lists work on Txt2Tags - I don't think there --- is a specific representation of them. --- TODO This creates double '; ; ' if there is a bullet or ordered list inside a definition list blockToTxt2Tags opts (DefinitionList items) = do indent <- asks stIndent - backSlash <- asks stBackSlashLB - contents <- local (\s -> s { stIndent = stIndent s <> " " - , stBackSlashLB = backSlash}) + contents <- local (\s -> s { stIndent = stIndent s <> " " }) (mapM (definitionListItemToTxt2Tags opts) items) return $ vcat contents <> if T.null indent then "\n" else "" @@ -223,98 +187,54 @@ blockToTxt2Tags opts (DefinitionList items) = do listItemToTxt2Tags :: PandocMonad m => WriterOptions -> [Block] -> Txt2Tags m Text listItemToTxt2Tags opts items = do - bqLevel <- asks stBlockQuoteLevel - let bqPrefix = case bqLevel of - 0 -> "" - n -> T.replicate n ">" <> " " - let useWrap = not (isSimpleListItem items) bs <- mapM (blockToTxt2Tags opts) items - let contents = case items of - [_, CodeBlock _ _] -> T.concat bs - _ -> vcat bs indent <- asks stIndent - backSlash <- asks stBackSlashLB - let indent' = if backSlash then T.drop 2 indent else indent - return $ bqPrefix <> indent' <> "- " <> - if useWrap - then "\n" <> contents <> "\n" - else contents + -- BulletList increments indent by " ", so T.drop 2 gives the marker position: + -- level 1 -> "", level 2 -> " ", etc. + let markerIndent = T.drop 2 indent + -- Use newlines between blocks; T.stripEnd preserves leading spaces (e.g. nested + -- list indentation) while removing trailing newlines that would create blank lines. + let contents = T.intercalate "\n" (map T.stripEnd bs) + return $ markerIndent <> "- " <> contents -- | Convert ordered list item (list of blocks) to Txt2Tags. --- | TODO Emiminate dreadful duplication of text from listItemToTxt2Tags orderedListItemToTxt2Tags :: PandocMonad m => WriterOptions -> [Block] -> Txt2Tags m Text orderedListItemToTxt2Tags opts items = do - bqLevel <- asks stBlockQuoteLevel - let bqPrefix = case bqLevel of - 0 -> "" - n -> T.replicate n ">" <> " " - let useWrap = not (isSimpleListItem items) - contents <- local (\st -> st{ stBlockQuoteLevel = 0 }) - (blockListToTxt2Tags opts items) + bs <- mapM (blockToTxt2Tags opts) items indent <- asks stIndent - backSlash <- asks stBackSlashLB - let indent' = if backSlash then T.drop 2 indent else indent - return $ bqPrefix <> indent' <> "+ " <> - if useWrap - then "\n" <> contents <> "\n" - else contents + -- OrderedList increments indent by " ", so T.drop 2 gives the marker position + let markerIndent = T.drop 2 indent + let contents = T.intercalate "\n" (map T.stripEnd bs) + return $ markerIndent <> "+ " <> contents -- | Convert definition list item (label, list of blocks) to Txt2Tags. +-- Txt2Tags has no native definition list syntax; we use ": **term**" as a +-- label followed by indented definition content. definitionListItemToTxt2Tags :: PandocMonad m => WriterOptions - -> ([Inline],[[Block]]) + -> ([Inline], [[Block]]) -> Txt2Tags m Text definitionListItemToTxt2Tags opts (label, items) = do - let useWrap = not (all isSimpleListItem items) - bqLevel <- asks stBlockQuoteLevel - let bqPrefix = case bqLevel of - 0 -> "" - n -> T.replicate n ">" <> " " labelText <- inlineListToTxt2Tags opts label - contents <- local (\st -> st{ stBlockQuoteLevel = 0 }) - (mapM (blockListToTxt2Tags opts) items) - indent <- asks stIndent - backSlash <- asks stBackSlashLB - let indent' = if backSlash then T.drop 2 indent else indent - return $ bqPrefix <> indent' <> "* **" <> labelText <> "** " <> - if useWrap - then "\n" <> vcat contents <> "\n" - else T.intercalate "; " contents - --- | True if list item can be handled with the simple wiki syntax. False if --- WRAP tags will be needed. -isSimpleListItem :: [Block] -> Bool -isSimpleListItem [] = True -isSimpleListItem [x, CodeBlock{}] | isPlainOrPara x = True -isSimpleListItem (Div _ bs : ys) = -- see #8920 - isSimpleListItem bs && all isSimpleList ys -isSimpleListItem (x:ys) | isPlainOrPara x = all isSimpleList ys -isSimpleListItem _ = False ---- | True if the list can be handled by simple wiki markup, False if HTML tags will be needed. - -isSimpleList :: Block -> Bool -isSimpleList x = - case x of - BulletList items -> all isSimpleListItem items - OrderedList (1, _, _) items -> all isSimpleListItem items - DefinitionList items -> all (all isSimpleListItem . snd) items - _ -> False - -isPlainOrPara :: Block -> Bool -isPlainOrPara (Plain _) = True -isPlainOrPara (Para _) = True -isPlainOrPara _ = False - --- | Concatenates strings with line breaks between them. + contents <- mapM (blockListToTxt2Tags opts) items + indent <- asks stIndent + let markerIndent = T.drop 2 indent + let defIndent = markerIndent <> " " + let fmtItem c = defIndent <> T.stripEnd c + return $ markerIndent <> ": **" <> labelText <> "**\n" <> + T.intercalate "\n" (map fmtItem contents) + +-- | Concatenates strings with newlines between them. vcat :: [Text] -> Text vcat = T.intercalate "\n" --- | For each string in the input list, convert all newlines to --- Txt2Tags escaped newlines. Then concat the list using double linebreaks. +-- | For each string in the input list, replace newlines with Txt2Tags line +-- breaks (\\). Then join the list using double line breaks to simulate +-- paragraph breaks in table cells. backSlashLineBreaks :: [Text] -> Text backSlashLineBreaks ls = vcatBackSlash $ map (T.pack . escape . T.unpack) ls where - vcatBackSlash = T.intercalate "\\\\ \\\\ " -- simulate paragraphs. + vcatBackSlash = T.intercalate "\\\\ \\\\ " -- simulate paragraph break escape ['\n'] = "" -- remove trailing newlines escape ('\n':cs) = "\\\\ " <> escape cs escape (c:cs) = c : escape cs @@ -328,14 +248,15 @@ tableItemToTxt2Tags :: PandocMonad m -> [Block] -> Txt2Tags m Text tableItemToTxt2Tags opts align' item = do + -- In Txt2Tags, alignment is indicated by spaces around cell content: + -- leading space -> right, trailing space -> left, both -> center. let mkcell x = (if align' == AlignRight || align' == AlignCenter then " " else "") <> x <> (if align' == AlignLeft || align' == AlignCenter then " " else "") - contents <- local (\s -> s { stBackSlashLB = True - , stBlockQuoteLevel = 0 }) $ + contents <- local (\s -> s { stBackSlashLB = True }) $ blockListToTxt2Tags opts item return $ mkcell contents @@ -386,6 +307,7 @@ inlineToTxt2Tags opts (Strikeout lst) = do contents <- inlineListToTxt2Tags opts lst return $ "--" <> contents <> "--" +-- Txt2Tags has no superscript/subscript syntax; fall back to HTML tags. inlineToTxt2Tags opts (Superscript lst) = do contents <- inlineListToTxt2Tags opts lst return $ "" <> contents <> "" @@ -404,39 +326,30 @@ inlineToTxt2Tags opts (Quoted DoubleQuote lst) = do contents <- inlineListToTxt2Tags opts lst return $ "\8220" <> contents <> "\8221" -inlineToTxt2Tags opts (Cite _ lst) = inlineListToTxt2Tags opts lst +inlineToTxt2Tags opts (Cite _ lst) = inlineListToTxt2Tags opts lst +-- | Inline code uses Txt2Tags verbatim syntax (double backticks). inlineToTxt2Tags _ (Code _ str) = - -- In Txt2Tags, text surrounded by '' is really just a font statement, i.e. , - -- and so other formatting can be present inside. - -- However, in pandoc, and markdown, inlined code doesn't contain formatting. - -- So I have opted for using %% to disable all formatting inside inline code blocks. - -- This gives the best results when converting from other formats to Txt2Tags, even if - -- the resultand code is a little ugly, for short strings that don't contain formatting - -- characters. - -- It does mean that if pandoc could ever read Txt2Tags, and so round-trip the format, - -- any formatting inside inlined code blocks would be lost, or presented incorrectly. - return $ "''%%" <> str <> "%%''" + return $ "``" <> str <> "``" inlineToTxt2Tags _ (Str str) = return $ escapeString str inlineToTxt2Tags _ (Math mathType str) = return $ delim <> str <> delim - -- note: str should NOT be escaped + -- note: str should NOT be escaped where delim = case mathType of DisplayMath -> "$$" InlineMath -> "$" inlineToTxt2Tags opts il@(RawInline f str) - | f == Format "Txt2Tags" = return str + | f == Format "txt2tags" = return str + -- Use the Txt2Tags inline raw syntax ("") for HTML pass-through: | f == Format "html" - , isEnabled Ext_raw_html opts = return $ "" <> str <> "" - | otherwise = "" <$ report (InlineNotRendered il) + , isEnabled Ext_raw_html opts = return $ "\"\"" <> str <> "\"\"" + | otherwise = "" <$ report (InlineNotRendered il) inlineToTxt2Tags _ LineBreak = do backSlash <- asks stBackSlashLB - return $ if backSlash - then "\n" - else "\\\\\n" + return $ if backSlash then "\n" else "\\\\\n" inlineToTxt2Tags opts SoftBreak = case writerWrapText opts of @@ -451,12 +364,13 @@ inlineToTxt2Tags opts (Link _ txt (src, _)) = do case txt of [Str s] | "mailto:" `T.isPrefixOf` src -> return $ "<" <> s <> ">" | escapeURI s == src -> return src - _ -> if isURI src - then return $ "[" <> label <> " " <> src <> "]" - else return $ "[" <> label <> " " <> src' <> "]" - where src' = case T.uncons src of - Just ('/',xs) -> xs -- with leading / it's a - _ -> src -- link to a help page + _ -> if isURI src + then return $ "[" <> label <> " " <> src <> "]" + else return $ "[" <> label <> " " <> src' <> "]" + where src' = case T.uncons src of + Just ('/', xs) -> xs + _ -> src + inlineToTxt2Tags opts (Image attr alt (source, tit)) = do alt' <- inlineListToTxt2Tags opts alt let txt = case (tit, alt) of @@ -465,11 +379,10 @@ inlineToTxt2Tags opts (Image attr alt (source, tit)) = do (_ , _ ) -> "|" <> tit return $ "[" <> source <> imageDims opts attr <> txt <> "]" +-- | Txt2Tags has no footnote syntax; render note content inline in parentheses. inlineToTxt2Tags opts (Note contents) = do - contents' <- local (\st -> st{ stBlockQuoteLevel = 0 }) - (blockListToTxt2Tags opts contents) - return $ "((" <> contents' <> "))" - -- note - may not work for notes with multiple blocks + contents' <- blockListToTxt2Tags opts contents + return $ "(" <> T.strip contents' <> ")" imageDims :: WriterOptions -> Attr -> Text imageDims opts attr = go (toPx $ dimension Width attr) (toPx $ dimension Height attr) @@ -481,19 +394,3 @@ imageDims opts attr = go (toPx $ dimension Width attr) (toPx $ dimension Height go (Just w) (Just h) = "?" <> w <> "x" <> h go Nothing (Just h) = "?0x" <> h go Nothing Nothing = "" - -languageNames :: M.Map Text Text -languageNames = M.fromList - [("cs", "csharp") - ,("coffee", "cofeescript") - ,("commonlisp", "lisp") - ,("gcc", "c") - ,("html", "html5") - ,("makefile", "make") - ,("objectivec", "objc") - ,("r", "rsplus") - ,("sqlmysql", "mysql") - ,("sqlpostgresql", "postgresql") - ,("sci", "scilab") - ,("xorg", "xorgconf") - ] diff --git a/test/Tests/Writers/Txt2Tags.hs b/test/Tests/Writers/Txt2Tags.hs new file mode 100644 index 000000000000..caa04ddaf738 --- /dev/null +++ b/test/Tests/Writers/Txt2Tags.hs @@ -0,0 +1,124 @@ +{-# LANGUAGE OverloadedStrings #-} +module Tests.Writers.Txt2Tags (tests) where + +import Data.Text (Text) +import Test.Tasty +import Test.Tasty.HUnit (HasCallStack) +import Tests.Helpers +import Text.Pandoc +import Text.Pandoc.Arbitrary () +import Text.Pandoc.Builder + +txt2tags :: (ToPandoc a) => a -> Text +txt2tags = purely (writeTxt2Tags def) . toPandoc + +infix 4 =: +(=:) :: (ToString a, ToPandoc a, HasCallStack) + => String -> (a, Text) -> TestTree +(=:) = test txt2tags + +tests :: [TestTree] +tests = + [ testGroup "inline formatting" + [ "bold" =: strong "text" =?> "**text**" + , "italic" =: emph "text" =?> "//text//" + , "underline" =: underline "text" =?> "__text__" + , "strikeout" =: strikeout "text" =?> "--text--" + , "inline code" =: code "foo" =?> "``foo``" + ] + , testGroup "escape special characters" + [ "escape bold marker" =: str "a**b" =?> "a%%**%%b" + , "escape italic marker" =: str "a//b" =?> "a%%//%%b" + , "escape underline marker" =: str "a__b" =?> "a%%__%%b" + , "escape strikeout marker" =: str "a--b" =?> "a%%--%%b" + , "escape code marker" =: str "a``b" =?> "a%%``%%b" + ] + , testGroup "headers" + [ "h1" =: header 1 "Heading" =?> "= Heading =\n" + , "h2" =: header 2 "Heading" =?> "== Heading ==\n" + , "h3" =: header 3 "Heading" =?> "=== Heading ===\n" + , "h4" =: header 4 "Heading" =?> "==== Heading ====\n" + ] + , testGroup "code blocks" + [ "no trailing newline in source" + =: codeBlock "foo" + =?> "```\nfoo\n```\n" + , "trailing newline preserved" + =: codeBlock "foo\n" + =?> "```\nfoo\n```\n" + ] + , testGroup "lists" + [ "bullet list" + =: bulletList [para "foo", para "bar"] + =?> "- foo\n- bar\n" + , "ordered list" + =: orderedList [para "foo", para "bar"] + =?> "+ foo\n+ bar\n" + , "nested bullet list" + =: bulletList [para "a" <> bulletList [para "b"]] + =?> "- a\n - b\n" + , "nested ordered list" + =: orderedList [para "a" <> orderedList [para "b"]] + =?> "+ a\n + b\n" + ] + , testGroup "definition lists" + [ "single term and definition" + =: definitionList [("term", [para "definition"])] + =?> ": **term**\n definition\n" + , "multiple terms" + =: definitionList + [ ("foo", [para "def foo"]) + , ("bar", [para "def bar"]) + ] + =?> ": **foo**\n def foo\n: **bar**\n def bar\n" + , "multiple definitions for one term" + =: definitionList [("term", [para "def1", para "def2"])] + =?> ": **term**\n def1\n def2\n" + ] + , testGroup "blockquote" + -- Txt2Tags has no blockquote syntax; content is rendered without special markup. + [ "blockquote rendered as plain content" + =: blockQuote (para "quoted") + =?> "quoted\n" + ] + , testGroup "horizontal rule" + [ "hr" =: horizontalRule =?> "\n---\n" + ] + , testGroup "tables" + [ "table with headers" + =: simpleTable [plain "A", plain "B"] + [[plain "1", plain "2"]] + =?> "|A|B|\n|1|2|\n" + , "table without headers" + =: simpleTable [] [[plain "1", plain "2"]] + =?> "|1|2|\n" + ] + , testGroup "links" + [ "link with label" + =: link "http://example.com" "" "example" + =?> "[example http://example.com]" + , "autolink" + =: link "http://example.com" "" "http://example.com" + =?> "http://example.com" + , "email" + =: link "mailto:user@example.com" "" "user@example.com" + =?> "" + ] + , testGroup "images" + [ "simple image (no title, no alt)" + -- Use imageWith + mempty to get truly empty alt ([]) vs str "" which gives [Str ""] + =: imageWith ("", [], []) "image.png" "" mempty + =?> "[image.png]" + , "image with title" + =: image "image.png" "My title" "ignored alt" + =?> "[image.png|My title]" + , "image with alt text (no title)" + =: image "image.png" "" "alt text" + =?> "[image.png|alt text]" + ] + , testGroup "raw blocks" + [ "txt2tags passthrough" + =: rawBlock "txt2tags" "raw content" + =?> "raw content" + ] + ] diff --git a/test/test-pandoc.hs b/test/test-pandoc.hs index 9ae97d9c0af9..02af58e46fbb 100644 --- a/test/test-pandoc.hs +++ b/test/test-pandoc.hs @@ -53,6 +53,7 @@ import qualified Tests.Writers.AnnotatedTable import qualified Tests.Writers.TEI import qualified Tests.Writers.Markua import qualified Tests.Writers.BBCode +import qualified Tests.Writers.Txt2Tags import qualified Tests.XML import qualified Tests.MediaBag import Text.Pandoc.Shared (inDirectory) @@ -86,6 +87,7 @@ tests pandocPath = testGroup "pandoc tests" , testGroup "Ms" Tests.Writers.Ms.tests , testGroup "AnnotatedTable" Tests.Writers.AnnotatedTable.tests , testGroup "BBCode" Tests.Writers.BBCode.tests + , testGroup "Txt2Tags" Tests.Writers.Txt2Tags.tests ] , testGroup "Readers" [ testGroup "LaTeX" Tests.Readers.LaTeX.tests From 5e8313c25f01cdba6870d77097482a63942e418c Mon Sep 17 00:00:00 2001 From: farvardin Date: Fri, 10 Apr 2026 22:51:18 +0200 Subject: [PATCH 07/11] restore github ci stuff --- .github/workflows/ci.yml | 245 +++++++++++++++++++++ .github/workflows/commit-validation-pr.yml | 32 +++ updating_fork.txt | 15 ++ 3 files changed, 292 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e69de29bb2d1..b6fa5eddfd88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -0,0 +1,245 @@ +name: CI tests + +on: + push: + branches: + - 'main' + paths-ignore: + - 'doc/*.md' + - 'MANUAL.txt' + - '*.md' + - '.cirrus.yml' + - 'RELEASE_CHECKLIST' + - 'BUGS' + - 'README.template' + - 'hie.yaml' + - '*.nix' + - 'tools/**' + - 'linux/**' + - 'macos/**' + - 'windows/**' + - 'man/**' + pull_request: + paths-ignore: + - 'doc/*.md' + - 'MANUAL.txt' + - '*.md' + - 'RELEASE_CHECKLIST' + - 'BUGS' + - 'README.template' + - 'hie.yaml' + - '*.nix' + - 'tools/**' + - 'linux/**' + - 'macos/**' + - 'windows/**' + - 'man/**' + +permissions: + contents: read + +jobs: + linux: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + versions: + # NOTE: Update `CONTRIBUTING.md` when changing the minimum version + - ghc: '9.6' + cabal: 'latest' + cabalopts: '--ghc-option=-Werror' + - ghc: '9.8' + cabal: 'latest' + cabalopts: '--ghc-option=-Werror' + - ghc: '9.10' + cabal: 'latest' + cabalopts: '--ghc-option=-Werror' + - ghc: '9.12' + cabal: 'latest' + cabalopts: '--ghc-option=-Werror' + + steps: + - uses: actions/checkout@v6 + + # needed by memory + - name: Install numa + run: sudo apt-get install libnuma-dev + + - name: Workaround runner image issue + # https://github.com/actions/runner-images/issues/7061 + run: sudo chown -R $USER /usr/local/.ghcup + + - name: Install cabal/ghc + run: | + ghcup install ghc --set ${{ matrix.versions.ghc }} + ghcup install cabal --set ${{ matrix.versions.cabal }} + + # declare/restore cached things + + - name: Cache cabal global package db + id: cabal-global + uses: actions/cache@v5 + with: + path: | + ~/.local/state/cabal + key: ${{ runner.os }}-${{ matrix.versions.ghc }}-${{ matrix.versions.cabal }}-cabal-global-${{ secrets.CACHE_VERSION }} + + - name: Cache cabal work + id: cabal-local + uses: actions/cache@v5 + with: + path: | + dist-newstyle + key: ${{ runner.os }}-${{ matrix.versions.ghc }}-${{ matrix.versions.cabal }}-cabal-local-${{ secrets.CACHE_VERSION }} + + - name: Update cabal + run: | + cabal update + + - name: Build and test + run: | + cabal build ${{ matrix.versions.cabalopts }} --enable-tests --disable-optimization all + cabal test ${{ matrix.versions.cabalopts }} --disable-optimization all + + linux-stack: + + runs-on: ubuntu-latest + strategy: + fail-fast: true + steps: + - uses: actions/checkout@v6 + + # needed by memory + - name: Install numa + run: sudo apt-get install libnuma-dev + + # declare/restore cached things + - name: Cache stack global package db + id: stack-global + uses: actions/cache@v5 + with: + path: | + ~/.stack + key: ${{ runner.os }}-${{ matrix.versions.ghc }}-${{ matrix.versions.cabal }}-stack-global-${{ secrets.CACHE_VERSION }} + + - name: Cache stack work + id: stack-local + uses: actions/cache@v5 + with: + path: | + .stack-work + key: ${{ runner.os }}-${{ matrix.versions.ghc }}-${{ matrix.versions.cabal }}-stack-local-${{ secrets.CACHE_VERSION }} + + - name: Build and test + run: | + stack test --fast + + windows: + + runs-on: windows-2022 + + steps: + - uses: actions/checkout@v6 + + - name: Install ghc + run: | + ghcup install ghc --set 9.10 + ghcup install cabal --set latest + + # declare/restore cached things + + - name: Cache cabal global package db + id: cabal-global + uses: actions/cache@v5 + with: + path: | + C:\cabal\store + key: ${{ runner.os }}-appdata-cabal-${{ hashFiles('cabal.project') }}-${{ secrets.CACHE_VERSION }} + + - name: Cache cabal work + id: cabal-local + uses: actions/cache@v5 + with: + path: | + dist-newstyle + key: ${{ runner.os }}-stack-work-${{ hashFiles('cabal.project') }}-${{ secrets.CACHE_VERSION }} + + - name: Build and test + run: | + cabal update + cabal test --ghc-option=-Werror -fhttp --enable-tests --disable-optimization --ghc-options=-Werror all + +# The nix build ran out of resources. Not sure how to fix. +# +# nix-wasm: +# +# runs-on: ubuntu-latest +# strategy: +# fail-fast: true +# steps: +# - uses: actions/checkout@v6 +# # Remove unneeded cruft in runner so we have room for Nix: +# - uses: wimpysworld/nothing-but-nix@v9 +# with: +# hatchet-protocol: 'rampage' +# - uses: DeterminateSystems/nix-installer-action@main +# - uses: DeterminateSystems/magic-nix-cache-action@main +# - name: Cache cabal work +# id: cabal-local +# uses: actions/cache@v5 +# with: +# path: | +# dist-newstyle +# key: ${{ runner.os }}-nix-cabal-local-${{ secrets.CACHE_VERSION }} +# - run: | +# nix develop --command bash -c "make pandoc.wasm" + +# We no longer run the macos tests, to make CI faster. +# macos: + +# runs-on: macos-12 +# strategy: +# fail-fast: true +# matrix: +# versions: +# - ghc: '8.8.4' +# cabal: '3.2' + +# steps: +# - uses: actions/checkout@v6 + +# - name: Install cabal/ghc +# run: | +# ghcup install ghc --set ${{ matrix.versions.ghc }} +# ghcup install cabal ${{ matrix.versions.cabal }} + +# # declare/restore cached things + +# - name: Cache cabal global package db +# id: cabal-global +# uses: actions/cache@v5 +# with: +# path: | +# ~/.cabal +# key: ${{ runner.os }}-${{ matrix.versions.ghc }}-${{ matrix.versions.cabal }}-cabal-global-${{ secrets.CACHE_VERSION }} + +# - name: Cache cabal work +# id: cabal-local +# uses: actions/cache@v5 +# with: +# path: | +# dist-newstyle +# key: ${{ runner.os }}-${{ matrix.versions.ghc }}-${{ matrix.versions.cabal }}-cabal-local-${{ secrets.CACHE_VERSION }} + +# - name: Install dependencies +# run: | +# cabal v2-update +# cabal v2-build --dependencies-only --enable-tests --disable-optimization +# - name: Build and test +# run: | +# cabal v2-build --enable-tests --disable-optimization 2>&1 | tee build.log +# # fail if warnings in local build +# ! grep -q ": *[Ww]arning:" build.log || exit 1 +# cabal v2-test --disable-optimization --test-option=--hide-successes --test-option=--ansi-tricks=false diff --git a/.github/workflows/commit-validation-pr.yml b/.github/workflows/commit-validation-pr.yml index e69de29bb2d1..b8aab60d3356 100644 --- a/.github/workflows/commit-validation-pr.yml +++ b/.github/workflows/commit-validation-pr.yml @@ -0,0 +1,32 @@ +name: commit-validation-pr +on: [pull_request] + +permissions: + contents: read + +jobs: + check-commit-msg-length: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Check commit message length + run: | + git log ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} | ( + longlines=0 + while IFS='' read -r line; do + if [ "${#line}" -gt 78 ] ; then + if echo "$line" | grep -q '^\s*https://\S*\s*$'; then + echo "Ignoring long line with URL." + else + echo "Overlong line: ${line}" >&2 + if echo "$line" | grep -q 'https://'; then + echo "Put a long URL on a line by itself." + fi + longlines=$(( longlines + 1 )) + fi + fi + done + [ "${longlines}" -eq 0 ] + ) diff --git a/updating_fork.txt b/updating_fork.txt index b067dcfe36d3..e4bd348ac875 100644 --- a/updating_fork.txt +++ b/updating_fork.txt @@ -20,3 +20,18 @@ stack install copy to ~/.local/bin/pandoc +● D'après les logs, le binaire a été installé ici : + + /home/alan/src/pandoc/.stack-work/install/x86_64-linux-tinfo6/39facb83e47fd854833cddc51836d26465fe362d2857e + 8000a1554652b7242d9/9.10.3/bin/pandoc + + Pour l'utiliser plus facilement, vous pouvez soit : + - stack exec pandoc -- --version pour l'exécuter via stack + - stack install pour le copier dans ~/.local/bin/pandoc + + + + /home/alan/src/pandoc/.stack-work/install/x86_64-linux-tinfo6/39facb83e47fd854833cddc51836d26465fe362d2857e8000a1554652b7242d9/9.10.3/bin/pandoc + + + \ No newline at end of file From 8cc26813f2df8d99652913b96946bdc3818d2c35 Mon Sep 17 00:00:00 2001 From: farvardin Date: Fri, 10 Apr 2026 22:52:39 +0200 Subject: [PATCH 08/11] remove personal note --- updating_fork.txt | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 updating_fork.txt diff --git a/updating_fork.txt b/updating_fork.txt deleted file mode 100644 index e4bd348ac875..000000000000 --- a/updating_fork.txt +++ /dev/null @@ -1,37 +0,0 @@ -== 2022 == - -git pull firstrepo master -git push origin -cabal install --overwrite-policy=always - -cabal build - -cabal install -fembed_data_files - -== 2026 == - ---cabal update-- - -stack setup -stack upgrade -stack setup -stack build -stack install - -copy to ~/.local/bin/pandoc - -● D'après les logs, le binaire a été installé ici : - - /home/alan/src/pandoc/.stack-work/install/x86_64-linux-tinfo6/39facb83e47fd854833cddc51836d26465fe362d2857e - 8000a1554652b7242d9/9.10.3/bin/pandoc - - Pour l'utiliser plus facilement, vous pouvez soit : - - stack exec pandoc -- --version pour l'exécuter via stack - - stack install pour le copier dans ~/.local/bin/pandoc - - - - /home/alan/src/pandoc/.stack-work/install/x86_64-linux-tinfo6/39facb83e47fd854833cddc51836d26465fe362d2857e8000a1554652b7242d9/9.10.3/bin/pandoc - - - \ No newline at end of file From 40b7928ac31cf8a55171cbd19a2e1b3a78300b61 Mon Sep 17 00:00:00 2001 From: luginf Date: Fri, 10 Apr 2026 10:58:49 +0200 Subject: [PATCH 09/11] Txt2Tags writer: fix syntax, add tests - Fix inline code to use `` `` `` `` instead of `''%%...%%''` - Fix raw block/inline format name to lowercase `txt2tags` - Fix HTML raw passthrough syntax: `"""\n...\n"""` for blocks, `""..."""` for inline - Remove unused `stBlockQuoteLevel` field; blockquotes render as plain content - Fix `escapeString` to also escape `--` and ` `` ` - Fix definition list: use `: **term**` with indented content instead of DokuWiki `* **term**` - Remove leftover DokuWiki-specific `languageNames` map and `isSimpleList` helpers - Add `Tests.Writers.Txt2Tags` with unit tests covering inline formatting, escaping, headers, code blocks, lists, definition lists, blockquotes, horizontal rules, tables, links, images, and raw blocks Co-Authored-By: Claude Sonnet 4.6 --- pandoc.cabal | 1 + src/Text/Pandoc/Writers/Txt2Tags.hs | 265 +++++++++------------------- test/Tests/Writers/Txt2Tags.hs | 124 +++++++++++++ test/test-pandoc.hs | 2 + 4 files changed, 208 insertions(+), 184 deletions(-) create mode 100644 test/Tests/Writers/Txt2Tags.hs diff --git a/pandoc.cabal b/pandoc.cabal index 627e4ed368ef..1ee2a381afbd 100644 --- a/pandoc.cabal +++ b/pandoc.cabal @@ -922,6 +922,7 @@ test-suite test-pandoc Tests.Writers.Ms Tests.Writers.AnnotatedTable Tests.Writers.BBCode + Tests.Writers.Txt2Tags benchmark benchmark-pandoc import: common-executable diff --git a/src/Text/Pandoc/Writers/Txt2Tags.hs b/src/Text/Pandoc/Writers/Txt2Tags.hs index facf66d9f740..9b0c869c21c8 100644 --- a/src/Text/Pandoc/Writers/Txt2Tags.hs +++ b/src/Text/Pandoc/Writers/Txt2Tags.hs @@ -13,17 +13,6 @@ Conversion of 'Pandoc' documents to Txt2Tags markup. Txt2Tags: -} -{- - It works for most of the syntax. But some improvements or fix can be made: - - On lists (2 extra lines to terminate a list) - - On the 3 lines header at the begining of a file - - Definition lists are broken - - Tables could be improved - - Some formats make better results than others (html is ok, md is somehow broken) - - code related to dokuwiki only could be removed - - a test case should be made --} - module Text.Pandoc.Writers.Txt2Tags ( writeTxt2Tags ) where import Control.Monad (zipWithM) import Control.Monad.Reader (ReaderT, asks, local, runReaderT) @@ -45,25 +34,21 @@ import Text.Pandoc.URI (escapeURI, isURI) import Text.Pandoc.Templates (renderTemplate) import Text.DocLayout (render, literal) import Text.Pandoc.Writers.Shared (defField, metaToContext, toLegacyTable) -import Data.Maybe (fromMaybe) -import qualified Data.Map as M data WriterState = WriterState { } data WriterEnvironment = WriterEnvironment { - stIndent :: Text -- Indent after the marker at the beginning of list items - , stBackSlashLB :: Bool -- True if we should produce formatted strings with newlines (as in a table cell) - , stBlockQuoteLevel :: Int -- Block quote level + stIndent :: Text -- Indentation prefix for the current list nesting level + , stBackSlashLB :: Bool -- True inside table cells (use \\ for line breaks) } instance Default WriterState where def = WriterState {} instance Default WriterEnvironment where - def = WriterEnvironment { stIndent = "" - , stBackSlashLB = False - , stBlockQuoteLevel = 0 } + def = WriterEnvironment { stIndent = "" + , stBackSlashLB = False } type Txt2Tags m = ReaderT WriterEnvironment (StateT WriterState m) @@ -92,8 +77,11 @@ pandocToTxt2Tags opts (Pandoc meta blocks) = do Just tpl -> render Nothing $ renderTemplate tpl context -- | Escape special characters for Txt2Tags. +-- The %%text%% syntax disables Txt2Tags formatting interpretation. escapeString :: Text -> Text -escapeString = T.replace "__" "%%__%%" . +escapeString = T.replace "``" "%%``%%" . + T.replace "--" "%%--%%" . + T.replace "__" "%%__%%" . T.replace "**" "%%**%%" . T.replace "//" "%%//%%" @@ -106,57 +94,42 @@ blockToTxt2Tags :: PandocMonad m blockToTxt2Tags opts (Div _attrs bs) = do contents <- blockListToTxt2Tags opts bs indent <- asks stIndent - return $ contents <> if T.null indent then "\n\n" else "" + return $ contents <> if T.null indent then "\n" else "" blockToTxt2Tags opts (Plain inlines) = inlineListToTxt2Tags opts inlines blockToTxt2Tags opts (Para inlines) = do - bqLevel <- asks stBlockQuoteLevel - let bqPrefix = case bqLevel of - 0 -> "" - n -> T.replicate n ">" <> " " indent <- asks stIndent contents <- inlineListToTxt2Tags opts inlines - return $ bqPrefix <> contents <> if T.null indent then "\n" else "" + return $ contents <> if T.null indent then "\n" else "" blockToTxt2Tags opts (LineBlock lns) = blockToTxt2Tags opts $ linesToPara lns blockToTxt2Tags opts b@(RawBlock f str) - | f == Format "Txt2Tags" = return str - -- See https://www.Txt2Tags.org/wiki:syntax - -- use uppercase HTML tag for block-level content: + | f == Format "txt2tags" = return str + -- Use the Txt2Tags raw area syntax (""") for block-level HTML pass-through: | f == Format "html" - , isEnabled Ext_raw_html opts = return $ "\n" <> str <> "\n" - | otherwise = "" <$ - report (BlockNotRendered b) + , isEnabled Ext_raw_html opts = return $ "\"\"\"\n" <> str <> "\n\"\"\"\n" + | otherwise = "" <$ report (BlockNotRendered b) -blockToTxt2Tags _ HorizontalRule = return "\n---------------\n" +blockToTxt2Tags _ HorizontalRule = return "\n---\n" blockToTxt2Tags opts (Header level _ inlines) = do - -- emphasis, links etc. not allowed in headers, apparently, - -- so we remove formatting: + -- Formatting is not allowed in headers, so strip it contents <- inlineListToTxt2Tags opts $ removeFormatting inlines - let eqs = T.replicate level "=" + let eqs = T.replicate level "=" return $ eqs <> " " <> contents <> " " <> eqs <> "\n" -blockToTxt2Tags _ (CodeBlock (_,classes,_) str) = do - bqLevel <- asks stBlockQuoteLevel - let bqPrefix = case bqLevel of - 0 -> "" - n -> T.replicate n ">" <> " " - return $ bqPrefix <> - " - (case classes of - [] -> "" - (x:_) -> " " <> fromMaybe x (M.lookup x languageNames)) <> - ">\n" <> str <> - (if "\n" `T.isSuffixOf` str then "" else "\n") <> "\n" +-- | Txt2Tags verbatim area uses ``` delimiters. +blockToTxt2Tags _ (CodeBlock _ str) = + return $ "```\n" <> str <> + (if "\n" `T.isSuffixOf` str then "" else "\n") <> "```\n" +-- | Txt2Tags has no blockquote syntax; render content without special markup. blockToTxt2Tags opts (BlockQuote blocks) = - local (\st -> st{ stBlockQuoteLevel = stBlockQuoteLevel st + 1 }) - (blockListToTxt2Tags opts blocks) + blockListToTxt2Tags opts blocks blockToTxt2Tags opts (Table _ blkCapt specs thead tbody tfoot) = do let (capt, aligns, _, headers, rows) = toLegacyTable blkCapt specs thead tbody tfoot @@ -164,7 +137,7 @@ blockToTxt2Tags opts (Table _ blkCapt specs thead tbody tfoot) = do then return "" else do c <- inlineListToTxt2Tags opts capt - return $ "" <> c <> "\n" + return $ c <> "\n" headers' <- if all null headers then return [] else zipWithM (tableItemToTxt2Tags opts) aligns headers @@ -181,39 +154,30 @@ blockToTxt2Tags opts (Table _ blkCapt specs thead tbody tfoot) = do else T.replicate (x `div` 2) " " <> s <> T.replicate (x - x `div` 2) " " | otherwise -> s - let renderRow sep cells = sep <> - T.intercalate sep (zipWith padTo (zip widths aligns) cells) <> sep + let renderRow sep cells = + sep <> T.intercalate sep (zipWith padTo (zip widths aligns) cells) <> sep return $ captionDoc <> (if null headers' then "" else renderRow "|" headers' <> "\n") <> T.unlines (map (renderRow "|") rows') blockToTxt2Tags opts (BulletList items) = do indent <- asks stIndent - backSlash <- asks stBackSlashLB - contents <- local (\s -> s { stIndent = stIndent s <> " " - , stBackSlashLB = backSlash}) - (mapM (listItemToTxt2Tags opts) items) + contents <- local (\s -> s { stIndent = stIndent s <> " " }) + (mapM (listItemToTxt2Tags opts) items) return $ vcat contents <> if T.null indent then "\n" else "" blockToTxt2Tags opts (OrderedList _attribs items) = do indent <- asks stIndent - backSlash <- asks stBackSlashLB - contents <- local (\s -> s { stIndent = stIndent s <> " " - , stBackSlashLB = backSlash}) + contents <- local (\s -> s { stIndent = stIndent s <> " " }) (mapM (orderedListItemToTxt2Tags opts) items) return $ vcat contents <> if T.null indent then "\n" else "" blockToTxt2Tags opts (Figure attr capt body) = blockToTxt2Tags opts $ figureDiv attr capt body --- TODO Need to decide how to make definition lists work on Txt2Tags - I don't think there --- is a specific representation of them. --- TODO This creates double '; ; ' if there is a bullet or ordered list inside a definition list blockToTxt2Tags opts (DefinitionList items) = do indent <- asks stIndent - backSlash <- asks stBackSlashLB - contents <- local (\s -> s { stIndent = stIndent s <> " " - , stBackSlashLB = backSlash}) + contents <- local (\s -> s { stIndent = stIndent s <> " " }) (mapM (definitionListItemToTxt2Tags opts) items) return $ vcat contents <> if T.null indent then "\n" else "" @@ -223,98 +187,54 @@ blockToTxt2Tags opts (DefinitionList items) = do listItemToTxt2Tags :: PandocMonad m => WriterOptions -> [Block] -> Txt2Tags m Text listItemToTxt2Tags opts items = do - bqLevel <- asks stBlockQuoteLevel - let bqPrefix = case bqLevel of - 0 -> "" - n -> T.replicate n ">" <> " " - let useWrap = not (isSimpleListItem items) bs <- mapM (blockToTxt2Tags opts) items - let contents = case items of - [_, CodeBlock _ _] -> T.concat bs - _ -> vcat bs indent <- asks stIndent - backSlash <- asks stBackSlashLB - let indent' = if backSlash then T.drop 2 indent else indent - return $ bqPrefix <> indent' <> "- " <> - if useWrap - then "\n" <> contents <> "\n" - else contents + -- BulletList increments indent by " ", so T.drop 2 gives the marker position: + -- level 1 -> "", level 2 -> " ", etc. + let markerIndent = T.drop 2 indent + -- Use newlines between blocks; T.stripEnd preserves leading spaces (e.g. nested + -- list indentation) while removing trailing newlines that would create blank lines. + let contents = T.intercalate "\n" (map T.stripEnd bs) + return $ markerIndent <> "- " <> contents -- | Convert ordered list item (list of blocks) to Txt2Tags. --- | TODO Emiminate dreadful duplication of text from listItemToTxt2Tags orderedListItemToTxt2Tags :: PandocMonad m => WriterOptions -> [Block] -> Txt2Tags m Text orderedListItemToTxt2Tags opts items = do - bqLevel <- asks stBlockQuoteLevel - let bqPrefix = case bqLevel of - 0 -> "" - n -> T.replicate n ">" <> " " - let useWrap = not (isSimpleListItem items) - contents <- local (\st -> st{ stBlockQuoteLevel = 0 }) - (blockListToTxt2Tags opts items) + bs <- mapM (blockToTxt2Tags opts) items indent <- asks stIndent - backSlash <- asks stBackSlashLB - let indent' = if backSlash then T.drop 2 indent else indent - return $ bqPrefix <> indent' <> "+ " <> - if useWrap - then "\n" <> contents <> "\n" - else contents + -- OrderedList increments indent by " ", so T.drop 2 gives the marker position + let markerIndent = T.drop 2 indent + let contents = T.intercalate "\n" (map T.stripEnd bs) + return $ markerIndent <> "+ " <> contents -- | Convert definition list item (label, list of blocks) to Txt2Tags. +-- Txt2Tags has no native definition list syntax; we use ": **term**" as a +-- label followed by indented definition content. definitionListItemToTxt2Tags :: PandocMonad m => WriterOptions - -> ([Inline],[[Block]]) + -> ([Inline], [[Block]]) -> Txt2Tags m Text definitionListItemToTxt2Tags opts (label, items) = do - let useWrap = not (all isSimpleListItem items) - bqLevel <- asks stBlockQuoteLevel - let bqPrefix = case bqLevel of - 0 -> "" - n -> T.replicate n ">" <> " " labelText <- inlineListToTxt2Tags opts label - contents <- local (\st -> st{ stBlockQuoteLevel = 0 }) - (mapM (blockListToTxt2Tags opts) items) - indent <- asks stIndent - backSlash <- asks stBackSlashLB - let indent' = if backSlash then T.drop 2 indent else indent - return $ bqPrefix <> indent' <> "* **" <> labelText <> "** " <> - if useWrap - then "\n" <> vcat contents <> "\n" - else T.intercalate "; " contents - --- | True if list item can be handled with the simple wiki syntax. False if --- WRAP tags will be needed. -isSimpleListItem :: [Block] -> Bool -isSimpleListItem [] = True -isSimpleListItem [x, CodeBlock{}] | isPlainOrPara x = True -isSimpleListItem (Div _ bs : ys) = -- see #8920 - isSimpleListItem bs && all isSimpleList ys -isSimpleListItem (x:ys) | isPlainOrPara x = all isSimpleList ys -isSimpleListItem _ = False ---- | True if the list can be handled by simple wiki markup, False if HTML tags will be needed. - -isSimpleList :: Block -> Bool -isSimpleList x = - case x of - BulletList items -> all isSimpleListItem items - OrderedList (1, _, _) items -> all isSimpleListItem items - DefinitionList items -> all (all isSimpleListItem . snd) items - _ -> False - -isPlainOrPara :: Block -> Bool -isPlainOrPara (Plain _) = True -isPlainOrPara (Para _) = True -isPlainOrPara _ = False - --- | Concatenates strings with line breaks between them. + contents <- mapM (blockListToTxt2Tags opts) items + indent <- asks stIndent + let markerIndent = T.drop 2 indent + let defIndent = markerIndent <> " " + let fmtItem c = defIndent <> T.stripEnd c + return $ markerIndent <> ": **" <> labelText <> "**\n" <> + T.intercalate "\n" (map fmtItem contents) + +-- | Concatenates strings with newlines between them. vcat :: [Text] -> Text vcat = T.intercalate "\n" --- | For each string in the input list, convert all newlines to --- Txt2Tags escaped newlines. Then concat the list using double linebreaks. +-- | For each string in the input list, replace newlines with Txt2Tags line +-- breaks (\\). Then join the list using double line breaks to simulate +-- paragraph breaks in table cells. backSlashLineBreaks :: [Text] -> Text backSlashLineBreaks ls = vcatBackSlash $ map (T.pack . escape . T.unpack) ls where - vcatBackSlash = T.intercalate "\\\\ \\\\ " -- simulate paragraphs. + vcatBackSlash = T.intercalate "\\\\ \\\\ " -- simulate paragraph break escape ['\n'] = "" -- remove trailing newlines escape ('\n':cs) = "\\\\ " <> escape cs escape (c:cs) = c : escape cs @@ -328,14 +248,15 @@ tableItemToTxt2Tags :: PandocMonad m -> [Block] -> Txt2Tags m Text tableItemToTxt2Tags opts align' item = do + -- In Txt2Tags, alignment is indicated by spaces around cell content: + -- leading space -> right, trailing space -> left, both -> center. let mkcell x = (if align' == AlignRight || align' == AlignCenter then " " else "") <> x <> (if align' == AlignLeft || align' == AlignCenter then " " else "") - contents <- local (\s -> s { stBackSlashLB = True - , stBlockQuoteLevel = 0 }) $ + contents <- local (\s -> s { stBackSlashLB = True }) $ blockListToTxt2Tags opts item return $ mkcell contents @@ -386,6 +307,7 @@ inlineToTxt2Tags opts (Strikeout lst) = do contents <- inlineListToTxt2Tags opts lst return $ "--" <> contents <> "--" +-- Txt2Tags has no superscript/subscript syntax; fall back to HTML tags. inlineToTxt2Tags opts (Superscript lst) = do contents <- inlineListToTxt2Tags opts lst return $ "" <> contents <> "" @@ -404,39 +326,30 @@ inlineToTxt2Tags opts (Quoted DoubleQuote lst) = do contents <- inlineListToTxt2Tags opts lst return $ "\8220" <> contents <> "\8221" -inlineToTxt2Tags opts (Cite _ lst) = inlineListToTxt2Tags opts lst +inlineToTxt2Tags opts (Cite _ lst) = inlineListToTxt2Tags opts lst +-- | Inline code uses Txt2Tags verbatim syntax (double backticks). inlineToTxt2Tags _ (Code _ str) = - -- In Txt2Tags, text surrounded by '' is really just a font statement, i.e. , - -- and so other formatting can be present inside. - -- However, in pandoc, and markdown, inlined code doesn't contain formatting. - -- So I have opted for using %% to disable all formatting inside inline code blocks. - -- This gives the best results when converting from other formats to Txt2Tags, even if - -- the resultand code is a little ugly, for short strings that don't contain formatting - -- characters. - -- It does mean that if pandoc could ever read Txt2Tags, and so round-trip the format, - -- any formatting inside inlined code blocks would be lost, or presented incorrectly. - return $ "''%%" <> str <> "%%''" + return $ "``" <> str <> "``" inlineToTxt2Tags _ (Str str) = return $ escapeString str inlineToTxt2Tags _ (Math mathType str) = return $ delim <> str <> delim - -- note: str should NOT be escaped + -- note: str should NOT be escaped where delim = case mathType of DisplayMath -> "$$" InlineMath -> "$" inlineToTxt2Tags opts il@(RawInline f str) - | f == Format "Txt2Tags" = return str + | f == Format "txt2tags" = return str + -- Use the Txt2Tags inline raw syntax ("") for HTML pass-through: | f == Format "html" - , isEnabled Ext_raw_html opts = return $ "" <> str <> "" - | otherwise = "" <$ report (InlineNotRendered il) + , isEnabled Ext_raw_html opts = return $ "\"\"" <> str <> "\"\"" + | otherwise = "" <$ report (InlineNotRendered il) inlineToTxt2Tags _ LineBreak = do backSlash <- asks stBackSlashLB - return $ if backSlash - then "\n" - else "\\\\\n" + return $ if backSlash then "\n" else "\\\\\n" inlineToTxt2Tags opts SoftBreak = case writerWrapText opts of @@ -451,12 +364,13 @@ inlineToTxt2Tags opts (Link _ txt (src, _)) = do case txt of [Str s] | "mailto:" `T.isPrefixOf` src -> return $ "<" <> s <> ">" | escapeURI s == src -> return src - _ -> if isURI src - then return $ "[" <> label <> " " <> src <> "]" - else return $ "[" <> label <> " " <> src' <> "]" - where src' = case T.uncons src of - Just ('/',xs) -> xs -- with leading / it's a - _ -> src -- link to a help page + _ -> if isURI src + then return $ "[" <> label <> " " <> src <> "]" + else return $ "[" <> label <> " " <> src' <> "]" + where src' = case T.uncons src of + Just ('/', xs) -> xs + _ -> src + inlineToTxt2Tags opts (Image attr alt (source, tit)) = do alt' <- inlineListToTxt2Tags opts alt let txt = case (tit, alt) of @@ -465,11 +379,10 @@ inlineToTxt2Tags opts (Image attr alt (source, tit)) = do (_ , _ ) -> "|" <> tit return $ "[" <> source <> imageDims opts attr <> txt <> "]" +-- | Txt2Tags has no footnote syntax; render note content inline in parentheses. inlineToTxt2Tags opts (Note contents) = do - contents' <- local (\st -> st{ stBlockQuoteLevel = 0 }) - (blockListToTxt2Tags opts contents) - return $ "((" <> contents' <> "))" - -- note - may not work for notes with multiple blocks + contents' <- blockListToTxt2Tags opts contents + return $ "(" <> T.strip contents' <> ")" imageDims :: WriterOptions -> Attr -> Text imageDims opts attr = go (toPx $ dimension Width attr) (toPx $ dimension Height attr) @@ -481,19 +394,3 @@ imageDims opts attr = go (toPx $ dimension Width attr) (toPx $ dimension Height go (Just w) (Just h) = "?" <> w <> "x" <> h go Nothing (Just h) = "?0x" <> h go Nothing Nothing = "" - -languageNames :: M.Map Text Text -languageNames = M.fromList - [("cs", "csharp") - ,("coffee", "cofeescript") - ,("commonlisp", "lisp") - ,("gcc", "c") - ,("html", "html5") - ,("makefile", "make") - ,("objectivec", "objc") - ,("r", "rsplus") - ,("sqlmysql", "mysql") - ,("sqlpostgresql", "postgresql") - ,("sci", "scilab") - ,("xorg", "xorgconf") - ] diff --git a/test/Tests/Writers/Txt2Tags.hs b/test/Tests/Writers/Txt2Tags.hs new file mode 100644 index 000000000000..caa04ddaf738 --- /dev/null +++ b/test/Tests/Writers/Txt2Tags.hs @@ -0,0 +1,124 @@ +{-# LANGUAGE OverloadedStrings #-} +module Tests.Writers.Txt2Tags (tests) where + +import Data.Text (Text) +import Test.Tasty +import Test.Tasty.HUnit (HasCallStack) +import Tests.Helpers +import Text.Pandoc +import Text.Pandoc.Arbitrary () +import Text.Pandoc.Builder + +txt2tags :: (ToPandoc a) => a -> Text +txt2tags = purely (writeTxt2Tags def) . toPandoc + +infix 4 =: +(=:) :: (ToString a, ToPandoc a, HasCallStack) + => String -> (a, Text) -> TestTree +(=:) = test txt2tags + +tests :: [TestTree] +tests = + [ testGroup "inline formatting" + [ "bold" =: strong "text" =?> "**text**" + , "italic" =: emph "text" =?> "//text//" + , "underline" =: underline "text" =?> "__text__" + , "strikeout" =: strikeout "text" =?> "--text--" + , "inline code" =: code "foo" =?> "``foo``" + ] + , testGroup "escape special characters" + [ "escape bold marker" =: str "a**b" =?> "a%%**%%b" + , "escape italic marker" =: str "a//b" =?> "a%%//%%b" + , "escape underline marker" =: str "a__b" =?> "a%%__%%b" + , "escape strikeout marker" =: str "a--b" =?> "a%%--%%b" + , "escape code marker" =: str "a``b" =?> "a%%``%%b" + ] + , testGroup "headers" + [ "h1" =: header 1 "Heading" =?> "= Heading =\n" + , "h2" =: header 2 "Heading" =?> "== Heading ==\n" + , "h3" =: header 3 "Heading" =?> "=== Heading ===\n" + , "h4" =: header 4 "Heading" =?> "==== Heading ====\n" + ] + , testGroup "code blocks" + [ "no trailing newline in source" + =: codeBlock "foo" + =?> "```\nfoo\n```\n" + , "trailing newline preserved" + =: codeBlock "foo\n" + =?> "```\nfoo\n```\n" + ] + , testGroup "lists" + [ "bullet list" + =: bulletList [para "foo", para "bar"] + =?> "- foo\n- bar\n" + , "ordered list" + =: orderedList [para "foo", para "bar"] + =?> "+ foo\n+ bar\n" + , "nested bullet list" + =: bulletList [para "a" <> bulletList [para "b"]] + =?> "- a\n - b\n" + , "nested ordered list" + =: orderedList [para "a" <> orderedList [para "b"]] + =?> "+ a\n + b\n" + ] + , testGroup "definition lists" + [ "single term and definition" + =: definitionList [("term", [para "definition"])] + =?> ": **term**\n definition\n" + , "multiple terms" + =: definitionList + [ ("foo", [para "def foo"]) + , ("bar", [para "def bar"]) + ] + =?> ": **foo**\n def foo\n: **bar**\n def bar\n" + , "multiple definitions for one term" + =: definitionList [("term", [para "def1", para "def2"])] + =?> ": **term**\n def1\n def2\n" + ] + , testGroup "blockquote" + -- Txt2Tags has no blockquote syntax; content is rendered without special markup. + [ "blockquote rendered as plain content" + =: blockQuote (para "quoted") + =?> "quoted\n" + ] + , testGroup "horizontal rule" + [ "hr" =: horizontalRule =?> "\n---\n" + ] + , testGroup "tables" + [ "table with headers" + =: simpleTable [plain "A", plain "B"] + [[plain "1", plain "2"]] + =?> "|A|B|\n|1|2|\n" + , "table without headers" + =: simpleTable [] [[plain "1", plain "2"]] + =?> "|1|2|\n" + ] + , testGroup "links" + [ "link with label" + =: link "http://example.com" "" "example" + =?> "[example http://example.com]" + , "autolink" + =: link "http://example.com" "" "http://example.com" + =?> "http://example.com" + , "email" + =: link "mailto:user@example.com" "" "user@example.com" + =?> "" + ] + , testGroup "images" + [ "simple image (no title, no alt)" + -- Use imageWith + mempty to get truly empty alt ([]) vs str "" which gives [Str ""] + =: imageWith ("", [], []) "image.png" "" mempty + =?> "[image.png]" + , "image with title" + =: image "image.png" "My title" "ignored alt" + =?> "[image.png|My title]" + , "image with alt text (no title)" + =: image "image.png" "" "alt text" + =?> "[image.png|alt text]" + ] + , testGroup "raw blocks" + [ "txt2tags passthrough" + =: rawBlock "txt2tags" "raw content" + =?> "raw content" + ] + ] diff --git a/test/test-pandoc.hs b/test/test-pandoc.hs index 9ae97d9c0af9..02af58e46fbb 100644 --- a/test/test-pandoc.hs +++ b/test/test-pandoc.hs @@ -53,6 +53,7 @@ import qualified Tests.Writers.AnnotatedTable import qualified Tests.Writers.TEI import qualified Tests.Writers.Markua import qualified Tests.Writers.BBCode +import qualified Tests.Writers.Txt2Tags import qualified Tests.XML import qualified Tests.MediaBag import Text.Pandoc.Shared (inDirectory) @@ -86,6 +87,7 @@ tests pandocPath = testGroup "pandoc tests" , testGroup "Ms" Tests.Writers.Ms.tests , testGroup "AnnotatedTable" Tests.Writers.AnnotatedTable.tests , testGroup "BBCode" Tests.Writers.BBCode.tests + , testGroup "Txt2Tags" Tests.Writers.Txt2Tags.tests ] , testGroup "Readers" [ testGroup "LaTeX" Tests.Readers.LaTeX.tests From af4d48a1fa31bae62db703e2308fd9c263f75d19 Mon Sep 17 00:00:00 2001 From: luginf Date: Fri, 10 Apr 2026 23:12:01 +0200 Subject: [PATCH 10/11] Txt2Tags writer: export writeTxt2Tags from Text.Pandoc.Writers Add `writeTxt2Tags` to the export list so it is accessible via the `Text.Pandoc` module, fixing a compilation error in tests. --- src/Text/Pandoc/Writers.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Text/Pandoc/Writers.hs b/src/Text/Pandoc/Writers.hs index 9224648fbb46..5f6867d57287 100644 --- a/src/Text/Pandoc/Writers.hs +++ b/src/Text/Pandoc/Writers.hs @@ -75,6 +75,7 @@ module Text.Pandoc.Writers , writeTEI , writeTexinfo , writeTextile + , writeTxt2Tags , writeTypst , writeXML , writeXWiki From 9780b6b8414b52ce534824f34c164d5ddff6790a Mon Sep 17 00:00:00 2001 From: luginf Date: Sat, 11 Apr 2026 21:28:10 +0200 Subject: [PATCH 11/11] fixed tests --- src/Text/Pandoc/Writers/Txt2Tags.hs | 2 +- test/Tests/Writers/Txt2Tags.hs | 2 +- test/writer.t2t | 388 +++++++++++++--------------- 3 files changed, 184 insertions(+), 208 deletions(-) diff --git a/src/Text/Pandoc/Writers/Txt2Tags.hs b/src/Text/Pandoc/Writers/Txt2Tags.hs index 9b0c869c21c8..b84e7f64f5c9 100644 --- a/src/Text/Pandoc/Writers/Txt2Tags.hs +++ b/src/Text/Pandoc/Writers/Txt2Tags.hs @@ -114,7 +114,7 @@ blockToTxt2Tags opts b@(RawBlock f str) , isEnabled Ext_raw_html opts = return $ "\"\"\"\n" <> str <> "\n\"\"\"\n" | otherwise = "" <$ report (BlockNotRendered b) -blockToTxt2Tags _ HorizontalRule = return "\n---\n" +blockToTxt2Tags _ HorizontalRule = return "\n--------------------\n" blockToTxt2Tags opts (Header level _ inlines) = do -- Formatting is not allowed in headers, so strip it diff --git a/test/Tests/Writers/Txt2Tags.hs b/test/Tests/Writers/Txt2Tags.hs index caa04ddaf738..ff16ea5a134a 100644 --- a/test/Tests/Writers/Txt2Tags.hs +++ b/test/Tests/Writers/Txt2Tags.hs @@ -82,7 +82,7 @@ tests = =?> "quoted\n" ] , testGroup "horizontal rule" - [ "hr" =: horizontalRule =?> "\n---\n" + [ "hr" =: horizontalRule =?> "\n--------------------\n" ] , testGroup "tables" [ "table with headers" diff --git a/test/writer.t2t b/test/writer.t2t index f23c0ab477ae..c631846d8a51 100644 --- a/test/writer.t2t +++ b/test/writer.t2t @@ -1,7 +1,13 @@ + + + + + + This is a set of tests for pandoc. Most of them are adapted from John Gruber’s markdown test suite. ---------------------- +-------------------- = Headers = @@ -26,7 +32,7 @@ with no blank line with no blank line ---------------------- +-------------------- = Paragraphs = @@ -40,15 +46,14 @@ There should be a hard line break\\ here. ---------------------- +-------------------- = Block Quotes = E-mail style: - This is a block quote. It is pretty short. +This is a block quote. It is pretty short. -``` Code in a block quote: ``` @@ -56,23 +61,24 @@ sub status { print "working"; } ``` + A list: - + item one - + item two ++ item one ++ item two Nested block quotes: - nested +nested + +nested - nested -``` This should not be a block quote: 2 > 1. And a following paragraph. ---------------------- +-------------------- = Code Blocks = @@ -87,6 +93,7 @@ sub status { this code block is indented by one tab ``` + And: ``` @@ -95,7 +102,8 @@ And: These should not be escaped: \$ \\ \> \[ \{ ``` ---------------------- + +-------------------- = Lists = @@ -103,139 +111,126 @@ These should not be escaped: \$ \\ \> \[ \{ Asterisks tight: - - asterisk 1 - - asterisk 2 - - asterisk 3 +- asterisk 1 +- asterisk 2 +- asterisk 3 Asterisks loose: - - asterisk 1 - - asterisk 2 - - asterisk 3 +- asterisk 1 +- asterisk 2 +- asterisk 3 Pluses tight: - - Plus 1 - - Plus 2 - - Plus 3 +- Plus 1 +- Plus 2 +- Plus 3 Pluses loose: - - Plus 1 - - Plus 2 - - Plus 3 +- Plus 1 +- Plus 2 +- Plus 3 Minuses tight: - - Minus 1 - - Minus 2 - - Minus 3 +- Minus 1 +- Minus 2 +- Minus 3 Minuses loose: - - Minus 1 - - Minus 2 - - Minus 3 +- Minus 1 +- Minus 2 +- Minus 3 == Ordered == Tight: - + First - + Second - + Third ++ First ++ Second ++ Third and: - + One - + Two - + Three ++ One ++ Two ++ Three Loose using tabs: - + First - + Second - + Third ++ First ++ Second ++ Third and using spaces: - + One - + Two - + Three ++ One ++ Two ++ Three Multiple paragraphs: -+ style="list-style-type: decimal;"> -
  • Item 1, graf one.

    -

    Item 1. graf two. The quick brown fox jumped over the lazy dog’s back.

  • -
  • Item 2.

  • -
  • Item 3.

  • ++ Item 1, graf one. +Item 1. graf two. The quick brown fox jumped over the lazy dog’s back. ++ Item 2. ++ Item 3. == Nested == +- Tab - Tab - Tab - - Tab Here’s another: - + First - + Second: - - Fee - - Fie - - Foe - + Third ++ First ++ Second: + - Fee + - Fie + - Foe ++ Third Same thing but with paragraphs: - + First - + Second: - - Fee - - Fie - - Foe - + Third ++ First ++ Second: + - Fee + - Fie + - Foe ++ Third == Tabs and spaces == - - this is a list item indented with tabs - - this is a list item indented with spaces - - this is an example list item indented with tabs - - this is an example list item indented with spaces +- this is a list item indented with tabs +- this is a list item indented with spaces + - this is an example list item indented with tabs + - this is an example list item indented with spaces == Fancy list markers == -+ start="2" style="list-style-type: decimal;"> -
  • begins with 2

  • -
  • and now 3

    -

    with a continuation

    -+ start="4" style="list-style-type: lower-roman;"> -
  • sublist with roman numerals, starting with 4
  • -
  • more items -+ style="list-style-type: upper-alpha;"> -
  • a subsublist
  • -
  • a subsublist
  • - - ++ begins with 2 ++ and now 3 +with a continuation + + sublist with roman numerals, starting with 4 + + more items + + a subsublist + + a subsublist Nesting: -+ style="list-style-type: upper-alpha;"> -
  • Upper Alpha -+ style="list-style-type: upper-roman;"> -
  • Upper Roman. -+ start="6" style="list-style-type: decimal;"> -
  • Decimal start with 6 -+ start="3" style="list-style-type: lower-alpha;"> -
  • Lower alpha with paren
  • - - - ++ Upper Alpha + + Upper Roman. + + Decimal start with 6 + + Lower alpha with paren Autonumbering: - + Autonumber. - + More. - + Nested. ++ Autonumber. ++ More. + + Nested. Should not be a list item: @@ -244,58 +239,77 @@ M.A. 2007 B. Williams ---------------------- +-------------------- = Definition Lists = Tight using spaces: - * **apple** red fruit - * **orange** orange fruit - * **banana** yellow fruit +: **apple** + red fruit +: **orange** + orange fruit +: **banana** + yellow fruit Tight using tabs: - * **apple** red fruit - * **orange** orange fruit - * **banana** yellow fruit +: **apple** + red fruit +: **orange** + orange fruit +: **banana** + yellow fruit Loose: - * **apple** red fruit - * **orange** orange fruit - * **banana** yellow fruit +: **apple** + red fruit +: **orange** + orange fruit +: **banana** + yellow fruit Multiple blocks with italics: -: -
    //apple//
    -

    red fruit

    -

    contains seeds, crisp, pleasant to taste

    -
    //orange//
    -

    orange fruit

    +: **//apple//** + red fruit +contains seeds, crisp, pleasant to taste +: **//orange//** + orange fruit ``` { orange code block } ``` -

    orange block quote

    -
    + +orange block quote Multiple definitions, tight: - * **apple** red fruitcomputer - * **orange** orange fruitbank +: **apple** + red fruit + computer +: **orange** + orange fruit + bank Multiple definitions, loose: - * **apple** red fruitcomputer - * **orange** orange fruitbank +: **apple** + red fruit + computer +: **orange** + orange fruit + bank Blank line after term, indented marker, alternate markers: - * **apple** red fruitcomputer - * **orange** orange fruit - + sublist - + sublist +: **apple** + red fruit + computer +: **orange** + orange fruit + + sublist + + sublist = HTML Blocks = @@ -303,45 +317,27 @@ Simple block on one line: foo - And nested without indentation: foo - - bar - - Interpreted markdown in a table: - - - - - - -
    - + This is //emphasized// - - - + And this is **strong** - -
    - - + Here’s a simple block: foo - This should be a code block, though: ``` @@ -349,66 +345,45 @@ This should be a code block, though: foo ``` + As should this: ```
    foo
    ``` + Now, nested: foo - - - This should just be an HTML comment: - - - + Multiline: - - - - + Code block: ``` ``` + Just plain comment, with trailing spaces on the line: - - - + Code: ```
    ``` + Hr’s: - -
    -
    -
    -
    -
    -
    -
    -
    -
    - ---------------------- + +-------------------- = Inline Markup = @@ -426,7 +401,7 @@ So is **//this//** word. So is **//this//** word. -This is code: ''%%>%%'', ''%%$%%'', ''%%\%%'', ''%%\$%%'', ''%%%%''. +This is code: ``>``, ``$``, ``\``, ``\$``, ````. --This is //strikeout//.-- @@ -437,7 +412,7 @@ Subscripts: H2O, H23O, Hmany of themO. These should not be superscripts or subscripts, because of the unescaped spaces: a^b c^d, a~b c~d. ---------------------- +-------------------- = Smart quotes, ellipses, dashes = @@ -449,7 +424,7 @@ These should not be superscripts or subscripts, because of the unescaped spaces: ‘He said, “I want to go.”’ Were you alive in the 70’s? -Here is some quoted ‘''%%code%%''’ and a “[quoted link http://example.com/?foo=1&bar=2]”. +Here is some quoted ‘``code``’ and a “[quoted link http://example.com/?foo=1&bar=2]”. Some dashes: one—two — three—four — five. @@ -458,41 +433,41 @@ Dashes between numbers: 5–7, 255–66, 1987–1999. Ellipses…and…and…. ---------------------- +-------------------- = LaTeX = - - - - $2+2=4$ - - $x \in y$ - - $\alpha \wedge \omega$ - - $223$ - - $p$-Tree - - Here’s some display math: $$\frac{d}{dx}f(x)=\lim_{h\to 0}\frac{f(x+h)-f(x)}{h}$$ - - Here’s one that has a line break in it: $\alpha + \omega \times x^2$. +- +- $2+2=4$ +- $x \in y$ +- $\alpha \wedge \omega$ +- $223$ +- $p$-Tree +- Here’s some display math: $$\frac{d}{dx}f(x)=\lim_{h\to 0}\frac{f(x+h)-f(x)}{h}$$ +- Here’s one that has a line break in it: $\alpha + \omega \times x^2$. These shouldn’t be math: - - To get the famous equation, write ''%%$e = mc^2$%%''. - - $22,000 is a //lot// of money. So is $34,000. (It worked if “lot” is emphasized.) - - Shoes ($20) and socks ($5). - - Escaped ''%%$%%'': $73 //this should be emphasized// 23$. +- To get the famous equation, write ``$e = mc^2$``. +- $22,000 is a //lot// of money. So is $34,000. (It worked if “lot” is emphasized.) +- Shoes ($20) and socks ($5). +- Escaped ``$``: $73 //this should be emphasized// 23$. Here’s a LaTeX table: ---------------------- +-------------------- = Special Characters = Here is some unicode: - - I hat: Î - - o umlaut: ö - - section: § - - set membership: ∈ - - copyright: © +- I hat: Î +- o umlaut: ö +- section: § +- set membership: ∈ +- copyright: © AT&T has an ampersand in their name. @@ -537,7 +512,7 @@ Plus: + Minus: - ---------------------- +-------------------- = Links = @@ -580,6 +555,7 @@ This should [not][] be a link. ``` [not]: /url ``` + Foo [bar url/]. Foo [biz url/]. @@ -598,50 +574,50 @@ Here’s an [inline link in pointy braces script?foo=1&bar=2]. With an ampersand: http://example.com/?foo=1&bar=2 - - In a list? - - http://example.com/ - - It should. +- In a list? +- http://example.com/ +- It should. An e-mail address: - Blockquoted: http://example.com/ +Blockquoted: http://example.com/ -Auto-links should not occur here: ''%%%%'' +Auto-links should not occur here: ```` ``` or here: ``` ---------------------- + +-------------------- = Images = From “Voyage dans la Lune” by Georges Melies (1902): -{{lalune.jpg|Voyage dans la Lune lalune}} +[lalune.jpg|Voyage dans la Lune] +lalune + Here is a movie [movie.jpg|movie] icon. ---------------------- +-------------------- = Footnotes = -Here is a footnote reference,((Here is the footnote. It can go anywhere after the footnote reference. It need not be placed at the end of the document. -)) and another.((Here’s the long note. This one contains multiple blocks. +Here is a footnote reference,(Here is the footnote. It can go anywhere after the footnote reference. It need not be placed at the end of the document.) and another.(Here’s the long note. This one contains multiple blocks. Subsequent blocks are indented to show that they belong to the footnote (as with list items). ``` { } ``` -If you want, you can indent every line, but you can also be lazy and just indent the first line of each block. -)) This should //not// be a footnote reference, because it contains a space.[^my note] Here is an inline note.((This is //easier// to type. Inline notes may contain [links http://google.com] and ''%%]%%'' verbatim characters, as well as [bracketed text]. -)) - Notes can go in quotes.((In quote. - )) +If you want, you can indent every line, but you can also be lazy and just indent the first line of each block.) This should //not// be a footnote reference, because it contains a space.[^my note] Here is an inline note.(This is //easier// to type. Inline notes may contain [links http://google.com] and ``]`` verbatim characters, as well as [bracketed text].) + +Notes can go in quotes.(In quote.) - + And in list items.((In list.)) ++ And in list items.(In list.) This paragraph should not be part of the note, as it is not indented.