packages feed

pubgrub-0.1.0.0: test/Toy.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies      #-}

module Toy where

import           Data.Functor.Identity (Identity, runIdentity)
import           Data.List             (maximumBy)
import           Data.Map.Strict       (Map)
import qualified Data.Map.Strict       as Map
import           Data.Ord              (comparing)
import           Data.Set              (Set)
import qualified Data.Set              as Set
import           Data.Text             (Text)
import qualified Data.Text             as T
import qualified Data.Text.IO          as T
import           PubGrub

newtype Pkg = Pkg String
    deriving (Eq, Ord, Show)

data ToyFact
    = Wants Pkg (Set Int)
    | Needs Pkg Int Pkg (Set Int)

instance Domain Pkg where

    type ValueOf Pkg = Int
    type SetOf Pkg = Set Int
    type FactOf Pkg = ToyFact
    singleton _ v = Set.singleton v

wants :: Pkg -> [Int] -> Incompatibility Pkg
wants p vs = seed (Wants p (Set.fromList vs)) p (Negative (Set.fromList vs))

needs :: Pkg -> Int -> Pkg -> [Int] -> Incompatibility Pkg
needs p v q ws =
    fact (Needs p v q (Set.fromList ws)) [(p, Positive (Set.singleton v)), (q, Negative (Set.fromList ws))]

type World = Map Pkg [(Int, [(Pkg, [Int])])]

app, lib, util :: Pkg
app = Pkg "app"
lib = Pkg "lib"
util = Pkg "util"

world :: World
world =
    Map.fromList
        [ (app, [(1, [(lib, [1, 2])])])
        , (lib, [(1, [(util, [1])]), (2, [(util, [2])])])
        , (util, [(1, [])])
        ]

provider :: World -> Provider Pkg Identity
provider w = Provider $ \p offered ->
    pure $ case [(v, deps) | (v, deps) <- Map.findWithDefault [] p w, v `Set.member` offered] of
        [] -> (Nothing, [])
        candidates ->
            let (v, deps) = maximumBy (comparing fst) candidates
             in (Just v, [needs p v q ws | (q, ws) <- deps])

plan :: World -> [Incompatibility Pkg] -> Maybe (Map Pkg Int)
plan w seeds = case runIdentity (solve mempty (provider w) seeds) of
    Right solution -> Just solution
    Left _         -> Nothing

broken :: World
broken = Map.insert util [(3, [])] world

versions :: Set Int -> Text
versions vs = T.intercalate " or " (map (T.pack . show) (Set.toList vs))

name :: Pkg -> Set Int -> Text
name (Pkg p) vs = T.pack p <> " " <> versions vs

formatter :: Formatter Pkg
formatter = mkFormatter says hole name
  where
    says (Wants p vs) _     = name p vs <> " is requested"
    says (Needs p v q ws) _ = name p (Set.singleton v) <> " depends on " <> name q ws
    hole (Pkg p) vs = T.pack p <> " has no version " <> versions vs

why :: World -> [Incompatibility Pkg] -> Text
why w seeds = case runIdentity (solve mempty (provider w) seeds) of
    Right _                -> "There is a plan."
    Left (NoSolution root) -> reportConflict formatter root
    Left _                 -> "The solver failed unexpectedly."

explain :: World -> [Incompatibility Pkg] -> IO ()
explain w seeds = T.putStrLn (why w seeds)