diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2014 Daniel Choi
+
+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/Main.hs b/Main.hs
new file mode 100644
--- /dev/null
+++ b/Main.hs
@@ -0,0 +1,98 @@
+{-# LANGUAGE OverloadedStrings, ScopedTypeVariables#-}
+module Main where
+import Data.Monoid
+import qualified Data.Map.Strict as M
+import Data.Text (Text)
+import qualified Data.Text.Encoding as T (decodeUtf8)
+import Data.List (intersperse)
+import qualified Data.List 
+import qualified Data.Text as T
+import qualified Data.Text.IO as T
+import Data.Maybe (catMaybes)
+import Control.Applicative
+import Control.Monad (when)
+import Data.Attoparsec.Text 
+import System.Environment (getArgs)
+import qualified Options.Applicative as O
+
+data Options = Options {  
+      template :: Template
+    } deriving Show
+
+data Template = TemplateFile FilePath | TemplateText Text deriving (Show)
+
+parseOpts :: O.Parser Options
+parseOpts = Options <$> (tmplText <|> tmplFile)
+
+tmplText = TemplateText . T.pack <$> O.argument O.str (O.metavar "TEMPLATE")
+tmplFile = TemplateFile 
+      <$> O.strOption (O.metavar "FILE" <> O.short 'f' <> O.help "Template file")
+
+opts = O.info (O.helper <*> parseOpts)
+          (O.fullDesc 
+          <> O.progDesc "Inject TSV into SQL template strings" 
+          <> O.header "tsvsql 0.1.1.0")
+
+main = do
+  Options tmpl <- O.execParser opts
+  template <- case tmpl of
+                  TemplateFile fp -> T.readFile fp
+                  TemplateText t -> return t
+  xs :: [[Text]] <- fmap (map (T.splitOn "\t") . T.lines) T.getContents 
+  let chunks :: [TemplateChunk] 
+      chunks = parseText template
+      results = map (evalText chunks) xs
+  mapM_ T.putStrLn results
+
+data ValType = String | Bool | Number deriving (Eq, Show)
+
+data TemplateChunk = Pass Text 
+    | Placeholder Int ValType 
+    deriving (Show, Eq)
+
+evalText :: [TemplateChunk] -> [Text] -> Text
+evalText xs vals = mconcat $ map (evalChunk vals) xs
+
+evalChunk :: [Text] -> TemplateChunk -> Text
+evalChunk vs (Pass s) = s
+evalChunk vs (Placeholder idx _) | (vs !! idx) == "null" = "NULL"
+evalChunk vs (Placeholder idx String) = wrapQuote (vs !! idx)
+evalChunk vs (Placeholder idx Number) | (vs !! idx) == "" = "NULL"
+                                      | otherwise         = (vs !! idx)
+evalChunk vs (Placeholder idx Bool) | (vs !! idx) == "t" = "true"
+evalChunk vs (Placeholder idx Bool) | (vs !! idx) == "f" = "false"
+
+wrapQuote x = T.singleton '\'' <> (escapeText x) <> T.singleton '\''
+
+escapeText = T.pack . escapeStringLiteral . T.unpack 
+
+escapeStringLiteral :: String -> String
+escapeStringLiteral ('\'':xs) = '\'': ('\'' : escapeStringLiteral xs)
+escapeStringLiteral (x:xs) = x : escapeStringLiteral xs
+escapeStringLiteral [] = []
+
+parseText :: Text -> [TemplateChunk]
+parseText = either error id . parseOnly (many textChunk)
+
+textChunk = placeholderChunk <|> passChunk
+
+placeholderChunk :: Parser TemplateChunk
+placeholderChunk = do
+    try (char '$')
+    idx <- decimal
+    type' <- pType
+    return $ Placeholder (idx - 1) type'
+
+pType :: Parser ValType
+pType = 
+  (do
+    try (char ':') 
+    (Bool <$ string "bool") <|> (Number <$ string "num"))
+  <|> pure String
+  
+passChunk :: Parser TemplateChunk
+passChunk = Pass <$> takeWhile1 (notInClass "$")
+
+
+------------------------------------------------------------------------
+
diff --git a/README b/README
new file mode 100644
--- /dev/null
+++ b/README
@@ -0,0 +1,35 @@
+tsvsql
+
+Templates TSV values into a SQL template
+
+input.tsv:
+apple 1
+banana 2
+
+tsvsql $ < input.tsv dist/build/tsvsql/tsvsql 'INSERT into fruits (name, price) VALUES ($1, $2:num);'
+INSERT into fruits (name, price) VALUES ('apple', 1);
+INSERT into fruits (name, price) VALUES ('banana', 2);
+
+
+WARNING: Use single quotes around the SQL template expression so that
+Bash does not do interpolation.
+
+'null' text is translated into NULL:
+
+input2.tsv
+apple	1
+banana	null
+
+tsvsql $ < input2.tsv dist/build/tsvsql/tsvsql 'INSERT into fruits (name, price) VALUES ($1, $2:num);'
+INSERT into fruits (name, price) VALUES ('apple', 1);
+INSERT into fruits (name, price) VALUES ('banana', NULL);
+
+
+The subtitution placeholders are like this:
+
+    $1      # value is a string; the value is quoted and escaped
+    $2:num  # value is a number; not quoted
+    $3:bool # value is a bool; "t" is true, "f" is false
+
+The placeholders start counting the first TSV value from position 1, not
+zero.
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/input.tsv b/input.tsv
new file mode 100644
--- /dev/null
+++ b/input.tsv
@@ -0,0 +1,2 @@
+apple	1
+banana	2
diff --git a/input2.tsv b/input2.tsv
new file mode 100644
--- /dev/null
+++ b/input2.tsv
@@ -0,0 +1,2 @@
+apple	1
+banana	null
diff --git a/tsvsql.cabal b/tsvsql.cabal
new file mode 100644
--- /dev/null
+++ b/tsvsql.cabal
@@ -0,0 +1,31 @@
+-- Initial tsvsql.cabal generated by cabal init.  For further 
+-- documentation, see http://haskell.org/cabal/users-guide/
+
+name:                tsvsql
+version:             0.1.1.0
+synopsis:            Template tsv into SQL
+-- description:         
+homepage:            https://github.com/danchoi/tsvsql
+license:             MIT
+license-file:        LICENSE
+author:              Daniel Choi
+maintainer:          dhchoi@gmail.com
+-- copyright:           
+category:            Text
+build-type:          Simple
+-- extra-source-files:  
+cabal-version:       >=1.10
+
+executable tsvsql
+  main-is:             Main.hs
+  -- other-modules:       
+  -- other-extensions:    
+  build-depends:       base >=4.6 && <4.8
+                     , unordered-containers 
+                     , containers 
+                     , text >= 1.1.0.0
+                     , bytestring 
+                     , optparse-applicative
+                     , attoparsec
+  -- hs-source-dirs:      
+  default-language:    Haskell2010
