diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2014, Kyle Hanson
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Kyle Hanson nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/StrappedTemplates.cabal b/StrappedTemplates.cabal
new file mode 100644
--- /dev/null
+++ b/StrappedTemplates.cabal
@@ -0,0 +1,72 @@
+name:                StrappedTemplates
+version:             0.1.0.0
+synopsis:            General purpose templates in haskell
+homepage:            https://github.com/hansonkd/StrappedTemplates
+license:             BSD3
+license-file:        LICENSE
+author:              Kyle Hanson
+maintainer:          me@khanson.io
+category:            Text
+build-type:          Simple
+cabal-version:       >=1.8
+description:
+  Easy templating in haskell.
+  .
+  >  import Control.Monad.IO.Class
+  >  import qualified Blaze.ByteString.Builder as B
+  >  import qualified Data.Text.Lazy as T
+  >  import Data.Time
+  >  
+  >  import Text.Strapped
+  >  
+  >  makeBucket :: Integer -> InputBucket IO
+  >  makeBucket i = bucketFromList 
+  >        [ ("is", List $ map (LitVal . LitInteger) [1..i])
+  >        , ("ioTime", Func (\_ -> (liftIO $ getCurrentTime) >>= (\c -> return $ LitText $ T.pack $ show c)))
+  >        ]
+  >  
+  >  main :: IO ()
+  >  main = do
+  >    tmpls <- templateStoreFromDirectory "benchmarks/strapped_templates" ".strp"
+  >    case tmpls of
+  >      Left err -> print err
+  >      Right store -> do
+  >        rendered <- render (putStore store defaultConfig) (makeBucket 2) "base_simple.strp"
+  >        either (print) (print . B.toByteString) rendered
+  .
+  @
+  &#160;$&#x7b; ioTime &#x7d;
+  &#160;
+  &#160;&#x7b;$ for i in is $&#x7d;
+  &#160;    $&#x7b; i &#x7d;
+  &#160;&#x7b;$ endfor $&#x7d;
+  @
+
+library
+  exposed-modules:     Text.Strapped, Text.Strapped.Parser, Text.Strapped.Render, Text.Strapped.Types, Text.Strapped.Utils
+  build-depends:       base >= 4.7 && < 4.8,
+                       containers >= 0.5 && < 0.6,
+                       text >= 1.0 && < 1.2, 
+                       blaze-builder >=0.3 && < 0.4,
+                       parsec >=3.1 && < 3.2,
+                       mtl >=2.1 && < 2.3,
+                       transformers >= 0.4 && < 0.5,
+                       filemanip >=0.3.6 && < 0.3.7,
+                       filepath >=1.3 && < 1.4
+  hs-source-dirs:      src
+
+test-suite Main
+  type:            exitcode-stdio-1.0
+  build-depends:   StrappedTemplates >= 0.1 && < 0.2,
+                   base >= 4.7 && < 4.8, 
+                   hspec >= 1.11 && < 1.12,
+                   text >= 1.0 && < 1.2,
+                   bytestring >= 0.10 && < 0.11,
+                   blaze-builder >=0.3 && < 0.4
+
+  ghc-options:     -Wall -rtsopts
+  hs-source-dirs:  tests
+  default-language: Haskell2010
+  hs-source-dirs:  tests
+  main-is:         StrappedTest.hs
+  
diff --git a/src/Text/Strapped.hs b/src/Text/Strapped.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Strapped.hs
@@ -0,0 +1,24 @@
+module Text.Strapped
+  ( -- * Rendering
+    render
+  , defaultConfig
+  , showToBuilder
+    -- ** Controlling variables 
+  , combineBuckets
+  , varBucket
+  , bucketLookup
+  , bucketFromList
+  , emptyBucket
+    -- * Parsing
+  , parseTemplate
+    -- * TemplateLoading
+  , templateStoreFromList
+  , templateStoreFromDirectory
+  , putStore
+  , module Text.Strapped.Types
+  ) where
+  
+import Text.Strapped.Types
+import Text.Strapped.Render
+import Text.Strapped.Parser
+import Text.Strapped.Utils
diff --git a/src/Text/Strapped/Parser.hs b/src/Text/Strapped/Parser.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Strapped/Parser.hs
@@ -0,0 +1,196 @@
+module Text.Strapped.Parser
+  ( parseTemplate
+  ) where
+
+import Control.Applicative ((<*>))
+import Control.Monad
+import Data.Monoid
+import qualified Data.Text.Lazy as T
+import Blaze.ByteString.Builder as B
+import Blaze.ByteString.Builder.Char.Utf8 as B
+import Text.Parsec
+import Text.Parsec.String
+import qualified Text.Parsec.Token as P
+import Text.Parsec.Language (emptyDef)
+import Text.Strapped.Types
+
+
+tagStart :: GenParser Char st String
+tagStart = string "{$"
+
+tagEnd :: GenParser Char st String
+tagEnd = string "$}"
+
+wordString :: GenParser Char st String
+wordString = many1 $ oneOf "_" <|> alphaNum
+
+pathString :: GenParser Char st String
+pathString = many1 $ oneOf "_./" <|> alphaNum
+
+peekChar :: Char -> GenParser Char st ()
+peekChar = void . try . lookAhead . char
+
+tryTag :: GenParser Char st a -> GenParser Char st ()
+tryTag = void . try . tag
+
+tag :: GenParser Char st a -> GenParser Char st a
+tag p = between (tagStart >> spaces) (spaces >> tagEnd) p <?> "Tag"
+
+parseFloat :: GenParser Char st Double
+parseFloat = do sign <- option 1 (do s <- oneOf "+-"
+                                     return $ if s == '-' then-1.0 else 1.0)
+                x    <- P.float $ P.makeTokenParser emptyDef
+                return $ sign * x
+
+parseInt :: GenParser Char st Integer
+parseInt = do sign <- option 1 (do s <- oneOf "+-"
+                                   return $ if s == '-' then-1 else 1)
+              x    <- P.integer $ P.makeTokenParser emptyDef
+              return $ sign * x
+
+parseContent :: GenParser Char st a -> GenParser Char st [ParsedPiece]
+parseContent end = do
+  decls <- many (try $ spaces >> parseDecl)
+  spaces
+  extends <- optionMaybe (try $ spaces >> parseInherits)
+  case (extends) of
+    Just (e, epos) -> do
+      includes <- manyTill parseIsIgnoreSpace end
+      return $ (decls) ++ [ParsedPiece (Inherits e includes) epos]
+    _     -> do
+      ps <- manyTill parsePiece end
+      return $ decls ++ ps
+  where parseIsIgnoreSpace = do {spaces; b <- parseIsBlock; spaces; return b}
+
+parseBlock :: GenParser Char st ParsedPiece
+parseBlock = do
+  pos <- getPosition
+  blockName <- tag (string "block" >> spaces >> wordString) <?> "Block tag"
+  blockContent <- parseContent (tryTag $ string "endblock") 
+  return $ ParsedPiece (BlockPiece blockName blockContent) pos
+
+parseRaw :: GenParser Char st ParsedPiece
+parseRaw = do
+  pos <- getPosition
+  tag (string "raw") <?> "Raw tag"
+  c <- anyChar
+  s <- manyTill anyChar (tryTag (string "endraw"))
+  return $ ParsedPiece (StaticPiece (B.fromString $ c:s)) pos
+
+parseComment :: GenParser Char st ParsedPiece
+parseComment = do
+  pos <- getPosition
+  tag (string "comment") <?> "Comment tag"
+  c <- anyChar
+  s <- manyTill anyChar (tryTag (string "endcomment"))
+  return $ ParsedPiece (StaticPiece mempty) pos
+
+parseFor :: GenParser Char st ParsedPiece
+parseFor = do
+  pos <- getPosition
+  (newVarName, exp) <- (tagStart >> spaces >> string "for" >> argParser) <?> "For tag"
+  blockContent <- parseContent (tryTag $ string "endfor") 
+  return $ ParsedPiece (ForPiece newVarName exp blockContent) pos
+  where argParser = do 
+        spaces
+        v <- wordString
+        spaces >> (string "in") >> spaces
+        func <- parseExpression (try $ spaces >> tagEnd)
+        return (v, func)
+
+parseDecl :: GenParser Char st ParsedPiece
+parseDecl = do {spaces; decl <- parserDecl; spaces; return decl} <?> "Let tag"
+  where parserDecl = do
+          pos <- getPosition
+          tagStart >> spaces
+          string "let" >> spaces
+          varName <- wordString
+          spaces >> string "=" >> spaces
+          func <- parseExpression (try $ spaces >> tagEnd)
+          return $ ParsedPiece (Decl varName func) pos
+          
+parseIsBlock = do
+      blockName <- tag (string "isblock" >> spaces >> wordString) <?> "Isblock tag"
+      blockContent <- parseContent (tryTag $ string "endisblock") 
+      return (blockName, blockContent)
+
+parseInclude :: GenParser Char st ParsedPiece
+parseInclude = do
+  pos <- getPosition
+  tag (parserInclude pos) <?> "Include tag"
+  where parserInclude pos = do
+                string "include" >> spaces
+                includeName <- pathString
+                return $ ParsedPiece (Include includeName) pos
+
+parseInherits = do {pos <- getPosition; mtag <- tag (string "inherits" >> spaces >> pathString); return (mtag, pos)} <?> "Inherits tag"
+
+parseFunc ::  GenParser Char st ParsedPiece
+parseFunc = parserFunc <?> "Call tag"
+  where parserFunc = do
+          pos <- getPosition
+          string "${" >> spaces
+          exp <- parseExpression (try $ spaces >> string "}")
+          return $ ParsedPiece (FuncPiece exp) pos
+
+parseExpression ::  GenParser Char st a -> GenParser Char st ParsedExpression
+parseExpression end = manyPart <?> "Expression"
+  where parseGroup = try parens <|> parseAtomic
+        parseAtomic  = do
+            pos <- getPosition
+            exp <- try parseList <|> 
+                   try (parseString '\"') <|> 
+                   try (parseString '\'') <|> 
+                   try (parseFloat >>= (return . FloatExpression)) <|> 
+                   try (parseInt >>= (return . IntegerExpression)) <|>
+                   literal
+            return $ ParsedExpression exp pos
+        parens = (string "(" >> spaces) >> parseExpression (try $ spaces >> string ")")
+        parseList = between (string "[" >> spaces) 
+                            (spaces >> string "]") 
+                            (sepBy (spaces >> parseGroup) (string ",")) 
+                    >>= (return . ListExpression)
+        manyPart = do
+          pos <- getPosition
+          pieces <- manyTill (spaces >> parseGroup) end
+          return $ ParsedExpression (Multipart pieces) pos
+        parseString esc = parseStringContents esc >>= (return . StringExpression)
+        literal = wordString >>= (return . LookupExpression)
+
+parseStringContents ::  Char -> GenParser Char st String
+parseStringContents esc = between (char esc) (char esc) (many chars)
+    where chars = (try escaped) <|> noneOf [esc]
+          escaped = char '\\' >> choice (zipWith escapedChar codes replacements)
+          escapedChar code replacement = char code >> return replacement
+          codes        = ['b',  'n',  'f',  'r',  't',  '\\', '\"', '\'', '/']
+          replacements = ['\b', '\n', '\f', '\r', '\t', '\\', '\"', '\'', '/']
+
+parseStatic :: GenParser Char st ParsedPiece
+parseStatic = do
+  pos <- getPosition
+  c <- anyChar
+  s <- manyTill anyChar (peekChar '{' <|> peekChar '$' <|> eof)
+  return $ ParsedPiece (StaticPiece (B.fromString $ c:s)) pos
+
+parseNonStatic :: GenParser Char st ParsedPiece
+parseNonStatic =  try parseComment
+              <|> try parseRaw
+              <|> try parseBlock
+              <|> try parseFor
+              <|> try parseInclude
+              <|> parseFunc
+              
+parsePiece :: GenParser Char st ParsedPiece
+parsePiece = (try parseNonStatic <|> parseStatic)
+
+parsePieces :: GenParser Char st [ParsedPiece]
+parsePieces = parseContent eof
+
+parseToTemplate :: GenParser Char st Template
+parseToTemplate = (parseContent eof) >>= (return . Template)
+
+-- | Take a template body and a template name and return either an error or a
+--   renderable template.
+
+parseTemplate :: String -> String -> Either ParseError Template
+parseTemplate s tmplN = parse parseToTemplate tmplN s
diff --git a/src/Text/Strapped/Render.hs b/src/Text/Strapped/Render.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Strapped/Render.hs
@@ -0,0 +1,132 @@
+module Text.Strapped.Render 
+  ( combineBuckets
+  , varBucket
+  , bucketLookup
+  , bucketFromList
+  , emptyBucket
+  , render
+  , defaultConfig
+  ) where
+
+import Blaze.ByteString.Builder
+import Blaze.ByteString.Builder.Char.Utf8
+import Control.Monad
+import qualified Data.Map as M
+import Data.List (intersperse)
+import Data.Monoid ((<>), mempty, mconcat)
+import Control.Monad.Except
+import Control.Monad.IO.Class
+import Data.Maybe (catMaybes)
+import qualified Data.Text.Lazy as T
+import Text.Strapped.Types
+import Text.Parsec.Pos
+
+
+instance Renderable Builder where
+  renderOutput _ = id
+
+instance Renderable Literal where
+  renderOutput (RenderConfig _ ef) (LitText s) = fromLazyText $ ef s
+  renderOutput _ (LitSafe s)     = fromLazyText s
+  renderOutput rc (LitInteger i) = fromShow i
+  renderOutput rc (LitDouble i)  = fromShow i
+  renderOutput _ (LitBuilder b)  = b
+  renderOutput rc (LitList l)    = (fromChar '[') <> 
+                                   (mconcat $ intersperse (fromChar ',') (map (renderOutput rc) l)) <> 
+                                   (fromChar ']')
+  renderOutput rc (LitDyn r) = renderOutput rc r
+  
+-- | Default render configuration. No text escaping.
+defaultConfig :: RenderConfig
+defaultConfig = RenderConfig (\_ -> return Nothing) id
+
+-- | If the first bucket fails, try the second.
+combineBuckets :: InputBucket m -> InputBucket m -> InputBucket m
+combineBuckets = (++) 
+
+-- | Basic bucket. Matches on string and return input. Returns Nothing for
+--   everything else.
+varBucket :: String -> Input m -> InputBucket m
+varBucket varName o = [M.fromList [(varName, o)]]
+
+emptyBucket :: InputBucket m
+emptyBucket = []
+
+bucketLookup :: String -> InputBucket m -> Maybe (Input m)
+bucketLookup v [] = Nothing
+bucketLookup v (m:ms) = maybe (bucketLookup v ms) Just (M.lookup v m)
+
+bucketFromList :: [(String, Input m)] -> InputBucket m
+bucketFromList l = [M.fromList l]
+
+getOrThrow v getter pos = maybe (throwError $ InputNotFound v pos) return (bucketLookup v getter)
+
+reduceExpression :: Monad m => RenderConfig -> ParsedExpression -> InputBucket m -> ExceptT StrapError m Literal
+reduceExpression c (ParsedExpression exp pos) getter = convert exp
+  where convertMore exp = reduceExpression c exp getter
+        convert (IntegerExpression i) = return $ LitInteger i
+        convert (FloatExpression i) = return $ LitDouble i
+        convert (StringExpression s) = return $ LitText (T.pack s)
+        convert (Multipart []) = return $ LitEmpty
+        convert (Multipart (f:[])) = convertMore f
+        convert (Multipart ((ParsedExpression (LookupExpression func) ipos):args)) = do
+          val <- getOrThrow func getter pos
+          case val of
+            (Func f) -> convert (Multipart args) >>= f
+            _ -> throwError $ StrapError ("`" ++ func ++ "` is not a function but has args: " ++ (show args)) ipos
+        convert (Multipart v) = throwError $ StrapError ("`" ++ (show v) ++ "` cannot be reduced.") pos
+        convert (ListExpression args) = mapM convertMore args >>= (return . LitList) 
+        convert (LookupExpression f) = do
+            val <- getOrThrow f getter pos
+            inputToLiteral val
+        inputToLiteral inp = case inp of
+                    (List args) -> mapM inputToLiteral args >>= (return . LitList)
+                    (RenderVal r) -> return $ LitBuilder (renderOutput c r)
+                    (Func f) -> f LitEmpty
+                    (LitVal v) -> return v
+
+-- | Using a 'TemplateStore' and an 'InputBucket' render the template name.
+render :: MonadIO m => RenderConfig -> InputBucket m -> String -> m (Either StrapError Output)
+render renderConfig getter' tmplName = do
+      tmpl <- liftIO $ tmplStore tmplName
+      maybe (return $ Left $ TemplateNotFound tmplName (initialPos tmplName)) 
+            (\(Template c) -> runExceptT $ loop mempty mempty getter' c) 
+            tmpl
+  where tmplStore = templateStore renderConfig
+        loop accum _ _ [] = return accum
+        loop accum blks getter ((ParsedPiece (StaticPiece s) pos):ps) =
+          loop (accum <> s) blks getter ps
+        loop accum blks getter ((ParsedPiece (BlockPiece n def) pos):ps) = 
+          (maybe (loop accum blks getter def) 
+                 (\content -> loop accum blks getter content)
+                 (lookup n blks)
+          ) >>= (\a -> loop a blks getter ps)
+        loop accum blks getter ((ParsedPiece (ForPiece n exp c) pos):ps) = do
+          var <- reduceExpression renderConfig exp getter
+          case var of 
+            LitList l -> (processFor getter n c accum blks l) >>= (\a -> loop a blks getter ps)
+            _ -> throwError $ StrapError ("`" ++ show exp ++ "` is not a LitList") pos
+        loop accum blks getter ((ParsedPiece (Inherits n b) pos):ps) =
+            liftIO (tmplStore n) >>=
+            maybe (throwError (TemplateNotFound n pos))
+                  (\(Template c) -> (loop accum (b ++ blks) getter c) >>= 
+                                    (\a -> loop a blks getter ps))
+        loop accum blks getter ((ParsedPiece (Include n) pos):ps) =
+            liftIO (tmplStore n) >>=
+            maybe (throwError (TemplateNotFound n pos)) 
+                  (\(Template c) -> (loop accum blks getter c) >>=
+                                    (\a -> loop a blks getter ps))
+        loop accum blks getter ((ParsedPiece (Decl n exp) pos):ps) = 
+            (reduceExpression renderConfig exp getter) >>=
+            (\v -> loop accum blks (combineBuckets (varBucket n (LitVal v)) getter) ps)
+
+        loop accum blks getter ((ParsedPiece (FuncPiece exp) pos):ps) = 
+            (reduceExpression renderConfig exp getter) >>= 
+            (\r -> loop (accum <> (renderOutput renderConfig r)) blks getter ps)      
+        
+        processFor getter varName content accum blks objs = loopFor accum objs
+          where loopGetter o = combineBuckets (varBucket varName (LitVal o)) getter
+                loopFor accum [] = return accum
+                loopFor accum (o:os) = do
+                      s <- loop accum blks (loopGetter o) content
+                      loopFor s os
diff --git a/src/Text/Strapped/Types.hs b/src/Text/Strapped/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Strapped/Types.hs
@@ -0,0 +1,83 @@
+{-# LANGUAGE ExistentialQuantification #-}
+module Text.Strapped.Types where
+
+import Blaze.ByteString.Builder
+import Control.Monad.Except
+import Data.List (intersperse)
+import qualified Data.Map as M
+import Data.Monoid (mconcat)
+import Data.Text.Lazy (Text)
+import Data.Typeable
+import Text.Parsec.Pos
+
+type Output = Builder
+
+instance Show Builder where
+  show = show . toByteString
+
+data Expression = 
+  LookupExpression String |
+  IntegerExpression Integer |
+  FloatExpression Double |
+  StringExpression String |
+  ListExpression [ParsedExpression] |
+  Multipart [ParsedExpression]
+
+instance Show Expression where
+  show (LookupExpression s) = s
+  show (IntegerExpression s) = show s
+  show (FloatExpression s) = show s
+  show (StringExpression s) = "\"" ++ s ++ "\""
+  show (ListExpression l) = "[" ++ (mconcat $ intersperse "," (map show l)) ++ "]"
+  show (Multipart l) = mconcat $ map show l
+
+data ParsedExpression = ParsedExpression Expression SourcePos
+
+instance Show ParsedExpression where
+  show (ParsedExpression exp _) = show exp
+
+data Piece = StaticPiece Output
+           | BlockPiece String [ParsedPiece]
+           | ForPiece String ParsedExpression [ParsedPiece]
+           | FuncPiece ParsedExpression
+           | Decl String ParsedExpression
+           | Include String
+           | Inherits String [(String, [ParsedPiece])]
+           deriving (Show)
+
+data ParsedPiece = ParsedPiece Piece SourcePos
+  deriving (Show)
+  
+class Renderable a where
+  renderOutput :: RenderConfig -> a -> Output
+  
+data Input m = forall a . (Renderable a) => RenderVal a
+             | List [Input m]
+             | Func  (Literal -> ExceptT StrapError m Literal)
+             | LitVal Literal
+
+data Literal = forall a . (Typeable a, Renderable a) => LitDyn a
+             | LitText Text
+             | LitSafe Text
+             | LitInteger Integer
+             | LitDouble Double
+             | LitBuilder Builder
+             | LitList ![Literal]
+             | LitEmpty
+
+data StrapError = StrapError String  SourcePos 
+                | InputNotFound String  SourcePos 
+                | TemplateNotFound String  SourcePos
+  deriving (Show, Eq)
+
+type InputBucket m = [M.Map String (Input m)]
+
+type TemplateStore = String -> IO (Maybe Template)
+
+data Template = Template [ParsedPiece]
+  deriving Show
+
+data RenderConfig = RenderConfig 
+  { templateStore :: TemplateStore
+  , escapeFunc :: Text -> Text
+  }
diff --git a/src/Text/Strapped/Utils.hs b/src/Text/Strapped/Utils.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Strapped/Utils.hs
@@ -0,0 +1,33 @@
+module Text.Strapped.Utils where
+
+import           Blaze.ByteString.Builder
+import           Blaze.ByteString.Builder.Char.Utf8
+import           Control.Monad
+import           Data.List hiding (find)
+import           Text.Strapped.Types
+import           Text.Strapped.Parser
+
+import           System.FilePath.Find
+import           System.FilePath.Posix (addTrailingPathSeparator)
+
+import           Text.ParserCombinators.Parsec
+
+templateStoreFromList :: [(String, String)] -> Either ParseError TemplateStore
+templateStoreFromList tmpls = do
+  templateTups <- forM tmpls (\(tn, t) -> fmap ((,) tn) $ parseTemplate t tn)
+  return (\n -> return $ lookup n templateTups)
+
+-- | Given a file path and extension, load all templates in a directory, recursively.
+templateStoreFromDirectory :: FilePath -> String -> IO (Either ParseError TemplateStore)
+templateStoreFromDirectory dir ext = do
+  files <- find always (extension ==? ext) dirPath
+  tmpls <- forM files (\fn -> let fname = maybe [] id $ stripPrefix dirPath fn 
+                              in print fname >> readFile fn >>= (return . (,) fname))
+  return $ templateStoreFromList tmpls
+  where dirPath = addTrailingPathSeparator dir
+
+putStore :: TemplateStore -> RenderConfig -> RenderConfig
+putStore ts rc = rc { templateStore = ts } 
+
+showToBuilder :: Show a => a -> Builder
+showToBuilder = fromShow
diff --git a/tests/StrappedTest.hs b/tests/StrappedTest.hs
new file mode 100644
--- /dev/null
+++ b/tests/StrappedTest.hs
@@ -0,0 +1,77 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+import Test.Hspec
+
+import qualified Blaze.ByteString.Builder as B
+import qualified Data.Text.Lazy as T
+import qualified Data.ByteString as BS
+import Text.Strapped
+import System.Exit
+
+data Custom = Custom
+  deriving (Show)
+
+instance Renderable Custom where
+  renderOutput _ c = showToBuilder c
+
+mainBucket :: InputBucket IO
+mainBucket = bucketFromList [
+          ("custom", RenderVal Custom),
+          ("is", List $ map (LitVal . LitInteger) [1..3]),
+          ("addNumbers", Func add)
+        ]
+  where add (LitList ((LitInteger a):(LitInteger b):[])) = return $ LitInteger $ a + b
+        add (LitList ((LitInteger a):(LitInteger b):bs)) = add $ LitList ((LitInteger (a + b)):bs)
+        add _ = return $ LitText $ T.pack $ "Only list of ints."
+
+renderShouldBe :: IO (Either StrapError Output) -> Either StrapError BS.ByteString -> Expectation
+renderShouldBe f c = f >>= (\e -> (fmap B.toByteString e) `shouldBe` c)
+
+spec :: RenderConfig -> Spec
+spec ts = do
+    describe "for loop" $
+      it "Should loop through stuff" $ do
+        (render ts mainBucket "for_loop") `renderShouldBe` (Right "1, 2, 3, ")
+
+    describe "RenderVal" $
+      it "class RenderVal" $ do
+        (render ts mainBucket "custom") `renderShouldBe` (Right "Custom")
+
+    describe "Comment" $
+      it "Check comments" $ do
+        (render ts mainBucket "comment") `renderShouldBe` (Right "")
+
+    describe "Raw" $
+      it "Check raw" $ do
+        (render ts mainBucket "raw") `renderShouldBe` (Right "${ custom }")
+
+    describe "Let" $
+      it "Check let" $ do
+        (render ts mainBucket "let") `renderShouldBe` (Right "Custom")
+
+    describe "Include" $
+      it "Check include" $ do
+        (render ts mainBucket "include") `renderShouldBe` (Right "Custom")
+
+    describe "Inherit" $
+      it "Check inherits" $ do
+        (render ts mainBucket "inherits") `renderShouldBe` (Right "Some base override")
+
+    describe "Function" $
+      it "Checks Function" $ 
+        (render ts mainBucket "func") `renderShouldBe` (Right "4")
+
+
+main = do
+    let ets = templateStoreFromList 
+            [ ("for_loop", "{$ for i in is $}${ i }, {$ endfor $}")
+            , ("custom", "${ custom }")
+            , ("comment", "{$ comment $}${ custom }{$ endcomment $}")
+            , ("raw", "{$ raw $}${ custom }{$ endraw $}")
+            , ("let", "{$ let i = custom $}\n${ i }")
+            , ("include", "{$ include custom $}")
+            , ("base", "Some base {$ block some_block $}blah blah {$ endblock $}")
+            , ("inherits", "{$ inherits base $}{$ isblock some_block $}override{$ endisblock $}")
+            , ("func", "${ addNumbers [(addNumbers [1, 1]), 2] }")
+            ]
+    either (\_ -> exitFailure) (\ts -> hspec $ spec (defaultConfig {templateStore = ts})) ets
