packages feed

tsvsql-0.1.1.0: Main.hs

{-# 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 "$")


------------------------------------------------------------------------