packages feed

file-modules-0.1.0.0: src/Development/FileModules.hs

{-# LANGUAGE RecordWildCards #-}
module Development.FileModules where

import           Control.Concurrent.Async (mapConcurrently)
import           Control.Concurrent.STM
import           Control.Monad            (forM)
import           Data.String.Utils        (split)
import           Language.Haskell.Exts    (ImportDecl (..),
                                           ModuleHeadAndImports (..),
                                           ModuleName (..), NonGreedy (..),
                                           ParseResult (..), parse)
import qualified STMContainers.Set        as Set
import           System.Directory
import           System.FilePath

fileModulesRecur :: Set.Set String -> FilePath -> IO [String]
fileModulesRecur seen fname = do
    modules <- fileModules fname
    modules' <- flip mapConcurrently modules $ \m -> do
        let pth = takeDirectory fname </> joinPath (split "." m) ++ ".hs"
        isSeen <- atomically $ Set.lookup pth seen
        if isSeen
           then return [m]
           else do
               atomically $ Set.insert pth seen
               isLocalModule <- doesFileExist pth
               if isLocalModule
                   -- If we're hitting a local modules, ignore it on the
                   -- output (this may not be what we want)
                   then fileModulesRecur seen pth
                   else return [m]
    return (concat modules')

fileModules :: FilePath -> IO [String]
fileModules fname = do
    result <- parse <$> readFile fname
    case result of
        (ParseOk (NonGreedy{..})) -> do
            let (ModuleHeadAndImports _ _ mimports) = unNonGreedy
            forM mimports $ \imp ->
                let ModuleName iname = importModule imp
                in return iname
        (ParseFailed _ _) -> error $ "Failed to parse module in " ++ fname