summaryrefslogtreecommitdiff
path: root/Coolbal/CabalPlan.hs
blob: ae6dbf6353b3ac08f1d6256449865407c9e2e3ce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE ViewPatterns #-}
module Coolbal.CabalPlan (
    CabalPlan(..),
    PlanPackage(..),
    ParseResult(..),
    parseCabalPlan,
) where

import qualified Data.Bytes as Bytes
import Data.Foldable (toList)
import Data.List (find)
import qualified GHC.Exts as IsList (IsList(..))
import qualified Json
import System.IO.Error (catchIOError)


data CabalPlan = CabalPlan
    { planCompiler :: String
    , planPackages :: [PlanPackage] }
  deriving (Show)

data PlanPackage
    = PreExisting 
        { ppName :: String
        , ppVersion :: String
        , ppDepends :: Maybe [String] }
    | Configured
        { ppName :: String
        , ppVersion :: String
        , ppLocal :: Bool
        , ppDepends :: Maybe [String] }
  deriving (Show)

data ParseResult e a = NotFound | ParseError e | Parsed a
  deriving (Show)

instance Functor (ParseResult e) where
    fmap _ NotFound = NotFound
    fmap _ (ParseError e) = ParseError e
    fmap f (Parsed x) = Parsed (f x)

instance Applicative (ParseResult e) where
    pure = Parsed
    NotFound <*> _ = NotFound
    _ <*> NotFound = NotFound
    ParseError e <*> _ = ParseError e
    _ <*> ParseError e = ParseError e
    Parsed f <*> Parsed x = Parsed (f x)

instance Monad (ParseResult e) where
    NotFound >>= _ = NotFound
    ParseError e >>= _ = ParseError e
    Parsed x >>= f = f x

instance MonadFail (ParseResult String) where
    fail = ParseError


parseCabalPlan :: FilePath -> IO (ParseResult String CabalPlan)
parseCabalPlan fpath = do
    res <- (Just <$> Bytes.readFile fpath) `catchIOError` (\_ -> return Nothing)
    case res of
      Nothing -> return NotFound
      Just contents -> return (parseFromValue contents)
  where
    parseFromValue (Json.decode -> Right toplevel) = do
        Json.Object obj <- return toplevel
        Json.String compiler <- locate "compiler-id" obj
        Json.Array pkgs <- locate "install-plan" obj
        CabalPlan (IsList.toList compiler) <$> mapM parsePackage (toList pkgs)
    parseFromValue _ = fail "JSON decode failed"

    parsePackage value = do
        Json.Object obj <- return value
        Json.String typ <- locate "type" obj
        Json.String name <- locate "pkg-name" obj
        Json.String version <- locate "pkg-version" obj
        depends <- case locate "depends" obj of
                     Parsed (Json.Array dependsArr) ->
                         Just <$> mapM (\case Json.String s -> return (IsList.toList s)
                                              _ -> fail "Dependency is not a string")
                                       (toList dependsArr)
                     _ -> return Nothing
        if | typ == IsList.fromList "pre-existing" ->
               return (PreExisting (IsList.toList name) (IsList.toList version) depends)
           | typ == IsList.fromList "configured" -> do
               Json.String style <- locate "style" obj
               return (Configured (IsList.toList name) (IsList.toList version)
                                  (style == IsList.fromList "local")
                                  depends)
           | otherwise ->
               fail ("Unknown package type '" ++ IsList.toList typ ++ "'")

locate :: Foldable f => String -> f Json.Member -> ParseResult String Json.Value
locate key l
  | Just value <- fmap (\(Json.Member _ v) -> v) (find (\(Json.Member k _) -> k == IsList.fromList key) l)
  = return value
  | otherwise
  = fail ("Key not found in object: '" ++ key ++ "'")