darcs-2.12.0: src/Darcs/UI/Commands/ShowDependencies.hs
module Darcs.UI.Commands.ShowDependencies
( showDeps )
where
import Control.Arrow ( (***) )
import Data.Maybe( fromMaybe )
import Data.GraphViz
import Data.GraphViz.Algorithms ( transitiveReduction )
import Data.GraphViz.Attributes.Complete
import Data.Graph.Inductive.Graph ( Graph(..), mkGraph, LNode, UEdge )
import Data.Graph.Inductive.PatriciaTree ( Gr )
import qualified Data.Text.Lazy as T
import qualified Data.ByteString.Char8 as BC ( unpack )
import Darcs.Util.Tree ( Tree )
import Darcs.Repository ( readRepo, withRepositoryDirectory, RepoJob(..) )
import Darcs.UI.Flags ( DarcsFlag(..), getRepourl
, useCache, toMatchFlags )
import Darcs.UI.Options ( PrimDarcsOption, oid, odesc, ocheck, onormalise, defaultFlags )
import qualified Darcs.UI.Options.All as O
import Darcs.UI.Commands ( DarcsCommand(..), nodefaults, findRepository, withStdOpts )
import Darcs.UI.Commands.Unrecord ( matchingHead )
import Darcs.Util.Text ( formatText )
import Darcs.Util.Path ( AbsolutePath )
import Darcs.Patch ( RepoPatch )
import Darcs.Patch.Set ( PatchSet(..), newset2FL )
import Darcs.Patch.Info ( _piName )
import Darcs.Patch.PatchInfoAnd ( hopefully )
import Darcs.Patch.Apply( ApplyState )
import Darcs.Patch.Named ( Named (..), patch2patchinfo )
import Darcs.Patch.Named.Wrapped ( removeInternalFL )
import Darcs.Patch.Match ( firstMatch, matchFirstPatchset )
import Darcs.Patch.Choices ( lpPatch, LabelledPatch, label, getLabelInt )
import Darcs.Patch.Depends ( SPatchAndDeps, getDeps, findCommonWithThem )
import Darcs.Patch.Witnesses.Sealed ( Sealed2(..), seal2, Sealed(..) )
import Darcs.Patch.Witnesses.Ordered ( (:>)(..), (:>)(..), RL(..)
, reverseFL, foldlFL, mapFL_FL )
import Darcs.Repository.Flags ( Verbosity(..), UseCache(..) )
showDepsDescription :: String
showDepsDescription = "Generate the graph of dependencies."
showDepsHelp :: String
showDepsHelp = formatText 80
[ unwords [ "The `darcs show dependencies` command is used to create"
, "a graph of the dependencies between patches of the"
, "repository (by default up to last tag)."
]
, unwords [ "The resulting graph is described in Dot Language, a"
, "general example of use could be:"
]
, "darcs show dependencies | dot -Tpdf -o FILE.pdf"
]
showDepsBasicOpts :: PrimDarcsOption [O.MatchFlag]
showDepsBasicOpts = O.matchSeveralOrLast
showDepsOpts :: O.DarcsOption
a
([O.MatchFlag]
-> Maybe O.StdCmdAction
-> Bool
-> Bool
-> Verbosity
-> Bool
-> UseCache
-> Maybe String
-> Bool
-> Maybe String
-> Bool
-> a)
showDepsOpts = showDepsBasicOpts `withStdOpts` oid
showDeps :: DarcsCommand [DarcsFlag]
showDeps = DarcsCommand
{ commandProgramName = "darcs"
, commandName = "dependencies"
, commandHelp = showDepsHelp
, commandDescription = showDepsDescription
, commandExtraArgs = 0
, commandExtraArgHelp = []
, commandCommand = depsCmd
, commandPrereq = findRepository
, commandGetArgPossibilities = return []
, commandArgdefaults = nodefaults
, commandAdvancedOptions = []
, commandBasicOptions = odesc showDepsBasicOpts
, commandDefaults = defaultFlags showDepsOpts
, commandCheckOptions = ocheck showDepsOpts
, commandParseOptions = onormalise showDepsOpts
}
type DepsGraph = Gr String ()
depsCmd :: (AbsolutePath, AbsolutePath) -> [DarcsFlag] -> [String] -> IO ()
depsCmd _ opts _ = do
let repodir = fromMaybe "." (getRepourl opts)
withRepositoryDirectory (useCache opts) repodir $ RepoJob $ \repo -> do
Sealed2 r <- readRepo repo >>= pruneRepo
let rFl = newset2FL r
deps = getDeps
(removeInternalFL . mapFL_FL hopefully $ rFl)
rFl
dGraph = transitiveReduction $
graphToDot nodeLabeledParams $ makeGraph deps
putStrLn $ T.unpack $ printDotGraph dGraph
where
nodeLabeledParams :: GraphvizParams n String el () String
nodeLabeledParams =
defaultParams { globalAttributes =
[GraphAttrs {attrs = [RankDir FromLeft]}]
, fmtNode = \(_,l) ->
[ toLabel l
, ImageScale UniformScale
]
}
pruneRepo r = let matchFlags = toMatchFlags opts in
if firstMatch matchFlags
then case getLastPatches matchFlags r of
Sealed2 ps -> return $ seal2 $ PatchSet NilRL ps
else case matchingHead matchFlags r of
_ :> patches -> return $ seal2 $ PatchSet NilRL $ reverseFL patches
getLastPatches matchFlags ps =
case matchFirstPatchset matchFlags ps of
Sealed p1s -> case findCommonWithThem ps p1s of
_ :> ps' -> seal2 $ reverseFL ps'
makeGraph :: (RepoPatch p,ApplyState p ~ Tree) => [SPatchAndDeps p] -> DepsGraph
makeGraph = uncurry mkGraph . (id *** concat) . unzip . map mkNodeWithEdges
where
mkNodeWithEdges :: SPatchAndDeps p -> (LNode String, [UEdge])
mkNodeWithEdges (Sealed2 father, Sealed2 childs) = (mkLNode father,mkUEdges)
where
mkNode :: LabelledPatch (Named p) wX wY -> Int
mkNode = fromInteger . getLabelInt . label
mkUEdge :: [UEdge] -> LabelledPatch (Named p) wX wY -> [UEdge]
mkUEdge les child = (mkNode father, mkNode child,()) : les
mkLabel :: LabelledPatch (Named p) wX wY -> String
mkLabel = formatText 20 . (:[]) . BC.unpack . _piName . patch2patchinfo . lpPatch
mkLNode :: LabelledPatch (Named p) wX wY -> LNode String
mkLNode p = (mkNode p, mkLabel p)
mkUEdges :: [UEdge]
mkUEdges = foldlFL mkUEdge [] childs