packages feed

graphviz 2999.7.0.0 → 2999.8.0.0

raw patch · 21 files changed

+1673/−1100 lines, 21 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Data.GraphViz.Types: instance Eq GraphID
- Data.GraphViz.Types: instance Ord GraphID
- Data.GraphViz.Types: instance ParseDot GraphID
- Data.GraphViz.Types: instance PrintDot GraphID
- Data.GraphViz.Types: instance Read GraphID
- Data.GraphViz.Types: instance Show GraphID
- Data.GraphViz.Types.Parsing: character :: Char -> Parse Char
- Data.GraphViz.Types.Parsing: class ParseDot a
- Data.GraphViz.Types.Parsing: commaSep :: (ParseDot a, ParseDot b) => Parse (a, b)
- Data.GraphViz.Types.Parsing: commaSep' :: Parse a -> Parse b -> Parse (a, b)
- Data.GraphViz.Types.Parsing: commaSepUnqt :: (ParseDot a, ParseDot b) => Parse (a, b)
- Data.GraphViz.Types.Parsing: hasString :: String -> Parse Bool
- Data.GraphViz.Types.Parsing: instance (ParseDot a) => ParseDot [a]
- Data.GraphViz.Types.Parsing: instance ParseDot Bool
- Data.GraphViz.Types.Parsing: instance ParseDot Char
- Data.GraphViz.Types.Parsing: instance ParseDot Double
- Data.GraphViz.Types.Parsing: instance ParseDot Int
- Data.GraphViz.Types.Parsing: instance ParseDot Word8
- Data.GraphViz.Types.Parsing: isIntString :: String -> Bool
- Data.GraphViz.Types.Parsing: isNumString :: String -> Bool
- Data.GraphViz.Types.Parsing: newline :: Parse String
- Data.GraphViz.Types.Parsing: newline' :: Parse ()
- Data.GraphViz.Types.Parsing: noneOf :: (Eq a) => [a] -> Parser a a
- Data.GraphViz.Types.Parsing: numString :: Parse String
- Data.GraphViz.Types.Parsing: onlyBool :: Parse Bool
- Data.GraphViz.Types.Parsing: optionalQuoted :: Parse a -> Parse a
- Data.GraphViz.Types.Parsing: optionalQuotedString :: String -> Parse String
- Data.GraphViz.Types.Parsing: orQuote :: Parse Char -> Parse Char
- Data.GraphViz.Types.Parsing: parse :: (ParseDot a) => Parse a
- Data.GraphViz.Types.Parsing: parseAndSpace :: Parse a -> Parse a
- Data.GraphViz.Types.Parsing: parseComma :: Parse Char
- Data.GraphViz.Types.Parsing: parseField :: (ParseDot a) => String -> Parse a
- Data.GraphViz.Types.Parsing: parseFieldBool :: String -> Parse Bool
- Data.GraphViz.Types.Parsing: parseFieldDef :: (ParseDot a) => a -> String -> Parse a
- Data.GraphViz.Types.Parsing: parseFields :: (ParseDot a) => [String] -> Parse a
- Data.GraphViz.Types.Parsing: parseFieldsBool :: [String] -> Parse Bool
- Data.GraphViz.Types.Parsing: parseFieldsDef :: (ParseDot a) => a -> [String] -> Parse a
- Data.GraphViz.Types.Parsing: parseIt :: (ParseDot a) => String -> (a, String)
- Data.GraphViz.Types.Parsing: parseList :: (ParseDot a) => Parse [a]
- Data.GraphViz.Types.Parsing: parseStrictFloat :: Parse Double
- Data.GraphViz.Types.Parsing: parseUnqt :: (ParseDot a) => Parse a
- Data.GraphViz.Types.Parsing: parseUnqtList :: (ParseDot a) => Parse [a]
- Data.GraphViz.Types.Parsing: preProcess :: String -> String
- Data.GraphViz.Types.Parsing: quoteChar :: Char
- Data.GraphViz.Types.Parsing: quotedParse :: Parse a -> Parse a
- Data.GraphViz.Types.Parsing: quotedString :: Parse String
- Data.GraphViz.Types.Parsing: skipToNewline :: Parse String
- Data.GraphViz.Types.Parsing: string :: String -> Parse String
- Data.GraphViz.Types.Parsing: stringBlock :: Parse String
- Data.GraphViz.Types.Parsing: stringRep :: a -> String -> Parse a
- Data.GraphViz.Types.Parsing: stringReps :: a -> [String] -> Parse a
- Data.GraphViz.Types.Parsing: strings :: [String] -> Parse String
- Data.GraphViz.Types.Parsing: tryParseList :: (ParseDot a) => Parse [a]
- Data.GraphViz.Types.Parsing: tryParseList' :: Parse [a] -> Parse [a]
- Data.GraphViz.Types.Parsing: type Parse a = Parser Char a
- Data.GraphViz.Types.Parsing: whitespace :: Parse String
- Data.GraphViz.Types.Parsing: whitespace' :: Parse String
- Data.GraphViz.Types.Printing: addQuotes :: String -> DotCode -> DotCode
- Data.GraphViz.Types.Printing: class PrintDot a
- Data.GraphViz.Types.Printing: commaDel :: (PrintDot a, PrintDot b) => a -> b -> DotCode
- Data.GraphViz.Types.Printing: instance (PrintDot a) => PrintDot [a]
- Data.GraphViz.Types.Printing: instance PrintDot Bool
- Data.GraphViz.Types.Printing: instance PrintDot Char
- Data.GraphViz.Types.Printing: instance PrintDot Double
- Data.GraphViz.Types.Printing: instance PrintDot Int
- Data.GraphViz.Types.Printing: instance PrintDot Word8
- Data.GraphViz.Types.Printing: listToDot :: (PrintDot a) => [a] -> DotCode
- Data.GraphViz.Types.Printing: printField :: (PrintDot a) => String -> a -> DotCode
- Data.GraphViz.Types.Printing: printIt :: (PrintDot a) => a -> String
- Data.GraphViz.Types.Printing: renderDot :: DotCode -> String
- Data.GraphViz.Types.Printing: toDot :: (PrintDot a) => a -> DotCode
- Data.GraphViz.Types.Printing: type DotCode = Doc
- Data.GraphViz.Types.Printing: unqtDot :: (PrintDot a) => a -> DotCode
- Data.GraphViz.Types.Printing: unqtListToDot :: (PrintDot a) => [a] -> DotCode
- Data.GraphViz.Types.Printing: wrap :: DotCode -> DotCode -> DotCode -> DotCode
+ Data.GraphViz: addEdgeIDs :: (Graph gr) => gr a b -> gr a (EdgeID b)
+ Data.GraphViz: augmentGraph :: (Graph gr, DotRepr dg Node) => gr a (EdgeID b) -> dg Node -> gr (AttributeNode a) (AttributeEdge b)
+ Data.GraphViz: data EdgeID b
+ Data.GraphViz: dotAttributes :: (Graph gr, DotRepr dg Node) => Bool -> gr a (EdgeID b) -> dg Node -> IO (gr (AttributeNode a) (AttributeEdge b))
+ Data.GraphViz: dotToGraph :: (DotRepr dg Node, Graph gr) => dg Node -> gr Attributes Attributes
+ Data.GraphViz: instance (Eq b) => Eq (EdgeID b)
+ Data.GraphViz: instance (Ord b) => Ord (EdgeID b)
+ Data.GraphViz: instance (Show b) => Show (EdgeID b)
+ Data.GraphViz: preview :: (Ord b, Graph gr) => gr a b -> IO ()
+ Data.GraphViz: setEdgeComment :: (LEdge b -> Attributes) -> (LEdge (EdgeID b) -> Attributes)
+ Data.GraphViz.Commands: maybeErr :: Either String a -> RunResult
+ Data.GraphViz.Parsing: character :: Char -> Parse Char
+ Data.GraphViz.Parsing: class ParseDot a
+ Data.GraphViz.Parsing: commaSep :: (ParseDot a, ParseDot b) => Parse (a, b)
+ Data.GraphViz.Parsing: commaSep' :: Parse a -> Parse b -> Parse (a, b)
+ Data.GraphViz.Parsing: commaSepUnqt :: (ParseDot a, ParseDot b) => Parse (a, b)
+ Data.GraphViz.Parsing: hasString :: String -> Parse Bool
+ Data.GraphViz.Parsing: instance (ParseDot a) => ParseDot [a]
+ Data.GraphViz.Parsing: instance ParseDot Bool
+ Data.GraphViz.Parsing: instance ParseDot Char
+ Data.GraphViz.Parsing: instance ParseDot Double
+ Data.GraphViz.Parsing: instance ParseDot Int
+ Data.GraphViz.Parsing: instance ParseDot Word8
+ Data.GraphViz.Parsing: isIntString :: String -> Bool
+ Data.GraphViz.Parsing: isNumString :: String -> Bool
+ Data.GraphViz.Parsing: newline :: Parse String
+ Data.GraphViz.Parsing: newline' :: Parse ()
+ Data.GraphViz.Parsing: noneOf :: (Eq a) => [a] -> Parser a a
+ Data.GraphViz.Parsing: numString :: Parse String
+ Data.GraphViz.Parsing: onlyBool :: Parse Bool
+ Data.GraphViz.Parsing: optionalQuoted :: Parse a -> Parse a
+ Data.GraphViz.Parsing: optionalQuotedString :: String -> Parse String
+ Data.GraphViz.Parsing: orQuote :: Parse Char -> Parse Char
+ Data.GraphViz.Parsing: parse :: (ParseDot a) => Parse a
+ Data.GraphViz.Parsing: parseAndSpace :: Parse a -> Parse a
+ Data.GraphViz.Parsing: parseComma :: Parse Char
+ Data.GraphViz.Parsing: parseField :: (ParseDot a) => String -> Parse a
+ Data.GraphViz.Parsing: parseFieldBool :: String -> Parse Bool
+ Data.GraphViz.Parsing: parseFieldDef :: (ParseDot a) => a -> String -> Parse a
+ Data.GraphViz.Parsing: parseFields :: (ParseDot a) => [String] -> Parse a
+ Data.GraphViz.Parsing: parseFieldsBool :: [String] -> Parse Bool
+ Data.GraphViz.Parsing: parseFieldsDef :: (ParseDot a) => a -> [String] -> Parse a
+ Data.GraphViz.Parsing: parseIt :: (ParseDot a) => String -> (a, String)
+ Data.GraphViz.Parsing: parseList :: (ParseDot a) => Parse [a]
+ Data.GraphViz.Parsing: parseStrictFloat :: Parse Double
+ Data.GraphViz.Parsing: parseUnqt :: (ParseDot a) => Parse a
+ Data.GraphViz.Parsing: parseUnqtList :: (ParseDot a) => Parse [a]
+ Data.GraphViz.Parsing: preProcess :: String -> String
+ Data.GraphViz.Parsing: quoteChar :: Char
+ Data.GraphViz.Parsing: quotedParse :: Parse a -> Parse a
+ Data.GraphViz.Parsing: quotedString :: Parse String
+ Data.GraphViz.Parsing: skipToNewline :: Parse String
+ Data.GraphViz.Parsing: string :: String -> Parse String
+ Data.GraphViz.Parsing: stringBlock :: Parse String
+ Data.GraphViz.Parsing: stringRep :: a -> String -> Parse a
+ Data.GraphViz.Parsing: stringReps :: a -> [String] -> Parse a
+ Data.GraphViz.Parsing: strings :: [String] -> Parse String
+ Data.GraphViz.Parsing: tryParseList :: (ParseDot a) => Parse [a]
+ Data.GraphViz.Parsing: tryParseList' :: Parse [a] -> Parse [a]
+ Data.GraphViz.Parsing: type Parse a = Parser Char a
+ Data.GraphViz.Parsing: whitespace :: Parse String
+ Data.GraphViz.Parsing: whitespace' :: Parse String
+ Data.GraphViz.Printing: addQuotes :: String -> DotCode -> DotCode
+ Data.GraphViz.Printing: class PrintDot a
+ Data.GraphViz.Printing: commaDel :: (PrintDot a, PrintDot b) => a -> b -> DotCode
+ Data.GraphViz.Printing: instance (PrintDot a) => PrintDot [a]
+ Data.GraphViz.Printing: instance PrintDot Bool
+ Data.GraphViz.Printing: instance PrintDot Char
+ Data.GraphViz.Printing: instance PrintDot Double
+ Data.GraphViz.Printing: instance PrintDot Int
+ Data.GraphViz.Printing: instance PrintDot Word8
+ Data.GraphViz.Printing: listToDot :: (PrintDot a) => [a] -> DotCode
+ Data.GraphViz.Printing: printField :: (PrintDot a) => String -> a -> DotCode
+ Data.GraphViz.Printing: printIt :: (PrintDot a) => a -> String
+ Data.GraphViz.Printing: renderDot :: DotCode -> String
+ Data.GraphViz.Printing: toDot :: (PrintDot a) => a -> DotCode
+ Data.GraphViz.Printing: type DotCode = Doc
+ Data.GraphViz.Printing: unqtDot :: (PrintDot a) => a -> DotCode
+ Data.GraphViz.Printing: unqtListToDot :: (PrintDot a) => [a] -> DotCode
+ Data.GraphViz.Printing: wrap :: DotCode -> DotCode -> DotCode -> DotCode
+ Data.GraphViz.Types: class (PrintDot (dg n), ParseDot (dg n)) => DotRepr dg n
+ Data.GraphViz.Types: graphIsDirected :: (DotRepr dg n) => dg n -> Bool
+ Data.GraphViz.Types: instance (PrintDot n, ParseDot n) => DotRepr DotGraph n
+ Data.GraphViz.Types.Generalised: DE :: (DotEdge a) -> GDotStatement a
+ Data.GraphViz.Types.Generalised: DN :: (DotNode a) -> GDotStatement a
+ Data.GraphViz.Types.Generalised: Dbl :: Double -> GraphID
+ Data.GraphViz.Types.Generalised: DotEdge :: a -> a -> Bool -> Attributes -> DotEdge a
+ Data.GraphViz.Types.Generalised: DotNode :: a -> Attributes -> DotNode a
+ Data.GraphViz.Types.Generalised: EdgeAttrs :: Attributes -> GlobalAttributes
+ Data.GraphViz.Types.Generalised: GA :: GlobalAttributes -> GDotStatement a
+ Data.GraphViz.Types.Generalised: GDotGraph :: Bool -> Bool -> Maybe GraphID -> GDotStatements a -> GDotGraph a
+ Data.GraphViz.Types.Generalised: GDotSG :: Bool -> Maybe GraphID -> GDotStatements a -> GDotSubGraph a
+ Data.GraphViz.Types.Generalised: GraphAttrs :: Attributes -> GlobalAttributes
+ Data.GraphViz.Types.Generalised: HTML :: URL -> GraphID
+ Data.GraphViz.Types.Generalised: Int :: Int -> GraphID
+ Data.GraphViz.Types.Generalised: NodeAttrs :: Attributes -> GlobalAttributes
+ Data.GraphViz.Types.Generalised: SG :: (GDotSubGraph a) -> GDotStatement a
+ Data.GraphViz.Types.Generalised: Str :: String -> GraphID
+ Data.GraphViz.Types.Generalised: attrs :: GlobalAttributes -> Attributes
+ Data.GraphViz.Types.Generalised: data DotEdge a
+ Data.GraphViz.Types.Generalised: data DotNode a
+ Data.GraphViz.Types.Generalised: data GDotGraph a
+ Data.GraphViz.Types.Generalised: data GDotStatement a
+ Data.GraphViz.Types.Generalised: data GDotSubGraph a
+ Data.GraphViz.Types.Generalised: data GlobalAttributes
+ Data.GraphViz.Types.Generalised: data GraphID
+ Data.GraphViz.Types.Generalised: directedEdge :: DotEdge a -> Bool
+ Data.GraphViz.Types.Generalised: edgeAttributes :: DotEdge a -> Attributes
+ Data.GraphViz.Types.Generalised: edgeFromNodeID :: DotEdge a -> a
+ Data.GraphViz.Types.Generalised: edgeToNodeID :: DotEdge a -> a
+ Data.GraphViz.Types.Generalised: gDirectedGraph :: GDotGraph a -> Bool
+ Data.GraphViz.Types.Generalised: gGraphID :: GDotGraph a -> Maybe GraphID
+ Data.GraphViz.Types.Generalised: gGraphStatements :: GDotGraph a -> GDotStatements a
+ Data.GraphViz.Types.Generalised: gIsCluster :: GDotSubGraph a -> Bool
+ Data.GraphViz.Types.Generalised: gStrictGraph :: GDotGraph a -> Bool
+ Data.GraphViz.Types.Generalised: gSubGraphID :: GDotSubGraph a -> Maybe GraphID
+ Data.GraphViz.Types.Generalised: gSubGraphStmts :: GDotSubGraph a -> GDotStatements a
+ Data.GraphViz.Types.Generalised: generaliseDotGraph :: DotGraph a -> GDotGraph a
+ Data.GraphViz.Types.Generalised: instance (Eq a) => Eq (GDotGraph a)
+ Data.GraphViz.Types.Generalised: instance (Eq a) => Eq (GDotStatement a)
+ Data.GraphViz.Types.Generalised: instance (Eq a) => Eq (GDotSubGraph a)
+ Data.GraphViz.Types.Generalised: instance (Ord a) => Ord (GDotGraph a)
+ Data.GraphViz.Types.Generalised: instance (Ord a) => Ord (GDotStatement a)
+ Data.GraphViz.Types.Generalised: instance (Ord a) => Ord (GDotSubGraph a)
+ Data.GraphViz.Types.Generalised: instance (ParseDot a) => ParseDot (GDotGraph a)
+ Data.GraphViz.Types.Generalised: instance (ParseDot a) => ParseDot (GDotStatement a)
+ Data.GraphViz.Types.Generalised: instance (ParseDot a) => ParseDot (GDotSubGraph a)
+ Data.GraphViz.Types.Generalised: instance (PrintDot a) => PrintDot (GDotGraph a)
+ Data.GraphViz.Types.Generalised: instance (PrintDot a) => PrintDot (GDotStatement a)
+ Data.GraphViz.Types.Generalised: instance (PrintDot a) => PrintDot (GDotSubGraph a)
+ Data.GraphViz.Types.Generalised: instance (PrintDot n, ParseDot n) => DotRepr GDotGraph n
+ Data.GraphViz.Types.Generalised: instance (Read a) => Read (GDotGraph a)
+ Data.GraphViz.Types.Generalised: instance (Read a) => Read (GDotStatement a)
+ Data.GraphViz.Types.Generalised: instance (Read a) => Read (GDotSubGraph a)
+ Data.GraphViz.Types.Generalised: instance (Show a) => Show (GDotGraph a)
+ Data.GraphViz.Types.Generalised: instance (Show a) => Show (GDotStatement a)
+ Data.GraphViz.Types.Generalised: instance (Show a) => Show (GDotSubGraph a)
+ Data.GraphViz.Types.Generalised: instance Functor GDotGraph
+ Data.GraphViz.Types.Generalised: instance Functor GDotStatement
+ Data.GraphViz.Types.Generalised: instance Functor GDotSubGraph
+ Data.GraphViz.Types.Generalised: nodeAttributes :: DotNode a -> Attributes
+ Data.GraphViz.Types.Generalised: nodeID :: DotNode a -> a
+ Data.GraphViz.Types.Generalised: type GDotStatements a = Seq (GDotStatement a)
- Data.GraphViz: prettyPrint :: (PrintDot a) => DotGraph a -> IO String
+ Data.GraphViz: prettyPrint :: (DotRepr dg n) => dg n -> IO String
- Data.GraphViz: prettyPrint' :: (PrintDot a) => DotGraph a -> String
+ Data.GraphViz: prettyPrint' :: (DotRepr dg n) => dg n -> String
- Data.GraphViz.Commands: addExtension :: (GraphvizOutput -> FilePath -> IO a) -> GraphvizOutput -> FilePath -> IO a
+ Data.GraphViz.Commands: addExtension :: (GraphvizOutput -> FilePath -> a) -> GraphvizOutput -> FilePath -> a
- Data.GraphViz.Commands: commandFor :: DotGraph a -> GraphvizCommand
+ Data.GraphViz.Commands: commandFor :: (DotRepr dg n) => dg n -> GraphvizCommand
- Data.GraphViz.Commands: graphvizWithHandle :: (PrintDot n, Show a) => GraphvizCommand -> DotGraph n -> GraphvizOutput -> (Handle -> IO a) -> IO (Either String a)
+ Data.GraphViz.Commands: graphvizWithHandle :: (DotRepr dg n, Show a) => GraphvizCommand -> dg n -> GraphvizOutput -> (Handle -> IO a) -> IO (Either String a)
- Data.GraphViz.Commands: runGraphviz :: (PrintDot n) => DotGraph n -> GraphvizOutput -> FilePath -> IO RunResult
+ Data.GraphViz.Commands: runGraphviz :: (DotRepr dg n) => dg n -> GraphvizOutput -> FilePath -> IO (Either String FilePath)
- Data.GraphViz.Commands: runGraphvizCanvas :: (PrintDot n) => GraphvizCommand -> DotGraph n -> GraphvizCanvas -> IO RunResult
+ Data.GraphViz.Commands: runGraphvizCanvas :: (DotRepr dg n) => GraphvizCommand -> dg n -> GraphvizCanvas -> IO RunResult
- Data.GraphViz.Commands: runGraphvizCanvas' :: (PrintDot n) => DotGraph n -> GraphvizCanvas -> IO RunResult
+ Data.GraphViz.Commands: runGraphvizCanvas' :: (DotRepr dg n) => dg n -> GraphvizCanvas -> IO RunResult
- Data.GraphViz.Commands: runGraphvizCommand :: (PrintDot n) => GraphvizCommand -> DotGraph n -> GraphvizOutput -> FilePath -> IO RunResult
+ Data.GraphViz.Commands: runGraphvizCommand :: (DotRepr dg n) => GraphvizCommand -> dg n -> GraphvizOutput -> FilePath -> IO (Either String FilePath)
- Data.GraphViz.Types: graphEdges :: DotGraph a -> [DotEdge a]
+ Data.GraphViz.Types: graphEdges :: (DotRepr dg n) => dg n -> [DotEdge n]
- Data.GraphViz.Types: graphNodes :: DotGraph a -> [DotNode a]
+ Data.GraphViz.Types: graphNodes :: (DotRepr dg n) => dg n -> [DotNode n]
- Data.GraphViz.Types: makeStrict :: DotGraph a -> DotGraph a
+ Data.GraphViz.Types: makeStrict :: (DotRepr dg n) => dg n -> dg n
- Data.GraphViz.Types: parseDotGraph :: (ParseDot a) => String -> DotGraph a
+ Data.GraphViz.Types: parseDotGraph :: (DotRepr dg n) => String -> dg n
- Data.GraphViz.Types: printDotGraph :: (PrintDot a) => DotGraph a -> String
+ Data.GraphViz.Types: printDotGraph :: (DotRepr dg n) => dg n -> String
- Data.GraphViz.Types: setID :: GraphID -> DotGraph a -> DotGraph a
+ Data.GraphViz.Types: setID :: (DotRepr dg n) => GraphID -> dg n -> dg n

