hbro-1.2.0.0: library/Hbro/Options.hs
{-# LANGUAGE TemplateHaskell, ViewPatterns #-}
-- | Commandline options tools.
module Hbro.Options (
-- * Types
CliOptions()
, startURIL
, socketPathL
, dyreModeL
, uiFileL
, dyreDebugL
-- * Util
, parseOptions
, usage
, getSocketURI
) where
-- {{{ Imports
import qualified Hbro.Dyre as Dyre
import Hbro.Error
import Hbro.Logger as Logger
import Hbro.Prelude
import Control.Lens.Getter
import Control.Lens.Lens
import Control.Lens.Setter
import Control.Lens.TH
import Data.Version
import Filesystem
import Network.URI.Monadic
import Paths_hbro
import System.Console.GetOpt
import System.Posix.Process
import qualified System.ZMQ4 as ZMQ (version)
import Text.Read hiding(get, lift)
-- }}}
-- {{{ Types
-- | Available commandline options (cf @hbro -h@).
data CliOptions = CliOptions
{ _startURI :: Maybe URI
, _socketPath :: Maybe FilePath
, _uiFile :: Maybe FilePath
, _dyreMode :: Dyre.Mode
, _dyreDebug :: Bool
} deriving(Eq)
makeLensesWith ?? ''CliOptions $ lensRules
& lensField .~ (\name -> Just (tail name ++ "L"))
instance Describable CliOptions where
describe opts = unwords $ catMaybes
[ ("URI=" ++) . tshow <$> (opts^.startURIL)
, ("SOCKET=" ++) . fpToText <$> (opts^.socketPathL)
, ("UI_FILE=" ++) . fpToText <$> (opts^.uiFileL)
, Just . ("DYRE_MODE=" ++) . tshow $ opts^.dyreModeL
, Just "DYRE_DEBUG" <| opts^.dyreDebugL |> Nothing
]
instance Default CliOptions where
def = CliOptions
{ _startURI = Nothing
, _socketPath = Nothing
, _uiFile = Nothing
, _dyreMode = def
, _dyreDebug = False
}
-- }}}
-- {{{ Util
action :: (BaseIO m, MonadPlus m) => m () -> ArgDescr (a -> m b)
action f = NoArg . const $ f >> abort
flag :: (Monad m) => Lens' CliOptions a -> a -> ArgDescr (CliOptions -> m CliOptions)
flag attribute value = NoArg $ return . set attribute value
optional :: (Monad m, IsString s) => Lens' CliOptions (Maybe s) -> String -> ArgDescr (CliOptions -> m CliOptions)
optional attribute name = ReqArg (\x -> return . set attribute (Just $ fromString x)) name
-- }}}
description :: (BaseIO m, MonadPlus m) => [OptDescr (CliOptions -> m CliOptions)]
description =
-- Action
[ Option ['h'] ["help"] (action help)
"Print this help."
, Option ['r'] ["recompile"] (action recompile)
"Recompile configuration file and exit."
, Option ['V'] ["version"] (action printVersion)
"Print version."
-- Log level
, Option ['v'] ["verbose"] (setLogLevel DEBUG)
"Equivalent to -l DEBUG."
, Option ['q'] ["quiet"] (setLogLevel ERROR)
"Equivalent to -l ERROR."
, Option ['l'] ["log-level"] (ReqArg setCustomLogLevel "VALUE")
"Set log level. VALUE may be one of DEBUG|INFO|NOTICE|WARNING|ERROR|CRITICAL|ALERT|EMERGENCY."
-- Paths
, Option ['u'] ["uri"] (ReqArg setStartURI "URI")
"URI to load at start-up."
, Option ['S'] ["socket"] (optional socketPathL "PATH")
"Where to open IPC socket"
, Option ['U'] ["ui"] (optional uiFileL "PATH")
"Path to UI descriptor (XML file)"
-- Dynamic reconfiguration
, Option ['1'] ["vanilla"] (flag dyreModeL Dyre.Vanilla)
"Do not read custom configuration file"
, Option [] ["force-reconf"] (flag dyreModeL Dyre.ForceReconfiguration)
"Recompile configuration file before starting the program"
, Option [] ["dyre-master-binary"] (ReqArg (const return) "PATH")
"Internal flag used for dynamic reconfiguration."
, Option [] ["deny-reconf"] (flag dyreModeL Dyre.IgnoreReconfiguration)
"Do not recompile configuration file even if it has changed"
, Option [] ["dyre-debug"] (NoArg return)
"Use './cache/' as the cache directory and ./ as the configuration directory. Useful to debug the program."
]
help, printVersion, recompile :: (BaseIO m, MonadPlus m) => m ()
help = putStrLn usage
recompile = Dyre.recompile >>= mapM_ putStrLn
printVersion = do
(a, b, c) <- io ZMQ.version
putStrLn $ "hbro: v" ++ pack (showVersion version)
putStrLn $ "0MQ library: v" ++ intercalate "." (map tshow [a, b, c])
setLogLevel :: (BaseIO m, MonadPlus m) => Priority -> ArgDescr (a -> m a)
setLogLevel priority = NoArg $ \options -> do
Logger.initialize priority
return options
setCustomLogLevel :: (BaseIO m) => String -> (a -> m a)
setCustomLogLevel level options = do
maybe (errorM "hbro.options" message) Logger.initialize $ readMaybe level
return options
where message = "Invalid log level '" ++ pack level ++ "'."
-- | Parse URI passed in commandline, check whether it is a file path or an internet URI
-- and return the corresponding normalized URI (that is: prefixed with "file://" or "http://")
setStartURI :: (BaseIO m, MonadPlus m) => String -> (CliOptions -> m CliOptions)
setStartURI (pack -> uri) options = do
fileURI <- io . isFile $ fpFromText uri
workingDir <- io getWorkingDirectory
let fileURI' = ("file://" ++ fpToText workingDir ++ "/" ++ uri) <| fileURI |> uri
parsedURI <- maybe abort return =<< logErrors (parseURIReference fileURI')
return $ set startURIL (Just parsedURI) options
-- | Usage text (cf @hbro -h@)
usage :: Text
usage = pack $ usageInfo "Usage: hbro [OPTIONS]" (description :: [OptDescr (CliOptions -> IO CliOptions)])
-- | Get and parse commandline options
parseOptions :: (BaseIO m, MonadPlus m) => m CliOptions
parseOptions = do
Logger.initialize INFO
(opts, input', unknown', errors') <- io $ getOpt' Permute description . map unpack <$> getArgs
let (input, unknown, errors) = (tailSafe $ (pack <$> input'), pack <$> unknown', pack <$> errors')
unless (null errors) $ errorM "hbro.options" (unlines errors) >> abort
unless (null unknown) . infoM "hbro.options" $ "Unrecognized options: " ++ unwords unknown
unless (null input) . infoM "hbro.options" $ "Ignored input: " ++ unlines input
options <- foldl' (>>=) (return def) opts
debugM "hbro.options" $ "Used options: " ++ describe options
return options
-- | Return socket URI used by this instance
getSocketURI :: (BaseIO m) => CliOptions -> m Text
getSocketURI options = maybe getDefaultSocketURI (return . normalize) $ options^.socketPathL
where
normalize = ("ipc://" ++) . fpToText
getDefaultSocketURI = do
dir <- fpToText <$> (io $ getAppCacheDirectory "hbro")
pid <- io getProcessID
return $ "ipc://" ++ dir ++ "/hbro." ++ tshow pid