packages feed

imm 0.4.0.0 → 0.4.1.0

raw patch · 11 files changed

+109/−141 lines, 11 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Imm.Mail: buildBody :: MonadReader Settings m => (Item, Feed) -> m Text
- Imm.Mail: buildFrom :: MonadReader Settings m => (Item, Feed) -> m String
- Imm.Mail: buildSubject :: MonadReader Settings m => (Item, Feed) -> m Text
- Imm.Types: RefDirs :: FilePath -> FilePath -> FilePath -> FilePath -> RefDirs
- Imm.Types: data RefDirs
- Imm.Types: mConfiguration :: RefDirs -> FilePath
- Imm.Types: mData :: RefDirs -> FilePath
- Imm.Types: mDenyReconf :: CliOptions -> Bool
- Imm.Types: mHome :: RefDirs -> FilePath
- Imm.Types: mMasterBinary :: CliOptions -> Maybe String
- Imm.Types: mTemporary :: RefDirs -> FilePath
- Imm.Types: type PortableFilePath = RefDirs -> FilePath
- Imm.Util: resolve :: MonadIO m => (RefDirs -> a) -> m a
+ Imm.Feed: check :: (MonadReader Settings m, MonadIO m, MonadError ImmError m) => ImmFeed -> m ()
+ Imm.Main: check :: MonadIO m => FeedList -> m ()
+ Imm.Types: mCheck :: CliOptions -> Bool
+ Imm.Util: (>/>) :: MonadIO m => IO FilePath -> FilePath -> m FilePath
- Imm.Feed: printStatus :: (MonadReader Settings m, MonadIO m) => String -> m ()
+ Imm.Feed: printStatus :: (MonadReader Settings m, MonadIO m) => URI -> m ()
- Imm.Feed: update :: (MonadReader Settings m, MonadIO m, MonadError ImmError m) => ImmFeed -> m ()
+ Imm.Feed: update :: (Applicative m, MonadReader Settings m, MonadIO m, MonadError ImmError m) => ImmFeed -> m ()
- Imm.Feed: updateItem :: (MonadReader Settings m, MonadIO m, MonadError ImmError m) => (Item, Feed) -> m ()
+ Imm.Feed: updateItem :: (Applicative m, MonadReader Settings m, MonadIO m, MonadError ImmError m) => (Item, Feed) -> m ()
- Imm.Mail: build :: MonadReader Settings m => TimeZone -> (Item, Feed) -> m Mail
+ Imm.Mail: build :: (Applicative m, MonadReader Settings m) => TimeZone -> (Item, Feed) -> m Mail
- Imm.Maildir: add :: (MonadIO m, MonadError ImmError m) => PortableFilePath -> Mail -> m ()
+ Imm.Maildir: add :: (MonadIO m, MonadError ImmError m) => IO FilePath -> Mail -> m ()
- Imm.Maildir: init :: (MonadIO m, MonadError ImmError m) => PortableFilePath -> m ()
+ Imm.Maildir: init :: (MonadIO m, MonadError ImmError m) => IO FilePath -> m ()
- Imm.Types: CliOptions :: Maybe String -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Maybe String -> CliOptions
+ Imm.Types: CliOptions :: Bool -> Maybe String -> Bool -> Bool -> Bool -> Bool -> Bool -> CliOptions
- Imm.Types: Settings :: PortableFilePath -> PortableFilePath -> ((Item, Feed) -> String) -> ((Item, Feed) -> Text) -> ((Item, Feed) -> Text) -> Settings
+ Imm.Types: Settings :: IO FilePath -> IO FilePath -> ((Item, Feed) -> String) -> ((Item, Feed) -> Text) -> ((Item, Feed) -> Text) -> Settings
- Imm.Types: mMaildir :: Settings -> PortableFilePath
+ Imm.Types: mMaildir :: Settings -> IO FilePath
- Imm.Types: mStateDirectory :: Settings -> PortableFilePath
+ Imm.Types: mStateDirectory :: Settings -> IO FilePath

Files

