diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Andrew Martin (c) 2016
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Andrew Martin nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/dot.cabal b/dot.cabal
new file mode 100644
--- /dev/null
+++ b/dot.cabal
@@ -0,0 +1,29 @@
+name:                dot
+version:             0.2.0
+synopsis:            Data types and encoding for graphviz dot files
+description:         Please see README.md
+homepage:            https://github.com/andrewthad/dot#readme
+license:             BSD3
+license-file:        LICENSE
+author:              Andrew Martin
+maintainer:          andrew.thaddeus@gmail.com
+copyright:           2016 Andrew Martin
+category:            web
+build-type:          Simple
+cabal-version:       >=1.10
+
+library
+  hs-source-dirs:      src
+  exposed-modules:
+    Dot.Types
+    Dot.Text
+    Data.Graph.Immutable.Dot
+  build-depends:
+      base >= 4.7 && < 5
+    , text
+    , impure-containers >= 0.3 && < 0.4
+  default-language:    Haskell2010
+
+source-repository head
+  type:     git
+  location: https://github.com/andrewthad/dot
diff --git a/src/Data/Graph/Immutable/Dot.hs b/src/Data/Graph/Immutable/Dot.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Graph/Immutable/Dot.hs
@@ -0,0 +1,54 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Data.Graph.Immutable.Dot
+  ( toDotGraph
+  , toLabeledDotGraph
+  ) where
+
+import Data.Graph.Types
+import Dot.Types
+import Control.Applicative
+import Data.Text (Text)
+import qualified Data.Graph.Immutable as Graph
+import qualified Data.Text as Text
+import qualified Data.Text.Lazy as LText
+import qualified Data.Text.Lazy.Builder as Builder
+import qualified Data.Text.Lazy.Builder.Int as Builder
+
+-- | This is a fairly general way to build a dot file from a graph.
+toDotGraph :: Directionality -> (v -> [Attribute]) -> (v -> v -> e -> [Attribute]) -> Graph g e v -> DotGraph
+toDotGraph directionality vertexAttrs edgeAttrs g =
+  DotGraph strictness directionality Nothing $ []
+    ++ vertexDeclarations
+    ++ edgeDeclarations
+  where
+  vertexDeclarations = getConst $ flip Graph.traverseVertices_ g $ \vertex v ->
+    Const [StatementNode $ NodeStatement (vertexToNodeId vertex) (vertexAttrs v)]
+  edgeDeclarations = getConst $ flip Graph.traverseEdges_ g $ \vertexFrom vertexTo vFrom vTo e ->
+    Const [StatementEdge $ EdgeStatement
+             (ListTwo
+               (EdgeNode (vertexToNodeId vertexFrom))
+               (EdgeNode (vertexToNodeId vertexTo))
+               []
+             )
+             (edgeAttrs vFrom vTo e)
+          ]
+  strictness = case directionality of
+    Undirected -> NonStrict
+    Directed -> Strict
+
+-- | This is a more convenient variant of 'toDotGraph' that just labels
+-- all of the nodes and edges. It does not color or style anything.
+toLabeledDotGraph :: Directionality -> (v -> Text) -> (e -> Text) -> Graph g e v -> DotGraph
+toLabeledDotGraph dir vertexLabel edgeLabel =
+  toDotGraph dir (textToLabel . vertexLabel) (const $ const $ textToLabel . edgeLabel)
+
+textToLabel :: Text -> [Attribute]
+textToLabel name = [Attribute "label" (Id name)]
+
+vertexToNodeId :: Vertex g -> NodeId
+vertexToNodeId = id
+  . flip NodeId Nothing . Id
+  . LText.toStrict . Builder.toLazyText
+  . mappend "a" . Builder.decimal . Graph.vertexInt
+
diff --git a/src/Dot/Text.hs b/src/Dot/Text.hs
new file mode 100644
--- /dev/null
+++ b/src/Dot/Text.hs
@@ -0,0 +1,136 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Dot.Text
+  ( encode
+  , encodeLazy
+  , builder
+  ) where
+
+import Data.Text (Text)
+import Data.Text.Lazy.Builder (Builder)
+import qualified Data.Text.Lazy.Builder as Builder
+import qualified Data.Text.Lazy as LText
+import Data.Monoid
+import Dot.Types
+
+levelSpaces :: Int
+levelSpaces = 2
+
+indentationBuilder :: Builder
+indentationBuilder = "  "
+
+encode :: DotGraph -> Text
+encode = LText.toStrict . encodeLazy
+
+encodeLazy :: DotGraph -> LText.Text
+encodeLazy = Builder.toLazyText . builder
+
+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) = Builder.fromText theId
+
+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"
+    ]
+  ]
+
diff --git a/src/Dot/Types.hs b/src/Dot/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Dot/Types.hs
@@ -0,0 +1,86 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+module Dot.Types where
+
+import Data.Text (Text)
+import Data.Text.Lazy.Builder (Builder)
+import Data.String (IsString(fromString))
+
+data Strictness = Strict | NonStrict
+  deriving (Show,Read)
+
+data Directionality = Directed | Undirected
+  deriving (Show,Read)
+
+data CardinalDirection
+  = North
+  | East
+  | South
+  | West
+  | Northeast
+  | Northwest
+  | Southeast
+  | Southwest
+  deriving (Show,Read)
+
+data Element = Graph | Node | Edge
+  deriving (Show,Read)
+
+data EdgeElement = EdgeSubgraph Subgraph | EdgeNode NodeId
+  deriving (Show,Read)
+
+instance IsString EdgeElement where
+  fromString str = EdgeNode (fromString str)
+
+newtype Id = Id Text
+  deriving (Show,Read,IsString)
+
+data NodeId = NodeId Id (Maybe Port)
+  deriving (Show,Read)
+
+instance IsString NodeId where
+  fromString str = NodeId (fromString str) Nothing
+
+-- | Stole this from semigroups. Remove it once GHC 8.0 gains
+--   widespread adoption.
+data NonEmpty a = a :| [a]
+
+data ListTwo a = ListTwo
+  { listTwoFirst :: a
+  , listTwoSecond :: a
+  , listTwoOther :: [a]
+  } deriving (Show,Read)
+
+data Port = Port
+  { portId :: Id
+  , portCompass :: Maybe CardinalDirection
+  } deriving (Show,Read)
+
+data DotGraph = DotGraph Strictness Directionality (Maybe Id) [Statement]
+  deriving (Show,Read)
+
+data Statement
+  = StatementAttribute AttributeStatement
+  | StatementNode NodeStatement
+  | StatementEdge EdgeStatement
+  | StatementSubgraph Subgraph
+  | StatementEquality Id Id
+  deriving (Show,Read)
+
+data AttributeStatement = AttributeStatement Element [Attribute]
+  deriving (Show,Read)
+
+data Attribute = Attribute Id Id
+  deriving (Show,Read)
+
+data NodeStatement = NodeStatement NodeId [Attribute]
+  deriving (Show,Read)
+
+data EdgeStatement = EdgeStatement (ListTwo EdgeElement) [Attribute]
+  deriving (Show,Read)
+
+data Subgraph = Subgraph
+  { subgraphId :: Maybe Id
+  , subgraphStatements :: [Statement]
+  } deriving (Show,Read)
+
