stack-2.15.1: src/Stack/Types/CompCollection.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | A module providing the type 'CompCollection' and associated helper
-- functions.
--
-- The corresponding Cabal approach uses lists. See, for example, the
-- 'Distribution.Types.PackageDescription.sublibraries',
-- 'Distribution.Types.PackageDescription.foreignLibs',
-- 'Distribution.Types.PackageDescription.executables',
-- 'Distribution.Types.PackageDescription.testSuites', and
-- 'Distribution.Types.PackageDescription.benchmarks' fields.
--
-- Cabal removes all the unbuildable components very early (at the cost of
-- slightly worse error messages).
module Stack.Types.CompCollection
( CompCollection
, getBuildableSet
, getBuildableSetText
, getBuildableListText
, getBuildableListAs
, foldAndMakeCollection
, hasBuildableComponent
, collectionLookup
, collectionKeyValueList
, collectionMember
, foldComponentToAnotherCollection
) where
import qualified Data.Map as M
import qualified Data.Set as Set
import Stack.Prelude
import Stack.Types.Component
( HasBuildInfo, HasName, StackBuildInfo (..)
, StackUnqualCompName (..)
)
-- | A type representing collections of components, distinguishing buildable
-- components and non-buildable components.
data CompCollection component = CompCollection
{ buildableOnes :: {-# UNPACK #-} !(InnerCollection component)
, unbuildableOnes :: Set StackUnqualCompName
-- ^ The field is lazy beacause it should only serve when users explicitely
-- require unbuildable components to be built. The field allows for
-- intelligible error messages.
}
deriving (Show)
instance Semigroup (CompCollection component) where
a <> b = CompCollection
{ buildableOnes = a.buildableOnes <> b.buildableOnes
, unbuildableOnes = a.unbuildableOnes <> b.unbuildableOnes
}
instance Monoid (CompCollection component) where
mempty = CompCollection
{ buildableOnes = mempty
, unbuildableOnes = mempty
}
instance Foldable CompCollection where
foldMap fn collection = foldMap fn collection.buildableOnes
foldr' fn c collection = M.foldr' fn c collection.buildableOnes
null = M.null . (.buildableOnes)
-- | The 'Data.HashMap.Strict.HashMap' type is a more suitable choice than 'Map'
-- for 'Data.Text.Text' based keys in general (it scales better). However,
-- constant factors are largely dominant for maps with less than 1000 keys.
-- Packages with more than 100 components are extremely unlikely, so we use a
-- 'Map'.
type InnerCollection component = Map StackUnqualCompName component
-- | A function to add a component to a collection of components. Ensures that
-- both 'asNameMap' and 'asNameSet' are updated consistently.
addComponent ::
HasName component
=> component
-- ^ Component to add.
-> InnerCollection component
-- ^ Existing collection of components.
-> InnerCollection component
addComponent component = M.insert component.name component
-- | For the given function and foldable data structure of components of type
-- @compA@, iterates on the elements of that structure and maps each element to
-- a component of type @compB@ while building a 'CompCollection'.
foldAndMakeCollection ::
(HasBuildInfo compB, HasName compB, Foldable sourceCollection)
=> (compA -> compB)
-- ^ Function to apply to each element in the data struture.
-> sourceCollection compA
-- ^ Given foldable data structure of components of type @compA@.
-> CompCollection compB
foldAndMakeCollection mapFn = foldl' compIterator mempty
where
compIterator existingCollection component =
compCreator existingCollection (mapFn component)
compCreator existingCollection component
| component.buildInfo.buildable = existingCollection
{ buildableOnes =
addComponent component existingCollection.buildableOnes
}
| otherwise = existingCollection
{ unbuildableOnes =
Set.insert component.name existingCollection.unbuildableOnes
}
-- | Get the names of the buildable components in the given collection, as a
-- 'Set' of 'StackUnqualCompName'.
getBuildableSet :: CompCollection component -> Set StackUnqualCompName
getBuildableSet = M.keysSet . (.buildableOnes)
-- | Get the names of the buildable components in the given collection, as a
-- 'Set' of 'Text'.
getBuildableSetText :: CompCollection component -> Set Text
getBuildableSetText = Set.mapMonotonic (.unqualCompToText) . getBuildableSet
-- | Get the names of the buildable components in the given collection, as a
-- list of 'Text.
getBuildableListText :: CompCollection component -> [Text]
getBuildableListText = getBuildableListAs (.unqualCompToText)
-- | Apply the given function to the names of the buildable components in the
-- given collection, yielding a list.
getBuildableListAs ::
(StackUnqualCompName -> something)
-- ^ Function to apply to buildable components.
-> CompCollection component
-- ^ Collection of components.
-> [something]
getBuildableListAs fn = Set.foldr' (\v l -> fn v:l) [] . getBuildableSet
-- | Yields 'True' if, and only if, the given collection includes at least one
-- buildable component.
hasBuildableComponent :: CompCollection component -> Bool
hasBuildableComponent = not . null . getBuildableSet
-- | For the given name of a buildable component and the given collection of
-- components, yields 'Just' @component@ if the collection includes a buildable
-- component of that name, and 'Nothing' otherwise.
collectionLookup ::
Text
-- ^ Name of the buildable component.
-> CompCollection component
-- ^ Collection of components.
-> Maybe component
collectionLookup needle haystack =
M.lookup (StackUnqualCompName needle) haystack.buildableOnes
-- | For a given collection of components, yields a list of pairs for buildable
-- components of the name of the component and the component.
collectionKeyValueList :: CompCollection component -> [(Text, component)]
collectionKeyValueList haystack =
(\(StackUnqualCompName k, !v) -> (k, v))
<$> M.toList haystack.buildableOnes
-- | Yields 'True' if, and only if, the given collection of components includes
-- a buildable component with the given name.
collectionMember ::
Text
-- ^ Name of the buildable component.
-> CompCollection component
-- ^ Collection of components.
-> Bool
collectionMember needle haystack = isJust $ collectionLookup needle haystack
-- | Reduce the buildable components of the given collection of components by
-- applying the given binary operator to all buildable components, using the
-- given starting value (typically the right-identity of the operator).
foldComponentToAnotherCollection ::
(Monad m)
=> CompCollection component
-- ^ Collection of components.
-> (component -> m a -> m a)
-- ^ Binary operator.
-> m a
-- ^ Starting value.
-> m a
foldComponentToAnotherCollection collection fn initialValue =
M.foldr' fn initialValue collection.buildableOnes