-- | Utility functions to work with 'GenericPackageDescription' and
-- other miscellaneous stuff in .cabal files.

module Importify.Cabal.Package
       ( extractFromTargets
       , packageDependencies
       , readCabal
       ) where

import           Universum                             hiding (fromString)

import           Distribution.Package                  (Dependency (..), unPackageName)
import           Distribution.PackageDescription       (Benchmark (benchmarkBuildInfo),
                                                        BuildInfo (..), CondTree,
                                                        Executable (..),
                                                        GenericPackageDescription (..),
                                                        Library (..),
                                                        TestSuite (testBuildInfo),
                                                        condTreeData)
import           Distribution.PackageDescription.Parse (readPackageDescription)
import           Distribution.Verbosity                (normal)

-- | Parse 'GenericPackageDescription' from given path to .cabal file.
readCabal :: MonadIO m => FilePath -> m GenericPackageDescription
readCabal = liftIO . readPackageDescription normal

dependencyName :: Dependency -> String
dependencyName (Dependency name _) = unPackageName name

-- | Retrieve list of unique names for all package dependencies inside
-- library, all executables, all test suites and all benchmarks for a
-- given package.
packageDependencies :: GenericPackageDescription -> [String]
packageDependencies = ordNub
                    . concatMap (map dependencyName . targetBuildDepends)
                    . allBuildInfos

allBuildInfos :: GenericPackageDescription -> [BuildInfo]
allBuildInfos = extractFromTargets       libBuildInfo
                                            buildInfo
                                        testBuildInfo
                                   benchmarkBuildInfo

-- | Extract some uniform data from every target if it's present.
extractFromTargets :: (Library    -> r)  -- ^ 'Library' extractor
                   -> (Executable -> r)  -- ^ 'Executable' extractor
                   -> (TestSuite  -> r)  -- ^ 'TestSuite' extractor
                   -> (Benchmark  -> r)  -- ^ 'Benchmakr' extractor
                   -> GenericPackageDescription -- ^ Package
                   -> [r]  -- ^ List of results collected from each target
extractFromTargets fromLib fromExe fromTst fromBnc GenericPackageDescription{..} =
  concat
    [ maybe [] (one . fromLib . condTreeData) condLibrary
    , mapTargets fromExe condExecutables
    , mapTargets fromTst condTestSuites
    , mapTargets fromBnc condBenchmarks
    ]

mapTargets :: (t -> r) -> [(s, CondTree v c t)] -> [r]
mapTargets extractor = map (extractor . condTreeData . snd)

 | Utility functions to work with 'GenericPackageDescription' and
 other miscellaneous stuff in .cabal files.
 | Parse 'GenericPackageDescription' from given path to .cabal file.
 | Retrieve list of unique names for all package dependencies inside
 library, all executables, all test suites and all benchmarks for a
 given package.
 | Extract some uniform data from every target if it's present.
 ^ 'Library' extractor
 ^ 'Executable' extractor
 ^ 'TestSuite' extractor
 ^ 'Benchmakr' extractor
 ^ Package
 ^ List of results collected from each target

module Importify.Cabal.Package
       ( extractFromTargets
       , packageDependencies
       , readCabal
       ) where

import           Universum                             hiding (fromString)

import           Distribution.Package                  (Dependency (..), unPackageName)
import           Distribution.PackageDescription       (Benchmark (benchmarkBuildInfo),
                                                        BuildInfo (..), CondTree,
                                                        Executable (..),
                                                        GenericPackageDescription (..),
                                                        Library (..),
                                                        TestSuite (testBuildInfo),
                                                        condTreeData)
import           Distribution.PackageDescription.Parse (readPackageDescription)
import           Distribution.Verbosity                (normal)

readCabal :: MonadIO m => FilePath -> m GenericPackageDescription
readCabal = liftIO . readPackageDescription normal

dependencyName :: Dependency -> String
dependencyName (Dependency name _) = unPackageName name

packageDependencies :: GenericPackageDescription -> [String]
packageDependencies = ordNub
                    . concatMap (map dependencyName . targetBuildDepends)
                    . allBuildInfos

allBuildInfos :: GenericPackageDescription -> [BuildInfo]
allBuildInfos = extractFromTargets       libBuildInfo
                                            buildInfo
                                        testBuildInfo
                                   benchmarkBuildInfo

extractFromTargets fromLib fromExe fromTst fromBnc GenericPackageDescription{..} =
  concat
    [ maybe [] (one . fromLib . condTreeData) condLibrary
    , mapTargets fromExe condExecutables
    , mapTargets fromTst condTestSuites
    , mapTargets fromBnc condBenchmarks
    ]

mapTargets :: (t -> r) -> [(s, CondTree v c t)] -> [r]
mapTargets extractor = map (extractor . condTreeData . snd)
