-- | A small Markdown eDSL.
module Util.Markdown
( Markdown
, HeaderLevel (..)
, nextHeaderLevel
, mdHeader
, mdSubsection
, mdSubsectionTitle
, mdBold
, mdItalic
, mdTicked
, mdRef
, mdLocalRef
, mdAnchor
, mdSeparator
, mdSpoiler
, mdComment
, md
) where
import qualified Data.String.Interpolate.IsString as Interpolate
import Data.String.Interpolate.Util (unindent)
import Fmt (Builder, build, (+|), (|+))
import Language.Haskell.TH.Quote (QuasiQuoter(..))
-- | A piece of markdown document.
--
-- This is opposed to 'Text' type, which in turn is not supposed to contain
-- markup elements.
type Markdown = Builder
-- | Level of header, starting from 1.
newtype HeaderLevel = HeaderLevel Int
nextHeaderLevel :: HeaderLevel -> HeaderLevel
nextHeaderLevel (HeaderLevel l) = HeaderLevel (l + 1)
mdHeader :: HeaderLevel -> Markdown -> Markdown
mdHeader (HeaderLevel lvl) text =
mconcat (replicate lvl "#") +| " " +| text |+ "\n\n"
mdSubsectionTitle :: Markdown -> Markdown
mdSubsectionTitle title = mdBold (title <> ":")
mdSubsection :: Markdown -> Markdown -> Markdown
mdSubsection name txt = mdSubsectionTitle name <> " " <> txt
mdBold :: Markdown -> Markdown
mdBold x = "**" <> x <> "**"
mdItalic :: Markdown -> Markdown
mdItalic x = "*" <> x <> "*"
mdTicked :: Markdown -> Markdown
mdTicked x = "`" +| x |+ "`"
mdEscapeAnchorS :: String -> Markdown
mdEscapeAnchorS = \case
[] -> ""
' ' : s -> "-" <> mdEscapeAnchorS s
'(' : s -> "lparen" <> mdEscapeAnchorS s
')' : s -> "rparen" <> mdEscapeAnchorS s
'[' : s -> "lbracket" <> mdEscapeAnchorS s
']' : s -> "rbracket" <> mdEscapeAnchorS s
'{' : s -> "lbrace" <> mdEscapeAnchorS s
'}' : s -> "rbrace" <> mdEscapeAnchorS s
',' : s -> "comma" <> mdEscapeAnchorS s
';' : s -> "semicolon" <> mdEscapeAnchorS s
':' : s -> "colon" <> mdEscapeAnchorS s
'#' : s -> "hash" <> mdEscapeAnchorS s
c : s -> build (toText [c]) <> mdEscapeAnchorS s
-- | Turn text into valid anchor. Human-readability is not preserved.
mdEscapeAnchor :: Text -> Markdown
mdEscapeAnchor = mdEscapeAnchorS . toString
mdRef :: Markdown -> Markdown -> Markdown
mdRef txt ref = "[" <> txt <> "](" <> ref <> ")"
mdLocalRef :: Markdown -> Text -> Markdown
mdLocalRef txt anchor = mdRef txt ("#" <> mdEscapeAnchor anchor)
mdAnchor :: Text -> Markdown
mdAnchor name = " mdEscapeAnchor name <> "\">\n\n"
mdSeparator :: Markdown
mdSeparator = "---\n\n"
-- | Text which is hidden until clicked.
mdSpoiler :: Markdown -> Markdown -> Markdown
mdSpoiler name contents =
mconcat $ intersperse "\n"
[ "" <> htmlBold (build name) <> "
"
, contents
, "
" ] where -- Markdown's bold does not always work within spoiler header htmlBold txt = "" <> txt <> "" mdComment :: Builder -> Builder mdComment commentText = "" -- | Quasi quoter for Markdown. md :: QuasiQuoter md = QuasiQuoter { quoteExp = \s -> [|fromString @Markdown $ unindent $(quoteExp Interpolate.i s) |] , quotePat = \_ -> fail "Cannot be used at pattern position" , quoteType = \_ -> fail "Cannot be used at type position" , quoteDec = \_ -> fail "Cannot be used as declaration" }