extensions-0.1.0.0: src/Extensions/Package.hs
{- |
Copyright: (c) 2020-2022 Kowainik
SPDX-License-Identifier: MPL-2.0
Maintainer: Kowainik <xrom.xkov@gmail.com>
Parse Haskell Language Extensions.
-}
module Extensions.Package
( -- * Package modules
getPackageExtentions
, getPackageExtentionsBySources
-- * Single module
, getModuleAndCabalExtentions
, getModuleExtentions
, getModuleAndCabalExtentionsBySource
, getModuleExtentionsBySource
) where
import Control.Exception (catch)
import Data.ByteString (ByteString)
import Data.Functor ((<&>))
import Data.Map.Merge.Strict (mapMissing, merge, zipWithMatched)
import Data.Map.Strict (Map)
import Extensions.Cabal (parseCabalFileExtensions)
import Extensions.Module (parseFile, parseSourceWithPath)
import Extensions.Types (CabalAndModuleExtensions (..), CabalException, ExtensionsError (..),
ExtensionsResult, ModuleParseError, ParsedExtensions (..),
mergeAnyExtensions)
import qualified Data.Map.Strict as Map
{- | By given path to @.cabal@ file, analyse extensions for each Haskell module
and return the corresponding 'Map' with 'ExtensionsResult's.
__Throws__:
* 'CabalException'
-}
getPackageExtentions
:: FilePath -- ^ Path to @.cabal@ file.
-> IO (Map FilePath ExtensionsResult)
getPackageExtentions cabalFile = do
cabalMap <- parseCabalFileExtensions cabalFile
Map.traverseWithKey perModuleParseMerge cabalMap
where
perModuleParseMerge :: FilePath -> ParsedExtensions -> IO ExtensionsResult
perModuleParseMerge path cabalExts = do
moduleRes <- parseFile path
pure $ mergeCabalAndModule cabalExts path moduleRes
{- | By given path to @.cabal@ file and 'Map' of sources of all Haskell
modules, analyse extensions for each Haskell module and return the corresponding
'Map' with 'ExtensionsResult's.
-}
getPackageExtentionsBySources
:: FilePath -- ^ Path to @.cabal@ file.
-> Map FilePath ByteString -- ^ Path to modules with corresponding sources.
-> IO (Map FilePath ExtensionsResult)
getPackageExtentionsBySources cabalFile sourcesMap =
parseCabalHandleException cabalFile <&> \case
Left err -> Left err <$ sourcesMap
Right cabalMap -> merge
(mapMissing cabalNotSource) -- in cabal but not in sources
(mapMissing sourceNotCabal) -- in sources but not in cabal
(zipWithMatched cabalAndSource) -- in cabal and sources
cabalMap
sourcesMap
where
cabalNotSource :: FilePath -> ParsedExtensions -> ExtensionsResult
cabalNotSource path _cabalExts = Left $ SourceNotFound path
sourceNotCabal :: FilePath -> ByteString -> ExtensionsResult
sourceNotCabal path _source = Left $ NotCabalModule path
cabalAndSource
:: FilePath
-> ParsedExtensions
-> ByteString
-> ExtensionsResult
cabalAndSource path cabalExts source =
mergeCabalAndModule cabalExts path $ parseSourceWithPath path source
{- | By given path to @.cabal@ file and path to Haskell module of the
corresponding package, analyse and return extensions for the
given module separately from .cabal file and from the module itself.
-}
getModuleAndCabalExtentions
:: FilePath -- ^ Path to @.cabal@ file.
-> FilePath -- ^ Path to Haskell module file.
-> IO (Either ExtensionsError CabalAndModuleExtensions)
getModuleAndCabalExtentions cabalFile path =
parseCabalHandleException cabalFile >>= \case
Left err -> pure $ Left err
Right cabalMap -> case Map.lookup path cabalMap of
Nothing -> pure $ Left $ NotCabalModule path
Just cabalExts -> getCabalAndModuleExts path cabalExts <$> parseFile path
{- | By given path to @.cabal@ file and path to Haskell module of the
corresponding package, analyse and return summary set of extensions for the
given module.
-}
getModuleExtentions
:: FilePath -- ^ Path to @.cabal@ file.
-> FilePath -- ^ Path to Haskell module file.
-> IO ExtensionsResult
getModuleExtentions cabalFile path =
parseCabalHandleException cabalFile >>= \case
Left err -> pure $ Left err
Right cabalMap -> case Map.lookup path cabalMap of
Nothing -> pure $ Left $ NotCabalModule path
Just cabalExts -> do
moduleRes <- parseFile path
pure $ mergeCabalAndModule cabalExts path moduleRes
{- | By given path to @.cabal@ file and path to Haskell module of the
corresponding package, analyse and return extensions extensions for the
given module separately from .cabal file and from the module itself.
-}
getModuleAndCabalExtentionsBySource
:: FilePath -- ^ Maybe path to @.cabal@ file.
-> FilePath -- ^ Path to the module's source (needed for matching with cabal file).
-> ByteString -- ^ Source of a Haskell module file.
-> IO (Either ExtensionsError CabalAndModuleExtensions)
getModuleAndCabalExtentionsBySource cabalFile path source =
parseCabalHandleException cabalFile <&> \case
Left cabalError -> Left cabalError
Right cabalMap -> case Map.lookup path cabalMap of
Nothing -> Left $ NotCabalModule path
Just cabalExts -> getCabalAndModuleExts path cabalExts
(parseSourceWithPath path source)
{- | By given path to @.cabal@ file and path to Haskell module of the
corresponding package, analyse and return combined set of extensions for the
given module.
-}
getModuleExtentionsBySource
:: FilePath -- ^ Maybe path to @.cabal@ file.
-> FilePath -- ^ Path to the module's source (needed for matching with cabal file).
-> ByteString -- ^ Source of a Haskell module file.
-> IO ExtensionsResult
getModuleExtentionsBySource cabalFile path source =
parseCabalHandleException cabalFile <&> \case
Left cabalError -> Left cabalError
Right cabalMap -> case Map.lookup path cabalMap of
Nothing -> Left $ NotCabalModule path
Just cabalExts -> mergeCabalAndModule cabalExts path
(parseSourceWithPath path source)
----------------------------------------------------------------------------
-- Internal helpers
----------------------------------------------------------------------------
mergeCabalAndModule
:: ParsedExtensions
-> FilePath
-> Either ModuleParseError ParsedExtensions
-> ExtensionsResult
mergeCabalAndModule cabalExts path moduleRes = case moduleRes of
Right moduleExts -> mergeAnyExtensions cabalExts moduleExts
Left parseErr -> Left $ ModuleParseError path parseErr
-- | 'parseCabalFileExtensions' with 'handleCabalException'.
parseCabalHandleException
:: FilePath
-> IO (Either ExtensionsError (Map FilePath ParsedExtensions))
parseCabalHandleException cabalFile = (Right <$> parseCabalFileExtensions cabalFile)
`catch` handleCabalException
-- | Handle 'CabalException' and return corresponding 'CabalError'.
handleCabalException
:: CabalException
-> IO (Either ExtensionsError (Map FilePath ParsedExtensions))
handleCabalException = pure . Left . CabalError
getCabalAndModuleExts
:: FilePath
-> ParsedExtensions
-> Either ModuleParseError ParsedExtensions
-> Either ExtensionsError CabalAndModuleExtensions
getCabalAndModuleExts path cabalExts moduleRes = case moduleRes of
Left err -> Left $ ModuleParseError path err
Right moduleExts -> Right $ CabalAndModuleExtensions
{ cabalExtensions = cabalExts
, moduleExtensions = moduleExts
}