packages feed

haphviz 0.1.0.2 → 0.1.1.1

raw patch · 11 files changed

+508/−115 lines, 11 filesdep +QuickCheckdep +checkersdep +haphvizdep ~basedep ~textPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: QuickCheck, checkers, haphviz, hspec, quickcheck-text

Dependency ranges changed: base, text

API changes (from Hackage documentation)

- Text.Dot: (-->) :: NodeId -> NodeId -> DotGen ()
- Text.Dot: (.:) :: NodeId -> Text -> NodeId
- Text.Dot: (=:) :: AttributeName -> AttributeValue -> Attribute
- Text.Dot: cluster :: Text -> DotGen () -> DotGen GraphName
- Text.Dot: cluster_ :: Text -> DotGen () -> DotGen ()
- Text.Dot: color :: AttributeName
- Text.Dot: compound :: AttributeName
- Text.Dot: data DecType
- Text.Dot: data Dot
- Text.Dot: data DotGraph
- Text.Dot: data NodeId
- Text.Dot: data RankdirType
- Text.Dot: dir :: AttributeName
- Text.Dot: directed :: GraphType
- Text.Dot: edgeDec :: [Attribute] -> DotGen ()
- Text.Dot: false :: AttributeValue
- Text.Dot: genDec :: DecType -> [Attribute] -> DotGen ()
- Text.Dot: genDot :: DotGen a -> Dot
- Text.Dot: genEdge :: NodeId -> NodeId -> [Attribute] -> DotGen ()
- Text.Dot: genNode :: NodeId -> [Attribute] -> DotGen ()
- Text.Dot: genSubDot :: Int -> DotGen a -> Dot
- Text.Dot: graph :: GraphType -> GraphName -> DotGen a -> DotGraph
- Text.Dot: graphDec :: [Attribute] -> DotGen ()
- Text.Dot: graph_ :: GraphType -> DotGen a -> DotGraph
- Text.Dot: height :: AttributeName
- Text.Dot: label :: AttributeName
- Text.Dot: labelDec :: Text -> DotGen ()
- Text.Dot: leftRight :: RankdirType
- Text.Dot: namedNode :: Text -> [Attribute] -> DotGen NodeId
- Text.Dot: namelessNode :: [Attribute] -> DotGen NodeId
- Text.Dot: newNode :: DotGen NodeId
- Text.Dot: node :: Text -> DotGen NodeId
- Text.Dot: nodeDec :: [Attribute] -> DotGen ()
- Text.Dot: node_ :: NodeId -> Text -> DotGen ()
- Text.Dot: none :: AttributeValue
- Text.Dot: rankdir :: RankdirType -> DotGen ()
- Text.Dot: shape :: AttributeName
- Text.Dot: subgraph :: Text -> DotGen () -> DotGen GraphName
- Text.Dot: topBottom :: RankdirType
- Text.Dot: true :: AttributeValue
- Text.Dot: type Attribute = (Text, Text)
- Text.Dot: type DotGen = StateT State (WriterT Dot Identity)
- Text.Dot: type State = Int
- Text.Dot: undirected :: GraphType
- Text.Dot: width :: AttributeName
- Text.Dot.Gen.FSA: fsaGraph :: [Text] -> Text -> [Text] -> [(Text, Text, Text)] -> DotGraph
+ Text.Dot.Attributes: color :: AttributeName
+ Text.Dot.Attributes: compound :: AttributeName
+ Text.Dot.Attributes: dir :: AttributeName
+ Text.Dot.Attributes: false :: AttributeValue
+ Text.Dot.Attributes: height :: AttributeName
+ Text.Dot.Attributes: label :: AttributeName
+ Text.Dot.Attributes: none :: AttributeValue
+ Text.Dot.Attributes: shape :: AttributeName
+ Text.Dot.Attributes: true :: AttributeValue
+ Text.Dot.Attributes: width :: AttributeName
+ Text.Dot.Attributes.Arrows: arrowhead :: AttributeName
+ Text.Dot.Attributes.Arrows: arrowtail :: AttributeName
+ Text.Dot.Attributes.Arrows: box :: AttributeValue
+ Text.Dot.Attributes.Arrows: circle :: AttributeValue
+ Text.Dot.Attributes.Arrows: crow :: AttributeValue
+ Text.Dot.Attributes.Arrows: diamond :: AttributeValue
+ Text.Dot.Attributes.Arrows: dot :: AttributeValue
+ Text.Dot.Attributes.Arrows: ediamond :: AttributeValue
+ Text.Dot.Attributes.Arrows: empty :: AttributeValue
+ Text.Dot.Attributes.Arrows: halfopen :: AttributeValue
+ Text.Dot.Attributes.Arrows: inv :: AttributeValue
+ Text.Dot.Attributes.Arrows: invdot :: AttributeValue
+ Text.Dot.Attributes.Arrows: invempty :: AttributeValue
+ Text.Dot.Attributes.Arrows: invodot :: AttributeValue
+ Text.Dot.Attributes.Arrows: normal :: AttributeValue
+ Text.Dot.Attributes.Arrows: obox :: AttributeValue
+ Text.Dot.Attributes.Arrows: odiamond :: AttributeValue
+ Text.Dot.Attributes.Arrows: odot :: AttributeValue
+ Text.Dot.Attributes.Arrows: open :: AttributeValue
+ Text.Dot.Attributes.Arrows: tee :: AttributeValue
+ Text.Dot.Attributes.Arrows: vee :: AttributeValue
+ Text.Dot.Attributes.Styles: bold :: AttributeValue
+ Text.Dot.Attributes.Styles: dashed :: AttributeValue
+ Text.Dot.Attributes.Styles: diagonals :: AttributeValue
+ Text.Dot.Attributes.Styles: dotted :: AttributeValue
+ Text.Dot.Attributes.Styles: filled :: AttributeValue
+ Text.Dot.Attributes.Styles: rounded :: AttributeValue
+ Text.Dot.Attributes.Styles: solid :: AttributeValue
+ Text.Dot.Attributes.Styles: striped :: AttributeValue
+ Text.Dot.Attributes.Styles: style :: AttributeName
+ Text.Dot.Attributes.Styles: wedged :: AttributeValue
+ Text.Dot.FSA: fsaGraph :: [Text] -> Text -> [Text] -> [(Text, Text, Text)] -> DotGraph
+ Text.Dot.Gen: (-->) :: NodeId -> NodeId -> DotGen ()
+ Text.Dot.Gen: (.:) :: NodeId -> Text -> NodeId
+ Text.Dot.Gen: (=:) :: AttributeName -> AttributeValue -> Attribute
+ Text.Dot.Gen: bottomTop :: RankdirType
+ Text.Dot.Gen: cluster :: Text -> DotGen () -> DotGen GraphName
+ Text.Dot.Gen: cluster_ :: Text -> DotGen () -> DotGen ()
+ Text.Dot.Gen: data DecType
+ Text.Dot.Gen: data Dot
+ Text.Dot.Gen: data DotGraph
+ Text.Dot.Gen: data NodeId
+ Text.Dot.Gen: data RankdirType
+ Text.Dot.Gen: directed :: GraphType
+ Text.Dot.Gen: edgeDec :: [Attribute] -> DotGen ()
+ Text.Dot.Gen: genDec :: DecType -> [Attribute] -> DotGen ()
+ Text.Dot.Gen: genDot :: DotGen a -> Dot
+ Text.Dot.Gen: genEdge :: NodeId -> NodeId -> [Attribute] -> DotGen ()
+ Text.Dot.Gen: genNode :: NodeId -> [Attribute] -> DotGen ()
+ Text.Dot.Gen: genSubDot :: Int -> DotGen a -> Dot
+ Text.Dot.Gen: graph :: GraphType -> GraphName -> DotGen a -> DotGraph
+ Text.Dot.Gen: graphDec :: [Attribute] -> DotGen ()
+ Text.Dot.Gen: graph_ :: GraphType -> DotGen a -> DotGraph
+ Text.Dot.Gen: labelDec :: Text -> DotGen ()
+ Text.Dot.Gen: leftRight :: RankdirType
+ Text.Dot.Gen: namedNode :: Text -> [Attribute] -> DotGen NodeId
+ Text.Dot.Gen: namelessNode :: [Attribute] -> DotGen NodeId
+ Text.Dot.Gen: newNode :: DotGen NodeId
+ Text.Dot.Gen: node :: Text -> DotGen NodeId
+ Text.Dot.Gen: nodeDec :: [Attribute] -> DotGen ()
+ Text.Dot.Gen: node_ :: NodeId -> Text -> DotGen ()
+ Text.Dot.Gen: rankdir :: RankdirType -> DotGen ()
+ Text.Dot.Gen: rightLeft :: RankdirType
+ Text.Dot.Gen: subgraph :: Text -> DotGen () -> DotGen GraphName
+ Text.Dot.Gen: topBottom :: RankdirType
+ Text.Dot.Gen: type Attribute = (AttributeName, AttributeValue)
+ Text.Dot.Gen: type AttributeName = Text
+ Text.Dot.Gen: type AttributeValue = Text
+ Text.Dot.Gen: type DotGen = StateT State (WriterT Dot Identity)
+ Text.Dot.Gen: type State = Int
+ Text.Dot.Gen: undirected :: GraphType
+ Text.Dot.Render: renderGraph :: DotGraph -> Text
+ Text.Dot.Render: renderToFile :: FilePath -> DotGraph -> IO ()
+ Text.Dot.Render: renderToStdOut :: DotGraph -> IO ()
+ Text.Dot.Types.Internal: BT :: RankdirType
+ Text.Dot.Types.Internal: DecEdge :: DecType
+ Text.Dot.Types.Internal: DecGraph :: DecType
+ Text.Dot.Types.Internal: DecNode :: DecType
+ Text.Dot.Types.Internal: Declaration :: DecType -> [Attribute] -> Dot
+ Text.Dot.Types.Internal: DirectedGraph :: GraphType
+ Text.Dot.Types.Internal: DotEmpty :: Dot
+ Text.Dot.Types.Internal: DotSeq :: Dot -> Dot -> Dot
+ Text.Dot.Types.Internal: Edge :: NodeId -> NodeId -> [Attribute] -> Dot
+ Text.Dot.Types.Internal: Graph :: GraphType -> GraphName -> Dot -> DotGraph
+ Text.Dot.Types.Internal: Identity :: a -> Identity a
+ Text.Dot.Types.Internal: LR :: RankdirType
+ Text.Dot.Types.Internal: Label :: Text -> Dot
+ Text.Dot.Types.Internal: Nameless :: Int -> NodeId
+ Text.Dot.Types.Internal: Node :: NodeId -> [Attribute] -> Dot
+ Text.Dot.Types.Internal: RL :: RankdirType
+ Text.Dot.Types.Internal: Rankdir :: RankdirType -> Dot
+ Text.Dot.Types.Internal: RawDot :: Text -> Dot
+ Text.Dot.Types.Internal: Subgraph :: Text -> Dot -> Dot
+ Text.Dot.Types.Internal: TB :: RankdirType
+ Text.Dot.Types.Internal: UndirectedGraph :: GraphType
+ Text.Dot.Types.Internal: UserId :: Text -> NodeId
+ Text.Dot.Types.Internal: [runIdentity] :: Identity a -> a
+ Text.Dot.Types.Internal: class Monoid a
+ Text.Dot.Types.Internal: data DecType
+ Text.Dot.Types.Internal: data Dot
+ Text.Dot.Types.Internal: data DotGraph
+ Text.Dot.Types.Internal: data GraphType
+ Text.Dot.Types.Internal: data NodeId
+ Text.Dot.Types.Internal: data RankdirType
+ Text.Dot.Types.Internal: instance GHC.Base.Monoid Text.Dot.Types.Internal.Dot
+ Text.Dot.Types.Internal: instance GHC.Classes.Eq Text.Dot.Types.Internal.DecType
+ Text.Dot.Types.Internal: instance GHC.Classes.Eq Text.Dot.Types.Internal.Dot
+ Text.Dot.Types.Internal: instance GHC.Classes.Eq Text.Dot.Types.Internal.DotGraph
+ Text.Dot.Types.Internal: instance GHC.Classes.Eq Text.Dot.Types.Internal.GraphType
+ Text.Dot.Types.Internal: instance GHC.Classes.Eq Text.Dot.Types.Internal.NodeId
+ Text.Dot.Types.Internal: instance GHC.Classes.Eq Text.Dot.Types.Internal.RankdirType
+ Text.Dot.Types.Internal: instance GHC.Show.Show Text.Dot.Types.Internal.DecType
+ Text.Dot.Types.Internal: instance GHC.Show.Show Text.Dot.Types.Internal.Dot
+ Text.Dot.Types.Internal: instance GHC.Show.Show Text.Dot.Types.Internal.DotGraph
+ Text.Dot.Types.Internal: instance GHC.Show.Show Text.Dot.Types.Internal.GraphType
+ Text.Dot.Types.Internal: instance GHC.Show.Show Text.Dot.Types.Internal.NodeId
+ Text.Dot.Types.Internal: instance GHC.Show.Show Text.Dot.Types.Internal.RankdirType
+ Text.Dot.Types.Internal: mappend :: Monoid a => a -> a -> a
+ Text.Dot.Types.Internal: mconcat :: Monoid a => [a] -> a
+ Text.Dot.Types.Internal: mempty :: Monoid a => a
+ Text.Dot.Types.Internal: newtype Identity a :: * -> *
+ Text.Dot.Types.Internal: type Attribute = (AttributeName, AttributeValue)
+ Text.Dot.Types.Internal: type AttributeName = Text
+ Text.Dot.Types.Internal: type AttributeValue = Text
+ Text.Dot.Types.Internal: type GraphName = Text

