scrod-0.2026.2.21: source/library/Scrod/Convert/FromHaddock.hs
{-# LANGUAGE TemplateHaskellQuotes #-}
-- | Convert Haddock's documentation AST (@DocH@) into Scrod's 'Doc.Doc' type.
--
-- The Haddock parser produces @DocH Void Identifier@ nodes; this module
-- maps each node kind (paragraphs, code blocks, identifiers, hyperlinks,
-- examples, tables, etc.) to the corresponding @Scrod.Core.*@ constructors.
module Scrod.Convert.FromHaddock where
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Text as Text
import qualified Data.Void as Void
import qualified Documentation.Haddock.Parser as Haddock
import qualified Documentation.Haddock.Types as Haddock
import qualified Scrod.Core.Definition as Definition
import qualified Scrod.Core.Doc as Doc
import qualified Scrod.Core.Example as Example
import qualified Scrod.Core.Header as Header
import qualified Scrod.Core.Hyperlink as Hyperlink
import qualified Scrod.Core.Identifier as Identifier
import qualified Scrod.Core.Level as Level
import qualified Scrod.Core.ModLink as ModLink
import qualified Scrod.Core.ModuleName as ModuleName
import qualified Scrod.Core.Namespace as Namespace
import qualified Scrod.Core.NumberedItem as NumberedItem
import qualified Scrod.Core.Picture as Picture
import qualified Scrod.Core.Table as Table
import qualified Scrod.Core.TableCell as TableCell
import qualified Scrod.Spec as Spec
fromHaddock :: Haddock.DocH Void.Void Haddock.Identifier -> Doc.Doc
fromHaddock = convertDoc . Haddock.overIdentifier convertIdentifier
convertIdentifier :: Haddock.Namespace -> String -> Maybe Identifier.Identifier
convertIdentifier ns str =
Just
Identifier.MkIdentifier
{ Identifier.namespace = convertNamespace ns,
Identifier.value = stripQualifier $ Text.pack str
}
-- | Strips the module qualifier from an identifier. For example, @A.b@
-- becomes @b@ and @Data.List.map@ becomes @map@.
stripQualifier :: Text.Text -> Text.Text
stripQualifier t =
let (_, name) = Text.breakOnEnd (Text.singleton '.') t
in if Text.null name then t else name
convertNamespace :: Haddock.Namespace -> Maybe Namespace.Namespace
convertNamespace ns = case ns of
Haddock.Value -> Just Namespace.Value
Haddock.Type -> Just Namespace.Type
Haddock.None -> Nothing
convertDoc :: Haddock.DocH Void.Void Identifier.Identifier -> Doc.Doc
convertDoc doc = case doc of
Haddock.DocEmpty -> Doc.Empty
Haddock.DocAppend a b -> Doc.Append [convertDoc a, convertDoc b]
Haddock.DocString s -> Doc.String $ Text.pack s
Haddock.DocParagraph d -> Doc.Paragraph $ convertDoc d
Haddock.DocIdentifier i -> Doc.Identifier i
Haddock.DocModule ml ->
Doc.Module
ModLink.MkModLink
{ ModLink.name = ModuleName.MkModuleName . Text.pack $ Haddock.modLinkName ml,
ModLink.label = convertDoc <$> Haddock.modLinkLabel ml
}
Haddock.DocWarning _ -> Doc.Empty -- `DocWarning` is never found in markup.
Haddock.DocEmphasis d -> Doc.Emphasis $ convertDoc d
Haddock.DocMonospaced d -> Doc.Monospaced $ convertDoc d
Haddock.DocBold d -> Doc.Bold $ convertDoc d
Haddock.DocUnorderedList ds -> Doc.UnorderedList $ fmap convertDoc ds
Haddock.DocOrderedList items -> Doc.OrderedList $ fmap (\(n, d) -> NumberedItem.MkNumberedItem {NumberedItem.index = n, NumberedItem.item = convertDoc d}) items
Haddock.DocDefList defs -> Doc.DefList $ fmap (\(t, d) -> Definition.MkDefinition {Definition.term = convertDoc t, Definition.definition = convertDoc d}) defs
Haddock.DocCodeBlock d -> Doc.CodeBlock $ convertDoc d
Haddock.DocHyperlink h ->
Doc.Hyperlink
Hyperlink.MkHyperlink
{ Hyperlink.url = Text.pack $ Haddock.hyperlinkUrl h,
Hyperlink.label = convertDoc <$> Haddock.hyperlinkLabel h
}
Haddock.DocPic p ->
Doc.Pic
Picture.MkPicture
{ Picture.uri = Text.pack $ Haddock.pictureUri p,
Picture.title = Text.pack <$> Haddock.pictureTitle p
}
Haddock.DocMathInline s -> Doc.MathInline $ Text.pack s
Haddock.DocMathDisplay s -> Doc.MathDisplay $ Text.pack s
Haddock.DocAName s -> Doc.AName $ Text.pack s
Haddock.DocProperty s -> Doc.Property $ Text.pack s
Haddock.DocExamples es ->
Doc.Examples . NonEmpty.fromList $
fmap
( \e ->
Example.MkExample
{ Example.expression = Text.pack $ Haddock.exampleExpression e,
Example.result = Text.pack <$> Haddock.exampleResult e
}
)
es
Haddock.DocHeader h ->
Doc.Header
Header.MkHeader
{ Header.level = Level.fromInt $ Haddock.headerLevel h,
Header.title = convertDoc $ Haddock.headerTitle h
}
Haddock.DocTable t ->
Doc.Table
Table.MkTable
{ Table.headerRows = convertTableRow <$> Haddock.tableHeaderRows t,
Table.bodyRows = convertTableRow <$> Haddock.tableBodyRows t
}
convertTableRow :: Haddock.TableRow (Haddock.DocH Void.Void Identifier.Identifier) -> [TableCell.Cell Doc.Doc]
convertTableRow = fmap convertTableCell . Haddock.tableRowCells
convertTableCell :: Haddock.TableCell (Haddock.DocH Void.Void Identifier.Identifier) -> TableCell.Cell Doc.Doc
convertTableCell cell =
TableCell.MkCell
{ TableCell.colspan = fromIntegral $ Haddock.tableCellColspan cell,
TableCell.rowspan = fromIntegral $ Haddock.tableCellRowspan cell,
TableCell.contents = convertDoc $ Haddock.tableCellContents cell
}
spec :: (Applicative m, Monad n) => Spec.Spec m n -> n ()
spec s = do
Spec.named s 'fromHaddock $ do
Spec.it s "works with empty" $ do
Spec.assertEq s (fromHaddock Haddock.DocEmpty) Doc.Empty
Spec.it s "works with append" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocAppend (Haddock.DocString "a") (Haddock.DocString "b")
let expected = Doc.Append [Doc.String $ Text.pack "a", Doc.String $ Text.pack "b"]
Spec.assertEq s (fromHaddock input) expected
Spec.it s "works with string" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocString "hello"
Spec.assertEq s (fromHaddock input) (Doc.String $ Text.pack "hello")
Spec.it s "works with paragraph" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocParagraph (Haddock.DocString "text")
let expected = Doc.Paragraph (Doc.String $ Text.pack "text")
Spec.assertEq s (fromHaddock input) expected
Spec.it s "works with identifier" $ do
Spec.assertEq s (fromHaddock . Haddock._doc $ Haddock.parseParas Nothing "'x'") . Doc.Paragraph . Doc.Identifier . Identifier.MkIdentifier Nothing $ Text.pack "x"
Spec.it s "works with value identifier" $ do
Spec.assertEq s (fromHaddock . Haddock._doc $ Haddock.parseParas Nothing "v'y'") . Doc.Paragraph . Doc.Identifier . Identifier.MkIdentifier (Just Namespace.Value) $ Text.pack "y"
Spec.it s "works with type identifier" $ do
Spec.assertEq s (fromHaddock . Haddock._doc $ Haddock.parseParas Nothing "t'z'") . Doc.Paragraph . Doc.Identifier . Identifier.MkIdentifier (Just Namespace.Type) $ Text.pack "z"
Spec.it s "strips module qualifier from identifier" $ do
Spec.assertEq s (fromHaddock . Haddock._doc $ Haddock.parseParas Nothing "'A.b'") . Doc.Paragraph . Doc.Identifier . Identifier.MkIdentifier Nothing $ Text.pack "b"
Spec.it s "strips deep module qualifier from identifier" $ do
Spec.assertEq s (fromHaddock . Haddock._doc $ Haddock.parseParas Nothing "'Data.List.map'") . Doc.Paragraph . Doc.Identifier . Identifier.MkIdentifier Nothing $ Text.pack "map"
Spec.it s "works with module" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocModule Haddock.ModLink {Haddock.modLinkName = "Data.List", Haddock.modLinkLabel = Nothing}
let expected = Doc.Module ModLink.MkModLink {ModLink.name = ModuleName.MkModuleName $ Text.pack "Data.List", ModLink.label = Nothing}
Spec.assertEq s (fromHaddock input) expected
Spec.it s "works with emphasis" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocEmphasis (Haddock.DocString "em")
let expected = Doc.Emphasis (Doc.String $ Text.pack "em")
Spec.assertEq s (fromHaddock input) expected
Spec.it s "works with monospaced" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocMonospaced (Haddock.DocString "code")
let expected = Doc.Monospaced (Doc.String $ Text.pack "code")
Spec.assertEq s (fromHaddock input) expected
Spec.it s "works with bold" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocBold (Haddock.DocString "strong")
let expected = Doc.Bold (Doc.String $ Text.pack "strong")
Spec.assertEq s (fromHaddock input) expected
Spec.it s "works with unordered list" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocUnorderedList [Haddock.DocString "item1", Haddock.DocString "item2"]
let expected = Doc.UnorderedList [Doc.String $ Text.pack "item1", Doc.String $ Text.pack "item2"]
Spec.assertEq s (fromHaddock input) expected
Spec.it s "works with ordered list" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocOrderedList [(1, Haddock.DocString "first"), (2, Haddock.DocString "second")]
let expected =
Doc.OrderedList
[ NumberedItem.MkNumberedItem {NumberedItem.index = 1, NumberedItem.item = Doc.String $ Text.pack "first"},
NumberedItem.MkNumberedItem {NumberedItem.index = 2, NumberedItem.item = Doc.String $ Text.pack "second"}
]
Spec.assertEq s (fromHaddock input) expected
Spec.it s "works with definition list" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocDefList [(Haddock.DocString "term", Haddock.DocString "def")]
let expected =
Doc.DefList
[Definition.MkDefinition {Definition.term = Doc.String $ Text.pack "term", Definition.definition = Doc.String $ Text.pack "def"}]
Spec.assertEq s (fromHaddock input) expected
Spec.it s "works with code block" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocCodeBlock (Haddock.DocString "x = 1")
let expected = Doc.CodeBlock (Doc.String $ Text.pack "x = 1")
Spec.assertEq s (fromHaddock input) expected
Spec.it s "works with hyperlink" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocHyperlink Haddock.Hyperlink {Haddock.hyperlinkUrl = "https://example.com", Haddock.hyperlinkLabel = Nothing}
let expected = Doc.Hyperlink Hyperlink.MkHyperlink {Hyperlink.url = Text.pack "https://example.com", Hyperlink.label = Nothing}
Spec.assertEq s (fromHaddock input) expected
Spec.it s "works with picture" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocPic Haddock.Picture {Haddock.pictureUri = "image.png", Haddock.pictureTitle = Just "title"}
let expected = Doc.Pic Picture.MkPicture {Picture.uri = Text.pack "image.png", Picture.title = Just $ Text.pack "title"}
Spec.assertEq s (fromHaddock input) expected
Spec.it s "works with math inline" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocMathInline "x^2"
Spec.assertEq s (fromHaddock input) (Doc.MathInline $ Text.pack "x^2")
Spec.it s "works with math display" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocMathDisplay "\\sum"
Spec.assertEq s (fromHaddock input) (Doc.MathDisplay $ Text.pack "\\sum")
Spec.it s "works with anchor name" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocAName "anchor"
Spec.assertEq s (fromHaddock input) (Doc.AName $ Text.pack "anchor")
Spec.it s "works with property" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocProperty "prop> x + y"
Spec.assertEq s (fromHaddock input) (Doc.Property $ Text.pack "prop> x + y")
Spec.it s "works with examples" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocExamples [Haddock.Example {Haddock.exampleExpression = "1 + 1", Haddock.exampleResult = ["2"]}]
let expected = Doc.Examples (Example.MkExample {Example.expression = Text.pack "1 + 1", Example.result = [Text.pack "2"]} NonEmpty.:| [])
Spec.assertEq s (fromHaddock input) expected
Spec.it s "works with header" $ do
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocHeader Haddock.Header {Haddock.headerLevel = 2, Haddock.headerTitle = Haddock.DocString "Section"}
let expected = Doc.Header Header.MkHeader {Header.level = Level.Two, Header.title = Doc.String $ Text.pack "Section"}
Spec.assertEq s (fromHaddock input) expected
Spec.it s "works with table" $ do
let cell :: Haddock.TableCell (Haddock.DocH Void.Void Haddock.Identifier)
cell = Haddock.TableCell {Haddock.tableCellColspan = 1, Haddock.tableCellRowspan = 1, Haddock.tableCellContents = Haddock.DocString "data"}
let row :: Haddock.TableRow (Haddock.DocH Void.Void Haddock.Identifier)
row = Haddock.TableRow [cell]
let input :: Haddock.DocH Void.Void Haddock.Identifier
input = Haddock.DocTable Haddock.Table {Haddock.tableHeaderRows = [], Haddock.tableBodyRows = [row]}
let expectedCell = TableCell.MkCell {TableCell.colspan = 1, TableCell.rowspan = 1, TableCell.contents = Doc.String $ Text.pack "data"}
let expected = Doc.Table Table.MkTable {Table.headerRows = [], Table.bodyRows = [[expectedCell]]}
Spec.assertEq s (fromHaddock input) expected