simplistic parser which can list the modules imported by a haskell source file provides a recursive-memoized mode which can efficiently get the complete transitive imports of a set of files provides a method of splitting the imports into modules which are in the local fileset, and modules which aren't (the local module dependencies can be used for a build tool, and the non-local ones for determining which packages are referenced) > {-# LANGUAGE TupleSections #-} > module Distribution.Pot.Modules > (ModuleInfo(..) > ,modulesInfo > ,showmi > ,filterModules) where > import Data.Maybe > import qualified System.FilePath.Find as F > import System.FilePath.Find ((==?),(||?)) > import System.FilePath > import Text.Groom > import Data.List > import Distribution.Pot.Packages > import Control.Arrow > parseImports :: String -> [String] > parseImports src = mapMaybe (im . words) $ lines src > where > im (">":x:_) | x /= "import" = Nothing > im (">":"import":"qualified":nm:_) = Just nm > im (">":"import":nm:_) = Just nm > im (x:_) | x /= "import" = Nothing > im ("import":"qualified":nm:_) = Just nm > im ("import":nm:_) = Just nm > im _ = Nothing reads the folders passed in as source roots and returns a list of all the modules > getLocalModules :: [FilePath] -> IO [(FilePath,FilePath)] > getLocalModules dirs = > concat `fmap` mapM findHs dirs > where > findHs dir = > map ((dir,) . makeRelative dir) `fmap` > F.find (return True) (F.extension ==? ".lhs" ||? F.extension ==? ".hs") dir > -- | The collected information on one module > data ModuleInfo = > ModuleInfo > {miFileName :: FilePath > ,miFilePrefix :: FilePath > ,miModuleFile :: FilePath > ,miModuleName :: String > ,miLocalDependencies :: [((FilePath,FilePath),String)] > ,miLocalTransitiveDependencies :: [((FilePath,FilePath),String)] > ,miPackages :: [String] > ,miTransitivePackages :: [String] > } deriving Show > -- | Takes a set of source files and gets the ModuleInfo information > -- for all of them > modulesInfo :: [FilePath] -- ^ the root folders containing the source files > -- to analyze > -> IO [(FilePath,ModuleInfo)] > modulesInfo srcs = do > lms <- getLocalModules srcs > mps <- mapM (\(a,b) -> ((a,b),) `fmap` parseImports `fmap` readFile (a b)) lms > -- mps is a map from (sourceroot,modulepath) to [modules imported] > --putStrLn $ intercalate "\n\n" $ map (\(n,is) -> show n ++ "\n" ++ groom is) mps > -- split mps 2nd elements into local modules and other modules > let localModuleFiles = map (snd . fst) mps > localModuleNames = map (repSlash . dropExtension) localModuleFiles > --putStrLn $ groom localModuleNames > let mps2 = map (second (partition (`elem` localModuleNames))) mps > --putStrLn $ intercalate "\n\n" $ map (\(n,is) -> show n ++ "\n" ++ groom is) mps2 > -- process the other module lists to get list of packages instead > pkgs <- readPackages > let mps3 = map (second $ second $ getPackages pkgs) mps2 > tms = map mm mps3 > -- add the transitive local dependencies > ldeps = map (\((_,mf),(lds,_)) -> > (repSlash (dropExtension mf), lds)) mps3 > allDeps :: String -> [String] > allDeps f = sort $ nub > $ maybe [] > (\x -> x ++ concatMap allDeps x) > $ lookup f ldeps > tmslds = map (second $ \m -> m {miLocalTransitiveDependencies = > map (("",""),) $ allDeps (miModuleName m)}) tms > -- add the transitive packages > packm :: [(String,[String])] > packm = map (\((_,mf),(_,ps)) -> > (repSlash (dropExtension mf), ps)) mps3 > packs s = fromMaybe [] $ lookup s packm > tmspacks = map (second $ \m -> m {miTransitivePackages = > sort $ nub > $ concatMap packs (miModuleName m > : map snd (miLocalTransitiveDependencies m))}) > tmslds > lmFile (_,m) = > maybe (error $ "couldn't get filename for " ++ m) > (\(r,f) -> ((r, f), m)) > $ find ((==m) . repSlash . dropExtension . snd) lms > ff x = x {miLocalDependencies = map lmFile $ miLocalDependencies x > ,miLocalTransitiveDependencies = map lmFile $ miLocalTransitiveDependencies x} > --putStrLn $ intercalate "\n\n" $ map (\(n,is) -> show n ++ "\n" ++ groom is) mps3 > t2 = map (second ff) tmspacks > return t2 > where > mm ((r,f),(lds,ps)) = (r f > ,ModuleInfo {miFileName = r f > ,miFilePrefix = r > ,miModuleFile = f > ,miModuleName = repSlash (dropExtension f) > ,miLocalDependencies = map (("",""),) lds > ,miLocalTransitiveDependencies = [] > ,miPackages = ps > ,miTransitivePackages = []}) > repSlash = map $ \c -> case c of > '/' -> '.' > _ -> c > getPackages :: [(String,[String])] -> [String] -> [String] > getPackages pkgs ms = > let pkg m = fst `fmap` find (\(_,pms) -> m `elem` pms) pkgs > p1 = mapMaybe pkg ms > in sort $ nub p1 > -- | helper to filter the two module lists according to some function > filterModules :: (String -> Bool) -> ModuleInfo -> ModuleInfo > filterModules f mi = mi {miPackages = filter f $ miPackages mi > ,miTransitivePackages = filter f $ miTransitivePackages mi} > showmi :: [(FilePath,ModuleInfo)] -> String > showmi = intercalate "\n\n" . map (\(f,i) -> f ++ "\n" ++ groom i)