content: {-# LANGUAGE OverloadedStrings #-}
  {- |
     Module      : Tests.Readers.Mdoc
     Copyright   : © 2024 Evan Silberman
     License     : GNU GPL, version 2 or above
  
     Maintainer  : 
     Stability   : alpha
     Portability : portable
  
  Tests for the Mdoc reader.
  -}
  
  module Tests.Readers.Mdoc (tests) where
  
  import Data.Text (Text)
  import qualified Data.Text as T
  import Test.Tasty
  import Test.Tasty.HUnit (HasCallStack)
  import Tests.Helpers
  import Text.Pandoc
  import Text.Pandoc.Arbitrary ()
  import Text.Pandoc.Builder
  
  mdoc :: Text -> Pandoc
  mdoc = purely $ readMdoc def
  
  infix 4 =:
  (=:) :: (ToString c, HasCallStack)
       => String -> (Text, c) -> TestTree
  (=:) = test mdoc
  
  cls :: Text -> Attr
  cls x = (mempty, [x], mempty)
  
  tests :: [TestTree]
  tests = [
    testGroup "one-line enclosures"
      [ "Dq" =:
          ".Dq hello world" =?>
          para (doubleQuoted "hello world")
      , "Sq" =:
          ".Sq hello world" =?>
          para (singleQuoted "hello world")
      , "empty" =:
          ".Dq" =?>
          para (doubleQuoted mempty)
      , "nested" =:
          ".Dq Pq hello world" =?>
          para (doubleQuoted "(hello world)")
      , "nested with closing delimiters" =:
          ".Dq Pq hi mom !" =?>
          para (doubleQuoted "(hi mom)" <> "!")
      , "nested multiline enclosure" =:
          ".Dq Po a \\&; b \\&; c Pc ." =?>
          para (doubleQuoted "(a ; b ; c)" <> ".")
      , "with inlines" =:
          ".Dq hello Sy world ." =?>
          para (doubleQuoted ("hello" <> space <> strong "world" <> "."))
      , "with text production" =:
          ".Dq I love the St -iso8601 standard!" =?>
          para (doubleQuoted "I love the ISO 8601 standard!")
      , "with Ns" =:
          ".Op , Ns Ar value ..." =?>
          para ("[," <> (codeWith (cls "variable") "value ...") <> "]")
      , "ending with open delimiter" =:
          ".Dq hi (" =?>
          para (doubleQuoted "hi (")
      ]
    , testGroup "multiline enclosures"
      [ "nested multiline" =:
          T.unlines [".Bo", ".Po", "hi", ".Pc", ".Bc"] =?>
          para ("[(hi)]")
      , "nested on one line" =:
          ".Bo Po hi Pc Bc" =?>
          para ("[(hi)]")
      , "with wacky delimiters" =:
          ".Bo ( | hi ! Bc ?" =?>
          para ("([| hi!]?")
      ]
    , testGroup "simple inlines"
      [ "Sy" =:
          ".Sy hello world" =?>
          para (strong "hello world")
      , "Em" =:
          ".Em hello world" =?>
          para (emph "hello world")
      , "Ev" =:
          ".Ev HELLO_WORLD ," =?>
          para (codeWith (cls "Ev") "HELLO_WORLD" <> ",")
      , "Ar" =:
          ".Ar ) z" =?>
          para (codeWith (cls "variable") "file ..." <> ") " <> codeWith (cls "variable") "z")
      , "In" =:
          ".In ( math.h ) b c" =?>
          para ("(" <> codeWith (cls "In") "<math.h>" <> ") b c")
      , "Mt" =:
          ".Mt a@example.org , b@example.org" =?>
          para ((link "mailto:a@example.org" "" "a@example.org") <>
                 "," <> space <> (link "mailto:b@example.org" "" "b@example.org"))
      , "No" =:
          ".No ( hello , world ! )" =?>
          para "(hello, world!)"
      , "empty Pa with closing punctuation" =:
          ".Pa ) z" =?>
          para (spanWith (cls "Pa") "~" <> ")" <> space <> spanWith (mempty, ["Pa"], mempty) "z")
      , "delimiters" =:
          ".Sy ( hello world )" =?>
          para (mconcat ["(", strong "hello world", ")"])
      , "multiple" =:
          ".Sy hello Em world" =?>
          para (strong "hello" <> space <> emph "world")
      ]
    , testGroup "Fl"
      [ "simple" =:
          ".Fl w" =?>
          para (codeWith (cls "Fl") "-w")
      , "multiple" =:
          ".Fl W all" =?>
          para (codeWith (cls "Fl") "-W" <> space <> codeWith (cls "Fl") "-all")
      , "empty with following macro" =:
          ".Fl Cm x" =?>
          para (codeWith (cls "Fl") "-" <> codeWith (cls "Cm") "x")
      , "enclosed with following macro" =:
          ".Pq Fl Cm x" =?>
          para ("(" <> codeWith (cls "Fl") "-" <> codeWith (cls "Cm") "x" <> ")")
      -- XXX this is a mandoc delta, the period is meant to land outside
      -- the enclosure. parseInline has learned how to defer eols but not
      -- closing delimiters
      , "enclosed with following delimiters" =:
          ".Pq Fl x ." =?>
          para ("(" <> codeWith (cls "Fl") "-x" <> ".)")
      , "following Ns" =:
          ".Fl W Ns Cm all" =?>
          para (codeWith (cls "Fl") "-W" <> codeWith (cls "Cm") "all")
      , "GNU" =:
          ".Fl -help" =?>
          para (codeWith (cls "Fl") "--help")
      , "GNU escaped" =:
          ".Fl \\-help" =?>
          para (codeWith (cls "Fl") "--help")
      , "GNU Fl Fl" =:
          ".Fl Fl help" =?>
          para (codeWith (cls "Fl") "--help")
      , "punctuation" =:
          ".Op Fl a | b" =?>
          para ("[" <> codeWith (cls "Fl") "-a" <> " | " <> codeWith (cls "Fl") "-b" <> "]")
      , "middle close paren" =:
          ".Fl a ) z" =?>
          para (codeWith (cls "Fl") "-a" <> ") " <> codeWith (cls "Fl") "-z")
      , "empty with close paren" =:
          ".Fl ) z" =?>
          para (codeWith (cls "Fl") "-" <> ") " <> codeWith (cls "Fl") "-z")
      , "empty with pipe" =:
          ".Fl | z" =?>
          para (codeWith (cls "Fl") "-" <> " | " <> codeWith (cls "Fl") "-z")
      , "empty with parens" =:
          ".Fl ( )" =?>
          para ("(" <> codeWith (cls "Fl") "-" <> ")")
      ]
    , testGroup "links"
      [ "basic" =:
          ".Lk href name" =?>
          para (link "href" "" "name")
      , "complicated" =:
          ".Lk , ( href name )" =?>
          para ("," <> space <> "(" <> link "href" "" "name" <> ")")
      , "unnamed" =:
          ".Lk href" =?>
          para (link "href" "" "href")
      ]
    , testGroup "Ns macro"
      [ "at the beginning of a macro line (mandoc delta)" =:
          T.unlines [".Op before", ".Ns Op after"] =?>
          para "[before] [after]"  -- mandoc: warning
      , "after a block closing macro" =:
          T.unlines [".Oo before", ".Oc Ns Op after"] =?>
          para "[before][after]"
      , "in the middle of a macro line" =:
          ".Oo before Oc Ns Op after" =?>
          para "[before][after]"
      , "before closing punctuation" =:
          ".Oo before Oc Ns : Op after" =?>
          para "[before]: [after]"  -- mandoc: warning
      , "after closing punctuation" =:
          ".Oo before Oc : Ns Op after" =?>
          para "[before]:[after]"
      , "at the end of a macro line" =:
          T.unlines [".Oo before Oc Ns", ".Op after"] =?>
          para "[before][after]"
      , "at the end of a partial-implicit line" =:
          T.unlines [".Op before Ns", ".Op after"] =?>
          para "[before][after]"
      , "normal words" =:
          ".No no Ns ns No no" =?>
          para ("nons" <> space <> "no")
      , "opening punctuation" =:
          ".No no Ns \"(\" ns No no" =?>
          para ("no(ns" <> space <> "no")
      , "closing punctuation" =:
          ".No no \"Ns\" ns \")\" No no" =?>
          para ("nons)" <> space <> "no")
      ]
    , testGroup "spacing mode"
      [ "all text" =:
          T.unlines ["a", ".Sm off", "b c", "d", ".Sm on", "e"] =?>
          para ("a b c d e")
      , "text around macro" =:
          T.unlines ["a", ".Sm off", ".Sy b c", ".Sm on", "d"] =?>
          para ("a" <> space <> strong "bc" <> space <> "d")
      , "mulitple macros" =:
          T.unlines ["a", ".Sm off", ".Sy b Em c", ".Sm on", "d"] =?>
          para ("a" <> space <> strong "b" <> emph "c" <> space <> "d")
      , "mulitple control lines" =:
          T.unlines ["a", ".Sm off", ".Sy b", ".Em c", ".Sm on", "d"] =?>
          para ("a" <> space <> strong "b" <> emph "c" <> space <> "d")
      , "mixed control and text lines" =:
          T.unlines ["a", ".Sm off", ".Sy b", "c", ".Em d", ".Sm on", "d"] =?>
          para ("a" <> space <> strong "b" <> "c" <> emph "d" <> space <> "d")
      , "delimiters" =:
          T.unlines [".Sm off", ".Em a ", ".Em [ b | c ]", ".Sm on"] =?>
          para (emph "a" <> "[" <> emph "b" <> "|" <> emph "c" <> "]")
      ]
    , testGroup "Ap macro"
      [ "in the middle of a macro line" =:
          ".Xr mandoc 1 Ap s" =?>
          para (spanWith (cls "Xr") "mandoc(1)" <> "'s")
      -- mandoc difference: the edge case of "Ap (" tested in this mandoc regress
      -- isn't present in any actual OpenBSD base system manuals, where Ap is
      -- only ever followed by a letter. Furthermore, "Ap" is generally uncommon
      -- compared to "Ns '" (e.g. ".Xr mandoc 1 Ns 's"). I'm accepting a
      -- difference from mandoc here because correctly suppressing space after
      -- the "(" here would require more refactoring than I feel like doing at
      -- time of writing.
      -- per mandoc, should be: para (strong "bold" <> "'(" <> strong "bold")
      , "with punctuation and called macro" =:
          ".Sy bold Ap ( \"Sy\" bold" =?>
          para (strong "bold" <> "'( " <> strong "bold")
      ]
    , testGroup "Pf macro"
      [ "closing punctuation" =:
          ".Pf . right ." =?>
          para ".right."
      , "double punctuation" =:
          ".Pf . . double" =?>
          para ".. double"
      , "opening punctuation" =:
          ".Pf ( left ." =?>
          para "(left."
      , "unparsed argument" =:
          ".Pf Ar Sy gument " =?>
          para ("Ar" <> strong "gument")
      ]
    , testGroup "text production"
      [ "early NetBSD versions" =:
          T.unlines [".Nx 0.9a", "and", ".Nx 9.4b"] =?>
          para "NetBSD 0.9A and NetBSD 9.4b"
      , "with Ns" =:
          ".Ox Ns -specific" =?>
          para "OpenBSD-specific"
      , "with punctuation" =:
          ".Fx ," =?>
          para "FreeBSD,"
      , "with argument and punctuation" =:
          ".Fx 12.0 ." =?>
          para "FreeBSD 12.0."
      , "BSD alone" =:
          ".Bx ." =?>
          para "BSD."
      , "BSD with macro" =:
          ".Bx No rocks" =?>
          para "BSD rocks"
      , "BSD with version" =:
          ".Bx 4.4 ," =?>
          para "4.4BSD,"
      , "BSD with variant" =:
          ".Bx 4.3 tahoe !" =?>
          para "4.3BSD-Tahoe!"
      ]
    , testGroup "inline punctuation"
      [ testGroup "leading punctuation"
        [ "open paren"           =: ".Em ( b"     =?> para ("(" <> emph "b")
        , "open square bracket"  =: ".Em \"[\" b" =?> para ("[" <> emph "b")
        , "pipe"                 =: ".Em | b"     =?> para ("|" <> space <> emph "b")
        , "period"               =: ".Em . b"     =?> para ("." <> space <> emph "b")
        , "comma"                =: ".Em , b"     =?> para ("," <> space <> emph "b")
        , "semicolon"            =: ".Em ; b"     =?> para (";" <> space <> emph "b")
        , "colon"                =: ".Em : b"     =?> para (":" <> space <> emph "b")
        , "question mark"        =: ".Em ? b"     =?> para ("?" <> space <> emph "b")
        , "exclamation"          =: ".Em ! b"     =?> para ("!" <> space <> emph "b")
        , "close paren"          =: ".Em ) b"     =?> para (")" <> space <> emph "b")
        , "close square bracket" =: ".Em \"]\" b" =?> para ("]" <> space <> emph "b")
        ]
      , testGroup "trailing punctuation"
        [ "open paren"           =: ".Em a ("     =?> para (emph "a" <> space <> "(")
        , "open square bracket"  =: ".Em a ["     =?> para (emph "a" <> space <> "[")
        , "pipe"                 =: ".Em a |"     =?> para (emph "a" <> space <> "|")
        , "period"               =: ".Em a ."     =?> para (emph "a" <> ".")
        , "comma"                =: ".Em a ,"     =?> para (emph "a" <> ",")
        , "semicolon"            =: ".Em a ;"     =?> para (emph "a" <> ";")
        , "colon"                =: ".Em a :"     =?> para (emph "a" <> ":")
        , "question mark"        =: ".Em a ?"     =?> para (emph "a" <> "?")
        , "exclamation"          =: ".Em a !"     =?> para (emph "a" <> "!")
        , "close parens"         =: ".Em a \")\"" =?> para (emph "a" <> ")")
        , "close square bracket" =: ".Em a ]"     =?> para (emph "a" <> "]")
        ]
      , testGroup "middle punctuation"
        [ "open paren"           =: ".Em a ( b"     =?> para (mconcat [emph "a", space, "(", emph "b"])
        , "open square bracket"  =: ".Em a [ b"     =?> para (mconcat [emph "a", space, "[", emph "b"])
        , "pipe"                 =: ".Em a \"|\" b" =?> para (mconcat [emph "a", space, "|", space, emph "b"])
        , "period"               =: ".Em a . b"     =?> para (mconcat [emph "a", ".", space, emph "b"])
        , "comma"                =: ".Em a , b"     =?> para (mconcat [emph "a", ",", space, emph "b"])
        , "semicolon"            =: ".Em a ; b"     =?> para (mconcat [emph "a", ";", space, emph "b"])
        , "colon"                =: ".Em a \":\" b" =?> para (mconcat [emph "a", ":", space, emph "b"])
        , "question mark"        =: ".Em a ? b"     =?> para (mconcat [emph "a", "?", space, emph "b"])
        , "exclamation"          =: ".Em a ! b"     =?> para (mconcat [emph "a", "!", space, emph "b"])
        , "close paren"          =: ".Em a ) b"     =?> para (mconcat [emph "a", ")", space, emph "b"])
        , "close square bracket" =: ".Em a ] b"     =?> para (mconcat [emph "a", "]", space, emph "b"])
        ]
      ]
    ]
  