diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,11 @@
+## [_Unreleased_](https://github.com/freckle/cfn-flip/compare/v0.1.0.1...main)
+
+None
+
+## [v0.1.0.1](https://github.com/freckle/cfn-flip/compare/v0.1.0.0...v0.1.0.1)
+
+Fix package manifest.
+
+## [v0.1.0.0](https://github.com/freckle/cfn-flip/tree/v0.1.0.0)
+
+Initial release.
diff --git a/DocTest.hs b/DocTest.hs
new file mode 100644
--- /dev/null
+++ b/DocTest.hs
@@ -0,0 +1,20 @@
+module Main
+  ( main
+  ) where
+
+import Prelude
+
+import Data.Aeson
+import qualified Data.Yaml as Yaml
+import Test.DocTest
+
+newtype PackageExtensions = PackageExtensions [String]
+
+instance FromJSON PackageExtensions where
+  parseJSON = withObject "PackageExtensions"
+    $ \o -> PackageExtensions <$> o .: "default-extensions"
+
+main :: IO ()
+main = do
+  PackageExtensions extensions <- Yaml.decodeFileThrow "package.yaml"
+  doctest $ map ("-X" <>) extensions <> ["src"]
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2021 Renaissance Learning Inc
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+# CfnFlip
+
+Pure Haskell implementation of [cfn-flip][].
+
+[cfn-flip]: https://github.com/awslabs/aws-cfn-template-flip
+
+## Usage
+
+See [CfnFlip.hs](./src/CfnFlip.hs).
+
+---
+
+[LICENSE](./LICENSE) | [CHANGELOG](./CHANGELOG.md)
diff --git a/cfn-flip.cabal b/cfn-flip.cabal
new file mode 100644
--- /dev/null
+++ b/cfn-flip.cabal
@@ -0,0 +1,165 @@
+cabal-version:      1.12
+name:               cfn-flip
+version:            0.1.0.1
+license:            MIT
+license-file:       LICENSE
+copyright:          2021 Renaissance Learning Inc
+maintainer:         freckle-engineering@renaissance.com
+author:             Freckle R&D
+homepage:           https://github.com/freckle/cfn-flip#readme
+bug-reports:        https://github.com/freckle/cfn-flip/issues
+synopsis:           Haskell implementation of aws/cfn-flip
+description:
+    Functions to flip between CloudFormation Yaml and JSON syntaxes
+
+category:           Tools
+build-type:         Simple
+extra-source-files:
+    CHANGELOG.md
+    README.md
+    test/examples/clean.json
+    test/examples/get-att.json
+    test/examples/import_value.json
+    test/examples/test.json
+    test/examples/test_json_data.json
+    test/examples/test_json_data_long_line.json
+    test/examples/test_json_def_string_with_sub.json
+    test/examples/test_json_state_machine.json
+    test/examples/test_long.json
+    test/examples/test_lorem.json
+    test/examples/test_multibyte.json
+    test/examples/test_multiline.json
+    test/examples/test_user_data.json
+    test/examples/test_yaml_def_string_with_sub.json
+    test/examples/test_yaml_long_line.json
+    test/examples/test_yaml_state_machine.json
+    test/examples/clean.yaml
+    test/examples/get-att.yaml
+    test/examples/import_value.yaml
+    test/examples/test.yaml
+    test/examples/test_json_data.yaml
+    test/examples/test_lorem.yaml
+    test/examples/test_multibyte.yaml
+    test/examples/test_multiline.yaml
+    test/examples/test_user_data.yaml
+    test/examples/test_yaml_def_string_with_sub.yaml
+    test/examples/test_yaml_long_line.yaml
+    test/examples/test_yaml_state_machine.yaml
+
+source-repository head
+    type:     git
+    location: https://github.com/freckle/cfn-flip
+
+library
+    exposed-modules:
+        CfnFlip
+        CfnFlip.Aeson
+        CfnFlip.Conduit
+        CfnFlip.IntrinsicFunction
+        CfnFlip.JsonToYaml
+        CfnFlip.Libyaml
+        CfnFlip.Prelude
+        CfnFlip.Yaml
+        CfnFlip.YamlToJson
+
+    hs-source-dirs:     src
+    other-modules:      Paths_cfn_flip
+    default-language:   Haskell2010
+    default-extensions:
+        BangPatterns DataKinds DeriveAnyClass DeriveFoldable DeriveFunctor
+        DeriveGeneric DeriveLift DeriveTraversable DerivingStrategies
+        FlexibleContexts FlexibleInstances GADTs GeneralizedNewtypeDeriving
+        LambdaCase MultiParamTypeClasses NoImplicitPrelude
+        NoMonomorphismRestriction OverloadedStrings RankNTypes
+        RecordWildCards ScopedTypeVariables StandaloneDeriving
+        TypeApplications TypeFamilies
+
+    ghc-options:
+        -Weverything -Wno-missing-exported-signatures
+        -Wno-missing-import-lists -Wno-unsafe
+
+    build-depends:
+        aeson >=1.4.6.0,
+        base >=4.11 && <10,
+        bytestring >=0.10.8.2,
+        conduit >=1.3.1.2,
+        libyaml >=0.1.2,
+        resourcet >=1.2.2,
+        text >=1.2.3.1,
+        unliftio >=0.2.12,
+        unliftio-core >=0.1.2.0,
+        yaml >=0.11.2.0
+
+    if impl(ghc >8.10)
+        default-extensions: DerivingVia
+        ghc-options:
+            -fwrite-ide-info -Wno-prepositive-qualified-module
+            -Wno-missing-safe-haskell-mode
+
+test-suite doctest
+    type:               exitcode-stdio-1.0
+    main-is:            DocTest.hs
+    other-modules:      Paths_cfn_flip
+    default-language:   Haskell2010
+    default-extensions:
+        BangPatterns DataKinds DeriveAnyClass DeriveFoldable DeriveFunctor
+        DeriveGeneric DeriveLift DeriveTraversable DerivingStrategies
+        FlexibleContexts FlexibleInstances GADTs GeneralizedNewtypeDeriving
+        LambdaCase MultiParamTypeClasses NoImplicitPrelude
+        NoMonomorphismRestriction OverloadedStrings RankNTypes
+        RecordWildCards ScopedTypeVariables StandaloneDeriving
+        TypeApplications TypeFamilies
+
+    ghc-options:
+        -Weverything -Wno-missing-exported-signatures
+        -Wno-missing-import-lists -Wno-unsafe
+
+    build-depends:
+        aeson >=1.4.6.0,
+        base >=4.11 && <10,
+        doctest >=0.16.2,
+        yaml >=0.11.2.0
+
+    if impl(ghc >8.10)
+        default-extensions: DerivingVia
+        ghc-options:
+            -fwrite-ide-info -Wno-prepositive-qualified-module
+            -Wno-missing-safe-haskell-mode
+
+test-suite spec
+    type:               exitcode-stdio-1.0
+    main-is:            Spec.hs
+    hs-source-dirs:     test
+    other-modules:
+        CfnFlipSpec
+        Paths_cfn_flip
+
+    default-language:   Haskell2010
+    default-extensions:
+        BangPatterns DataKinds DeriveAnyClass DeriveFoldable DeriveFunctor
+        DeriveGeneric DeriveLift DeriveTraversable DerivingStrategies
+        FlexibleContexts FlexibleInstances GADTs GeneralizedNewtypeDeriving
+        LambdaCase MultiParamTypeClasses NoImplicitPrelude
+        NoMonomorphismRestriction OverloadedStrings RankNTypes
+        RecordWildCards ScopedTypeVariables StandaloneDeriving
+        TypeApplications TypeFamilies
+
+    ghc-options:
+        -Weverything -Wno-missing-exported-signatures
+        -Wno-missing-import-lists -Wno-unsafe
+
+    build-depends:
+        Glob >=0.10.0,
+        aeson >=1.4.6.0,
+        base >=4.11 && <10,
+        cfn-flip -any,
+        conduit >=1.3.1.2,
+        filepath >=1.4.2.1,
+        hspec >=2.8.1,
+        libyaml >=0.1.2
+
+    if impl(ghc >8.10)
+        default-extensions: DerivingVia
+        ghc-options:
+            -fwrite-ide-info -Wno-prepositive-qualified-module
+            -Wno-missing-safe-haskell-mode
diff --git a/src/CfnFlip.hs b/src/CfnFlip.hs
new file mode 100644
--- /dev/null
+++ b/src/CfnFlip.hs
@@ -0,0 +1,38 @@
+-- | Haskell re-implementation of [cfn_flip](https://github.com/awslabs/aws-cfn-template-flip/blob/837aa56e21cb00b1c7fa0150f0fd38365c508e4e/cfn_flip/yaml_dumper.py)
+module CfnFlip
+  ( yamlFileToJson
+  , yamlToJson
+  , jsonToYamlFile
+  , jsonFileToYaml
+  , jsonToYaml
+  ) where
+
+import CfnFlip.Prelude
+
+import CfnFlip.Aeson
+import qualified CfnFlip.JsonToYaml as JsonToYaml
+import qualified CfnFlip.Yaml as Yaml
+import qualified CfnFlip.YamlToJson as YamlToJson
+
+-- | Read a file of Yaml and produce a @'FromJSON' a@
+yamlFileToJson :: (MonadIO m, FromJSON a) => FilePath -> m a
+yamlFileToJson = yamlToJson <=< readFileBinary
+
+-- | Convert a 'ByteString' of Yaml to a @'FromJSON' a@
+yamlToJson :: (MonadIO m, FromJSON a) => ByteString -> m a
+yamlToJson = Yaml.decode YamlToJson.translate
+
+-- | Write a @'ToJSON a@ to a file as Yaml
+jsonToYamlFile :: (MonadUnliftIO m, ToJSON a) => FilePath -> a -> m ()
+jsonToYamlFile path = writeFileBinary path <=< jsonToYaml
+
+-- | Read a file of JSON and produce a 'ByteString' of Yaml
+--
+-- NB. The conversion occurs at 'Value'.
+--
+jsonFileToYaml :: MonadUnliftIO m => FilePath -> m ByteString
+jsonFileToYaml = jsonToYaml @_ @Value <=< eitherDecodeFileStrictThrow
+
+-- | Convert a 'ToJSON a' to a 'ByteString' of Yaml
+jsonToYaml :: (MonadUnliftIO m, ToJSON a) => a -> m ByteString
+jsonToYaml = Yaml.encode JsonToYaml.translate
diff --git a/src/CfnFlip/Aeson.hs b/src/CfnFlip/Aeson.hs
new file mode 100644
--- /dev/null
+++ b/src/CfnFlip/Aeson.hs
@@ -0,0 +1,13 @@
+module CfnFlip.Aeson
+  ( eitherDecodeFileStrictThrow
+  , module Data.Aeson
+  ) where
+
+import CfnFlip.Prelude
+
+import Data.Aeson
+
+eitherDecodeFileStrictThrow :: (MonadIO m, FromJSON a) => FilePath -> m a
+eitherDecodeFileStrictThrow path = do
+  result <- liftIO $ eitherDecodeFileStrict path
+  either throwString pure result
diff --git a/src/CfnFlip/Conduit.hs b/src/CfnFlip/Conduit.hs
new file mode 100644
--- /dev/null
+++ b/src/CfnFlip/Conduit.hs
@@ -0,0 +1,58 @@
+module CfnFlip.Conduit
+  ( takeBalancedC
+  , module Conduit
+  , module Data.Conduit.List
+  ) where
+
+import CfnFlip.Prelude
+
+import Conduit
+import Data.Conduit.List (sourceList)
+
+-- | Take until an ending element, including any reopened-ended in between
+--
+-- >>> :{
+-- runIdentity
+--   $ runConduit
+--   $ yieldMany "this is (a thing) here) and more"
+--   .| takeBalancedC (== '(') (==')')
+--   .| sinkList
+-- :}
+-- "this is (a thing) here)"
+--
+-- Note that imbalance will terminate early,
+--
+-- >>> :{
+-- runIdentity
+--   $ runConduit
+--   $ yieldMany "this is, a) unexpected but b) the best we can do)"
+--   .| takeBalancedC (== '(') (==')')
+--   .| sinkList
+-- :}
+-- "this is, a)"
+--
+-- Or just run to the end
+--
+-- >>> :{
+-- runIdentity
+--   $ runConduit
+--   $ yieldMany "this is (pretty unlikely"
+--   .| takeBalancedC (== '(') (==')')
+--   .| sinkList
+-- :}
+-- "this is (pretty unlikely"
+--
+takeBalancedC :: Monad m => (a -> Bool) -> (a -> Bool) -> ConduitT a a m ()
+takeBalancedC reopens closes = go (0 :: Int)
+ where
+  go balance = do
+    me <- await
+
+    for_ me $ \a -> do
+      let
+        loop
+          | closes a = unless (balance <= 0) $ go $ balance - 1
+          | reopens a = go $ balance + 1
+          | otherwise = go balance
+
+      yield a >> loop
diff --git a/src/CfnFlip/IntrinsicFunction.hs b/src/CfnFlip/IntrinsicFunction.hs
new file mode 100644
--- /dev/null
+++ b/src/CfnFlip/IntrinsicFunction.hs
@@ -0,0 +1,66 @@
+module CfnFlip.IntrinsicFunction
+  ( getIntrinsicFunction
+  , setIntrinsicFunction
+  , fromIntrinsicFunction
+  ) where
+
+import CfnFlip.Prelude
+
+import CfnFlip.Libyaml (Event(..), Tag(..))
+
+-- | If an 'Event' is using a known @"!X"@, returns the @"Fn::Y"@ for it
+getIntrinsicFunction :: Event -> Maybe ByteString
+getIntrinsicFunction = \case
+  EventScalar _ (UriTag t) _ _ -> lookup t intrinsics
+  EventMappingStart (UriTag t) _ _ -> lookup t intrinsics
+  EventSequenceStart (UriTag t) _ _ -> lookup t intrinsics
+  _ -> Nothing
+
+-- | Set an 'Event' up with a @'UriTag' "!X"@
+setIntrinsicFunction :: String -> Event -> Event
+setIntrinsicFunction tag e = fromMaybe e $ do
+  let t = UriTag tag
+
+  case e of
+    EventScalar x _ y z -> Just $ EventScalar x t y z
+    EventMappingStart _ x y -> Just $ EventMappingStart t x y
+    EventSequenceStart _ x y -> Just $ EventSequenceStart t x y
+    _ -> Nothing
+
+fromIntrinsicFunction :: Event -> Maybe String
+fromIntrinsicFunction = \case
+  EventScalar x _ _ _ -> lookup x swappedIntrinsics
+  _ -> Nothing
+
+intrinsics :: [(String, ByteString)]
+intrinsics = map
+  toFn
+  [ "And"
+  , "Base64"
+  , "Cidr"
+  , "Condition"
+  , "Equals"
+  , "FindInMap"
+  , "GetAtt"
+  , "GetAZs"
+  , "If"
+  , "ImportValue"
+  , "Join"
+  , "Not"
+  , "Or"
+  , "Ref"
+  , "Select"
+  , "Split"
+  , "Sub"
+  , "Transform"
+  ]
+ where
+  toFn x
+    | x `elem` ["Ref", "Condition"] = ("!" <> unpack x, encodeUtf8 x)
+    | otherwise = ("!" <> unpack x, "Fn::" <> encodeUtf8 x)
+
+swappedIntrinsics :: [(ByteString, String)]
+swappedIntrinsics = map swap intrinsics
+ where
+  swap :: (a, b) -> (b, a)
+  swap (a, b) = (b, a)
diff --git a/src/CfnFlip/JsonToYaml.hs b/src/CfnFlip/JsonToYaml.hs
new file mode 100644
--- /dev/null
+++ b/src/CfnFlip/JsonToYaml.hs
@@ -0,0 +1,48 @@
+module CfnFlip.JsonToYaml
+  ( InvalidYamlEvent(..)
+  , translate
+  ) where
+
+import CfnFlip.Prelude
+
+import CfnFlip.Conduit
+import CfnFlip.IntrinsicFunction
+import CfnFlip.Libyaml
+
+translate :: MonadIO m => ConduitT Event Event m ()
+translate = awaitForever $ \e -> do
+  mS <- peekC
+
+  case (e, mS) of
+    (EventMappingStart{}, Just s@EventScalar{})
+      | Just tag <- fromIntrinsicFunction s -> do
+        dropC 1 -- Scalar
+
+        await >>= traverse_
+          (\case
+            i | tag == "!GetAtt" -> do
+              (resource, attribute) <- awaitGetAtt i
+              let key = resource <> "." <> attribute
+              yield $ EventScalar key (UriTag tag) SingleQuoted Nothing
+
+            i -> do
+              yield $ setIntrinsicFunction tag i
+              when (startsMapOrSequence i) $ takeMapOrSequenceC i .| translate
+          )
+
+        dropC 1 -- MappingEnd
+    _ -> yield e
+
+awaitGetAtt
+  :: MonadIO m => Event -> ConduitT Event Event m (ByteString, ByteString)
+awaitGetAtt e = do
+  results <- sequence [await, await, await]
+
+  case results of
+    [Just (EventScalar r _ _ _), Just (EventScalar a _ _ _), Just EventSequenceEnd{}]
+      -> pure (r, a)
+    _ ->
+      throwIO
+        $ InvalidYamlEvent e
+        $ "Unexpected GetAtt. Should be two Scalars and a SequenceEnd, saw: "
+        <> show results
diff --git a/src/CfnFlip/Libyaml.hs b/src/CfnFlip/Libyaml.hs
new file mode 100644
--- /dev/null
+++ b/src/CfnFlip/Libyaml.hs
@@ -0,0 +1,55 @@
+module CfnFlip.Libyaml
+  ( InvalidYamlEvent(..)
+  , makeMapping
+  , makeSequence
+  , startsMapOrSequence
+  , isSameStart
+  , isStartsEnd
+  , takeMapOrSequenceC
+  , module Text.Libyaml
+  ) where
+
+import CfnFlip.Prelude
+
+import CfnFlip.Conduit
+import Text.Libyaml
+  (Event(..), MappingStyle(..), SequenceStyle(..), Style(..), Tag(..))
+
+data InvalidYamlEvent = InvalidYamlEvent Event String
+  deriving stock Show
+  deriving anyclass Exception
+
+makeMapping
+  :: Monad m => ByteString -> ConduitT i Event m () -> ConduitT i Event m ()
+makeMapping tag inner = do
+  yield $ EventMappingStart NoTag BlockMapping Nothing
+  yield $ EventScalar tag NoTag Plain Nothing
+  inner
+  yield EventMappingEnd
+
+makeSequence :: Monad m => ConduitT i Event m () -> ConduitT i Event m ()
+makeSequence inner = do
+  yield $ EventSequenceStart NoTag BlockSequence Nothing
+  inner
+  yield EventSequenceEnd
+
+startsMapOrSequence :: Event -> Bool
+startsMapOrSequence = \case
+  EventMappingStart{} -> True
+  EventSequenceStart{} -> True
+  _ -> False
+
+isSameStart :: Event -> Event -> Bool
+x `isSameStart` y = case (x, y) of
+  (EventMappingStart{}, EventMappingStart{}) -> True
+  (EventSequenceStart{}, EventSequenceStart{}) -> True
+  _ -> False
+
+isStartsEnd :: Event -> Event -> Bool
+x `isStartsEnd` y = case (x, y) of
+  (EventMappingStart{}, EventMappingEnd{}) -> True
+  (EventSequenceStart{}, EventSequenceEnd{}) -> True
+  _ -> False
+
+takeMapOrSequenceC :: Monad m => Event -> ConduitT Event Event m ()
+takeMapOrSequenceC e = takeBalancedC (isSameStart e) (isStartsEnd e)
diff --git a/src/CfnFlip/Prelude.hs b/src/CfnFlip/Prelude.hs
new file mode 100644
--- /dev/null
+++ b/src/CfnFlip/Prelude.hs
@@ -0,0 +1,28 @@
+module CfnFlip.Prelude
+  ( module X
+  , readFileBinary
+  , writeFileBinary
+  ) where
+
+import Prelude as X
+
+import Control.Monad as X (unless, when, (<=<))
+import Control.Monad.IO.Class as X (MonadIO(..))
+import Control.Monad.IO.Unlift as X (MonadUnliftIO)
+import Data.ByteString as X (ByteString)
+import Data.Foldable as X (for_, traverse_)
+import Data.Maybe as X (fromMaybe)
+import Data.Text as X (Text, pack, unpack)
+import Data.Text.Encoding as X (encodeUtf8)
+import Data.Word as X (Word8)
+import UnliftIO.Exception as X (Exception, throwIO, throwString)
+
+import qualified Data.ByteString as BS
+
+{-# ANN module ("HLint: ignore Avoid restricted qualification" :: String) #-}
+
+readFileBinary :: MonadIO m => FilePath -> m ByteString
+readFileBinary = liftIO . BS.readFile
+
+writeFileBinary :: MonadIO m => FilePath -> ByteString -> m ()
+writeFileBinary p = liftIO . BS.writeFile p
diff --git a/src/CfnFlip/Yaml.hs b/src/CfnFlip/Yaml.hs
new file mode 100644
--- /dev/null
+++ b/src/CfnFlip/Yaml.hs
@@ -0,0 +1,60 @@
+{-# OPTIONS_GHC -Wno-missing-signatures #-}
+{-# OPTIONS_GHC -Wno-missing-local-signatures #-}
+
+module CfnFlip.Yaml
+  ( encode
+  , decode
+  ) where
+
+import CfnFlip.Prelude
+
+import CfnFlip.Aeson (ToJSON(..))
+import CfnFlip.Conduit
+import CfnFlip.IntrinsicFunction
+import CfnFlip.Libyaml
+import qualified Data.Yaml as Yaml
+import qualified Data.Yaml.Internal as Yaml
+import qualified Text.Libyaml as Libyaml
+
+newtype FromJSONError = FromJSONError String
+  deriving stock Show
+  deriving anyclass Exception
+
+encode
+  :: (MonadUnliftIO m, ToJSON a)
+  => ConduitT Event Event (ResourceT m) ()
+  -> a
+  -> m ByteString
+encode c a =
+  runConduitRes
+    $ sourceList (Yaml.objToStream stringStyle $ toJSON a)
+    .| c
+    .| fixQuoting
+    .| Libyaml.encodeWith formatOptions
+ where
+  stringStyle = Yaml.defaultStringStyle
+
+  formatOptions =
+    Libyaml.setTagRendering Libyaml.renderUriTags Libyaml.defaultFormatOptions
+
+fixQuoting :: Monad m => ConduitT Event Event m ()
+fixQuoting = awaitForever $ yield . \case
+  e@(EventScalar x t _ z) | Just _ <- getIntrinsicFunction e ->
+    EventScalar x t SingleQuoted z
+  e -> e
+
+-- We can't type this because Parse (nor ParserState) is exported
+--
+-- TODO: File a bug
+--
+-- decode
+--   :: (MonadIO m, FromJSON a)
+--   => ConduitT Event Event Yaml.Parse ()
+--   -> ByteString
+--   -> m a
+decode c bs = liftIO $ do
+  result <- Yaml.decodeHelper $ Libyaml.decode bs .| c
+
+  case result of
+    Left a -> throwIO a
+    Right (_, b) -> either (throwIO . FromJSONError) pure b
diff --git a/src/CfnFlip/YamlToJson.hs b/src/CfnFlip/YamlToJson.hs
new file mode 100644
--- /dev/null
+++ b/src/CfnFlip/YamlToJson.hs
@@ -0,0 +1,44 @@
+module CfnFlip.YamlToJson
+  ( InvalidYamlEvent(..)
+  , translate
+  ) where
+
+import CfnFlip.Prelude
+
+import CfnFlip.Conduit
+import CfnFlip.IntrinsicFunction
+import CfnFlip.Libyaml
+import qualified Data.ByteString as BS
+import qualified Prelude as Unsafe (toEnum)
+
+translate :: MonadIO m => ConduitT Event Event m ()
+translate = awaitForever $ \case
+  e@(EventScalar x (UriTag "!GetAtt") _ _) -> do
+    (resource, attribute) <- maybe (throwInvalidGetAtt e x) pure $ parseGetAtt x
+
+    makeMapping "Fn::GetAtt" $ makeSequence $ yieldMany
+      [ EventScalar resource NoTag Plain Nothing
+      , EventScalar attribute NoTag Plain Nothing
+      ]
+
+  e | Just tag <- getIntrinsicFunction e -> do
+    makeMapping tag $ do
+      yield e
+      when (startsMapOrSequence e) $ takeMapOrSequenceC e .| translate
+
+  e -> yield e
+
+parseGetAtt :: ByteString -> Maybe (ByteString, ByteString)
+parseGetAtt x = case BS.split (charToWord8 '.') x of
+  [resource, attribute] -> Just (resource, attribute)
+  _ -> Nothing
+
+throwInvalidGetAtt :: MonadIO m => Event -> ByteString -> m a
+throwInvalidGetAtt e x =
+  throwIO
+    $ InvalidYamlEvent e
+    $ "!GetAtt shoule be \"Resource.Attribute\", saw "
+    <> show x
+
+charToWord8 :: Char -> Word8
+charToWord8 = Unsafe.toEnum . fromEnum
diff --git a/test/CfnFlipSpec.hs b/test/CfnFlipSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/CfnFlipSpec.hs
@@ -0,0 +1,51 @@
+module CfnFlipSpec
+  ( spec
+  ) where
+
+import CfnFlip.Prelude
+
+import CfnFlip
+import CfnFlip.Aeson hiding (json)
+import CfnFlip.Conduit
+import qualified CfnFlip.JsonToYaml as JsonToYaml
+import qualified CfnFlip.YamlToJson as YamlToJson
+import System.FilePath ((-<.>))
+import System.FilePath.Glob (globDir1)
+import Test.Hspec
+import qualified Text.Libyaml as Libyaml
+
+spec :: Spec
+spec = do
+  yamls <- runIO $ globDir1 "*.yaml" "test/examples"
+  jsons <- runIO $ globDir1 "*.json" "test/examples"
+
+  describe "yaml-to-json" $ do
+    for_ yamls $ \yaml -> do
+      it yaml $ do
+        when (yaml == "test/examples/test_json_data.yaml") $ pendingWith
+          "Converting Scalar strings of JSON to actual Mapping objects"
+
+        actual <- yamlFileToJson @_ @Value yaml
+        expected <- eitherDecodeFileStrictThrow @_ @Value $ yaml -<.> "json"
+        actual `shouldBe` expected
+
+  describe "json-to-yaml-to-json" $ do
+    for_ jsons $ \json -> do
+      it json $ do
+        actual <- yamlToJson =<< jsonFileToYaml json
+        expected <- eitherDecodeFileStrictThrow @_ @Value json
+        actual `shouldBe` expected
+
+  describe "yaml-to-json-to-yaml" $ do
+    for_ yamls $ \yaml -> do
+      it yaml $ do
+        expected <-
+          runResourceT $ runConduit $ Libyaml.decodeFile yaml .| sinkList
+        actual <-
+          runConduit
+          $ sourceList expected
+          .| YamlToJson.translate
+          .| JsonToYaml.translate
+          .| sinkList
+
+        actual `shouldBe` expected
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,2 @@
+{-# OPTIONS_GHC -fno-warn-missing-export-lists #-}
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/test/examples/clean.json b/test/examples/clean.json
new file mode 100644
--- /dev/null
+++ b/test/examples/clean.json
@@ -0,0 +1,19 @@
+{
+    "Description": "Test",
+    "Foo": {
+        "Fn::Sub": "The ${cake} is a lie"
+    },
+    "Bar": {
+        "Fn::Base64": {
+            "Ref": "Binary"
+        }
+    },
+    "Baz": {
+        "Fn::Sub": [
+            "The cake is a...\n${CakeStatus}",
+            {
+                "CakeStatus": "lie"
+            }
+        ]
+    }
+}
diff --git a/test/examples/clean.yaml b/test/examples/clean.yaml
new file mode 100644
--- /dev/null
+++ b/test/examples/clean.yaml
@@ -0,0 +1,9 @@
+Description: Test
+Foo: !Sub 'The ${cake} is a lie'
+Bar: !Base64
+  Ref: Binary
+Baz: !Sub
+  - |-
+    The cake is a...
+    ${CakeStatus}
+  - CakeStatus: lie
diff --git a/test/examples/get-att.json b/test/examples/get-att.json
new file mode 100644
--- /dev/null
+++ b/test/examples/get-att.json
@@ -0,0 +1,9 @@
+{
+  "Resources": {
+    "Foo": {
+      "Properties": {
+        "Name": {"Fn::GetAtt": ["Bar", "Baz"]}
+      }
+    }
+  }
+}
diff --git a/test/examples/get-att.yaml b/test/examples/get-att.yaml
new file mode 100644
--- /dev/null
+++ b/test/examples/get-att.yaml
@@ -0,0 +1,4 @@
+Resources:
+  Foo:
+    Properties:
+      Name: !GetAtt 'Bar.Baz'
diff --git a/test/examples/import_value.json b/test/examples/import_value.json
new file mode 100644
--- /dev/null
+++ b/test/examples/import_value.json
@@ -0,0 +1,11 @@
+{
+  "Resources": {
+    "Foo": {
+      "Properties": {
+        "Name": {
+          "Fn::ImportValue": "networking-VpcId"
+        }
+      }
+    }
+  }
+}
diff --git a/test/examples/import_value.yaml b/test/examples/import_value.yaml
new file mode 100644
--- /dev/null
+++ b/test/examples/import_value.yaml
@@ -0,0 +1,4 @@
+Resources:
+  Foo:
+    Properties:
+      Name: !ImportValue "networking-VpcId"
diff --git a/test/examples/test.json b/test/examples/test.json
new file mode 100644
--- /dev/null
+++ b/test/examples/test.json
@@ -0,0 +1,30 @@
+{
+    "Description": "Test",
+    "Foo": {
+        "Fn::Join": [
+            " ",
+            [
+                "The",
+                {
+                    "Ref": "cake"
+                },
+                "is",
+                "a",
+                "lie"
+            ]
+        ]
+    },
+    "Bar": {
+        "Fn::Base64": {
+            "Ref": "Binary"
+        }
+    },
+    "Baz": {
+        "Fn::Sub": [
+            "The cake is a...\n${CakeStatus}",
+            {
+                "CakeStatus": "lie"
+            }
+        ]
+    }
+}
diff --git a/test/examples/test.yaml b/test/examples/test.yaml
new file mode 100644
--- /dev/null
+++ b/test/examples/test.yaml
@@ -0,0 +1,13 @@
+Description: Test
+Foo: !Join
+  - ' '
+  - - The
+    - !Ref 'cake'
+    - is
+    - a
+    - lie
+Bar: !Base64
+  Ref: Binary
+Baz: !Sub
+  - "The cake is a...\n${CakeStatus}"
+  - CakeStatus: lie
diff --git a/test/examples/test_json_data.json b/test/examples/test_json_data.json
new file mode 100644
--- /dev/null
+++ b/test/examples/test_json_data.json
@@ -0,0 +1,26 @@
+{
+    "AWSTemplateFormatVersion": "2010-09-09",
+    "Transform": "AWS::Serverless-2016-10-31",
+    "Resources": {
+        "ExampleStateMachineResource": {
+            "Type": "AWS::StepFunctions::StateMachine",
+            "Properties": {
+                "DefinitionString": {
+                  "StartAt": "First State",
+                  "States": {
+                    "First State": {
+                      "Type": "Task",
+                      "Resource": "FunctionARN",
+                      "Next": "Second State"
+                    },
+                    "Second State": {
+                      "Type": "Task",
+                      "Resource": "FunctionARN",
+                      "End": true
+                    }
+                  }
+                }
+            }
+        }
+    }
+}
diff --git a/test/examples/test_json_data.yaml b/test/examples/test_json_data.yaml
new file mode 100644
--- /dev/null
+++ b/test/examples/test_json_data.yaml
@@ -0,0 +1,22 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Transform: AWS::Serverless-2016-10-31
+Resources:
+  ExampleStateMachineResource:
+    Type: AWS::StepFunctions::StateMachine
+    Properties:
+      DefinitionString: |-
+        {
+          "StartAt": "First State",
+          "States": {
+            "First State": {
+              "Type": "Task",
+              "Resource": "FunctionARN",
+              "Next": "Second State"
+            },
+            "Second State": {
+              "Type": "Task",
+              "Resource": "FunctionARN",
+              "End": true
+            }
+          }
+        }
diff --git a/test/examples/test_json_data_long_line.json b/test/examples/test_json_data_long_line.json
new file mode 100644
--- /dev/null
+++ b/test/examples/test_json_data_long_line.json
@@ -0,0 +1,8 @@
+{
+      "app.example.com" : {
+         "(?#Configuration)^/(.*?\\.(fcgi|psgi)/)?(manager\\.html|confs/|$)" : "inGroup(\"admingroup\")",
+         "(?#Notifications)/(.*?\\.(fcgi|psgi)/)?notifications" : "inGroup(\"admingroup\") or $uid eq \"myuser\"",
+         "(?#Sessions)/(.*?\\.(fcgi|psgi)/)?sessions" : "inGroup(\"admingroup\") or $uid eq \"myuser\"",
+         "default" : "inGroup(\"admingroup\") or $uid eq \"myuser\""
+      }
+}
diff --git a/test/examples/test_json_def_string_with_sub.json b/test/examples/test_json_def_string_with_sub.json
new file mode 100644
--- /dev/null
+++ b/test/examples/test_json_def_string_with_sub.json
@@ -0,0 +1,85 @@
+{
+    "Resources": {
+        "FeedbackRequestStateMachine": {
+            "Properties": {
+                "DefinitionString": {
+                    "Fn::Sub": [
+                        "\n        {\n            \"Comment\": \"State machine which awaits until defined date to submit a feedback request\",\n            \"StartAt\": \"WaitForDueDate\",\n            \"States\": {\n                \"WaitForDueDate\": {\n                    \"Type\": \"Wait\",\n                    \"TimestampPath\": \"$.plannedAt\",\n                    \"Next\": \"SubmitFeedbackRequestToSQS\"\n                },\n                \"SubmitFeedbackRequestToSQS\": {\n                    \"Type\": \"Task\",\n                    \"Resource\": \"arn:aws:states:::sqs:sendMessage\",\n                    \"Parameters\": {\n                        \"MessageBody.$\": \"$\",\n                        \"QueueUrl\": \"${feedbackQueueUrl}\"\n                    },\n                    \"End\": true\n                }\n            }\n        }\n        ",
+                        {
+                            "feedbackQueueUrl": {
+                                "Ref": "FeedbackRequestsQueue"
+                            }
+                        }
+                    ]
+                },
+                "RoleArn": {
+                    "Fn::Join": [
+                        "/",
+                        [
+                            {
+                                "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role"
+                            },
+                            {
+                                "Ref": "FeedbackRequestStateMachineRole"
+                            }
+                        ]
+                    ]
+                },
+                "StateMachineName": {
+                    "Fn::Sub": "${AWS::StackName}-FeedbackRequestStateMachine"
+                }
+            },
+            "Type": "AWS::StepFunctions::StateMachine"
+        },
+        "FeedbackRequestStateMachineRole": {
+            "Properties": {
+                "AssumeRolePolicyDocument": {
+                    "Statement": [
+                        {
+                            "Action": [
+                                "sts:AssumeRole"
+                            ],
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": [
+                                    {
+                                        "Fn::Sub": "states.${AWS::Region}.amazonaws.com"
+                                    }
+                                ]
+                            }
+                        }
+                    ]
+                },
+                "Policies": [
+                    {
+                        "PolicyDocument": {
+                            "Statement": [
+                                {
+                                    "Action": [
+                                        "sqs:SendMessage"
+                                    ],
+                                    "Effect": "Allow",
+                                    "Resource": {
+                                        "Fn::GetAtt": [
+                                            "FeedbackRequestsQueue",
+                                            "Arn"
+                                        ]
+                                    }
+                                }
+                            ]
+                        },
+                        "PolicyName": "submit_request_to_sqs"
+                    }
+                ]
+            },
+            "Type": "AWS::IAM::Role"
+        },
+        "FeedbackRequestsQueue": {
+            "Properties": {
+                "MessageRetentionPeriod": 172800,
+                "VisibilityTimeout": 600
+            },
+            "Type": "AWS::SQS::Queue"
+        }
+    }
+}
diff --git a/test/examples/test_json_state_machine.json b/test/examples/test_json_state_machine.json
new file mode 100644
--- /dev/null
+++ b/test/examples/test_json_state_machine.json
@@ -0,0 +1,15 @@
+{
+   "AWSTemplateFormatVersion":"2010-09-09",
+   "Description":"An example template for a Step Functions state machine.",
+   "Resources":{
+      "MyStateMachine":{
+         "Type":"AWS::StepFunctions::StateMachine",
+         "Properties":{
+            "StateMachineName":"HelloWorld-StateMachine",
+            "StateMachineType":"STANDARD",
+            "DefinitionString":"{\"StartAt\": \"HelloWorld\", \"States\": {\"HelloWorld\": {\"Type\": \"Task\", \"Resource\": \"arn:aws:lambda:us-east-1:111122223333;:function:HelloFunction\", \"End\": true}}}",
+            "RoleArn":"arn:aws:iam::111122223333:role/service-role/StatesExecutionRole-us-east-1;"
+         }
+      }
+   }
+}
diff --git a/test/examples/test_long.json b/test/examples/test_long.json
new file mode 100644
--- /dev/null
+++ b/test/examples/test_long.json
@@ -0,0 +1,6 @@
+{
+    "Title": "Test pipe-style representation for multiline strings",
+    "TooShort": "foo\nbar\nbaz\nquuux",
+    "LongText": "Two roads diverged in a yellow wood,\nAnd sorry I could not travel both\nAnd be one traveler, long I stood\nAnd looked down one as far as I could\nTo where it bent in the undergrowth: \n\nThen took the other, as just as fair,\nAnd having perhaps the better claim\nBecause it was grassy and wanted wear,\nThough as for that the passing there\nHad worn them really about the same,\n\nAnd both that morning equally lay\nIn leaves no step had trodden black.\nOh, I kept the first for another day! \nYet knowing how way leads on to way\nI doubted if I should ever come back.\n\nI shall be telling this with a sigh\nSomewhere ages and ages hence;\nTwo roads diverged in a wood, and I,\nI took the one less traveled by,\nAnd that has made all the difference",
+    "WideText": "Two roads diverged in a yellow wood, And sorry I could not travel both And be one traveler, long I stood And looked down one as far as I could To where it bent in the undergrowth:   Then took the other, as just as fair, And having perhaps the better claim Because it was grassy and wanted wear, Though as for that the passing there Had worn them really about the same,  And both that morning equally lay In leaves no step had trodden black. Oh, I kept the first for another day!  Yet knowing how way leads on to way I doubted if I should ever come back.  I shall be telling this with a sigh Somewhere ages and ages hence; Two roads diverged in a wood, and I, I took the one less traveled by, And that has made all the difference"
+}
diff --git a/test/examples/test_lorem.json b/test/examples/test_lorem.json
new file mode 100644
--- /dev/null
+++ b/test/examples/test_lorem.json
@@ -0,0 +1,3 @@
+{
+    "Description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed egestas est diam, in malesuada odio ultrices non. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam id lorem leo. Proin sit amet sem in erat pellentesque pretium id cursus orci. In ac nulla id nisi tempor accumsan. Curabitur convallis tempus mauris non pharetra. Vivamus sit amet dui sed ligula vehicula hendrerit.\nInteger imperdiet nunc ut enim imperdiet, et faucibus erat luctus. Nullam pretium a turpis sed malesuada. Nullam ut arcu a quam laoreet tristique at id ex. Morbi convallis augue a libero elementum, vitae vestibulum lacus accumsan. Nunc ornare iaculis tortor, id accumsan turpis blandit ac. Quisque efficitur cursus malesuada. Nunc a nunc malesuada, aliquet ligula eu, consectetur nisl.\nDuis risus nunc, bibendum finibus dapibus at, vehicula sed lectus. Vestibulum molestie leo ante, quis consequat dui pretium eget. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent aliquam risus scelerisque elit rhoncus, in consectetur orci ultricies. Donec lobortis leo erat, a imperdiet risus scelerisque a. Cras nec est neque. Sed nibh orci, feugiat quis finibus at, bibendum non felis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc mollis scelerisque nisi a pellentesque. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer at eros dignissim, luctus risus a, convallis tortor.\nPellentesque vitae bibendum justo, non molestie nisi. Ut luctus facilisis nisi ac ullamcorper. Suspendisse orci ex, efficitur eget gravida tempor, semper a libero. Vivamus sit amet orci vitae felis consequat luctus vel vel nibh. Proin facilisis, nunc eu sodales malesuada, ligula urna varius risus, sed posuere magna urna quis dolor. Proin nec euismod libero. Etiam scelerisque vehicula tincidunt. Mauris vulputate ipsum ac eros lobortis, et condimentum mauris auctor. Praesent a felis sit amet augue lacinia interdum. Duis urna quam, consectetur sed pulvinar in, fringilla eu dolor. Pellentesque feugiat vitae turpis vel rutrum. In quis est lacinia, venenatis lorem eget, efficitur ipsum. Mauris efficitur fringilla leo, at accumsan lorem venenatis ullamcorper. Cras pretium tellus est, vel consequat orci gravida ut.\nNam lobortis tellus hendrerit volutpat pharetra. Praesent at interdum sapien. Donec condimentum dui vel sollicitudin malesuada. Nunc vitae tempus ipsum. Proin et efficitur ante. Mauris feugiat fringilla ex, eu tincidunt justo. Praesent in dolor porta, varius arcu non, ornare risus. Ut ullamcorper dui molestie sapien hendrerit vestibulum ut at turpis. Donec maximus nec arcu et accumsan. Integer tempus placerat dui, id congue ante semper at. Mauris tempus felis sed risus convallis, nec suscipit ligula molestie. In ornare nulla quam, et eleifend elit rutrum ac. Morbi interdum, nulla ac elementum rutrum, mauris odio faucibus augue, non feugiat tortor tellus vel turpis. Fusce id sem odio. Praesent non maximus leo, a cursus felis.\n"
+}
diff --git a/test/examples/test_lorem.yaml b/test/examples/test_lorem.yaml
new file mode 100644
--- /dev/null
+++ b/test/examples/test_lorem.yaml
@@ -0,0 +1,6 @@
+Description: |
+  Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed egestas est diam, in malesuada odio ultrices non. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam id lorem leo. Proin sit amet sem in erat pellentesque pretium id cursus orci. In ac nulla id nisi tempor accumsan. Curabitur convallis tempus mauris non pharetra. Vivamus sit amet dui sed ligula vehicula hendrerit.
+  Integer imperdiet nunc ut enim imperdiet, et faucibus erat luctus. Nullam pretium a turpis sed malesuada. Nullam ut arcu a quam laoreet tristique at id ex. Morbi convallis augue a libero elementum, vitae vestibulum lacus accumsan. Nunc ornare iaculis tortor, id accumsan turpis blandit ac. Quisque efficitur cursus malesuada. Nunc a nunc malesuada, aliquet ligula eu, consectetur nisl.
+  Duis risus nunc, bibendum finibus dapibus at, vehicula sed lectus. Vestibulum molestie leo ante, quis consequat dui pretium eget. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent aliquam risus scelerisque elit rhoncus, in consectetur orci ultricies. Donec lobortis leo erat, a imperdiet risus scelerisque a. Cras nec est neque. Sed nibh orci, feugiat quis finibus at, bibendum non felis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc mollis scelerisque nisi a pellentesque. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer at eros dignissim, luctus risus a, convallis tortor.
+  Pellentesque vitae bibendum justo, non molestie nisi. Ut luctus facilisis nisi ac ullamcorper. Suspendisse orci ex, efficitur eget gravida tempor, semper a libero. Vivamus sit amet orci vitae felis consequat luctus vel vel nibh. Proin facilisis, nunc eu sodales malesuada, ligula urna varius risus, sed posuere magna urna quis dolor. Proin nec euismod libero. Etiam scelerisque vehicula tincidunt. Mauris vulputate ipsum ac eros lobortis, et condimentum mauris auctor. Praesent a felis sit amet augue lacinia interdum. Duis urna quam, consectetur sed pulvinar in, fringilla eu dolor. Pellentesque feugiat vitae turpis vel rutrum. In quis est lacinia, venenatis lorem eget, efficitur ipsum. Mauris efficitur fringilla leo, at accumsan lorem venenatis ullamcorper. Cras pretium tellus est, vel consequat orci gravida ut.
+  Nam lobortis tellus hendrerit volutpat pharetra. Praesent at interdum sapien. Donec condimentum dui vel sollicitudin malesuada. Nunc vitae tempus ipsum. Proin et efficitur ante. Mauris feugiat fringilla ex, eu tincidunt justo. Praesent in dolor porta, varius arcu non, ornare risus. Ut ullamcorper dui molestie sapien hendrerit vestibulum ut at turpis. Donec maximus nec arcu et accumsan. Integer tempus placerat dui, id congue ante semper at. Mauris tempus felis sed risus convallis, nec suscipit ligula molestie. In ornare nulla quam, et eleifend elit rutrum ac. Morbi interdum, nulla ac elementum rutrum, mauris odio faucibus augue, non feugiat tortor tellus vel turpis. Fusce id sem odio. Praesent non maximus leo, a cursus felis.
diff --git a/test/examples/test_multibyte.json b/test/examples/test_multibyte.json
new file mode 100644
--- /dev/null
+++ b/test/examples/test_multibyte.json
@@ -0,0 +1,3 @@
+{
+    "Description": "僕の名前は成田です。"
+}
diff --git a/test/examples/test_multibyte.yaml b/test/examples/test_multibyte.yaml
new file mode 100644
--- /dev/null
+++ b/test/examples/test_multibyte.yaml
@@ -0,0 +1,1 @@
+Description: 僕の名前は成田です。
diff --git a/test/examples/test_multiline.json b/test/examples/test_multiline.json
new file mode 100644
--- /dev/null
+++ b/test/examples/test_multiline.json
@@ -0,0 +1,7 @@
+{
+    "UserData": {
+        "Fn::Base64": {
+            "Fn::Sub": "write_files:\n  - path: /etc/yum.repos.d/nix.repo\n    content: |\n       [nix]\n       name=nix Repository\n"
+        }
+    }
+}
diff --git a/test/examples/test_multiline.yaml b/test/examples/test_multiline.yaml
new file mode 100644
--- /dev/null
+++ b/test/examples/test_multiline.yaml
@@ -0,0 +1,7 @@
+UserData: !Base64
+  Fn::Sub: |
+    write_files:
+      - path: /etc/yum.repos.d/nix.repo
+        content: |
+           [nix]
+           name=nix Repository
diff --git a/test/examples/test_user_data.json b/test/examples/test_user_data.json
new file mode 100644
--- /dev/null
+++ b/test/examples/test_user_data.json
@@ -0,0 +1,7 @@
+{
+    "UserData": {
+        "Fn::Base64": {
+            "Fn::Sub": "#!/bin/bash -xe\necho \"WaitHandle '${WaitHandle}'\"\n/bin/echo '%password%' | /bin/passwd cloud_user --stdin\nyum update -y\nyum install python3 -y\nsu - cloud_user -c 'aws configure set region us-east-1'\nsu - cloud_user -c 'python3 -m pip install boto3'\n/opt/aws/bin/cfn-signal -e $? -r \"UserData script complete\" '${WaitHandle}'\n"
+        }
+    }
+}
diff --git a/test/examples/test_user_data.yaml b/test/examples/test_user_data.yaml
new file mode 100644
--- /dev/null
+++ b/test/examples/test_user_data.yaml
@@ -0,0 +1,10 @@
+UserData: !Base64
+  Fn::Sub: |
+    #!/bin/bash -xe
+    echo "WaitHandle '${WaitHandle}'"
+    /bin/echo '%password%' | /bin/passwd cloud_user --stdin
+    yum update -y
+    yum install python3 -y
+    su - cloud_user -c 'aws configure set region us-east-1'
+    su - cloud_user -c 'python3 -m pip install boto3'
+    /opt/aws/bin/cfn-signal -e $? -r "UserData script complete" '${WaitHandle}'
diff --git a/test/examples/test_yaml_def_string_with_sub.json b/test/examples/test_yaml_def_string_with_sub.json
new file mode 100644
--- /dev/null
+++ b/test/examples/test_yaml_def_string_with_sub.json
@@ -0,0 +1,85 @@
+{
+    "Resources": {
+        "FeedbackRequestStateMachine": {
+            "Properties": {
+                "DefinitionString": {
+                    "Fn::Sub": [
+                        "\n        {\n            \"Comment\": \"State machine which awaits until defined date to submit a feedback request\",\n            \"StartAt\": \"WaitForDueDate\",\n            \"States\": {\n                \"WaitForDueDate\": {\n                    \"Type\": \"Wait\",\n                    \"TimestampPath\": \"$.plannedAt\",\n                    \"Next\": \"SubmitFeedbackRequestToSQS\"\n                },\n                \"SubmitFeedbackRequestToSQS\": {\n                    \"Type\": \"Task\",\n                    \"Resource\": \"arn:aws:states:::sqs:sendMessage\",\n                    \"Parameters\": {\n                        \"MessageBody.$\": \"$\",\n                        \"QueueUrl\": \"${feedbackQueueUrl}\"\n                    },\n                    \"End\": true\n                }\n            }\n        }\n        ",
+                        {
+                            "feedbackQueueUrl": {
+                                "Ref": "FeedbackRequestsQueue"
+                            }
+                        }
+                    ]
+                },
+                "RoleArn": {
+                    "Fn::Join": [
+                        "/",
+                        [
+                            {
+                                "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role"
+                            },
+                            {
+                                "Ref": "FeedbackRequestStateMachineRole"
+                            }
+                        ]
+                    ]
+                },
+                "StateMachineName": {
+                    "Fn::Sub": "${AWS::StackName}-FeedbackRequestStateMachine"
+                }
+            },
+            "Type": "AWS::StepFunctions::StateMachine"
+        },
+        "FeedbackRequestStateMachineRole": {
+            "Properties": {
+                "AssumeRolePolicyDocument": {
+                    "Statement": [
+                        {
+                            "Action": [
+                                "sts:AssumeRole"
+                            ],
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": [
+                                    {
+                                        "Fn::Sub": "states.${AWS::Region}.amazonaws.com"
+                                    }
+                                ]
+                            }
+                        }
+                    ]
+                },
+                "Policies": [
+                    {
+                        "PolicyDocument": {
+                            "Statement": [
+                                {
+                                    "Action": [
+                                        "sqs:SendMessage"
+                                    ],
+                                    "Effect": "Allow",
+                                    "Resource": {
+                                        "Fn::GetAtt": [
+                                            "FeedbackRequestsQueue",
+                                            "Arn"
+                                        ]
+                                    }
+                                }
+                            ]
+                        },
+                        "PolicyName": "submit_request_to_sqs"
+                    }
+                ]
+            },
+            "Type": "AWS::IAM::Role"
+        },
+        "FeedbackRequestsQueue": {
+            "Properties": {
+                "MessageRetentionPeriod": 172800,
+                "VisibilityTimeout": 600
+            },
+            "Type": "AWS::SQS::Queue"
+        }
+    }
+}
diff --git a/test/examples/test_yaml_def_string_with_sub.yaml b/test/examples/test_yaml_def_string_with_sub.yaml
new file mode 100644
--- /dev/null
+++ b/test/examples/test_yaml_def_string_with_sub.yaml
@@ -0,0 +1,40 @@
+Resources:
+  FeedbackRequestStateMachine:
+    Properties:
+      DefinitionString: !Sub
+        - "\n        {\n            \"Comment\": \"State machine which awaits until defined date to submit a feedback request\",\n            \"StartAt\": \"WaitForDueDate\",\n            \"States\": {\n\
+          \                \"WaitForDueDate\": {\n                    \"Type\": \"Wait\",\n                    \"TimestampPath\": \"$.plannedAt\",\n                    \"Next\": \"SubmitFeedbackRequestToSQS\"\
+          \n                },\n                \"SubmitFeedbackRequestToSQS\": {\n                    \"Type\": \"Task\",\n                    \"Resource\": \"arn:aws:states:::sqs:sendMessage\",\n    \
+          \                \"Parameters\": {\n                        \"MessageBody.$\": \"$\",\n                        \"QueueUrl\": \"${feedbackQueueUrl}\"\n                    },\n                 \
+          \   \"End\": true\n                }\n            }\n        }\n        "
+        - feedbackQueueUrl: !Ref 'FeedbackRequestsQueue'
+      RoleArn: !Join
+        - /
+        - - !Sub 'arn:aws:iam::${AWS::AccountId}:role'
+          - !Ref 'FeedbackRequestStateMachineRole'
+      StateMachineName: !Sub '${AWS::StackName}-FeedbackRequestStateMachine'
+    Type: AWS::StepFunctions::StateMachine
+  FeedbackRequestStateMachineRole:
+    Properties:
+      AssumeRolePolicyDocument:
+        Statement:
+          - Action:
+              - sts:AssumeRole
+            Effect: Allow
+            Principal:
+              Service:
+                - !Sub 'states.${AWS::Region}.amazonaws.com'
+      Policies:
+        - PolicyDocument:
+            Statement:
+              - Action:
+                  - sqs:SendMessage
+                Effect: Allow
+                Resource: !GetAtt 'FeedbackRequestsQueue.Arn'
+          PolicyName: submit_request_to_sqs
+    Type: AWS::IAM::Role
+  FeedbackRequestsQueue:
+    Properties:
+      MessageRetentionPeriod: 172800
+      VisibilityTimeout: 600
+    Type: AWS::SQS::Queue
diff --git a/test/examples/test_yaml_long_line.json b/test/examples/test_yaml_long_line.json
new file mode 100644
--- /dev/null
+++ b/test/examples/test_yaml_long_line.json
@@ -0,0 +1,8 @@
+{
+    "app.example.com": {
+        "(?#Configuration)^/(.*?\\.(fcgi|psgi)/)?(manager\\.html|confs/|$)": "inGroup(\"admingroup\")",
+        "(?#Notifications)/(.*?\\.(fcgi|psgi)/)?notifications": "inGroup(\"admingroup\") or $uid eq \"myuser\"",
+        "(?#Sessions)/(.*?\\.(fcgi|psgi)/)?sessions": "inGroup(\"admingroup\") or $uid eq \"myuser\"",
+        "default": "inGroup(\"admingroup\") or $uid eq \"myuser\""
+    }
+}
diff --git a/test/examples/test_yaml_long_line.yaml b/test/examples/test_yaml_long_line.yaml
new file mode 100644
--- /dev/null
+++ b/test/examples/test_yaml_long_line.yaml
@@ -0,0 +1,5 @@
+app.example.com:
+  (?#Configuration)^/(.*?\.(fcgi|psgi)/)?(manager\.html|confs/|$): inGroup("admingroup")
+  (?#Notifications)/(.*?\.(fcgi|psgi)/)?notifications: inGroup("admingroup") or $uid eq "myuser"
+  (?#Sessions)/(.*?\.(fcgi|psgi)/)?sessions: inGroup("admingroup") or $uid eq "myuser"
+  default: inGroup("admingroup") or $uid eq "myuser"
diff --git a/test/examples/test_yaml_state_machine.json b/test/examples/test_yaml_state_machine.json
new file mode 100644
--- /dev/null
+++ b/test/examples/test_yaml_state_machine.json
@@ -0,0 +1,15 @@
+{
+    "AWSTemplateFormatVersion": "2010-09-09",
+    "Description": "An example template for a Step Functions state machine.",
+    "Resources": {
+        "MyStateMachine": {
+            "Type": "AWS::StepFunctions::StateMachine",
+            "Properties": {
+                "StateMachineName": "HelloWorld-StateMachine",
+                "StateMachineType": "STANDARD",
+                "DefinitionString": "{\"StartAt\": \"HelloWorld\", \"States\": {\"HelloWorld\": {\"Type\": \"Task\", \"Resource\": \"arn:aws:lambda:us-east-1:111122223333;:function:HelloFunction\", \"End\": true}}}",
+                "RoleArn": "arn:aws:iam::111122223333:role/service-role/StatesExecutionRole-us-east-1;"
+            }
+        }
+    }
+}
diff --git a/test/examples/test_yaml_state_machine.yaml b/test/examples/test_yaml_state_machine.yaml
new file mode 100644
--- /dev/null
+++ b/test/examples/test_yaml_state_machine.yaml
@@ -0,0 +1,10 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: An example template for a Step Functions state machine.
+Resources:
+  MyStateMachine:
+    Type: AWS::StepFunctions::StateMachine
+    Properties:
+      StateMachineName: HelloWorld-StateMachine
+      StateMachineType: STANDARD
+      DefinitionString: '{"StartAt": "HelloWorld", "States": {"HelloWorld": {"Type": "Task", "Resource": "arn:aws:lambda:us-east-1:111122223333;:function:HelloFunction", "End": true}}}'
+      RoleArn: arn:aws:iam::111122223333:role/service-role/StatesExecutionRole-us-east-1;
