calligraphy-0.1.2: src/Calligraphy/Phases/Render.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
-- | Rendering takes a callgraph, and produces a dot file
module Calligraphy.Phases.Render (render, pRenderConfig, RenderConfig (..)) where
import Calligraphy.Util.Printer
import Calligraphy.Util.Types
import Control.Monad
import qualified Data.EnumSet as EnumSet
import Data.List (intercalate)
import Data.Tree (Tree (..))
import Options.Applicative hiding (style)
import Text.Show (showListWith)
data RenderConfig = RenderConfig
{ showCalls :: Bool,
showTypes :: Bool,
showKey :: Bool,
showGHCKeys :: Bool,
showModulePath :: Bool,
showChildArrowhead :: Bool,
locMode :: LocMode,
clusterModules :: Bool,
clusterGroups :: Bool,
splines :: Bool,
reverseDependencyRank :: Bool
}
render :: RenderConfig -> Prints CallGraph
render RenderConfig {..} (CallGraph modules calls types) = do
brack "digraph calligraphy {" "}" $ do
unless splines $ textLn "splines=false;"
textLn "node [style=filled fillcolor=\"#ffffffcf\"];"
textLn "graph [outputorder=edgesfirst];"
let nonEmptyModules = filter (not . null . moduleForest) modules
forM_ (zip nonEmptyModules [0 :: Int ..]) $
\(Module modName modPath forest, moduleIx) ->
moduleCluster moduleIx (if showModulePath then modPath else modName) $
forM_ (zip forest [0 :: Int ..]) $ \(root, forestIx) -> do
treeCluster moduleIx forestIx $
renderTreeNode root
when showCalls $
forM_ calls $ \(caller, callee) ->
if reverseDependencyRank
then edge caller callee []
else edge callee caller ["dir" .= "back"]
when showTypes $
forM_ types $ \(caller, callee) ->
if reverseDependencyRank
then edge caller callee ["style" .= "dotted"]
else edge callee caller ["style" .= "dotted", "dir" .= "back"]
where
moduleCluster :: Int -> String -> Printer a -> Printer a
moduleCluster modIx title inner
| clusterModules =
brack ("subgraph cluster_module_" <> show modIx <> " {") "}" $ do
strLn $ "label=" <> show title <> ";"
inner
| otherwise = inner
treeCluster :: Int -> Int -> Printer a -> Printer a
treeCluster modIx groupIx inner
| clusterGroups =
brack ("subgraph cluster_" <> show modIx <> "_" <> show groupIx <> " {") "}" $ do
textLn "style=invis;"
inner
| otherwise = inner
nodeLabel :: Decl -> String
nodeLabel (Decl name key ghcKeys _ _ loc) =
intercalate "\n"
. cons name
. consIf showKey (show (unKey key))
. consManyIf showGHCKeys (fmap (show . unGHCKey) (EnumSet.toList ghcKeys))
. ( case locMode of
Hide -> id
Line -> cons ('L' : show (locLine loc))
LineCol -> cons (show loc)
)
$ []
renderTreeNode :: Prints (Tree Decl)
renderTreeNode (Node decl@(Decl _ key _ exported typ _) children) = do
strLn $ show (unKey key) <> " " <> style ["label" .= ("\"" <> nodeLabel decl <> "\""), "shape" .= nodeShape typ, "style" .= nodeStyle]
forM_ children $ \child@(Node childDecl _) -> do
renderTreeNode child
edge key (declKey childDecl)
. cons ("style" .= "dashed")
. consIf (not showChildArrowhead) ("arrowhead" .= "none")
$ []
where
nodeStyle :: String
nodeStyle =
show . intercalate ", "
. consIf (typ == RecDecl) "rounded"
. consIf (not exported) "dashed"
. cons "filled"
$ []
cons :: a -> [a] -> [a]
cons = (:)
consIf :: Bool -> a -> [a] -> [a]
consIf True = (:)
consIf False = flip const
consManyIf :: Foldable f => Bool -> f a -> [a] -> [a]
consManyIf True fs as = foldr (:) as fs
consManyIf False _ as = as
nodeShape :: DeclType -> String
nodeShape DataDecl = "octagon"
nodeShape ConDecl = "box"
nodeShape RecDecl = "box"
nodeShape ClassDecl = "house"
nodeShape ValueDecl = "ellipse"
edge :: MonadPrint m => Key -> Key -> Style -> m ()
edge (Key from) (Key to) sty = strLn $ show from <> " -> " <> show to <> " " <> style sty
(.=) :: String -> String -> (String, String)
(.=) = (,)
style :: Style -> String
style sty = showListWith (\(key, val) -> showString key . showChar '=' . showString val) sty ";"
type Style = [(String, String)]
data LocMode = Hide | Line | LineCol
pLocMode :: Parser LocMode
pLocMode =
flag' Line (long "show-line" <> help "Show line numbers")
<|> flag' LineCol (long "show-line-col" <> help "Show line and column numbers")
<|> pure Hide
pRenderConfig :: Parser RenderConfig
pRenderConfig =
RenderConfig
<$> flag True False (long "hide-calls" <> help "Don't show call arrows")
<*> flag True False (long "hide-types" <> help "Don't show type arrows")
<*> flag False True (long "show-key" <> help "Show internal keys with identifiers. Useful for debugging.")
<*> flag False True (long "show-ghc-key" <> help "Show GHC keys with identifiers. Useful for debugging.")
<*> flag False True (long "show-module-path" <> help "Show a module's filepath instead of its name")
<*> flag False True (long "show-child-arrowhead" <> help "Put an arrowhead at the end of a parent-child edge")
<*> pLocMode
<*> flag True False (long "no-cluster-modules" <> help "Don't draw modules as a cluster.")
<*> flag True False (long "no-cluster-trees" <> help "Don't draw definition trees as a cluster.")
<*> flag True False (long "no-splines" <> help "Render arrows as straight lines instead of splines")
<*> flag False True (long "reverse-dependency-rank" <> help "Make dependencies have lower rank than the dependee, i.e. show dependencies above their parent.")