clogparse (empty) → 0.1
raw patch · 5 files changed
+248/−0 lines, 5 filesdep +attoparsecdep +basedep +bytestringsetup-changed
Dependencies added: attoparsec, base, bytestring, filepath, text, time, timezone-olson, timezone-series
Files
- Data/IRC/CLog/Parse.hs +149/−0
- Data/IRC/Event.hs +38/−0
- LICENSE +26/−0
- Setup.hs +4/−0
- clogparse.cabal +31/−0
+ Data/IRC/CLog/Parse.hs view
@@ -0,0 +1,149 @@+{-# LANGUAGE+ PatternGuards #-}++-- | Parse events from @clog@ output, such as the files+-- at <http://tunes.org/~nef/logs/haskell/>.+--+-- IRC has no single standard character encoding. This+-- module decodes messages as UTF-8 following common+-- practice on Freenode.++module Data.IRC.CLog.Parse+ (+ -- * Parsing log files+ parseLog+ -- * Configuring the parser+ , Config(..)+ , haskellConfig+ -- * Re-export+ , module Data.IRC.Event+ ) where++import Data.IRC.Event++import Data.Word+import Data.List+import Control.Applicative++import qualified Data.Foldable as F+import qualified Data.Attoparsec as P+import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as B8+import qualified Data.Time as Time+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.Text.Encoding.Error as T+import qualified System.FilePath as Path+import qualified System.Environment as Env+import qualified System.IO.Error as IOError+import qualified Data.Time.LocalTime.TimeZone.Series as Zone+import qualified Data.Time.LocalTime.TimeZone.Olson as Zone+++-- | Configuring the parser.+data Config = Config+ { timeZone :: String -- ^ Timestamp time zone; an Olson time zone name.+ , zoneInfo :: FilePath -- ^ Directory for time zone files; @$TZDIR@ overrides.+ } deriving (Show)++-- | @'Config'@ value suitable for parsing @#haskell@ logs on Linux.+haskellConfig :: Config+haskellConfig = Config+ { timeZone = "America/Los_Angeles"+ , zoneInfo = "/usr/share/zoneinfo" }+++-- Many text encodings are used on IRC.+-- We decode clog metadata as ASCII.+-- We parse messages as UTF-8 in a lenient mode.++decode :: B.ByteString -> T.Text+decode = T.decodeUtf8With T.lenientDecode+++-- Timestamps are in local time and must be converted.++type TimeConv = Time.LocalTime -> Time.UTCTime++getTimeConv :: FilePath -> IO TimeConv+getTimeConv p = Zone.localTimeToUTC' <$> Zone.getTimeZoneSeriesFromOlsonFile p++data TimeAdj = TimeAdj Time.Day TimeConv+++-- Parsers.++notNewline :: Word8 -> Bool+notNewline w = w /= 13 && w /= 10++restOfLine :: P.Parser T.Text+restOfLine = decode <$> P.takeWhile notNewline <* P.take 1++nextLine :: P.Parser ()+nextLine = P.skipWhile notNewline <* P.take 1++digits :: Int -> P.Parser Int+digits n = atoi <$> P.count n digit where+ atoi = foldl' (\m d -> m*10 + fromIntegral d - 48) 0+ digit = P.satisfy isDigit+ isDigit w = w >= 48 && w <= 57++time :: TimeAdj -> P.Parser Time.UTCTime+time (TimeAdj day conv) = f <$> d2 <* col <*> d2 <* col <*> d2 where+ d2 = digits 2+ col = P.word8 58+ f h m s = conv . Time.LocalTime day $ Time.TimeOfDay h m (fromIntegral s)++event :: P.Parser Event+event = F.asum+ [ str " --- " *> F.asum+ [ userAct Join "join: "+ , userAct Part "part: "+ , userAct Quit "quit: "+ , ReNick <$ str "nick: " <*> nick <* str " -> " <*> nick <* nextLine+ , Mode <$ str "mode: " <*> nick <* str " set " <*> restOfLine+ , Kick <$ str "kick: " <*> nick <* str " was kicked by " <*> nick <* chr ' ' <*> restOfLine+ , global Log "log: "+ , global Topic "topic: "+ , global Names "names: "+ ]+ , Talk <$ str " <" <*> nick <* str "> " <*> restOfLine+ , Notice <$ str " -" <*> nick <*> restOfLine -- FIXME: parse host+ , Act <$ str " * " <*> nick <* chr ' ' <*> restOfLine+ ] where+ chr = P.word8 . fromIntegral . fromEnum+ str = P.string . B8.pack+ nick = (Nick . decode) <$> P.takeWhile (not . P.inClass " \n\r\t\v<>")+ userAct f x = f <$ str x <*> nick <* chr ' ' <*> restOfLine+ global f x = f <$ str x <*> restOfLine++line :: TimeAdj -> P.Parser EventAt+line adj =+ P.try (EventAt <$> time adj <*> event)+ <|> (NoParse <$> restOfLine)++safeRead :: (Read a) => String -> Maybe a+safeRead x | [(v,"")] <- reads x = Just v+safeRead _ = Nothing++getDay :: FilePath -> Time.Day+getDay p+ | (_, [y1,y0,'.',m1,m0,'.',d1,d0]) <- Path.splitFileName p+ , Just [y,m,d] <- mapM safeRead [[y1,y0],[m1,m0],[d1,d0]]+ = Time.fromGregorian (2000 + fromIntegral y) m d+getDay p = error ("cannot parse date from filename: " ++ p)++-- | Parse a log file. The file name (after any directory)+-- is significant. It is used to set the date for timestamps.+-- It should have the form @YY.MM.DD@, as do the files on+-- @tunes.org@.+parseLog :: Config -> FilePath -> IO [EventAt]+parseLog (Config{timeZone=tz, zoneInfo=zi}) p = do+ tzdir <- either (const zi) id <$> IOError.try (Env.getEnv "TZDIR")+ adj <- TimeAdj (getDay p) <$> getTimeConv (Path.combine tzdir tz)+ b <- B.readFile p+ let go r@P.Fail{} = error $ show r+ go (P.Partial g) = go $ g B.empty+ go (P.Done _ x) = x+ let es = go $ P.parse (P.manyTill (line adj) P.endOfInput) b+ return es
+ Data/IRC/Event.hs view
@@ -0,0 +1,38 @@+-- | Represents events in an IRC channel.+-- These do not correspond precisely to messages of the IRC+-- protocol. They provide a somewhat higher-level view.++module Data.IRC.Event+ ( Nick (..)+ , Event (..)+ , EventAt(..)+ ) where++import qualified Data.Time as Time+import qualified Data.Text as T++-- | Event with timestamp.+data EventAt+ = EventAt Time.UTCTime Event -- ^ Event with timestamp.+ | NoParse T.Text -- ^ Unparsable line.+ deriving (Show, Eq, Ord)++-- | IRC nicks.+newtype Nick = Nick T.Text+ deriving (Show, Eq, Ord)++-- | Events in an IRC channel.+data Event+ = Join Nick T.Text -- ^ User joined.+ | Part Nick T.Text -- ^ User left the channel.+ | Quit Nick T.Text -- ^ User quit the server.+ | ReNick Nick Nick -- ^ User changed from one to another nick.+ | Talk Nick T.Text -- ^ User spoke (@PRIVMSG@).+ | Notice Nick T.Text -- ^ User spoke (@NOTICE@).+ | Act Nick T.Text -- ^ User acted (@CTCP ACTION@).+ | Kick Nick Nick T.Text -- ^ User was kicked by user.+ | Mode Nick T.Text -- ^ User set mode on the channel.+ | Log T.Text -- ^ Logging started or stopped.+ | Topic T.Text -- ^ Topic listing or change.+ | Names T.Text -- ^ Users list.+ deriving (Show, Eq, Ord)
+ LICENSE view
@@ -0,0 +1,26 @@+Copyright (c) Keegan McAllister 2010++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.+3. Neither the name of the author nor the names of his contributors+ may be used to endorse or promote products derived from this software+ without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,4 @@+#! /usr/bin/runhaskell++import Distribution.Simple+main = defaultMain
+ clogparse.cabal view
@@ -0,0 +1,31 @@+name: clogparse+version: 0.1+license: BSD3+license-file: LICENSE+synopsis: Parse IRC logs such as the #haskell logs on tunes.org+category: Text, IRC, Language+author: Keegan McAllister <mcallister.keegan@gmail.com>+maintainer: Keegan McAllister <mcallister.keegan@gmail.com>+build-type: Simple+cabal-version: >=1.2+description:+ Use this library to parse IRC logs saved by the @clog@ bot on Freenode.+ This includes the logs of @#haskell@ which are available from+ <http://tunes.org/~nef/logs/haskell/>.+ .+ Suggestions and patches are welcome.++library+ exposed-modules:+ Data.IRC.Event+ , Data.IRC.CLog.Parse+ ghc-options: -Wall+ build-depends:+ base >= 3 && < 5+ , bytestring >= 0.9+ , text >= 0.8+ , time >= 1.1+ , filepath >= 1.1+ , timezone-olson >= 0.1+ , timezone-series >= 0.1+ , attoparsec >= 0.8