import Control.Applicative( (<$>) ) import Control.Monad( when ) import Data.DateTime( diffSeconds ) import Data.List.Split( splitOn ) import Data.List( zipWith4 ) import qualified Data.Map as Map import Data.Maybe( fromMaybe, fromJust ) import Data.Time.Clock( UTCTime(..) ) import Data.Time.Format( parseTime, formatTime ) import Data.Time.LocalTime( LocalTime(..), ZonedTime(..), TimeZone(..), zonedTimeToUTC, midnight, localDay ) import Diddo( DiddoEntry(..), DiddoLogline(..), DiddoParsed(..) ) import HMSTime( secondsToHMS ) import System.Console.GetOpt import System.Environment( getArgs ) import System.Exit( exitSuccess ) import System.IO( stderr, hPutStr, hPutStrLn ) import System.Locale data Opt = Verbose | Version | Help | InputFile String | OutputFile String | InputFormat String | OutputFormat String | StartDate String | EndDate String deriving (Show, Eq) options :: [OptDescr Opt] options = [ Option "v" ["verbose"] (NoArg Verbose) "More detailed output" , Option "V" ["version"] (NoArg Version) "Display program version" , Option "h" ["help"] (NoArg Help) "Display program help" , Option "f" ["file"] (ReqArg InputFile "FILE") "Read from FILE" , Option "w" ["output"] (ReqArg OutputFile "FILE") "Write to FILE" , Option "i" ["informat"] (ReqArg InputFormat "FORMAT") "Timeformat used in input" , Option "o" ["outformat"] (ReqArg OutputFormat "FORMAT") "Timeformat used in output" , Option "s" ["start"] (ReqArg StartDate "DATE") "Start of reporting period" , Option "e" ["end"] (ReqArg EndDate "DATE") "End of reporting period" ] parseToZonedTime :: String -> String -> ZonedTime parseToZonedTime format string = fromMaybe (error $ "Input data broken: " ++ string) $ parseTime defaultTimeLocale format string parseISOsecondsTime :: String -> ZonedTime parseISOsecondsTime = parseToZonedTime $ iso8601DateFormat $ Just "%T%z" zonedToUTCandTZ :: ZonedTime -> (UTCTime, TimeZone) zonedToUTCandTZ zt = (zonedTimeToUTC zt, zonedTimeZone zt) parseRFC822Time :: String -> ZonedTime parseRFC822Time = parseToZonedTime rfc822DateFormat formatZonedTime :: String -> ZonedTime -> String formatZonedTime = formatTime defaultTimeLocale utcTimesDeltas' :: [UTCTime] -> [Integer] utcTimesDeltas' [] = error "Function utcTimesDeltas' called with no argument" utcTimesDeltas' [_] = error "Function utcTimesDeltas' called with bougus argument" utcTimesDeltas' (x:y:[]) = [diffSeconds y x] utcTimesDeltas' (x:y:xs) = diffSeconds y x : utcTimesDeltas' (y:xs) utcTimesDeltas :: ZonedTime -> [UTCTime] -> [Integer] utcTimesDeltas startTime timestamps = let startTimeUTC = zonedTimeToUTC startTime relevantTimestamps = dropWhile (< startTimeUTC) timestamps in zipWith diffSeconds relevantTimestamps $ startTimeUTC : init relevantTimestamps startOfDay :: ZonedTime -> ZonedTime startOfDay time = ZonedTime (LocalTime day midnight) $ zonedTimeZone time where day = localDay $ zonedTimeToLocalTime time startOfMonth :: ZonedTime -> ZonedTime startOfMonth time = ZonedTime (LocalTime day midnight) $ zonedTimeZone time where day = localDay $ zonedTimeToLocalTime time parseToUTCFromZonedString :: String -> String -> UTCTime parseToUTCFromZonedString fmt time = zonedTimeToUTC $ parseToZonedTime fmt time linesFromFiles :: [String] -> IO [String] linesFromFiles filenames = lines . concat <$> mapM readFile filenames splitToMapOn :: String -> [String] -> Map.Map String [String] splitToMapOn sep loglines = Map.fromList $ map (listToTuple . splitOn sep) loglines where listToTuple (x:xs) = (x, xs) listToTuple [] = ("",[""]) logLinesToDiddohs :: String -> [String] -> [DiddoEntry] logLinesToDiddohs inDateFmt logLines = let loglineMap = splitToMapOn ";" logLines zonedtimeEntryMap = Map.mapKeysMonotonic (parseToZonedTime inDateFmt) loglineMap utctimeEntryMap = Map.mapKeys zonedTimeToUTC zonedtimeEntryMap timeStamps = Map.keys loglineMap entryTexts = map head $ Map.elems loglineMap parsedTimes = Map.keys zonedtimeEntryMap deltasHMS = map secondsToHMS $ utcTimesDeltas (startOfDay $ head parsedTimes) $ Map.keys utctimeEntryMap in zipWith4 DiddoEntry (formatZonedTime inDateFmt (startOfDay $ head parsedTimes) : init timeStamps) timeStamps deltasHMS entryTexts --parsedLinesToDiddohs :: String -> Map.Map UTCTime DiddoParsed -> [DiddoEntry] --parsedLinesToDiddohs inDateFmt parsedLines = -- Map.foldrWithKey (\t e ts -> ) parseDddLog :: String -> Map.Map UTCTime DiddoParsed parseDddLog line = Map.singleton timestamp $ DiddoParsed timestamp zt entry where (timestring:entry:_) = splitOn ";" line (timestamp,zt) = zonedToUTCandTZ $ parseISOsecondsTime timestring main :: IO () main = do argv <- getArgs case getOpt Permute options argv of (opts,args,[]) -> do when (Help `elem` opts) $ (putStrLn $ usageInfo "Usage: diddohs [OPTION...]" options) >> exitSuccess let logFileNames = [file | InputFile file <- opts] logLines <- linesFromFiles logFileNames let inDateFmt = head [fmt | InputFormat fmt <- opts] -- reportPeriod = ( head [time | StartDate time <- opts] -- , head [time | EndDate time <- opts] -- ) -- DEBUG mapM_ putStrLn args -- putStrLn $ show reportPeriod let loglineMap = Map.unions $ map parseDddLog logLines deltasHMS = map secondsToHMS $ utcTimesDeltas' $ fst (Map.findMin loglineMap) : Map.keys loglineMap -- loglineMapReduced = Map.filterWithKey (\t _ -> t > startDay) loglineMap -- diddoEntries = Map.foldrWithKey mapM_ print $ logLinesToDiddohs inDateFmt logLines -- mapM_ print deltasHMS (_,_,errs) -> do hPutStr stderr $ usageInfo header options ioError (userError ('\n' : concat errs)) where header = "Usage: diddohs [OPTION...]"