1 import Control.Applicative( (<$>), (<*>) )
2 import Data.DateTime( diffSeconds )
3 import Data.List.Split( splitOn )
4 import Data.List( zipWith4, transpose )
5 import qualified Data.Map as Map
6 import Data.Maybe( fromJust, fromMaybe )
7 import Data.Time.Clock( UTCTime(..), secondsToDiffTime )
8 import Data.Time.Format( parseTime, formatTime )
9 import Data.Time.LocalTime( LocalTime(..), ZonedTime(..), zonedTimeToUTC, midnight, localDay )
10 import Diddo.Entry( DiddoEntry(..) )
11 import Diddo.Log( DiddoLogline(..) )
12 import HMSTime( HMSTime(..), secondsToHMS )
13 import System.Console.GetOpt
14 import System.Environment( getArgs )
16 import System.IO( stderr, hPutStr, hPutStrLn )
22 | InputFile String | OutputFile String
23 | InputFormat String | OutputFormat String
26 options :: [OptDescr Flag]
28 [ Option "v" ["verbose"] (NoArg Verbose) "More detailed output"
29 , Option "V" ["version"] (NoArg Version) "Display program version"
30 , Option "h" ["help"] (NoArg Help) "Display program version"
31 , Option "f" ["file"] (ReqArg InputFile "FILE") "Read from FILE"
32 , Option "w" ["output"] (ReqArg OutputFile "FILE") "Write to FILE"
33 , Option "i" ["informat"] (ReqArg InputFormat "FORMAT") "Parse dates in the given FORMAT"
34 , Option "o" ["outformat"] (ReqArg OutputFormat "FORMAT") "Output dates in the given FORMAT"
37 parseToZonedTime :: String -> String -> ZonedTime
38 parseToZonedTime format = fromMaybe (error "Input data broken.") . parseTime defaultTimeLocale format
40 parseISOsecondsTime :: String -> ZonedTime
41 parseISOsecondsTime = parseToZonedTime $ iso8601DateFormat $ Just "%T%z"
43 parseRFC822Time :: String -> ZonedTime
44 parseRFC822Time = parseToZonedTime rfc822DateFormat
46 formatZonedTime :: String -> ZonedTime -> String
47 formatZonedTime = formatTime defaultTimeLocale
49 utcTimesDeltas :: ZonedTime -> [UTCTime] -> [Integer]
50 utcTimesDeltas startTime timestamps =
52 startTimeUTC = zonedTimeToUTC startTime
53 relevantTimestamps = dropWhile (< startTimeUTC) timestamps
55 zipWith diffSeconds relevantTimestamps $ startTimeUTC : init relevantTimestamps
57 startOfDay :: ZonedTime -> ZonedTime
58 startOfDay time = ZonedTime (LocalTime day midnight) $ zonedTimeZone time
60 day = localDay $ zonedTimeToLocalTime time
62 parseToUTCFromZonedString :: String -> String -> UTCTime
63 parseToUTCFromZonedString fmt time = zonedTimeToUTC $ parseToZonedTime fmt time
65 splitToMapOn :: String -> [String] -> Map.Map String [String]
66 splitToMapOn sep lines = Map.fromList $ map (listToTuple . splitOn sep) lines
67 where listToTuple (x:xs) = (x, xs)
69 logLinesToDiddohs :: String -> [String] -> [DiddoEntry]
70 logLinesToDiddohs inDateFmt logLines =
72 loglineMap = splitToMapOn ";" logLines
73 zonedtimeEntryMap = Map.mapKeysMonotonic (parseToZonedTime inDateFmt) loglineMap
74 utctimeEntryMap = Map.mapKeys zonedTimeToUTC zonedtimeEntryMap
76 timeStamps = Map.keys loglineMap
77 entryTexts = map head $ Map.elems loglineMap
78 parsedTimes = Map.keys zonedtimeEntryMap
80 deltasHMS = map secondsToHMS $ utcTimesDeltas (startOfDay $ head parsedTimes) $ Map.keys utctimeEntryMap
82 zipWith4 DiddoEntry (formatZonedTime inDateFmt (startOfDay $ head parsedTimes) : init timeStamps)
83 timeStamps deltasHMS entryTexts
85 readFilesToLines :: [String] -> IO [String]
86 readFilesToLines filenames = lines . concat <$> mapM readFile filenames
92 case getOpt Permute options argv of
95 logFileNames = [file | InputFile file <- opts]
96 inDateFmt = head [fmt | InputFormat fmt <- opts]
98 logLines <- readFilesToLines logFileNames
100 mapM_ print $ logLinesToDiddohs inDateFmt logLines
103 hPutStr stderr $ usageInfo header options
104 ioError (userError ('\n' : concat errs))
105 where header = "Usage: diddohs [OPTION...]"