Files

haphviz.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                haphviz-version:             0.1.0.2+version:             0.1.1.1 synopsis:            Graphviz code generation with Haskell description:   There are multiple ways to describe this package:@@ -11,6 +11,8 @@   - "A graph visualization code generation EDSL"   - "An easy way to draw graphs" +  If any combinators are still missing, feel free to shoot the maintainer an email!+ author:              Tom Sydney Kerckhove maintainer:          syd.kerckhove@gmail.com copyright:           Tom Sydney Kerckhove 2015@@ -26,9 +28,12 @@  library   exposed-modules:     Text.Dot-                       Text.Dot.Gen.FSA-  other-modules:       Text.Dot.Gen+                       Text.Dot.Gen+                       Text.Dot.FSA                        Text.Dot.Render+                       Text.Dot.Attributes+                       Text.Dot.Attributes.Arrows+                       Text.Dot.Attributes.Styles                        Text.Dot.Types.Internal   -- other-extensions:       build-depends:       base         >= 4.6   && <  4.10@@ -36,3 +41,18 @@                      , mtl          >= 2.2   && <  2.3   hs-source-dirs:      src   default-language:    Haskell2010++test-suite haphviz-test+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             Spec.hs+  build-depends:       base+                     , haphviz+                     , hspec+                     , QuickCheck+                     , checkers+                     , quickcheck-text+                     , text+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N+  default-language:    Haskell2010+
src/Text/Dot.hs view
@@ -1,4 +1,30 @@ {-# LANGUAGE OverloadedStrings #-}++-- | Graphviz code-generation in Haskell+--+-- This can be used to generate graphviz code for large graph visualisations.+--+-- > {-# LANGUAGE OverloadedStrings #-}+-- > import Text.Dot+-- >+-- > main :: IO ()+-- > main = renderToStdOut $ graph directed "example" $ do+-- >     a <- node "a"+-- >     b <- node "b"+-- >     a --> b+-- >     b --> a+-- >     b --> b+--+-- >>> runhaskell example.hs+-- > digraph example {+-- >   0 [label=<a>];+-- >   1 [label=<b>];+-- >   0 -> 1;+-- >   1 -> 0;+-- >   1 -> 1;+-- > }+--+-- module Text.Dot (       module Text.Dot.Gen     -- * Graph rendering
+ src/Text/Dot/Attributes.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE OverloadedStrings #-}+module Text.Dot.Attributes (+      module Text.Dot.Attributes+    -- * Arrows+    , module Text.Dot.Attributes.Arrows+    -- * Styles+    , module Text.Dot.Attributes.Styles+    ) where++import           Text.Dot.Attributes.Arrows+import           Text.Dot.Attributes.Styles+import           Text.Dot.Types.Internal++-- * Attribute Names++label :: AttributeName+label = "label"++compound :: AttributeName+compound = "compound"++shape :: AttributeName+shape = "shape"++color :: AttributeName+color = "color"++dir :: AttributeName+dir = "dir"++width :: AttributeName+width = "width"++height :: AttributeName+height = "height"++-- * Attribute values++true :: AttributeValue+true = "true"++false :: AttributeValue+false = "false"++none :: AttributeValue+none = "none"
+ src/Text/Dot/Attributes/Arrows.hs view
@@ -0,0 +1,72 @@+{-# LANGUAGE OverloadedStrings #-}+module Text.Dot.Attributes.Arrows where++import           Text.Dot.Types.Internal++-- * Arrow attributes++arrowhead :: AttributeName+arrowhead = "arrowhead"++arrowtail :: AttributeName+arrowtail = "arrowtail"++-- * Arrow attribute values++box :: AttributeValue+box = "box"++circle :: AttributeValue+circle = "circle"++crow :: AttributeValue+crow = "crow"++diamond :: AttributeValue+diamond = "diamond"++dot :: AttributeValue+dot = "dot"++ediamond :: AttributeValue+ediamond = "ediamond"++empty :: AttributeValue+empty = "empty"++halfopen :: AttributeValue+halfopen = "halfopen"++inv :: AttributeValue+inv = "inv"++invdot :: AttributeValue+invdot = "invdot"++invempty :: AttributeValue+invempty = "invempty"++invodot :: AttributeValue+invodot = "invodot"++normal :: AttributeValue+normal = "normal"++obox :: AttributeValue+obox = "obox"++odiamond :: AttributeValue+odiamond = "odiamond"++odot :: AttributeValue+odot = "odot"++open :: AttributeValue+open = "open"++tee :: AttributeValue+tee = "tee"++vee :: AttributeValue+vee = "vee"+
+ src/Text/Dot/Attributes/Styles.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Style attributes.+--+-- See http://www.graphviz.org/doc/info/attrs.html#k:style for more info on graphviz styles+module Text.Dot.Attributes.Styles where++import           Text.Dot.Types.Internal++-- * Style attributes++style :: AttributeName+style = "style"++-- * Style attribute values++bold :: AttributeValue+bold = "bold"++striped :: AttributeValue+striped = "striped"++filled :: AttributeValue+filled = "filled"++solid :: AttributeValue+solid = "solid"++dashed :: AttributeValue+dashed = "dashed"++dotted :: AttributeValue+dotted = "dotted"++rounded :: AttributeValue+rounded = "rounded"++-- ** Styles only for nodes++diagonals :: AttributeValue+diagonals = "diagonals"++wedged :: AttributeValue+wedged = "wedged"+
+ src/Text/Dot/FSA.hs view
@@ -0,0 +1,81 @@+{-# LANGUAGE OverloadedStrings #-}+-- | Easy FSA visualisation+module Text.Dot.FSA where++import           Text.Dot++import           Control.Monad (forM, forM_, when)+import           Data.Maybe    (fromMaybe, isNothing)++import           Data.Text+import qualified Data.Text     as T+import qualified Data.Text.IO  as T++-- | An easy way to generate an FSA visualization+--+-- You only need to specify the FSA definition and Haphviz takes care of the rest.+--+-- > main :: IO ()+-- > main = renderToStdOut $ fsaGraph+-- >             ["a", "b", "c"]+-- >             "a"+-- >             ["b"]+-- >             [("a", "b", "p"), ("b", "b", "q"), ("b", "c", "p")]+--+-- >>> runhaskell fsa.hs+-- > digraph haphviz {+-- >     node [width=<0>, height=<0>];+-- >     rankdir = LR;+-- >     0 [label=<a>];+-- >     1 [label=<b>, shape=<doublecircle>];+-- >     2 [label=<c>];+-- >     3 [style=<invis>];+-- >     3 -> 0;+-- >     0 -> 1 [label=<p>];+-- >     1 -> 1 [label=<q>];+-- >     1 -> 2 [label=<p>];+-- > }+--+--+fsaGraph :: [Text] -- ^ Set of states+          -> Text -- ^ Initial state+          -> [Text] -- ^ Accepting states+          -> [(Text, Text, Text)] -- ^ Edges: From, To, Symbol+          -> DotGraph+fsaGraph states initial accepting edges = graph_ directed $ do+    nodeDec [width =: "0", height =: "0"] -- Nodes as small as possible+    rankdir leftRight++    stateNodes <- forM states $ \s -> do+        n <- newNode+        genNode n $+            if s `elem` accepting+                then [label =: s, shape =: "doublecircle"]+                else [label =: s]+        return (s, n)++    -- Check that accepting states are actually states+    forM_ accepting $ \s -> do+        when+            (isNothing $ lookup s stateNodes)+            (error $ "Accepting state is not in the set of states: " ++ T.unpack s)++    -- Draw an edge from an invisible state to the initial state+    case lookup initial stateNodes of+        Nothing -> error "Initial state is not in the set of states"+        Just initialNode -> do+            n <- newNode+            genNode n ["style" =: "invis"]+            n --> initialNode++    -- Draw the edges+    forM_ edges $ \(from, to, symbol) -> do+        let fromNode = fromMaybe+                    (error $ "From node not found: " ++ T.unpack from)+                    (lookup from stateNodes)+        let toNode = fromMaybe+                    (error $ "To node not found: " ++ T.unpack to)+                    (lookup to stateNodes)+        genEdge fromNode toNode [label =: symbol]++
src/Text/Dot/Gen.hs view
@@ -1,11 +1,15 @@ {-# LANGUAGE OverloadedStrings #-}+-- | Generating graph contents module Text.Dot.Gen (       module Text.Dot.Gen-    -- * Graph Types+    , module Text.Dot.Attributes+     , Dot     , DotGraph     , NodeId     , Attribute+    , AttributeName+    , AttributeValue     , DecType     , RankdirType     ) where@@ -13,6 +17,7 @@ import           Control.Monad.State     (StateT, execStateT, get, modify) import           Control.Monad.Writer    (WriterT, execWriterT, tell) +import           Text.Dot.Attributes import           Text.Dot.Types.Internal  import           Control.Monad           (void)@@ -21,45 +26,80 @@ import           Data.Text               (Text) import qualified Data.Text               as T -type DotGen = StateT State (WriterT Dot Identity) -type State = Int -- Next nameless node number---- | Generate graph+-- | Generate a haphviz graph with a given name and content+--+-- >>> graph directed "mygraph" $ do+--         a <- node "a"+--         b <- node "b"+--         a --> b+-- > graph mygraph {+-- >   0 [label="a"];+-- >   1 [label="b"];+-- >   0 -- 1;+-- > } graph :: GraphType-      -> GraphName -- ^ Graph name+      -> GraphName -- ^ Internal graph name       -> DotGen a  -- ^ Content       -> DotGraph graph gt gn func = Graph gt gn $ genDot func +-- | Like 'graph' but without an internal graph name+--+-- > graph_ gt func = graph gt "haphviz" func graph_ :: GraphType        -> DotGen a -- ^ Content        -> DotGraph graph_ gt func = graph gt "haphviz" func --- | Generate Internal dot AST+-- | Generate Internal dot content AST genDot :: DotGen a -> Dot genDot = genSubDot 0 +-- | Utility function to generate a graph with nameless nodes starting from a given starting number. genSubDot :: Int -> DotGen a -> Dot genSubDot n func = runIdentity $ execWriterT $ execStateT func n  -- * Graph types --- | Undirected graph+-- | Directed graph+--+-- >>> directed+-- > digraph directed :: GraphType directed = DirectedGraph --- | Directed graph+-- | Undirected graph+--+-- >>> undirected+-- > graph undirected :: GraphType undirected = UndirectedGraph  -- * Nodes+ -- | Most general node declaration+--+-- This allows you to specify a node identifier for the node.+--+-- In general it is more efficient to use nameless nodes and have the identifiers generated for you.+--+-- It also allows you to specify attributes.+-- In general it is better to use 'namelessNode'.+--+-- >>> n <- newNode+-- >>> genNode n [color =: green]+-- > 0 [color="green"]; genNode :: NodeId -> [Attribute] -> DotGen () genNode ni ats = tell $ Node ni ats --- | Node with given name and attributes+-- | Node with given (internal) name and attributes+--+-- Aside from human-readable output, there is no reason to use named nodes.+-- Use 'node' instead.+--+-- >>> void $ namedNode "woohoo" [color =: red]+-- > wohoo [color="red"]; namedNode :: Text -- ^ Name           -> [Attribute] -> DotGen NodeId namedNode t ats = do@@ -68,6 +108,12 @@     return ni  -- | Nameless node with attributes+--+-- This generates a nameless node for you but still allows you to specify its individual attributes.+-- In general it is better to use 'nodeDec' and then 'node'.+--+-- >>> void $ namelessNode [color =: blue]+-- > 0 [color="blue"]; namelessNode :: [Attribute] -> DotGen NodeId namelessNode ats = do     ni <- newNode@@ -75,17 +121,27 @@     return ni  -- | Node with a label but no other attributes+--+-- A node with a given label and no other attributes.+-- Usually used in conjunction with 'nodeDec'.+--+-- >>> void $ node "server"+-- > 0 [label="server"]; node :: Text -- ^ Label      -> DotGen NodeId node l = namelessNode [label =: l]  -- | Node with given node Id and label+--+-- > node_ ni l = genNode ni [label =: l] node_ :: NodeId -- ^ given Node ID       -> Text -- ^ Label       -> DotGen () node_ ni l = genNode ni [label =: l] --- Generate a new nameless node ID+-- | Generate a new internally nameless node ID+--+-- It is not generally a good idea to use this directly but it can be used to define node identifiers before a subgraph to reference them both in- and outside of it. newNode :: DotGen NodeId newNode = do     i <- get@@ -96,58 +152,46 @@ -- * Edges  -- | Most general edge declaration+--+-- This allows you to specify attributes for a single edge.+--+-- Usually it is better to use 'edgeDec' and then '-->'.+--+-- >>> genEdge a b [label =: "MyEdge"]+-- > a -> b [label="MyEdge"]; genEdge :: NodeId -> NodeId -> [Attribute] -> DotGen () genEdge n1 n2 ats = tell $ Edge n1 n2 ats   -- | Infix edge constructor. (No attributes)+--+-- This takes care of using the right edge declaration for the given graph.+--+-- For undirected graphs, the output would be @--@ ...+--+-- >>> a --> b+-- > a -- b;+--+-- ... and for directed graphs it would be @->@.+--+-- >>> a --> b+-- > a -> b; (-->) :: NodeId -> NodeId -> DotGen () n1 --> n2 = genEdge n1 n2 []  -- * Attributes  -- | Infix operator for an attribute pair+--+-- >>> [label =: "MyNode"]+-- > [label="MyNode"] (=:) :: AttributeName -> AttributeValue -> Attribute (=:) = (,) --- ** Attribute Names -label :: AttributeName-label = "label"--compound :: AttributeName-compound = "compound"--shape :: AttributeName-shape = "shape"--color :: AttributeName-color = "color"--dir :: AttributeName-dir = "dir"--width :: AttributeName-width = "width"--height :: AttributeName-height = "height"---- ** Attribute values--true :: AttributeValue-true = "true"--false :: AttributeValue-false = "false"--none :: AttributeValue-none = "none"-- -- * Declarations --- | General declaration of attributes+-- | General declaration of common attributes genDec :: DecType -> [Attribute] -> DotGen () genDec t ats = tell $ Declaration t ats @@ -175,41 +219,81 @@  -- * Subgraphs --- | Subgraph-subgraph :: Text -> DotGen () -> DotGen GraphName-subgraph name content = do-    n <- get-    let c = genSubDot n content-    tell $ Subgraph name c-    return name --- | Cluster+-- | Cluster with a given name+--+-- The @cluster_@ prefix is taken care of. cluster :: Text -> DotGen () -> DotGen GraphName cluster name = subgraph $ "cluster_" <> name --- | Cluster, discarding the graph name+-- | Like 'cluster', discarding the graph name. cluster_ :: Text -> DotGen () -> DotGen () cluster_ name subgraph = void $ cluster name subgraph +-- | Subgraph declaration+--+-- This is rarely useful. Just use 'cluster'.+subgraph :: Text -> DotGen () -> DotGen GraphName+subgraph name content = do+    n <- get+    let c = genSubDot n content+    tell $ Subgraph name c+    return name+ -- * Miscelaneous -- ** Rankdir++-- | The rankdir declaration+--+--  This changes the default layout of nodes+--+-- >>> rankdir leftRight+-- > rankdir = LR; rankdir :: RankdirType -> DotGen () rankdir = tell . Rankdir +-- |+-- >>> leftRight+-- > LR leftRight :: RankdirType leftRight = LR +-- |+-- >>> rightLeft+-- > RL+rightLeft :: RankdirType+rightLeft = RL++-- |+-- >>> topBottom+-- > TB topBottom :: RankdirType topBottom = TB +-- |+-- >>> bottomTop+-- > BT+bottomTop :: RankdirType+bottomTop = BT+ -- ** Labels -- | Label declaration for graphs or subgraphs labelDec :: Text -> DotGen () labelDec = tell . Label  -- ** Ports++-- | Use a certain port on a given node's label as an endpoint for an edge (.:) :: NodeId      -> Text -- ^ Port      -> NodeId (UserId t) .: p = UserId $ t <> ":" <> p (Nameless i) .: p = UserId $ T.pack (show i) <> ":" <> p++-- * Internals+-- | Generation monad+type DotGen = StateT State (WriterT Dot Identity)++-- | The next id for a nameless node+type State = Int+
− src/Text/Dot/Gen/FSA.hs
@@ -1,55 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Text.Dot.Gen.FSA where--import           Text.Dot--import           Control.Monad (forM, forM_, when)-import           Data.Maybe    (fromMaybe, isNothing)--import           Data.Text-import qualified Data.Text     as T-import qualified Data.Text.IO  as T---- | An easy way to generate an FSA visualization-fsaGraph :: [Text] -- ^ Set of states-          -> Text -- Initial state-          -> [Text] -- Accepting states-          -> [(Text, Text, Text)] -- ^ Edges: From, To, Symbol-          -> DotGraph-fsaGraph states initial accepting edges = graph_ directed $ do-    nodeDec [width =: "0", height =: "0"] -- Nodes as small as possible-    rankdir leftRight--    stateNodes <- forM states $ \s -> do-        n <- newNode-        genNode n $-            if s `elem` accepting-                then [label =: s, shape =: "doublecircle"]-                else [label =: s]-        return (s, n)--    -- Check that accepting states are actually states-    forM_ accepting $ \s -> do-        when-            (isNothing $ lookup s stateNodes)-            (error $ "Accepting state is not in the set of states: " ++ T.unpack s)--    -- Draw an edge from an invisible state to the initial state-    case lookup initial stateNodes of-        Nothing -> error "Initial state is not in the set of states"-        Just initialNode -> do-            n <- newNode-            genNode n ["style" =: "invis"]-            n --> initialNode--    -- Draw the edges-    forM_ edges $ \(from, to, symbol) -> do-        let fromNode = fromMaybe-                    (error $ "From node not found: " ++ T.unpack from)-                    (lookup from stateNodes)-        let toNode = fromMaybe-                    (error $ "To node not found: " ++ T.unpack to)-                    (lookup to stateNodes)-        genEdge fromNode toNode [label =: symbol]--
src/Text/Dot/Render.hs view
@@ -1,4 +1,6 @@ {-# LANGUAGE OverloadedStrings #-}++-- | Rendering Graphviz code from Haphviz graphs module Text.Dot.Render (       renderGraph     , renderToFile@@ -20,12 +22,15 @@  type Render = ReaderT GraphType (StateT Int (WriterT Text Identity)) +-- | Render a given graph and write the result to the given file renderToFile :: FilePath -> DotGraph -> IO () renderToFile file g = T.writeFile file $ renderGraph g +-- | Render a given graph and print it to std out renderToStdOut :: DotGraph -> IO () renderToStdOut = T.putStrLn . renderGraph +-- | Render a graph to graphviz code renderGraph :: DotGraph -> Text renderGraph (Graph gtype name content) = mconcat     [ "digraph"@@ -139,6 +144,8 @@  renderRankDirType :: RankdirType -> Render () renderRankDirType TB = tell "TB"+renderRankDirType BT = tell "BT"+renderRankDirType RL = tell "RL" renderRankDirType LR = tell "LR"  indent :: Render ()
src/Text/Dot/Types/Internal.hs view
@@ -1,5 +1,10 @@+-- | Internal types.+--+-- You may want to keep off these. module Text.Dot.Types.Internal (       module Text.Dot.Types.Internal+    -- * Re-exports+     , Identity(..)     , Monoid(..)     ) where@@ -14,31 +19,54 @@ import           Control.Monad.Writer   (WriterT, execWriterT, tell)  +-- | Internal name of a graph, used to reference graphs and subgraphs type GraphName = Text++-- | Type of a graph, directed or undirected.+--+-- This also specifies what edge declarations look like. data GraphType = UndirectedGraph                | DirectedGraph     deriving (Show, Eq) +-- | Attribute name: just text type AttributeName = Text++-- | Attribute value: just text type AttributeValue = Text-type Attribute = (Text, Text) +-- | Attribute: a tuple of name and value.+type Attribute = (AttributeName, AttributeValue)++-- | A node identifier.+--+-- This is either a user supplied name or a generated numerical identifier. data NodeId = UserId Text             | Nameless Int     deriving (Show, Eq) +-- | Declaration type+--+-- Used to declare common attributes for nodes or edges. data DecType = DecGraph              | DecNode              | DecEdge     deriving (Show, Eq) +-- | A Haphviz Graph data DotGraph = Graph GraphType GraphName Dot     deriving (Show, Eq) +-- | Rankdir Type+--+-- Used to specify the default node layout direction data RankdirType = LR+                 | RL                  | TB+                 | BT     deriving (Show, Eq) +-- | Haphviz internal graph content AST data Dot = Node NodeId [Attribute]          | Edge NodeId NodeId [Attribute]          | Declaration DecType [Attribute]@@ -50,6 +78,19 @@          | DotEmpty     deriving (Show, Eq) +-- | Dot is a monoid, duh, that's the point. instance Monoid Dot where-    mappend d1 d2 = DotSeq d1 d2     mempty = DotEmpty++    -- Left identity+    mappend DotEmpty d = d++    -- Right identity+    mappend d DotEmpty = d++    -- Associativity+    mappend d (DotSeq d1 d2) = DotSeq (mappend d d1) d2++    mappend d1 d2 = DotSeq d1 d2++
+ test/Spec.hs view
@@ -0,0 +1,26 @@+import           Text.Dot.Types.Arbitrary+import           Text.Dot.Types.Internal++import           Control.Exception        (evaluate)+import           Control.Monad            (forM_)+import           Test.Hspec+import           Test.QuickCheck+import           Test.QuickCheck.Checkers+import           Test.QuickCheck.Classes  (monoid)+++main :: IO ()+main = hspec $ do+    describe "Dot" $ do+        batch "is a Monoid" $ monoid (undefined :: Dot)++-- Recall:+-- type TestBatch = (String, [Test])+-- type Test = (String, Property)+batch :: String -> TestBatch -> Spec+batch str (name, ts) =+    describe str $+        forM_ ts $ \(tname, p) ->+            it tname $ property p++