diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2013 Simon Hengel <sol@typeful.net>
+
+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/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,3 @@
+#!/usr/bin/env runhaskell
+> import Distribution.Simple
+> main = defaultMain
diff --git a/driver/Main.hs b/driver/Main.hs
new file mode 100644
--- /dev/null
+++ b/driver/Main.hs
@@ -0,0 +1,8 @@
+module Main (main) where
+
+import qualified Data.Text.IO as T
+
+import Text.Inject
+
+main :: IO ()
+main = T.getContents >>= inject >>= T.putStr
diff --git a/inject.cabal b/inject.cabal
new file mode 100644
--- /dev/null
+++ b/inject.cabal
@@ -0,0 +1,64 @@
+name:             inject
+version:          0.1.0
+license:          MIT
+license-file:     LICENSE
+copyright:        (c) 2013 Simon Hengel
+author:           Simon Hengel <sol@typeful.net>
+maintainer:       Simon Hengel <sol@typeful.net>
+build-type:       Simple
+cabal-version:    >= 1.8
+category:         Text
+synopsis:         A minimalistic template engine
+description:      Inject expands shell commands in arbitrary text templates.
+                  .
+                  <https://github.com/sol/inject#readme>
+
+source-repository head
+  type: git
+  location: https://github.com/sol/inject
+
+library
+  ghc-options:
+      -Wall
+  hs-source-dirs:
+      src
+  exposed-modules:
+      Text.Inject
+  other-modules:
+      Util
+  build-depends:
+      base    == 4.*
+    , text
+    , process
+    , attoparsec
+
+executable inject
+  ghc-options:
+      -Wall
+  hs-source-dirs:
+      driver
+  main-is:
+      Main.hs
+  build-depends:
+      base    == 4.*
+    , text
+    , inject
+
+test-suite spec
+  type:
+      exitcode-stdio-1.0
+  ghc-options:
+      -Wall -Werror
+  cpp-options:
+      -DTEST
+  hs-source-dirs:
+      src, test
+  main-is:
+      Spec.hs
+  build-depends:
+      base    == 4.*
+    , text
+    , process
+    , attoparsec
+    , hspec >= 1.3
+    , hspec-expectations
diff --git a/src/Text/Inject.hs b/src/Text/Inject.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Inject.hs
@@ -0,0 +1,50 @@
+{-# LANGUAGE OverloadedStrings, CPP #-}
+module Text.Inject (
+  inject
+#ifdef TEST
+, Element (..)
+, pBraces
+#endif
+) where
+
+import           Prelude hiding (takeWhile)
+import           Control.Applicative
+import           Data.Text (Text)
+import qualified Data.Text as T
+import           Data.Attoparsec.Text
+import           Util
+
+type Template = [Element]
+
+data Element = Plain Text | Braces Text
+  deriving (Eq, Show)
+
+inject :: Text -> IO Text
+inject = fmap T.concat . mapM f . parseTemplate
+  where
+    f :: Element -> IO Text
+    f element = case element of
+      Plain text -> return text
+      Braces cmd -> system (T.unpack cmd)
+
+parseTemplate :: Text -> Template
+parseTemplate = either parseError id . parseOnly (pTemplate <* endOfInput)
+  where
+    parseError = (error . unwords) [
+        "Parsing failed."
+      , "This should never happen"
+      , "Please report a bug!"
+      ]
+
+pTemplate :: Parser Template
+pTemplate = many (pBraces <|> (Plain <$> pText))
+
+pText :: Parser Text
+pText = T.cons <$> anyChar <*> takeWhile (/= '{')
+
+pBraces :: Parser Element
+pBraces = Braces . T.init <$> ("{{" *> scan 0 f <* "}")
+  where
+    f 0 '}' = Just 1
+    f 1 '}' = Nothing
+    f _ _   = Just (0 :: Int)
diff --git a/src/Util.hs b/src/Util.hs
new file mode 100644
--- /dev/null
+++ b/src/Util.hs
@@ -0,0 +1,23 @@
+module Util where
+
+import qualified Control.Exception as E
+import           System.Process
+import           System.IO (hClose)
+import           System.Exit
+import           Data.Text
+import           Data.Text.IO
+
+
+system :: String -> IO Text
+system cmd = E.mask $ \restore -> do
+  (Nothing, Just h, Nothing, pid) <- createProcess (shell cmd) {std_out = CreatePipe}
+  restore $ do
+    output <- hGetContents h
+    waitForProcess pid >>= \r -> case r of
+      ExitSuccess -> return output
+      ExitFailure c ->
+        (E.throwIO . userError) ("system: " ++ cmd ++ " (exit " ++ show c ++ ")")
+    `E.onException` do
+      hClose h
+      terminateProcess pid
+      waitForProcess pid
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 #-}
