packages feed

stackage2nix-0.4.0: src/Stack/Config.hs

{-# LANGUAGE ViewPatterns #-}

module Stack.Config where

import Control.Lens
import Data.Bifunctor
import Data.ByteString as BS
import Data.Coerce
import Data.Foldable as F
import Data.List.NonEmpty as NE
import Data.Maybe
import Data.Semigroup (sconcat)
import Data.Text as T
import Data.Yaml
import Network.URI
import Stack.Config.Yaml as Yaml
import Stack.Types
import System.FilePath


newtype StackResolver = StackResolver { fromStackResolver :: Text }
  deriving (Eq, Ord, Show)

makePrisms ''StackResolver

unStackResolver :: StackResolver -> String
unStackResolver = T.unpack . fromStackResolver

newtype Vcs = Vcs { fromVcs :: Text }
  deriving (Eq, Ord, Show)

data Repo = Repo
  { _rVcs    :: !Vcs
  , _rUri    :: !Text
  , _rCommit :: !Text
  } deriving (Eq, Ord, Show)

makeLenses ''Repo

data PackageLocation
  = HackagePackage Text
  | StackFilePath FilePath
  | StackUri URI
  | StackRepo Repo
  deriving (Eq, Ord, Show)

makePrisms ''PackageLocation

data StackPackage = StackPackage
  { _spLocation :: !PackageLocation
  , _spExtraDep :: !Bool
    -- | Subdirectory containing the cabal file if any specified.
  , _spDir  :: !(Maybe FilePath)
  } deriving (Eq, Ord, Show)

makeLenses ''StackPackage

data StackConfig = StackConfig
  { _scResolver  :: !StackResolver
  , _scPackages  :: !(NonEmpty StackPackage)
  } deriving (Eq, Ord, Show)

makeLenses ''StackConfig

fromYamlConfig :: Yaml.Config -> StackConfig
fromYamlConfig c = StackConfig{..}
  where
    _scResolver = coerce $ c ^. cResolver
    _scPackages = F.foldr (NE.<|) neYamlPackages yamlExtraDeps
    neYamlPackages = fromMaybe (pure defaultPackage)
                   . fmap sconcat $ NE.nonEmpty yamlPackages
    yamlPackages   = fromYamlPackage <$> (fromMaybe mempty (c ^. cPackages))
    yamlExtraDeps  = fromYamlExtraDep <$> fromMaybe mempty (c ^. cExtraDeps)
    defaultPackage = StackPackage (StackFilePath ".") False Nothing


fromYamlPackage
  :: Yaml.Package
     -- ^ A single 'Yaml.Package' can result in multiple actual
     -- packages if it has multiple subdirs specified.
  -> NonEmpty StackPackage
fromYamlPackage = \case
  Yaml.Simple p                                  ->
    unroll Nothing $ StackPackage (parseSimplePath p) False
  Yaml.LocationSimple (Location p extraDep ms) ->
    unroll ms $ StackPackage (parseSimplePath p) (fromMaybe False extraDep)
  Yaml.LocationGit (Location git extraDep ms)       ->
    unroll ms $ StackPackage (StackRepo $ fromYamlGit git) (fromMaybe False extraDep)
  Yaml.LocationHg (Location hg extraDep ms)       ->
    unroll ms $ StackPackage (StackRepo $ fromYamlHg hg) (fromMaybe False extraDep)
  where
    parseSimplePath (T.unpack -> p) = maybe (StackFilePath p) StackUri $ parseURI p
    -- Each package gets a single directory with cabal file in it. If
    -- it's not specified, path is empty.
    unroll subs p = case subs of
      Just (x : xs) -> NE.map (p . Just) (x :| xs)
      _ -> p Nothing :| []

fromYamlExtraDep :: Text -> StackPackage
fromYamlExtraDep t = StackPackage (HackagePackage t) True Nothing

fromYamlGit :: Yaml.Git -> Repo
fromYamlGit yg = Repo{..}
  where
    _rVcs = Vcs "git"
    _rUri = yg ^. gGit
    _rCommit = yg ^. gCommit

fromYamlHg :: Yaml.Hg -> Repo
fromYamlHg yh = Repo{..}
  where
    _rVcs = Vcs "hg"
    _rUri = yh ^. hHg
    _rCommit = yh ^. hCommit

readStackConfig :: StackYaml -> IO (Either String StackConfig)
readStackConfig stackYaml = do
  let
    relativeToStackYaml = \case
      StackFilePath p -> StackFilePath $ stackYaml ^. syDirName </> p
      packageLocation -> packageLocation
    mkStackConfig = over (scPackages . traversed . spLocation) relativeToStackYaml
      . fromYamlConfig
  second mkStackConfig . decodeEither <$> BS.readFile (stackYaml ^. syFilePath)