Files

Changelog view
@@ -1,3 +1,33 @@+Changes in 2999.8.0.0+=====================++* Added support for generalised DotGraphs; this optional+  representation removes the restriction of ordering of Dot+  statements.  This allows for greater flexibility of how to specify+  Dot code.  As an offshoot from this, most relevant functions now+  utilise a new DotRepr class that work with both DotGraphs and the+  new GDotGraphs; this shouldn't affect any code already in use.++* With the prompting of Noam Lewis, the augmentation functions have+  been revamped in two ways:++  - Now contains support for multiple edges.++  - The ability to perform "manual" augmentation if greater control is+    desired.++* Add a preview function to quickly render and visualise an FGL graph+  using the Xlib canvas.++* Added a pseudo-inverse of the FGL -> Dot functions (a graph is+  created, but the original node and edge labels are unrecoverable).++* The Printing and Parsing modules have been moved (from+  Data.GraphViz.Types to Data.GraphViz).++* Reworked file-generating commands such that they return the filename+  of the created file if successful.+ Changes in 2999.7.0.0 ===================== 
Data/GraphViz.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE FlexibleContexts #-}+ {- |    Module      : Data.GraphViz    Description : Graphviz bindings for Haskell.@@ -13,39 +15,45 @@    Information about Graphviz and the Dot language can be found at:    <http://graphviz.org/> -   Commands for converting graphs to Dot format have two options: one-   in which the user specifies whether the graph is directed or-   undirected, and a primed version which attempts to automatically-   infer if the graph is directed or not.  Note that these conversion-   functions assume that undirected graphs have every edge being-   duplicated (or at least that if there exists an edge from /n1/ to-   /n2/, then /n1 <= n2/).  -} module Data.GraphViz     ( -- * Conversion from graphs to /Dot/ format.+      -- $conversion       graphToDot     , graphToDot'       -- ** Conversion with support for clusters.     , NodeCluster(..)     , clusterGraphToDot     , clusterGraphToDot'-      -- ** Utility functions-    , prettyPrint-    , prettyPrint'-      -- * Passing the graph through Graphviz.+      -- ** Pseudo-inverse conversion.+    , dotToGraph+      -- * Graph augmentation.+      -- $augment       -- ** Type aliases for @Node@ and @Edge@ labels.     , AttributeNode     , AttributeEdge-      -- ** For normal graphs.+      -- ** Customisable augmentation.     , graphToGraph     , graphToGraph'-    , dotizeGraph-    , dotizeGraph'-      -- ** For clustered graphs.     , clusterGraphToGraph     , clusterGraphToGraph'+      -- ** Quick augmentation.+      -- $quickAugment+    , dotizeGraph+    , dotizeGraph'     , dotizeClusterGraph     , dotizeClusterGraph'+      -- ** Manual augmentation.+      -- $manualAugment+    , EdgeID+    , addEdgeIDs+    , setEdgeComment+    , dotAttributes+    , augmentGraph+      -- * Utility functions+    , prettyPrint+    , prettyPrint'+    , preview       -- * Re-exporting other modules.     , module Data.GraphViz.Types     , module Data.GraphViz.Attributes@@ -54,17 +62,18 @@  import Data.GraphViz.Types import Data.GraphViz.Types.Clustering+import Data.GraphViz.Util(uniq, uniqBy) import Data.GraphViz.Attributes import Data.GraphViz.Commands-import Data.GraphViz.Types.Printing(PrintDot)  import Data.Graph.Inductive.Graph import qualified Data.Set as Set import Control.Arrow((&&&))-import Data.Maybe(mapMaybe, fromJust)+import Data.Maybe(mapMaybe, isNothing) import qualified Data.Map as Map import Control.Monad(liftM) import System.IO.Unsafe(unsafePerformIO)+import Control.Concurrent(forkIO)  -- ----------------------------------------------------------------------------- @@ -83,8 +92,24 @@  -- ----------------------------------------------------------------------------- --- | Convert a graph to Graphviz's /Dot/ format.  The 'Bool' value is---   'True' for directed graphs, 'False' otherwise.+{- $conversion+   There are various functions available for converting 'Graph's to+   Graphviz's /Dot/ format (represented using the 'DotGraph' type).+   There are two main types: converting plain graphs and converting+   /clustered/ graphs (where the graph cluster that a particular 'Node'+   belongs to is determined by its label).++   These functions have two versions: one in which the user specifies+   whether the graph is directed or undirected (with a 'Bool' value of+   'True' indicating that the graph is directed), and a primed version+   which attempts to automatically infer if the graph is directed or not.+   Note that these conversion functions assume that undirected graphs+   have every edge being duplicated (or at least that if there exists an+   edge from /n1/ to /n2/, then /n1 <= n2/; if /n1 > n2/ then it is+   removed for an undirected graph).+-}++-- | Convert a graph to Graphviz's /Dot/ format. graphToDot :: (Graph gr) => Bool -> gr a b -> [GlobalAttributes]               -> (LNode a -> Attributes) -> (LEdge b -> Attributes)               -> DotGraph Node@@ -103,10 +128,32 @@                      -> DotGraph Node graphToDot' graph = graphToDot (isDirected graph) graph +-- | A pseudo-inverse to 'graphToDot' and 'graphToDot''; \"pseudo\" in+--   the sense that the original node and edge labels aren't able to+--   be reconstructed.+dotToGraph    :: (DotRepr dg Node, Graph gr) => dg Node+                 -> gr Attributes Attributes+dotToGraph dg = mkGraph ns' es+  where+    -- Applying uniqBy just in case...+    ns = uniqBy fst . map toLN $ graphNodes dg+    es = concatMap toLE $ graphEdges dg+    -- Need to ensure that for some reason there are node IDs in an+    -- edge but not on their own.+    nSet = Set.fromList $ map fst ns+    nEs = map (flip (,) [])+          . uniq+          . filter (flip Set.notMember nSet)+          $ concatMap (\(n1,n2,_) -> [n1,n2]) es+    ns' = ns ++ nEs+    -- Conversion functions+    toLN (DotNode n as) = (n,as)+    toLE (DotEdge f t d as) = (if d then id else (:) (t,f,as)) [(f,t,as)]++ -- | Convert a graph to /Dot/ format, using the specified clustering function --   to group nodes into clusters. --   Clusters can be nested to arbitrary depth.---   The 'Bool' argument is 'True' for directed graphs, 'False' otherwise. clusterGraphToDot :: (Ord c, Graph gr) => Bool -> gr a b                      -> [GlobalAttributes] -> (LNode a -> NodeCluster c l)                      -> (c -> Maybe GraphID) -> (c -> [GlobalAttributes])@@ -147,57 +194,53 @@  -- ----------------------------------------------------------------------------- +{- $augment+   The following functions provide support for passing a 'Graph'+   through the appropriate 'GraphvizCommand' to augment the 'Graph' by+   adding positional information, etc.++   Please note that there are some restrictions on this: to enable+   support for multiple edges between two nodes, the 'Comment'+   'Attribute' is used to provide a unique identifier for each edge.  As+   such, you should /not/ set this 'Attribute' for any 'LEdge'.++   For unprimed functions, the 'Bool' argument is 'True' for directed+   graphs, 'False' otherwise; for the primed versions of functions the+   directionality of the graph is automatically inferred.  Directed+   graphs are passed through 'Dot', and undirected graphs through+   'Neato'.+   Note that the reason these functions do not have 'unsafePerformIO'+   applied to them is because if you set a global 'Attribute' of:++   @+    'Start' ('StartStyle' 'RandomStyle')+   @++   then it will not necessarily be referentially transparent (ideally,+   no matter what the seed is, it will still eventually be drawn to the+   same optimum, but this can't be guaranteed).  As such, if you are sure+   that you're not using such an 'Attribute', then you should be able to+   use 'unsafePerformIO' directly in your own code.+-}+ type AttributeNode a = (Attributes, a) type AttributeEdge b = (Attributes, b)  -- | Run the appropriate Graphviz command on the graph to get --   positional information and then combine that information back---   into the original graph.  Note that for the edge information to---   be parsed properly when using multiple edges, each edge between---   two nodes needs to have a unique label.------   The 'Bool' argument is 'True' for directed graphs, 'False'---   otherwise.  Directed graphs are passed through /dot/, and---   undirected graphs through /neato/.+--   into the original graph. graphToGraph :: (Graph gr) => Bool -> gr a b -> [GlobalAttributes]                 -> (LNode a -> Attributes) -> (LEdge b -> Attributes)                 -> IO (gr (AttributeNode a) (AttributeEdge b)) graphToGraph isDir gr gAttributes fmtNode fmtEdge-    = dotAttributes isDir gr dot-    where-      dot = graphToDot isDir gr gAttributes fmtNode fmtEdge--dotAttributes :: (Graph gr) => Bool -> gr a b -> DotGraph Node-                 -> IO (gr (AttributeNode a) (AttributeEdge b))-dotAttributes isDir gr dot-    = do (Right output) <- graphvizWithHandle command-                                              dot-                                              DotOutput-                                              hGetContents'-         return $ rebuildGraphWithAttributes output+    = dotAttributes isDir gr' dot     where-      command = if isDir then dirCommand else undirCommand-      rebuildGraphWithAttributes dotResult =  mkGraph lnodes ledges-          where-            lnodes = map (\(n, l) -> (n, (nodeMap Map.! n, l)))-                     $ labNodes gr-            ledges = map createEdges $ labEdges gr-            createEdges (f, t, l) = if isDir || f <= t-                                    then (f, t, getLabel (f,t))-                                    else (f, t, getLabel (t,f))-                where-                  getLabel c = (fromJust $ Map.lookup c edgeMap, l)-            g' = parseDotGraph dotResult-            ns = graphNodes g'-            es = graphEdges g'-            nodeMap = Map.fromList $ map (nodeID &&& nodeAttributes) ns-            edgeMap = Map.fromList $ map ( (edgeFromNodeID &&& edgeToNodeID)-                                           &&& edgeAttributes) es+      dot = graphToDot isDir gr' gAttributes fmtNode (setEdgeComment fmtEdge)+      gr' = addEdgeIDs gr  -- | Run the appropriate Graphviz command on the graph to get --   positional information and then combine that information back --   into the original graph.--- --   Graph direction is automatically inferred. graphToGraph'    :: (Ord b, Graph gr) => gr a b -> [GlobalAttributes]                     -> (LNode a -> Attributes) -> (LEdge b -> Attributes)@@ -206,27 +249,22 @@  -- | Run the appropriate Graphviz command on the clustered graph to --   get positional information and then combine that information back---   into the original graph.  Note that for the edge information to---   be parsed properly when using multiple edges, each edge between---   two nodes needs to have a unique label.------   The 'Bool' argument is 'True' for directed graphs, 'False'---   otherwise.  Directed graphs are passed through /dot/, and---   undirected graphs through /neato/.+--   into the original graph. clusterGraphToGraph :: (Ord c, Graph gr) => Bool -> gr a b                        -> [GlobalAttributes] -> (LNode a -> NodeCluster c l)                        -> (c -> Maybe GraphID) -> (c -> [GlobalAttributes])                        -> (LNode l -> Attributes) -> (LEdge b -> Attributes)                        -> IO (gr (AttributeNode a) (AttributeEdge b)) clusterGraphToGraph isDir gr gAtts clBy cID fmtClust fmtNode fmtEdge-    = dotAttributes isDir gr dot+    = dotAttributes isDir gr' dot     where-      dot = clusterGraphToDot isDir gr gAtts clBy cID fmtClust fmtNode fmtEdge+      dot = clusterGraphToDot isDir gr' gAtts clBy cID fmtClust fmtNode fmtEdge'+      gr' = addEdgeIDs gr+      fmtEdge' = setEdgeComment fmtEdge  -- | Run the appropriate Graphviz command on the clustered graph to --   get positional information and then combine that information back --   into the original graph.--- --   Graph direction is automatically inferred. clusterGraphToGraph'    :: (Ord b, Ord c, Graph gr) => gr a b                            -> [GlobalAttributes] -> (LNode a -> NodeCluster c l)@@ -235,15 +273,16 @@                            -> IO (gr (AttributeNode a) (AttributeEdge b)) clusterGraphToGraph' gr = clusterGraphToGraph (isDirected gr) gr +-- ----------------------------------------------------------------------------- --- | Pass the graph through 'graphToGraph' with no 'Attribute's.  This---   is an @'IO'@ action, however since the state doesn't change it's---   safe to use 'unsafePerformIO' to convert this to a normal---   function.------   The 'Bool' argument is 'True' for directed graphs, 'False'---   otherwise.  Directed graphs are passed through /dot/, and---   undirected graphs through /neato/.+{- $quickAugment+   This section contains convenience functions for quick-and-dirty+   augmentation of graphs.  No 'Attribute's are applied, and+   'unsafePerformIO' is used to make these normal functions.  Note that+   this should be safe since these should be referentially transparent.+-}++-- | Pass the graph through 'graphToGraph' with no 'Attribute's. dotizeGraph         :: (Graph gr) => Bool -> gr a b                        -> gr (AttributeNode a) (AttributeEdge b) dotizeGraph isDir g = unsafePerformIO@@ -252,24 +291,14 @@       gAttrs = []       noAttrs = const [] --- | Pass the graph through 'graphToGraph' with no 'Attribute's.  This---   is an @'IO'@ action, however since the state doesn't change it's---   safe to use 'unsafePerformIO' to convert this to a normal---   function.---+-- | Pass the graph through 'graphToGraph' with no 'Attribute's. --   The graph direction is automatically inferred. dotizeGraph'   :: (Graph gr, Ord b) => gr a b                   -> gr (AttributeNode a) (AttributeEdge b) dotizeGraph' g = dotizeGraph (isDirected g) g  -- | Pass the clustered graph through 'clusterGraphToGraph' with no---   'Attribute's.  This is an @'IO'@ action, however since the state---   doesn't change it's safe to use 'unsafePerformIO' to convert this---   to a normal function.------   The 'Bool' argument is 'True' for directed graphs, 'False'---   otherwise.  Directed graphs are passed through /dot/, and---   undirected graphs through /neato/.+--   'Attribute's. dotizeClusterGraph                 :: (Ord c, Graph gr) => Bool -> gr a b                                       -> (LNode a -> NodeCluster c l)                                       -> gr (AttributeNode a) (AttributeEdge b)@@ -284,11 +313,8 @@       cAttrs = const gAttrs       noAttrs = const [] --- | Pass the clustered graph through 'graphToGraph' with no---   'Attribute's.  This is an @'IO'@ action, however since the state---   doesn't change it's safe to use 'unsafePerformIO' to convert this---   to a normal function.---+-- | Pass the clustered graph through 'clusterGraphToGraph' with no+--   'Attribute's. --   The graph direction is automatically inferred. dotizeClusterGraph'   :: (Ord b, Ord c, Graph gr) => gr a b                          -> (LNode a -> NodeCluster c l)@@ -296,28 +322,136 @@ dotizeClusterGraph' g = dotizeClusterGraph (isDirected g) g  -- -----------------------------------------------------------------------------++{- $manualAugment++   This section allows you to manually augment graphs by providing+   fine-grained control over the augmentation process (the standard+   augmentation functions compose these together).  Possible reasons for+   manual augmentation are:++   * Gain access to the intermediary 'DotRepr' used.++   * Convert the default 'DotGraph' to a @GDotGraph@ (found in+     "Data.GraphViz.Types.Generalised") so as to have greater control over+     the generated Dot code.++   * Use a specific 'GraphvizCommand' rather than the default.++   Note that whilst these functions provide you with more control, you+   must be careful how you use them: if you use the wrong 'DotRepr' for+   a 'Graph', then the behaviour of 'augmentGraph' (and all functions+   that use it) is undefined.  The main point is to make sure that the+   defined 'DotNode' and 'DotEdge' values aren't removed (or their ID+   values - or the 'Comment' 'Attribute' for the 'DotEdge's - altered) to+   ensure that it is possible to match up the nodes and edges in the+   'Graph' with those in the 'DotRepr'.++-}++-- | Used to augment an edge label with a unique identifier.+data EdgeID b = EID { eID  :: String+                    , eLbl :: b+                    }+              deriving (Eq, Ord, Show)+-- Show is only provided for printing/debugging purposes when using a+-- normal Tree-based graph.  Since it doesn't support Read, neither+-- does EdgeID.++-- | Add unique edge identifiers to each label.  This is useful for+--   when multiple edges between two nodes need to be distinguished.+addEdgeIDs   :: (Graph gr) => gr a b -> gr a (EdgeID b)+addEdgeIDs g = mkGraph ns es'+  where+    ns = labNodes g+    es = labEdges g+    es' = zipWith addID es ([1..] :: [Int])+    addID (f,t,l) i = (f,t,EID (show i) l)++-- | Add the 'Comment' to the list of attributes containing the value+--   of the unique edge identifier.+setEdgeComment     :: (LEdge b -> Attributes)+                      -> (LEdge (EdgeID b) -> Attributes)+setEdgeComment f = \ e@(_,_,eid) -> Comment (eID eid) : (f . stripID) e++-- | Remove the unique identifier from the 'LEdge'.+stripID           :: LEdge (EdgeID b) -> LEdge b+stripID (f,t,eid) = (f,t, eLbl eid)++-- | Pass the 'DotGraph' through the relevant command and then augment+--   the 'Graph' that it came from.+dotAttributes :: (Graph gr, DotRepr dg Node) => Bool -> gr a (EdgeID b)+                 -> dg Node -> IO (gr (AttributeNode a) (AttributeEdge b))+dotAttributes isDir gr dot+  = liftM (augmentGraph gr . parseDG . fromDotResult)+    $ graphvizWithHandle command dot DotOutput hGetContents'+    where+      parseDG = asTypeOf dot . parseDotGraph+      command = if isDir then dirCommand else undirCommand++-- | Use the 'Attributes' in the provided 'DotGraph' to augment the+--   node and edge labels in the provided 'Graph'.  The unique+--   identifiers on the edges are also stripped off.+--+--   Please note that the behaviour for this function is undefined if+--   the 'DotGraph' does not come from the original 'Graph' (either+--   by using a conversion function or by passing the result of a+--   conversion function through a 'GraphvizCommand' via the+--   'DotOutput' or similar).+augmentGraph      :: (Graph gr, DotRepr dg Node) => gr a (EdgeID b)+                     -> dg Node -> gr (AttributeNode a) (AttributeEdge b)+augmentGraph g dg = mkGraph lns les+  where+    lns = map (\(n, l) -> (n, (nodeMap Map.! n, l)))+          $ labNodes g+    les = map augmentEdge $ labEdges g+    augmentEdge (f,t,(EID eid l)) = (f,t, (edgeMap Map.! eid, l))+    ns = graphNodes dg+    es = graphEdges dg+    nodeMap = Map.fromList $ map (nodeID &&& nodeAttributes) ns+    edgeMap = Map.fromList $ map (findID &&& edgeAttributes') es+    findID = head . mapMaybe commentID . edgeAttributes+    commentID (Comment s) = Just s+    commentID _           = Nothing+    -- Strip out the comment+    edgeAttributes' = filter (isNothing . commentID) . edgeAttributes+++-- ----------------------------------------------------------------------------- -- Utility Functions  -- | Pretty-print the 'DotGraph' by passing it through the 'Canon' --   output type (which produces \"canonical\" output).  This is---   required because the @printIt@ function in---   "Data.GraphViz.Types.Printing" no longer uses indentation to---   ensure the Dot code is printed correctly.-prettyPrint    :: (PrintDot a) => DotGraph a -> IO String-prettyPrint dg = liftM fromRight+--   required because the 'printDotGraph' function (and all printing+--   functions in "Data.GraphViz.Types.Printing") no longer uses+--   indentation (this is to ensure the Dot code is printed correctly+--   due to the limitations of the Pretty Printer used).+prettyPrint    :: (DotRepr dg n) => dg n -> IO String+prettyPrint dg = liftM fromDotResult                  -- Note that the choice of command here should be                  -- arbitrary.                  $ graphvizWithHandle (commandFor dg)                                       dg                                       Canon                                       hGetContents'-  where-    fromRight (Right r) = r-    fromRight Left{}    = fail "Usage of prettyPrint failed; \-                                \is the Graphviz suite of tools installed?"  -- | The 'unsafePerformIO'd version of 'prettyPrint'.  Graphviz should --   always produce the same pretty-printed output, so this should be --   safe.-prettyPrint' :: (PrintDot a) => DotGraph a -> String+prettyPrint' :: (DotRepr dg n) => dg n -> String prettyPrint' = unsafePerformIO . prettyPrint++-- | Quickly visualise a graph using the 'Xlib' 'GraphvizCanvas'.+preview   :: (Ord b, Graph gr) => gr a b -> IO ()+preview g = ign $ forkIO (ign $ runGraphvizCanvas' dg Xlib)+  where+    dg = graphToDot' g [] (const []) (const [])+    ign = (>> return ())++-- | Used for obtaining results from 'graphvizWithHandle', etc. when+--   errors should only occur when Graphviz isn't installed.  If the+--   value is @'Left' _@, then 'error' is used.+fromDotResult           :: Either l r -> r+fromDotResult (Right r) = r+fromDotResult Left{}    = error "Could not run the relevant Graphviz command; \+                                 \is the Graphviz suite of tools installed?"
Data/GraphViz/Attributes.hs view
@@ -67,6 +67,10 @@     * Deprecated 'Overlap' algorithms are not defined. +   * The global @Orientation@ attribute is not defined, as it is+     difficult to distinguish from the node-based 'Orientation'+     'Attribute'; also, its behaviour is duplicated by 'Rotate'.+  -} module Data.GraphViz.Attributes     ( -- * The actual /Dot/ attributes.@@ -159,9 +163,9 @@     ) where  import Data.GraphViz.Attributes.Colors-import Data.GraphViz.Types.Internal-import Data.GraphViz.Types.Parsing-import Data.GraphViz.Types.Printing+import Data.GraphViz.Util+import Data.GraphViz.Parsing+import Data.GraphViz.Printing  import Data.Char(toLower) import Data.Maybe(isJust)
Data/GraphViz/Attributes/Colors.hs view
@@ -37,8 +37,8 @@        , fromAColour        ) where -import Data.GraphViz.Types.Parsing-import Data.GraphViz.Types.Printing+import Data.GraphViz.Parsing+import Data.GraphViz.Printing  import Data.Colour( AlphaColour, opaque, transparent, withOpacity                   , over, black, alphaChannel, darken)
Data/GraphViz/Commands.hs view
@@ -17,6 +17,11 @@    not easy (there is no listing of available renderers or formatters    on the Graphviz website), and for the most part the default ones do    the job well.++   Please note that for 'GraphvizOutput' and 'GraphvizCanvas', you+   will see that they are instances of a @GraphvizResult@ class; this is+   an internal class that should not be visible outside this module, but+   Haddock is being too helpful for its own good. -} module Data.GraphViz.Commands     ( -- * The different Graphviz tools available.@@ -30,6 +35,7 @@     , GraphvizCanvas(..)       -- * Running Graphviz.     , RunResult(..)+    , maybeErr     , runGraphviz     , runGraphvizCommand     , addExtension@@ -44,7 +50,6 @@ import Prelude hiding (catch)  import Data.GraphViz.Types-import Data.GraphViz.Types.Printing -- This is here just for Haddock linking purposes. import Data.GraphViz.Attributes(Attribute(Z)) @@ -91,8 +96,8 @@ undirCommand = Neato  -- | The appropriate (default) Graphviz command for the given graph.-commandFor    :: DotGraph a -> GraphvizCommand-commandFor dg = if directedGraph dg+commandFor    :: (DotRepr dg n) => dg n -> GraphvizCommand+commandFor dg = if graphIsDirected dg                 then dirCommand                 else undirCommand @@ -272,24 +277,26 @@  -- | Run the recommended Graphviz command on this graph, saving the result --   to the file provided (note: file extensions are /not/ checked).-runGraphviz    :: (PrintDot n) => DotGraph n -> GraphvizOutput -> FilePath-                  -> IO RunResult+runGraphviz    :: (DotRepr dg n) => dg n -> GraphvizOutput -> FilePath+                  -> IO (Either String FilePath) runGraphviz gr = runGraphvizCommand (commandFor gr) gr  -- | Run the chosen Graphviz command on this graph, saving the result --   to the file provided (note: file extensions are /not/ checked).-runGraphvizCommand :: (PrintDot n) => GraphvizCommand -> DotGraph n-                      -> GraphvizOutput -> FilePath -> IO RunResult+runGraphvizCommand :: (DotRepr dg n) => GraphvizCommand -> dg n+                      -> GraphvizOutput -> FilePath+                      -> IO (Either String FilePath) runGraphvizCommand cmd gr t fp-  = liftM maybeErr+  = liftM (either (Left . addFl) (const $ Right fp))     $ graphvizWithHandle cmd gr t toFile       where+        addFl = (++) ("Unable to create " ++ fp ++ "\n")         toFile h = B.hGetContents h >>= B.writeFile fp  -- | Append the default extension for the provided 'GraphvizOutput' to --   the provided 'FilePath' for the output file.-addExtension          :: (GraphvizOutput -> FilePath -> IO a)-                         -> GraphvizOutput -> FilePath -> IO a+addExtension          :: (GraphvizOutput -> FilePath -> a)+                         -> GraphvizOutput -> FilePath -> a addExtension cmd t fp = cmd t fp'     where       fp' = fp <.> defaultExtension t@@ -298,13 +305,13 @@ --   result to the given handle rather than to a file. -- --   Note that the @'Handle' -> 'IO' a@ function /must/ fully consume---   the input from the 'Handle'; 'hGetContents'' is an version of+--   the input from the 'Handle'; 'hGetContents'' is a version of --   'hGetContents' that will ensure this happens. -----   If the command was unsuccessful, then @Left error@ is returned.-graphvizWithHandle :: (PrintDot n, Show a)+--   If the command was unsuccessful, then @'Left' error@ is returned.+graphvizWithHandle :: (DotRepr dg n, Show a)                       => GraphvizCommand      -- ^ Which command to run-                      -> DotGraph n           -- ^ The 'DotGraph' to use+                      -> dg n                 -- ^ The 'DotRepr' to use                       -> GraphvizOutput       -- ^ The 'GraphvizOutput' type                       -> (Handle -> IO a)     -- ^ Extract the output                       -> IO (Either String a) -- ^ The error or the result.@@ -312,8 +319,8 @@  -- This version is not exported as we don't want to let arbitrary -- @Handle -> IO a@ functions to be used for GraphvizCanvas outputs.-graphvizWithHandle' :: (PrintDot n, GraphvizResult o, Show a)-                       => GraphvizCommand -> DotGraph n -> o+graphvizWithHandle' :: (DotRepr dg n, GraphvizResult o, Show a)+                       => GraphvizCommand -> dg n -> o                        -> (Handle -> IO a) -> IO (Either String a) graphvizWithHandle' cmd gr t f   = handle notRunnable@@ -373,7 +380,7 @@  -- | Run the chosen Graphviz command on this graph and render it using --   the given canvas type.-runGraphvizCanvas          :: (PrintDot n) => GraphvizCommand -> DotGraph n+runGraphvizCanvas          :: (DotRepr dg n) => GraphvizCommand -> dg n                               -> GraphvizCanvas -> IO RunResult runGraphvizCanvas cmd gr c = liftM maybeErr                              $ graphvizWithHandle' cmd gr c nullHandle@@ -385,7 +392,7 @@  -- | Run the recommended Graphviz command on this graph and render it --   using the given canvas type.-runGraphvizCanvas'   :: (PrintDot n) => DotGraph n -> GraphvizCanvas+runGraphvizCanvas'   :: (DotRepr dg n) => dg n -> GraphvizCanvas                         -> IO RunResult runGraphvizCanvas' d = runGraphvizCanvas (commandFor d) d 
+ Data/GraphViz/Parsing.hs view
@@ -0,0 +1,395 @@+{- |+   Module      : Data.GraphViz.Parsing+   Description : Helper functions for Parsing.+   Copyright   : (c) Matthew Sackman, Ivan Lazar Miljenovic+   License     : 3-Clause BSD-style+   Maintainer  : Ivan.Miljenovic@gmail.com++   This module defines simple helper functions for use with+   "Text.ParserCombinators.Poly.Lazy".++   Note that the 'ParseDot' instances for 'Bool', etc. match those+   specified for use with Graphviz (e.g. non-zero integers are+   equivalent to 'True').++   You should not be using this module; rather, it is here for+   informative/documentative reasons.  If you want to parse a+   @'Data.GraphViz.Types.DotRepr'@, you should use+   @'Data.GraphViz.Types.parseDotGraph'@ rather than its 'ParseDot'+   instance.+-}+module Data.GraphViz.Parsing+    ( -- * Re-exporting pertinent parts of Polyparse.+      module Text.ParserCombinators.Poly.Lazy+      -- * The ParseDot class.+    , Parse+    , ParseDot(..)+    , parseIt+      -- * Convenience parsing combinators.+    , onlyBool+    , stringBlock+    , numString+    , isNumString+    , isIntString+    , quotedString+    , parseAndSpace+    , string+    , strings+    , hasString+    , character+    , parseStrictFloat+    , noneOf+    , whitespace+    , whitespace'+    , optionalQuotedString+    , optionalQuoted+    , quotedParse+    , orQuote+    , quoteChar+    , newline+    , newline'+    , parseComma+    , tryParseList+    , tryParseList'+    , skipToNewline+    , parseField+    , parseFields+    , parseFieldBool+    , parseFieldsBool+    , parseFieldDef+    , parseFieldsDef+    , commaSep+    , commaSepUnqt+    , commaSep'+    , stringRep+    , stringReps+    -- * Pre-processing of Dot code to remove comments, etc.+    , preProcess+    ) where++import Data.GraphViz.Util++import Text.ParserCombinators.Poly.Lazy+import Data.Char( digitToInt+                , isDigit+                , isSpace+                , toLower+                )+import Data.Function(on)+import Data.Maybe(isJust, fromMaybe, isNothing)+import Data.Ratio((%))+import Data.Word(Word8)+import Control.Monad(liftM, when)++-- -----------------------------------------------------------------------------+-- Based off code from Text.Parse in the polyparse library++-- | A @ReadS@-like type alias.+type Parse a = Parser Char a++class ParseDot a where+    parseUnqt :: Parse a++    parse :: Parse a+    parse = optionalQuoted parseUnqt++    parseUnqtList :: Parse [a]+    parseUnqtList = bracketSep (parseAndSpace $ character '[')+                               (parseAndSpace $ parseComma)+                               (parseAndSpace $ character ']')+                               (parseAndSpace parse)++    parseList :: Parse [a]+    parseList = quotedParse parseUnqtList++-- | Parse the required value, returning also the rest of the input+--   'String' that hasn't been parsed.+parseIt :: (ParseDot a) => String -> (a, String)+parseIt = runParser parse++instance ParseDot Int where+    parseUnqt = parseInt'++instance ParseDot Word8 where+    parseUnqt = parseInt++instance ParseDot Double where+    parseUnqt = parseFloat'++instance ParseDot Bool where+    parseUnqt = onlyBool+                `onFail`+                liftM (zero /=) parseInt'+        where+          zero :: Int+          zero = 0++-- | Use this when you do not want numbers to be treated as 'Bool' values.+onlyBool :: Parse Bool+onlyBool = oneOf [ stringRep True "true"+                 , stringRep False "false"+                 ]++instance ParseDot Char where+    -- Can't be a quote character.+    parseUnqt = satisfy ((/=) quoteChar)++    parse = satisfy restIDString+            `onFail`+            quotedParse parseUnqt++    -- Too many problems with using this within other parsers where+    -- using numString or stringBlock will cause a parse failure.  As+    -- such, this will successfully parse all un-quoted Strings.+    parseUnqtList = quotedString++    parseList = oneOf [numString, stringBlock]+                `onFail`+                -- This will also take care of quoted versions of+                -- above.+                quotedParse quotedString++instance (ParseDot a) => ParseDot [a] where+    parseUnqt = parseUnqtList++    parse = parseList++numString :: Parse String+numString = liftM show parseStrictFloat+            `onFail`+            liftM show parseInt'++stringBlock :: Parse String+stringBlock = do frst <- satisfy frstIDString+                 rest <- many (satisfy restIDString)+                 return $ frst : rest++-- | Used when quotes are explicitly required;+quotedString :: Parse String+quotedString = many stringInterior++stringInterior :: Parse Char+stringInterior = orQuote $ satisfy ((/=) quoteChar)++parseSigned :: Real a => Parse a -> Parse a+parseSigned p = (character '-' >> liftM negate p)+                `onFail`+                p++parseInt :: (Integral a) => Parse a+parseInt = do cs <- many1 (satisfy isDigit)+              return (foldl1 (\n d-> n*radix+d)+                                   (map (fromIntegral . digitToInt) cs))+           `adjustErr` (++ "\nexpected one or more digits")+    where+      radix = 10++parseInt' :: Parse Int+parseInt' = parseSigned parseInt++-- | Parse a floating point number that actually contains decimals.+parseStrictFloat :: Parse Double+parseStrictFloat = parseSigned parseFloat++parseFloat :: (RealFrac a) => Parse a+parseFloat = do ds   <- many (satisfy isDigit)+                frac <- optional+                        $ do character '.'+                             many1 (satisfy isDigit)+                               `adjustErr` (++ "\nexpected digit after .")+                when (isNothing frac && null ds)+                  (fail "No actual digits in floating point number!")+                expn  <- optional parseExp+                when (isNothing frac && isNothing expn)+                  (fail "This is an integer, not a floating point number!")+                let frac' = fromMaybe "" frac+                    expn' = fromMaybe 0 expn+                ( return . fromRational . (* (10^^(expn' - length frac')))+                  . (%1) . fst+                  . runParser parseInt) (ds++frac')+             `onFail`+             fail "Expected a floating point number"+    where parseExp = do character 'e'+                        ((character '+' >> parseInt)+                         `onFail`+                         parseInt')++parseFloat' :: Parse Double+parseFloat' = parseSigned ( parseFloat+                            `onFail`+                            liftM fI parseInt+                          )+    where+      fI :: Integer -> Double+      fI = fromIntegral++-- -----------------------------------------------------------------------------++parseAndSpace   :: Parse a -> Parse a+parseAndSpace p = p `discard` whitespace'++string :: String -> Parse String+string = mapM character++stringRep   :: a -> String -> Parse a+stringRep v = stringReps v . return++stringReps      :: a -> [String] -> Parse a+stringReps v ss = oneOf (map string ss) >> return v++strings :: [String] -> Parse String+strings = oneOf . map string++hasString :: String -> Parse Bool+hasString = liftM isJust . optional . string++character   :: Char -> Parse Char+character c = satisfy (((==) `on` toLower) c)+              `adjustErr`+              (++ "\nnot the expected char: " ++ [c])++noneOf :: (Eq a) => [a] -> Parser a a+noneOf t = satisfy (\x -> all (/= x) t)++whitespace :: Parse String+whitespace = many1 (satisfy isSpace)++whitespace' :: Parse String+whitespace' = many (satisfy isSpace)++optionalQuotedString :: String -> Parse String+optionalQuotedString = optionalQuoted . string++optionalQuoted   :: Parse a -> Parse a+optionalQuoted p = quotedParse p+                   `onFail`+                   p++quotedParse :: Parse a -> Parse a+quotedParse = bracket parseQuote parseQuote++parseQuote :: Parse Char+parseQuote = character quoteChar++orQuote   :: Parse Char -> Parse Char+orQuote p = stringRep quoteChar "\\\""+            `onFail`+            p++quoteChar :: Char+quoteChar = '"'++newline :: Parse String+newline = oneOf $ map string ["\r\n", "\n", "\r"]++-- | Consume all whitespace and newlines until a line with+--   non-whitespace is reached.  The whitespace on that line is+--   not consumed.+newline' :: Parse ()+newline' = many (whitespace' >> newline) >> return ()++-- | Parses and returns all characters up till the end of the line,+--   then skips to the beginning of the next line.+skipToNewline :: Parse String+skipToNewline = many (noneOf ['\n','\r']) `discard` newline++parseField     :: (ParseDot a) => String -> Parse a+parseField fld = do string fld+                    whitespace'+                    character '='+                    whitespace'+                    parse++parseFields :: (ParseDot a) => [String] -> Parse a+parseFields = oneOf . map parseField++parseFieldBool :: String -> Parse Bool+parseFieldBool = parseFieldDef True++parseFieldsBool :: [String] -> Parse Bool+parseFieldsBool = oneOf . map parseFieldBool++-- | For 'Bool'-like data structures where the presence of the field+--   name without a value implies a default value.+parseFieldDef       :: (ParseDot a) => a -> String -> Parse a+parseFieldDef d fld = parseField fld+                      `onFail`+                      stringRep d fld++parseFieldsDef   :: (ParseDot a) => a -> [String] -> Parse a+parseFieldsDef d = oneOf . map (parseFieldDef d)++commaSep :: (ParseDot a, ParseDot b) => Parse (a, b)+commaSep = commaSep' parse parse++commaSepUnqt :: (ParseDot a, ParseDot b) => Parse (a, b)+commaSepUnqt = commaSep' parseUnqt parseUnqt++commaSep'       :: Parse a -> Parse b -> Parse (a,b)+commaSep' pa pb = do a <- pa+                     whitespace'+                     parseComma+                     whitespace'+                     b <- pb+                     return (a,b)++parseComma :: Parse Char+parseComma = character ','++tryParseList :: (ParseDot a) => Parse [a]+tryParseList = tryParseList' parse++tryParseList' :: Parse [a] -> Parse [a]+tryParseList' = liftM (fromMaybe []) . optional++-- -----------------------------------------------------------------------------+-- Filtering out unwanted Dot items such as comments++-- | Remove unparseable features of Dot, such as comments and+--   multi-line strings (which are converted to single-line strings).+preProcess :: String -> String+preProcess = fst . runParser parseOutUnwanted+             -- snd should be null++-- | Parse out comments and make quoted strings spread over multiple+--   lines only over a single line.  Should parse the /entire/ input+--   'String'.+parseOutUnwanted :: Parse String+parseOutUnwanted = liftM concat (many getNext)+    where+      getNext :: Parse String+      getNext = parseSplitStrings+                `onFail`+                (oneOf [ parseLineComment, parseMultiLineComment ] >> return [])+                `onFail`+                liftM return next++-- | Parse @//@-style comments.+parseLineComment :: Parse String+parseLineComment = string "//" >> newline++-- | Parse @/* ... */@-style comments.+parseMultiLineComment :: Parse String+parseMultiLineComment = bracket start end (liftM concat $ many inner)+    where+      start = string "/*"+      end = string "*/"+      inner = many1 (satisfy ((/=) '*'))+              `onFail`+              do ast <- character '*'+                 n <- satisfy ((/=) '/')+                 liftM ((:) ast . (:) n) inner++-- | Parse out @\<newline>@ from a quoted string.+parseSplitStrings :: Parse String+parseSplitStrings = do oq <- parseQuote+                       inner <- liftM concat $ many parseInner+                       cq <- parseQuote+                       return $ oq : inner ++ [cq]+    where+      parseInner = string "\\\""+                   `onFail`+                   (character '\\' >> newline >> return [])+                   `onFail`+                   liftM return (satisfy ((/=) quoteChar))+
+ Data/GraphViz/Printing.hs view
@@ -0,0 +1,175 @@+{- |+   Module      : Data.GraphViz.Printing+   Description : Helper functions for converting to Dot format.+   Copyright   : (c) Ivan Lazar Miljenovic+   License     : 3-Clause BSD-style+   Maintainer  : Ivan.Miljenovic@gmail.com++   This module defines simple helper functions for use with+   "Text.PrettyPrint".  It also re-exports all the pretty-printing+   combinators from that module.++   Note that the 'PrintDot' instances for 'Bool', etc. match those+   specified for use with Graphviz.++   You should only be using this module if you are writing custom node+   types for use with "Data.GraphViz.Types".  For actual printing of+   code, use @'Data.GraphViz.Types.printDotGraph'@ (which produces a+   'String' value).++   The Dot language specification specifies that any identifier is in+   one of four forms:++       * Any string of alphabetic ([a-zA-Z\200-\377]) characters, underscores ('_') or digits ([0-9]), not beginning with a digit;++       * a number [-]?(.[0-9]+ | [0-9]+(.[0-9]*)? );++       * any double-quoted string (\"...\") possibly containing escaped quotes (\\\");++       * an HTML string (<...>).++   Due to these restrictions, you should only use 'text' when you are+   sure that the 'String' in question is static and quotes are+   definitely needed/unneeded; it is better to use the 'String'+   instance for 'PrintDot'.  For more information, see the+   specification page:+      <http://graphviz.org/doc/info/lang.html>+-}+module Data.GraphViz.Printing+    ( module Text.PrettyPrint+    , DotCode+    , renderDot -- Exported for Data.GraphViz.Types.printSGID+    , PrintDot(..)+    , printIt+    , addQuotes+    , wrap+    , commaDel+    , printField+    ) where++import Data.GraphViz.Util++-- Only implicitly import and re-export combinators.+import Text.PrettyPrint hiding ( Style(..)+                               , Mode(..)+                               , TextDetails(..)+                               , render+                               , style+                               , renderStyle+                               , fullRender+                               )++import qualified Text.PrettyPrint as PP++import Data.Word(Word8)++-- -----------------------------------------------------------------------------++-- | A type alias to indicate what is being produced.+type DotCode = Doc++-- | Correctly render Graphviz output.+renderDot :: DotCode -> String+renderDot = PP.renderStyle style'+    where+      style' = PP.style { PP.mode = PP.LeftMode }++-- | A class used to correctly print parts of the Graphviz Dot language.+--   Minimal implementation is 'unqtDot'.+class PrintDot a where+    -- | The unquoted representation, for use when composing values to+    --   produce a larger printing value.+    unqtDot :: a -> DotCode++    -- | The actual quoted representation; this should be quoted if it+    --   contains characters not permitted a plain ID String, a number+    --   or it is not an HTML string.+    --   Defaults to 'unqtDot'.+    toDot :: a -> DotCode+    toDot = unqtDot++    -- | The correct way of representing a list of this value when+    --   printed; not all Dot values require this to be implemented.+    --   Defaults to Haskell-like list representation.+    unqtListToDot :: [a] -> DotCode+    unqtListToDot = brackets . hsep . punctuate comma+                    . map unqtDot++    -- | The quoted form of 'unqtListToDot'; defaults to wrapping+    --   double quotes around the result of 'unqtListToDot' (since the+    --   default implementation has characters that must be quoted).+    listToDot :: [a] -> DotCode+    listToDot = doubleQuotes . unqtListToDot++-- | Convert to DotCode; note that this has no indentation, as we can+--   only have one of indentation and (possibly) infinite line lengths.+printIt :: (PrintDot a) => a -> String+printIt = renderDot . toDot++instance PrintDot Int where+    unqtDot = int++instance PrintDot Word8 where+    unqtDot = int . fromIntegral++instance PrintDot Double where+    -- If it's an "integral" double, then print as an integer.+    -- This seems to match how Graphviz apps use Dot.+    unqtDot d = if d == fromIntegral di+                then int di+                else double d+        where+          di = round d++instance PrintDot Bool where+    unqtDot True  = text "true"+    unqtDot False = text "false"++instance PrintDot Char where+    unqtDot = char++    toDot = qtChar++    unqtListToDot = unqtString++    listToDot = qtString++-- | Check to see if this 'Char' needs to be quoted or not.+qtChar :: Char -> DotCode+qtChar c+    | restIDString c = char c -- Could be a number as well.+    | otherwise      = doubleQuotes $ char c++needsQuotes :: String -> Bool+needsQuotes str+  | null str        = True+  | isKeyword str   = True+  | isIDString str  = False+  | isNumString str = False+  | otherwise       = True++addQuotes :: String -> DotCode -> DotCode+addQuotes = bool id doubleQuotes . needsQuotes++-- | Escape quotes in Strings that need them.+unqtString     :: String -> DotCode+unqtString ""  = empty+unqtString str = text $ escapeQuotes str -- no quotes? no worries!++-- | Escape quotes and quote Strings that need them (including keywords).+qtString     :: String -> DotCode+qtString str = addQuotes str $ unqtString str++instance (PrintDot a) => PrintDot [a] where+    unqtDot = unqtListToDot++    toDot = listToDot++wrap       :: DotCode -> DotCode -> DotCode -> DotCode+wrap b a d = b <> d <> a++commaDel     :: (PrintDot a, PrintDot b) => a -> b -> DotCode+commaDel a b = unqtDot a <> comma <> unqtDot b++printField     :: (PrintDot a) => String -> a -> DotCode+printField f v = text f <> equals <> toDot v
Data/GraphViz/Testing.hs view
@@ -21,7 +21,7 @@     * The generated Strings are very simple, only composed of lower      case letters, digits and some symbols.  This is because too many-     tests were "failing" due to some corner case; e.g. lower-case+     tests were \"failing\" due to some corner case; e.g. lower-case      letters only because the parser parses Strings as lowercase, so      if a particular String isn't valid (e.g. @\"all\"@ for 'LayerID',      then the 'Arbitrary' instance has to ensure that all possible@@ -33,12 +33,10 @@      as dot, etc. choke on invalid Dot code.     * To avoid needless endless recursion, 'DotSubGraph's do not have-     sub-'DotSubGraph's.+     sub-'DotSubGraph's (same with 'GDotSubGraph's). -   * The 'Graph' values that are generated do not have multiple edges,-     as the 'prop_dotizeAugment' property uses 'dotize'', which makes-     no guarantees on what will happen for 'Graph's with multiple-     edges.+   * The 'Graph' values that are generated do not have multiple edges+     for simplicity purposes.     * This test suite isn't perfect: if you deliberately try to stuff      something up, you probably can.@@ -49,15 +47,23 @@          -- ** The tests themselves        , Test(..)        , test_printParseID_Attributes+       , test_generalisedSameDot        , test_printParseID        , test_preProcessingID        , test_parsePrettyID        , test_dotizeAugment         -- * Re-exporting modules for manual testing.        , module Data.GraphViz+       , module Data.GraphViz.Types.Generalised        , module Data.GraphViz.Testing.Properties+         -- * Debugging printing+       , PrintDot(..)        , printIt+       , renderDot+         -- * Debugging parsing+       , ParseDot(..)        , parseIt+       , runParser        , preProcess        ) where @@ -70,8 +76,12 @@ import Data.GraphViz.Testing.Properties  import Data.GraphViz hiding (RunResult(..))-import Data.GraphViz.Types.Parsing(parseIt, preProcess)-import Data.GraphViz.Types.Printing(printIt)+import Data.GraphViz.Parsing(ParseDot(..), parseIt, runParser, preProcess)+import Data.GraphViz.Printing(PrintDot(..), printIt, renderDot)+import Data.GraphViz.Types.Generalised hiding ( GraphID(..)+                                              , GlobalAttributes(..)+                                              , DotNode(..)+                                              , DotEdge(..)) -- Can't use PatriciaTree because a Show instance is needed. import Data.Graph.Inductive.Tree(Gr) @@ -91,7 +101,7 @@            \If any of these tests fail, please inform the maintainer,\n\            \including full output of this test suite.\n\            \\n\-           \This test suite takes approximately 90 minutes to run on a\n\+           \This test suite takes approximately 110 minutes to run on a\n\            \2 GHz Mobile Core 2 Duo (running with a single thread)."      successMsg = "All tests were successful!"@@ -148,7 +158,9 @@ -- | The tests to run by default. defaultTests :: [Test] defaultTests = [ test_printParseID_Attributes+               , test_generalisedSameDot                , test_printParseID+               , test_printParseGID                , test_preProcessingID                  -- Can't run this test, since we don't generate valid                  -- DotGraphs to pass to Graphviz!@@ -176,6 +188,19 @@             \rest of the tests, generating " ++ show numGen ++ " lists of\n\             \Attributes rather than the default " ++ show defGen ++ " tests." +test_generalisedSameDot :: Test+test_generalisedSameDot+  = Test { name = "Printing generalised Dot code"+         , desc = dsc+         , test = quickCheckResult prop+         }+    where+      prop :: DotGraph Int -> Bool+      prop = prop_generalisedSameDot++      dsc = "When generalising \"DotGraph\" values to \"GDotGraph\" values,\n\+             \the generated Dot code should be identical."+ test_printParseID :: Test test_printParseID   = Test { name = "Printing and Parsing DotGraphs"@@ -190,6 +215,20 @@              \generated Dot code.  This test aims to determine the validity\n\              \of this for the overall \"DotGraph Int\" values." +test_printParseGID :: Test+test_printParseGID+  = Test { name = "Printing and Parsing Generalised DotGraphs"+         , desc = dsc+         , test = quickCheckResult prop+         }+    where+      prop :: GDotGraph Int -> Bool+      prop = prop_printParseID++      dsc = "The graphviz library should be able to parse back in its own\n\+             \generated Dot code.  This test aims to determine the validity\n\+             \of this for the overall \"GDotGraph Int\" values."+ test_preProcessingID :: Test test_preProcessingID   = Test { name = "Pre-processing Dot code"@@ -203,7 +242,9 @@       dsc = "When parsing Dot code, some pre-processing is done to remove items\n\              \such as comments and to join together multi-line strings.  This\n\              \test verifies that this pre-processing doesn't affect actual\n\-             \Dot code by running the pre-processor on generated Dot code."+             \Dot code by running the pre-processor on generated Dot code.\n\n\+             \This test is not run on generalised Dot graphs as if it works for\n\+             \normal dot graphs then it should also work for generalised ones."  -- | This test is not valid for use until valid DotGraphs can be generated. test_parsePrettyID :: Test
Data/GraphViz/Testing/Instances.hs view
@@ -17,15 +17,17 @@ -} module Data.GraphViz.Testing.Instances() where -import Data.GraphViz.Types.Parsing(isNumString)+import Data.GraphViz.Parsing(isNumString)  import Data.GraphViz.Attributes import Data.GraphViz.Types+import Data.GraphViz.Types.Generalised  import Test.QuickCheck  import Data.Char(toLower) import Data.List(nub)+import qualified Data.Sequence as Seq import Control.Monad(liftM, liftM2, liftM3, liftM4, guard) import Data.Word(Word8) @@ -51,7 +53,7 @@   shrink (HTML u) = map HTML $ shrink u  instance (Eq a, Arbitrary a) => Arbitrary (DotStatements a) where-  arbitrary = arbDS True+  arbitrary = sized (arbDS True)    shrink ds@(DotStmts gas sgs ns es) = do gas' <- shrinkL gas                                           sgs' <- shrinkL sgs@@ -61,11 +63,12 @@                                             $ DotStmts gas' sgs' ns' es'  -- | If 'True', generate 'DotSubGraph's; otherwise don't.-arbDS         :: (Arbitrary a, Eq a) => Bool -> Gen (DotStatements a)-arbDS haveSGs = liftM4 DotStmts arbitrary genSGs arbitrary arbitrary+arbDS           :: (Arbitrary a, Eq a) => Bool -> Int -> Gen (DotStatements a)+arbDS haveSGs s = liftM4 DotStmts arbitrary genSGs arbitrary arbitrary   where+    s' = min s 2     genSGs = if haveSGs-             then resize 2 arbitrary+             then resize s' arbitrary              else return []  instance Arbitrary GlobalAttributes where@@ -79,7 +82,7 @@   shrink (EdgeAttrs  atts) = map EdgeAttrs  $ nonEmptyShrinks atts  instance (Eq a, Arbitrary a) => Arbitrary (DotSubGraph a) where-  arbitrary = liftM3 DotSG arbitrary arbitrary (arbDS False)+  arbitrary = liftM3 DotSG arbitrary arbitrary (sized $ arbDS False)    shrink (DotSG isCl mid stmts) = map (DotSG isCl mid) $ shrink stmts @@ -92,6 +95,58 @@   arbitrary = liftM4 DotEdge arbitrary arbitrary arbitrary arbitrary    shrink (DotEdge f t isDir as) = map (DotEdge f t isDir) $ shrinkList as++-- -----------------------------------------------------------------------------+-- Defining Arbitrary instances for the generalised types++instance (Eq a, Arbitrary a) => Arbitrary (GDotGraph a) where+  arbitrary = liftM4 GDotGraph arbitrary arbitrary arbitrary genGDStmts++  shrink (GDotGraph str dir gid stmts) = map (GDotGraph str dir gid)+                                         $ shrinkGDStmts stmts++genGDStmts :: (Eq a, Arbitrary a) => Gen (GDotStatements a)+genGDStmts = sized (arbGDS True)++shrinkGDStmts :: (Eq a, Arbitrary a) => GDotStatements a -> [GDotStatements a]+shrinkGDStmts gds+  | len == 1  = map Seq.singleton . shrink $ Seq.index gds 0+  | otherwise = [gds1, gds2]+    where+      len = Seq.length gds+      -- Halve the sequence+      (gds1, gds2) = (len `div` 2) `Seq.splitAt` gds++instance (Eq a, Arbitrary a) => Arbitrary (GDotStatement a) where+  -- Don't want as many sub-graphs as nodes, etc.+  arbitrary = frequency [ (3, liftM GA arbitrary)+                        , (1, liftM SG arbitrary)+                        , (5, liftM DN arbitrary)+                        , (7, liftM DE arbitrary)+                        ]++  shrink (GA ga) = map GA $ shrink ga+  shrink (SG sg) = map SG $ shrink sg+  shrink (DN dn) = map DN $ shrink dn+  shrink (DE de) = map DE $ shrink de++-- | If 'True', generate 'GDotSubGraph's; otherwise don't.+arbGDS           :: (Arbitrary a, Eq a) => Bool -> Int -> Gen (GDotStatements a)+arbGDS haveSGs s = liftM (Seq.fromList . checkSGs) (resize s' arbList)+  where+    checkSGs = if haveSGs+               then id+               else filter notSG+    notSG SG{} = False+    notSG _    = True++    s' = min s 10+++instance (Eq a, Arbitrary a) => Arbitrary (GDotSubGraph a) where+  arbitrary = liftM3 GDotSG arbitrary arbitrary (sized $ arbGDS False)++  shrink (GDotSG isCl mid stmts) = map (GDotSG isCl mid) $ shrinkGDStmts stmts  -- ----------------------------------------------------------------------------- -- Defining Arbitrary instances for Attributes
Data/GraphViz/Testing/Instances/FGL.hs view
@@ -18,9 +18,9 @@  import Test.QuickCheck +import Data.GraphViz.Util(uniq, uniqBy)+ import Data.Graph.Inductive.Graph(Graph, mkGraph, nodes, delNode)-import Data.Function(on)-import Data.List(group, sort, nubBy) import Control.Monad(liftM, liftM3)  -- -----------------------------------------------------------------------------@@ -30,11 +30,10 @@   arbitrary = do ns <- suchThat genNs (not . null)                  let nGen = elements ns                  lns <- mapM makeLNode ns-                 les <- liftM filtEs . listOf $ makeLEdge nGen+                 les <- liftM (uniqBy toE) . listOf $ makeLEdge nGen                  return $ mkGraph lns les     where-      genNs = liftM (map head . group . sort) arbitrary-      filtEs = nubBy ((==) `on` toE)+      genNs = liftM uniq arbitrary       toE (f,t,_) = (f,t)       makeLNode n = liftM ((,) n) arbitrary       makeLEdge nGen = liftM3 (,,) nGen nGen arbitrary
Data/GraphViz/Testing/Properties.hs view
@@ -10,9 +10,10 @@ module Data.GraphViz.Testing.Properties where  import Data.GraphViz(dotizeGraph', prettyPrint')-import Data.GraphViz.Types(DotGraph, printDotGraph)-import Data.GraphViz.Types.Printing(PrintDot(..), printIt)-import Data.GraphViz.Types.Parsing(ParseDot(..), parseIt, preProcess)+import Data.GraphViz.Types(DotRepr, DotGraph, printDotGraph)+import Data.GraphViz.Types.Generalised(generaliseDotGraph)+import Data.GraphViz.Printing(PrintDot(..), printIt)+import Data.GraphViz.Parsing(ParseDot(..), parseIt, preProcess)  import Test.QuickCheck @@ -32,20 +33,28 @@ prop_printParseListID    :: (ParseDot a, PrintDot a, Eq a) => [a] -> Property prop_printParseListID as =  not (null as) ==> prop_printParseID as +-- | When converting a 'DotGraph' value to a 'GDotGraph' one, they+--   should generate the same Dot code.+prop_generalisedSameDot    :: (ParseDot n, PrintDot n) => DotGraph n -> Bool+prop_generalisedSameDot dg = printDotGraph dg == printDotGraph gdg+  where+    gdg = generaliseDotGraph dg+ -- | Pre-processing shouldn't change the output of printed Dot code. --   This should work for all 'PrintDot' instances, but is more --   specific to 'DotGraph' values.-prop_preProcessingID    :: (PrintDot a) => DotGraph a -> Bool+prop_preProcessingID    :: (DotRepr dg n) => dg n -> Bool prop_preProcessingID dg = preProcess dotCode == dotCode   where     dotCode = printDotGraph dg  -- | This is a version of 'prop_printParseID' that tries to parse the --   pretty-printed output of 'prettyPrint'' rather than just 'printIt'.-prop_parsePrettyID    :: (Eq a, ParseDot a, PrintDot a) => DotGraph a -> Bool+prop_parsePrettyID    :: (DotRepr dg n, Eq (dg n), ParseDot (dg n))+                         => dg n -> Bool prop_parsePrettyID dg = (fst . parseIt . prettyPrint') dg == dg --- | This property verifies that 'dotize'', etc. only /augment/ the+-- | This property verifies that 'dotizeGraph'', etc. only /augment/ the --   original graph; that is, the actual nodes, edges and labels for --   each remain unchanged.  Whilst 'dotize'', etc. only require --   'Graph' instances, this property requires 'DynGraph' (which is a
Data/GraphViz/Types.hs view
@@ -1,3 +1,7 @@+{-# LANGUAGE   MultiParamTypeClasses+             , FlexibleInstances+  #-}+ {- |    Module      : Data.GraphViz.Types    Description : Definition of the Graphviz types.@@ -26,7 +30,8 @@      does indeed have a valid 'GraphID'.     * All 'DotSubGraph's with @'isCluster' = 'True'@ /must/ have unique-     'subGraphID' values.+     'subGraphID' values (well, only if you want them to be generated+     sensibly).     * If eventually outputting to a format such as SVG, then you should      make sure that your 'DotGraph' has a 'graphID', as that is used@@ -61,8 +66,10 @@     * Cannot place items in an arbitrary order: in particular, this      means that it is not possible to use the normal Graphviz hack of-     having graph attributes that do not apply to subgraphs\/clusters-     by listing them /after/ the subgraphs\/clusters.+     having graph attributes that do not apply to subgraphs\/clusters by+     listing them /after/ the subgraphs\/clusters.  If you wish to be able+     to use an arbitrary ordering, you may wish to use+     "Data.GraphViz.Types.Generalised".     * The parser will strip out comments and convert multiline strings      into a single line string.  Pre-processor lines (i.e. those@@ -72,16 +79,13 @@  -} module Data.GraphViz.Types-    ( -- * The overall representation of a graph in /Dot/ format.-      DotGraph(..)-      -- ** Printing and parsing a @DotGraph@.+    ( -- * Abstraction from the representation type+      DotRepr(..)+      -- ** Printing and parsing a @DotRepr@.     , printDotGraph     , parseDotGraph-      -- ** Functions acting on a @DotGraph@.-    , setID-    , makeStrict-    , graphNodes-    , graphEdges+      -- * The overall representation of a graph in /Dot/ format.+    , DotGraph(..)       -- ** Reporting of errors in a @DotGraph@.     , DotError(..)     , isValidGraph@@ -95,16 +99,53 @@     , DotEdge(..)     ) where +import Data.GraphViz.Types.Common import Data.GraphViz.Attributes-import Data.GraphViz.Types.Internal-import Data.GraphViz.Types.Parsing-import Data.GraphViz.Types.Printing+import Data.GraphViz.Util+import Data.GraphViz.Parsing+import Data.GraphViz.Printing -import Data.Maybe(isJust)-import Control.Monad(liftM, when)+import Control.Monad(liftM)  -- ----------------------------------------------------------------------------- +-- | This class is used to provide a common interface to different+--   ways of representing a graph in /Dot/ form.+class (PrintDot (dg n), ParseDot (dg n)) => DotRepr dg n where+  -- | Is this graph directed?+  graphIsDirected :: dg n -> Bool++  -- | A strict graph disallows multiple edges.+  makeStrict :: dg n -> dg n++  -- | Set the ID of the graph.+  setID :: GraphID -> dg n -> dg n++  -- | Return all the 'DotNode's contained within this 'DotRepr'.+  --   There is no requirement for it to return implicitly defined+  --   nodes found only within a 'DotEdge'.+  graphNodes :: dg n -> [DotNode n]++  -- | Return all the 'DotEdge's contained within this 'DotRepr'.+  graphEdges :: dg n -> [DotEdge n]++-- | The actual /Dot/ code for an instance of 'DotRepr'.  Note that it+--   is expected that @'parseDotGraph' . 'printDotGraph' == 'id'@+--   (this might not be true the other way around due to un-parseable+--   components).+printDotGraph :: (DotRepr dg n) => dg n -> String+printDotGraph = printIt++-- | Parse a limited subset of the Dot language to form an instance of+--   'DotRepr'.  Each instance may have its own limitations on what+--   may or may not be parseable Dot code.+--+--   Also removes any comments, etc. before parsing.+parseDotGraph :: (DotRepr dg n) => String -> dg n+parseDotGraph = fst . parseIt . preProcess++-- -----------------------------------------------------------------------------+ -- | The internal representation of a graph in Dot form. data DotGraph a = DotGraph { strictGraph     :: Bool  -- ^ If 'True', no multiple edges are drawn.                            , directedGraph   :: Bool@@ -113,37 +154,16 @@                            }                   deriving (Eq, Ord, Show, Read) --- | A strict graph disallows multiple edges.-makeStrict   :: DotGraph a -> DotGraph a-makeStrict g = g { strictGraph = True }---- | Set the ID of the graph.-setID     :: GraphID -> DotGraph a -> DotGraph a-setID i g = g { graphID = Just i }+instance (PrintDot n, ParseDot n) => DotRepr DotGraph n where+  graphIsDirected = directedGraph --- | Return all the 'DotNode's contained within this 'DotGraph'.  Note---   that it does not yet return all implicitly defined nodes---   contained only within 'DotEdge's.-graphNodes :: DotGraph a -> [DotNode a]-graphNodes = statementNodes . graphStatements+  makeStrict g = g { strictGraph = True } --- | Return all the 'DotEdge's contained within this 'DotGraph'.-graphEdges :: DotGraph a -> [DotEdge a]-graphEdges = statementEdges . graphStatements+  setID i g = g { graphID = Just i } --- | The actual /Dot/ code for a 'DotGraph'.  Note that it is expected---   that @'parseDotGraph' . 'printDotGraph' == 'id'@ (this might not---   be true the other way around due to un-parseable components).-printDotGraph :: (PrintDot a) => DotGraph a -> String-printDotGraph = printIt+  graphNodes = statementNodes . graphStatements --- | Parse a limited subset of the Dot language to form a 'DotGraph'---   (that is, the caveats listed in "Data.GraphViz.Attributes" aside,---   Dot graphs are parsed if they match the layout of 'DotGraph').------   Also removes any comments, etc. before parsing.-parseDotGraph :: (ParseDot a) => String -> DotGraph a-parseDotGraph = fst . parseIt . preProcess+  graphEdges = statementEdges . graphStatements  -- | Check if all the 'Attribute's are being used correctly. isValidGraph :: DotGraph a -> Bool@@ -154,48 +174,17 @@ graphErrors = invalidStmts usedByGraphs . graphStatements  instance (PrintDot a) => PrintDot (DotGraph a) where-    unqtDot = printStmtBased printGraphID graphStatements--printGraphID   :: (PrintDot a) => DotGraph a -> DotCode-printGraphID g = bool empty strGraph' (strictGraph g)-                 <+> bool undirGraph' dirGraph' (directedGraph g)-                 <+> maybe empty toDot (graphID g)+  unqtDot = printStmtBased printGraphID' graphStatements toDot+    where+      printGraphID' = printGraphID strictGraph directedGraph graphID  instance (ParseDot a) => ParseDot (DotGraph a) where-    parseUnqt = parseStmtBased parseGraphID+    parseUnqt = parseStmtBased parse (parseGraphID DotGraph)      parse = parseUnqt -- Don't want the option of quoting             `adjustErr`             (++ "\n\nNot a valid DotGraph") -parseGraphID :: (ParseDot a) => Parse (DotStatements a -> DotGraph a)-parseGraphID = do str <- liftM isJust-                         $ optional (parseAndSpace $ string strGraph)-                  dir <- parseAndSpace ( stringRep True dirGraph-                                         `onFail`-                                         stringRep False undirGraph-                                       )-                  gID <- optional $ parseAndSpace parse-                  return $ DotGraph str dir gID--dirGraph :: String-dirGraph = "digraph"--dirGraph' :: DotCode-dirGraph' = text dirGraph--undirGraph :: String-undirGraph = "graph"--undirGraph' :: DotCode-undirGraph' = text undirGraph--strGraph :: String-strGraph = "strict"--strGraph' :: DotCode-strGraph' = text strGraph- instance Functor DotGraph where     fmap f g = g { graphStatements = fmap f $ graphStatements g } @@ -211,45 +200,6 @@  -- ----------------------------------------------------------------------------- --- | A polymorphic type that covers all possible ID values allowed by---   Dot syntax.  Note that whilst the 'ParseDot' and 'PrintDot'---   instances for 'String' will properly take care of the special---   cases for numbers, they are treated differently here.-data GraphID = Str String-             | Int Int-             | Dbl Double-             | HTML URL-               deriving (Eq, Ord, Show, Read)--instance PrintDot GraphID where-    unqtDot (Str str) = unqtDot str-    unqtDot (Int i)   = unqtDot i-    unqtDot (Dbl d)   = unqtDot d-    unqtDot (HTML u)  = unqtDot u--    toDot (Str str) = toDot str-    toDot gID       = unqtDot gID--instance ParseDot GraphID where-    parseUnqt = liftM HTML parseUnqt-                `onFail`-                liftM stringNum parseUnqt--    parse = liftM HTML parse-            `onFail`-            liftM stringNum parse-            `adjustErr`-            (++ "\nNot a valid GraphID")--stringNum     :: String -> GraphID-stringNum str = maybe checkDbl Int $ stringToInt str-  where-    checkDbl = if isNumString str-               then Dbl $ toDouble str-               else Str str---- ------------------------------------------------------------------------------ data DotStatements a = DotStmts { attrStmts :: [GlobalAttributes]                                 , subGraphs :: [DotSubGraph a]                                 , nodeStmts :: [DotNode a]@@ -258,10 +208,10 @@                      deriving (Eq, Ord, Show, Read)  instance (PrintDot a) => PrintDot (DotStatements a) where-    unqtDot stmts = vcat [ toDot $ attrStmts stmts-                         , toDot $ subGraphs stmts-                         , toDot $ nodeStmts stmts-                         , toDot $ edgeStmts stmts+    unqtDot stmts = vcat [ unqtDot $ attrStmts stmts+                         , unqtDot $ subGraphs stmts+                         , unqtDot $ nodeStmts stmts+                         , unqtDot $ edgeStmts stmts                          ]  instance (ParseDot a) => ParseDot (DotStatements a) where@@ -284,37 +234,6 @@                          , edgeStmts = map (fmap f) $ edgeStmts stmts                          } -printStmtBased          :: (PrintDot n) => (a -> DotCode)-                           -> (a -> DotStatements n) -> a -> DotCode-printStmtBased ff fss a = vcat [ ff a <+> lbrace-                               , ind stmts-                               , rbrace-                               ]-    where-      ind = nest 4-      stmts = toDot $ fss a--printStmtBasedList        :: (PrintDot n) => (a -> DotCode)-                             -> (a -> DotStatements n) -> [a] -> DotCode-printStmtBasedList ff fss = vcat . map (printStmtBased ff fss)--parseStmtBased   :: (ParseDot n) => Parse (DotStatements n -> a) -> Parse a-parseStmtBased p = do f <- p-                      whitespace'-                      character '{'-                      newline'-                      stmts <- parse-                      newline'-                      whitespace'-                      character '}'-                      return $ f stmts-                   `adjustErr`-                   (++ "\n\nNot a valid statement-based structure")--parseStmtBasedList   :: (ParseDot n) => Parse (DotStatements n -> a)-                        -> Parse [a]-parseStmtBasedList p = sepBy (whitespace' >> parseStmtBased p) newline'- -- | The function represents which function to use to check the --   'GraphAttrs' values. invalidStmts         :: (Attribute -> Bool) -> DotStatements a -> [DotError a]@@ -403,103 +322,26 @@                    deriving (Eq, Ord, Show, Read)  instance (PrintDot a) => PrintDot (DotSubGraph a) where-    unqtDot = printStmtBased printSubGraphID subGraphStmts+    unqtDot = printStmtBased printSubGraphID' subGraphStmts toDot -    unqtListToDot = printStmtBasedList printSubGraphID subGraphStmts+    unqtListToDot = printStmtBasedList printSubGraphID' subGraphStmts toDot      listToDot = unqtListToDot -printSubGraphID   :: DotSubGraph a -> DotCode-printSubGraphID s = sGraph'-                    <+> maybe cl dtID (subGraphID s)-    where-      isCl = isCluster s-      cl = bool empty clust' isCl-      dtID = printSGID isCl---- | Print the actual ID for a 'DotSubGraph'.-printSGID          :: Bool -> GraphID -> DotCode-printSGID isCl sID = bool noClust addClust isCl-    where-      noClust = toDot sID-      -- Have to manually render it as we need the un-quoted form.-      addClust = toDot . (++) clust . (:) '_'-                 . renderDot $ mkDot sID-      mkDot (Str str) = text str -- Quotes will be escaped later-      mkDot gid       = unqtDot gid+printSubGraphID' :: DotSubGraph a -> DotCode+printSubGraphID' = printSubGraphID (\sg -> (isCluster sg, subGraphID sg))  instance (ParseDot a) => ParseDot (DotSubGraph a) where-    parseUnqt = parseStmtBased parseSubGraphID+    parseUnqt = parseStmtBased parseUnqt (parseSubGraphID DotSG)      parse = parseUnqt -- Don't want the option of quoting             `adjustErr`             (++ "\n\nNot a valid Sub Graph") -    parseUnqtList = parseStmtBasedList parseSubGraphID+    parseUnqtList = parseStmtBasedList parseUnqt (parseSubGraphID DotSG)      parseList = parseUnqtList -parseSubGraphID :: Parse (DotStatements a -> DotSubGraph a)-parseSubGraphID = do string sGraph-                     whitespace-                     liftM (uncurry DotSG) parseSGID--parseSGID :: Parse (Bool, Maybe GraphID)-parseSGID = oneOf [ liftM getClustFrom $ parseAndSpace parse-                  , return (False, Nothing)-                  ]-  where-    -- If it's a String value, check to see if it's actually a-    -- cluster_Blah value; thus need to manually re-parse it.-    getClustFrom (Str str) = fst $ runParser pStr str-    getClustFrom gid       = (False, Just gid)--    checkCl = stringRep True clust-    pStr = do isCl <- checkCl-                      `onFail`-                      return False-              when isCl $ optional (character '_') >> return ()-              sID <- optional pID-              let sID' = if sID == emptyID-                         then Nothing-                         else sID-              return (isCl, sID')--    emptyID = Just $ Str ""--    -- For Strings, there are no more quotes to unescape, so consume-    -- what you can.-    pID = liftM HTML parseUnqt-                `onFail`-                liftM stringNum (many next)--{- This is a much nicer result, but unfortunately it doesn't work.-   The problem is that Graphviz decides that a subgraph is a cluster-   if the ID starts with "cluster" (no quotes); thus, we _have_ to do-   the double layer of parsing to get it to work :@--            do isCl <- stringRep True clust-                       `onFail`-                       return False-               sID <- optional $ do when isCl-                                      $ optional (character '_') >> return ()-                                    parseUnqt-               when (isCl || isJust sID) $ whitespace >> return ()-               return (isCl, sID)--}--sGraph :: String-sGraph = "subgraph"--sGraph' :: DotCode-sGraph' = text sGraph--clust :: String-clust = "cluster"--clust' :: DotCode-clust' = text clust- instance Functor DotSubGraph where     fmap f sg = sg { subGraphStmts = fmap f $ subGraphStmts sg } @@ -516,9 +358,7 @@  -- ----------------------------------------------------------------------------- --- | A node in 'DotGraph' is either a singular node, or a cluster---   containing nodes (or more clusters) within it.---   At the moment, clusters are not parsed.+-- | A node in 'DotGraph'. data DotNode a = DotNode { nodeID :: a                          , nodeAttributes :: Attributes                          }
+ Data/GraphViz/Types/Common.hs view
@@ -0,0 +1,216 @@+{-# OPTIONS_HADDOCK hide #-}++{- |+   Module      : Data.GraphViz.Types.Common+   Description : Common internal functions for dealing with overall types.+   Copyright   : (c) Ivan Lazar Miljenovic+   License     : 3-Clause BSD-style+   Maintainer  : Ivan.Miljenovic@gmail.com++   This module provides common functions used by both+   "Data.GraphViz.Types" as well as "Data.GraphViz.Types.Generalised".+-}+module Data.GraphViz.Types.Common where++import Data.GraphViz.Parsing+import Data.GraphViz.Printing+import Data.GraphViz.Util+import Data.GraphViz.Attributes(URL)++import Data.Maybe(isJust)+import Control.Monad(liftM, when)++-- -----------------------------------------------------------------------------+-- This is re-exported by Data.GraphViz.Types++-- | A polymorphic type that covers all possible ID values allowed by+--   Dot syntax.  Note that whilst the 'ParseDot' and 'PrintDot'+--   instances for 'String' will properly take care of the special+--   cases for numbers, they are treated differently here.+data GraphID = Str String+             | Int Int+             | Dbl Double+             | HTML URL+               deriving (Eq, Ord, Show, Read)++instance PrintDot GraphID where+    unqtDot (Str str) = unqtDot str+    unqtDot (Int i)   = unqtDot i+    unqtDot (Dbl d)   = unqtDot d+    unqtDot (HTML u)  = unqtDot u++    toDot (Str str) = toDot str+    toDot gID       = unqtDot gID++instance ParseDot GraphID where+    parseUnqt = liftM HTML parseUnqt+                `onFail`+                liftM stringNum parseUnqt++    parse = liftM HTML parse+            `onFail`+            liftM stringNum parse+            `adjustErr`+            (++ "\nNot a valid GraphID")++stringNum     :: String -> GraphID+stringNum str = maybe checkDbl Int $ stringToInt str+  where+    checkDbl = if isNumString str+               then Dbl $ toDouble str+               else Str str++-- -----------------------------------------------------------------------------+-- Labels++dirGraph :: String+dirGraph = "digraph"++dirGraph' :: DotCode+dirGraph' = text dirGraph++undirGraph :: String+undirGraph = "graph"++undirGraph' :: DotCode+undirGraph' = text undirGraph++strGraph :: String+strGraph = "strict"++strGraph' :: DotCode+strGraph' = text strGraph++sGraph :: String+sGraph = "subgraph"++sGraph' :: DotCode+sGraph' = text sGraph++clust :: String+clust = "cluster"++clust' :: DotCode+clust' = text clust++-- -----------------------------------------------------------------------------++printGraphID                 :: (a -> Bool) -> (a -> Bool)+                                -> (a -> Maybe GraphID)+                                -> a -> DotCode+printGraphID str isDir mID g = bool empty strGraph' (str g)+                               <+> bool undirGraph' dirGraph' (isDir g)+                               <+> maybe empty toDot (mID g)++parseGraphID   :: (Bool -> Bool -> Maybe GraphID -> a) -> Parse a+parseGraphID f = do str <- liftM isJust+                           $ optional (parseAndSpace $ string strGraph)+                    dir <- parseAndSpace ( stringRep True dirGraph+                                           `onFail`+                                           stringRep False undirGraph+                                         )+                    gID <- optional $ parseAndSpace parse+                    return $ f str dir gID++printStmtBased          :: (a -> DotCode) -> (a -> b) -> (b -> DotCode)+                           -> a -> DotCode+printStmtBased f r dr a = printBracesBased (f a) (dr $ r a)++printStmtBasedList        :: (a -> DotCode) -> (a -> b) -> (b -> DotCode)+                             -> [a] -> DotCode+printStmtBasedList f r dr = vcat . map (printStmtBased f r dr)++parseStmtBased :: Parse stmt -> Parse (stmt -> a) -> Parse a+parseStmtBased = flip apply . parseBracesBased++parseStmtBasedList       :: Parse stmt -> Parse (stmt -> a) -> Parse [a]+parseStmtBasedList ps pr = sepBy (whitespace' >> parseStmtBased ps pr) newline'++printBracesBased     :: DotCode -> DotCode -> DotCode+printBracesBased h i = vcat [ h <+> lbrace+                            , ind i+                            , rbrace+                            ]+  where+    ind = nest 4++parseBracesBased   :: Parse a -> Parse a+parseBracesBased p = do whitespace'+                        character '{'+                        newline'+                        a <- p+                        newline'+                        whitespace'+                        character '}'+                        return a+                     `adjustErr`+                     (++ "\nNot a valid value wrapped in braces.")+++printSubGraphID     :: (a -> (Bool, Maybe GraphID)) -> a -> DotCode+printSubGraphID f a = sGraph'+                      <+> maybe cl dtID mID+  where+    (isCl, mID) = f a+    cl = bool empty clust' isCl+    dtID = printSGID isCl++-- | Print the actual ID for a 'DotSubGraph'.+printSGID          :: Bool -> GraphID -> DotCode+printSGID isCl sID = bool noClust addClust isCl+  where+    noClust = toDot sID+    -- Have to manually render it as we need the un-quoted form.+    addClust = toDot . (++) clust . (:) '_'+               . renderDot $ mkDot sID+    mkDot (Str str) = text str -- Quotes will be escaped later+    mkDot gid       = unqtDot gid++parseSubGraphID   :: (Bool -> Maybe GraphID -> c) -> Parse c+parseSubGraphID f = do string sGraph+                       whitespace+                       liftM (uncurry f) parseSGID++parseSGID :: Parse (Bool, Maybe GraphID)+parseSGID = oneOf [ liftM getClustFrom $ parseAndSpace parse+                  , return (False, Nothing)+                  ]+  where+    -- If it's a String value, check to see if it's actually a+    -- cluster_Blah value; thus need to manually re-parse it.+    getClustFrom (Str str) = fst $ runParser pStr str+    getClustFrom gid       = (False, Just gid)++    checkCl = stringRep True clust+    pStr = do isCl <- checkCl+                      `onFail`+                      return False+              when isCl $ optional (character '_') >> return ()+              sID <- optional pID+              let sID' = if sID == emptyID+                         then Nothing+                         else sID+              return (isCl, sID')++    emptyID = Just $ Str ""++    -- For Strings, there are no more quotes to unescape, so consume+    -- what you can.+    pID = liftM HTML parseUnqt+                `onFail`+                liftM stringNum (many next)++{- This is a much nicer definition, but unfortunately it doesn't work.+   The problem is that Graphviz decides that a subgraph is a cluster+   if the ID starts with "cluster" (no quotes); thus, we _have_ to do+   the double layer of parsing to get it to work :@++            do isCl <- stringRep True clust+                       `onFail`+                       return False+               sID <- optional $ do when isCl+                                      $ optional (character '_') >> return ()+                                    parseUnqt+               when (isCl || isJust sID) $ whitespace >> return ()+               return (isCl, sID)+-}
+ Data/GraphViz/Types/Generalised.hs view
@@ -0,0 +1,205 @@+{-# LANGUAGE   MultiParamTypeClasses+             , FlexibleInstances+  #-}++{- |+   Module      : Data.GraphViz.Types.Generalised.+   Description : Alternate definition of the Graphviz types.+   Copyright   : (c) Ivan Lazar Miljenovic+   License     : 3-Clause BSD-style+   Maintainer  : Ivan.Miljenovic@gmail.com++   This module provides an alternate definition of the types found in+   "Data.GraphViz.Types", in that the ordering constraint found in+   'DotStatements' is no longer present.  All other+   limitations\/constraints are still present however.++   The types here have the same names as those in+   "Data.GraphViz.Types" but with a prefix of @\"G\"@.++   This module is partially experimental, and may change in the+   future.+-}+module Data.GraphViz.Types.Generalised+       ( -- * The overall representation of a graph in generalised /Dot/ format.+         GDotGraph(..)+         -- * Sub-components of a @GDotGraph@.+       , GDotStatements+       , GDotStatement(..)+       , GDotSubGraph(..)+         -- ** Re-exported from @Data.GraphViz.Types@.+       , GraphID(..)+       , GlobalAttributes(..)+       , DotNode(..)+       , DotEdge(..)+         -- * Conversion from a @DotGraph@.+       , generaliseDotGraph+       ) where++import Data.GraphViz.Types hiding (GraphID(..))+import Data.GraphViz.Types.Common+import Data.GraphViz.Parsing+import Data.GraphViz.Printing++import qualified Data.Sequence as Seq+import Data.Sequence(Seq, (><))+import qualified Data.Foldable as F+import Control.Monad(liftM)++-- -----------------------------------------------------------------------------++-- | The internal representation of a generalised graph in Dot form.+data GDotGraph a = GDotGraph { gStrictGraph     :: Bool  -- ^ If 'True', no multiple edges are drawn.+                             , gDirectedGraph   :: Bool+                             , gGraphID         :: Maybe GraphID+                             , gGraphStatements :: GDotStatements a+                             }+                 deriving (Eq, Ord, Show, Read)++instance (PrintDot n, ParseDot n) => DotRepr GDotGraph n where+  graphIsDirected = gDirectedGraph++  makeStrict g = g { gStrictGraph = True }++  setID i g = g { gGraphID = Just i }++  graphNodes = statementNodes . gGraphStatements++  graphEdges = statementEdges . gGraphStatements++instance (PrintDot a) => PrintDot (GDotGraph a) where+  unqtDot = printStmtBased printGraphID' gGraphStatements printGStmts+    where+      printGraphID' = printGraphID gStrictGraph gDirectedGraph gGraphID++instance (ParseDot a) => ParseDot (GDotGraph a) where+    parseUnqt = parseStmtBased parseGStmts (parseGraphID GDotGraph)++    parse = parseUnqt -- Don't want the option of quoting+            `adjustErr`+            (++ "\n\nNot a valid DotGraph")++instance Functor GDotGraph where+    fmap f g = g { gGraphStatements = (fmap . fmap) f $ gGraphStatements g }++-- | Convert a 'DotGraph' to a 'GDotGraph', keeping the same order of+--   statements.+generaliseDotGraph    :: DotGraph a -> GDotGraph a+generaliseDotGraph dg = GDotGraph { gStrictGraph = strictGraph dg+                                  , gDirectedGraph = directedGraph dg+                                  , gGraphID = graphID dg+                                  , gGraphStatements = generaliseStatements+                                                       $ graphStatements dg+                                  }++-- -----------------------------------------------------------------------------++type GDotStatements a = Seq (GDotStatement a)++printGStmts :: (PrintDot a) => GDotStatements a -> DotCode+printGStmts = vcat . map toDot . F.toList++parseGStmts :: (ParseDot a) => Parse (GDotStatements a)+parseGStmts = liftM Seq.fromList $ many p+  where+    p = whitespace' >> parse `discard` newline'++statementNodes :: GDotStatements a -> [DotNode a]+statementNodes = concatMap stmtNodes . F.toList++statementEdges :: GDotStatements a -> [DotEdge a]+statementEdges = concatMap stmtEdges . F.toList++generaliseStatements       :: DotStatements a -> GDotStatements a+generaliseStatements stmts = atts >< sgs >< ns >< es+  where+    atts = Seq.fromList . map GA $ attrStmts stmts+    sgs = Seq.fromList . map (SG . generaliseSubGraph) $ subGraphs stmts+    ns = Seq.fromList . map DN $ nodeStmts stmts+    es = Seq.fromList . map DE $ edgeStmts stmts+++data GDotStatement a = GA GlobalAttributes+                     | SG (GDotSubGraph a)+                     | DN (DotNode a)+                     | DE (DotEdge a)+                     deriving (Eq, Ord, Show, Read)++instance (PrintDot a) => PrintDot (GDotStatement a) where+  unqtDot (GA ga) = unqtDot ga+  unqtDot (SG sg) = unqtDot sg+  unqtDot (DN dn) = unqtDot dn+  unqtDot (DE de) = unqtDot de++instance (ParseDot a) => ParseDot (GDotStatement a) where+  parseUnqt = oneOf [ liftM GA parseUnqt+                    , liftM SG parseUnqt+                    , liftM DN parseUnqt+                    , liftM DE parseUnqt+                    ]++  parse = parseUnqt -- Don't want the option of quoting+          `adjustErr`+          (++ "Not a valid statement")++instance Functor GDotStatement where+  fmap _ (GA ga) = GA ga -- Have to re-make this to make the type checker happy.+  fmap f (SG sg) = SG $ fmap f sg+  fmap f (DN dn) = DN $ fmap f dn+  fmap f (DE de) = DE $ fmap f de++stmtNodes         :: GDotStatement a -> [DotNode a]+stmtNodes (SG sg) = subGraphNodes sg+stmtNodes (DN dn) = [dn]+stmtNodes _       = []++stmtEdges         :: (GDotStatement a) -> [DotEdge a]+stmtEdges (SG sg) = subGraphEdges sg+stmtEdges (DE de) = [de]+stmtEdges _       = []++-- -----------------------------------------------------------------------------++data GDotSubGraph a = GDotSG { gIsCluster     :: Bool+                             , gSubGraphID    :: Maybe GraphID+                             , gSubGraphStmts :: GDotStatements a+                             }+                    deriving (Eq, Ord, Show, Read)++instance (PrintDot a) => PrintDot (GDotSubGraph a) where+  unqtDot = printStmtBased printSubGraphID' gSubGraphStmts printGStmts++  unqtListToDot = printStmtBasedList printSubGraphID' gSubGraphStmts printGStmts++  listToDot = unqtListToDot++printSubGraphID' :: GDotSubGraph a -> DotCode+printSubGraphID' = printSubGraphID (\sg -> (gIsCluster sg, gSubGraphID sg))++instance (ParseDot a) => ParseDot (GDotSubGraph a) where+  parseUnqt = parseStmtBased parseGStmts (parseSubGraphID GDotSG)++  parse = parseUnqt -- Don't want the option of quoting+          `adjustErr`+          (++ "\n\nNot a valid Sub Graph")++  parseUnqtList = parseStmtBasedList parseGStmts (parseSubGraphID GDotSG)++  parseList = parseUnqtList++instance Functor GDotSubGraph where+    fmap f sg = sg { gSubGraphStmts = (fmap . fmap) f $ gSubGraphStmts sg }++subGraphNodes :: GDotSubGraph a -> [DotNode a]+subGraphNodes = statementNodes . gSubGraphStmts++subGraphEdges :: GDotSubGraph a -> [DotEdge a]+subGraphEdges = statementEdges . gSubGraphStmts++generaliseSubGraph                       :: DotSubGraph a -> GDotSubGraph a+generaliseSubGraph (DotSG isC mID stmts) = GDotSG { gIsCluster     = isC+                                                  , gSubGraphID    = mID+                                                  , gSubGraphStmts = stmts'+                                                  }+  where+    stmts' = generaliseStatements stmts
− Data/GraphViz/Types/Internal.hs
@@ -1,119 +0,0 @@-{- |-   Module      : Data.GraphViz.Types.Internal-   Description : Internal functions-   Copyright   : (c) Ivan Lazar Miljenovic-   License     : 3-Clause BSD-style-   Maintainer  : Ivan.Miljenovic@gmail.com--   This module defines internal functions.--}-module Data.GraphViz.Types.Internal where--import Data.Char( isAsciiUpper-                , isAsciiLower-                , isDigit-                , toLower-                )--import Data.Maybe(isJust)-import qualified Data.Set as Set-import Data.Set(Set)-import Control.Monad(liftM2)--isIDString        :: String -> Bool-isIDString []     = True-isIDString (f:os) = frstIDString f-                    && all restIDString os---- | First character of a non-quoted 'String' must match this.-frstIDString   :: Char -> Bool-frstIDString c = any ($c) [ isAsciiUpper-                          , isAsciiLower-                          , (==) '_'-                          , liftM2 (&&) (>= '\200') (<= '\377')-                          ]---- | The rest of a non-quoted 'String' must match this.-restIDString   :: Char -> Bool-restIDString c = frstIDString c || isDigit c---- | Determine if this String represents a number.-isNumString     :: String -> Bool-isNumString ""  = False-isNumString "-" = False-isNumString str = case str of-                    ('-':str') -> go str'-                    _          -> go str-    where-      go s = case span isDigit (map toLower s) of-               (ds@(_:_),[]) -> all isDigit ds-               ([],'.':[])   -> False-               ([],'.':d:ds) -> isDigit d && checkEs' ds-               (_,'.':ds)    -> checkEs $ dropWhile isDigit ds-               ([],_)        -> False-               (_,ds)        -> checkEs ds-      checkEs' s = case break ((==) 'e') s of-                     ([], _) -> False-                     (ds,es) -> all isDigit ds && checkEs es-      checkEs []       = True-      checkEs ('e':ds) = isIntString ds-      checkEs _        = False---- | This assumes that 'isNumString' is 'True'.-toDouble     :: String -> Double-toDouble str = case str of-                 ('-':str') -> read $ '-' : adj str'-                 _          -> read $ adj str-  where-    adj s = (:) '0'-            $ case span ((==) '.') (map toLower s) of-                (ds@(_:_), '.':[])   -> ds ++ '.' : '0' : []-                (ds, '.':es@('e':_)) -> ds ++ '.' : '0' : es-                _                    -> s--isIntString :: String -> Bool-isIntString = isJust . stringToInt---- | Determine if this String represents an integer.-stringToInt     :: String -> Maybe Int-stringToInt str = if isNum-                  then Just (read str)-                  else Nothing-  where-    isNum = case str of-              ""        -> False-              ['-']     -> False-              ('-':num) -> isNum' num-              _         -> isNum' str-    isNum' = all isDigit---- | Graphviz requires double quotes to be explicitly escaped.-escapeQuotes           :: String -> String-escapeQuotes []        = []-escapeQuotes ('"':str) = '\\':'"': escapeQuotes str-escapeQuotes (c:str)   = c : escapeQuotes str---- | Remove explicit escaping of double quotes.-descapeQuotes                :: String -> String-descapeQuotes []             = []-descapeQuotes ('\\':'"':str) = '"' : descapeQuotes str-descapeQuotes (c:str)        = c : descapeQuotes str--isKeyword :: String -> Bool-isKeyword = flip Set.member keywords . map toLower---- | The following are Dot keywords and are not valid as labels, etc. unquoted.-keywords :: Set String-keywords = Set.fromList [ "node"-                        , "edge"-                        , "graph"-                        , "digraph"-                        , "subgraph"-                        , "strict"-                        ]---- | Fold over 'Bool's; first param is for 'False', second for 'True'.-bool       :: a -> a -> Bool -> a-bool f t b = if b-             then t-             else f
− Data/GraphViz/Types/Parsing.hs
@@ -1,395 +0,0 @@-{- |-   Module      : Data.GraphViz.Types.Parsing-   Description : Helper functions for Parsing.-   Copyright   : (c) Matthew Sackman, Ivan Lazar Miljenovic-   License     : 3-Clause BSD-style-   Maintainer  : Ivan.Miljenovic@gmail.com--   This module defines simple helper functions for use with-   "Text.ParserCombinators.Poly.Lazy".--   Note that the 'ParseDot' instances for 'Bool', etc. match those-   specified for use with Graphviz (e.g. non-zero integers are-   equivalent to 'True').--   You should not be using this module; rather, it is here for-   informative/documentative reasons.  If you want to parse a-   @'Data.GraphViz.Types.DotGraph'@, you should use-   @'Data.GraphViz.Types.parseDotGraph'@ rather than its 'ParseDot'-   instance.--}-module Data.GraphViz.Types.Parsing-    ( -- * Re-exporting pertinent parts of Polyparse.-      module Text.ParserCombinators.Poly.Lazy-      -- * The ParseDot class.-    , Parse-    , ParseDot(..)-    , parseIt-      -- * Convenience parsing combinators.-    , onlyBool-    , stringBlock-    , numString-    , isNumString-    , isIntString-    , quotedString-    , parseAndSpace-    , string-    , strings-    , hasString-    , character-    , parseStrictFloat-    , noneOf-    , whitespace-    , whitespace'-    , optionalQuotedString-    , optionalQuoted-    , quotedParse-    , orQuote-    , quoteChar-    , newline-    , newline'-    , parseComma-    , tryParseList-    , tryParseList'-    , skipToNewline-    , parseField-    , parseFields-    , parseFieldBool-    , parseFieldsBool-    , parseFieldDef-    , parseFieldsDef-    , commaSep-    , commaSepUnqt-    , commaSep'-    , stringRep-    , stringReps-    -- * Pre-processing of Dot code to remove comments, etc.-    , preProcess-    ) where--import Data.GraphViz.Types.Internal--import Text.ParserCombinators.Poly.Lazy-import Data.Char( digitToInt-                , isDigit-                , isSpace-                , toLower-                )-import Data.Function(on)-import Data.Maybe(isJust, fromMaybe, isNothing)-import Data.Ratio((%))-import Data.Word(Word8)-import Control.Monad(liftM, when)---- -------------------------------------------------------------------------------- Based off code from Text.Parse in the polyparse library---- | A @ReadS@-like type alias.-type Parse a = Parser Char a--class ParseDot a where-    parseUnqt :: Parse a--    parse :: Parse a-    parse = optionalQuoted parseUnqt--    parseUnqtList :: Parse [a]-    parseUnqtList = bracketSep (parseAndSpace $ character '[')-                               (parseAndSpace $ parseComma)-                               (parseAndSpace $ character ']')-                               (parseAndSpace parse)--    parseList :: Parse [a]-    parseList = quotedParse parseUnqtList---- | Parse the required value, returning also the rest of the input---   'String' that hasn't been parsed.-parseIt :: (ParseDot a) => String -> (a, String)-parseIt = runParser parse--instance ParseDot Int where-    parseUnqt = parseInt'--instance ParseDot Word8 where-    parseUnqt = parseInt--instance ParseDot Double where-    parseUnqt = parseFloat'--instance ParseDot Bool where-    parseUnqt = onlyBool-                `onFail`-                liftM (zero /=) parseInt'-        where-          zero :: Int-          zero = 0---- | Use this when you do not want numbers to be treated as 'Bool' values.-onlyBool :: Parse Bool-onlyBool = oneOf [ stringRep True "true"-                 , stringRep False "false"-                 ]--instance ParseDot Char where-    -- Can't be a quote character.-    parseUnqt = satisfy ((/=) quoteChar)--    parse = satisfy restIDString-            `onFail`-            quotedParse parseUnqt--    -- Too many problems with using this within other parsers where-    -- using numString or stringBlock will cause a parse failure.  As-    -- such, this will successfully parse all un-quoted Strings.-    parseUnqtList = quotedString--    parseList = oneOf [numString, stringBlock]-                `onFail`-                -- This will also take care of quoted versions of-                -- above.-                quotedParse quotedString--instance (ParseDot a) => ParseDot [a] where-    parseUnqt = parseUnqtList--    parse = parseList--numString :: Parse String-numString = liftM show parseStrictFloat-            `onFail`-            liftM show parseInt'--stringBlock :: Parse String-stringBlock = do frst <- satisfy frstIDString-                 rest <- many (satisfy restIDString)-                 return $ frst : rest---- | Used when quotes are explicitly required;-quotedString :: Parse String-quotedString = many stringInterior--stringInterior :: Parse Char-stringInterior = orQuote $ satisfy ((/=) quoteChar)--parseSigned :: Real a => Parse a -> Parse a-parseSigned p = (character '-' >> liftM negate p)-                `onFail`-                p--parseInt :: (Integral a) => Parse a-parseInt = do cs <- many1 (satisfy isDigit)-              return (foldl1 (\n d-> n*radix+d)-                                   (map (fromIntegral . digitToInt) cs))-           `adjustErr` (++ "\nexpected one or more digits")-    where-      radix = 10--parseInt' :: Parse Int-parseInt' = parseSigned parseInt---- | Parse a floating point number that actually contains decimals.-parseStrictFloat :: Parse Double-parseStrictFloat = parseSigned parseFloat--parseFloat :: (RealFrac a) => Parse a-parseFloat = do ds   <- many (satisfy isDigit)-                frac <- optional-                        $ do character '.'-                             many1 (satisfy isDigit)-                               `adjustErr` (++ "\nexpected digit after .")-                when (isNothing frac && null ds)-                  (fail "No actual digits in floating point number!")-                expn  <- optional parseExp-                when (isNothing frac && isNothing expn)-                  (fail "This is an integer, not a floating point number!")-                let frac' = fromMaybe "" frac-                    expn' = fromMaybe 0 expn-                ( return . fromRational . (* (10^^(expn' - length frac')))-                  . (%1) . fst-                  . runParser parseInt) (ds++frac')-             `onFail`-             fail "Expected a floating point number"-    where parseExp = do character 'e'-                        ((character '+' >> parseInt)-                         `onFail`-                         parseInt')--parseFloat' :: Parse Double-parseFloat' = parseSigned ( parseFloat-                            `onFail`-                            liftM fI parseInt-                          )-    where-      fI :: Integer -> Double-      fI = fromIntegral---- -------------------------------------------------------------------------------parseAndSpace   :: Parse a -> Parse a-parseAndSpace p = p `discard` whitespace'--string :: String -> Parse String-string = mapM character--stringRep   :: a -> String -> Parse a-stringRep v = stringReps v . return--stringReps      :: a -> [String] -> Parse a-stringReps v ss = oneOf (map string ss) >> return v--strings :: [String] -> Parse String-strings = oneOf . map string--hasString :: String -> Parse Bool-hasString = liftM isJust . optional . string--character   :: Char -> Parse Char-character c = satisfy (((==) `on` toLower) c)-              `adjustErr`-              (++ "\nnot the expected char: " ++ [c])--noneOf :: (Eq a) => [a] -> Parser a a-noneOf t = satisfy (\x -> all (/= x) t)--whitespace :: Parse String-whitespace = many1 (satisfy isSpace)--whitespace' :: Parse String-whitespace' = many (satisfy isSpace)--optionalQuotedString :: String -> Parse String-optionalQuotedString = optionalQuoted . string--optionalQuoted   :: Parse a -> Parse a-optionalQuoted p = quotedParse p-                   `onFail`-                   p--quotedParse :: Parse a -> Parse a-quotedParse = bracket parseQuote parseQuote--parseQuote :: Parse Char-parseQuote = character quoteChar--orQuote   :: Parse Char -> Parse Char-orQuote p = stringRep quoteChar "\\\""-            `onFail`-            p--quoteChar :: Char-quoteChar = '"'--newline :: Parse String-newline = oneOf $ map string ["\r\n", "\n", "\r"]---- | Consume all whitespace and newlines until a line with---   non-whitespace is reached.  The whitespace on that line is---   not consumed.-newline' :: Parse ()-newline' = many (whitespace' >> newline) >> return ()---- | Parses and returns all characters up till the end of the line,---   then skips to the beginning of the next line.-skipToNewline :: Parse String-skipToNewline = many (noneOf ['\n','\r']) `discard` newline--parseField     :: (ParseDot a) => String -> Parse a-parseField fld = do string fld-                    whitespace'-                    character '='-                    whitespace'-                    parse--parseFields :: (ParseDot a) => [String] -> Parse a-parseFields = oneOf . map parseField--parseFieldBool :: String -> Parse Bool-parseFieldBool = parseFieldDef True--parseFieldsBool :: [String] -> Parse Bool-parseFieldsBool = oneOf . map parseFieldBool---- | For 'Bool'-like data structures where the presence of the field---   name without a value implies a default value.-parseFieldDef       :: (ParseDot a) => a -> String -> Parse a-parseFieldDef d fld = parseField fld-                      `onFail`-                      stringRep d fld--parseFieldsDef   :: (ParseDot a) => a -> [String] -> Parse a-parseFieldsDef d = oneOf . map (parseFieldDef d)--commaSep :: (ParseDot a, ParseDot b) => Parse (a, b)-commaSep = commaSep' parse parse--commaSepUnqt :: (ParseDot a, ParseDot b) => Parse (a, b)-commaSepUnqt = commaSep' parseUnqt parseUnqt--commaSep'       :: Parse a -> Parse b -> Parse (a,b)-commaSep' pa pb = do a <- pa-                     whitespace'-                     parseComma-                     whitespace'-                     b <- pb-                     return (a,b)--parseComma :: Parse Char-parseComma = character ','--tryParseList :: (ParseDot a) => Parse [a]-tryParseList = tryParseList' parse--tryParseList' :: Parse [a] -> Parse [a]-tryParseList' = liftM (fromMaybe []) . optional---- -------------------------------------------------------------------------------- Filtering out unwanted Dot items such as comments---- | Remove unparseable features of Dot, such as comments and---   multi-line strings (which are converted to single-line strings).-preProcess :: String -> String-preProcess = fst . runParser parseOutUnwanted-             -- snd should be null---- | Parse out comments and make quoted strings spread over multiple---   lines only over a single line.  Should parse the /entire/ input---   'String'.-parseOutUnwanted :: Parse String-parseOutUnwanted = liftM concat (many getNext)-    where-      getNext :: Parse String-      getNext = parseSplitStrings-                `onFail`-                (oneOf [ parseLineComment, parseMultiLineComment ] >> return [])-                `onFail`-                liftM return next---- | Parse @//@-style comments.-parseLineComment :: Parse String-parseLineComment = string "//" >> newline---- | Parse @/* ... */@-style comments.-parseMultiLineComment :: Parse String-parseMultiLineComment = bracket start end (liftM concat $ many inner)-    where-      start = string "/*"-      end = string "*/"-      inner = many1 (satisfy ((/=) '*'))-              `onFail`-              do ast <- character '*'-                 n <- satisfy ((/=) '/')-                 liftM ((:) ast . (:) n) inner---- | Parse out @\<newline>@ from a quoted string.-parseSplitStrings :: Parse String-parseSplitStrings = do oq <- parseQuote-                       inner <- liftM concat $ many parseInner-                       cq <- parseQuote-                       return $ oq : inner ++ [cq]-    where-      parseInner = string "\\\""-                   `onFail`-                   (character '\\' >> newline >> return [])-                   `onFail`-                   liftM return (satisfy ((/=) quoteChar))-
− Data/GraphViz/Types/Printing.hs
@@ -1,175 +0,0 @@-{- |-   Module      : Data.GraphViz.Types.Printing-   Description : Helper functions for converting to Dot format.-   Copyright   : (c) Ivan Lazar Miljenovic-   License     : 3-Clause BSD-style-   Maintainer  : Ivan.Miljenovic@gmail.com--   This module defines simple helper functions for use with-   "Text.PrettyPrint".  It also re-exports all the pretty-printing-   combinators from that module.--   Note that the 'PrintDot' instances for 'Bool', etc. match those-   specified for use with Graphviz.--   You should only be using this module if you are writing custom node-   types for use with "Data.GraphViz.Types".  For actual printing of-   code, use @'Data.GraphViz.Types.printDotGraph'@ (which produces a-   'String' value).--   The Dot language specification specifies that any identifier is in-   one of four forms:--       * Any string of alphabetic ([a-zA-Z\200-\377]) characters, underscores ('_') or digits ([0-9]), not beginning with a digit;--       * a number [-]?(.[0-9]+ | [0-9]+(.[0-9]*)? );--       * any double-quoted string (\"...\") possibly containing escaped quotes (\\\");--       * an HTML string (<...>).--   Due to these restrictions, you should only use 'text' when you are-   sure that the 'String' in question is static and quotes are-   definitely needed/unneeded; it is better to use the 'String'-   instance for 'PrintDot'.  For more information, see the-   specification page:-      <http://graphviz.org/doc/info/lang.html>--}-module Data.GraphViz.Types.Printing-    ( module Text.PrettyPrint-    , DotCode-    , renderDot -- Exported for Data.GraphViz.Types.printSGID-    , PrintDot(..)-    , printIt-    , addQuotes-    , wrap-    , commaDel-    , printField-    ) where--import Data.GraphViz.Types.Internal---- Only implicitly import and re-export combinators.-import Text.PrettyPrint hiding ( Style(..)-                               , Mode(..)-                               , TextDetails(..)-                               , render-                               , style-                               , renderStyle-                               , fullRender-                               )--import qualified Text.PrettyPrint as PP--import Data.Word(Word8)---- --------------------------------------------------------------------------------- | A type alias to indicate what is being produced.-type DotCode = Doc---- | Correctly render Graphviz output.-renderDot :: DotCode -> String-renderDot = PP.renderStyle style'-    where-      style' = PP.style { PP.mode = PP.LeftMode }---- | A class used to correctly print parts of the Graphviz Dot language.---   Minimal implementation is 'unqtDot'.-class PrintDot a where-    -- | The unquoted representation, for use when composing values to-    --   produce a larger printing value.-    unqtDot :: a -> DotCode--    -- | The actual quoted representation; this should be quoted if it-    --   contains characters not permitted a plain ID String, a number-    --   or it is not an HTML string.-    --   Defaults to 'unqtDot'.-    toDot :: a -> DotCode-    toDot = unqtDot--    -- | The correct way of representing a list of this value when-    --   printed; not all Dot values require this to be implemented.-    --   Defaults to Haskell-like list representation.-    unqtListToDot :: [a] -> DotCode-    unqtListToDot = brackets . hsep . punctuate comma-                    . map unqtDot--    -- | The quoted form of 'unqtListToDot'; defaults to wrapping-    --   double quotes around the result of 'unqtListToDot' (since the-    --   default implementation has characters that must be quoted).-    listToDot :: [a] -> DotCode-    listToDot = doubleQuotes . unqtListToDot---- | Convert to DotCode; note that this has no indentation, as we can---   only have one of indentation and (possibly) infinite line lengths.-printIt :: (PrintDot a) => a -> String-printIt = renderDot . toDot--instance PrintDot Int where-    unqtDot = int--instance PrintDot Word8 where-    unqtDot = int . fromIntegral--instance PrintDot Double where-    -- If it's an "integral" double, then print as an integer.-    -- This seems to match how Graphviz apps use Dot.-    unqtDot d = if d == fromIntegral di-                then int di-                else double d-        where-          di = round d--instance PrintDot Bool where-    unqtDot True  = text "true"-    unqtDot False = text "false"--instance PrintDot Char where-    unqtDot = char--    toDot = qtChar--    unqtListToDot = unqtString--    listToDot = qtString---- | Check to see if this 'Char' needs to be quoted or not.-qtChar :: Char -> DotCode-qtChar c-    | restIDString c = char c -- Could be a number as well.-    | otherwise      = doubleQuotes $ char c--needsQuotes :: String -> Bool-needsQuotes str-  | null str        = True-  | isKeyword str   = True-  | isIDString str  = False-  | isNumString str = False-  | otherwise       = True--addQuotes :: String -> DotCode -> DotCode-addQuotes = bool id doubleQuotes . needsQuotes---- | Escape quotes in Strings that need them.-unqtString     :: String -> DotCode-unqtString ""  = empty-unqtString str = text $ escapeQuotes str -- no quotes? no worries!---- | Escape quotes and quote Strings that need them (including keywords).-qtString     :: String -> DotCode-qtString str = addQuotes str $ unqtString str--instance (PrintDot a) => PrintDot [a] where-    unqtDot = unqtListToDot--    toDot = listToDot--wrap       :: DotCode -> DotCode -> DotCode -> DotCode-wrap b a d = b <> d <> a--commaDel     :: (PrintDot a, PrintDot b) => a -> b -> DotCode-commaDel a b = unqtDot a <> comma <> unqtDot b--printField     :: (PrintDot a) => String -> a -> DotCode-printField f v = text f <> equals <> toDot v
+ Data/GraphViz/Util.hs view
@@ -0,0 +1,133 @@+{-# OPTIONS_HADDOCK hide #-}++{- |+   Module      : Data.GraphViz.Util+   Description : Internal utility functions+   Copyright   : (c) Ivan Lazar Miljenovic+   License     : 3-Clause BSD-style+   Maintainer  : Ivan.Miljenovic@gmail.com++   This module defines internal utility functions.+-}+module Data.GraphViz.Util where++import Data.Char( isAsciiUpper+                , isAsciiLower+                , isDigit+                , toLower+                )++import Data.List(groupBy, sortBy)+import Data.Maybe(isJust)+import Data.Function(on)+import qualified Data.Set as Set+import Data.Set(Set)+import Control.Monad(liftM2)++-- -----------------------------------------------------------------------------++isIDString        :: String -> Bool+isIDString []     = True+isIDString (f:os) = frstIDString f+                    && all restIDString os++-- | First character of a non-quoted 'String' must match this.+frstIDString   :: Char -> Bool+frstIDString c = any ($c) [ isAsciiUpper+                          , isAsciiLower+                          , (==) '_'+                          , liftM2 (&&) (>= '\200') (<= '\377')+                          ]++-- | The rest of a non-quoted 'String' must match this.+restIDString   :: Char -> Bool+restIDString c = frstIDString c || isDigit c++-- | Determine if this String represents a number.+isNumString     :: String -> Bool+isNumString ""  = False+isNumString "-" = False+isNumString str = case str of+                    ('-':str') -> go str'+                    _          -> go str+    where+      go s = case span isDigit (map toLower s) of+               (ds@(_:_),[]) -> all isDigit ds+               ([],'.':[])   -> False+               ([],'.':d:ds) -> isDigit d && checkEs' ds+               (_,'.':ds)    -> checkEs $ dropWhile isDigit ds+               ([],_)        -> False+               (_,ds)        -> checkEs ds+      checkEs' s = case break ((==) 'e') s of+                     ([], _) -> False+                     (ds,es) -> all isDigit ds && checkEs es+      checkEs []       = True+      checkEs ('e':ds) = isIntString ds+      checkEs _        = False++-- | This assumes that 'isNumString' is 'True'.+toDouble     :: String -> Double+toDouble str = case str of+                 ('-':str') -> read $ '-' : adj str'+                 _          -> read $ adj str+  where+    adj s = (:) '0'+            $ case span ((==) '.') (map toLower s) of+                (ds@(_:_), '.':[])   -> ds ++ '.' : '0' : []+                (ds, '.':es@('e':_)) -> ds ++ '.' : '0' : es+                _                    -> s++isIntString :: String -> Bool+isIntString = isJust . stringToInt++-- | Determine if this String represents an integer.+stringToInt     :: String -> Maybe Int+stringToInt str = if isNum+                  then Just (read str)+                  else Nothing+  where+    isNum = case str of+              ""        -> False+              ['-']     -> False+              ('-':num) -> isNum' num+              _         -> isNum' str+    isNum' = all isDigit++-- | Graphviz requires double quotes to be explicitly escaped.+escapeQuotes           :: String -> String+escapeQuotes []        = []+escapeQuotes ('"':str) = '\\':'"': escapeQuotes str+escapeQuotes (c:str)   = c : escapeQuotes str++-- | Remove explicit escaping of double quotes.+descapeQuotes                :: String -> String+descapeQuotes []             = []+descapeQuotes ('\\':'"':str) = '"' : descapeQuotes str+descapeQuotes (c:str)        = c : descapeQuotes str++isKeyword :: String -> Bool+isKeyword = flip Set.member keywords . map toLower++-- | The following are Dot keywords and are not valid as labels, etc. unquoted.+keywords :: Set String+keywords = Set.fromList [ "node"+                        , "edge"+                        , "graph"+                        , "digraph"+                        , "subgraph"+                        , "strict"+                        ]++-- -----------------------------------------------------------------------------++uniq :: (Ord a) => [a] -> [a]+uniq = uniqBy id++uniqBy   :: (Ord b) => (a -> b) -> [a] -> [a]+uniqBy f = map head . groupBy ((==) `on` f) . sortBy (compare `on` f)++-- | Fold over 'Bool's; first param is for 'False', second for 'True'.+bool       :: a -> a -> Bool -> a+bool f t b = if b+             then t+             else f
README view
@@ -21,16 +21,15 @@ * Functions for running a Graphviz layout tool with all specified   output types. -* The ability to not only generate but also parse Dot code (note that-  currently this is limited to a rather strict ordering of-  statements).+* The ability to not only generate but also parse Dot code with two+  options: strict and liberal (in terms of ordering of statements).  * Functions to convert FGL graphs to Dot code - including support to   group them into clusters - with a high degree of customisation by   specifying which attributes to use.  * Round-trip support for passing an FGL graph through Graphviz to-  augment node and edge labels with positional information.+  augment node and edge labels with positional information, etc.  Please note that currently the convenience functions are only available for FGL [3] graphs; this will be extended in future once a
TODO view
@@ -2,8 +2,5 @@  * Utilise the generic graph class once it is finalised. -* Add generic versions (that is, order isn't important) of-  Data.GraphViz.Types- * Encoding: Graphviz uses UTF-8 by default, Latin1 when set; should   graphviz use utf8-string and disable changing the encoding?
graphviz.cabal view
@@ -1,18 +1,39 @@ Name:               graphviz-Version:            2999.7.0.0+Version:            2999.8.0.0 Stability:          Beta Synopsis:           Graphviz bindings for Haskell.-Description:        This library provides bindings for the Dot language-                      used by the Graphviz (<http://graphviz.org/>)-                      suite of programs.  Also provided are-                      convenience functions to convert FGL graphs into-                      Dot code with a large degree of customisation-                      for layout, etc.+Description: {+This library provides bindings for the Dot language used by the+Graphviz (<http://graphviz.org/>) suite of programs, as well as+functions to call the Grapvhiz programs.+.+Features of this library are:+.+* Almost complete coverage of all Graphviz attributes, etc. for+  graphs, sub-graphs, clusters, nodes and edges:+  <http://graphviz.org/doc/info/attrs.html>+.+* Thorough documentation on known problems with the library and how it+  differs from the actual Dot specification.+.+* Support for specifying clusters.+.+* The ability to use a custom node type.+.+* Functions for running a Graphviz layout tool with all specified+  output types.+.+* The ability to not only generate but also parse Dot code with two+  options: strict and liberal (in terms of ordering of statements).+.+* Functions to convert FGL graphs to Dot code - including support to+  group them into clusters - with a high degree of customisation by+  specifying which attributes to use.+.+* Round-trip support for passing an FGL graph through Graphviz to+  augment node and edge labels with positional information, etc.+} -                    Also allows a limited amount of parsing of Dot,-                      and round-trip usage of Graphviz to attach-                      positional data to each node and edge in the-                      graph. Category:           Graphs, Graphics License:            BSD3 License-File:       LICENSE@@ -48,14 +69,16 @@          Exposed-Modules:   Data.GraphViz                            Data.GraphViz.Types-                           Data.GraphViz.Types.Parsing-                           Data.GraphViz.Types.Printing+                           Data.GraphViz.Types.Generalised+                           Data.GraphViz.Parsing+                           Data.GraphViz.Printing                            Data.GraphViz.Commands                            Data.GraphViz.Attributes                            Data.GraphViz.Attributes.Colors          Other-Modules:     Data.GraphViz.Types.Clustering-                           Data.GraphViz.Types.Internal+                           Data.GraphViz.Types.Common+                           Data.GraphViz.Util         if flag(test)            Build-Depends:       QuickCheck >= 2.1 && < 2.2