diff --git a/MANUAL.txt b/MANUAL.txt index fd2a2fbb9aa9..55b22cc9562d 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 0f6ebbfb21c6..05a1c581a81e 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 @@ -348,6 +349,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 @@ -391,6 +393,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 @@ -672,6 +675,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, @@ -919,6 +923,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.hs b/src/Text/Pandoc/Writers.hs index e74ea4f00353..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 @@ -135,6 +136,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 +204,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/src/Text/Pandoc/Writers/Txt2Tags.hs b/src/Text/Pandoc/Writers/Txt2Tags.hs new file mode 100644 index 000000000000..b84e7f64f5c9 --- /dev/null +++ b/src/Text/Pandoc/Writers/Txt2Tags.hs @@ -0,0 +1,396 @@ +{-# LANGUAGE OverloadedStrings #-} +{- | + Module : Text.Pandoc.Writers.Txt2Tags + Copyright : Copyright (C) 2008-2024 Eric Forgeot, based on John MacFarlane DokuWiki writer + License : GNU GPL, version 2 or above + + Maintainer : Clare Macrae + Stability : alpha + Portability : portable + +Conversion of 'Pandoc' documents to Txt2Tags markup. + +Txt2Tags: +-} + +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 (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), 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) + +data WriterState = WriterState { + } + +data WriterEnvironment = WriterEnvironment { + 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 } + +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. +-- The %%text%% syntax disables Txt2Tags formatting interpretation. +escapeString :: Text -> Text +escapeString = T.replace "``" "%%``%%" . + T.replace "--" "%%--%%" . + T.replace "__" "%%__%%" . + T.replace "**" "%%**%%" . + T.replace "//" "%%//%%" + +-- | Convert Pandoc block element to Txt2Tags. +blockToTxt2Tags :: PandocMonad m + => WriterOptions -- ^ Options + -> Block -- ^ Block element + -> Txt2Tags m Text + +blockToTxt2Tags opts (Div _attrs bs) = do + contents <- blockListToTxt2Tags opts bs + indent <- asks stIndent + return $ contents <> if T.null indent then "\n" else "" + +blockToTxt2Tags opts (Plain inlines) = + inlineListToTxt2Tags opts inlines + +blockToTxt2Tags opts (Para inlines) = do + indent <- asks stIndent + contents <- inlineListToTxt2Tags opts inlines + 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 + -- Use the Txt2Tags raw area syntax (""") for block-level HTML pass-through: + | f == Format "html" + , isEnabled Ext_raw_html opts = return $ "\"\"\"\n" <> str <> "\n\"\"\"\n" + | otherwise = "" <$ report (BlockNotRendered b) + +blockToTxt2Tags _ HorizontalRule = return "\n--------------------\n" + +blockToTxt2Tags opts (Header level _ inlines) = do + -- Formatting is not allowed in headers, so strip it + contents <- inlineListToTxt2Tags opts $ removeFormatting inlines + let eqs = T.replicate level "=" + return $ eqs <> " " <> contents <> " " <> eqs <> "\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) = + blockListToTxt2Tags opts blocks + +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 (maybe 0 maximum . nonEmpty . 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 (BulletList items) = do + indent <- asks stIndent + 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 + 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 + +blockToTxt2Tags opts (DefinitionList items) = do + indent <- asks stIndent + contents <- local (\s -> s { stIndent = stIndent s <> " " }) + (mapM (definitionListItemToTxt2Tags opts) items) + return $ vcat contents <> if T.null indent then "\n" else "" + +-- Auxiliary functions for lists: + +-- | Convert bullet list item (list of blocks) to Txt2Tags. +listItemToTxt2Tags :: PandocMonad m + => WriterOptions -> [Block] -> Txt2Tags m Text +listItemToTxt2Tags opts items = do + bs <- mapM (blockToTxt2Tags opts) items + indent <- asks stIndent + -- 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. +orderedListItemToTxt2Tags :: PandocMonad m => WriterOptions -> [Block] -> Txt2Tags m Text +orderedListItemToTxt2Tags opts items = do + bs <- mapM (blockToTxt2Tags opts) items + indent <- asks stIndent + -- 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]]) + -> Txt2Tags m Text +definitionListItemToTxt2Tags opts (label, items) = do + labelText <- inlineListToTxt2Tags opts label + 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, 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 paragraph break + 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 + -- 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 }) $ + 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 <> "--" + +-- Txt2Tags has no superscript/subscript syntax; fall back to HTML tags. +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 + +-- | Inline code uses Txt2Tags verbatim syntax (double backticks). +inlineToTxt2Tags _ (Code _ str) = + 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 opts il@(RawInline f 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) + +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 + _ -> src + +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 <> "]" + +-- | Txt2Tags has no footnote syntax; render note content inline in parentheses. +inlineToTxt2Tags opts (Note contents) = do + contents' <- blockListToTxt2Tags opts contents + return $ "(" <> T.strip contents' <> ")" + +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-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/test/Tests/Old.hs b/test/Tests/Old.hs index ed5a3ef938bf..af92bd9d4feb 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"] diff --git a/test/Tests/Writers/Txt2Tags.hs b/test/Tests/Writers/Txt2Tags.hs new file mode 100644 index 000000000000..ff16ea5a134a --- /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/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/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 diff --git a/test/writer.t2t b/test/writer.t2t new file mode 100644 index 000000000000..c631846d8a51 --- /dev/null +++ b/test/writer.t2t @@ -0,0 +1,623 @@ + + + + + + +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: + ++ 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 == + ++ begins with 2 ++ and now 3 +with a continuation + + sublist with roman numerals, starting with 4 + + more items + + a subsublist + + a subsublist + +Nesting: + ++ Upper Alpha + + Upper Roman. + + Decimal start with 6 + + 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 fruit + computer +: **orange** + orange fruit + bank + +Multiple definitions, loose: + +: **apple** + red fruit + computer +: **orange** + orange fruit + bank + +Blank line after term, indented marker, alternate markers: + +: **apple** + red fruit + computer +: **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.