packages feed

flow2dot 0.9.0.3 → 0.9.1

raw patch · 13 files changed

+378/−506 lines, 13 filesdep +dotgendep +flow2dotdep −haskell98dep −utf8-stringsetup-changedbinary-addedPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: dotgen, flow2dot

Dependencies removed: haskell98, utf8-string

API changes (from Hackage documentation)

- Text.FlowDiagram: instance Arbitrary Flow
- Text.FlowDiagram: instance Arbitrary Message
- Text.FlowDiagram: instance Arbitrary Name
- Text.FlowDiagram: instance Eq Flow
- Text.FlowDiagram: instance Show Flow
+ Text.FlowDiagram: instance GHC.Classes.Eq Text.FlowDiagram.Flow
+ Text.FlowDiagram: instance GHC.Show.Show Text.FlowDiagram.Flow
- Text.FlowDiagram: Action :: String -> String -> Flow
+ Text.FlowDiagram: Action :: Swimline -> String -> Flow
- Text.FlowDiagram: Msg :: String -> String -> String -> Flow
+ Text.FlowDiagram: Msg :: Swimline -> Swimline -> String -> Flow
- Text.FlowDiagram: Order :: [String] -> Flow
+ Text.FlowDiagram: Order :: [Swimline] -> Flow

Files

