{-# LANGUAGE OverloadedStrings #-}
module Darcs.UI.Commands.ShowDependencies ( showDeps ) where
import Darcs.Prelude
import qualified Data.Map.Strict as M
import Data.Maybe( fromJust, fromMaybe )
import qualified Data.Set as S
import Darcs.Repository ( RepoJob(..), readRepo, withRepositoryLocation )
import Darcs.UI.Flags ( DarcsFlag, getRepourl, useCache )
import Darcs.UI.Options ( oid, odesc, ocheck, defaultFlags, (?) )
import qualified Darcs.UI.Options.All as O
import Darcs.UI.Commands ( DarcsCommand(..), nodefaults, findRepository, withStdOpts )
import Darcs.UI.Commands.Util ( matchRange )
import Darcs.UI.Completion ( noArgs )
import Darcs.Util.Hash ( sha1short, showAsHex )
import Darcs.Util.Path ( AbsolutePath )
import Darcs.Util.Printer
( Doc
, (<+>)
, formatText
, hsep
, prefixLines
, putDocLn
, quoted
, renderString
, text
, vcat
)
import Darcs.Patch.Commute ( Commute, commuteFL )
import Darcs.Patch.Ident ( PatchId, Ident(..) )
import Darcs.Patch.Info ( PatchInfo, piName, makePatchname )
import Darcs.Patch.Witnesses.Ordered
( (:>)(..)
, FL(..)
, RL(..)
, reverseFL
)
import Darcs.Patch.Witnesses.Sealed ( Sealed2(..) )
showDepsDescription :: String
showDepsDescription = "Generate the graph of dependencies."
showDepsHelp :: Doc
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"
]
showDeps :: DarcsCommand
showDeps = DarcsCommand
{ commandProgramName = "darcs"
, commandName = "dependencies"
, commandHelp = showDepsHelp
, commandDescription = showDepsDescription
, commandExtraArgs = 0
, commandExtraArgHelp = []
, commandCommand = depsCmd
, commandPrereq = findRepository
, commandCompleteArgs = noArgs
, commandArgdefaults = nodefaults
, commandAdvancedOptions = []
, commandBasicOptions = odesc showDepsBasicOpts
, commandDefaults = defaultFlags showDepsOpts
, commandCheckOptions = ocheck showDepsOpts
}
where
showDepsBasicOpts = O.matchRange
showDepsOpts = showDepsBasicOpts `withStdOpts` oid
depsCmd :: (AbsolutePath, AbsolutePath) -> [DarcsFlag] -> [String] -> IO ()
depsCmd _ opts _ = do
let repodir = fromMaybe "." (getRepourl opts)
withRepositoryLocation (useCache ? opts) repodir $ RepoJob $ \repo -> do
Sealed2 rFl <- matchRange (O.matchRange ? opts) <$> readRepo repo
putDocLn $ renderDepsGraphAsDot $ depsGraph $ reverseFL rFl
type DepsGraph p = M.Map (PatchId p) (Deps p)
type Deps p = (S.Set (PatchId p), S.Set (PatchId p))
depsGraph :: forall p wX wY. (Commute p, Ident p) => RL p wX wY -> DepsGraph p
depsGraph NilRL = M.empty
depsGraph (ps :<: p) =
M.insert i (foldDeps ps (p :>: NilFL) NilFL (S.empty, S.empty)) m
where
m = depsGraph ps
i = ident p
allDeps j = uncurry S.union . fromJust . M.lookup j
addDeps j = S.insert j . S.union (allDeps j m)
foldDeps :: RL p wA wB -> FL p wB wC -> FL p wC wD -> Deps p -> Deps p
foldDeps NilRL _ _ acc = acc
foldDeps (qs :<: q) p_and_deps non_deps acc@(direct, indirect)
| j `S.member` indirect = foldDeps qs (q :>: p_and_deps) non_deps acc
| Just (p_and_deps' :> q') <- commuteFL (q :> p_and_deps) =
foldDeps qs p_and_deps' (q' :>: non_deps) acc
| otherwise =
foldDeps qs (q :>: p_and_deps) non_deps (addDeps j direct, indirect)
where
j = ident q
renderDepsGraphAsDot :: M.Map PatchInfo (S.Set PatchInfo, S.Set PatchInfo) -> Doc
renderDepsGraphAsDot g = vcat ["digraph {", indent body, "}"]
where
indent = prefixLines (" ")
body = vcat
[ "graph [rankdir=LR];"
, "node [imagescale=true];"
, vcat (map showNode (map fst pairs))
, vcat (map showEdges pairs)
]
pairs = M.toList $ M.map fst g
showEdges (i, ds)
| S.null ds = mempty
| otherwise =
hsep [showID i, "->", "{" <> hsep (map showID (S.toList ds)) <> "}"]
showNode i = showID i <+> "[label=" <> showLabel i <> "]"
showID = quoted . showAsHex . sha1short . makePatchname
showLabel i = text $ show $ renderString $ formatText 20 [piName i]