packages feed

imm-0.5.0.0: Imm/Options.hs

{-# LANGUAGE FlexibleInstances, TemplateHaskell #-}
-- | Commandline options tools. Designed to be imported as @qualified@.
module Imm.Options where

-- {{{ Imports
import Imm.Util

import Control.Conditional
import Control.Lens as L  hiding((??))
import Control.Monad.Base
import Control.Monad.Reader hiding(when)

import Data.Default
import Data.Either
import Data.Functor
import Data.List
import Data.Maybe

import Network.URI as N

import Prelude hiding(log)

import System.Console.GetOpt
import System.Environment
import System.Environment.XDG.BaseDir
import System.IO
-- }}}

-- {{{ Types
-- | Available commandline options (cf @imm -h@)
data CliOptions = CliOptions {
    _stateDirectory :: Maybe FilePath,
    _check          :: Bool,
    _feedList       :: [URI],
    _importOPML     :: Maybe FilePath,
    _list           :: Bool,
    _markAsRead     :: Bool,
    _markAsUnread   :: Bool,
    _update         :: Bool,
    _help           :: Bool,
    _quiet          :: Bool,
    _verbose        :: Bool,
    _version        :: Bool,
    _vanilla        :: Bool,
    _recompile      :: Bool,
    _denyReconf     :: Bool,
    _forceReconf    :: Bool,
    _dyreDebug      :: Bool}
    deriving(Eq)

makeLenses ''CliOptions

instance Show CliOptions where
    show opts = intercalate " " $ catMaybes [
        null (view feedList opts) ? Nothing ?? Just ("FEED_URI=[" ++ (intercalate " " . map show $ view feedList opts) ++ "]"),
        return . ("IMPORT_OPML=" ++) =<< view importOPML opts,
        return . ("STATE_DIR=" ++) =<< view stateDirectory opts,
        view check        opts ? Just "CHECK"                 ?? Nothing,
        view list         opts ? Just "LIST"                  ?? Nothing,
        view markAsRead   opts ? Just "MARK_READ"             ?? Nothing,
        view markAsUnread opts ? Just "MARK_UNREAD"           ?? Nothing,
        view update       opts ? Just "UPDATE"                ?? Nothing,
        view help         opts ? Just "HELP"                  ?? Nothing,
        view quiet        opts ? Just "QUIET"                 ?? Nothing,
        view verbose      opts ? Just "VERBOSE"               ?? Nothing,
        view version      opts ? Just "VERSION"               ?? Nothing,
        view vanilla      opts ? Just "VANILLA"               ?? Nothing,
        view recompile    opts ? Just "RECOMPILE"             ?? Nothing,
        view denyReconf   opts ? Just "DENY_RECONFIGURATION"  ?? Nothing,
        view forceReconf  opts ? Just "FORCE_RECONFIGURATION" ?? Nothing,
        view dyreDebug    opts ? Just "DYRE_DEBUG"            ?? Nothing]

instance Default CliOptions where
    def = CliOptions {
        _stateDirectory = Nothing,
        _check          = False,
        _feedList       = [],
        _importOPML     = Nothing,
        _list           = False,
        _markAsRead     = False,
        _markAsUnread   = False,
        _update         = False,
        _help           = False,
        _quiet          = False,
        _verbose        = False,
        _version        = False,
        _vanilla        = False,
        _recompile      = False,
        _denyReconf     = False,
        _forceReconf    = False,
        _dyreDebug      = False}

-- | 'MonadReader' for 'CliOptions'
class OptionsReader m where
    readOptions :: Simple Lens CliOptions a -> m a

instance (Monad m) => OptionsReader (ReaderT CliOptions m) where
    readOptions l = return . view l =<< ask

instance OptionsReader ((->) CliOptions) where
    readOptions l = view l
-- }}}

description :: [OptDescr (CliOptions -> CliOptions)]
description = [
    Option ['s']     ["state"]              (ReqArg (\v -> set stateDirectory (Just v)) "PATH") "Where feeds' state (last update time) will be stored",
    Option ['c']     ["check"]              (NoArg (set check True))                        "Check availability and validity of all feed sources currently configured, without writing any mail",
    Option ['l']     ["list"]               (NoArg (set list True))                         "List all feed sources currently configured, along with their status",
    Option ['R']     ["mark-read"]          (NoArg (set markAsRead True))                   "Mark every item of processed feeds as read, ie set last update as now without writing any mail",
    Option ['U']     ["mark-unread"]        (NoArg (set markAsUnread True))                 "Mark every item of processed feeds as unread, ie delete corresponding state files",
    Option ['u']     ["update"]             (NoArg (set update True))                       "Update list of feeds (mostly used option)",
    Option ['i']     ["import"]             (ReqArg (\v -> set importOPML (Just v)) "PATH") "Import feeds list from an OPML descriptor (read from stdin)",
    Option ['h']     ["help"]               (NoArg (set help True))                         "Print this help",
    Option ['q']     ["quiet"]              (NoArg (set quiet True))                        "Do not print any log",
    Option ['v']     ["verbose"]            (NoArg (set verbose True))                      "Print detailed logs",
    Option ['V']     ["version"]            (NoArg (set version True))                      "Print version",
    Option ['1']     ["vanilla"]            (NoArg (set vanilla True))                      "Do not read custom configuration file",
    Option ['r']     ["recompile"]          (NoArg (set recompile True))                    "Only recompile configuration",
    Option []        ["force-reconf"]       (NoArg id)                                      "Recompile configuration before starting the program",
    Option []        ["deny-reconf"]        (NoArg id)                                      "Do not recompile configuration even if it has changed",
    Option []        ["dyre-debug"]         (NoArg id)                                      "Use './cache/' as the cache directory and ./ as the configuration directory. Useful to debug the program"]

-- | Usage text (cf @hbro -h@)
usage :: String
usage = usageInfo "Usage: imm [OPTIONS] [URI]\n\nConvert items from RSS/Atom feeds to maildir entries. If one or more URI(s) are given, they will be processed instead of the feeds list from configuration\n" description

-- | Get and parse commandline options
get :: (MonadBase IO m) => m CliOptions
get = io $ do
    options <- getOpt' Permute description <$> getArgs
    case options of
        (opts, input, _, []) -> do
            let (errors, valids) = partitionEithers $ map (\uri -> maybe (Left $ "Invalid URI given in commandline: " ++ uri) Right $ N.parseURI uri) input
            when (not $ null errors) $ io . putStrLn $ unlines errors
            return $ set feedList valids (foldl (flip id) def opts)
        (_, _, _, _)         -> return def

-- | Print logs with arbitrary importance
log, logE, logV :: (MonadBase IO m, OptionsReader m) => String -> m ()
log  = whenM (not <$> readOptions quiet) . io . putStrLn
logE = whenM (not <$> readOptions quiet) . io . hPutStr stderr
logV = whenM (readOptions verbose) . io . putStrLn


getStateDirectory :: (OptionsReader m, MonadBase IO m) => m FilePath
getStateDirectory = do
    stateFromOptions <- readOptions stateDirectory
    case stateFromOptions of
        Just x -> return x
        _      -> getUserConfigDir "imm" >/> "state"