{-# LANGUAGE OverloadedStrings, FlexibleContexts #-} module Graphviz (graphviz) where import Types import Hash import CmdLine import Log import Data.Char hiding (Control) import Data.Monoid import Data.GraphViz import Data.GraphViz.Attributes.Complete import Data.GraphViz.Types.Generalised as G import Data.GraphViz.Types.Monadic import Control.Monad import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L import qualified Data.Text.Lazy as T import qualified Data.Text.Lazy.Encoding as T import Data.Text.Encoding.Error graphviz :: GraphvizOpts -> IO () graphviz opts = do l <- loadLog (graphvizLogFile opts) let g = genGraph opts l f <- createImage (graphvizLogFile opts) Png g putStrLn ("Generated " ++ f) createImage :: PrintDotRepr dg n => FilePath -> GraphvizOutput -> dg n -> IO FilePath createImage f o g = Data.GraphViz.addExtension (runGraphvizCommand Dot g) o f genGraph :: GraphvizOpts -> [Log] -> G.DotGraph T.Text genGraph opts ls = digraph (Str "debug-me") $ do nodeAttrs [style filled] forM_ ls $ showlog [ xcolor Green ] where showlog s l = case (loggedMessage l, loggedHash l) of (User (ActivityMessage a), Just h) -> do node (display h) $ s ++ [ textLabel $ prettyDisplay $ activity a , shape BoxShape ] linkprev s a h (Developer (ActivityMessage a), Just h) -> do node (display h) $ s ++ [ textLabel $ prettyDisplay $ activity a , shape Circle ] linkprev s a h (User (ControlMessage c), Nothing) -> showcontrol c l (Developer (ControlMessage c), Nothing) -> showcontrol c l _ -> return () showcontrol (Control (Rejected ar) _) l = do let hr = hash ar let rejstyle = [ xcolor Red , Style [dashed, filled] ] let nodename = display $ "Rejected " <> display hr node nodename $ rejstyle ++ [ textLabel "Rejected" , shape BoxShape ] showlog rejstyle $ Log { loggedMessage = Developer (ActivityMessage ar) , loggedHash = Just hr , loggedTimestamp = loggedTimestamp l } edge nodename (display hr) rejstyle showcontrol _ _ = return () linkprev s a h = case prevActivity a of Nothing -> return () Just p -> link p h s link a b s = edge (display a) (display b) $ s ++ if graphvizShowHashes opts then [ textLabel (prettyDisplay a) ] else [] xcolor :: X11Color -> Attribute xcolor c = Color [toWC $ X11Color c] class Display t where -- Display more or less as-is, for graphviz. display :: t -> T.Text -- Prettified display for user-visible labels etc. prettyDisplay :: t -> T.Text prettyDisplay = prettyDisplay . display instance Display T.Text where display = id prettyDisplay t | all visible s = t | all isPrint s && not (leadingws s) && not (leadingws (reverse s)) = t | otherwise = T.pack (show s) where s = T.unpack t visible c = isPrint c && not (isSpace c) leadingws (c:_) = isSpace c leadingws _ = False instance Display Val where display (Val b) = T.decodeUtf8With lenientDecode (L.fromStrict b) instance Display Hash where display (Hash m h) = T.pack (show m) <> display h -- Use short hash for pretty display. -- The "h:" prefix is to work around this bug: -- https://github.com/ivan-m/graphviz/issues/16 prettyDisplay h = display $ Val $ "h:" <> (B.take 5 $ val $ hashValue h) instance Display Seen where display = display . seenData instance Display Entered where display v | B.null (val $ echoData v) = display $ enteredData v | otherwise = "[" <> display (echoData v) <> "] " <> display (enteredData v) instance Display Control where display = display . control instance Display ControlAction where display = T.pack . show