{-# LANGUAGE CPP #-} module HIndent.CabalFile ( getCabalExtensionsForSourcePath ) where import Control.Monad import qualified Data.ByteString as BS import Data.List import Data.Maybe import Data.Traversable import Distribution.ModuleName import Distribution.PackageDescription import Distribution.PackageDescription.Configuration #if MIN_VERSION_Cabal(2, 2, 0) import Distribution.PackageDescription.Parsec #else import Distribution.PackageDescription.Parse #endif import Language.Haskell.Extension import qualified Language.Haskell.Exts.Extension as HSE import System.Directory import System.FilePath import Text.Read data Stanza = MkStanza { _stanzaBuildInfo :: BuildInfo , stanzaIsSourceFilePath :: FilePath -> Bool } -- | Find the relative path of a child path in a parent, if it is a child toRelative :: FilePath -> FilePath -> Maybe FilePath toRelative parent child = let rel = makeRelative parent child in if rel == child then Nothing else Just rel -- | Create a Stanza from `BuildInfo` and names of modules and paths mkStanza :: BuildInfo -> [ModuleName] -> [FilePath] -> Stanza mkStanza bi mnames fpaths = MkStanza bi $ \path -> let modpaths = fmap toFilePath $ otherModules bi ++ mnames inDir dir = case toRelative dir path of Nothing -> False Just relpath -> any (equalFilePath $ dropExtension relpath) modpaths || any (equalFilePath relpath) fpaths in any inDir $ hsSourceDirs bi -- | Extract `Stanza`s from a package packageStanzas :: PackageDescription -> [Stanza] packageStanzas pd = let libStanza :: Library -> Stanza libStanza lib = mkStanza (libBuildInfo lib) (exposedModules lib) [] exeStanza :: Executable -> Stanza exeStanza exe = mkStanza (buildInfo exe) [] [modulePath exe] testStanza :: TestSuite -> Stanza testStanza ts = mkStanza (testBuildInfo ts) (case testInterface ts of TestSuiteLibV09 _ mname -> [mname] _ -> []) (case testInterface ts of TestSuiteExeV10 _ path -> [path] _ -> []) benchStanza :: Benchmark -> Stanza benchStanza bn = mkStanza (benchmarkBuildInfo bn) [] $ case benchmarkInterface bn of BenchmarkExeV10 _ path -> [path] _ -> [] in mconcat [ maybeToList $ fmap libStanza $ library pd , fmap exeStanza $ executables pd , fmap testStanza $ testSuites pd , fmap benchStanza $ benchmarks pd ] -- | Find cabal files that are "above" the source path findCabalFiles :: FilePath -> FilePath -> IO (Maybe ([FilePath], FilePath)) findCabalFiles dir rel = do names <- getDirectoryContents dir cabalnames <- filterM (doesFileExist . (dir )) $ filter (isSuffixOf ".cabal") names case cabalnames of [] | dir == "/" -> return Nothing [] -> findCabalFiles (takeDirectory dir) (takeFileName dir rel) _ -> return $ Just (fmap (\n -> dir n) cabalnames, rel) getGenericPackageDescription :: FilePath -> IO (Maybe GenericPackageDescription) #if MIN_VERSION_Cabal(2, 2, 0) getGenericPackageDescription cabalPath = do cabaltext <- BS.readFile cabalPath return $ parseGenericPackageDescriptionMaybe cabaltext #else getGenericPackageDescription cabalPath = do cabaltext <- readFile cabalPath case parsePackageDescription cabaltext of ParseOk _ gpd -> return $ Just gpd _ -> return Nothing #endif -- | Find the `Stanza` that refers to this source path getCabalStanza :: FilePath -> IO (Maybe Stanza) getCabalStanza srcpath = do abssrcpath <- canonicalizePath srcpath mcp <- findCabalFiles (takeDirectory abssrcpath) (takeFileName abssrcpath) case mcp of Just (cabalpaths, relpath) -> do stanzass <- for cabalpaths $ \cabalpath -> do genericPackageDescription <- getGenericPackageDescription cabalpath case genericPackageDescription of Nothing -> return [] Just gpd -> do return $ packageStanzas $ flattenPackageDescription gpd return $ case filter (\stanza -> stanzaIsSourceFilePath stanza relpath) $ mconcat stanzass of [] -> Nothing (stanza:_) -> Just stanza -- just pick the first one Nothing -> return Nothing -- | Get (Cabal package) language and extensions from the cabal file for this source path getCabalExtensions :: FilePath -> IO (Language, [Extension]) getCabalExtensions srcpath = do mstanza <- getCabalStanza srcpath return $ case mstanza of Nothing -> (Haskell98, []) Just (MkStanza bi _) -> do (fromMaybe Haskell98 $ defaultLanguage bi, defaultExtensions bi) convertLanguage :: Language -> HSE.Language convertLanguage lang = read $ show lang convertKnownExtension :: KnownExtension -> Maybe HSE.KnownExtension convertKnownExtension ext = case readEither $ show ext of Left _ -> Nothing Right hext -> Just hext convertExtension :: Extension -> Maybe HSE.Extension convertExtension (EnableExtension ke) = fmap HSE.EnableExtension $ convertKnownExtension ke convertExtension (DisableExtension ke) = fmap HSE.DisableExtension $ convertKnownExtension ke convertExtension (UnknownExtension s) = Just $ HSE.UnknownExtension s -- | Get extensions from the cabal file for this source path getCabalExtensionsForSourcePath :: FilePath -> IO [HSE.Extension] getCabalExtensionsForSourcePath srcpath = do (lang, exts) <- getCabalExtensions srcpath return $ fmap HSE.EnableExtension $ HSE.toExtensionList (convertLanguage lang) $ mapMaybe convertExtension exts