{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StrictData #-}
module Headroom.Ext.Haskell.Haddock
( HaddockModuleHeader(..)
, extractFieldOffsets
, extractModuleHeader
, indentField
, stripCommentSyntax
)
where
import Control.Applicative ( Alternative(..) )
import Control.Monad ( ap )
import Data.Default.Class ( Default(..) )
import Headroom.Data.Regex ( re
, replace
, scan
)
import Headroom.Data.TextExtra ( fromLines
, toLines
)
import Headroom.Template ( Template(..) )
import Headroom.Types ( HaddockFieldOffsets(..)
, TemplateMeta(..)
)
import RIO
import qualified RIO.Char as C
import qualified RIO.Text as T
data HaddockModuleHeader = HaddockModuleHeader
{ hmhCopyright :: Maybe Text
, hmhLicense :: Maybe Text
, hmhMaintainer :: Maybe Text
, hmhPortability :: Maybe Text
, hmhStability :: Maybe Text
, hmhShortDesc :: Maybe Text
, hmhLongDesc :: Maybe Text
}
deriving (Eq, Show)
extractFieldOffsets :: (Template t)
=> t
-> HaddockFieldOffsets
extractFieldOffsets template = HaddockFieldOffsets { .. }
where
hfoCopyright = extractCopyrightOffset text
text = stripCommentSyntax . rawTemplate $ template
extractCopyrightOffset :: Text -> Maybe Int
extractCopyrightOffset text = case scan [re|\h*Copyright\h*:\h*|] text of
[(full, _)] -> Just . T.length $ full
_ -> Nothing
extractModuleHeader :: Text
-> Maybe TemplateMeta
-> HaddockModuleHeader
extractModuleHeader text meta =
let hmhCopyright = indent hfoCopyright <$> extractField "Copyright"
hmhLicense = extractField "License"
hmhMaintainer = extractField "Maintainer"
hmhPortability = extractField "Portability"
hmhStability = extractField "Stability"
hmhShortDesc = extractField "Description"
hmhLongDesc = if null rest' then Nothing else process rest'
in HaddockModuleHeader { .. }
where
(fields', rest') = fromMaybe ([], input) $ runP fields input
input = T.unpack . stripCommentSyntax $ text
extractField name = fmap (T.strip . T.pack) (lookup name fields')
process = Just . T.strip . T.pack
indent c t = T.strip $ indentField c t
HaddockFieldOffsets {..} = case meta of
Just (HaskellTemplateMeta offsets') -> offsets'
_ -> def
indentField :: Maybe Int
-> Text
-> Text
indentField Nothing text = text
indentField (Just offset) text = fromLines . go . toLines $ text
where
go [] = []
go [x ] = [x]
go (x : xs) = x : fmap ((prefix <>) . T.stripStart) xs
prefix = T.replicate offset " "
stripCommentSyntax :: Text
-> Text
stripCommentSyntax text = fromLines $ go (toLines text) []
where
regex = [re|^(-- \||-{2,})|^\h*({-\h?\|?)|(-})\h*$|]
strip = replace regex (const . const $ "")
go [] acc = reverse acc
go (x : xs) acc = go xs (strip x : acc)
data C = C {-# UNPACK #-} !Int Char
newtype P a = P { unP :: [C] -> Maybe ([C], a) }
deriving Functor
instance Applicative P where
pure x = P $ \s -> Just (s, x)
(<*>) = ap
instance Monad P where
return = pure
m >>= k = P $ \s0 -> do
(s1, x) <- unP m s0
unP (k x) s1
instance Alternative P where
empty = P $ const Nothing
a <|> b = P $ \s -> unP a s <|> unP b s
runP :: P a -> String -> Maybe a
runP p input = fmap snd (unP p input')
where
input' =
concat [ zipWith C [0 ..] l <> [C (length l) '\n'] | l <- lines input ]
curInd :: P Int
curInd = P $ \s -> Just . (,) s $ case s of
[] -> 0
C i _ : _ -> i
rest :: P String
rest = P $ \cs -> Just ([], [ c | C _ c <- cs ])
munch :: (Int -> Char -> Bool) -> P String
munch p = P $ \cs -> let (xs, ys) = takeWhileMaybe p' cs in Just (ys, xs)
where
p' (C i c) | p i c = Just c
| otherwise = Nothing
munch1 :: (Int -> Char -> Bool) -> P String
munch1 p = P $ \case
[] -> Nothing
(c : cs)
| Just c' <- p' c
-> let (xs, ys) = takeWhileMaybe p' cs in Just (ys, c' : xs)
| otherwise
-> Nothing
where
p' (C i c) | p i c = Just c
| otherwise = Nothing
char :: Char -> P Char
char c = P $ \case
[] -> Nothing
(C _ c' : cs) | c == c' -> Just (cs, c)
| otherwise -> Nothing
skipSpaces :: P ()
skipSpaces = P $ \cs -> Just (dropWhile (\(C _ c) -> C.isSpace c) cs, ())
takeWhileMaybe :: (a -> Maybe b) -> [a] -> ([b], [a])
takeWhileMaybe f = go where
go xs0@[] = ([], xs0)
go xs0@(x : xs) = case f x of
Just y -> let (ys, zs) = go xs in (y : ys, zs)
Nothing -> ([], xs0)
field :: Int -> P (String, String)
field i = do
fn <- munch1 $ \_ c -> C.isAlpha c || c == '-'
skipSpaces
_ <- char ':'
skipSpaces
val <- munch $ \j c -> C.isSpace c || j > i
return (fn, val)
fields :: P ([(String, String)], String)
fields = do
skipSpaces
i <- curInd
fs <- many (field i)
r <- rest
return (fs, r)