diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,22 +1,31 @@
 # doctemplates
 
-This is the text templating system used by pandoc.  Its basic
-function is to fill holes in a template in a "Context" that
-provides values for variables.  Control structures are provided
-to test that a variable has a non-blank value and to iterate
-over the items of a list.  Partials---that is, subtemplates
-defined in different files---are also supported.
+[![CI
+tests](https://github.com/jgm/doctemplates/workflows/CI%20tests/badge.svg)](https://github.com/jgm/doctemplates/actions)
 
-Templates may be rendered to lazy or strict `Text`, `String`,
-or a doclayout `Doc`.  (Using a `Doc` allows rendered documents
-to wrap flexibly on breaking spaces.)  A Context can be
-constructed manually or an aeson `Value` may be used.
 
-Unlike the various HTML-centered template engines,
-doctemplates is output-format agnostic, so no automatic escaping
-is done on interpolated values.  Values are assumed to be
-escaped properly in the Context.
+This is the text templating system used by pandoc.  Its basic function is to
+fill variables in a template.  Variables are provided by a "context."
+Any instance of the `ToContext` typeclass (such as an aeson `Value`)
+can serve as the context, or a `Context` value can be constructed manually.
 
+Control structures are provided to test whether a variable has a non-blank
+value and to iterate over the items of a list.  Partials---that is,
+subtemplates defined in different files---are supported.  Filters
+can be used to transform the values of variables or partials.
+The provided filters make it possible to do list enumeration and
+tabular layout in templates.
+
+Templates are rendered to a doclayout `Doc` (which is polymorphic
+in the underlying string type). If `Doc` values are used in the
+context, rendered documents will be able to wrap flexibly on breaking
+spaces.  This feature makes doctemplates more suitable than other
+template engines for plain-text formats (like Markdown).
+
+Unlike the various HTML-centered template engines, doctemplates is
+output-format agnostic, so no automatic escaping is done on interpolated
+values.  Values are assumed to be escaped properly in the Context.
+
 ## Example of use
 
 ``` haskell
@@ -25,6 +34,7 @@
 import qualified Data.Text.IO as T
 import Data.Aeson
 import Text.DocTemplates
+import Text.DocLayout (render)
 
 data Employee = Employee { firstName :: String
                          , lastName  :: String
@@ -42,7 +52,7 @@
   res <- compileTemplate "mytemplate.txt" template
   case res of
          Left e    -> error e
-         Right t   -> T.putStrLn $ renderTemplate t $ object
+         Right t   -> T.putStrLn $ render Nothing $ renderTemplate t $ object
                         ["employee" .=
                           [ Employee "John" "Doe" Nothing
                           , Employee "Omar" "Smith" (Just 30000)
@@ -109,9 +119,6 @@
 - If the value is a JSON boolean, it will be rendered as `true`
   if true, and as the empty string if false.
 
-The value of a variable that occurs by itself on a line
-will be indented to the same level as the opening delimiter of
-the variable.
 
 ## Conditionals
 
@@ -180,12 +187,14 @@
 
 A for loop begins with `for(variable)` (enclosed in
 matched delimiters) and ends with `endfor` (enclosed in matched
-delimiters.  If `variable` is an array, the material inside
-the loop will be evaluated repeatedly, with `variable` being set
-to each value of the array in turn.  If the value of the
-associated variable is not an array, a single iteration will be
-performed on its value.
+delimiters.
 
+- If `variable` is an array, the material inside the loop will
+  be evaluated repeatedly, with `variable` being set to each
+  value of the array in turn, and concatenated.
+- If the value of the associated variable is not an array or
+  a map, a single iteration will be performed on its value.
+
 Examples:
 
 ```
@@ -250,8 +259,8 @@
 ```
 
 If `articles` is an array, this will iterate over its
-values, applying the partial `bibentry()` to each one.
-So the second example above is equivalent to
+values, applying the partial `bibentry()` to each one.  So the
+second example above is equivalent to
 
 ```
 ${ for(articles) }
@@ -284,39 +293,142 @@
 in an explicit `for` loop) cannot contain interpolated
 variables or other template directives.
 
+## Nesting
+
+To ensure that content is "nested," that is, subsequent lines
+indented, use the `^` directive:
+
+```
+$item.number$  $^$$item.description$ ($item.price$)
+```
+
+In this example, if `item.description` has multiple lines,
+they will all be indented to line up with the first line:
+
+```
+00123  A fine bottle of 18-year old
+       Oban whiskey. ($148)
+```
+
+To nest multiple lines to the same level, align them
+with the `^` directive in the template. For example:
+
+```
+$item.number$  $^$$item.description$ ($item.price$)
+               (Available til $item.sellby$.)
+```
+
+will produce
+
+```
+00123  A fine bottle of 18-year old
+       Oban whiskey. ($148)
+       (Available til March 30, 2020.)
+```
+
+If a variable occurs by itself on a line, preceded by whitespace
+and not followed by further text or directives on the same line,
+and the variable's value contains multiple lines, it will be
+nested automatically.
+
 ## Breakable spaces
 
 When rendering to a `Doc`, a distinction can be made between
 breakable and unbreakable spaces.  Normally, spaces in the
 template itself (as opposed to values of the interpolated
 variables) are not breakable, but they can be made breakable
-in part of the template by using the `+reflow` keyword (ended
-with `-reflow`).
+in part of the template by using the `~` keyword (ended
+with another `~`).
 
 ```
-${ +reflow }This long line may break if the document is rendered
-with a short line length.${ -reflow }
+$~$This long line may break if the document is rendered
+with a short line length.$~$
 ```
 
-The `+` keyword has no effect when rendering to `Text`
+The `~` keyword has no effect when rendering to `Text`
 or `String`.
 
-## Nesting
+## Filters
 
-As noted above, the value of a variable that occurs by itself on
-a line will be indented to the same level as the opening
-delimiter of the variable.
+A filter transforms the value of a variable.  Filters are
+specified using a slash (`/`) between the variable name and
+the filter name.  They may go anywhere a variable can go.
+Example:
 
-In addition, any part of a template can be marked explicitly for
-indented rendering, using the `+nest` keyword (o start nesting at
-the column where it appears) and `-nest` to stop nesting.
+```
+$for(name)$
+$name/uppercase$
+$endfor$
 
-Example:
+$for(metadata/pairs)$
+- $it.key$: $it.value$
+$endfor$
+```
 
+Filters may be chained:
+
 ```
-$for(article)$
-- $+nest$$article.author$, "$article.title$," in $article.book$
-  ($article.year$).$-nest$
+$for(employees/pairs)$
+$it.key/alpha/uppercase$. $it.name$
 $endfor$
 ```
+
+Some filters take parameters:
+
+```
+|----------------------|------------|
+$for(employee)$
+$it.name.first/uppercase/left 20 "| "$$it.name.salary/right 10 " | " " |"$
+$endfor$
+|----------------------|------------|
+```
+
+Currently the following filters are predefined:
+
+- `pairs`:  Converts a map or array to an array of maps,
+  each with `key` and `value` fields.  If the original
+  value was an array, the `key` will be the array index,
+  starting with 1.
+
+- `uppercase`:  Converts a textual value to uppercase,
+  and has no effect on other values.
+
+- `lowercase`:  Converts a textual value to lowercase,
+  and has no effect on other values.
+
+- `length`:  Returns the length of the value:  number
+  of characters for a textual value, number of elements
+  for a map or array.
+
+- `reverse`:  Reverses a textual value or array,
+  and has no effect on other values.
+
+- `alpha`:  Converts a textual value that can be
+  read as an integer into a lowercase alphabetic
+  character `a..z` (mod 26), and has no effect on
+  other values.  This can be used to get lettered
+  enumeration from array indices.  To get uppercase
+  letters, chain with `uppercase`.
+
+- `roman`:  Converts a textual value that can be
+  read as an integer into a lowercase roman numerial,
+  and has no effect on other values.  This can be used
+  to get lettered enumeration from array indices.  To
+  get uppercase roman, chain with `uppercase`.
+
+- `left n "leftborder" "rightborder"`:  Renders a textual value
+  in a block of width `n`, aligned to the left, with an optional
+  left and right border.  Has no effect on other values. This
+  can be used to align material in tables.  Widths are positive
+  integers indicating the number of characters.  Borders
+  are strings inside double quotes; literal `"` and `\` characters
+  must be backslash-escaped.
+
+- `right n "leftborder" "rightborder"`:  Renders a textual value
+  in a block of width `n`, aligned to the right, and has no
+  effect on other values.
+
+- `center n "leftborder" "rightborder"`:  Renders a textual
+  value in a block of width `n`, aligned to the center, and has
+  no effect on other values.
 
diff --git a/bench/bench.hs b/bench/bench.hs
--- a/bench/bench.hs
+++ b/bench/bench.hs
@@ -8,17 +8,19 @@
 import Control.Monad.Identity
 import Data.Semigroup ((<>))
 import Data.Aeson (object, (.=), Value)
+import Text.DocLayout (render)
 
 main :: IO ()
 main = do
   Right bigtextTemplate <- compileTemplate "bigtext.txt" bigtext
   defaultMainWith defaultConfig{ timeLimit = 5.0 } $
    [ bench "applyTemplate" $
-      nf (runIdentity . applyTemplate "bigtext" bigtext
+      nf (fmap (render Nothing)
+          . runIdentity . applyTemplate "bigtext" bigtext
           :: Value -> Either String Text)
         val
    , bench "renderTemplate" $
-      nf (renderTemplate bigtextTemplate :: Value -> Text)
+      nf (render Nothing . renderTemplate bigtextTemplate :: Value -> Text)
         val
    ]
 
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,14 @@
 # doctemplates
 
+## 0.6.2
+
+  * Remove unnecessary `TemplateTarget` constraints on
+    `ToContext` instances.
+
+  * Add `ToContext` instance for `Map Text a`.
+
+  * Add `Data`, `Typeable` instances for `Context` and `Val`.
+
 ## 0.6.1
 
   * Indent bare partials.
diff --git a/doctemplates.cabal b/doctemplates.cabal
--- a/doctemplates.cabal
+++ b/doctemplates.cabal
@@ -1,19 +1,18 @@
 name:                doctemplates
-version:             0.6.1
+version:             0.7
 synopsis:            Pandoc-style document templates
 description:         This is the text templating system used by pandoc.
                      It supports variable interpolation, iteration,
-                     tests for non-blank values, and partials.  Templates
-                     may be rendered to a variety of formats, including
-                     doclayout Docs, and variable values may come from
-                     a variety of different sources, including aeson
-                     Values.
+                     tests for non-blank values, filters, and partials.
+                     Templates are rendered to doclayout Docs,
+                     and variable values may come from a variety of
+                     different sources, including aeson Values.
 homepage:            https://github.com/jgm/doctemplates#readme
 license:             BSD3
 license-file:        LICENSE
 author:              John MacFarlane
 maintainer:          jgm@berkeley.edu
-copyright:           2016 John MacFarlane
+copyright:           2016-19 John MacFarlane
 category:            Text
 build-type:          Simple
 -- extra-source-files:
@@ -31,9 +30,11 @@
                        Text.DocTemplates.Internal
   build-depends:       base >= 4.9 && < 5,
                        safe,
+                       text-conversions,
                        aeson,
+                       HsYAML,
                        text,
-                       doclayout >= 0.1 && < 0.2,
+                       doclayout >= 0.2 && < 0.3,
                        containers,
                        vector,
                        filepath,
@@ -52,7 +53,7 @@
   main-is:             test.hs
   build-depends:       base,
                        doctemplates,
-                       doclayout >= 0.1 && < 0.2,
+                       doclayout >= 0.2 && < 0.3,
                        aeson,
                        Glob,
                        tasty,
@@ -70,6 +71,7 @@
   Main-Is:         bench.hs
   Hs-Source-Dirs:  bench
   Build-Depends:   doctemplates,
+                   doclayout >= 0.2 && < 0.3,
                    base >= 4.8 && < 5,
                    criterion >= 1.0 && < 1.6,
                    filepath,
diff --git a/src/Text/DocTemplates.hs b/src/Text/DocTemplates.hs
--- a/src/Text/DocTemplates.hs
+++ b/src/Text/DocTemplates.hs
@@ -8,15 +8,22 @@
    Portability : portable
 
 This is the text templating system used by pandoc. Its basic function is
-to fill holes in a template in a 'Context' that provides values for
-variables. Control structures are provided to test that a variable has a
+to fill variables in a template. Variables are provided by a “context.”
+Any instance of the @ToContext@ typeclass (such as an aeson @Value@) can
+serve as the context, or a @Context@ value can be constructed manually.
+
+Control structures are provided to test whether a variable has a
 non-blank value and to iterate over the items of a list. Partials—that
-is, subtemplates defined in different files—are also supported.
+is, subtemplates defined in different files—are supported. Filters can
+be used to transform the values of variables or partials. The provided
+filters make it possible to do list enumeration and tabular layout in
+templates.
 
-Templates may be rendered to lazy or strict 'Text', 'String', or a
-doclayout 'Doc'. (Using a 'Doc' allows rendered documents to wrap
-flexibly on breaking spaces.) A 'Context' can be constructed manually or
-an aeson 'Value' may be used.
+Templates are rendered to a doclayout @Doc@ (which is polymorphic in the
+underlying string type). If @Doc@ values are used in the context,
+rendered documents will be able to wrap flexibly on breaking spaces.
+This feature makes doctemplates more suitable than other template
+engines for plain-text formats (like Markdown).
 
 Unlike the various HTML-centered template engines, doctemplates is
 output-format agnostic, so no automatic escaping is done on interpolated
@@ -29,6 +36,7 @@
 > import qualified Data.Text.IO as T
 > import Data.Aeson
 > import Text.DocTemplates
+> import Text.DocLayout (render)
 >
 > data Employee = Employee { firstName :: String
 >                          , lastName  :: String
@@ -46,7 +54,7 @@
 >   res <- compileTemplate "mytemplate.txt" template
 >   case res of
 >          Left e    -> error e
->          Right t   -> T.putStrLn $ renderTemplate t $ object
+>          Right t   -> T.putStrLn $ render Nothing $ renderTemplate t $ object
 >                         ["employee" .=
 >                           [ Employee "John" "Doe" Nothing
 >                           , Employee "Omar" "Smith" (Just 30000)
@@ -86,8 +94,8 @@
 > ${foo_bar.baz-bim}
 > ${ foo }
 
-The values of variables are determined by the 'Context' that is passed
-as a parameter to 'renderTemplate'. So, for example, @title@ will return
+The values of variables are determined by the @Context@ that is passed
+as a parameter to @renderTemplate@. So, for example, @title@ will return
 the value of the @title@ field, and @employee.salary@ will return the
 value of the @salary@ field of the object that is the value of the
 @employee@ field.
@@ -100,7 +108,7 @@
 -   If the value is a map, the string @true@ will be rendered.
 -   Every other value will be rendered as the empty string.
 
-When a 'Context' is derived from an aeson (JSON) 'Value', the following
+When a @Context@ is derived from an aeson (JSON) @Value@, the following
 conversions are done:
 
 -   If the value is a number, it will be rendered as an integer if
@@ -108,9 +116,6 @@
 -   If the value is a JSON boolean, it will be rendered as @true@ if
     true, and as the empty string if false.
 
-The value of a variable that occurs by itself on a line will be indented
-to the same level as the opening delimiter of the variable.
-
 == Conditionals
 
 A conditional begins with @if(variable)@ (enclosed in matched
@@ -170,12 +175,14 @@
 == For loops
 
 A for loop begins with @for(variable)@ (enclosed in matched delimiters)
-and ends with @endfor@ (enclosed in matched delimiters. If @variable@ is
-an array, the material inside the loop will be evaluated repeatedly,
-with @variable@ being set to each value of the array in turn. If the
-value of the associated variable is not an array, a single iteration
-will be performed on its value.
+and ends with @endfor@ (enclosed in matched delimiters.
 
+-   If @variable@ is an array, the material inside the loop will be
+    evaluated repeatedly, with @variable@ being set to each value of the
+    array in turn, and concatenated.
+-   If the value of the associated variable is not an array or a map, a
+    single iteration will be performed on its value.
+
 Examples:
 
 > $for(foo)$$foo$$sep$, $endfor$
@@ -254,36 +261,123 @@
 explicit @for@ loop) cannot contain interpolated variables or other
 template directives.
 
+== Nesting
+
+To ensure that content is “nested,” that is, subsequent lines indented,
+use the @^@ directive:
+
+> $item.number$  $^$$item.description$ ($item.price$)
+
+In this example, if @item.description@ has multiple lines, they will all
+be indented to line up with the first line:
+
+> 00123  A fine bottle of 18-year old
+>        Oban whiskey. ($148)
+
+To nest multiple lines to the same level, align them with the @^@
+directive in the template. For example:
+
+> $item.number$  $^$$item.description$ ($item.price$)
+>                (Available til $item.sellby$.)
+
+will produce
+
+> 00123  A fine bottle of 18-year old
+>        Oban whiskey. ($148)
+>        (Available til March 30, 2020.)
+
+If a variable occurs by itself on a line, preceded by whitespace and not
+followed by further text or directives on the same line, and the
+variable’s value contains multiple lines, it will be nested
+automatically.
+
 == Breakable spaces
 
 When rendering to a @Doc@, a distinction can be made between breakable
 and unbreakable spaces. Normally, spaces in the template itself (as
 opposed to values of the interpolated variables) are not breakable, but
-they can be made breakable in part of the template by using the
-@+reflow@ keyword (ended with @-reflow@).
+they can be made breakable in part of the template by using the @~@
+keyword (ended with another @~@).
 
-> ${ +reflow }This long line may break if the document is rendered
-> with a short line length.${ -reflow }
+> $~$This long line may break if the document is rendered
+> with a short line length.$~$
 
-The @+@ keyword has no effect when rendering to @Text@ or @String@.
+The @~@ keyword has no effect when rendering to @Text@ or @String@.
 
-== Nesting
+== Filters
 
-As noted above, the value of a variable that occurs by itself on a line
-will be indented to the same level as the opening delimiter of the
-variable.
+A filter transforms the value of a variable. Filters are specified using
+a slash (@\/@) between the variable name and the filter name. They may
+go anywhere a variable can go. Example:
 
-In addition, any part of a template can be marked explicitly for
-indented rendering, using the @+nest@ keyword (o start nesting at the
-column where it appears) and @-nest@ to stop nesting.
+> $for(name)$
+> $name/uppercase$
+> $endfor$
+>
+> $for(metadata/pairs)$
+> - $it.key$: $it.value$
+> $endfor$
 
-Example:
+Filters may be chained:
 
-> $for(article)$
-> - $+nest$$article.author$, "$article.title$," in $article.book$
->   ($article.year$).$-nest$
+> $for(employees/pairs)$
+> $it.key/alpha/uppercase$. $it.name$
 > $endfor$
 
+Some filters take parameters:
+
+> |----------------------|------------|
+> $for(employee)$
+> $it.name.first/uppercase/left 20 "| "$$it.name.salary/right 10 " | " " |"$
+> $endfor$
+> |----------------------|------------|
+
+Currently the following filters are predefined:
+
+-   @pairs@: Converts a map or array to an array of maps, each with
+    @key@ and @value@ fields. If the original value was an array, the
+    @key@ will be the array index, starting with 1.
+
+-   @uppercase@: Converts a textual value to uppercase, and has no
+    effect on other values.
+
+-   @lowercase@: Converts a textual value to lowercase, and has no
+    effect on other values.
+
+-   @length@: Returns the length of the value: number of characters for
+    a textual value, number of elements for a map or array.
+
+-   @reverse@: Reverses a textual value or array, and has no effect on
+    other values.
+
+-   @alpha@: Converts a textual value that can be read as an integer
+    into a lowercase alphabetic character @a..z@ (mod 26), and has no
+    effect on other values. This can be used to get lettered enumeration
+    from array indices. To get uppercase letters, chain with
+    @uppercase@.
+
+-   @roman@: Converts a textual value that can be read as an integer
+    into a lowercase roman numerial, and has no effect on other values.
+    This can be used to get lettered enumeration from array indices. To
+    get uppercase roman, chain with @uppercase@.
+
+-   @left n \"leftborder\" \"rightborder\"@: Renders a textual value in
+    a block of width @n@, aligned to the left, with an optional left and
+    right border. Has no effect on other values. This can be used to
+    align material in tables. Widths are positive integers indicating
+    the number of characters. Borders are strings inside double quotes;
+    literal @\"@ and @\\@ characters must be backslash-escaped.
+
+-   @right n \"leftborder\" \"rightborder\"@: Renders a textual value in
+    a block of width @n@, aligned to the right, and has no effect on
+    other values.
+
+-   @center n \"leftborder\" \"rightborder\"@: Renders a textual value
+    in a block of width @n@, aligned to the center, and has no effect on
+    other values.
+
+
+
 -}
 
 module Text.DocTemplates ( renderTemplate
@@ -291,25 +385,28 @@
                          , compileTemplateFile
                          , applyTemplate
                          , TemplateMonad(..)
-                         , TemplateTarget(..)
+                         , TemplateTarget
                          , Context(..)
                          , Val(..)
                          , ToContext(..)
                          , FromContext(..)
                          , Template  -- export opaque type
+                         , Doc(..)
                          ) where
 
 import qualified Data.Text.IO as TIO
+import Text.DocLayout (Doc(..))
 import Data.Text (Text)
 import Text.DocTemplates.Parser (compileTemplate)
 import Text.DocTemplates.Internal ( TemplateMonad(..), Context(..),
-            Val(..), ToContext(..), FromContext(..), TemplateTarget(..),
+            Val(..), ToContext(..), FromContext(..), TemplateTarget,
             Template, renderTemplate )
 
 -- | Compile a template from a file.  IO errors will be
 -- raised as exceptions; template parsing errors result in
 -- Left return values.
-compileTemplateFile :: FilePath -> IO (Either String Template)
+compileTemplateFile :: TemplateTarget a
+                    => FilePath -> IO (Either String (Template a))
 compileTemplateFile templPath = do
   templateText <- TIO.readFile templPath
   compileTemplate templPath templateText
@@ -320,7 +417,7 @@
 -- more than once in the same process, compile it separately
 -- for better performance.
 applyTemplate :: (TemplateMonad m, TemplateTarget a, ToContext a b)
-           => FilePath -> Text -> b -> m (Either String a)
+           => FilePath -> Text -> b -> m (Either String (Doc a))
 applyTemplate fp t val = do
     res <- compileTemplate fp t
     case res of
diff --git a/src/Text/DocTemplates/Internal.hs b/src/Text/DocTemplates/Internal.hs
--- a/src/Text/DocTemplates/Internal.hs
+++ b/src/Text/DocTemplates/Internal.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE CPP #-}
+{-# LANGUAGE ConstraintKinds #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TypeSynonymInstances #-}
@@ -29,17 +30,24 @@
       , Val(..)
       , ToContext(..)
       , FromContext(..)
-      , TemplateTarget(..)
+      , TemplateTarget
       , Template(..)
       , Variable(..)
+      , Filter(..)
+      , Alignment(..)
+      , Border(..)
       ) where
 
-import Safe (lastMay, initDef)
+import Data.Text.Conversions (FromText(..), ToText(..))
 import Data.Aeson (Value(..), ToJSON(..), FromJSON(..), Result(..), fromJSON)
+import Data.YAML (ToYAML(..), FromYAML(..), Node(..), Scalar(..))
 import Control.Monad.Identity
+import qualified Control.Monad.State.Strict as S
+import Data.Char (chr, ord)
+import qualified Data.Text.Read as T
 import qualified Data.Text as T
-import qualified Data.Text.Lazy as TL
 import qualified Data.Text.IO as TIO
+import Text.DocLayout (Doc, HasChars)
 import qualified Text.DocLayout as DL
 import Data.String (IsString(..))
 import Data.Data (Data)
@@ -50,114 +58,87 @@
 import qualified Data.HashMap.Strict as H
 import qualified Data.Vector as V
 import Data.Scientific (floatingOrInteger)
-import Data.List (intersperse, intercalate)
+import Data.List (intersperse)
 #if MIN_VERSION_base(4,11,0)
 #else
 import Data.Semigroup
 #endif
 
 -- | A template.
-data Template =
+data Template a =
        Interpolate Variable
-     | Conditional Variable Template Template
-     | Iterate Variable Template Template
-     | Nested Int Template
-     | Partial Template
-     | Literal Text
-     | Concat Template Template
-     | BreakingSpace
+     | Conditional Variable (Template a) (Template a)
+     | Iterate Variable (Template a) (Template a)
+     | Nested (Template a)
+     | Partial (Template a)
+     | Literal (Doc a)
+     | Concat (Template a) (Template a)
      | Empty
-     deriving (Show, Read, Data, Typeable, Generic, Eq, Ord)
+     deriving (Show, Read, Data, Typeable, Generic, Eq, Ord,
+               Foldable, Traversable, Functor)
 
-instance Semigroup Template where
+instance Semigroup a => Semigroup (Template a) where
   x <> Empty = x
   Empty <> x = x
   x <> y = Concat x y
 
-instance Monoid Template where
+instance Semigroup a => Monoid (Template a) where
   mappend = (<>)
   mempty = Empty
 
+data Filter =
+      ToPairs
+    | ToUppercase
+    | ToLowercase
+    | ToLength
+    | Reverse
+    | ToAlpha
+    | ToRoman
+    | Block Alignment Int Border
+    deriving (Show, Read, Data, Typeable, Generic, Eq, Ord)
+
+data Alignment =
+      LeftAligned
+    | Centered
+    | RightAligned
+    deriving (Show, Read, Data, Typeable, Generic, Eq, Ord)
+
+data Border = Border
+     { borderLeft  :: Text
+     , borderRight :: Text
+     }
+    deriving (Show, Read, Data, Typeable, Generic, Eq, Ord)
+
 -- | A variable which may have several parts (@foo.bar.baz@).
-newtype Variable = Variable { unVariable :: [Text] }
+data Variable =
+  Variable
+    { varParts   :: [Text]
+    , varFilters :: [Filter]
+    }
   deriving (Show, Read, Data, Typeable, Generic, Eq, Ord)
 
 instance Semigroup Variable where
-  Variable xs <> Variable ys = Variable (xs <> ys)
+  Variable xs fs <> Variable ys gs = Variable (xs <> ys) (fs <> gs)
 
 instance Monoid Variable where
-  mempty = Variable []
+  mempty = Variable mempty mempty
   mappend = (<>)
 
--- | A type to which templates can be rendered.
-class Monoid a => TemplateTarget a where
-  fromText           :: Text -> a
-  toText             :: a -> Text
-  removeFinalNewline :: a -> a
-  isEmpty            :: a -> Bool
-  indent             :: Int -> a -> a
-  breakingSpace      :: a
-
-instance TemplateTarget Text where
-  fromText   = id
-  toText     = id
-  removeFinalNewline t =
-    case T.unsnoc t of
-      Just (t', '\n') -> t'
-      _               -> t
-  isEmpty    = T.null
-  indent 0   = id
-  indent ind = T.intercalate ("\n" <> T.replicate ind " ") . T.lines
-  breakingSpace = " "
-
-instance TemplateTarget TL.Text where
-  fromText   = TL.fromStrict
-  toText     = TL.toStrict
-  removeFinalNewline t =
-    case TL.unsnoc t of
-      Just (t', '\n') -> t'
-      _               -> t
-  isEmpty    = TL.null
-  indent 0   = id
-  indent ind = TL.intercalate ("\n" <> TL.replicate (fromIntegral ind) " ")
-               . TL.lines
-  breakingSpace = " "
-
-instance TemplateTarget String where
-  fromText   = T.unpack
-  toText     = T.pack
-  removeFinalNewline t =
-    case lastMay t of
-      Just '\n'       -> initDef t t
-      _               -> t
-  isEmpty    = null
-  indent 0   = id
-  indent ind = intercalate ("\n" <> replicate ind ' ') . lines
-  breakingSpace = " "
-
-instance (DL.HasChars a, IsString a, Eq a)
-    => TemplateTarget (DL.Doc a) where
-  fromText = DL.text . T.unpack
-  toText   = T.pack . DL.foldrChar (:) [] . DL.render Nothing
-  removeFinalNewline = DL.chomp
-  indent = DL.nest
-  isEmpty (DL.Empty)      = True
-  isEmpty (DL.Text 0 _)   = True
-  isEmpty (DL.Concat x y) = isEmpty x && isEmpty y
-  isEmpty _               = False
-  breakingSpace  = DL.space
+type TemplateTarget a =
+  (Monoid a, IsString a, HasChars a, ToText a, FromText a)
 
 -- | A 'Context' defines values for template's variables.
 newtype Context a = Context { unContext :: M.Map Text (Val a) }
-  deriving (Show, Semigroup, Monoid, Traversable, Foldable, Functor)
+  deriving (Show, Semigroup, Monoid, Traversable, Foldable, Functor,
+            Data, Typeable)
 
 -- | A variable value.
 data Val a =
-    SimpleVal  a
+    SimpleVal  (Doc a)
   | ListVal    [Val a]
   | MapVal     (Context a)
   | NullVal
-  deriving (Show, Traversable, Foldable, Functor)
+  deriving (Show, Traversable, Foldable, Functor, Data, Typeable)
 
 -- | The 'ToContext' class provides automatic conversion to
 -- a 'Context' or 'Val'.
@@ -168,26 +149,40 @@
                   _        -> mempty
   toVal     :: b -> Val a
 
-instance TemplateTarget a => ToContext a (Context a) where
+instance ToContext a (Context a) where
   toContext = id
   toVal     = MapVal
 
-instance TemplateTarget a => ToContext a (Val a) where
+instance ToContext a (Val a) where
   toVal     = id
 
 instance TemplateTarget a => ToContext a a where
-  toVal     = SimpleVal
+  toVal     = SimpleVal . DL.literal
 
+instance ToContext a a => ToContext a (Doc a) where
+  toVal    = SimpleVal
+
 -- This is needed because otherwise the compiler tries to
 -- match on ToContext a [b], with a = b = Char, even though
 -- we don't have ToContext Char Char.  I don't understand why.
 instance {-# OVERLAPS #-} ToContext String String where
-  toVal t   = SimpleVal t
+  toVal    = SimpleVal . DL.literal
 
+instance {-# OVERLAPS #-} ToContext String (Doc String) where
+  toVal    = SimpleVal
+
 instance ToContext a b => ToContext a [b] where
   toVal     = ListVal . map toVal
 
-instance TemplateTarget a => ToContext a Value where
+instance ToContext a b => ToContext a (M.Map Text b) where
+  toVal     = MapVal . toContext
+  toContext = Context . M.map toVal
+
+instance TemplateTarget a => ToContext a Bool where
+  toVal True  = SimpleVal "true"
+  toVal False = NullVal
+
+instance (IsString a, TemplateTarget a) => ToContext a Value where
   toContext x = case fromJSON x of
                   Success y -> y
                   Error _   -> mempty
@@ -195,13 +190,6 @@
                   Success y -> y
                   Error _   -> NullVal
 
-instance TemplateTarget a => ToContext a Bool where
-  toVal True  = SimpleVal $ fromText "true"
-  toVal False = NullVal
-
-instance (TemplateTarget a, DL.HasChars a) => ToContext (DL.Doc a) a where
-  toVal t   = SimpleVal $ DL.Text (DL.realLength t) t
-
 -- | The 'FromContext' class provides functions for extracting
 -- values from 'Val' and 'Context'.
 class FromContext a b where
@@ -212,42 +200,68 @@
 instance TemplateTarget a => FromContext a (Val a) where
   fromVal = Just
 
-instance TemplateTarget a => FromContext a a where
+instance TemplateTarget a => FromContext a (Doc a) where
   fromVal (SimpleVal x) = Just x
   fromVal _             = Nothing
 
+instance TemplateTarget a => FromContext a a where
+  fromVal (SimpleVal x) = Just (DL.render Nothing x)
+  fromVal _             = Nothing
+
 -- This is needed because otherwise the compiler tries to
 -- match on FromContext a [b], with a = b = Char, even though
 -- we don't have FromContext Char Char.  I don't understand why.
 instance {-# OVERLAPS #-} FromContext String String where
-  fromVal (SimpleVal x) = Just x
+  fromVal (SimpleVal x) = Just (DL.render Nothing x)
   fromVal _             = Nothing
 
 instance FromContext a b => FromContext a [b] where
   fromVal (ListVal  xs) = mapM fromVal xs
   fromVal x             = sequence [fromVal x]
 
-instance TemplateTarget a => FromJSON (Context a) where
-  parseJSON v = do
-    val <- parseJSON v
-    case val of
-      MapVal o -> return o
-      _        -> fail "Not a MapVal"
-
-instance TemplateTarget a => FromJSON (Val a) where
+instance (IsString a, TemplateTarget a) => FromJSON (Val a) where
   parseJSON v =
     case v of
       Array vec   -> ListVal <$> mapM parseJSON (V.toList vec)
-      String t    -> return $ SimpleVal $ fromText t
-      Number n    -> return $ SimpleVal $ fromText . fromString $
+      String t    -> return $ SimpleVal $ DL.literal $ fromText t
+      Number n    -> return $ SimpleVal $ fromString $
                               case floatingOrInteger n of
                                   Left (r :: Double)   -> show r
                                   Right (i :: Integer) -> show i
-      Bool True   -> return $ SimpleVal $ fromText "true"
+      Bool True   -> return $ SimpleVal "true"
       Object o    -> MapVal . Context . M.fromList . H.toList <$>
                        mapM parseJSON o
       _           -> return NullVal
 
+instance (IsString a, TemplateTarget a) => FromJSON (Context a) where
+  parseJSON v = do
+    val <- parseJSON v
+    case val of
+      MapVal o -> return o
+      _        -> fail "Expecting MapVal"
+
+instance TemplateTarget a => FromYAML (Val a) where
+  parseYAML v =
+    case v of
+      Mapping _ _ m -> MapVal . Context . M.fromList <$>
+                           mapM (\(key, val) -> do
+                                  val' <- parseYAML val
+                                  key' <- parseYAML key
+                                  return (key', val')) (M.toList m)
+      Sequence _ _ xs -> ListVal <$> mapM parseYAML xs
+      Scalar _ (SStr t) -> return $ SimpleVal $ fromString . fromText $ t
+      Scalar _ (SFloat n) -> return $ SimpleVal $ fromString . show $ n
+      Scalar _ (SInt n) -> return $ SimpleVal $ fromString . show $ n
+      Scalar _ (SBool True) -> return $ SimpleVal "true"
+      _           -> return NullVal
+
+instance (IsString a, TemplateTarget a) => FromYAML (Context a) where
+  parseYAML v = do
+    val <- parseYAML v
+    case val of
+      MapVal o -> return o
+      _        -> fail "Expecting MapVal"
+
 instance TemplateTarget a => ToJSON (Context a) where
   toJSON (Context m) = toJSON m
 
@@ -255,9 +269,111 @@
   toJSON NullVal = Null
   toJSON (MapVal m) = toJSON m
   toJSON (ListVal xs) = toJSON xs
-  toJSON (SimpleVal t) = toJSON $ toText t
+  toJSON (SimpleVal d) = toJSON $ toText $ DL.render Nothing d
 
-multiLookup :: [Text] -> Val a -> Val a
+instance TemplateTarget a => ToYAML (Context a) where
+  toYAML (Context m) = toYAML m
+
+instance TemplateTarget a => ToYAML (Val a) where
+  toYAML NullVal = toYAML (Nothing :: Maybe Text)
+  toYAML (MapVal m) = toYAML m
+  toYAML (ListVal xs) = toYAML xs
+  toYAML (SimpleVal d) = toYAML $ toText $ DL.render Nothing d
+
+applyFilter :: TemplateTarget a => Filter -> Val a -> Val a
+applyFilter ToLength val = SimpleVal $ fromString . show $ len
+  where
+   len = case val of
+           SimpleVal d        -> T.length . toText $ DL.render Nothing d
+           MapVal (Context m) -> M.size m
+           ListVal xs         -> length xs
+           NullVal            -> 0
+applyFilter ToUppercase val =
+  case val of
+    SimpleVal d -> SimpleVal . runIdentity $
+                    traverse (pure . fromText . T.toUpper . toText) d
+    _ -> val
+applyFilter ToLowercase val =
+  case val of
+    SimpleVal d -> SimpleVal . runIdentity $
+                    traverse (pure . fromText . T.toLower . toText) d
+    _ -> val
+applyFilter ToPairs val =
+  case val of
+    MapVal (Context m) ->
+      ListVal $ map toPair $ M.toList m
+    ListVal xs         ->
+      ListVal $ map toPair $ zip (map (fromString . show) [(1::Int)..]) xs
+    _                  -> val
+ where
+  toPair (k, v) = MapVal $ Context $ M.fromList
+                    [ ("key", SimpleVal $ fromString . T.unpack $ k)
+                    , ("value", v) ]
+applyFilter Reverse val =
+  case val of
+    SimpleVal d -> SimpleVal . runIdentity $
+                    traverse (pure . fromText . T.reverse . toText) d
+    ListVal xs  -> ListVal (reverse xs)
+    _           -> val
+applyFilter ToAlpha val =
+  case val of
+    SimpleVal (DL.Text _ t) ->
+      case T.decimal (toText t) of
+        Right (y,"") -> SimpleVal $ fromString
+                         [chr (ord 'a' + (y `mod` 26) - 1)]
+        _            -> val
+    _           -> val
+applyFilter ToRoman val =
+  case val of
+    SimpleVal (DL.Text _ t) ->
+      case T.decimal (toText t) of
+        Right (y,"") -> maybe val (SimpleVal . DL.literal . fromText)
+                                  (toRoman y)
+        _            -> val
+    _           -> val
+applyFilter (Block align n border) val =
+  let constructor = case align of
+                      LeftAligned  -> DL.lblock
+                      Centered     -> DL.cblock
+                      RightAligned -> DL.rblock
+      toBorder y = if T.null y
+                      then mempty
+                      else DL.vfill (fromText y)
+  in case nullToSimple val of
+       SimpleVal d -> SimpleVal $
+                        toBorder (borderLeft border) <>
+                        constructor n d <>
+                        toBorder (borderRight border)
+       _           -> val
+
+nullToSimple :: Monoid a => Val a -> Val a
+nullToSimple NullVal = SimpleVal mempty
+nullToSimple x = x
+
+-- | Convert number 0 < x < 4000 to lowercase roman numeral.
+toRoman :: Int -> Maybe Text
+toRoman x
+  | x >= 1000
+  , x < 4000  = ("m" <>) <$> toRoman (x - 1000)
+  | x >= 900  = ("cm" <>) <$> toRoman (x - 900)
+  | x >= 500  = ("d" <>) <$> toRoman (x - 500)
+  | x >= 400  = ("cd" <>) <$> toRoman (x - 400)
+  | x >= 100  = ("c" <>) <$> toRoman (x - 100)
+  | x >= 90   = ("xc" <>) <$> toRoman (x - 90)
+  | x >= 50   = ("l" <>) <$> toRoman (x - 50)
+  | x >= 40   = ("xl" <>) <$> toRoman (x - 40)
+  | x >= 10   = ("x" <>) <$> toRoman (x - 10)
+  | x == 9    = return "ix"
+  | x >= 5    = ("v" <>) <$> toRoman (x - 5)
+  | x == 4    = return "iv"
+  | x >= 1    = ("i" <>) <$> toRoman (x - 1)
+  | x == 0    = return ""
+  | otherwise = Nothing
+
+applyFilters :: TemplateTarget a => [Filter] -> Val a -> Val a
+applyFilters fs x = foldr applyFilter x $ reverse fs
+
+multiLookup :: TemplateTarget a => [Text] -> Val a -> Val a
 multiLookup [] x = x
 multiLookup (t:vs) (MapVal (Context o)) =
   case M.lookup t o of
@@ -265,52 +381,68 @@
     Just v' -> multiLookup vs v'
 multiLookup _ _ = NullVal
 
-resolveVariable :: TemplateTarget a => Variable -> Context a -> [a]
+resolveVariable :: TemplateTarget a => Variable -> Context a -> [Doc a]
 resolveVariable v ctx = resolveVariable' v (MapVal ctx)
 
-resolveVariable' :: TemplateTarget a => Variable -> Val a -> [a]
+resolveVariable' :: TemplateTarget a => Variable -> Val a -> [Doc a]
 resolveVariable' v val =
-  case multiLookup (unVariable v) val of
+  case applyFilters (varFilters v) $ multiLookup (varParts v) val of
     ListVal xs    -> concatMap (resolveVariable' mempty) xs
-    SimpleVal t
-      | isEmpty t -> []
-      | otherwise -> [removeFinalNewline t]
-    MapVal _      -> [fromText "true"]
+    SimpleVal d
+      | DL.isEmpty d -> []
+      | otherwise    -> [removeFinalNl d]
+    MapVal _      -> ["true"]
     NullVal       -> []
 
-withVariable :: TemplateTarget a
-             => Variable -> Context a -> (Context a -> a) -> [a]
+removeFinalNl :: Doc a -> Doc a
+removeFinalNl DL.NewLine        = mempty
+removeFinalNl DL.CarriageReturn = mempty
+removeFinalNl (DL.Concat d1 d2) = d1 <> removeFinalNl d2
+removeFinalNl x                 = x
+
+withVariable :: (Monad m, TemplateTarget a)
+             => Variable -> Context a -> (Context a -> m (Doc a))
+             -> m [Doc a]
 withVariable  v ctx f =
-  case multiLookup (unVariable v) (MapVal ctx) of
-    NullVal     -> mempty
-    ListVal xs  -> map (\iterval -> f $
+  case applyFilters (varFilters v) $ multiLookup (varParts v) (MapVal ctx) of
+    NullVal     -> return mempty
+    ListVal xs  -> mapM (\iterval -> f $
                     Context $ M.insert "it" iterval $ unContext ctx) xs
-    val' -> [f $ Context $ M.insert "it" val' $ unContext ctx]
+    val' -> (:[]) <$> f (Context $ M.insert "it" val' $ unContext ctx)
 
+type RenderState = S.State Int
+
 -- | Render a compiled template in a "context" which provides
 -- values for the template's variables.
 renderTemplate :: (TemplateTarget a, ToContext a b)
-               => Template -> b -> a
-renderTemplate t = renderTemp t . toContext
+               => Template a -> b -> Doc a
+renderTemplate t x = S.evalState (renderTemp t (toContext x)) 0
 
+updateColumn :: TemplateTarget a => Doc a -> RenderState (Doc a)
+updateColumn x = do
+  S.modify $ DL.updateColumn x
+  return x
+
 renderTemp :: forall a . TemplateTarget a
-           => Template -> Context a -> a
-renderTemp (Literal t) _ = fromText t
-renderTemp BreakingSpace _ = breakingSpace
-renderTemp (Interpolate v) ctx = mconcat $ resolveVariable v ctx
+           => Template a -> Context a -> RenderState (Doc a)
+renderTemp (Literal t) _ = updateColumn $ t
+renderTemp (Interpolate v) ctx = updateColumn $ mconcat $ resolveVariable v ctx
 renderTemp (Conditional v ift elset) ctx =
   let res = resolveVariable v ctx
    in case res of
         [] -> renderTemp elset ctx
         _  -> renderTemp ift ctx
-renderTemp (Iterate v t sep) ctx =
-  let sep' = renderTemp sep ctx
-   in mconcat . intersperse sep' $ withVariable v ctx (renderTemp t)
-renderTemp (Nested n t) ctx = indent n $ renderTemp t ctx
+renderTemp (Iterate v t sep) ctx = do
+  xs <- withVariable v ctx (renderTemp t)
+  sep' <- renderTemp sep ctx
+  return . mconcat . intersperse sep' $ xs
+renderTemp (Nested t) ctx = do
+  n <- S.get
+  DL.nest n <$> renderTemp t ctx
 renderTemp (Partial t) ctx = renderTemp t ctx
 renderTemp (Concat t1 t2) ctx =
-  mappend (renderTemp t1 ctx) (renderTemp t2 ctx)
-renderTemp Empty _ = mempty
+  mappend <$> renderTemp t1 ctx <*> renderTemp t2 ctx
+renderTemp Empty _ = return mempty
 
 -- | A 'TemplateMonad' defines a function to retrieve a partial
 -- (from the file system, from a database, or using a default
diff --git a/src/Text/DocTemplates/Parser.hs b/src/Text/DocTemplates/Parser.hs
--- a/src/Text/DocTemplates/Parser.hs
+++ b/src/Text/DocTemplates/Parser.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE BangPatterns #-}
 {- |
    Module      : Text.DocTemplates.Parser
    Copyright   : Copyright (C) 2009-2019 John MacFarlane
@@ -21,76 +22,104 @@
 import Control.Applicative
 import Data.String (IsString(..))
 import Data.Text (Text)
+import qualified Data.Text as T
+import qualified Data.Text.Read as T
 import Data.List (isPrefixOf)
 import System.FilePath
 import Text.DocTemplates.Internal
+import qualified Text.DocLayout as DL
 #if MIN_VERSION_base(4,11,0)
 #else
-import Data.Semigroup ((<>))
+import Data.Semigroup ((<>), Semigroup)
 #endif
 
 -- | Compile a template.  The FilePath parameter is used
 -- to determine a default path and extension for partials
 -- and may be left empty if partials are not used.
-compileTemplate :: TemplateMonad m
-                => FilePath -> Text -> m (Either String Template)
+compileTemplate :: (TemplateMonad m, TemplateTarget a)
+                => FilePath -> Text -> m (Either String (Template a))
 compileTemplate templPath template = do
   res <- P.runParserT (pTemplate <* P.eof)
-           PState{ templatePath   = templPath
-                 , partialNesting = 1
-                 , indentLevel = 0
-                 , beginsLine = True
-                 , breakingSpaces = False } templPath template
+           PState{ templatePath    = templPath
+                 , partialNesting  = 1
+                 , breakingSpaces  = False
+                 , firstNonspace   = P.initialPos templPath
+                 , nestedCol       = Nothing
+                 , insideDirective = False
+                 } templPath template
   case res of
        Left e   -> return $ Left $ show e
        Right x  -> return $ Right x
 
 
 data PState =
-  PState { templatePath   :: FilePath
-         , partialNesting :: Int
-         , indentLevel    :: Int
-         , beginsLine     :: Bool
-         , breakingSpaces :: Bool }
+  PState { templatePath    :: FilePath
+         , partialNesting  :: !Int
+         , breakingSpaces  :: !Bool
+         , firstNonspace   :: P.SourcePos
+         , nestedCol       :: Maybe Int
+         , insideDirective :: Bool
+         }
 
 type Parser = P.ParsecT Text PState
 
-pTemplate :: TemplateMonad m => Parser m Template
+pTemplate :: (TemplateMonad m, TemplateTarget a) => Parser m (Template a)
 pTemplate = do
   P.skipMany pComment
   mconcat <$> many
-    ((pLit <|> pNewline <|> pDirective <|> pEscape) <* P.skipMany pComment)
+    ((pLit <|> pNewline <|> pDirective <|>
+      pEscape) <* P.skipMany pComment)
 
-pNewline :: Monad m => Parser m Template
-pNewline = do
-  nls <- P.string "\n" <|> P.string "\r" <|> P.string "\r\n"
+pEndline :: Monad m => Parser m String
+pEndline = P.try $ do
+  nls <- pLineEnding
+  mbNested <- nestedCol <$> P.getState
+  inside <- insideDirective <$> P.getState
+  case mbNested of
+    Just col -> do
+      P.skipMany $ do
+        P.getPosition >>= guard . (< col) . P.sourceColumn
+        P.char ' ' <|> P.char '\t'
+      curcol <- P.sourceColumn <$> P.getPosition
+      guard $ inside || curcol >= col
+    Nothing  ->  return ()
+  return nls
+
+pBlankLine :: (TemplateTarget a, Monad m) => Parser m (Template a)
+pBlankLine =
+  P.try $ Literal . fromString <$> pLineEnding <* P.lookAhead pNewlineOrEof
+
+pNewline :: (TemplateTarget a, Monad m) => Parser m (Template a)
+pNewline = P.try $ do
+  nls <- pEndline
+  sps <- P.many (P.char ' ' <|> P.char '\t')
   breakspaces <- breakingSpaces <$> P.getState
-  return $
-   if breakspaces
-      then BreakingSpace
-      else Literal $ fromString nls
+  pos <- P.getPosition
+  P.updateState $ \st -> st{ firstNonspace = pos }
+  return $ Literal $
+    if breakspaces
+       then DL.BreakingSpace
+       else fromString $ nls <> sps
 
-pLit :: Monad m => Parser m Template
+pLit :: (TemplateTarget a, Monad m) => Parser m (Template a)
 pLit = do
-  col <- P.sourceColumn <$> P.getPosition
-  ind <- indentLevel <$> P.getState
-  -- eat up indentlevel spaces
-  P.skipMany $ do
-    P.getPosition >>= guard . (< (ind - 1)) . P.sourceColumn
-    P.satisfy (\c -> c == ' ' || c == '\t')
   cs <- P.many1 (P.satisfy (\c -> c /= '$' && c /= '\n' && c /= '\r'))
-  P.updateState $ \st -> st{ beginsLine = col == 1 && all (==' ') cs }
+  when (all (\c -> c == ' ' || c == '\t') cs) $ do
+     pos <- P.getPosition
+     when (P.sourceLine pos == 1) $
+       P.updateState $ \st -> st{ firstNonspace = pos }
   breakspaces <- breakingSpaces <$> P.getState
   if breakspaces
      then return $ toBreakable cs
      else return $ Literal $ fromString cs
 
-toBreakable :: String -> Template
+toBreakable :: TemplateTarget a => String -> Template a
 toBreakable [] = Empty
 toBreakable xs =
   case break isSpacy xs of
     ([], []) -> Empty
-    ([], zs) -> BreakingSpace <> toBreakable (dropWhile isSpacy zs)
+    ([], zs) -> Literal DL.BreakingSpace <>
+                   toBreakable (dropWhile isSpacy zs)
     (ys, []) -> Literal (fromString ys)
     (ys, zs) -> Literal (fromString ys) <> toBreakable zs
 
@@ -106,16 +135,14 @@
   pos <- P.getPosition
   P.setPosition $ P.incSourceColumn pos (- n)
 
-pEscape :: Monad m => Parser m Template
+pEscape :: (TemplateTarget a, Monad m) => Parser m (Template a)
 pEscape = Literal "$" <$ P.try (P.string "$$" <* backupSourcePos 1)
 
-pDirective :: TemplateMonad m
-           => Parser m Template
+pDirective :: (TemplateTarget a, TemplateMonad m)
+           => Parser m (Template a)
 pDirective = do
-  res <- pConditional <|> pForLoop <|> pReflow <|> pNest <|>
+  res <- pConditional <|> pForLoop <|> pReflowToggle <|> pNested <|>
          pInterpolate <|> pBarePartial
-  col <- P.sourceColumn <$> P.getPosition
-  P.updateState $ \st -> st{ beginsLine = col == 1 }
   return res
 
 pEnclosed :: Monad m => Parser m a -> Parser m a
@@ -134,25 +161,37 @@
   P.char ')'
   return result
 
-pConditional :: TemplateMonad m
-             => Parser m Template
+pInside :: Monad m
+        => Parser m (Template a)
+        -> Parser m (Template a)
+pInside parser = do
+  oldInside <- insideDirective <$> P.getState
+  P.updateState $ \st -> st{ insideDirective = True }
+  res <- parser
+  P.updateState $ \st -> st{ insideDirective = oldInside }
+  return res
+
+pConditional :: (TemplateTarget a, TemplateMonad m)
+             => Parser m (Template a)
 pConditional = do
   v <- pEnclosed $ P.try $ P.string "if" *> pParens pVar
-  -- if newline after the "if", then a newline after "endif" will be swallowed
-  multiline <- P.option False (True <$ skipEndline)
-  ifContents <- pTemplate
-  elseContents <- P.option mempty (pElse multiline <|> pElseIf)
-  pEnclosed (P.string "endif")
-  when multiline $ P.option () skipEndline
-  return $ Conditional v ifContents elseContents
+  pInside $ do
+    multiline <- P.option False (True <$ skipEndline)
+    -- if newline after the "if", then a newline after "endif" will be swallowed
+    ifContents <- pTemplate
+    elseContents <- P.option mempty (pElse multiline <|> pElseIf)
+    pEnclosed (P.string "endif")
+    when multiline $ P.option () skipEndline
+    return $ Conditional v ifContents elseContents
 
-pElse :: TemplateMonad m => Bool -> Parser m Template
+pElse :: (TemplateTarget a, TemplateMonad m)
+      => Bool -> Parser m (Template a)
 pElse multiline = do
   pEnclosed (P.string "else")
   when multiline $ P.option () skipEndline
   pTemplate
 
-pElseIf :: TemplateMonad m => Parser m Template
+pElseIf :: (TemplateTarget a, TemplateMonad m) => Parser m (Template a)
 pElseIf = do
   v <- pEnclosed $ P.try $ P.string "elseif" *> pParens pVar
   multiline <- P.option False (True <$ skipEndline)
@@ -161,44 +200,51 @@
   return $ Conditional v ifContents elseContents
 
 skipEndline :: Monad m => Parser m ()
-skipEndline = P.try $
-      (P.skipMany pSpaceOrTab <* P.char '\n')
-  <|> (P.skipMany1 pSpaceOrTab <* P.eof)
+skipEndline = do
+  pEndline
+  pos <- P.lookAhead $ do
+           P.skipMany (P.char ' ' <|> P.char '\t')
+           P.getPosition
+  P.updateState $ \st -> st{ firstNonspace = pos }
 
-pNest :: TemplateMonad m => Parser m Template
-pNest = do
-  col <- P.sourceColumn <$> P.getPosition
-  pEnclosed $ P.string "+nest"
-  P.updateState $ \st -> st{ indentLevel = col - 1 }
-  t <- pTemplate
-  P.optional $ pEnclosed $ P.string "-nest"
-  return $ Nested (col - 1) t
+pReflowToggle :: (Monoid a, Semigroup a, TemplateMonad m)
+              => Parser m (Template a)
+pReflowToggle = do
+  pEnclosed $ P.char '~'
+  P.modifyState $ \st -> st{ breakingSpaces = not (breakingSpaces st) }
+  return mempty
 
-pReflow :: TemplateMonad m => Parser m Template
-pReflow = mempty <$ (pReflowOn <|> pReflowOff)
- where
-  pReflowOn = do
-    pEnclosed $ P.string "+reflow"
-    P.modifyState $ \st -> st{ breakingSpaces = True }
-  pReflowOff = do
-    pEnclosed $ P.string "-reflow"
-    P.modifyState $ \st -> st{ breakingSpaces = False }
+pNested :: (TemplateTarget a, TemplateMonad m) => Parser m (Template a)
+pNested = do
+  col <- P.sourceColumn <$> P.getPosition
+  pEnclosed $ P.char '^'
+  oldNested <- nestedCol <$> P.getState
+  P.updateState $ \st -> st{ nestedCol = Just col }
+  x <- pTemplate
+  xs <- P.many $ P.try $ do
+          y <- mconcat <$> P.many1 pBlankLine
+          z <- pTemplate
+          return (y <> z)
+  let contents = x <> mconcat xs
+  P.updateState $ \st -> st{ nestedCol = oldNested }
+  return $ Nested $ contents
 
-pForLoop :: TemplateMonad m => Parser m Template
+pForLoop :: (TemplateTarget a, TemplateMonad m) => Parser m (Template a)
 pForLoop = do
   v <- pEnclosed $ P.try $ P.string "for" *> pParens pVar
   -- if newline after the "for", then a newline after "endfor" will be swallowed
-  multiline <- P.option False $ skipEndline >> return True
-  contents <- changeToIt v <$> pTemplate
-  sep <- P.option mempty $
-           do pEnclosed (P.string "sep")
-              when multiline $ P.option () skipEndline
-              changeToIt v <$> pTemplate
-  pEnclosed (P.string "endfor")
-  when multiline $ P.option () skipEndline
-  return $ Iterate v contents sep
+  pInside $ do
+    multiline <- P.option False $ skipEndline >> return True
+    contents <- changeToIt v <$> pTemplate
+    sep <- P.option mempty $
+             do pEnclosed (P.string "sep")
+                when multiline $ P.option () skipEndline
+                changeToIt v <$> pTemplate
+    pEnclosed (P.string "endfor")
+    when multiline $ P.option () skipEndline
+    return $ Iterate v contents sep
 
-changeToIt :: Variable -> Template -> Template
+changeToIt :: (Semigroup a) => Variable -> Template a -> Template a
 changeToIt v = go
  where
   go (Interpolate w) = Interpolate (reletter v w)
@@ -208,17 +254,16 @@
         (changeToIt v t1) (changeToIt v t2)
   go (Concat t1 t2) = changeToIt v t1 <> changeToIt v t2
   go (Partial t) = Partial t  -- don't reletter inside partial
-  go (Nested n t) = Nested n (go t)
+  go (Nested t) = Nested (go t)
   go x = x
-  reletter (Variable vs) (Variable ws) =
+  reletter (Variable vs _fs) (Variable ws gs) =
     if vs `isPrefixOf` ws
-       then Variable ("it" : drop (length vs) ws)
-       else Variable ws
+       then Variable ("it" : drop (length vs) ws) gs
+       else Variable ws gs
 
-pInterpolate :: TemplateMonad m
-             => Parser m Template
+pInterpolate :: (TemplateTarget a, TemplateMonad m)
+             => Parser m (Template a)
 pInterpolate = do
-  begins <- beginsLine <$> P.getState
   pos <- P.getPosition
   -- we don't used pEnclosed here, to get better error messages:
   (closer, var) <- P.try $ do
@@ -228,27 +273,37 @@
     P.notFollowedBy (P.char '(') -- bare partial
     return (cl, v)
   res <- (P.char ':' *> (pPartialName >>= pPartial (Just var)))
-      <|> Iterate var (Interpolate (Variable ["it"])) <$> pSep
+      <|> Iterate var (Interpolate (Variable ["it"] [])) <$> pSep
       <|> return (Interpolate var)
   P.skipMany pSpaceOrTab
   closer
-  ends <- P.lookAhead $ P.option False $
-             True <$ P.try (P.skipMany pSpaceOrTab *> pNewlineOrEof)
-  let toNested = case P.sourceColumn pos - 1 of
-                   0 -> id
-                   i -> Nested i
-  case (begins && ends, res) of
-    (True, Interpolate v)
-               -> return $ toNested $ Interpolate v
-    (True, Iterate v (Interpolate v') s)
-               -> return $ toNested $ Iterate v (Interpolate v') s
-    _ -> return res
+  handleNesting False pos res
 
+pLineEnding :: Monad m => Parser m String
+pLineEnding = P.string "\n" <|> P.try (P.string "\r\n") <|> P.string "\r"
+
 pNewlineOrEof :: Monad m => Parser m ()
-pNewlineOrEof = () <$ P.newline <|> P.eof
+pNewlineOrEof = () <$ pLineEnding <|> P.eof
 
-pBarePartial :: TemplateMonad m
-             => Parser m Template
+handleNesting :: TemplateMonad m
+              => Bool -> P.SourcePos -> Template a -> Parser m (Template a)
+handleNesting eatEndline pos templ = do
+  firstNonspacePos <- firstNonspace <$> P.getState
+  let beginline = firstNonspacePos == pos
+  endofline <- (True <$ P.lookAhead pNewlineOrEof) <|> pure False
+  when (eatEndline && beginline) $ P.optional skipEndline
+  mbNested <- nestedCol <$> P.getState
+  let toNested t@(Nested{}) = t
+      toNested t = case P.sourceColumn pos of
+                     1 -> t
+                     n | Just n == mbNested -> t
+                       | otherwise          -> Nested t
+  return $ if beginline && endofline
+              then toNested templ
+              else templ
+
+pBarePartial :: (TemplateTarget a, TemplateMonad m)
+             => Parser m (Template a)
 pBarePartial = do
   pos <- P.getPosition
   (closer, fp) <- P.try $ do
@@ -259,10 +314,7 @@
   res <- pPartial Nothing fp
   P.skipMany pSpaceOrTab
   closer
-  let toNested = case P.sourceColumn pos - 1 of
-                   0 -> id
-                   i -> Nested i
-  return $ toNested res
+  handleNesting True pos res
 
 pPartialName :: TemplateMonad m
              => Parser m FilePath
@@ -271,9 +323,10 @@
   P.string "()"
   return fp
 
-pPartial :: TemplateMonad m
-         => Maybe Variable -> FilePath -> Parser m Template
+pPartial :: (TemplateTarget a, TemplateMonad m)
+         => Maybe Variable -> FilePath -> Parser m (Template a)
 pPartial mbvar fp = do
+  oldst <- P.getState
   separ <- P.option mempty pSep
   tp <- templatePath <$> P.getState
   let fp' = case takeExtension fp of
@@ -289,16 +342,24 @@
             P.setPosition $ P.initialPos fp'
             P.setInput partial
             P.updateState $ \st -> st{ partialNesting = nesting + 1 }
+            P.updateState $ \st -> st{ nestedCol = Nothing }
             res' <- pTemplate <* P.eof
             P.updateState $ \st -> st{ partialNesting = nesting }
             P.setInput oldInput
             P.setPosition oldPos
             return res'
+  P.putState oldst
   case mbvar of
     Just var -> return $ Iterate var t separ
     Nothing  -> return $ Partial t
 
-pSep :: Monad m => Parser m Template
+removeFinalNewline :: Text -> Text
+removeFinalNewline t =
+  case T.unsnoc t of
+    Just (t', '\n') -> t'
+    _ -> t
+
+pSep :: (TemplateTarget a, Monad m) => Parser m (Template a)
 pSep = do
     P.char '['
     xs <- P.many (P.satisfy (/= ']'))
@@ -315,9 +376,7 @@
   P.skipMany (P.satisfy (/='\n'))
   -- If the comment begins in the first column, the line ending
   -- will be consumed; otherwise not.
-  when (P.sourceColumn pos == 1) $ () <$ do
-    pNewlineOrEof
-    P.updateState $ \st -> st{ beginsLine = True }
+  when (P.sourceColumn pos == 1) $ () <$ pNewlineOrEof
 
 pOpenDollar :: Monad m => Parser m (Parser m ())
 pOpenDollar =
@@ -338,8 +397,47 @@
 pVar :: Monad m => Parser m Variable
 pVar = do
   first <- pIdentPart <|> pIt
-  rest <- P.many $ (P.char '.' *> pIdentPart)
-  return $ Variable (first:rest)
+  rest <- P.many (P.char '.' *> pIdentPart)
+  filters <- P.many pFilter
+  return $ Variable (first:rest) filters
+
+pFilter :: Monad m => Parser m Filter
+pFilter = do
+  P.char '/'
+  filterName <- P.many1 P.letter
+  P.notFollowedBy P.letter
+  case filterName of
+    "uppercase" -> return ToUppercase
+    "lowercase" -> return ToLowercase
+    "pairs"     -> return ToPairs
+    "length"    -> return ToLength
+    "alpha"     -> return ToAlpha
+    "roman"     -> return ToRoman
+    "reverse"   -> return Reverse
+    "left"      -> Block LeftAligned <$> pBlockWidth <*> pBlockBorders
+    "right"     -> Block RightAligned <$> pBlockWidth <*> pBlockBorders
+    "center"    -> Block Centered <$> pBlockWidth <*> pBlockBorders
+    _           -> fail $ "Unknown filter " ++ filterName
+
+pBlockWidth :: Monad m => Parser m Int
+pBlockWidth = P.try (do
+  _ <- P.many1 P.space
+  ds <- P.many1 P.digit
+  case T.decimal (T.pack ds) of
+        Right (n,"") -> return n
+        _            -> fail "Expected integer parameter for filter") P.<?>
+          "integer parameter for filter"
+
+pBlockBorders :: Monad m => Parser m Border
+pBlockBorders = do
+  P.skipMany P.space
+  let pBorder = do
+        P.char '"'
+        cs <- P.many $ (P.noneOf ['"','\\']) <|> (P.char '\\' >> P.anyChar)
+        P.char '"'
+        P.skipMany P.space
+        return $ T.pack cs
+  Border <$> P.option mempty pBorder <*> P.option mempty pBorder
 
 pIt :: Monad m => Parser m Text
 pIt = fromString <$> P.try (P.string "it")
diff --git a/test/boilerplate.txt b/test/boilerplate.txt
--- a/test/boilerplate.txt
+++ b/test/boilerplate.txt
@@ -1,2 +1,3 @@
 BOILERPLATE
 HERE
+
diff --git a/test/enum.txt b/test/enum.txt
new file mode 100644
--- /dev/null
+++ b/test/enum.txt
@@ -0,0 +1,2 @@
+$it.key/alpha/uppercase$.  $^$$it.value$
+
diff --git a/test/filters.test b/test/filters.test
new file mode 100644
--- /dev/null
+++ b/test/filters.test
@@ -0,0 +1,61 @@
+{ "foo": 1,
+  "bar": null,
+  "baz": ["a", "b"],
+  "bim": { "Zub": "Sim" },
+  "sup": [ { "biz": "qux" },
+           { "sax": 2 } ],
+  "items": [ "one with\na line break",
+            "two",
+            "three with\na line break" ]
+}
+.
+$bar/length$
+$baz/length$
+$bim.Zub/length$
+$bim/length$
+$sup/length$
+
+$for(baz)$
+$it$
+$it/uppercase$
+$baz$
+$baz/uppercase$
+$endfor$
+
+$for(bim/pairs)$
+$it.key$: $it.value$
+$bim.key$: $bim.value/lowercase$
+$endfor$
+
+$for(baz/pairs)$
+$it.key/roman/uppercase/right 4$. $it.value$
+$endfor$
+
+$items/pairs/reverse:enum()$
+.
+0
+2
+3
+1
+2
+
+a
+A
+a
+A
+b
+B
+b
+B
+
+Zub: Sim
+Zub: sim
+
+   I. a
+  II. b
+
+C.  three with
+    a line break
+B.  two
+A.  one with
+    a line break
diff --git a/test/forloop.test b/test/forloop.test
--- a/test/forloop.test
+++ b/test/forloop.test
@@ -1,9 +1,9 @@
 { "employee":
-  [ { "name": { "first": "John", "last": "Doe" } }
-  , { "name": { "first": "Omar", "last": "Smith" }
+  [ { "name": { "first": "John", "last": "Doe" }
     , "salary": "30000" }
-  , { "name": { "first": "Sara", "last": "Chen" }
+  , { "name": { "first": "Omar", "last": "Smith" }
     , "salary": "60000" }
+  , { "name": { "first": "Sara", "last": "Chen" } }
   ]
 }
 .
@@ -12,10 +12,10 @@
 $endfor$
 
 
-$for(employee)$$employee.salary$$sep$:$endfor$
+$for(employee)$$employee.salary$$sep$; $endfor$
 .
 John Doe;
 Omar Smith;
 Sara Chen
 
-:30000:60000
+30000; 60000; 
diff --git a/test/keyval.txt b/test/keyval.txt
new file mode 100644
--- /dev/null
+++ b/test/keyval.txt
@@ -0,0 +1,1 @@
+
diff --git a/test/loop-in-partial.test b/test/loop-in-partial.test
--- a/test/loop-in-partial.test
+++ b/test/loop-in-partial.test
@@ -1,5 +1,6 @@
 {}
 .
 $loop1()$
+
 .
 (loop)
diff --git a/test/nest.test b/test/nest.test
new file mode 100644
--- /dev/null
+++ b/test/nest.test
@@ -0,0 +1,67 @@
+{ "foo": 1,
+  "baz": ["a", "b"],
+  "bim": { "zub": "sim" },
+  "sup": "a multiline\nstring"
+}
+.
+    $sup$
+   $sup$
+
+   $^$$sup$
+
+$bim.zub$ $^$$sup$
+
+$bim.zub$ $^$$foo$
+          bar $sup$
+
+$for(baz)$
+1. $^$Hello
+   $if(it)$
+     $it$
+   $endif$
+$endfor$
+
+  $^$hey $sup$
+  hey $sup$
+
+  hey $sup$
+
+  hey
+  $if(foo)$
+
+  $foo$
+  $endif$
+  hey
+.
+    a multiline
+    string
+   a multiline
+   string
+
+   a multiline
+   string
+
+sim a multiline
+    string
+
+sim 1
+    bar a multiline
+    string
+
+1. Hello
+     a
+1. Hello
+     b
+
+  hey a multiline
+  string
+  hey a multiline
+  string
+
+  hey a multiline
+  string
+
+  hey
+
+  1
+  hey
diff --git a/test/pad.test b/test/pad.test
new file mode 100644
--- /dev/null
+++ b/test/pad.test
@@ -0,0 +1,53 @@
+{ "sup": "a multiline\nstring",
+  "baz": ["a\nb", "b\nc\nd"],
+  "employee":
+  [ { "name": { "first": "John", "last": "Doe" } }
+  , { "name": { "first": "Omar", "last": "Smith" }
+    , "salary": "30000" }
+  , { "name": { "first": "Sara", "last": "Chen" }
+    , "salary": "60000" }
+  ]
+}
+.
+$sup/right 15$$sup/center 15$$sup/left 15$
+
+$for(baz/pairs)$
+$it.key/alpha/right 4$. $^$$it.value$
+$endfor$
+
++------+-----------+
+$for(baz/pairs)$
+$it.key/right 4 "| " " | "$$it.value/left 10 "" "|"$
++------+-----------+
+$endfor$
+
+|------------|------------|
+$for(employee)$
+$it.name.first/uppercase/left 10 "| "$$it.salary/right 10 " | " " |"$
+$endfor$
+|------------|------------|
+
+.
+    a multiline  a multiline  a multiline
+         string    string     string
+
+   a. a
+      b
+   b. b
+      c
+      d
+
++------+-----------+
+|    1 | a         |
+|      | b         |
++------+-----------+
+|    2 | b         |
+|      | c         |
+|      | d         |
++------+-----------+
+
+|------------|------------|
+| JOHN       |            |
+| OMAR       |      30000 |
+| SARA       |      60000 |
+|------------|------------|
diff --git a/test/partial_foo.txt b/test/partial_foo.txt
new file mode 100644
--- /dev/null
+++ b/test/partial_foo.txt
@@ -0,0 +1,4 @@
+Hello
+$if(foo)$
+$foo$
+$endif$
diff --git a/test/partials.test b/test/partials.test
--- a/test/partials.test
+++ b/test/partials.test
@@ -17,7 +17,10 @@
 
 $employee:name.tex()[; ]$
 
+---
   $boilerplate()$
+---
+$partial_foo()$
 .
 (John) Doe
 (Omar) Smith
@@ -29,5 +32,8 @@
 
 \name{John}{Doe}; \name{Omar}{Smith}; \name{Sara}{Chen}
 
+---
   BOILERPLATE
   HERE
+---
+Hello
diff --git a/test/test.hs b/test/test.hs
--- a/test/test.hs
+++ b/test/test.hs
@@ -29,36 +29,42 @@
 unitTests :: [TestTree]
 unitTests = [
     testCase "compile failure" $ do
-      res <- compileTemplate "" "$if(x$and$endif$"
-      res @?= Left "(line 1, column 6):\nunexpected \"$\"\nexpecting \".\" or \")\""
+      (res :: Either String (Template T.Text)) <-
+        compileTemplate "" "$if(x$and$endif$"
+      res @?= Left "(line 1, column 6):\nunexpected \"$\"\nexpecting \".\", \"/\" or \")\""
   , testCase "compile failure (keyword as variable)" $ do
-      res <- compileTemplate "foobar.txt" "$sep$"
-      res @?= Left "\"foobar.txt\" (line 1, column 5):\nunexpected \"$\"\nexpecting letter or digit or \"()\""
+    (res :: Either String (Template T.Text)) <-
+        compileTemplate "foobar.txt" "$sep$"
+    res @?= Left "\"foobar.txt\" (line 1, column 5):\nunexpected \"$\"\nexpecting letter or digit or \"()\""
+  , testCase "compile failure (unknown filter)" $ do
+    (res :: Either String (Template T.Text)) <-
+        compileTemplate "foobar.txt" "$foo/nope$"
+    res @?= Left "\"foobar.txt\" (line 1, column 10):\nunexpected \"$\"\nexpecting letter, letter or digit or \"()\"\nUnknown filter nope"
+  , testCase "compile failure (missing parameter for filter)" $ do
+    (res :: Either String (Template T.Text)) <-
+        compileTemplate "foobar.txt" "$foo/left$"
+    res @?=  Left "\"foobar.txt\" (line 1, column 10):\nunexpected \"$\"\nexpecting letter, integer parameter for filter, letter or digit or \"()\""
+  , testCase "compile failure (unexpected parameter for filter)" $ do
+    (res :: Either String (Template T.Text)) <-
+        compileTemplate "foobar.txt" "$foo/left a$"
+    res @?= Left "\"foobar.txt\" (line 1, column 11):\nunexpected \"a\"\nexpecting integer parameter for filter"
   , testCase "compile failure (error in partial)" $ do
-      res <- compileTemplate "test/foobar.txt" "$bad()$"
+      (res :: Either String (Template T.Text)) <-
+         compileTemplate "test/foobar.txt" "$bad()$"
       res @?= Left "\"test/bad.txt\" (line 2, column 7):\nunexpected \"s\"\nexpecting \"$\""
   , testCase "comment with no newline" $ do
-      res <- compileTemplate "foo" "$-- hi"
-      res @?= Right mempty
+      (res :: Either String (Template T.Text)) <-
+         compileTemplate "foo" "$-- hi"
+      res @?= Right (mempty :: Template T.Text)
   , testCase "reflow" $ do
-      templ <- compileTemplate "foo" "not breakable and $+reflow$ this is breakable\nok? $foo$$-reflow$"
-      let res :: T.Text
-          res = case templ of
-                  Right t -> render (Just 10)
-                   (renderTemplate t (object ["foo" .= ("42" :: T.Text)]))
-                  Left e  -> T.pack e
-      res @?= "not breakable and \nthis is\nbreakable\nok? 42"
-  , testCase "nest" $ do
-      res <- applyTemplate "foo" "- $+nest$aaa\nbbb$-nest$" (object [])
-      res @?= Right ("- aaa\n  bbb" :: T.Text)
-  , testCase "implicitly closed nest" $ do
-      templ <- compileTemplate "foo" "$if(foo)$\n- $+nest$a\nb$endif$"
+      (templ :: Either String (Template T.Text)) <-
+        compileTemplate "foo" "not breakable and$~$ this is breakable\nok? $foo$$~$"
       let res :: T.Text
           res = case templ of
                   Right t -> render (Just 10)
                    (renderTemplate t (object ["foo" .= ("42" :: T.Text)]))
                   Left e  -> T.pack e
-      res @?= "- a\n  b"
+      res @?= "not breakable and\nthis is\nbreakable\nok? 42"
   ]
 
 {- The test "golden" files are structured as follows:
@@ -72,7 +78,7 @@
 -}
 
 diff :: FilePath -> FilePath -> [String]
-diff ref new = ["diff", "-u", ref, new]
+diff ref new = ["diff", "-u", "--minimal", ref, new]
 
 getTest :: FilePath -> FilePath -> IO TestTree
 getTest tmpdir fp = do
@@ -91,4 +97,5 @@
     res <- applyTemplate templatePath template context
     case res of
       Left e -> error e
-      Right x -> T.writeFile actual $ j' <> ".\n" <> template <> ".\n" <> x
+      Right x -> T.writeFile actual $ j' <> ".\n" <> template <> ".\n" <>
+                    render Nothing x
