diff --git a/Main.hs b/Main.hs
--- a/Main.hs
+++ b/Main.hs
@@ -1,16 +1,19 @@
+{-# Language CPP #-}
 {-# Language NoMonomorphismRestriction #-}
 {-# Language OverloadedStrings #-}
 
 module Main where
 
 import Data.Char
-import Data.Monoid
 import Options.Applicative hiding (ParserResult(..))
 import StylishCabal
 import System.Exit
 import System.IO
-import Text.PrettyPrint.ANSI.Leijen (Doc, displayIO, displayS, plain, renderSmart)
 
+#if !MIN_VERSION_base(4,11,0)
+import Data.Monoid
+#endif
+
 data Opts = Opts
     { file :: Maybe FilePath
     , inPlace :: Bool
@@ -41,8 +44,8 @@
          value 2 <>
          metavar "INT")
 
-render :: Opts -> Doc -> Handle -> IO ()
-render o doc h = do
+output :: Opts -> Doc -> Handle -> IO ()
+output o doc h = do
     isTerminal <- hIsTerminalDevice stdout
     if color o && isTerminal
         then displayIO h (f doc)
@@ -50,7 +53,7 @@
             let docStr = unlines . map stripBlank . lines $ displayS (f $ plain doc) ""
             hPutStr h docStr
   where
-    f = renderSmart 1.0 (width o)
+    f = render (width o)
     stripBlank x
         | all isSpace x = []
         | otherwise = x
@@ -59,14 +62,11 @@
 main = do
     o <- execParser $ info (opts <**> helper) (fullDesc <> progDesc "Format a Cabal file")
     f <- maybe getContents readFile (file o)
-    case pretty (indent o) <$> parse f of
-        Error m s -> displayError (file o) m s
-        Warn warnings -> printWarnings warnings
-        Success doc ->
-            if inPlace o
-                then case file o of
-                         Just fname -> withFile fname WriteMode (render o doc)
-                         Nothing -> hPutStrLn stderr inPlaceErr >> exitFailure
-                else render o doc stdout
+    doc <- prettyWithIndent (indent o) <$> readCabalFile (file o) f
+    if inPlace o
+        then case file o of
+                 Just fname -> withFile fname WriteMode (output o doc)
+                 Nothing -> hPutStrLn stderr inPlaceErr >> exitFailure
+        else output o doc stdout
   where
     inPlaceErr = "stylish-cabal: --in-place specified, but I'm reading from stdin"
diff --git a/src/Parse.hs b/src/Parse.hs
--- a/src/Parse.hs
+++ b/src/Parse.hs
@@ -1,38 +1,83 @@
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+{-# Language CPP #-}
+{-# Language StandaloneDeriving #-}
+{-# Language DeriveGeneric #-}
+{-# Language DeriveDataTypeable #-}
+{-# Language DeriveFunctor #-}
+
 module Parse
-    ( parse
+    ( parseCabalFile
+    , readCabalFile
     , displayError
     , printWarnings
     , Result(..)
+    , result
     ) where
 
+import Control.DeepSeq
+import Data.Data
 import Data.Maybe
 import Distribution.PackageDescription.Parse
 import Distribution.ParseUtils
 import Distribution.Simple.Utils
 import Distribution.Verbosity
+import GHC.Generics
 import System.Environment
 import System.Exit
 import System.IO
 
+-- | Like Cabal's @ParseResult@, but treats warnings as a separate failure
+-- case.
 data Result a
     = Error (Maybe LineNo)
-            String
-    | Warn [PWarning]
-    | Success a
-    deriving (Show)
+            String -- ^ Parse error on the given line.
+    | Warn [PWarning] -- ^ Warnings emitted during parse.
+    | Success a -- ^ The input is a compliant package description.
+    deriving (Show, Eq, Functor, Generic, Typeable, Data)
 
-instance Functor Result where
-    fmap f (Success a) = Success (f a)
-    fmap _ (Warn ps) = Warn ps
-    fmap _ (Error m s) = Error m s
+-- | Case analysis for 'Result'.
+result :: (Maybe LineNo -> String -> b) -> ([PWarning] -> b) -> (a -> b) -> Result a -> b
+result e w s p =
+    case p of
+        Error l m -> e l m
+        Warn ws -> w ws
+        Success r -> s r
 
-parse input =
+instance NFData a => NFData (Result a)
+
+deriving instance Generic PWarning
+
+deriving instance Data PWarning
+
+#if !MIN_VERSION_base(4,8,0)
+deriving instance Typeable PWarning
+#endif
+
+instance NFData PWarning
+
+-- | This function is similar to Cabal's own file parser, except that it
+-- treats warnings as a separate failure case. There are a wide range of
+-- different behaviors accepted by different Cabal parser versions. Parse
+-- warnings generally indicate a version-related inconsistency, so we play
+-- it safe here.
+parseCabalFile input =
     case parseGenericPackageDescription input of
         ParseFailed e -> uncurry Error $ locatedErrorMsg e
         ParseOk warnings x
             | null warnings -> Success x
             | otherwise -> Warn $ reverse warnings
 
+-- | Shorthand to combine 'parseCabalFile' and one of 'printWarnings' or
+-- 'displayError'. The given 'FilePath' is used only for error messages and
+-- is not read from.
+readCabalFile fpath = result (displayError fpath) printWarnings return . parseCabalFile
+
+-- | Print some warnings to 'stderr' and exit.
+printWarnings ps = mapM_ (warn normal . showPWarning "<input>") ps >> exitFailure
+
+-- | Print a parse error to 'stderr', annotated with filepath and line
+-- number (if available), then exit.
+displayError :: Maybe FilePath -> Maybe LineNo -> String -> IO a
 displayError fpath line' message = do
     prog <- getProgName
     hPutStrLn stderr $
@@ -44,5 +89,3 @@
              Nothing -> "") ++
         ": " ++ message
     exitFailure
-
-printWarnings ps = mapM_ (warn normal . showPWarning "<input>") ps >> exitFailure
diff --git a/src/StylishCabal.hs b/src/StylishCabal.hs
--- a/src/StylishCabal.hs
+++ b/src/StylishCabal.hs
@@ -1,16 +1,44 @@
+{-# Language CPP #-}
+
+-- | Cabal file formatter.
 module StylishCabal
-    ( pretty
-    , displayError
+    ( -- * Formatting Cabal files
+      pretty
+    , prettyWithIndent
+    , render
+      -- * Parsing utilities
+    , parseCabalFile
+    , readCabalFile
+    , Result(..)
+    , result
     , printWarnings
-    , Result (..)
-    , parse
+    , displayError
+    -- * Reexports
+    , Doc
+    , plain
+    , displayIO
+    , displayS
     ) where
 
-import Data.Monoid
-import Text.PrettyPrint.ANSI.Leijen (line)
+import Text.PrettyPrint.ANSI.Leijen (Doc, displayIO, displayS, line, plain, renderSmart)
 
 import Parse
 import Render
 import Transform
 
-pretty i gpd = uncurry (blockBodyToDoc i) (toBlocks gpd) <> line
+#if !MIN_VERSION_base(4,11,0)
+import Data.Monoid
+#endif
+
+-- | @pretty pkg@ produces a colorized, formatted textual representation of
+-- a given 'Distribution.PackageDescription.GenericPackageDescription',
+-- with a default indent width of 2.
+--
+-- To remove syntax highlighting, you can use 'plain'.
+pretty = prettyWithIndent 2
+
+-- | Like 'pretty', but allows you to specify an indent size.
+prettyWithIndent i gpd = uncurry (blockBodyToDoc i) (toBlocks gpd) <> line
+
+-- | Render the given 'Doc' with the given width.
+render = renderSmart 1.0
diff --git a/src/Transform.hs b/src/Transform.hs
--- a/src/Transform.hs
+++ b/src/Transform.hs
@@ -23,7 +23,7 @@
 import Distribution.Types.PackageName
 import Distribution.Types.UnqualComponentName
 import Distribution.Version
-#if __GLASGOW_HASKELL__ < 710
+#if !MIN_VERSION_base(4,8,0)
 import Data.Functor ((<$>))
 #endif
 
diff --git a/stylish-cabal.cabal b/stylish-cabal.cabal
--- a/stylish-cabal.cabal
+++ b/stylish-cabal.cabal
@@ -1,21 +1,28 @@
 name:               stylish-cabal
-version:            0.1.0.0
+version:            0.2.0.0
 synopsis:           Format Cabal files
 description:        A tool for nicely formatting your Cabal file.
 license:            BSD3
 license-file:       LICENSE
 author:             Jude Taylor
 maintainer:         me@jude.xyz
-tested-with:        GHC == 7.8.4, GHC == 7.10.3, GHC == 8.0.2, GHC == 8.2.2
+tested-with:        GHC == 7.8.4, GHC == 7.10.3, GHC == 8.0.2, GHC == 8.2.2,
+                    GHC == 8.4.0.20180204
 category:           Language
 build-type:         Simple
 extra-source-files: ChangeLog.md
+                    tests/example.cabal
 cabal-version:      >= 1.10
 
 source-repository head
   type:     git
   location: https://github.com/pikajude/stylish-cabal.git
 
+flag werror
+  default:     False
+  manual:      True
+  description: build with -Werror
+
 flag test-hackage
   default:     False
   manual:      True
@@ -29,7 +36,6 @@
     on Hackage, and thus is disabled by default.
 
 library
-  exposed:            False
   exposed-modules:    StylishCabal
   other-modules:      Parse
                       Render
@@ -43,19 +49,29 @@
   default-extensions: NoMonomorphismRestriction
   ghc-options:        -Wall -fno-warn-missing-signatures
 
+  if flag(werror)
+    ghc-options: -Werror
+
 executable stylish-cabal
   main-is:          Main.hs
   build-depends:    base, ansi-wl-pprint, optparse-applicative, stylish-cabal
   default-language: Haskell2010
   ghc-options:      -Wall
 
+  if flag(werror)
+    ghc-options: -Werror
+
 test-suite hlint
   type:             exitcode-stdio-1.0
   main-is:          hlint.hs
   hs-source-dirs:   tests
   build-depends:    base, hlint
   default-language: Haskell2010
+  ghc-options:      -Wall
 
+  if flag(werror)
+    ghc-options: -Werror
+
 test-suite strictness
   type:               exitcode-stdio-1.0
   main-is:            Main.hs
@@ -76,6 +92,9 @@
   default-extensions: CPP
   ghc-options:        -freduction-depth=0
 
+  if flag(werror)
+    ghc-options: -Werror
+
   if !(flag(test-strictness) && impl(ghc >= 8.2))
     buildable: False
 
@@ -97,6 +116,9 @@
   default-extensions: CPP
   ghc-options:        -Wall -fno-warn-missing-signatures
 
+  if flag(werror)
+    ghc-options: -Werror
+
 test-suite roundtrip-hackage
   type:             exitcode-stdio-1.0
   main-is:          Hackage.hs
@@ -119,6 +141,9 @@
                   , wreq
   default-language: Haskell2010
   ghc-options:      -threaded -rtsopts -with-rtsopts=-N -Wall -fno-warn-missing-signatures
+
+  if flag(werror)
+    ghc-options: -Werror
 
   if !flag(test-hackage)
     buildable: False
diff --git a/tests/example.cabal b/tests/example.cabal
new file mode 100644
--- /dev/null
+++ b/tests/example.cabal
@@ -0,0 +1,119 @@
+name: example-package
+version: 1.2.3.4
+cabal-version: >=1.8
+build-type: Custom
+license: MIT
+license-files: LICENSE1, "license with spaces"
+copyright: (c) 2018 John Doe
+author: John Doe
+maintainer: johndoe@example.com
+stability: experimental
+homepage: https://example.com/cabal
+bug-reports: https://github.com/example/cabal
+package-url: https://gituh.bcom/example/Cabal/downloads/1.cabal
+synopsis: A short synopsis for this package
+description:
+    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam sagittis neque magna, eu accumsan velit convallis at. In at odio eu risus facilisis vehicula vel non elit. Vestibulum volutpat id tellus ac malesuada.
+    .
+    Fusce rutrum elit libero, ac malesuada nibh semper ut. Donec commodo viverra felis. Quisque tincidunt ultrices turpis. Vestibulum gravida, urna ac pharetra consectetur, nulla lacus sollicitudin diam, quis dignissim libero neque at nulla.
+category: Example
+tested-with: GHC >= 7.4 && < 8.4
+data-files: "file1 with spaces", data/*
+data-dir: .
+extra-source-files: extra-file1, extra-file2
+extra-doc-files: docfile1, docfile2
+extra-tmp-files: tmpfile1, tmpfile2
+
+custom-setup
+    setup-depends: base >= 4.5 && < 4.11, Cabal < 1.25
+
+source-repository head
+    type: git
+    location: https://github.com/pikajude/stylish-cabal.cabal
+    tag: latest
+    branch: master
+    subdir: tests
+
+source-repository this
+    type: cvs
+    location: anoncvs@cvs.foo.org:/cvs
+    module: cvsmodule
+
+flag neat-flag {
+    default: False
+    manual: True
+    description: This is a flag with a nice little description
+}
+
+library example-internal
+    exposed-modules: Example.Internal
+    build-depends: base
+
+library
+    exposed-modules: Example
+    exposed: False
+    reexported-modules: base:Numeric as MyNumericModule
+    signatures: ExampleSig
+    mixins: short (Mixin as Foo.Mixin) requires (Bar as Foo.Bar), example-mixin2 (Mixin as Foo.Mixin2, Mixina as Foo.Mixina2, Mixinb as Foo.Mixinb2, Mixinc as Foo.Mixinc2), example-mixin3 (Mixin as Bar.Mixin) requires hiding (Mixin4, Mixin5), example-mixin4
+    build-depends: base == 4.*, attoparsec, foo >= 1.2.3 && < 1.4, somelib ^>= 1.2.3.4, bar ^>= 3.4
+    other-modules: Example.Module1, Example.Module2
+    autogen-modules: Example.AutogenModule
+    other-languages: Haskell98
+    hs-source-dirs: src
+    default-extensions: NoMonomorphismRestriction
+    other-extensions: OverloadedStrings
+    build-tool-depends: cpphs:cpphs >= 4.0, foo:bar == 1.2.*
+    ghc-options: -Wall
+    ghc-prof-options: -fcaf-all
+    ghc-shared-options: -fobject-code
+    build-tools: hsc2hs, unknown-build-tool
+    includes: example.h
+    install-includes: output.h
+    include-dirs: include
+    c-sources: csource.c
+    js-sources: jssource.js
+    extra-libraries: iconv
+    extra-ghci-libraries: webkit
+    extra-lib-dirs: lib, lib2
+    cc-options: -compat
+    cpp-options: -DFOO=1
+    ld-options: -static
+    pkgconfig-depends: gtk+-2.0 >= 2.10, cairo >= 1.0
+    frameworks: CoreAudio
+    extra-framework-dirs: frameworks
+    x-a-custom-field: "Some Custom Value"
+
+    if impl(ghc >= 7.5) && (os(darwin) || !arch(i386))
+        other-extensions: PolyKinds
+
+executable example {
+    main-is: example.hs
+    scope: private
+}
+
+test-suite testname
+    type: exitcode-stdio-1.0
+    main-is: test.hs
+
+test-suite detailed
+    type: detailed-0.9
+    test-module: Example.Test
+
+benchmark bench
+    type: exitcode-stdio-1.0
+    main-is: benchmark.hs
+
+foreign-library examplelib
+    type: native-shared
+    lib-version-info: 6:3:2
+    lib-version-linux: 4.3.2
+
+    if os(windows)
+        options: standalone
+        mod-def-file: ExampleLib.def, ExampleLib2.def
+
+    other-modules: ExampleLib.SomeModule
+    build-depends: base == 4.*
+    hs-source-dirs: src
+    c-sources: csrc/ExampleLib.c
+    default-language: Haskell2010
diff --git a/tests/roundtrip/SortedDesc.hs b/tests/roundtrip/SortedDesc.hs
--- a/tests/roundtrip/SortedDesc.hs
+++ b/tests/roundtrip/SortedDesc.hs
@@ -1,4 +1,5 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
+{-# Language CPP #-}
 {-# Language NamedFieldPuns #-}
 {-# Language RecordWildCards #-}
 {-# Language StandaloneDeriving #-}
@@ -22,7 +23,7 @@
 import Distribution.Types.UnqualComponentName
 import Distribution.Version
 import Language.Haskell.Extension
-#if __GLASGOW_HASKELL__ < 710
+#if !MIN_VERSION_base(4,8,0)
 import Data.Functor ((<$>))
 #endif
 
diff --git a/tests/roundtrip/Utils.hs b/tests/roundtrip/Utils.hs
--- a/tests/roundtrip/Utils.hs
+++ b/tests/roundtrip/Utils.hs
@@ -12,16 +12,16 @@
 import StylishCabal
 import Test.Hspec.Core.Spec
 import Test.Hspec.Expectations.Pretty
-import Text.PrettyPrint.ANSI.Leijen (displayS, plain, renderSmart)
-#if __GLASGOW_HASKELL__ < 710
+#if !MIN_VERSION_base(4,8,0)
 import Control.Applicative (pure)
 import Data.Functor ((<$>))
 #endif
-
 deriving instance Eq a => Eq (ParseResult a)
 
 expectParse cabalStr = do
-    let doc = (`displayS` "") . renderSmart 1.0 80 . plain . pretty 2 <$> parse cabalStr
+    let doc =
+            (`displayS` "") . render 80 . plain . pretty <$>
+            parseCabalFile cabalStr
     case doc of
         StylishCabal.Success rendered -> do
             let original =
@@ -48,8 +48,8 @@
     i
         { itemExample =
               \a b c -> do
-                  result <- itemExample i a b c
-                  case result of
+                  res <- itemExample i a b c
+                  case res of
                       Right (Failure _ (Reason r))
                           | "SKIP " `isPrefixOf` r ->
                               pure $ Right $ Pending $ Just $ drop 5 r
diff --git a/tests/strictness/Main.hs b/tests/strictness/Main.hs
--- a/tests/strictness/Main.hs
+++ b/tests/strictness/Main.hs
@@ -32,7 +32,7 @@
         , C.foreignLibs pd
         , C.testSuites pd
         , C.benchmarks pd)
-        (length $ show $ pretty 2 gpd)
+        (length $ show $ pretty gpd)
   where
     pd = packageDescription gpd
 
