packages feed

kitchen-sink-0.1.0.0: src/KitchenSink/Commonmark/Free.hs

{-# OPTIONS_GHC -fno-warn-orphans #-}

module KitchenSink.Commonmark.Free where

import Commonmark.Extensions (HasDiv (..), HasSpan (..))
import Commonmark.Types
import Control.Applicative (pure)
import Data.Aeson (ToJSON (..), (.=))
import Data.Aeson qualified as Aeson
import Data.Char (Char)
import Data.Function (($), (.))
import Data.Int (Int)
import Data.Monoid (Monoid (..))
import Data.Semigroup (Semigroup (..))
import Data.Text (Text)
import GHC.Generics (Generic)
import Text.Show (Show)

import KitchenSink.Commonmark.HashTag (HasHashTag (..))

{- Note: neither 'Inline' nor 'Block' may be parameterized.

commonmark >= 0.3 ships an OVERLAPPABLE @IsBlock (f il) (f b)@ instance lifting
the class through applicative functors. 'IsBlock' has the functional dependency
@b -> il@, so a parameterized @Block a@ would unify with @f b@ and be forced to
pick @il ~ Block _@, conflicting with the @Inline@ instance below. GHC's fundep
consistency check runs regardless of OVERLAPPING pragmas, so keeping these type
constructors nullary is what makes the instances legal.
-}

-- INLINES

data InlineChunk
    = LineBreak
    | SoftBreak
    | Str Text
    | Entity Text
    | EscapedChar Char
    | Emph Inline
    | Strong Inline
    | Link Text Text Inline
    | Image Text Text Inline
    | Code Text
    | HashTag Text
    | RawInline Format Text
    | SpanInline Inline
    deriving (Show, Generic)
instance ToJSON InlineChunk

data Inline = Inline
    { inlineChunks :: [InlineChunk]
    , inlineAttributes :: Attributes
    }
    deriving (Show, Generic)
instance ToJSON Inline

instance ToJSON Format where
    toJSON (Format txt) = toJSON txt

inlineUniplate :: Inline -> [Inline]
inlineUniplate blk = mconcat [go c | c <- inlineChunks blk]
  where
    only a = [a]
    nil = []
    go bc = case bc of
        Emph il -> only il
        Strong il -> only il
        Link _ _ il -> only il
        Image _ _ il -> only il
        _ -> nil

inlineUniverse :: Inline -> [Inline]
inlineUniverse blk = blk : mconcat [inlineUniverse b | b <- inlineUniplate blk]

inline :: InlineChunk -> Inline
inline c = Inline [c] mempty

instance Semigroup Inline where
    Inline c1s a1s <> Inline c2s a2s = Inline (c1s <> c2s) (a1s <> a2s)

instance Monoid Inline where
    mempty = Inline mempty mempty

instance HasAttributes Inline where
    addAttributes xs (Inline c ys) = Inline c (xs <> ys)

instance HasHashTag Inline where
    hashtag t = Inline [HashTag t] mempty

instance HasSpan Inline where
    spanWith attrs il = Inline [SpanInline il] attrs

instance Rangeable Inline where
    ranged _ a = a

instance IsInline Inline where
    lineBreak = inline LineBreak
    softBreak = inline SoftBreak
    str = inline . Str
    entity = inline . Entity
    escapedChar = inline . EscapedChar
    emph = inline . Emph
    strong = inline . Strong
    link dst title = inline . Link dst title
    image dst title = inline . Image dst title
    code = inline . Code
    rawInline fmt = inline . RawInline fmt

-- BLOCKS

data BlockChunk
    = Paragraph Inline
    | Plain Inline
    | ThematicBreak
    | BlockQuote Block
    | CodeBlock Text Text
    | Heading Int Inline
    | RawBlock Format Text
    | ReferenceLinkDefinition Text (Text, Text)
    | List ListType ListSpacing [Block]
    | NestedDivBlock Block
    deriving (Show, Generic)
instance ToJSON BlockChunk

blockChunkInlines :: BlockChunk -> [Inline]
blockChunkInlines bc =
    let only a = [a]
        nil = []
     in case bc of
            Paragraph il -> only il
            Plain il -> only il
            Heading _ il -> only il
            _ -> nil

data Block = Block
    { blockChunks :: [BlockChunk]
    , blockAttributes :: Attributes
    }
    deriving (Show, Generic)
instance ToJSON Block

instance HasDiv Block where
    div_ bl = Block [NestedDivBlock bl] mempty

instance ToJSON EnumeratorType where
    toJSON Decimal = toJSON ("Decimal" :: Text)
    toJSON UpperAlpha = toJSON ("UpperAlpha" :: Text)
    toJSON LowerAlpha = toJSON ("LowerAlpha" :: Text)
    toJSON UpperRoman = toJSON ("UpperRoman" :: Text)
    toJSON LowerRoman = toJSON ("LowerRoman" :: Text)

instance ToJSON DelimiterType where
    toJSON Period = toJSON ("Period" :: Text)
    toJSON OneParen = toJSON ("OneParen" :: Text)
    toJSON TwoParens = toJSON ("TwoParens" :: Text)

instance ToJSON ListSpacing where
    toJSON TightList = toJSON ("TightList" :: Text)
    toJSON LooseList = toJSON ("LooseList" :: Text)

instance ToJSON ListType where
    toJSON (BulletList c) = Aeson.object ["tag" .= ("BulletList" :: Text), "contents" .= c]
    toJSON (OrderedList n et dt) =
        Aeson.object
            [ "tag" .= ("OrderedList" :: Text)
            , "contents"
                .= Aeson.toJSON1
                    [ toJSON n
                    , toJSON et
                    , toJSON dt
                    ]
            ]

blockUniplate :: Block -> [Block]
blockUniplate blk = mconcat [blockChunkBlocks c | c <- blockChunks blk]

blockChunkBlocks :: BlockChunk -> [Block]
blockChunkBlocks = go
  where
    only a = [a]
    nil = []
    go bc = case bc of
        BlockQuote bl -> only bl
        List _ _ bls -> bls
        _ -> nil

blockUniverse :: Block -> [Block]
blockUniverse blk = blk : mconcat [blockUniverse b | b <- blockUniplate blk]

blockInlines :: Block -> [Inline]
blockInlines root = do
    -- list monad!
    blk <- blockUniverse root
    chunk <- blockChunks blk
    il <- blockChunkInlines chunk
    pure il

block :: BlockChunk -> Block
block c = Block [c] mempty

instance Semigroup Block where
    Block c1s a1s <> Block c2s a2s = Block (c1s <> c2s) (a1s <> a2s)

instance Monoid Block where
    mempty = Block mempty mempty

instance HasAttributes Block where
    addAttributes xs (Block c ys) = Block c (xs <> ys)

instance Rangeable Block where
    ranged _ a = a

instance IsBlock Inline Block where
    paragraph = block . Paragraph
    plain = block . Plain
    thematicBreak = block ThematicBreak
    blockQuote = block . BlockQuote
    codeBlock t1 t2 = block $ CodeBlock t1 t2
    heading n = block . Heading n
    rawBlock fmt = block . RawBlock fmt
    referenceLinkDefinition lbl dsttile = block $ ReferenceLinkDefinition lbl dsttile
    list ltype lspacing = block . List ltype lspacing