cabal-plan-0.1.0.0: src-exe/cabal-plan.hs
{-# LANGUAGE RecordWildCards #-}
module Main where
import Control.Monad
import Control.Monad.RWS.Strict
import qualified Data.Graph as G
import Data.Map (Map)
import qualified Data.Map as M
import Data.Set (Set)
import qualified Data.Set as S
import qualified Data.Text as T
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.Builder as LT
import qualified Data.Text.Lazy.IO as LT
import System.Console.ANSI
import System.Environment
import Cabal.Plan
main :: IO ()
main = do
-- TODO: optparse-applicative
args <- getArgs
case args of
[] -> doInfo
("info":_) -> doInfo
("show":_) -> print =<< findAndDecodePlanJson
("list-bin":_) -> doListBin
_ -> fail "unknown command (known commands: info show list-bin)"
doListBin :: IO ()
doListBin = do
(v,_projbase) <- findAndDecodePlanJson
forM_ (M.toList (pjUnits v)) $ \(_,Unit{..}) -> do
forM_ (M.toList uComps) $ \(cn,ci) -> do
case ciBinFile ci of
Nothing -> return ()
Just fn -> do
let PkgId (PkgName pn) _ = uPId
g = case cn of
CompNameLib -> T.unpack (pn <> T.pack":lib:" <> pn)
_ -> T.unpack (pn <> T.pack":" <> dispCompName cn)
putStrLn (g ++ " " ++ fn)
doInfo :: IO ()
doInfo = do
(v,projbase) <- findAndDecodePlanJson
putStrLn ("using '" ++ projbase ++ "' as project root")
putStrLn ""
putStrLn "Tree"
putStrLn "~~~~"
putStrLn ""
LT.putStrLn (dumpPlanJson v)
-- print (findCycles (planJsonIdGrap v))
putStrLn ""
putStrLn "Top-sorted"
putStrLn "~~~~~~~~~~"
putStrLn ""
let xs = toposort (planJsonIdGraph v)
forM_ xs print
putStrLn ""
putStrLn "Direct deps"
putStrLn "~~~~~~~~~~~"
putStrLn ""
let locals = [ Unit{..} | Unit{..} <- M.elems pm, uType == UnitTypeLocal ]
pm = pjUnits v
forM_ locals $ \pitem -> do
print (uPId pitem)
forM_ (M.toList $ uComps pitem) $ \(ct,ci) -> do
print ct
forM_ (S.toList $ ciLibDeps ci) $ \dep -> do
let Just dep' = M.lookup dep pm
pid = uPId dep'
putStrLn (" " ++ T.unpack (dispPkgId pid))
putStrLn ""
return ()
----------------------------------------------------------------------------
dumpPlanJson :: PlanJson -> LT.Text
dumpPlanJson (PlanJson { pjUnits = pm }) = LT.toLazyText out
where
((),out) = evalRWS (mapM_ (go2 []) (S.toList roots)) () mempty
id2pid :: Map UnitId PkgId
id2pid = M.fromList [ (uId, uPId) | Unit{..} <- M.elems pm ]
lupPid uid = M.findWithDefault undefined uid id2pid
go2 :: [(CompName,Bool)] -> UnitId -> (RWS () LT.Builder (Set UnitId)) ()
go2 lvl pid = do
pidSeen <- gets (S.member pid)
let pid_label = if preExists then (prettyId pid) else colorify White (prettyId pid)
if not pidSeen
then do
tell $ LT.fromString (linepfx ++ pid_label ++ "\n")
showDeps
else do
tell $ LT.fromString (linepfx ++ pid_label ++ ccol CompNameLib " ┄┄\n")
-- tell $ LT.fromString (linepfx' ++ " └┄\n")
modify' (S.insert pid)
return ()
where
Just x' = M.lookup pid pm
preExists = uType x' == UnitTypeBuiltin
showDeps = forM_ (M.toList $ uComps x') $ \(ct,deps) -> do
unless (ct == CompNameLib) $
tell (LT.fromString $ linepfx' ++ " " ++ prettyCompTy (lupPid pid) ct ++ "\n")
forM_ (lastAnn $ S.toList (ciLibDeps deps)) $ \(l,y) -> do
go2 (lvl ++ [(ct, not l)]) y
linepfx = case unsnoc lvl of
Nothing -> ""
Just (xs,(zt,z)) -> concat [ if x then ccol xt " │ " else " " | (xt,x) <- xs ]
++ (ccol zt $ if z then " ├─ " else " └─ ")
linepfx' = concat [ if x then " │ " else " " | (_,x) <- lvl ]
roots :: Set UnitId
roots = M.keysSet pm `S.difference` leafs
where
leafs = mconcat $ concatMap (map (ciLibDeps . snd) . M.toList . uComps) (M.elems pm)
prettyId :: UnitId -> String
prettyId = prettyPid . lupPid
prettyPid = T.unpack . dispPkgId
prettyCompTy :: PkgId -> CompName -> String
prettyCompTy _pid c@CompNameLib = ccol c "[lib]"
prettyCompTy _pid c@CompNameSetup = ccol c "[setup]"
prettyCompTy pid c@(CompNameExe n) = ccol c $ "[" ++ prettyPid pid ++ ":exe:" ++ show n ++ "]"
prettyCompTy pid c@(CompNameTest n) = ccol c $ "[" ++ prettyPid pid ++ ":test:" ++ show n ++ "]"
prettyCompTy pid c@(CompNameBench n) = ccol c $ "[" ++ prettyPid pid ++ ":bench:" ++ show n ++ "]"
prettyCompTy pid c@(CompNameSubLib n) = ccol c $ "[" ++ prettyPid pid ++ ":lib:" ++ show n ++ "]"
ccol CompNameLib = colorify White
ccol (CompNameExe _) = colorify Green
ccol CompNameSetup = colorify Red
ccol (CompNameTest _) = colorify Yellow
ccol (CompNameBench _) = colorify Cyan
ccol (CompNameSubLib _) = colorify Blue
colorify :: Color -> String -> String
colorify col s = setSGRCode [SetColor Foreground Vivid col] ++ s ++ setSGRCode [Reset]
lastAnn :: [x] -> [(Bool,x)]
lastAnn = reverse . firstAnn . reverse
firstAnn :: [x] -> [(Bool,x)]
firstAnn [] = []
firstAnn (x:xs) = (True,x) : map ((,) False) xs
unsnoc :: [x] -> Maybe ([x],x)
unsnoc [] = Nothing
unsnoc xs = Just (init xs, last xs)
toposort :: Ord a => Map a (Set a) -> [a]
toposort m = reverse . map f . G.topSort $ g
where
(g, f) = graphFromMap m
graphFromMap :: Ord a => Map a (Set a) -> (G.Graph, G.Vertex -> a)
graphFromMap m = (g, v2k')
where
v2k' v = case v2k v of ((), k, _) -> k
(g, v2k, _) = G.graphFromEdges [ ((), k, S.toList v)
| (k,v) <- M.toList m ]