− ChangeLog
@@ -1,53 +0,0 @@-tagged 0.7--  * Added Order directive (see sample.flow for usage)-  * Fix: identifier could not be empty-  * Removing useles import-  * Preserve UTF8 chars in "reflow"-  * Fix to preserve UTF8 chars in "showDot" (again)-  * Imported Dot.hs from dotgen 0.4.1-  * Fixed "lost edge" errors from graphviz--tagged 0.6.1--  * Forgot to add Text.Dot local override to cabal. Thnx to dons for spotting it--tagged 0.6--  * Fixed repo URL in README-  * Made sources compilable with QuickCheck2-  * Dotgen kills UTF-8 in graph attributes. Incorporated patched version of dotgen-0.2 into flow2dot until it will be fixed upstream-  * Added quickCheck.hs and rebuild.sh--tagged 0.5.1--  * Moved all flow diagram processing to separate module (exposed from this package)--tagged 0.4-  * Documentation fixes-  * Switched to `dotgen` package for all graphviz generation needs.-	    Dropped support of preformatted strings in flow files.--tagged 0.3.1-  * Fixed links to repo and docs--tagged 0.3-  * Ditched Text.UTF8 in favor of utf8-string library-  * Quotes inside messages are now properly escaped for Dot. Sample updated accordingly.--tagged 0.2.1-  * Version bump to 0.2.1 - ready for GHC 6.8.2-  * -Wall-  * +LANGUAGE pragmas--tagged 0.2-  * Some minor clarifications in docs-  * Dropped regex-based parser in favor of parsec-based due to issues with Unicode-  * Added QuickCheck to cabal, added LICENSE to Dot.hs-  * Added QuickCheck for Flow parser/pretty-printer-  * Moved graph generation into separate module-  * Added UTF8 module-  * cabalization--tagged 0.1-  * initial version
+ ChangeLog.md view
@@ -0,0 +1,61 @@+2020-06-16+==========+Migrated to github, cleaned up sources++Version bump to 0.9.1++Pre-github Changelog+====================+tagged 0.7++  * Added Order directive (see sample.flow for usage)+  * Fix: identifier could not be empty+  * Removing useles import+  * Preserve UTF8 chars in "reflow"+  * Fix to preserve UTF8 chars in "showDot" (again)+  * Imported Dot.hs from dotgen 0.4.1+  * Fixed "lost edge" errors from graphviz++tagged 0.6.1++  * Forgot to add Text.Dot local override to cabal. Thnx to dons for spotting it++tagged 0.6++  * Fixed repo URL in README+  * Made sources compilable with QuickCheck2+  * Dotgen kills UTF-8 in graph attributes. Incorporated patched version of dotgen-0.2 into flow2dot until it will be fixed upstream+  * Added quickCheck.hs and rebuild.sh++tagged 0.5.1++  * Moved all flow diagram processing to separate module (exposed from this package)++tagged 0.4+  * Documentation fixes+  * Switched to `dotgen` package for all graphviz generation needs.+	    Dropped support of preformatted strings in flow files.++tagged 0.3.1+  * Fixed links to repo and docs++tagged 0.3+  * Ditched Text.UTF8 in favor of utf8-string library+  * Quotes inside messages are now properly escaped for Dot. Sample updated accordingly.++tagged 0.2.1+  * Version bump to 0.2.1 - ready for GHC 6.8.2+  * -Wall+  * +LANGUAGE pragmas++tagged 0.2+  * Some minor clarifications in docs+  * Dropped regex-based parser in favor of parsec-based due to issues with Unicode+  * Added QuickCheck to cabal, added LICENSE to Dot.hs+  * Added QuickCheck for Flow parser/pretty-printer+  * Moved graph generation into separate module+  * Added UTF8 module+  * cabalization++tagged 0.1+  * initial version
LICENSE view
@@ -1,27 +1,29 @@-Copyright (C) 2007 Dmitry Astapov+BSD 3-Clause License +Copyright (c) 2007-2020, Dmitry Astapov+All rights reserved.+ Redistribution and use in source and binary forms, with or without-modification, are permitted provided that the following conditions are-met: +modification, are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright-   notice, this list of conditions and the following disclaimer.  -2. Redistributions in binary form must reproduce the above copyright-   notice, this list of conditions and the following disclaimer in the-   documentation and/or other materials provided with the-   distribution.  -3. The names of the authors may not be used to endorse or promote-   products derived from this software without specific prior written-   permission. +1. Redistributions of source code must retain the above copyright notice, this+   list of conditions and the following disclaimer. -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR-IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE-DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,-INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)-HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,-STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING-IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE-POSSIBILITY OF SUCH DAMAGE. +2. Redistributions in binary form must reproduce the above copyright notice,+   this list of conditions and the following disclaimer in the documentation+   and/or other materials provided with the distribution.++3. Neither the name of the copyright holder nor the names of its+   contributors may be used to endorse or promote products derived from+   this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
− README
@@ -1,41 +0,0 @@-This is a tool to generate nice sequence (flow) diagrams from-textual descriptions, written in Haskell.--This tool generates a diagram _description_, which could be made into-a nice picture with help of Graphviz (www.graphviz.org)--To try it out, run:-  runhaskell flow2dot.hs sample.flow | dot -T png -o sample.png-and view "sample.png" with your favorite picture viewer. If you-dont get a nice picture and get something else (for example, ugly-segfault from dot), try upgrading to latest Graphviz (2.12 or later)--If you want to use national alphabets, make sure that your .flow files-are encoded in UTF-8. If you want to tweak the output - read Dot manual-and use it for scaling, colors, pagination etc.--Latest version could be obtained via:-  darcs pull http://adept.linux.kiev.ua:8080/repos/flow2dot/--License     :  BSD-style (see the file LICENSE)-Send patches to dastapov@gmail.com (using "darcs send")--Thanks to Cale, quicksilver and roconnor from #haskell for-suggestions on how to modularize this. Thanks to Dema from-haskell@conference.jabber.ru for win32 testing. Gwern0 helped-to adapt this to GHC 6.8.2. Radoslav Dorcik sent in parser improvements.-Mike Shigorin and Denis Smirnov packaged for Alt Linux and provided feedback-on compatibility with different GHC versions. Vasyl Vaskul reported bugs.--Known issues-============--* Dot < 2.12 will most likely segfault on files generated by flow2dot--* If your version of dot complains about "Error: lost n1 n2 edge", try using-flow2dot-fix-dot-lost-edges in place of dot:--   runhaskell flow2dot your.flow | flow2dot-fix-dot-lost-edges -T png -o your.png--Trick is to remove 'constraint="false"' from all edges that dot reports as lost.-
+ README.md view
@@ -0,0 +1,41 @@+This is a tool to generate nice sequence (flow) diagrams from+textual descriptions, written in Haskell.++This tool generates a diagram _description_, which could be made into+a nice picture with help of Graphviz (www.graphviz.org)++To try it out, run:+  runhaskell flow2dot.hs sample.flow | dot -T png -o sample.png+and view "sample.png" with your favorite picture viewer. If you+dont get a nice picture and get something else (for example, ugly+segfault from dot), try upgrading to latest Graphviz (2.12 or later)++If you want to use national alphabets, make sure that your .flow files+are encoded in UTF-8. If you want to tweak the output - read Dot manual+and use it for scaling, colors, pagination etc.++Latest version could be obtained via:+  darcs pull http://adept.linux.kiev.ua:8080/repos/flow2dot/++License     :  BSD-style (see the file LICENSE)+Send patches to dastapov@gmail.com (using "darcs send")++Thanks to Cale, quicksilver and roconnor from #haskell for+suggestions on how to modularize this. Thanks to Dema from+haskell@conference.jabber.ru for win32 testing. Gwern0 helped+to adapt this to GHC 6.8.2. Radoslav Dorcik sent in parser improvements.+Mike Shigorin and Denis Smirnov packaged for Alt Linux and provided feedback+on compatibility with different GHC versions. Vasyl Vaskul reported bugs.++Known issues+============++* Dot < 2.12 will most likely segfault on files generated by flow2dot++* If your version of dot complains about "Error: lost n1 n2 edge", try using+flow2dot-fix-dot-lost-edges in place of dot:++   runhaskell flow2dot your.flow | flow2dot-fix-dot-lost-edges -T png -o your.png++Trick is to remove 'constraint="false"' from all edges that dot reports as lost.+
− Setup.hs
@@ -1,3 +0,0 @@-#!/usr/bin/env runhaskell-import Distribution.Simple-main = defaultMain
− Text/Dot.hs
@@ -1,176 +0,0 @@--- |--- Module: Text.Dot--- Copyright: Andy Gill--- License: BSD3------ Maintainer: Andy Gill <andygill@ku.edu>--- Stability: unstable--- Portability: portable------ This module provides a simple interface for building .dot graph files, for input into the dot and graphviz tools. --- It includes a monadic interface for building graphs.--module Text.Dot -	( -	  -- * Dot-	  Dot		-- abstract-	  -- * Nodes-	, node-	, NodeId	-- abstract-	, userNodeId-	, userNode-	  -- * Edges-	, edge-        , edge'-	, (.->.)-	  -- * Showing a graph-	, showDot-	  -- * Other combinators-	, scope-	, attribute-	, share-	, same-	, cluster-	  -- * Simple netlist generation-	, netlistGraph-	) where--import Data.Char-import qualified Data.Map as M-import qualified Data.Set as S--data DotGraph = DotGraph [GraphElement]--data NodeId = NodeId String-	    | UserNodeId Int--instance Show NodeId where-  show (NodeId str) = str-  show (UserNodeId i) -	| i < 0     = "u_" ++ show (negate i)-	| otherwise = "u" ++ show i--data GraphElement = GraphAttribute String String-		  | GraphNode NodeId        [(String,String)]-		  | GraphEdge NodeId NodeId [(String,String)]-		  | GraphEdge' NodeId (Maybe String) NodeId (Maybe String) [(String,String)]-		  | Scope           [GraphElement]-		  | SubGraph NodeId [GraphElement]--data Dot a = Dot { unDot :: Int -> ([GraphElement],Int,a) }--instance Monad Dot where-  return a = Dot $ \ uq -> ([],uq,a)-  m >>= k  = Dot $ \ uq -> case unDot m uq of-			   (g1,uq',r) -> case unDot (k r) uq' of-					   (g2,uq2,r2) -> (g1 ++ g2,uq2,r2)---- | 'node' takes a list of attributes, generates a new node, and gives a 'NodeId'.-node      :: [(String,String)] -> Dot NodeId-node attrs = Dot $ \ uq -> let nid = NodeId $ "n" ++ show uq -			  in ( [ GraphNode nid attrs ],succ uq,nid)----- | 'userNodeId' allows a user to use their own (Int-based) node id's, without needing to remap them.-userNodeId :: Int -> NodeId-userNodeId i = UserNodeId i---- | 'userNode' takes a NodeId, and adds some attributes to that node. -userNode :: NodeId -> [(String,String)] -> Dot ()-userNode nId attrs = Dot $ \ uq -> ( [GraphNode nId attrs ],uq,())---- | 'edge' generates an edge between two 'NodeId's, with attributes.-edge      :: NodeId -> NodeId -> [(String,String)] -> Dot ()-edge  from to attrs = Dot (\ uq -> ( [ GraphEdge from to attrs ],uq,()))---- | 'edge' generates an edge between two 'NodeId's, with optional node sub-labels, and attributes.-edge'      :: NodeId -> Maybe String -> NodeId -> Maybe String -> [(String,String)] -> Dot ()-edge'  from optF to optT attrs = Dot (\ uq -> ( [ GraphEdge' from optF to optT attrs ],uq,()))---- | '.->.' generates an edge between two 'NodeId's.-(.->.) from to = edge from to []---- | 'scope' groups a subgraph together; in dot these are the subgraphs inside "{" and "}".-scope     :: Dot a -> Dot a-scope (Dot fn) = Dot (\ uq -> case fn uq of-			      ( elems,uq',a) -> ([Scope elems],uq',a))---- | 'share' is when a set of nodes share specific attributes. Usually used for layout tweaking.-share :: [(String,String)] -> [NodeId] -> Dot ()-share attrs nodeids = Dot $ \ uq -> -      ( [ Scope ( [ GraphAttribute name val | (name,val) <- attrs]-	       ++ [ GraphNode nodeid [] | nodeid <- nodeids ]-	       ) -        ], uq, ())---- | 'same' provides a combinator for a common pattern; a set of 'NodeId's with the same rank.-same :: [NodeId] -> Dot ()-same = share [("rank","same")]----- | 'cluster' builds an explicit, internally named subgraph (called cluster). -cluster :: Dot a -> Dot (NodeId,a)-cluster (Dot fn) = Dot (\ uq -> -		let cid = NodeId $ "cluster_" ++ show uq -		in case fn (succ uq) of-		    (elems,uq',a) -> ([SubGraph cid elems],uq',(cid,a)))---- | 'attribute' gives a attribute to the current scope.-attribute :: (String,String) -> Dot ()-attribute (name,val) = Dot (\ uq -> ( [  GraphAttribute name val ],uq,()))---- 'showDot' renders a dot graph as a 'String'.-showDot :: Dot a -> String-showDot (Dot dm) = case dm 0 of-		    (elems,_,_) -> "digraph G {\n" ++ unlines (map showGraphElement elems) ++ "\n}\n"--showGraphElement (GraphAttribute name val) = showAttr (name,val) ++ ";"-showGraphElement (GraphNode nid attrs)           = show nid ++ showAttrs attrs ++ ";"-showGraphElement (GraphEdge from to attrs) = show from ++ " -> " ++ show to ++  showAttrs attrs ++ ";"-showGraphElement (GraphEdge' from optF to optT attrs) = showName from optF ++ " -> " ++ showName to optT ++  showAttrs attrs ++ ";"-    where showName n Nothing = show n-          showName n (Just t) = show n ++ ":" ++ t-showGraphElement (Scope elems) = "{\n" ++ unlines (map showGraphElement elems) ++ "\n}"-showGraphElement (SubGraph nid elems) = "subgraph " ++ show nid ++ " {\n" ++ unlines (map showGraphElement elems) ++ "\n}"--showAttrs [] = ""-showAttrs xs = "[" ++ showAttrs' xs ++ "]"-    where-	-- never empty list-	showAttrs' [a]    = showAttr a-	showAttrs' (a:as) = showAttr a ++ "," ++ showAttrs' as--showAttr (name,val) = name ++ "=\""   ++ foldr showsDotChar "" val ++ "\""--showsDotChar '"'  = ("\\\"" ++)-showsDotChar '\\' = ("\\\\" ++)-showsDotChar x    | ord x < 32 = showLitChar x-                  | otherwise  = ([x]++)----- | 'netlistGraph' generates a simple graph from a netlist.-netlistGraph :: (Ord a) -          => (b -> [(String,String)])   -- ^ Attributes for each node-          -> (b -> [a])                 -- ^ Out edges leaving each node-          -> [(a,b)] 			-- ^ The netlist-	  -> Dot ()-netlistGraph attrFn outFn assocs = do-    let nodes = S.fromList $ [ a | (a,_) <- assocs ]-    let outs  = S.fromList $ [ o | (_,b) <- assocs-				 , o <- outFn b -			     ]-    nodeTab <- sequence [ do nd <- node (attrFn b)-                             return (a,nd)-                        | (a,b) <- assocs ]-    otherTab <- sequence [ do nd <- node []-                              return (o,nd)-                         | o <- S.toList outs-                         , o `S.notMember` nodes-                         ]-    let fm = M.fromList (nodeTab ++ otherTab)-    sequence_ [ (fm M.! src) .->. (fm M.! dst)-              | (dst,b) <- assocs-              , src     <- outFn b-              ]-    return ()-
Text/FlowDiagram.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} {-| Converts flow diagrams to the Graphviz (Dot) files for subsequent rendering into nice pictures.@@ -11,72 +10,47 @@                         ) where  import qualified Text.Dot as D-import Control.Monad.State (StateT, evalStateT, gets, modify, lift)-import qualified Data.Map as M (Map, empty, lookup, insert)-import Data.List (intercalate, unfoldr, splitAt, findIndex)-#ifndef NATIVEUTF8-import Prelude hiding (readFile)-import System.IO.UTF8 (readFile)-#endif-import Data.Maybe (catMaybes)+import Control.Monad.State (StateT, evalStateT, gets, modify, lift, zipWithM, zipWithM_, foldM, when)+import qualified Data.Map as M (Map, empty, lookup, insert, union, fromList)+import Data.List (intercalate, unfoldr, splitAt, findIndex, nub, sort)+import System.IO (readFile)+import Data.Maybe (catMaybes, isJust, fromMaybe) import Data.Char (isSpace)-import Test.QuickCheck-import Control.Monad (liftM, liftM2, liftM3) import Text.ParserCombinators.Parsec hiding (State)  {- Idea: In order to draw sequence (flow) diagram using graphviz we can use directed layout (dot) to generate "skeleton" of the diagram and draw message lines and action boxes+-} -Diagram could look like this:-strict digraph SeqDiagram-{-  { // Those are swimline heads-    rank=same-    actor [label="Some actor"];-    system [label="Some system"];-  }-  { //tier1-    rank=same-    node[style=invis,shape=point];-    tier1; // this is an "anchor" for 1st diagram tier-    actor1; // this is a 1st point on "actor" swimline-    system1; // this is a 1st point on "system" swimline-  }-  { //tier2-    rank=same-    node[style=invis,shape=point];-    tier2; // anchor for 2nd diagram tier-    actor2; // this is a 2nd point on "actor" swimline-    system2; // this is a 2nd point on "system" swimline-  }-  // Main body -  // Tiers ordering. We link "anchor" nodes and Dot will do the rest-  tier1 -> tier2;--  // Actual messages. Note the "constraint=false"-  actor1 -> system1[label="xxx", constraint=false];-  system2 -> actor2[label="yyy", constraint=false];-}--}+type Swimline = String  -- | Flow could include messages and actions, one item per source line-data Flow = Msg String String String+data Flow = Msg Swimline Swimline String           -- ^ Message (from, to, message text). Syntax in the source file: @from -> to: message text@-          | Action String String+          | Action Swimline String           -- ^ Action (actor, message text). Syntax in the source file: @actor: message text@-          | Order [String]+          | Order [Swimline]           -- ^ Tries to put swimlines in the specified order. Syntax: @order swimline1 swimline2 ...@             deriving (Eq,Show) +isOrder (Order _) = True+isOrder _         = False++isMsg (Msg _ _ _) = True+isMsg _           = False++names flow = nub $ flip concatMap flow $ \elt ->+  case elt of+    Msg from to _ -> [from,to]+    Action a _ -> [a]+    Order _ -> []+    + -- | State of the diagram builder-data DiagS = DiagS { swimlines::M.Map String D.NodeId-                   -- ^ name of the swimline, ID of the last node on it-                   , numTier :: Int-                   -- ^ number of the next diagram tier-                   , headings :: [D.NodeId]-                   -- ^ IDs of all "swimline start" nodes+data DiagS = DiagS { nodes :: M.Map (Int,Swimline) D.NodeId+                   -- ^ all the nodes of the graph, indexed by tier and swimline name                    }  type Diagram = StateT DiagS D.Dot@@ -84,46 +58,97 @@ -- | 'flow2dot' take a list of flow diagram items (`Flow') and converts them to Graphviz code flow2dot :: [Flow] -> String flow2dot flow = -  ("strict "++) $ D.showDot $ evalStateT (flow2dot' flow) (DiagS M.empty 1 [])+  ("strict "++) $ D.showDot $ evalStateT (flow2dot' flow) (DiagS M.empty)     -- NB: "strict" is VERY important here     -- Without it, "dot" segfaults while rendering diagram (dot 2.12)  flow2dot' :: [Flow] -> Diagram () flow2dot' flow = do+  -- Avoid curved edges at all cost+  attribute ("splines","line")+     let order = case [ ns | Order ns <- flow ] of                 []     -> Nothing                 -- Only the first Order directive would be taken into account                 (ns:_) -> Just ns-  mapM_ (flowElement2dot order) flow-  hs <- gets headings-  same hs+      flow' = filter (not.isOrder) flow+      swimlines = names flow' -flowElement2dot :: Maybe [String] -> Flow -> Diagram ()--- Make a graph block where swimline nodes for the current tier will be put.--- Populate tier with "tier anchor" node+  -- check order+  case order of+    Just order' | sort order' /= sort swimlines -> do+      error "order statement must mention all swimlines"+    _ -> return ()+  +  -- Create nodes for flow elements+  flip mapM_ (zip [0..] flow') $ \(tier,elt) -> do+    namedNodes <- flowElement2dot tier order elt+    addNodes $ M.fromList $ [ ((tier,swimline),node) | (swimline,node)<-namedNodes ]++  -- Add nodes on all the remaining places on all the swimlines+  -- We don't add nodes on the same tier as Msg elements to avoid dot trying to route+  -- Msg edge around those invisible support nodes.+  flip mapM_ [ (tier, sline) | sline <- swimlines+                             , (tier,elt) <-zip [0..] flow'+                             , not (isMsg elt) ] $ \(tier,sline) -> do+    n <- findNode tier sline+    case n of+      Just _ -> return ()+      Nothing -> do+          id <- invisNode+          addNodes (M.fromList [((tier,sline),id)])++  -- create swimline headers+  headers <- flip mapM (fromMaybe swimlines order) $ \sline ->+    node [ ("label", mkHeader sline)+         , ("shape","box")+         ]+  same (headers)++  -- apply order, if any+  when (isJust order) $ do+    zipWithM_ (\h1 h2 -> edge h1 h2 [ ("style","invis")+                                    , ("weight","1") -- this edge is weak compared to verticals+                                    ])+      headers (drop 1 headers)+  +  -- connect all nodes on all swimlines+  -- first, lets group them by tier+  nodesByTier <- flip mapM [ tier | (tier,_) <-zip [0..] flow' ] $ \tier ->+    mapM (findNode tier) [ line | line <- swimlines ]++  -- then, we descend through the tiers connectin nodes to the previous node on+  -- the same swimline, starting from headers+  _ <- foldM (\prevNodes thisTier -> do+          same $ catMaybes thisTier+          zipWithM (\prevNode maybeThisNode ->+                      case maybeThisNode of+                        Just thisNode -> do+                          edge prevNode thisNode+                            [("style","dotted")+                            ,("arrowhead","none")+                            ,("weight","1000") -- these edges want to be+                                               -- very vertical+                            ]+                          return thisNode+                        Nothing -> return prevNode)+            prevNodes thisTier)+         headers nodesByTier+  return ()++flowElement2dot :: Int -> Maybe [String] -> Flow -> Diagram [(String,D.NodeId)] -- Generate nodes for message/action on all required swimlines--- Connect generated nodes, if necessary--- Connect tier to previous, which will ensure that tiers are ordered properly-flowElement2dot _ (Action actor message) = do-  tier <- invisNode-  l <- mkLabel message+flowElement2dot tier _ (Action actor message) = do+  l <- mkLabel tier message   a <- node [("style","filled"),("shape","plaintext"),("label",l)]-  same [tier,a]-  connectToPrev actor a-  connectToPrev "___tier" tier-  incTier+  return [(actor,a)] -flowElement2dot order (Msg from to message) = do-  tier <- invisNode+flowElement2dot tier order (Msg from to message) = do   f    <- invisNode   t    <- invisNode-  same [f,t,tier] -  l <- mkLabel message+  l <- mkLabel tier message -  connectToPrev from f-  connectToPrev to t-  connectToPrev "___tier" tier   let (f',t',attrs) =          if order == Nothing            then (f,t,[])@@ -132,26 +157,24 @@                          (Just x, Just y) -> if x>y then (t,f,[("dir","back")]) else (f,t,[])                          _                -> (f,t,[]) -  edge f' t' $ [("label",l)] ++ attrs-  incTier+  edge f' t' $ [("label",l)+               ,("constraint","false") -- avoid pushing recipient node down+               ,("labelfloat","true") -- be a bit sloppy with label placement+               ] ++ attrs+  return [(from,f),(to,t)]      -- Order setting is done in Msg processing-flowElement2dot _ (Order _) = return ()--+flowElement2dot _ _ (Order _) = return [] -mkLabel :: String -> Diagram String-mkLabel lbl = do-  t <- gets numTier-  return $ if null lbl then show t-                       else (show t ++ ": " ++ reflow lbl)+mkLabel :: Int -> String -> Diagram String+mkLabel tier lbl = do+  return $ if null lbl then ""+                       else (show (tier+1) ++ ": " ++ reflow lbl)  invisNode :: Diagram D.NodeId invisNode = node [("style","invis"),("shape","point")]  reflow :: String -> String--- FIXME: for now, you have to hardcode desired width/height ratio--- FIXME: (tail $ init $ show) trick is needed to work around dotgen-0.2 limitations reflow str = intercalate "\n" $ map unwords $ splitInto words_in_row w       where w = words str             z = length w@@ -161,28 +184,10 @@             chunk 0 lst = Just (lst, [])             chunk n lst = Just $ splitAt n lst             splitInto n = unfoldr (chunk n)+            -- FIXME: for now, you have to hardcode desired width/height ratio             width=3             height=1 --- Return the ID of the next node in the swimline `name',--- generating all required nodes and swimline connections along the way-connectToPrev :: String -> D.NodeId -> Diagram ()-connectToPrev "___tier" _ = return ()-connectToPrev sline currNode = do-  s <- getSwimline sline-  case s of-       -- Swimline already exists-       (Just prevNode) ->  do edge prevNode currNode [("style","dotted"),("arrowhead","none")]-                              setSwimline sline currNode-       -- Otherwise, swimline hase to be created-       (Nothing) -> do setSwimline sline currNode-                       -- Add heading node-                       heading <- node [("label", mkHeader sline),("shape","box")]-                       addHeading heading-                       setSwimline sline heading-                       -- Retry connecting-                       connectToPrev sline currNode- mkHeader :: String -> String mkHeader = map remove_underscore   where@@ -193,21 +198,14 @@ -- State access/modify helpers ------------------------------ -incTier :: Diagram ()-incTier = modify (\e -> e {numTier = numTier e +1} )--getSwimline :: String -> Diagram (Maybe D.NodeId)-getSwimline name = do-  s <- gets swimlines-  return $ M.lookup name s--setSwimline :: String -> D.NodeId -> Diagram ()-setSwimline name x = do-  modify (\e -> e {swimlines = M.insert name x (swimlines e)})+addNodes :: M.Map (Int,String) D.NodeId -> Diagram ()+addNodes newNodes = do+  modify (\e -> e {nodes = M.union (nodes e) newNodes}) -addHeading :: D.NodeId -> Diagram ()-addHeading x = do-  modify (\e -> e {headings = x:(headings e)})+findNode :: Int -> String -> Diagram (Maybe D.NodeId)+findNode tier line = do+  ns <- gets nodes+  return $ M.lookup (tier, line) ns  ------------------------------------------------ -- Lifting Text.Dot functions to the State monad@@ -215,6 +213,7 @@ same = lift . D.same node = lift . D.node edge f t args = lift $ D.edge f t args+attribute = lift . D.attribute  --------- -- Parser@@ -268,52 +267,9 @@ trim :: String -> String trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace --- Parser tests-newtype Name = Name String-newtype Message = Message String--instance Arbitrary Name where-  arbitrary = liftM Name (listOf' $ elements "abcxyz_банк")--instance Arbitrary Message where-  -- words.unwords trick is needed to prevent Messages which contain only spaces-  arbitrary = liftM ((Message).unwords.words) $ frequency [ (50, listOf' $ elements "abcxyz_->; 123банк")-                                                          -- One special case which i decided to hard-code-                                                          , (1, return "foo -> bar")-                                                          ]--instance Arbitrary Flow where-  arbitrary = frequency [ (10, liftM3 Msg mkName mkName mkMsg)-                        , (5, liftM2 Action mkName mkMsg)-                        ]-    where-      mkName = do Name n <- arbitrary; return n-      mkMsg = do Message m <- arbitrary; return m---- Taken from a unreleased version of quickcheck--- Just added ' to the names---   / Kolmodin-listOf' :: Gen a -> Gen [a]-listOf' gen = sized $ \n ->-  do k <- choose (1,n)-     vectorOf' k gen--vectorOf' :: Int -> Gen a -> Gen [a]-vectorOf' k gen = sequence [ gen | _ <- [1..k] ]-- -- | Print element of the flow diagram as String showFlow :: Flow -> String showFlow (Order sl)   = "order " ++ intercalate " " sl showFlow (Msg f t m)  = unwords [ f, " -> ", t, ":", m ] showFlow (Action s a) = unwords [ s, ":", a ] -prop_reparse :: [Flow] -> Bool-prop_reparse x =-  let txt = unlines $ map showFlow x-      in x == parseFlow "" txt--prop_russian_k :: Bool-prop_russian_k =-  ( parseFlow "a->b" "A->B: клиент" == [Msg "A" "B" "клиент"] ) &&-  ( parseFlow "prod" "продавец -> клиент: подписание контракта, предоставление счета" == [Msg "продавец" "клиент" "подписание контракта, предоставление счета"] )
flow2dot.cabal view
@@ -1,42 +1,79 @@-Name:    flow2dot-Version: 0.9.0.3-License: BSD3-License-File: LICENSE-Author: Dmitry Astapov <dastapov@gmail.com>-Maintainer: Dmitry Astapov <dastapov@gmail.com>-Synopsis: Library and binary to generate sequence/flow diagrams from plain text source-Description: Generates sequence diagrams from textual descriptions with help of Graphviz graph drawing tool.-             Check out <http://adept.linux.kiev.ua:8080/repos/flow2dot/sample.flow> (source)-             and <http://adept.linux.kiev.ua:8080/repos/flow2dot/sample.png> (output).-Homepage: http://adept.linux.kiev.ua:8080/repos/flow2dot-Category: Tool-Stability: beta-Cabal-Version:   >= 1.6--Tested-With:        GHC >=6.10.4-Build-Type:         Simple-Extra-Source-Files: README ChangeLog sample.flow+cabal-version: 1.12 -Library-  hs-source-dirs: .-  Extensions: CPP-  Exposed-Modules: Text.FlowDiagram-  Other-Modules:   Text.Dot-  Build-Depends:   base >=3 && <=5, mtl >= 1.0, containers, QuickCheck, parsec, utf8-string-  if impl(ghc >= 7.0)-    cpp-options: -DNATIVEUTF8-  if impl(ghc < 7.2.1)-    Build-Depends: haskell98+-- This file has been generated from package.yaml by hpack version 0.33.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: 53fe3a5ecd77c08ff5a698606e70baced2b51cb4a2868c18fa8d092a9e48b557 -Executable  flow2dot-  hs-source-dirs: .-  Main-Is:       flow2dot.hs-  Extensions: CPP-  Other-Modules:   Text.Dot-                   Text.FlowDiagram-  if impl(ghc >= 7.0)-    cpp-options: -DNATIVEUTF8+name:           flow2dot+version:        0.9.1+synopsis:       Library and binary to generate sequence/flow diagrams from plain text source+description:    Generates sequence diagrams from textual descriptions with help of Graphviz graph drawing tool.+category:       Tool, Console+stability:      stable+homepage:       https://github.com/adept/flow2dot+bug-reports:    https://github.com/adept/flow2dot/issues+author:         Dmitry Astapov <dastapov@gmail.com>+maintainer:     Dmitry Astapov <dastapov@gmail.com>+license:        BSD3+license-file:   LICENSE+tested-with:    GHC ==6.10.4, GHC ==8.8.3+build-type:     Simple+extra-source-files:+    README.md+    ChangeLog.md+    sample.flow+    sample.png  source-repository head-  type:     darcs-  location: http://patch-tag.com/r/adept/flow2dot+  type: git+  location: https://github.com/adept/flow2dot++library+  exposed-modules:+      Text.FlowDiagram+  other-modules:+      Paths_flow2dot+  hs-source-dirs:+      ./.+  build-depends:+      base >=3 && <=5+    , containers+    , dotgen+    , mtl >=1.0+    , parsec+  default-language: Haskell2010++executable flow2dot+  main-is: flow2dot.hs+  other-modules:+      Text.FlowDiagram+      Paths_flow2dot+  hs-source-dirs:+      ./.+  build-depends:+      base >=3 && <=5+    , containers+    , dotgen+    , flow2dot+    , mtl >=1.0+    , parsec+  default-language: Haskell2010++test-suite flow2dot-test+  type: exitcode-stdio-1.0+  main-is: test.hs+  other-modules:+      Paths_flow2dot+  hs-source-dirs:+      test+  build-depends:+      QuickCheck+    , base >=3 && <=5+    , containers+    , dotgen+    , flow2dot+    , mtl >=1.0+    , parsec+  default-language: Haskell2010
flow2dot.hs view
@@ -13,10 +13,6 @@ module Main where  import Text.FlowDiagram-#ifndef NATIVEUTF8-import System.IO.UTF8 (putStrLn)-import Prelude hiding (putStrLn)-#endif import System.Environment (getArgs)  main :: IO ()
sample.flow view
@@ -1,9 +1,9 @@ order a b c d-a -> b: let's play "catch a ball"!+a -> b: let's toss a frisbee b -> c: i'll pass it along-c: what to do next?+c: where do I throw it next? c -> a-a: "hello d!"-a -> c: d wants to play too!-c -> d: ok, here you go: catch!-d: oops..+a: "Hello d!"+a -> c: "d wants to play too!"+c -> d: "ok, catch!"+d: oops.. frisbee fell
+ sample.png view

