packages feed

zwirn-0.2.3.1: app/zwirnmill/Docs/Markdown.hs

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wno-orphans #-}

module Docs.Markdown where

import Commonmark
import qualified Data.ByteString as BS
import Data.Char (isPunctuation, isSpace)
import Data.FileEmbed (embedDir)
import qualified Data.Map as Map
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified System.FilePath as Native
import qualified System.FilePath.Posix as Posix
import UI.Core (Doc (..), DocBlock (..), DocInline (..), DocMap, DocStyle (..))

instance Semigroup Doc where
  Doc a <> Doc b = Doc (a <> b)

instance Monoid Doc where
  mempty = Doc []

instance Rangeable Doc where
  ranged _ x = x

instance Rangeable [DocInline] where
  ranged _ x = x

instance HasAttributes Doc where
  addAttributes _ x = x

instance HasAttributes [DocInline] where
  addAttributes _ x = x

instance IsInline [DocInline] where
  lineBreak = [Linebreak]
  softBreak = [Linebreak]
  str s = [TextChunk s Normal]
  entity e = [TextChunk e Normal]
  escapedChar c = [TextChunk (T.singleton c) Normal]
  emph = map toEmph
    where
      toEmph (TextChunk x _) = TextChunk x Emph
      toEmph x = x
  strong = map toStrong
    where
      toStrong (TextChunk x _) = TextChunk x Strong
      toStrong x = x
  code c = [TextChunk c Code]
  rawInline _fmt t = [TextChunk t Normal]
  link dest _title l = [Link (T.concat $ map labelText l) dest]
  image dest _title l = [Link (T.concat $ map labelText l) dest]

labelText :: DocInline -> Text
labelText (TextChunk t _) = t
labelText (Link t _) = t
labelText Linebreak = ""

instance IsBlock [DocInline] Doc where
  paragraph is = Doc [Paragraph $ mergePunctuation is]
  plain is = Doc [Paragraph $ mergePunctuation is]
  thematicBreak = Doc [Paragraph [TextChunk "\n---\n" Normal]]
  blockQuote = id
  codeBlock _info ct = Doc [CodeBlock ct]
  heading _lvl is = Doc [Paragraph is]
  rawBlock _fmt t = Doc [Paragraph [TextChunk t Normal]]
  referenceLinkDefinition _ _ = mempty
  list _ty _spacing = mconcat

mergePunctuation :: [DocInline] -> [DocInline]
mergePunctuation (TextChunk a s1 : TextChunk b s2 : ds)
  | T.all isSpace b = TextChunk a s1 : TextChunk b s2 : mergePunctuation ds
  | T.all isSpace a = TextChunk a s1 : mergePunctuation (TextChunk b s2 : ds)
  | isPuncBack a || isPuncFront b = mergePunctuation $ TextChunk (a <> b) s1 : ds
  | otherwise = TextChunk a s1 : mergePunctuation (TextChunk b s2 : ds)
  where
    isPuncFront x = case T.uncons x of
      Just (c, _) -> isPunctuation c
      Nothing -> False
    isPuncBack x = case T.unsnoc x of
      Just (_, c) -> isPunctuation c
      Nothing -> False
mergePunctuation (d : ds) = d : mergePunctuation ds
mergePunctuation x = x

parseDoc :: Text -> Either ParseError Doc
parseDoc src =
  case commonmark "input" src of
    Left err -> Left err
    Right blocks -> Right blocks

normalizePath :: FilePath -> String
normalizePath = Posix.joinPath . Native.splitDirectories

embeddedFilesList :: [(FilePath, BS.ByteString)]
embeddedFilesList = $(embedDir "docs")

docMap :: DocMap
docMap = Map.fromList [(T.pack $ normalizePath path, toDoc content) | (path, content) <- embeddedFilesList]
  where
    toDoc bs = case parseDoc $ TE.decodeUtf8 bs of
      Left err -> error $ "Error in parsing documentation:" <> show err
      Right x -> x

startDoc :: Doc
startDoc = fromMaybe (error "Failed to get start Documentation!") $ Map.lookup "welcome.md" docMap