packages feed

ghcup-0.1.30.0: lib/GHCup/CabalConfig.hs

{-# LANGUAGE CPP                  #-}
{-# LANGUAGE OverloadedStrings    #-}
{-# LANGUAGE UndecidableInstances #-}

module GHCup.CabalConfig (getStoreDir) where

import Data.ByteString          (ByteString)
import Data.List.NonEmpty       (NonEmpty)
import Data.Map                 (Map)
import System.Directory         (getAppUserDataDirectory, doesDirectoryExist, getXdgDirectory, XdgDirectory(XdgConfig))
import System.Environment       (lookupEnv)
import System.FilePath          ((</>))

import qualified Data.ByteString               as BS
import qualified Data.Map.Strict               as M
import qualified Distribution.CabalSpecVersion as C
import qualified Distribution.FieldGrammar     as C
import qualified Distribution.FieldGrammar.Parsec     as C
import qualified Distribution.Fields           as C
import qualified Distribution.Fields.LexerMonad as C
import qualified Distribution.Parsec           as C
import qualified Distribution.Utils.Generic    as C
import qualified Text.Parsec                    as P

import Data.Foldable              (for_)
import Distribution.Parsec.Error




getStoreDir :: IO FilePath
getStoreDir = do
    fp <- findConfig
    bs <- BS.readFile fp
    either (fail . show . fmap (showPError fp)) resolveConfig (parseConfig bs)

-------------------------------------------------------------------------------
-- Find config
-------------------------------------------------------------------------------

-- | Find the @~\/.cabal\/config@ file.
findConfig :: IO FilePath
findConfig = do
    env <- lookupEnv "CABAL_CONFIG"
    case env of
        Just p -> return p
        Nothing -> do
            cabalDir <- findCabalDir
            return (cabalDir </> "config")

-- | Find the @~\/.cabal@ dir.
findCabalDir :: IO FilePath
findCabalDir = do
    cabalDirVar <- lookupEnv "CABAL_DIR"
    appDir <- getAppUserDataDirectory "cabal"
    isXdg <- not <$> doesDirectoryExist appDir
    if | Just dir <- cabalDirVar -> pure dir
       | isXdg -> getXdgDirectory XdgConfig "cabal"
       | otherwise -> pure appDir


-------------------------------------------------------------------------------
-- Parsing
-------------------------------------------------------------------------------

-- | Parse @~\/.cabal\/config@ file.
parseConfig :: ByteString -> Either (NonEmpty PError) (Maybe FilePath)
parseConfig = parseWith $ \fields0 -> do
    let (fields1, _) = C.partitionFields fields0
    let fields2 = M.filterWithKey (\k _ -> k `elem` knownFields) fields1
    parse fields2
  where
    knownFields = C.fieldGrammarKnownFieldList grammar

    parse :: Map C.FieldName [C.NamelessField C.Position]
          -> C.ParseResult (Maybe FilePath)
    parse fields = C.parseFieldGrammar C.cabalSpecLatest fields grammar

grammar :: C.ParsecFieldGrammar (Maybe FilePath) (Maybe FilePath)
grammar = mempty
    <$> C.optionalFieldAla "store-dir" C.FilePathNT id

parseWith
    :: ([C.Field C.Position] -> C.ParseResult a)  -- ^ parse
    -> ByteString                                 -- ^ contents
    -> Either (NonEmpty PError) a
parseWith parser bs = case C.runParseResult result of
    (_, Right x)      -> Right x
    (_, Left (_, es)) -> Left es
  where
    result = case C.readFields' bs of
        Left perr -> C.parseFatalFailure pos (show perr) where
            ppos = P.errorPos perr
            pos  = C.Position (P.sourceLine ppos) (P.sourceColumn ppos)
        Right (fields, lexWarnings) -> do
            C.parseWarnings (C.toPWarnings lexWarnings)
            for_ (C.validateUTF8 bs) $ \pos ->
                C.parseWarning C.zeroPos C.PWTUTF $ "UTF8 encoding problem at byte offset " ++ show pos
            parser fields

-------------------------------------------------------------------------------
-- Resolving
-------------------------------------------------------------------------------

-- | Fill the default in @~\/.cabal\/config@  file.
resolveConfig :: Maybe FilePath -> IO FilePath
resolveConfig (Just fp) = pure fp
resolveConfig Nothing = do
    c <- findCabalDir
    return (c </> "store")