-- | -- Module : Portager.Flatten -- Copyright : (C) 2017 Jiri Marsicek -- License : BSD-style (see the file LICENSE) -- -- Maintainer : Jiri Marsicek -- -- This module provides functionality for converting 'Package's to 'FlatPackage's. -- module Portager.Flatten where import Data.Foldable (foldr') import qualified Data.List as List (groupBy) import Data.List.NonEmpty (nonEmpty) import Data.Maybe (mapMaybe) import Data.Semigroup (Semigroup(..)) import Data.Set (Set) import qualified Data.Set as Set (fromList, singleton, toAscList, union) import Portager.DSL -- |Flat representation of 'Package' without nested dependencies. data FlatPackage = FlatPackage { _fpAtom :: Atom , _fpUseflags :: Set Use , _fpKeywords :: Set Keyword , _fpLicenses :: Set License } deriving (Eq, Show) instance Ord FlatPackage where fp `compare` fp' = _fpAtom fp `compare` _fpAtom fp' instance Semigroup FlatPackage where FlatPackage atom us ks ls <> FlatPackage _ us' ks' ls' = FlatPackage atom (us `Set.union` us') (ks `Set.union` ks') (ls `Set.union` ls') -- |Merges two lists together, if both lists are sorted in ascending order, the result will be also sorted -- in ascending order. mergeLists :: Ord a => [a] -> [a] -> [a] mergeLists [] bs = bs mergeLists as [] = as mergeLists (a : as) (b : bs) | a <= b = a : mergeLists as (b : bs) | otherwise = b : mergeLists (a : as) bs -- |Merges two sets together, performing 'sconcat' on elements that are determined to be equal by result -- of a function given in argument. merge :: (Ord a, Semigroup a, Eq b) => (a -> b) -> Set a -> Set a -> Set a merge get lefts rights = Set.fromList $ map sconcat $ mapMaybe nonEmpty $ List.groupBy (\a b -> get a == get b) $ mergeLists (Set.toAscList lefts) (Set.toAscList rights) -- |Performs 'merge' of two sets of 'FlatPackage's where equality is determined by 'Atom'. -- See 'merge'. mergePackages :: Set FlatPackage -> Set FlatPackage -> Set FlatPackage mergePackages = merge _fpAtom -- |Converts a 'Package' to a 'FlatPackage' with globals 'Use' flags applied. -- Package 'Use' flags take precedence before globals 'Use' flags. flattenPackage :: Set Use -> Package -> FlatPackage flattenPackage globals pkg = let pkgcfg = _configuration pkg ufs = Set.fromList $ _useflags pkgcfg kws = Set.fromList $ _keywords pkgcfg lcs = Set.fromList $ _licenses pkgcfg in FlatPackage (_atom pkg) (ufs `Set.union` globals) kws lcs -- |Converts a 'Package' to a list of 'FlatPackages' with set 'Use' flags applied. flatten :: Set Use -> Package -> Set FlatPackage flatten globals pkg = let fp = Set.singleton $ flattenPackage globals pkg fdeps = flattenPackages globals $ _dependencies $ _configuration pkg in mergePackages fp fdeps -- |Convert a list of 'Package's to a list of 'FlatPackages' with set 'Use' flags applied. -- See 'flatten' for details on how a single 'Package' is converted to a list of 'FlatPackage's. flattenPackages :: Set Use -> [Package] -> Set FlatPackage flattenPackages globals = foldr' step mempty where step pkg flats = mergePackages flats $ flatten globals pkg -- |Converts a 'PackageSet' to a list of 'FlatPackage's. -- See 'flattenPackages' for details on how list of 'Package's is converted to a list of 'FlatPackage's. flattenSet :: PackageSet -> Set FlatPackage flattenSet s = let cfg = _setConfiguration s globals = Set.fromList $ _setUseflags cfg setPkgs = flattenPackages globals $ _setPackages cfg setDeps = flattenPackages globals $ _setDependencies cfg in mergePackages setPkgs setDeps