dataflow 0.3.2.0 → 0.4.0.0
raw patch · 8 files changed
+325/−30 lines, 8 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- DataFlow.Core: Edge :: ID -> ID -> Operation -> Description -> Object
- DataFlow.Core: External :: ID -> Name -> Object
- DataFlow.Core: GenState :: Indent -> IndentNext -> Step -> GenState
- DataFlow.Core: Process :: ID -> Name -> Object
- DataFlow.Core: brackets :: Enclosing
- DataFlow.Core: curlyBrackets :: Enclosing
- DataFlow.Core: data GenState
- DataFlow.Core: incrStep :: Gen ()
- DataFlow.Core: type Enclosing = (Char, Char)
- DataFlow.Core: type Indent = Int
- DataFlow.Core: type IndentNext = Bool
- DataFlow.Core: type Step = Int
- DataFlow.DFD: runDfd :: Diagram -> String
+ DataFlow.Core: Brackets :: Enclosing
+ DataFlow.Core: CurlyBrackets :: Enclosing
+ DataFlow.Core: Flow :: ID -> ID -> Operation -> Description -> Object
+ DataFlow.Core: Function :: ID -> Name -> Object
+ DataFlow.Core: InputOutput :: ID -> Name -> Object
+ DataFlow.Core: data Enclosing
+ DataFlow.Core: evalDiagram :: Gen () -> String
+ DataFlow.DFD: evalDfd :: Diagram -> String
Files
- README.md +68/−0
- dataflow.cabal +20/−5
- example/example.hs +23/−0
- example/example.svg +70/−0
- example/legend.hs +17/−0
- example/legend.svg +46/−0
- src/DataFlow/Core.hs +74/−16
- src/DataFlow/DFD.hs +7/−9
+ README.md view
@@ -0,0 +1,68 @@+# DataFlow++Generate Graphviz documents from a Haskell representation.++```+cabal configure+cabal build+```++## Usage++++The objects supported by DataFlow is:++* `TrustBoundary`+* `InputOutput`+* `Function`+* `Flow`++These are composed in a `Diagram` to get something printable.++For more information see the [Hackage site](https://hackage.haskell.org/package/dataflow).++## Example++```haskell+module Main where++import DataFlow.Core+import DataFlow.DFD++main :: IO ()+main = printDfd $+ Diagram "My Diagram" [+ TrustBoundary "browser" "Browser" [+ Function "webapp" "Webapp"+ ],+ TrustBoundary "aws" "Amazon AWS" [+ Function "server" "Web Server",+ Database "logs" "Logs"+ ],+ InputOutput "analytics" "Google Analytics",++ Flow "webapp" "server" "Request /" "",+ Flow "server" "logs" "Log" "User IP",+ Flow "server" "webapp" "Response" "User Profile",++ Flow "webapp" "analytics" "Log" "Page Navigation"+ ]+```++Then generate your output with dot.++```bash+runhaskell example.hs | dot -Tsvg > example.svg+```++That should generate something like the following.++++## Building the Examples++```bash+cabal install+make -C example+```
dataflow.cabal view
@@ -1,16 +1,31 @@ name: dataflow-version: 0.3.2.0+version: 0.4.0.0 synopsis: Generate Graphviz documents from a Haskell representation. description: Outputs .dot files that can be processed by the dot- command. Currently it only supports the DFD format.+ command. Currently it only supports the DFD output format+ (http://en.wikipedia.org/wiki/Data_flow_diagram). Support+ for a Graphviz-like input format (instead of using the+ data structures in Haskell) is planned.+homepage: https://github.com/owickstrom/dataflow license: MIT license-file: LICENSE author: Oskar Wickström-maintainer: oskar.wickstrom@live.com--- copyright:+maintainer: oskar.wickstrom@gmail.com+copyright: oskar.wickstrom@gmail.com+category: Code Generation, Compiler, Graphs+ build-type: Simple--- extra-source-files:+extra-source-files: README.md,+ LICENSE,+ example/example.hs,+ example/example.svg,+ example/legend.hs,+ example/legend.svg cabal-version: >=1.10++source-repository head+ type: git+ location: git@github.com:owickstrom/dataflow.git library exposed-modules:
+ example/example.hs view
@@ -0,0 +1,23 @@+module Main where++import DataFlow.Core+import DataFlow.DFD++main :: IO ()+main = printDfd $+ Diagram "My Diagram" [+ TrustBoundary "browser" "Browser" [+ Function "webapp" "Webapp"+ ],+ TrustBoundary "aws" "Amazon AWS" [+ Function "server" "Web Server",+ Database "logs" "Logs"+ ],+ InputOutput "analytics" "Google Analytics",++ Flow "webapp" "server" "Request /" "",+ Flow "server" "logs" "Log" "User IP",+ Flow "server" "webapp" "Response" "User Profile",++ Flow "webapp" "analytics" "Log" "Page Navigation"+ ]
+ example/example.svg view
@@ -0,0 +1,70 @@+<?xml version="1.0" encoding="UTF-8" standalone="no"?>+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">+<!-- Generated by graphviz version 2.38.0 (20140413.2041)+ -->+<!-- Title: My Diagram Pages: 1 -->+<svg width="502pt" height="382pt"+ viewBox="0.00 0.00 502.14 382.14" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 378.145)">+<title>My Diagram</title>+<polygon fill="white" stroke="none" points="-4,4 -4,-378.145 498.137,-378.145 498.137,4 -4,4"/>+<text text-anchor="start" x="193.172" y="-355.145" font-family="sans-serif" font-weight="bold" font-size="20.00">My Diagram</text>+<g id="clust1" class="cluster"><title>cluster_browser</title>+<polygon fill="none" stroke="#4d4d4d" stroke-dasharray="5,2" points="8,-110.145 8,-230.145 109.78,-230.145 109.78,-110.145 8,-110.145"/>+<text text-anchor="start" x="40.5524" y="-218.145" font-family="sans-serif" font-size="10.00" fill="#4d4d4d">Browser</text>+</g>+<g id="clust2" class="cluster"><title>cluster_aws</title>+<polygon fill="none" stroke="#4d4d4d" stroke-dasharray="5,2" points="221.346,-190.145 221.346,-338.145 486.137,-338.145 486.137,-190.145 221.346,-190.145"/>+<text text-anchor="start" x="323.078" y="-326.145" font-family="sans-serif" font-size="10.00" fill="#4d4d4d">Amazon AWS</text>+</g>+<!-- webapp -->+<g id="node1" class="node"><title>webapp</title>+<ellipse fill="none" stroke="black" cx="58.8898" cy="-161.145" rx="42.7799" ry="42.7799"/>+<text text-anchor="start" x="32.9406" y="-157.945" font-family="sans-serif" font-weight="bold" font-size="14.00">Webapp</text>+</g>+<!-- server -->+<g id="node2" class="node"><title>server</title>+<ellipse fill="none" stroke="black" cx="285.985" cy="-255.145" rx="56.7776" ry="56.7776"/>+<text text-anchor="start" x="249.153" y="-251.945" font-family="sans-serif" font-weight="bold" font-size="14.00">Web Server</text>+</g>+<!-- webapp->server -->+<g id="edge1" class="edge"><title>webapp->server</title>+<path fill="none" stroke="black" d="M98.8414,-177.411C133.323,-191.81 184.216,-213.063 224.076,-229.709"/>+<polygon fill="black" stroke="black" points="222.86,-232.994 233.436,-233.618 225.557,-226.534 222.86,-232.994"/>+<text text-anchor="middle" x="163.81" y="-224.545" font-family="sans-serif" font-size="12.00">webappserver-></text>+</g>+<!-- analytics -->+<g id="node4" class="node"><title>analytics</title>+<polygon fill="none" stroke="black" stroke-width="2" points="346.274,-120.434 225.695,-120.434 225.695,0.144879 346.274,0.144879 346.274,-120.434"/>+<text text-anchor="start" x="233.84" y="-56.9445" font-family="sans-serif" font-weight="bold" font-size="14.00">Google Analytics</text>+</g>+<!-- webapp->analytics -->+<g id="edge4" class="edge"><title>webapp->analytics</title>+<path fill="none" stroke="black" d="M84.2655,-126.418C94.0477,-114.842 106.228,-102.928 119.78,-95.1445 148.674,-78.5496 184.474,-69.7911 215.353,-65.1818"/>+<polygon fill="black" stroke="black" points="216.188,-68.6001 225.619,-63.7724 215.236,-61.6651 216.188,-68.6001"/>+<text text-anchor="start" x="144.799" y="-110.545" font-family="sans-serif" font-weight="bold" font-size="12.00">(4) Log</text>+<text text-anchor="start" x="119.78" y="-98.5445" font-family="sans-serif" font-size="12.00">Page Navigation</text>+</g>+<!-- server->webapp -->+<g id="edge3" class="edge"><title>server->webapp</title>+<path fill="none" stroke="black" d="M256.434,-206.502C240.39,-181.104 221.004,-153.919 207.84,-147.145 177.542,-131.553 139.128,-135.775 109.036,-143.516"/>+<polygon fill="black" stroke="black" points="107.874,-140.206 99.1738,-146.253 109.746,-146.952 107.874,-140.206"/>+<text text-anchor="start" x="127.792" y="-162.545" font-family="sans-serif" font-weight="bold" font-size="12.00">(3) Response</text>+<text text-anchor="start" x="132.468" y="-150.545" font-family="sans-serif" font-size="12.00">User Profile</text>+</g>+<!-- logs -->+<g id="node3" class="node"><title>logs</title>+<text text-anchor="start" x="434.958" y="-266.945" font-family="sans-serif" font-weight="bold" font-size="14.00">Logs</text>+<polyline fill="none" stroke="black" points="430.137,-258.145 470.137,-258.145 "/>+<polyline fill="none" stroke="black" points="470.137,-282.145 430.137,-282.145 "/>+</g>+<!-- server->logs -->+<g id="edge2" class="edge"><title>server->logs</title>+<path fill="none" stroke="black" d="M342.788,-260.307C365.568,-262.414 391.305,-264.795 411.754,-266.686"/>+<polygon fill="black" stroke="black" points="411.642,-270.191 421.922,-267.627 412.287,-263.221 411.642,-270.191"/>+<text text-anchor="start" x="365.122" y="-280.545" font-family="sans-serif" font-weight="bold" font-size="12.00">(2) Log</text>+<text text-anchor="start" x="364.129" y="-268.545" font-family="sans-serif" font-size="12.00">User IP</text>+</g>+</g>+</svg>
+ example/legend.hs view
@@ -0,0 +1,17 @@+module Main where++import DataFlow.Core+import DataFlow.DFD++main :: IO ()+main = printDfd $+ Diagram "Legend" [+ TrustBoundary "trust" "TrustBoundary" [+ Function "function" "Function",+ Database "database" "Database",+ InputOutput "io" "InputOutput"+ ],+ Flow "function" "io" "Flow" "",+ Flow "function" "database" "Flow" ""+ ]+
+ example/legend.svg view
@@ -0,0 +1,46 @@+<?xml version="1.0" encoding="UTF-8" standalone="no"?>+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">+<!-- Generated by graphviz version 2.38.0 (20140413.2041)+ -->+<!-- Title: Legend Pages: 1 -->+<svg width="297pt" height="284pt"+ viewBox="0.00 0.00 296.55 284.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 280)">+<title>Legend</title>+<polygon fill="white" stroke="none" points="-4,4 -4,-280 292.552,-280 292.552,4 -4,4"/>+<text text-anchor="start" x="110.907" y="-257" font-family="sans-serif" font-weight="bold" font-size="20.00">Legend</text>+<g id="clust1" class="cluster"><title>cluster_trust</title>+<polygon fill="none" stroke="#4d4d4d" stroke-dasharray="5,2" points="8,-8 8,-240 280.552,-240 280.552,-8 8,-8"/>+<text text-anchor="start" x="111.666" y="-228" font-family="sans-serif" font-size="10.00" fill="#4d4d4d">TrustBoundary</text>+</g>+<!-- function -->+<g id="node1" class="node"><title>function</title>+<ellipse fill="none" stroke="black" cx="60.0255" cy="-129" rx="44.051" ry="44.051"/>+<text text-anchor="start" x="33.1773" y="-125.8" font-family="sans-serif" font-weight="bold" font-size="14.00">Function</text>+</g>+<!-- database -->+<g id="node2" class="node"><title>database</title>+<text text-anchor="start" x="198.504" y="-30.8" font-family="sans-serif" font-weight="bold" font-size="14.00">Database</text>+<polyline fill="none" stroke="black" points="193.469,-22 262.469,-22 "/>+<polyline fill="none" stroke="black" points="262.469,-46 193.469,-46 "/>+</g>+<!-- function->database -->+<g id="edge2" class="edge"><title>function->database</title>+<path fill="none" stroke="black" d="M98.6807,-107.443C124.884,-92.4419 159.787,-72.4609 186.571,-57.1275"/>+<polygon fill="black" stroke="black" points="188.379,-60.1253 195.319,-52.1195 184.901,-54.0504 188.379,-60.1253"/>+<text text-anchor="start" x="122.051" y="-96.4" font-family="sans-serif" font-weight="bold" font-size="12.00">(2) Flow</text>+</g>+<!-- io -->+<g id="node3" class="node"><title>io</title>+<polygon fill="none" stroke="black" stroke-width="2" points="272.635,-213.665 183.304,-213.665 183.304,-124.335 272.635,-124.335 272.635,-213.665"/>+<text text-anchor="start" x="191.387" y="-165.8" font-family="sans-serif" font-weight="bold" font-size="14.00">InputOutput</text>+</g>+<!-- function->io -->+<g id="edge1" class="edge"><title>function->io</title>+<path fill="none" stroke="black" d="M102.9,-139.094C124.311,-144.255 150.608,-150.593 173.472,-156.105"/>+<polygon fill="black" stroke="black" points="172.828,-159.55 183.37,-158.49 174.468,-152.745 172.828,-159.55"/>+<text text-anchor="start" x="122.051" y="-157.4" font-family="sans-serif" font-weight="bold" font-size="12.00">(1) Flow</text>+</g>+</g>+</svg>
src/DataFlow/Core.hs view
@@ -1,31 +1,71 @@-module DataFlow.Core where+module DataFlow.Core (+ ID,+ Name,+ Operation,+ Description,+ Diagram(..),+ Object(..),+ Gen,+ evalDiagram,+ write,+ writeln,+ nextStep,+ indent,+ dedent,+ withIndent,+ blank,+ label,+ tag,+ bold,+ table,+ tr,+ td,+ Enclosing(..),+ objectWith,+ attrs+ ) where import Control.Monad.State import Control.Monad.Writer +-- | An identifier corresponding to those in Graphviz. type ID = String+-- | The name of a 'Diagram' or 'Object'. type Name = String+-- | Operation heading. type Operation = String+-- | Operation description. type Description = String -- | The top level diagram. data Diagram = Diagram Name [Object] -- | An object in a diagram.-data Object = External ID Name+data Object =+ -- | A "Input" or "Output" in DFD.+ InputOutput ID Name+ -- | Surrounds other objects, denoting a boundary. | TrustBoundary ID Name [Object]- | Process ID Name+ -- | A \"Function\" in DFD.+ | Function ID Name+ -- | A \"Database\" in DFD. | Database ID Name- | Edge ID ID Operation Description deriving (Show, Eq)+ -- | Describes the flow of data between two objects.+ | Flow ID ID Operation Description deriving (Show, Eq) type Indent = Int type IndentNext = Bool type Step = Int data GenState = GenState Indent IndentNext Step --- | The monad stack for generating output based on Diagram.+-- | The Gen represents some output generator that runs on a 'Diagram'.. type Gen t = WriterT [String] (State GenState) t +-- | Run the 'Gen' and get the output as a 'String'.+evalDiagram :: Gen () -> String+evalDiagram g = concat $ evalState (execWriterT g) (GenState 0 False 0)++-- | Write a string to the output (no linefeed). write :: String -> Gen () write s = do (GenState n indentNext step) <- lift get@@ -34,6 +74,7 @@ else tell [s] put $ GenState n False step +-- | Write a string to the output (with linefeed). writeln :: String -> Gen () writeln s = do write s@@ -43,63 +84,80 @@ incrStep :: Gen () incrStep = modify $ \(GenState n indentNext s') -> GenState n indentNext (s' + 1) +-- | Get the next \"step\" number (the order of flow arrows in the diagram). nextStep :: Gen Int nextStep = do incrStep (GenState _ _ s) <- lift get return s +-- | Increase indent with 2 spaces. indent :: Gen () indent = modify $ \(GenState n indentNext s) -> GenState (n + 2) indentNext s +-- | Decrease indent with 2 spaces. dedent :: Gen () dedent = modify $ \(GenState n indentNext s) -> GenState (n - 2) indentNext s +-- | Indent the output of gen with 2 spaces. withIndent :: Gen () -> Gen () withIndent gen = do indent gen dedent +-- | Write a blank line. blank :: Gen () blank = tell [""] +-- | Write a label with the output of gen as its contents. label :: Gen () -> Gen () label contents = do write "label = <" contents writeln ">;" +-- | Write an HTML tag t with the output of gen as its contents. tag :: String -> String -> Gen () -> Gen () tag t a contents = do write $ "<" ++ t ++ (if null a then "" else " " ++ a) ++ ">" contents write $ "</" ++ t ++ ">" +-- | Write a \<b\> tag surrounding the output of another 'Gen'. bold :: Gen () -> Gen () bold = tag "b" "" +-- | Write a \<table\> tag, with attributes, surrounding the output of+-- another 'Gen'. table :: String -> Gen () -> Gen () table = tag "table" +-- | Write a \<tr\> tag surrounding the output of another 'Gen'. tr :: Gen () -> Gen () tr = tag "tr" "" +-- | Write a \<td\> tag surrounding the output of another 'Gen'. td :: Gen () -> Gen () td = tag "td" "" -type Enclosing = (Char, Char)-brackets, curlyBrackets :: Enclosing-brackets = ('[', ']')-curlyBrackets = ('{', '}')+-- | The enclosing characters in a block.+data Enclosing = Brackets | CurlyBrackets +-- | Write an object with the given 'Enclosing' characters, 'ID' and 'Gen' as+-- its contents. objectWith :: Enclosing -> ID -> Gen () -> Gen ()-objectWith (before, after) id' attributes = do- blank- writeln $ id' ++ " " ++ [before]- withIndent attributes- writeln [after]+objectWith enc id' attributes =+ do+ blank+ writeln $ id' ++ " " ++ before enc+ withIndent attributes+ writeln $ after enc+ where before Brackets = "["+ before CurlyBrackets = "{"+ after Brackets = "]"+ after CurlyBrackets = "}" +-- | Write an attributes declaration for the given 'ID'. attrs :: ID -> String -> Gen ()-attrs id' = objectWith brackets id' . writeln-+attrs id' = objectWith Brackets id' . writeln
src/DataFlow/DFD.hs view
@@ -1,7 +1,5 @@ module DataFlow.DFD where -import Control.Monad.State-import Control.Monad.Writer import DataFlow.Core -- | Type class for types that can be rendered as DFD.@@ -9,7 +7,7 @@ dfd :: t -> Gen () instance DFD Object where- dfd (External id' name) = objectWith brackets id' $ do+ dfd (InputOutput id' name) = objectWith Brackets id' $ do writeln "shape = square;" writeln "style = bold;" label $ bold $ write name@@ -26,11 +24,11 @@ writeln "graph[style = dashed, color=grey30];" writeln "}" - dfd (Process id' name) = objectWith brackets id' $ do+ dfd (Function id' name) = objectWith Brackets id' $ do writeln "shape = circle;" label $ bold $ write name - dfd (Database id' name) = objectWith brackets id' $ do+ dfd (Database id' name) = objectWith Brackets id' $ do label $ table "sides=\"TB\" cellborder=\"0\"" $ tr $@@ -38,7 +36,7 @@ bold $ write name writeln "shape = none;" - dfd (Edge i1 i2 operation description) = do+ dfd (Flow i1 i2 operation description) = do step <- nextStep blank writeln $ i1 ++ " -> " ++ i2 ++ " ["@@ -70,9 +68,9 @@ writeln "}" -- | Generates the DFD output as a String.-runDfd :: Diagram -> String-runDfd diagram = concat $ evalState (execWriterT (dfd diagram)) (GenState 0 False 0)+evalDfd :: Diagram -> String+evalDfd = evalDiagram . dfd -- | Prints the DFD output to stdout. printDfd :: Diagram -> IO ()-printDfd = putStr . runDfd+printDfd = putStr . evalDfd