tsvsql (empty) → 0.1.1.0
raw patch · 7 files changed
+190/−0 lines, 7 filesdep +attoparsecdep +basedep +bytestringsetup-changed
Dependencies added: attoparsec, base, bytestring, containers, optparse-applicative, text, unordered-containers
Files
- LICENSE +20/−0
- Main.hs +98/−0
- README +35/−0
- Setup.hs +2/−0
- input.tsv +2/−0
- input2.tsv +2/−0
- tsvsql.cabal +31/−0
+ LICENSE view
@@ -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.
+ Main.hs view
@@ -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 "$")+++------------------------------------------------------------------------+
+ README view
@@ -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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ input.tsv view
@@ -0,0 +1,2 @@+apple 1+banana 2
+ input2.tsv view
@@ -0,0 +1,2 @@+apple 1+banana null
+ tsvsql.cabal view
@@ -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