dawdle 0.1.0.0 → 0.1.0.1
raw patch · 8 files changed
+94/−74 lines, 8 filesdep ~dawdlePVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: dawdle
API changes (from Hackage documentation)
- Paths_dawdle: getBinDir :: IO FilePath
- Paths_dawdle: getDataDir :: IO FilePath
- Paths_dawdle: getDataFileName :: FilePath -> IO FilePath
- Paths_dawdle: getLibDir :: IO FilePath
- Paths_dawdle: getLibexecDir :: IO FilePath
- Paths_dawdle: getSysconfDir :: IO FilePath
- Paths_dawdle: version :: Version
+ Database.Dawdle.Options: [optVerifyIntegrity] :: Options -> Bool
+ Database.Dawdle.Options: defaultOptions :: Options
- Database.Dawdle.Dawdle: analyzeFile :: [[String]] -> Either String [CellType]
+ Database.Dawdle.Dawdle: analyzeFile :: Options -> [[String]] -> Either String [CellType]
- Database.Dawdle.Options: Options :: Bool -> Maybe FilePath -> Integer -> Bool -> Char -> Options
+ Database.Dawdle.Options: Options :: Bool -> Maybe FilePath -> Maybe Int -> Bool -> Char -> Bool -> Options
- Database.Dawdle.Options: [optStopAfter] :: Options -> Integer
+ Database.Dawdle.Options: [optStopAfter] :: Options -> Maybe Int
Files
- bin/main.lhs +30/−25
- dawdle.cabal +4/−6
- src/Database/Dawdle/Dawdle.lhs +29/−27
- src/Database/Dawdle/Options.lhs +17/−10
- src/Database/Dawdle/Parser.lhs +6/−1
- src/Database/Dawdle/PrettyPrint.lhs +4/−3
- src/Database/Dawdle/Types.lhs +1/−0
- src/Database/Dawdle/Utils.lhs +3/−2
bin/main.lhs view
@@ -1,49 +1,54 @@+ > {-# LANGUAGE ExplicitForAll,TupleSections, > NoMonomorphismRestriction,OverloadedStrings, -> FlexibleContexts, RankNTypes, ViewPatterns #-} +> FlexibleContexts, RankNTypes #-} -> import Database.Dawdle.Parser -> import Database.Dawdle.Dawdle -> import Database.Dawdle.Options -> import Database.Dawdle.Utils -> import Database.Dawdle.PrettyPrint +> import Database.Dawdle.Parser +> import Database.Dawdle.Dawdle +> import Database.Dawdle.Options +> import Database.Dawdle.Utils +> import Database.Dawdle.PrettyPrint > import qualified Data.Text.Lazy.IO as LT -> import qualified Data.Text.Lazy as LT -> import Text.Parsec.Text () +> import qualified Data.Text.Lazy as LT -> import Data.Maybe -> import System.Environment -> import System.FilePath - +> import Data.Maybe +> import System.Environment +> import System.FilePath +> import System.IO ( hPutStrLn, stdin, stderr ) > main :: IO () > main = do > a <- getArgs -> opts <- getOpts a -> let source = fromMaybe "stdin" $ (optInput . fst) opts -> sepChar = (optSepChar . fst) opts -> inputMode = (optInput . fst) opts -> withHeader = (optWithHeader . fst) opts +> opts <- fst <$> getOpts a +> let source = fromMaybe "stdin" $ optInput opts +> sepChar = optSepChar opts +> inputMode = optInput opts +> withHeader = optWithHeader opts +> stopAfter = optStopAfter opts > c <- case inputMode of > Just f -> LT.readFile f -> Nothing -> do -> LT.pack <$> getContents -> case parseCsv sepChar source c of +> Nothing -> LT.hGetContents stdin +> c' <- case stopAfter of +> Nothing -> +> hPutStrLn stderr "Warning: No limit was set. Entire file will be read" >> +> return c +> Just x -> return $ LT.unlines $ take x $ LT.lines c +> case parseCsv sepChar source c' of > Left e -> do > putStrLn "Error parsing input:" > print e > Right [] -> error "Empty parsing result. Empty CSV?" -> Right res@(x:xs) -> do +> Right res@(x:xs) -> > if withHeader > then if null xs > then error "No content after header. Empty CSV!" -> else -- Parser rest with pretty columns +> else -- Parse rest with pretty columns > let x' = normalizeNames x -> in putStrLn $ either error (pretty (genTbName source) x') $ analyzeFile xs -> else -- No header +> in putStrLn $ either error (pretty (genTbName source) x') $ analyzeFile opts xs +> else -- No header, parse entire file > let colNms = take (length x) [ "col_"++show i | i <- [1..] :: [Int] ] -> in putStrLn $ either error (pretty (genTbName source) colNms) $ analyzeFile res +> in putStrLn $ either error (pretty (genTbName source) colNms) $ analyzeFile opts res > where > genTbName = takeBaseName
dawdle.cabal view
@@ -2,9 +2,9 @@ -- documentation, see http://haskell.org/cabal/users-guide/ name: dawdle -version: 0.1.0.0 +version: 0.1.0.1 synopsis: Generates DDL suggestions based on a CSV file --- description: +description: Generates DDL suggestions based on a CSV file homepage: https://github.com/arnons1/dawdle license: MIT license-file: LICENSE @@ -36,12 +36,10 @@ Database.Dawdle.PrettyPrint, Database.Dawdle.Types, Database.Dawdle.Options, - Database.Dawdle.Utils, - Paths_dawdle + Database.Dawdle.Utils ghc-options: -Wall -O3 executable dawdle main-is: main.lhs - other-modules: Paths_dawdle hs-source-dirs: bin default-language: Haskell2010 build-depends: base >=4.8 && <4.9, @@ -49,5 +47,5 @@ pretty >= 1.0 && < 1.2, text >= 0.11.1.13 && < 1.3, filepath >= 1.4.0.0, - dawdle == 0.1.0.0 + dawdle == 0.1.0.1 ghc-options: -Wall -O3
src/Database/Dawdle/Dawdle.lhs view
@@ -1,31 +1,34 @@+ > {-# LANGUAGE ExplicitForAll,TupleSections, > NoMonomorphismRestriction,OverloadedStrings, -> FlexibleContexts, RankNTypes, ViewPatterns #-} +> FlexibleContexts, RankNTypes #-} > module Database.Dawdle.Dawdle > (analyzeFile) > where -> import Database.Dawdle.Types -> import Database.Dawdle.Utils +> import Database.Dawdle.Options +> import Database.Dawdle.Types +> import Database.Dawdle.Utils -> import Data.Char -> import Data.Int -> import Data.List -> import Data.Time.Format -> import Data.Time.Calendar -> import Text.Read (readMaybe) -> import Control.Monad +> import Control.Monad +> import Data.Char +> import Data.Int +> import Data.List +> import Data.Time.Format +> import Data.Time.Calendar +> import Text.Read (readMaybe) -> analyzeFile :: [[String]] -> Either String [CellType] -> analyzeFile mtrx = do +> analyzeFile :: Options -> [[String]] -> Either String [CellType] +> analyzeFile o mtrx = do > let cols = transpose mtrx > -- sanity on structure -> unless (allTheSame (map length cols)) $ Left $ "Invalid CSV: Some rows have a different number of columns.\nPerhaps your separator is wrong?" +> when (optVerifyIntegrity o) $ unless (allTheSame (map length cols)) +> $ Left "Invalid CSV: Some rows have a different number of columns.\nPerhaps your separator is wrong?" > mapM workOnAColumn cols > workOnAColumn :: [String] -> Either String CellType > workOnAColumn [] = Left "Empty column, no cell types inferred" -> workOnAColumn cts = do +> workOnAColumn cts = > foldM analyzeCell Unknown cts > analyzeCell :: CellType -> String -> Either String CellType @@ -33,17 +36,17 @@ > | null s || isNullText s = Right $ composeMaxTypesWithNulls mp $ Nullable Unknown > | b <- map toLower s > , b `elem` ["false","true"] -> , (removeNull mp) `elem` [CTBool,Unknown] = +> , removeNull mp `elem` [CTBool,Unknown] = > Right $ composeMaxTypesWithNulls mp CTBool -> | (isDateOrUnknown $ removeNull mp) +> | isDateOrUnknown $ removeNull mp > , Just fFmt <- msum -> [ ptm fmt s | fmt <- (iso8601DateFormat Nothing) : -> (if length s == 8 then ["%Y%m%d"] else [])] = +> [ ptm fmt s | fmt <- iso8601DateFormat Nothing : +> ["%Y%m%d" | length s == 8] ] = > case getDateFormatFromCT mp of > Just oFmt | > oFmt /= fFmt -> return $ composeMaxTypesWithNulls mp $ CTChar (length s) > _ -> return $ composeMaxTypesWithNulls mp $ CTDate fFmt -> | (isDateTimeOrUnknown $ removeNull mp) +> | isDateTimeOrUnknown $ removeNull mp > , Just fFmt <- msum > [ ptm fmt s | fmt <- [iso8601DateFormat $ Just "%H:%M:%S" > ,"%Y-%m-%d %H:%M:%S%Q" @@ -54,22 +57,21 @@ > oFmt /= fFmt -> return $ composeMaxTypesWithNulls mp $ CTChar (length s) > _ -> return $ composeMaxTypesWithNulls mp $ CTDateTime fFmt > | isInt s -> , (removeNull mp) < CTDateTime "" = do -> s' <- maybeToEither ("Can't read integer as Integer (??)") (readMaybe s :: Maybe Integer) +> , removeNull mp < CTDateTime "" +> , Right s' <- maybeToEither "Can't read integer as Integer (??)" (readMaybe s :: Maybe Integer) = > composeMaxTypesWithNulls mp <$> intType' s' > | (not . isInt) s && isCTNumber s -> , (removeNull mp) < CTChar 1 = do -> s' <- maybeToEither ("Can't read float type as Double (??)") (readMaybe s :: Maybe Double) +> , removeNull mp < CTChar 1 +> , Right s' <- maybeToEither "Can't read float type as Double (??)" (readMaybe s :: Maybe Double) = > composeMaxTypesWithNulls mp <$> floatType s' > | otherwise = Right $ composeMaxTypesWithNulls mp $ CTChar (length s) - +> {-# INLINE analyzeCell #-} > isNullText :: String -> Bool > isNullText s = (map toLower s == "null") || (s == "\\N") > ptm :: String -> String -> Maybe String -> ptm tf ts = maybe Nothing (const $ Just tf) ((parseTimeM True defaultTimeLocale tf ts) :: Maybe Day) - +> ptm tf ts = maybe Nothing (const $ Just tf) (parseTimeM True defaultTimeLocale tf ts :: Maybe Day) > intType' :: Integer -> Either String CellType > intType' x @@ -82,6 +84,6 @@ > floatType :: Double -> Either String CellType > floatType x = > let x' = realToFrac x :: Float -> in if (show x') /= (show x) +> in if show x' /= show x > then Right CTDouble > else Right CTFloat
src/Database/Dawdle/Options.lhs view
@@ -1,27 +1,31 @@+ + > module Database.Dawdle.Options > (Options(..) -> ,getOpts) +> ,getOpts +> ,defaultOptions) > where > -> import System.Console.GetOpt -> import Data.Maybe ( fromMaybe ) -> import Text.Read ( readMaybe ) +> import System.Console.GetOpt +> import Text.Read ( readMaybe ) > > data Options = Options > { optVerbose :: Bool > , optInput :: Maybe FilePath -> , optStopAfter :: Integer +> , optStopAfter :: Maybe Int > , optWithHeader :: Bool > , optSepChar :: Char +> , optVerifyIntegrity :: Bool > } deriving Show > defaultOptions :: Options > defaultOptions = Options > { optVerbose = False > , optInput = Nothing -> , optStopAfter = 10000 +> , optStopAfter = Just 10000 > , optWithHeader = False > , optSepChar = ',' +> , optVerifyIntegrity = True > } > > options :: [OptDescr (Options -> Options)] @@ -30,17 +34,20 @@ > (NoArg (\ opts -> opts { optVerbose = True })) > "chatty output on stderr" > ,Option ['f'] ["input"] -> (OptArg ((\ f opts -> opts { optInput = Just f }) . fromMaybe "input") -> "FILE") +> (ReqArg (\ f opts -> opts { optInput = Just f }) "FILE") > "input FILE" > ,Option ['t'] ["threshold"] -> (ReqArg (\ d opts -> opts { optStopAfter = (fromMaybe (1000 :: Integer) (readMaybe d :: Maybe Integer))}) "INTEGER") +> (ReqArg (\ d opts -> opts { optStopAfter = readMaybe d :: Maybe Int}) "INT") > "stop after how many lines" > ,Option ['h'] ["with-header"] > (NoArg (\ opts -> opts { optWithHeader = True })) > "is there a header line" > ,Option ['s'] ["separator"] -> (ReqArg (\d opts -> opts { optSepChar = headErr' d }) "CHAR") "Separator char" +> (ReqArg (\d opts -> opts { optSepChar = headErr' d }) "CHAR") +> "Separator char" +> ,Option [] ["skip-verify-integrity"] +> (NoArg (\ opts -> opts { optVerifyIntegrity = False })) +> "Skip integrity verification of CSV file - all lines have the same number of columns" > ] > headErr' :: [a] -> a
src/Database/Dawdle/Parser.lhs view
@@ -1,3 +1,4 @@+ > {-# LANGUAGE ExplicitForAll,TupleSections, > NoMonomorphismRestriction,OverloadedStrings, > FlexibleContexts, RankNTypes #-} @@ -24,13 +25,15 @@ > line sepChar = sepBy (cell sepChar) (char sepChar) > cell :: Char -> SParser String > cell sepChar = quotedCell <|> many (noneOf (sepChar:newLines)) +> {-# INLINE cell #-} > quotedCell :: SParser String > quotedCell = do -> sChar <- (oneOf quoteChars) +> sChar <- oneOf quoteChars > content <- many (fmap return nonEscape <|> escape) --(quotedChar sChar) > _ <- char sChar <?> "quote at end of cell" > return $ concat content +> {-# INLINE quotedCell #-} quotedChar :: Char -> SParser Char quotedChar csep = @@ -39,12 +42,14 @@ > nonEscape :: SParser Char > nonEscape = noneOf "\\\"\0\n\r\v\t\b\f" +> {-# INLINE nonEscape #-} > escape :: SParser String > escape = do > d <- char '\\' > c <- oneOf "\\\"0nrvtbf" > return [d,c] +> {-# INLINE escape #-} > eol :: SParser String > eol = try (string newLines)
src/Database/Dawdle/PrettyPrint.lhs view
@@ -1,3 +1,4 @@+ > {-# LANGUAGE ExplicitForAll,LambdaCase, > NoMonomorphismRestriction,OverloadedStrings, > FlexibleContexts, RankNTypes #-} @@ -8,12 +9,12 @@ > import Text.PrettyPrint > pretty :: String -> [String] -> [CellType] -> String -> pretty fn colns = render . (ddl fn colns) +> pretty fn colns = render . ddl fn colns > ddl :: String -> [String] -> [CellType] -> Doc > ddl fn colns cts = text "create table" <+> text fn <+> body > where -> body = parens $ nest 3 $ vcat $ (punctuate comma clnms) +> body = parens $ nest 3 $ vcat $ punctuate comma clnms > clnms = zipWith (\a b -> text a <+> b) colns $ map (prettyTypes True) cts > prettyTypes :: Bool -> CellType -> Doc @@ -28,7 +29,7 @@ > CTDouble -> intp $ text "float" > CTDate _ -> intp $ text "date" > CTDateTime _ -> intp $ text "datetime" -> CTChar i -> intp $ text "varchar" <> (parens $ int i) +> CTChar i -> intp $ text "varchar" <> (parens (int i)) > Unknown -> intp $ text "!!UNKNOWN!!" > where > intp tn = if isNullPass
src/Database/Dawdle/Types.lhs view
@@ -1,3 +1,4 @@+ > {-# LANGUAGE FlexibleContexts, DeriveDataTypeable, RankNTypes, KindSignatures #-} > module Database.Dawdle.Types
src/Database/Dawdle/Utils.lhs view
@@ -1,3 +1,4 @@+ > module Database.Dawdle.Utils > (getFstIfJust > ,isCTNumber @@ -25,9 +26,9 @@ > maybeToEither s Nothing = Left s > allTheSame :: (Eq a) => [a] -> Bool -> allTheSame xs = and $ map (== head xs) (tail xs) +> allTheSame xs = all (== head xs) (tail xs) > normalizeNames :: [String] -> [String] > normalizeNames = map normalize > where -> normalize = map toLower . (filter (\x -> x=='_' || isAlphaNum x)) . intercalate "_" . words +> normalize = map toLower . filter (\x -> x=='_' || isAlphaNum x) . intercalate "_" . words