leksah-server-0.16.2.0: src/IDE/Utils/CabalPlan.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
-----------------------------------------------------------------------------
--
-- Module : IDE.Utils.CabalPlan
-- Copyright : 2016-2016 Herbert Valerio Riedel
-- License : GPL
--
-- | Parse plan.json files
--
-----------------------------------------------------------------------------
module IDE.Utils.CabalPlan (
PlanJson(..)
, PlanItem(..)
, PID
, unitIdToPackageId
) where
import GHC.Generics (Generic)
import Data.List (sortOn)
import qualified Data.Set as S (fromList, Set)
import qualified Data.Map as M (empty, Map, toList)
import Data.Text (Text)
import qualified Data.Text as T
(breakOnEnd, init, all, null, Text, pack, unpack, splitOn)
import Data.Aeson (FromJSON(..), withObject, (.:), (.:?))
import Distribution.Package (PackageIdentifier, UnitId)
import Distribution.Text (display, simpleParse)
import Data.Maybe (fromMaybe)
-- $setup
-- >>> import Data.Aeson (eitherDecodeStrict')
-- >>> import qualified Data.ByteString.Char8 as BS (pack)
-- | Information cabal-install writes to the plan.json file
-- >>> :{
-- eitherDecodeStrict' (BS.pack
-- "{\
-- \ \"cabal-lib-version\" : \"1.24.0.0\",\
-- \ \"cabal-version\" : \"1.24.0.0\",\
-- \ \"install-plan\" : [\
-- \ {\
-- \ \"type\" : \"pre-existing\",\
-- \ \"components\" : {\
-- \ \"lib\" : {\
-- \ \"depends\" : [\
-- \ \"array-0.5.1.1\",\
-- \ \"base-4.9.0.0\"\
-- \ ]\
-- \ }\
-- \ },\
-- \ \"id\" : \"Cabal-1.24.0.0\"\
-- \ },\
-- \ {\
-- \ \"id\" : \"QuickCheck-2.9.1-ec9a1c39266d75ed2c3314f6e846a8f11853eff43fc45db79c7256d9bfd94602\",\
-- \ \"components\" : {\
-- \ \"lib\" : {\
-- \ \"depends\" : [\
-- \ \"base-4.9.0.0\",\
-- \ \"containers-0.5.7.1\",\
-- \ \"random-1.1-fe6ccf72ebd63a2d68570bb45b42bd08df5570c6151cb9af54907d40ef9af454\"\
-- \ ]\
-- \ }\
-- \ },\
-- \ \"flags\" : {\
-- \ \"templatehaskell\" : true\
-- \ },\
-- \ \"type\" : \"configured\"\
-- \ }\
-- \ ]\
-- \}") :: Either String PlanJson
-- :}
-- Right (PlanJson {pjPlan = [PlanItem {piId = "Cabal-1.24.0.0", piType = "pre-existing", piComps = [(ComponentLib,fromList ["array-0.5.1.1","base-4.9.0.0"])]},PlanItem {piId = "QuickCheck-2.9.1-ec9a1c39266d75ed2c3314f6e846a8f11853eff43fc45db79c7256d9bfd94602", piType = "configured", piComps = [(ComponentLib,fromList ["base-4.9.0.0","containers-0.5.7.1","random-1.1-fe6ccf72ebd63a2d68570bb45b42bd08df5570c6151cb9af54907d40ef9af454"])]}]})
data PlanJson = PlanJson
{ pjPlan :: [PlanItem]
, pjCabalVersion :: String
, pjCabalLibVersion :: String
, pjCompilerId :: Maybe String
, pjOS :: Maybe String
, pjArch :: Maybe String
} deriving Show
instance FromJSON PlanJson where
parseJSON = withObject "PlanJson" $ \o ->
PlanJson <$> o .: "install-plan"
<*> o .: "cabal-version"
<*> o .: "cabal-lib-version"
<*> o .:? "compiler-id"
<*> o .:? "os"
<*> o .:? "arch"
type PID = Text
data PlanItem = PlanItem
{ piId :: !PID
, piType :: !Text
, piComps :: [(Component, S.Set PID)]
-- flags
} deriving Show
instance FromJSON PlanItem where
parseJSON = withObject "PlanItem" $ \o ->
PlanItem <$> o .: "id"
<*> o .: "type"
<*> (doComps . fromMaybe M.empty <$> o .:? "components")
where
doComps :: M.Map Text CompInfo -> [(Component, S.Set PID)]
doComps m = sortOn fst [ (toComp k, S.fromList v) | (k,CompInfo v) <- M.toList m ]
data CompInfo = CompInfo [PID]
instance FromJSON CompInfo where
parseJSON = withObject "CompInfo" $ \o -> CompInfo <$> o .: "depends"
-- | Component of a package
--
-- NB: a similiar type exists in cabal's codebase
data Component =
ComponentLib
| ComponentSubLib !Text
| ComponentExe !Text
| ComponentTest !Text
| ComponentBench !Text
| ComponentSetup
deriving (Show, Eq, Ord, Generic)
toComp :: Text -> Component
toComp t0 = case T.splitOn ":" t0 of
["lib"] -> ComponentLib
["lib",n] -> ComponentSubLib n
["exe",n] -> ComponentExe n
["bench",n] -> ComponentBench n
["test",n] -> ComponentTest n
["setup"] -> ComponentSetup
_ -> error "IDE.Utils.CabalPlan.toComp"
-- |
-- >>> display <$> (unitIdToPackageId =<< simpleParse "base-4.9.0.0")
-- Just "base-4.9.0.0"
-- >>> display <$> (unitIdToPackageId =<< simpleParse "QuickCheck-2.9.1-ec9a1c39266d75ed2c3314f6e846a8f11853eff43fc45db79c7256d9bfd94602")
-- Just "QuickCheck-2.9.1"
unitIdToPackageId :: UnitId -> Maybe PackageIdentifier
unitIdToPackageId = (>>= simpleParse . T.unpack) . takePackageId . T.pack . display
takePackageId :: T.Text -> Maybe T.Text
takePackageId t
| T.null pfx = Nothing
| T.all (`elem` ("0123456789." :: String)) sfx = Just t -- assume hash-less
| T.all (`elem` ("0123456789abcdef" :: String)) sfx = Just (T.init pfx)
| otherwise = Nothing
where
(pfx, sfx) = T.breakOnEnd "-" t