diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2015 Geraud Boyer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,36 @@
+# Templater
+[![Build Status](https://travis-ci.org/geraud/templater.svg?branch=master)](https://travis-ci.org/geraud/templater)
+
+Simple string templater
+
+## Installation
+
+```bash
+cabal update
+cabal install templater
+```
+
+## Example
+
+```haskell
+{-# LANGUAGE OverloadedStrings #-}
+import           Data.Text
+import qualified Data.Text.IO   as TIO
+import           Text.Templater
+
+main :: IO ()
+main = do
+  let textTemplate = "Hello, %{what is it ?}!"
+      res = template textTemplate context
+  case res of
+    Left error -> do
+        putStrLn $ "Got Error:" ++ error
+    Right result -> TIO.putStrLn result
+
+context :: Context -- Context is a type alias for Text -> Maybe Text
+context "what is it ?" = Just "world"
+context _ = Nothing
+```
+renders to `Hello, world!`
+
+Well that's all there is to it!...
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/src/Text/Templater.hs b/src/Text/Templater.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Templater.hs
@@ -0,0 +1,52 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Text.Templater where
+
+import           Control.Applicative
+import           Data.Attoparsec.Text
+import           Data.Either          (partitionEithers)
+import           Data.List            (intercalate)
+import           Data.Monoid          ((<>))
+import           Data.Text            (Text)
+import qualified Data.Text            as T
+import           Prelude              hiding (takeWhile)
+
+data TemplateItem
+    = Literal Text
+    | Variable Text
+    deriving (Show, Eq)
+
+type Context = Text -> Maybe Text
+
+template :: Text -> Context -> Either String Text
+template "" _  = Right ""
+template st ctx = case parseOnly templateP st of
+                  Right items -> expandItems ctx items
+                  Left e -> Left e
+
+expandItems :: Context -> [TemplateItem] -> Either String Text
+expandItems context items =
+    let (lefts, rights) = partitionEithers $ expandItem context <$> items
+    in if null lefts
+       then Right $ T.concat rights
+       else Left $ intercalate ", " lefts
+
+expandItem :: Context -> TemplateItem -> Either String Text
+expandItem ctx (Literal v) = Right v
+expandItem ctx (Variable name) =
+    case ctx name of
+    Nothing -> Left $ "key '" <> T.unpack name <> "' not found in context"
+    Just v  -> Right v
+
+templateP :: Parser  [TemplateItem]
+templateP = many1 templateItemP <* endOfInput
+
+templateItemP :: Parser TemplateItem
+templateItemP = choice [escapedOrVariableP, literalItemP]
+
+literalItemP :: Parser TemplateItem
+literalItemP = Literal <$> takeWhile1 (/= '%')
+
+escapedOrVariableP :: Parser TemplateItem
+escapedOrVariableP = char '%' *> choice [variableItemP, escapedPercentP]
+    where variableItemP   = Variable <$> ("{" *> takeWhile (/= '}') <* "}")
+          escapedPercentP = Literal <$> "%"
diff --git a/templater.cabal b/templater.cabal
new file mode 100644
--- /dev/null
+++ b/templater.cabal
@@ -0,0 +1,50 @@
+name:                templater
+version:             0.0.2.0
+synopsis:            Simple string templater
+-- description:
+homepage:            https://github.com/geraud/templater
+license:             MIT
+license-file:        LICENSE
+author:              Geraud Boyer
+maintainer:          geraud@gmail.com
+-- copyright:
+category:            Text
+build-type:          Simple
+extra-source-files:  README.md
+cabal-version:       >=1.10
+tested-with:
+    GHC == 7.6
+  , GHC == 7.8
+  , GHC == 7.10
+
+library
+  build-depends:
+    base >=4.5 && <4.9
+    , text >= 1.1
+    , attoparsec >= 0.12
+  hs-source-dirs:
+    src
+  default-language:
+    Haskell2010
+  exposed-modules:
+    Text.Templater
+
+test-suite tests
+  main-is: Spec.hs
+  build-depends:
+    base
+    , templater
+    , hspec == 2.*
+    , hspec-attoparsec >= 0.1.0
+    , HUnit
+    , QuickCheck
+    , text
+  default-language:
+    Haskell2010
+  ghc-options:
+    -Wall
+    -Werror
+  hs-source-dirs:
+    test
+  type:
+    exitcode-stdio-1.0
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
