forked from neilmayhew/RepoExplorer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDependencyRoots.hs
112 lines (91 loc) · 3.64 KB
/
DependencyRoots.hs
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
103
104
105
106
107
108
109
110
111
112
{-# LANGUAGE CPP, DeriveDataTypeable #-}
module Main where
import Debian.Control.ByteString
import Debian.Relation
import Data.Graph.Inductive
import Data.Tree
import Data.Set (fromList, member)
import Data.List
import Data.Maybe
import Data.Either
import Data.Either.Utils
import Data.Ord
import Control.Monad
import System.IO
import System.Console.CmdArgs.Implicit
import qualified Data.ByteString.Char8 as B
type Package = Paragraph
type FieldValue = B.ByteString
type PackageName = FieldValue
data Style = Roots | Forest
deriving (Show, Data, Typeable)
data Options = Options
{ statusFile :: String
, style :: Style }
deriving (Show, Data, Typeable)
options = Options
{ statusFile = def &= typ "STATUSFILE" &= argPos 0 &= opt "/var/lib/dpkg/status"
, style = enum [Roots &= help "Show dependency roots (default)", Forest &= help "Show dependency forest"]
&= groupname "Options" }
&= program "DependencyRoots"
&= summary "DependencyRoots v0.5"
&= details ["STATUSFILE defaults to /var/lib/dpkg/status"]
main = do
args <- cmdArgs options
(parseControlFromFile $ statusFile args)
>>= either (putErr "Parse error") (putDeps (style args) . packageDeps)
where putDeps style = case style of
Roots -> putRoots graphRoots showAlts
Forest -> putRoots graphForest showTree
showTree = drawTree
showAlts = intercalate "|" . flatten
putErr :: Show e => String -> e -> IO ()
putErr msg e = hPutStrLn stderr $ msg ++ ": " ++ show e
putRoots :: (Gr String () -> Forest String) -> (Tree String -> String) -> [[String]] -> IO ()
putRoots fRoots fShow = mapM_ putStrLn . map fShow . sortForest . fRoots . makeGraph
where sortForest = sortBy (comparing rootLabel)
graphRoots :: Gr a b -> Forest a
graphRoots g = map labelAlts alternates
where forest = dff (topsort g) g
alternates = map (ancestors . rootLabel) forest
ancestors n = head $ rdff [n] g
labelAlts = fmap (fromJust . lab g)
graphForest :: Gr a b -> Forest a
graphForest g = map labelTree forest
where forest = dff (topsort g) g
labelTree = fmap (fromJust . lab g)
makeGraph :: [[String]] -> Gr String ()
makeGraph deps = fst $ mkMapGraph nodes edges
where nodes = map head deps
edges = concatMap mkEdges deps
mkEdges (n : sucs) = map (\s -> (n, s, ())) sucs
packageDeps :: Control -> [[String]]
packageDeps c = map mkDeps pkgs
where pkgs = filter pkgIsInstalled . unControl $ c
names = fromList . map extName $ pkgs
mkDeps p = extName p : filter installed (pkgDeps p)
installed name = name `member` names
extName p = if a /= baseArch && a /= "all" then n ++ ':' : a else n
where n = pkgName p
a = pkgArch p
baseArch = maybe "" pkgArch $ find (\p -> pkgName p == "base-files") pkgs
pkgName :: Package -> String
pkgName = maybe "Unnamed" B.unpack . fieldValue "Package"
pkgArch :: Package -> String
pkgArch = maybe "" B.unpack . fieldValue "Architecture"
pkgIsInstalled :: Package -> Bool
pkgIsInstalled = maybe False isInstalled . fieldValue "Status"
where isInstalled v = parseStatus v !! 2 == B.pack "installed"
parseStatus = B.split ' ' . stripWS
#if !MIN_VERSION_debian(3,64,0)
unBinPkgName = id
#elif !MIN_VERSION_debian(3,69,0)
unBinPkgName_ = unPkgName . unBinPkgName
#define unBinPkgName unBinPkgName_
#endif
pkgDeps :: Package -> [String]
pkgDeps p = names "Depends" ++ names "Recommends"
where field = B.unpack . fromMaybe B.empty . flip fieldValue p
rels = fromRight . parseRelations . field
names = map (relName . head) . rels
relName (Rel name _ _) = unBinPkgName name