module Extract (Module(..), extract) where
import Prelude hiding (mod, catch)
import Control.Monad
import Control.Applicative
import Control.Exception
import Control.DeepSeq (deepseq, NFData(rnf))
import Data.Generics
import GHC hiding (flags, Module, Located)
import NameSet (NameSet)
import Coercion (Coercion)
import FastString (unpackFS)
import Digraph (flattenSCCs)
import GhcUtil (withGhc)
import Location hiding (unLoc)
import Util (convertDosLineEndings)
newtype ExtractError = ExtractError SomeException
deriving Typeable
instance Show ExtractError where
show (ExtractError e) =
unlines [
"Ouch! Hit an error thunk in GHC's AST while extracting documentation."
, ""
, " " ++ msg
, ""
, "This is most likely a bug in doctest."
, ""
, "Please report it here: https://github.com/sol/doctest-haskell/issues/new"
]
where
msg = case fromException e of
Just (Panic s) -> "GHC panic: " ++ s
_ -> show e
instance Exception ExtractError
data Module a = Module {
moduleName :: String
, moduleContent :: [a]
} deriving (Eq, Functor)
deriving instance Show a => Show (Module a)
instance NFData a => NFData (Module a) where
rnf (Module name docs) = name `deepseq` docs `deepseq` ()
parse :: [String]
-> [String]
-> IO [TypecheckedModule]
parse flags modules = withGhc flags $ do
mapM (`guessTarget` Nothing) modules >>= setTargets
mods <- depanal [] False
let sortedMods = flattenSCCs (topSortModuleGraph False mods Nothing)
reverse <$> mapM (parseModule >=> typecheckModule >=> loadModule) sortedMods
extract :: [String]
-> [String]
-> IO [Module (Located String)]
extract flags modules = do
mods <- parse flags modules
let docs = map (fmap (fmap convertDosLineEndings) . extractFromModule . tm_parsed_module) mods
(docs `deepseq` return docs) `catches` [
Handler (\e -> throw (e :: AsyncException))
, Handler (throwIO . ExtractError)
]
extractFromModule :: ParsedModule -> Module (Located String)
extractFromModule m = Module name docs
where
docs = docStringsFromModule m
name = (moduleNameString . GHC.moduleName . ms_mod . pm_mod_summary) m
docStringsFromModule :: ParsedModule -> [Located String]
docStringsFromModule mod = map (toLocated . fmap unpackDocString) docs
where
source = (unLoc . pm_parsed_source) mod
docs = (maybe id (:) mHeader . maybe id (++) mExports) decls
mHeader = hsmodHaddockModHeader source
mExports = f `fmap` hsmodExports source
where
f xs = [L loc doc | L loc (IEDoc doc) <- xs]
decls = extractDocStrings (hsmodDecls source)
type Selector a = a -> ([LHsDocString], Bool)
ignore :: Selector a
ignore = const ([], True)
select :: a -> ([a], Bool)
select x = ([x], False)
extractDocStrings :: Data a => a -> [LHsDocString]
extractDocStrings = everythingBut (++) (([], False) `mkQ` fromLHsDecl
`extQ` fromLDocDecl
`extQ` fromLHsDocString
`extQ` (ignore :: Selector NameSet)
`extQ` (ignore :: Selector PostTcKind)
`extQ` (ignore :: Selector (HsExpr RdrName))
`extQ` (ignore :: Selector Coercion)
)
where
fromLHsDecl :: Selector (LHsDecl RdrName)
fromLHsDecl (L loc decl) = case decl of
DocD x -> (select . L loc . docDeclDoc) x
_ -> (extractDocStrings decl, True)
fromLDocDecl :: Selector LDocDecl
fromLDocDecl = select . fmap docDeclDoc
fromLHsDocString :: Selector LHsDocString
fromLHsDocString = select
unpackDocString :: HsDocString -> String
unpackDocString (HsDocString s) = unpackFS s