-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFmtComb.hs
172 lines (142 loc) · 6.04 KB
/
FmtComb.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
--------------------------------------------------------------------
-- |
-- Module : FmtComb
-- Copyright : (c) Nicolas Pouillard 2010, 2011
-- License : BSD3
--
-- Maintainer: Nicolas Pouillard <[email protected]>
-- Stability : provisional
-- Portability:
--
--------------------------------------------------------------------
{-# LANGUAGE BangPatterns,
OverloadedStrings, GeneralizedNewtypeDeriving #-}
module FmtComb where
import Control.Applicative
import Control.Monad.Reader
import qualified Data.ByteString.Lazy.Char8 as C
import qualified Data.ByteString.Char8 as S8
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as B
{- TMP-NO-MIME
import Codec.MIME.Type (MIMEValue(..), Type(..), showMIMEType)
-}
import Codec.Mbox (MboxMessage(..), showMboxMessage)
import qualified Data.Digest.Pure.MD5 as MD5 (md5)
import Data.Maybe (fromMaybe)
import Data.List (intersperse)
import Data.Monoid (Monoid(..))
import Data.Char (isSpace)
import Email
type Message = (Email, MboxMessage B.ByteString)
newtype ReaderMsg a = ReaderMsg { runReaderMsg :: Reader Message a }
deriving (Monad, Functor, Applicative, MonadReader Message)
type FmtComb = ReaderMsg B.ByteString
type FmtMod = FmtComb -> FmtComb
instance Monoid w => Monoid (ReaderMsg w) where
mempty = pure mempty
mappend x y = mappend <$> x <*> y
evalReaderMsg :: Message -> ReaderMsg a -> a
evalReaderMsg msg = flip runReader msg . runReaderMsg
-- read/show extras
mayRead :: Read a => String -> Maybe a
mayRead s = case reads s of
[(x, "")] -> Just x
_ -> Nothing
showC ::Show a => a -> B.ByteString
showC = C.pack . show
viaS :: (S.ByteString -> S.ByteString) -> B.ByteString -> B.ByteString
viaS f = C.fromChunks . pure . f . foldr S.append S.empty . C.toChunks
stripS ::S.ByteString -> S.ByteString
stripS = S8.dropWhile isSpace . fst . S8.spanEnd isSpace
stripC ::B.ByteString -> B.ByteString
stripC = viaS stripS
strip ::String -> String
strip = S8.unpack . stripS . S8.pack
split :: Char -> String -> [String]
split c = map S8.unpack . S8.split c . S8.pack
mkF :: (MboxMessage B.ByteString -> a) -> ReaderMsg a
mkF f = asks (f . snd)
mboxMsgSenderF, mboxMsgTimeF, mboxFromF, mboxMsgFileF, mboxMsgOffsetF,
mboxMsgF, mboxMsgBodyF, messageIDF, subjectF, {-TMP-NO-MIME mimeTypeF,-} emailShown,
oneLinerF :: FmtComb
mboxMsgTimeF = mkF _mboxMsgTime
mboxMsgSenderF = mkF _mboxMsgSender
mboxMsgFileF = mkF $ C.pack . _mboxMsgFile
mboxMsgOffsetF = mkF $ showC . _mboxMsgOffset
mboxMsgF = mkF $ flip C.snoc '\n' . showMboxMessage
mboxMsgBodyF = mkF _mboxMsgBody
emailShown = showC . fst <$> ask
mboxFromF
= mconcat [pure "From ", mboxMsgSenderF, pure " ", mboxMsgTimeF]
messageIDF
= C.pack . fromMaybe "<NO-VALID-MESSAGE-ID>"
. (>>= unquote) . messageId . fst <$> ask
subjectF
= C.pack . fromMaybe "<NO-VALID-SUBJECT>" . messageSubject . fst <$> ask
{- TMP-NO-MIME
mimeTypeF
= C.pack . showMIMEType . mimeType . mime_val_type
. get emailContent . fst <$> ask
-}
oneLinerF
= mconcat $ intersperse (pure " | ")
[ align 40 $ ellipse 40 $ subjectF
, {- TMP-NO-MIME align 15 $ mimeTypeF,-} messageIDF ]
align :: Int -> FmtMod
align n x = B.take (fi n) <$> (x `mappend` pure (C.repeat ' '))
ellipse :: Int -> FmtMod
ellipse n s = (B.take (fi n) <$> s) `mappend` pure "..."
fi :: (Integral a, Num b) => a -> b
fi = fromIntegral
size, md5 :: FmtMod
size = fmap (showC . C.length)
md5 = fmap (showC . MD5.md5)
fmtCombs :: [(String, (FmtComb, String))]
fmtCombs = [ ("one", (oneLinerF , "One line per email with: subject, mimetype and message ID (default)"))
, ("subj", (subjectF , "Subject"))
, ("mboxmsgsender", (mboxMsgSenderF , "Mbox Sender"))
, ("mboxmsgtime", (mboxMsgTimeF , "Mbox Msg Time"))
, ("mboxmsgfile", (mboxMsgFileF , "Mbox Msg File"))
, ("offset", (mboxMsgOffsetF , "Mbox Offset"))
, ("mboxmsg", (mboxMsgF , "Mbox Msg"))
, ("mboxmsgbody", (mboxMsgBodyF , "Mbox Msg Body"))
-- TMP-NO-MIME , ("mimetype", (mimeTypeF , "MIME type"))
, ("fromline", (mboxFromF , "Mbox From Line [as 'From %(mboxmsgsender) %(mboxmsgtime)']"))
, ("mid", (messageIDF , "Message ID"))
]
fmtMods :: [(String, (FmtMod, String))]
fmtMods = [ ("size", (size, "Commpute the size of the input"))
, ("md5", (md5, "Hash the input with MD5"))
, ("strip", (fmap stripC, "Strip leading and trailling spaces"))
]
intFmtMods :: [(String, ((Int -> FmtMod), String))]
intFmtMods = [ ("ellipse", (ellipse, "Truncate at N and put an ellipse"))
, ("align", (align, "Fill upto N with spaces"))
, ("take", (fmap . C.take . fi, "Take the N firsts"))
, ("drop", (fmap . C.take . fi, "Drop the N firsts"))
]
mayEvalStr :: String -> Maybe String
mayEvalStr = mayRead . ('\"' :) . foldr escapeDQuote "\""
where escapeDQuote '"' = ('\\':).('"':)
escapeDQuote c = (c:)
mayReadIntFmtMod :: String -> Maybe FmtMod
mayReadIntFmtMod s = fst <$> lookup s1 intFmtMods <*> mayRead s2
where (s1, s2) = break isSpace s
mayReadFmtMod :: String -> Maybe FmtMod
mayReadFmtMod s = (fst <$> lookup s fmtMods) `mplus` mayReadIntFmtMod s
mayReadFmtComb :: String -> Maybe FmtComb
mayReadFmtComb s = case reverse (split '.' s) of
[] -> Nothing
lst : xs -> do cmb <- fst <$> lookup lst fmtCombs
mods <- mapM (mayReadFmtMod . strip) (reverse xs)
return $ foldr (.) id mods cmb
mayReadShowFmts :: String -> Maybe FmtComb
mayReadShowFmts = f
where f [] = Just mempty
f ('%':'(':s) = let (s1,s2) = break (==')') s in
mappend <$> (mayReadFmtComb s1) <*> f (drop 1 s2)
f s = let (s1,s2) = break (=='%') s in
mappend <$> (pure . C.pack <$> mayEvalStr s1) <*> f s2
renderFmtComb :: FmtComb -> Message -> B.ByteString
renderFmtComb = flip evalReaderMsg