arbtt-0.10: src/import-main.hs
{-# LANGUAGE CPP #-}
module Main where
import System.Directory
import System.FilePath
import System.Console.GetOpt
import System.Environment
import System.Exit
import System.IO
import qualified Data.Map as M
import Data.Version (showVersion)
import Data.Maybe
import Control.Monad
import Control.Applicative
import Data.Conduit.Attoparsec
import Data.Conduit
import Data.Aeson (parseJSON, json)
import Data.Aeson.Types (parseEither)
import Data.Binary.StringRef
import Data.Attoparsec.ByteString.Char8 (skipSpace, option)
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Conduit.List as C
import qualified Data.Conduit.Binary as C
import TimeLog
import Data
import CommonStartup
import DumpFormat
import LockFile
import Paths_arbtt (version)
data Options = Options
{ optLogFile :: String
, optFormat :: DumpFormat
, optAppend :: Bool
}
defaultOptions dir = Options
{ optLogFile = dir </> "capture.log"
, optAppend = False
, optFormat = DFShow
}
versionStr = "arbtt-import " ++ showVersion version
header = "Usage: arbtt-import [OPTIONS...]"
options :: [OptDescr (Options -> IO Options)]
options =
[ Option "h?" ["help"]
(NoArg $ \_ -> do
putStr (usageInfo header options)
exitSuccess
)
"show this help"
, Option "V" ["version"]
(NoArg $ \_ -> do
putStrLn versionStr
exitSuccess
)
"show the version number"
, Option "f" ["logfile"]
(ReqArg (\arg opt -> return opt { optLogFile = arg }) "FILE")
"use this file instead of ~/.arbtt/capture.log"
, Option "a" ["append"]
(NoArg (\opt -> return opt { optAppend = True } ))
"append to the logfile, instead of overriding it"
, Option "t" ["format"]
(ReqArg (\arg opt ->
case readDumpFormat arg of
Just DFHuman -> do
hPutStrLn stderr "The “Human” format cannot imported."
hPutStr stderr (usageInfo header options)
exitFailure
Just fm -> return $ opt { optFormat = fm}
Nothing -> do
hPutStrLn stderr ("Invalid format \"" ++ arg ++ "\".")
hPutStr stderr (usageInfo header options)
exitFailure
) "FORMAT")
"output format, one of Show (default) or JSON "
]
parseConduit :: DumpFormat -> Conduit BS.ByteString IO [TimeLogEntry CaptureData]
parseConduit DFHuman = error "Cannot read back human format"
parseConduit DFShow = C.lines =$= C.map ( (:[]) . read . BS.unpack)
parseConduit DFJSON =
conduitParser (ignoreWhiteSpace json)
=$= C.map snd
=$= C.catMaybes
=$= tlConduit
where
tlConduit = C.mapM $ \ v ->
case parseEither oneOrMany v of
Left e -> do
hPutStrLn stderr ("Cannot parse log entry: " ++ e)
exitFailure
Right x -> pure x
oneOrMany v = parseJSON v
<|> ((:[]) <$> parseJSON v)
ignoreWhiteSpace p = skipSpace *> option Nothing (Just <$> p)
binaryConduit :: ListOfStringable a =>
Conduit [(TimeLogEntry a, Maybe a)] IO (Flush BS.ByteString)
binaryConduit = C.map (map go) =$= C.concatMap chunk
where
go (x,prev) = BSL.toStrict $ ls_encode strs x
where strs = maybe [] listOfStrings prev
chunk xs = map Chunk xs ++ [Flush]
main = do
commonStartup
args <- getArgs
actions <- case getOpt Permute options args of
(o,[],[]) -> return o
(_,_,errs) -> do
hPutStr stderr (concat errs ++ usageInfo header options)
exitFailure
dir <- getAppUserDataDirectory "arbtt"
flags <- foldl (>>=) (return (defaultOptions dir)) actions
lockFile (optLogFile flags)
ex <- doesFileExist (optLogFile flags)
when (ex && not (optAppend flags)) $ do
putStrLn $ "File at " ++ (optLogFile flags) ++ " does already exist. Please delete this"
putStrLn $ "file before running arbtt-import."
exitFailure
createTimeLog False (optLogFile flags)
h <- openBinaryFile (optLogFile flags) AppendMode
runConduit $ C.sourceHandle stdin
=$= parseConduit (optFormat flags)
=$= stutterList
=$= binaryConduit
=$= C.sinkHandleFlush h
hClose h
stutter :: Monad m => Conduit (TimeLogEntry a) m (TimeLogEntry a, Maybe a)
stutter =
loop Nothing
where
loop prev = do
mx <- await
case mx of
Nothing -> return ()
Just x -> do
yield (x, prev)
loop (Just (tlData x))
stutterList :: Monad m => Conduit [TimeLogEntry a] m [(TimeLogEntry a, Maybe a)]
stutterList =
loop Nothing
where
loop prev = do
mx <- await
case mx of
Nothing -> return ()
Just xs -> do
let xs' = zip xs (prev : map (Just . tlData) xs)
yield xs'
loop (Just (tlData (last xs)))