binary file changed (absent → 28416 bytes)

+ test/test.hs view
@@ -0,0 +1,52 @@+import Text.FlowDiagram+import Test.QuickCheck+import Control.Monad (liftM, liftM2, liftM3)++-- Parser tests+newtype Name = Name String+newtype Message = Message String+++instance Arbitrary Name where+  arbitrary = liftM Name (listOf' $ elements "abcxyz_банк")++instance Arbitrary Message where+  -- words.unwords trick is needed to prevent Messages which contain only spaces+  arbitrary = liftM ((Message).unwords.words) $ frequency [ (50, listOf' $ elements "abcxyz_->; 123банк")+                                                          -- One special case which i decided to hard-code+                                                          , (1, return "foo -> bar")+                                                          ]++instance Arbitrary Flow where+  arbitrary = frequency [ (10, liftM3 Msg mkName mkName mkMsg)+                        , (5, liftM2 Action mkName mkMsg)+                        ]+    where+      mkName = do Name n <- arbitrary; return n+      mkMsg = do Message m <- arbitrary; return m++-- Taken from a unreleased version of quickcheck+-- Just added ' to the names+--   / Kolmodin+listOf' :: Gen a -> Gen [a]+listOf' gen = sized $ \n ->+  do k <- choose (1,n)+     vectorOf' k gen++vectorOf' :: Int -> Gen a -> Gen [a]+vectorOf' k gen = sequence [ gen | _ <- [1..k] ]+++prop_reparse :: [Flow] -> Bool+prop_reparse x =+  let txt = unlines $ map showFlow x+      in x == parseFlow "" txt++prop_russian_k :: Bool+prop_russian_k =+  ( parseFlow "a->b" "A->B: клиент" == [Msg "A" "B" "клиент"] ) &&+  ( parseFlow "prod" "продавец -> клиент: подписание контракта, предоставление счета" == [Msg "продавец" "клиент" "подписание контракта, предоставление счета"] )++main = do+  quickCheck prop_reparse+  quickCheck prop_russian_k