module Dhall.Dep ( getFileDeps , getAllFileDeps ) where import Control.Exception (throw) import Control.Monad ((<=<)) import Data.Bifunctor (first, second) import Data.Containers.ListUtils (nubOrd) import Data.Foldable (toList) import qualified Data.Text.IO as T import Dhall.Core (Import, ImportMode (Code), ImportType (Local), importHashed, importMode, importType) import Dhall.Import (localToPath) import Dhall.Parser (exprFromText) import System.Directory (canonicalizePath, makeRelativeToCurrentDirectory) import System.FilePath (isAbsolute, takeDirectory, ()) -- | Given a path, the file paths it depends on getFileDeps :: FilePath -> IO [FilePath] getFileDeps fp = traverse canonicalizeRelative =<< catFilePaths <$> getCodeDeps fp getCodeDeps :: FilePath -> IO [DhallImport] getCodeDeps fp = do contents <- T.readFile fp let fileDir = takeDirectory fp fileMod fp' = if isAbsolute fp' then fp' else fileDir fp' importMod = mapFp fileMod tree = either throw id (exprFromText fp contents) imports = toList tree filter isRelevant <$> traverse ((importMod <$>) . fromImport) imports canonicalizeRelative :: FilePath -> IO FilePath canonicalizeRelative = makeRelativeToCurrentDirectory <=< canonicalizePath getAllCodeDeps :: FilePath -> IO [DhallImport] getAllCodeDeps fp = do deps <- traverse (traverseFp canonicalizeRelative) =<< getCodeDeps fp let (nextSrc, _) = partitionImports deps level <- traverse getAllCodeDeps nextSrc pure $ if null level then deps else nubOrd (concat (deps : level)) -- | Get all transitive dependencies getAllFileDeps :: FilePath -> IO [FilePath] getAllFileDeps = fmap catFilePaths . getAllCodeDeps data DhallImport = DhallCode FilePath | OtherImport FilePath | Irrelevant -- for URLs and such deriving (Ord, Eq) partitionImports :: [DhallImport] -> ([FilePath], [FilePath]) partitionImports [] = ([], []) partitionImports (Irrelevant:is) = partitionImports is partitionImports (DhallCode fp:is) = first (fp:) $ partitionImports is partitionImports (OtherImport fp:is) = second (fp:) $ partitionImports is mapFp :: (FilePath -> FilePath) -> DhallImport -> DhallImport mapFp f (DhallCode fp) = DhallCode $ f fp mapFp f (OtherImport fp) = OtherImport $ f fp mapFp _ Irrelevant = Irrelevant traverseFp :: Applicative f => (FilePath -> f FilePath) -> DhallImport -> f DhallImport traverseFp f (DhallCode fp) = DhallCode <$> f fp traverseFp f (OtherImport fp) = OtherImport <$> f fp traverseFp _ Irrelevant = pure Irrelevant catFilePaths :: [DhallImport] -> [FilePath] catFilePaths [] = [] catFilePaths (Irrelevant:is) = catFilePaths is catFilePaths (DhallCode fp:is) = fp : catFilePaths is catFilePaths (OtherImport fp:is) = fp : catFilePaths is isRelevant :: DhallImport -> Bool isRelevant Irrelevant = False isRelevant _ = True fromImport :: Import -> IO DhallImport fromImport i | importMode i == Code = asDhall <$> mImport i | otherwise = asOther <$> mImport i mImport :: Import -> IO (Maybe FilePath) mImport = fromImportType . importType . importHashed asDhall :: Maybe FilePath -> DhallImport asDhall (Just fp) = DhallCode fp asDhall Nothing = Irrelevant asOther :: Maybe FilePath -> DhallImport asOther (Just fp) = OtherImport fp asOther Nothing = Irrelevant fromImportType :: ImportType -> IO (Maybe FilePath) fromImportType (Local pre fp) = Just <$> localToPath pre fp fromImportType _ = pure Nothing