Imm/Boot.hs view
@@ -1,4 +1,3 @@--- | Low level functions used at start-up to parse commandline and handle dynamic reconfiguration. module Imm.Boot where  -- {{{ Imports@@ -13,7 +12,6 @@ import System.IO -- }}} --- {{{ Commandline options -- | Available commandline options. cliOptions :: Mode (CmdArgs CliOptions) cliOptions = cmdArgsMode $ baseOptions@@ -24,16 +22,13 @@     &= program "imm"   where     baseOptions = CliOptions {-        --    mCheck         = def &= explicit &= name "c" &= name "check" &= help "Check availability and validity of all feed sources currently configured.",+            mCheck         = def &= explicit &= name "c" &= name "check" &= help "Check availability and validity of all feed sources currently configured, without writing any mail.",             mFeedURI       = def &= explicit &= name "f" &= name "feed"   &= help "Only process given feed." &= typ "URI",             mImportOPML    = def &= explicit &= name "i" &= name "import" &= help "Import feeds list from an OPML descriptor (read from stdin).",             mList          = def &= explicit &= name "l" &= name "list"  &= help "List all feed sources currently configured, along with their status.",             mMarkAsRead    = def &= explicit &= name "R" &= name "mark-read" &= help "Mark every item of processed feeds as read, ie set last update as now without writing any mail.",             mMarkAsUnread  = def &= explicit &= name "U" &= name "mark-unread" &= help "Mark every item of processed feeds as unread, ie delete corresponding state files.",-            mUpdate        = def &= explicit &= name "u" &= name "update" &= help "Update list of feeds (mostly used option).",-            mDenyReconf    = def &= explicit &= name "deny-reconf"       &= help "Deny recompilation even if the configuration file has changed.",-            mMasterBinary  = def &= explicit &= name "dyre-master-binary" &= help "Flag used internally for dynamic reconfiguration purposes." &= typ "PATH"}--- }}}+            mUpdate        = def &= explicit &= name "u" &= name "update" &= help "Update list of feeds (mostly used option)."}  -- {{{ Dynamic reconfiguration -- | Print various paths used for dynamic reconfiguration.@@ -47,7 +42,7 @@         "Cache directory: " ++ d,         "Lib directory:   " ++ e, []] --- | Dynamic configuration settings.+-- | Dynamic reconfiguration settings. dyreParameters :: D.Params (Either String FeedList) dyreParameters = D.defaultParams {   D.projectName  = "imm",@@ -61,7 +56,7 @@ showError _ = Left -- }}} --- | Main function to call in your configuration file.+-- | Main function to call in the configuration file. imm :: FeedList -> IO () imm = D.wrapMain dyreParameters . Right @@ -69,19 +64,20 @@ realMain :: Either String FeedList -> IO () realMain (Left e) = putStrLn e realMain (Right feeds) = do-    options <- cmdArgsRun cliOptions     whenLoud printDyrePaths+    options <- cmdArgsRun cliOptions+     let feeds' = case (mFeedURI options) of           Just uri -> filter (\(_, u) -> u == uri) feeds           _        -> feeds+     realMain' (feeds', options)   where     realMain' (f, options)-      | mImportOPML options   = Main.importOPML-      | mList       options   = Main.list f-      | mMarkAsRead options   = Main.markAsRead f+      | mCheck        options = Main.check f+      | mImportOPML   options = Main.importOPML+      | mList         options = Main.list f+      | mMarkAsRead   options = Main.markAsRead f       | mMarkAsUnread options = Main.markAsUnread f---      | mCheck options = Main.check f-      | mUpdate     options   = Main.update f+      | mUpdate       options = Main.update f       | otherwise             = print $ helpText [] HelpFormatDefault cliOptions--- }}}
Imm/Config.hs view
@@ -13,7 +13,8 @@ import Data.Foldable hiding(concat) import qualified Data.Text.Lazy as T -import System.FilePath+import System.Directory+import System.Environment.XDG.BaseDir  import Text.Feed.Query -- }}}@@ -21,8 +22,8 @@  instance Default Settings where     def = Settings {-        mStateDirectory = (</> "state") . mConfiguration,-        mMaildir        = (</> "feeds") . mHome,+        mStateDirectory = getUserConfigDir "imm" >/> "state",+        mMaildir        = getHomeDirectory >/> "feeds",         mFromBuilder    = \(item, feed) -> maybe (getFeedTitle feed) id $ getItemAuthor item,         mSubjectBuilder = \(item, _feed) -> T.pack . maybe "Untitled" id $ getItemTitle item,         mBodyBuilder    = \(item, _feed) -> T.unlines $ map (flip ($) item) [T.pack . getItemLinkNM, getItemContent]}
Imm/Feed.hs view
@@ -8,11 +8,12 @@ import Imm.Types import Imm.Util +import Control.Applicative import Control.Conditional hiding(when) import Control.Monad.Error import Control.Monad.Reader hiding(when) ---import Data.Functor+import Data.Either import qualified Data.Text.Lazy as T import Data.Time hiding(parseTime) import Data.Time.Clock.POSIX@@ -20,7 +21,7 @@ import Network.URI as N  import System.Directory-import System.FilePath+--import System.FilePath import System.IO import System.Locale @@ -47,21 +48,18 @@ parse x = maybe (throwError $ ParseFeedError x) return $ parseFeedString x  -- | -printStatus :: (MonadReader Settings m, MonadIO m) => String -> m ()-printStatus feedUri = do-    prefix <- case N.parseURI feedUri of-        Just uri -> do-          lastCheck <- getLastCheck uri-          return $ (lastCheck == posixSecondsToUTCTime 0) ? "[NEW] " ?? ("[Last update: "++ show lastCheck ++ "]")-        _ -> return "[Not an URI]"-    io . putStrLn $ prefix ++ " " ++ feedUri+printStatus :: (MonadReader Settings m, MonadIO m) => URI -> m ()+printStatus uri = do+    lastCheck <- getLastCheck uri+    let prefix = (lastCheck == posixSecondsToUTCTime 0) ? "[NEW] " ?? ("[Last update: "++ show lastCheck ++ "]")+    io . putStrLn $ prefix ++ " " ++ show uri   getLastCheck :: (MonadReader Settings m, MonadIO m) => URI -> m UTCTime getLastCheck feedUri = do-    directory <- asks mStateDirectory >>= resolve+    directory <- asks mStateDirectory     result    <- runErrorT $ do-        content <- try $ readFile (directory </> fileName)+        content <- try $ readFile =<< (directory >/> fileName)         parseTime content              either (const $ return timeZero) return result@@ -72,12 +70,12 @@  storeLastCheck :: (MonadReader Settings m, MonadIO m, MonadError ImmError m) => URI -> UTCTime -> m () storeLastCheck feedUri date = do-    directory <- asks mStateDirectory >>= resolve+    directory <- asks mStateDirectory     -    (file, stream) <- try $ openTempFile directory fileName+    (file, stream) <- try $ (`openTempFile` fileName) =<< directory     io $ hPutStrLn stream (formatTime defaultTimeLocale "%c" date)     io $ hClose stream-    try $ renameFile file (directory </> fileName)+    try $ renameFile file =<< (directory >/> fileName)   where     fileName = getStateFile feedUri @@ -86,10 +84,17 @@     feed <- parse . T.unpack =<< decode =<< HTTP.getRaw uri     return (uri, feed) +-- | +check :: (MonadReader Settings m, MonadIO m, MonadError ImmError m) => ImmFeed -> m ()+check (uri, feed) = do+    lastCheck <- getLastCheck uri+    dates     <- return . rights =<< forM (feedItems feed) (runErrorT . getDate)+    let newItems = filter (> lastCheck) dates+    io . putStrLn $ "==> " ++ show (length newItems) ++ " new item(s) "+ -- | Create mails for each new item-update :: (MonadReader Settings m, MonadIO m, MonadError ImmError m) => ImmFeed -> m ()+update :: (Applicative m, MonadReader Settings m, MonadIO m, MonadError ImmError m) => ImmFeed -> m () update (uri, feed) = do-    logNormal $ "Updating feed " ++ show uri --    checkStateDirectory     Maildir.init =<< asks mMaildir @@ -99,15 +104,15 @@         "Home:   " ++ (maybe "No home"   id $ getFeedHome feed)]          lastCheck <- getLastCheck uri-    forM_ (feedItems feed) $ \item -> +    results <- forM (feedItems feed) $ \item ->        do         date <- getDate item-        when (date > lastCheck) $ updateItem (item, feed)-      `catchError` (io . print)-+        (date > lastCheck) ? (updateItem (item, feed) >> return 1) ?? return 0+      `catchError` (\e -> (io . print) e >> return 0 )+    io . putStrLn $ "==> " ++ show (sum results) ++ " new item(s)"     markAsRead uri -updateItem :: (MonadReader Settings m, MonadIO m, MonadError ImmError m) => (Item, Feed) -> m ()+updateItem :: (Applicative m, MonadReader Settings m, MonadIO m, MonadError ImmError m) => (Item, Feed) -> m () updateItem (item, feed) = do     date <- getDate item     logVerbose $ unlines [@@ -127,8 +132,8 @@  markAsUnread :: forall (m :: * -> *) . (MonadIO m, MonadError ImmError m, MonadReader Settings m) => URI -> m () markAsUnread uri = do-    directory <- asks mStateDirectory >>= resolve-    try $ removeFile $ directory </> (getStateFile uri)+    directory <- asks mStateDirectory+    try $ removeFile =<< directory >/> (getStateFile uri)     logVerbose $ "Feed " ++ show uri ++ " marked as unread."      
Imm/HTTP.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE FlexibleContexts, ScopedTypeVariables, KindSignatures #-}--- | HTTP client related functions module Imm.HTTP where  -- {{{ Imports@@ -20,7 +19,7 @@ import Prelude hiding(catch) -- }}} -+-- | Perform an HTTP GET request and return the response body as raw 'ByteString' getRaw :: (MonadIO m, MonadError ImmError m) => URI -> m BL.ByteString getRaw uri = do     logVerbose $ "Downloading " ++ show uri@@ -29,7 +28,7 @@     return $ responseBody res  --- | Monad-agnostic version of 'withManager+-- | Monad-agnostic version of 'withManager' withManager' f = do     res <- timeout 11000000 $ (Right <$> withManager f) `catch` (return . Left . IOE) `catch` (return . Left . HTTPError) `catch` (return . Left . TLSError)     either throwError return res@@ -41,7 +40,7 @@     either throwError return result      --- | Build an HTTP request for given URI.+-- | Build an HTTP request for given URI request :: (MonadError ImmError m, MonadIO m) => String -> m (Request a) request uri = do     req <- parseURL uri
Imm/Mail.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE FlexibleContexts #-}--- | Utilities to manipulate mails. module Imm.Mail where  -- {{{ Imports@@ -7,6 +6,7 @@ import Imm.Types import Imm.Util +import Control.Applicative import Control.Monad import Control.Monad.Reader @@ -31,6 +31,7 @@         mSubject            = T.pack "Untitled",         mReturnPath         = "<imm@noreply>"} + toText :: Mail -> T.Text toText mail = T.unlines [     T.pack $ "Return-Path: " ++ mReturnPath mail,@@ -42,30 +43,12 @@     T.pack "",     mBody mail]  --build :: MonadReader Settings m => TimeZone -> (Item, Feed) -> m Mail+-- | Build mail from a given feed, using builders functions from 'Settings'.+build :: (Applicative m, MonadReader Settings m) => TimeZone -> (Item, Feed) -> m Mail build timeZone (item, feed) = do-    from    <- buildFrom    (item, feed)-    subject <- buildSubject (item, feed)-    body    <- buildBody    (item, feed)+    from    <- asks mFromBuilder    <*> return (item, feed)+    subject <- asks mSubjectBuilder <*> return (item, feed)+    body    <- asks mBodyBuilder    <*> return (item, feed)     return def {mDate = date, mFrom = from, mSubject = subject, mBody = body}   where     date = maybe Nothing (Just . utcToZonedTime timeZone) . parseDate <=< F.getItemDate $ item---buildFrom :: MonadReader Settings m => (Item, Feed) -> m String-buildFrom (item, feed) = do-    builder <- asks mFromBuilder-    return $ builder (item, feed)---buildSubject :: MonadReader Settings m => (Item, Feed) -> m T.Text-buildSubject (item, feed) = do-    builder <- asks mSubjectBuilder-    return $ builder (item, feed)---buildBody :: MonadReader Settings m => (Item, Feed) -> m T.Text-buildBody (item, feed) = do-    builder <- asks mBodyBuilder-    return $ builder (item, feed)
Imm/Maildir.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE FlexibleContexts #-}--- | Utilities to manipulate maildirs. module Imm.Maildir where  -- {{{ Imports@@ -17,26 +16,26 @@ import Network.BSD  import System.Directory-import System.FilePath+--import System.FilePath import System.Random -- }}} -init :: (MonadIO m, MonadError ImmError m) => PortableFilePath -> m ()+-- | Build a maildir with subdirectories cur, new and tmp.+init :: (MonadIO m, MonadError ImmError m) => IO FilePath -> m () init directory = do-    dir <- io $ resolve directory-    try $ createDirectoryIfMissing True dir-    try $ createDirectoryIfMissing True (dir </> "cur")-    try $ createDirectoryIfMissing True (dir </> "new")-    try $ createDirectoryIfMissing True (dir </> "tmp")-+    try $ createDirectoryIfMissing True =<< directory+    try $ createDirectoryIfMissing True =<< (directory >/> "cur")+    try $ createDirectoryIfMissing True =<< (directory >/> "new")+    try $ createDirectoryIfMissing True =<< (directory >/> "tmp") -add :: (MonadIO m, MonadError ImmError m) => PortableFilePath -> Mail -> m ()+-- | Add a mail to the maildir+add :: (MonadIO m, MonadError ImmError m) => IO FilePath -> Mail -> m () add directory mail = do-    dir      <- resolve directory     fileName <- io getUniqueName-    try $ T.writeFile (dir </> "new" </> fileName) (toText mail)-+    dir      <- directory >/> "new" >/> fileName +    try $ T.writeFile dir (toText mail) +-- | Return an allegedly unique filename; useful to add new mail files in a maildir. getUniqueName :: MonadIO m => m String  getUniqueName = io $ do     time     <- show <$> getPOSIXTime@@ -44,4 +43,3 @@     rand     <- show <$> (getStdRandom $ randomR (1,100000) :: IO Int)          return . concat $ [time, ".", rand, ".", hostname]-
Imm/Main.hs view
@@ -1,10 +1,9 @@ {-# LANGUAGE FlexibleContexts #-}--- | Main high level process functions. module Imm.Main where  -- {{{ Imports import Imm.Config-import Imm.Feed as Feed+import qualified Imm.Feed as Feed import Imm.OPML as OPML import Imm.Types import Imm.Util@@ -24,26 +23,38 @@ -- }}}  +check :: (MonadIO m) => FeedList -> m ()+check feeds = io . forM_ feeds $ \(f, u) -> do    +    result <- runErrorT . (`runReaderT` (f def)) $ do+        logNormal $ "Checking: " ++ u+        Feed.check =<< Feed.download =<< parseURI u+    either print return result++ importOPML :: (MonadIO m) => m ()-importOPML = io $ (maybe (return ()) addFeeds) =<< OPML.read <$> hGetContents stdin+importOPML = io $ mapM_ addFeeds =<< OPML.read <$> hGetContents stdin + list :: (MonadIO m) => FeedList -> m ()-list = io . mapM_ (\(f, u) -> runReaderT (printStatus u) (f def))+list = io . mapM_ (\(f, u) -> runReaderT (runErrorT $ parseURI u >>= Feed.printStatus) (f def)) + markAsRead :: (MonadIO m) => FeedList -> m () markAsRead = mapM_ (\(f,u) -> runReaderT (runErrorT $ checkStateDirectory >> parseURI u >>= Feed.markAsRead) (f def)) + markAsUnread :: (MonadIO m) => FeedList -> m () markAsUnread = mapM_ (\(f,u) -> runReaderT (runErrorT $ parseURI u >>= Feed.markAsUnread) (f def)) + update :: (MonadIO m) => FeedList -> m () update feeds = io . forM_ feeds $ \(f, u) -> do     result <- runErrorT . (`runReaderT` (f def)) $ do-        feed <- Feed.download =<< parseURI u+        logNormal $ "Updating: " ++ u         checkStateDirectory-        Feed.update feed+        Feed.update =<< Feed.download =<< parseURI u     either print return result   checkStateDirectory :: (MonadReader Settings m, MonadIO m, MonadError ImmError m) => m ()-checkStateDirectory = asks mStateDirectory >>= resolve >>= try . io . createDirectoryIfMissing True+checkStateDirectory = try . io . (createDirectoryIfMissing True =<<) =<< asks mStateDirectory
Imm/OPML.hs view
@@ -6,6 +6,7 @@ import Text.XML.Light.Types -- }}} +-- | Parse an OPML string and return a list of feed groups read :: String -> Maybe [(String, [String])] read rawOPML = do     opml <- parseOPMLString rawOPML
Imm/Types.hs view
@@ -27,7 +27,6 @@ -- }}}  -- {{{ Error handling--- | Errors that can be returned by an Imm process data ImmError =      OtherError         String   | HTTPError          HttpException@@ -65,28 +64,25 @@ -- }}}  -- {{{ Settings type+-- | Available commandline options (cf imm -h) data CliOptions = CliOptions {---    mCheck        :: Bool,+    mCheck        :: Bool,     mFeedURI      :: Maybe String,     mImportOPML   :: Bool,     mList         :: Bool,     mMarkAsRead   :: Bool,     mMarkAsUnread :: Bool,-    mUpdate       :: Bool,-    mDenyReconf   :: Bool,         -- ^ Do not recompile configuration even if it has changed-    mMasterBinary :: Maybe String+    mUpdate       :: Bool } deriving (Data, Typeable, Show, Eq) --- | Set of settings for imm data Settings = Settings {-    mStateDirectory :: PortableFilePath,-    mMaildir        :: PortableFilePath,-    mFromBuilder    :: (Item, Feed) -> String,-    mSubjectBuilder :: (Item, Feed) -> TL.Text,-    mBodyBuilder    :: (Item, Feed) -> TL.Text   -- ^ sic!+    mStateDirectory :: IO FilePath,              -- ^ Where feeds' state (last update time) will be stored+    mMaildir        :: IO FilePath,              -- ^ Where mails will be written+    mFromBuilder    :: (Item, Feed) -> String,   -- ^ Called to write the From: header of feed mails+    mSubjectBuilder :: (Item, Feed) -> TL.Text,  -- ^ Called to write the Subject: header of feed mails+    mBodyBuilder    :: (Item, Feed) -> TL.Text   -- ^ Called to write the body of feed mails (sic!) } --- |  type CustomSettings = Settings -> Settings -- }}} @@ -105,15 +101,3 @@     mContentDisposition :: String,     mBody               :: TL.Text }---- {{{ Generic file paths--- | Set of reference directories, typically used to build FilePath-s-data RefDirs = RefDirs {-    mHome          :: FilePath,        -- ^ Home directory-    mTemporary     :: FilePath,        -- ^ Temporary files directory-    mConfiguration :: FilePath,        -- ^ Configuration directory-    mData          :: FilePath         -- ^ Data directory-}--type PortableFilePath = RefDirs -> FilePath--- }}}
Imm/Util.hs view
@@ -1,7 +1,4 @@-{-# LANGUAGE NoMonomorphismRestriction #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE FlexibleContexts #-}--- | Various utilities+{-# LANGUAGE NoMonomorphismRestriction, RankNTypes, FlexibleContexts #-} module Imm.Util where  -- {{{ Imports@@ -13,6 +10,7 @@  import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL+import Data.Functor import Data.Maybe import Data.Text.ICU.Convert import Data.Text.Lazy.Encoding hiding(decodeUtf8)@@ -24,13 +22,17 @@ import Network.URI as N  import System.Console.CmdArgs-import System.Directory-import System.Environment.XDG.BaseDir+import System.FilePath import System.IO import System.Locale import System.Timeout as S -- }}} ++-- | Like '(</>)' with first argument in IO to build platform-dependent paths.+(>/>) :: (MonadIO m) => IO FilePath -> FilePath -> m FilePath+(>/>) a b = io $ (</> b) <$> a+ -- {{{ Monadic utilities -- | Shortcut to 'liftIO' io :: MonadIO m => IO a -> m a@@ -51,37 +53,25 @@ logNormal  = io . whenNormal . putStrLn logVerbose = io . whenLoud . putStrLn --- | Evaluate given function while replacing directory variables appropriately for the current system-resolve :: MonadIO m => (RefDirs -> a) -> m a-resolve f = io $ do-    homeDir   <- getHomeDirectory-    tmpDir    <- getTemporaryDirectory-    configDir <- getUserConfigDir "imm"-    dataDir   <- getUserDataDir   "imm"-    -    return . f $ RefDirs homeDir tmpDir configDir dataDir  -- {{{ Monad-agnostic version of various error-prone functions -- | Monad-agnostic version of Data.Text.Encoding.decodeUtf8 decodeUtf8 :: MonadError ImmError m => BL.ByteString -> m TL.Text decodeUtf8 = either (throwError . UnicodeError) return . decodeUtf8' -decode :: (MonadIO m, MonadError ImmError m) => BL.ByteString -> m TL.Text-decode raw = do-    catchError (decodeUtf8 raw) $ return $ do-        conv <- io $ open "ISO-8859-1" Nothing-        return . TL.fromChunks . (\a -> [a]) . toUnicode conv . B.concat . BL.toChunks $ raw----- | Monad-agnostic version of Network.URI.parseURI+-- | Monad-agnostic version of 'Network.URI.parseURI' parseURI :: (MonadError ImmError m) => String -> m URI parseURI uri = maybe (throwError $ ParseUriError uri) return $ N.parseURI uri --- | Monad-agnostic version of Data.Time.Format.parseTime+-- | Monad-agnostic version of 'Data.Time.Format.parseTime' parseTime :: (MonadError ImmError m) => String -> m UTCTime parseTime string = maybe (throwError $ ParseTimeError string) return $ T.parseTime defaultTimeLocale "%c" string -- }}} +decode :: (MonadIO m, MonadError ImmError m) => BL.ByteString -> m TL.Text+decode raw = catchError (decodeUtf8 raw) $ return $ do+    conv <- io $ open "ISO-8859-1" Nothing+    return . TL.fromChunks . (\a -> [a]) . toUnicode conv . B.concat . BL.toChunks $ raw  parseDate :: String -> Maybe UTCTime parseDate date = listToMaybe . map T.zonedTimeToUTC . catMaybes . flip map [readRFC2822, readRFC3339, T.parseTime defaultTimeLocale "%a, %d %b %G %T", T.parseTime defaultTimeLocale "%Y-%m-%d", T.parseTime defaultTimeLocale "%e %b %Y", T.parseTime defaultTimeLocale "%a, %e %b %Y %k:%M:%S %z", T.parseTime defaultTimeLocale "%a, %e %b %Y %T %Z"] $ \f -> f . TL.unpack . TL.strip . TL.pack $ date
imm.cabal view
@@ -1,5 +1,5 @@ Name:                imm-Version:             0.4.0.0+Version:             0.4.1.0 Synopsis:            Retrieve RSS/Atom feeds and write one mail per new item in a maildir. --Description:          --Homepage: