dot-0.3: src/Dot/Text.hs
{-# LANGUAGE OverloadedStrings #-}
module Dot.Text
( encode
, encodeLazy
, builder
, encodeToFile
) where
import Data.Monoid
import Data.Text (Text)
import Data.Text.Lazy.Builder (Builder)
import Dot.Types
import qualified Data.Text as Text
import qualified Data.Text.IO as TIO
import qualified Data.Text.Lazy as LText
import qualified Data.Text.Lazy.Builder as Builder
levelSpaces :: Int
levelSpaces = 2
indentationBuilder :: Builder
indentationBuilder = " "
encode :: DotGraph -> Text
encode = LText.toStrict . encodeLazy
encodeLazy :: DotGraph -> LText.Text
encodeLazy = Builder.toLazyText . builder
encodeToFile :: FilePath -> DotGraph -> IO ()
encodeToFile fp dg = TIO.writeFile fp (encode dg)
builder :: DotGraph -> Builder
builder (DotGraph strictness directionality mid statements) = mempty
<> encodeStrictness strictness
<> encodeGraphDirectionality directionality
<> encodeMaybeId mid
<> "{\n"
<> foldr (\statement builder -> encodeStatement directionality indentationBuilder statement <> builder) mempty statements
<> "}"
encodeId :: Id -> Builder
encodeId (Id theId) = case Text.uncons theId of
Just (c,_) -> if not (c >= '0' && c <= '9') && Text.all (\c -> (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') theId
then Builder.fromText theId
else "\""
<> Builder.fromText
( Text.replace "\"" "\\\""
$ Text.replace "\n" "\\n"
$ Text.replace "\\" "\\\\"
$ theId
)
<> "\""
Nothing -> "\"\""
encodeNodeId :: NodeId -> Builder
encodeNodeId (NodeId theId mport) =
encodeId theId <> maybe mempty encodePort mport
encodePort :: Port -> Builder
encodePort (Port theId mcompass) =
":" <> encodeId theId <> maybe mempty encodeCompass mcompass
encodeCompass :: CardinalDirection -> Builder
encodeCompass x = case x of
North -> "n"
East -> "e"
South -> "s"
West -> "w"
Northeast -> "ne"
Northwest -> "nw"
Southeast -> "se"
Southwest -> "sw"
encodeMaybeId :: Maybe Id -> Builder
encodeMaybeId x = case x of
Just theId -> encodeId theId <> " "
Nothing -> mempty
encodeStrictness :: Strictness -> Builder
encodeStrictness x = case x of
Strict -> "strict "
NonStrict -> mempty
encodeElement :: Element -> Builder
encodeElement x = case x of
Graph -> "graph "
Node -> "node "
Edge -> "edge "
encodeSubgraph :: Subgraph -> Builder
encodeSubgraph = error "encodeSubgraph: have not written this function yet"
encodeStatement :: Directionality -> Builder -> Statement -> Builder
encodeStatement directionality indentation x = case x of
StatementAttribute (AttributeStatement element attrs) -> indentation
<> encodeElement element
<> encodeAttributes attrs
StatementNode (NodeStatement theNodeId attrs) -> indentation
<> encodeNodeId theNodeId
<> encodeAttributes attrs
StatementSubgraph subgraph -> encodeSubgraph subgraph
StatementEdge (EdgeStatement elements attrs) -> indentation
<> encodeEdgeElements directionality elements
<> encodeAttributes attrs
StatementEquality a b -> indentation
<> encodeId a <> " = " <> encodeId b <> "\n"
where nextIndentation = indentationBuilder <> indentation
encodeEdgeOp :: Directionality -> Builder
encodeEdgeOp x = case x of
Undirected -> " -- "
Directed -> " -> "
encodeEdgeElements :: Directionality -> ListTwo EdgeElement -> Builder
encodeEdgeElements edgeOp (ListTwo a b xs) =
encodeEdgeElement a <> edgeOpBuilder <> encodeEdgeElement b
<> foldr (\e builder -> edgeOpBuilder <> encodeEdgeElement e <> builder) mempty xs
where edgeOpBuilder = encodeEdgeOp edgeOp
encodeEdgeElement :: EdgeElement -> Builder
encodeEdgeElement x = case x of
EdgeSubgraph subgraph -> encodeSubgraph subgraph
EdgeNode theNodeId -> encodeNodeId theNodeId
encodeAttributes :: [Attribute] -> Builder
encodeAttributes (x : xs) = " ["
<> foldr (\attr builder -> encodeAttribute attr <> "," <> builder) (encodeAttribute x) xs
<> "];\n"
encodeAttributes [] = " [];\n"
encodeAttribute :: Attribute -> Builder
encodeAttribute (Attribute attrId valId) = encodeId attrId <> "=" <> encodeId valId
encodeGraphDirectionality :: Directionality -> Builder
encodeGraphDirectionality x = case x of
Directed -> "digraph "
Undirected -> "graph "
example :: DotGraph
example = DotGraph Strict Directed (Just "foobar")
[ StatementNode $ NodeStatement "a1"
[ Attribute "color" "blue"
, Attribute "shape" "box"
]
, StatementNode $ NodeStatement "a2" []
, StatementEdge $ EdgeStatement (ListTwo "a1" "a2" ["a3"])
[ Attribute "color" "red"
]
]