module Ganeti.Common
( GenericOptType
, StandardOptions(..)
, OptCompletion(..)
, ArgCompletion(..)
, PersonalityList
, optComplYesNo
, oShowHelp
, oShowVer
, oShowComp
, usageHelp
, versionInfo
, formatCommands
, reqWithConversion
, parseYesNo
, parseOpts
, parseOptsInner
, parseOptsCmds
, genericMainCmds
) where
import Control.Monad (foldM)
import Data.Char (toLower)
import Data.List (intercalate, stripPrefix, sortBy)
import Data.Maybe (fromMaybe)
import Data.Ord (comparing)
import qualified Data.Version
import System.Console.GetOpt
import System.Environment
import System.Exit
import System.Info
import System.IO
import Text.Printf (printf)
import Ganeti.BasicTypes
import qualified Ganeti.Constants as C
import Ganeti.Utils (wrap)
import qualified Ganeti.Version as Version (version)
data OptCompletion = OptComplNone
| OptComplFile
| OptComplDir
| OptComplHost
| OptComplInetAddr
| OptComplOneNode
| OptComplManyNodes
| OptComplOneInstance
| OptComplManyInstances
| OptComplOneOs
| OptComplOneIallocator
| OptComplInstAddNodes
| OptComplOneGroup
| OptComplInteger
| OptComplFloat
| OptComplJobId
| OptComplCommand
| OptComplString
| OptComplChoices [String]
| OptComplSuggest [String]
deriving (Show, Eq)
data ArgCompletion = ArgCompletion OptCompletion Int (Maybe Int)
deriving (Show, Eq)
type Personality a = ( a -> [String] -> IO ()
, IO [GenericOptType a]
, [ArgCompletion]
, String
)
type PersonalityList a = [(String, Personality a)]
optComplYesNo :: OptCompletion
optComplYesNo = OptComplChoices ["yes", "no"]
complToText :: OptCompletion -> String
complToText (OptComplChoices choices) = "choices=" ++ intercalate "," choices
complToText (OptComplSuggest choices) = "suggest=" ++ intercalate "," choices
complToText compl =
let show_compl = show compl
stripped = stripPrefix "OptCompl" show_compl
in map toLower $ fromMaybe show_compl stripped
argComplToText :: ArgCompletion -> String
argComplToText (ArgCompletion optc min_cnt max_cnt) =
complToText optc ++ " " ++ show min_cnt ++ " " ++ maybe "none" show max_cnt
type GenericOptType a = (OptDescr (a -> Result a), OptCompletion)
class StandardOptions a where
helpRequested :: a -> Bool
verRequested :: a -> Bool
compRequested :: a -> Bool
requestHelp :: a -> a
requestVer :: a -> a
requestComp :: a -> a
oShowHelp :: (StandardOptions a) => GenericOptType a
oShowHelp = (Option "h" ["help"] (NoArg (Ok . requestHelp)) "show help",
OptComplNone)
oShowVer :: (StandardOptions a) => GenericOptType a
oShowVer = (Option "V" ["version"] (NoArg (Ok . requestVer))
"show the version of the program",
OptComplNone)
oShowComp :: (StandardOptions a) => GenericOptType a
oShowComp =
(Option "" ["help-completion"] (NoArg (Ok . requestComp) )
"show completion info", OptComplNone)
usageHelp :: String -> [GenericOptType a] -> String
usageHelp progname =
usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
progname Version.version progname) . map fst
versionInfo :: String -> String
versionInfo progname =
printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
progname Version.version compilerName
(Data.Version.showVersion compilerVersion)
os arch
completionInfo :: String -> [GenericOptType a] -> [ArgCompletion] -> String
completionInfo _ opts args =
unlines $
map (\(Option shorts longs _ _, compinfo) ->
let all_opts = map (\c -> ['-', c]) shorts ++ map ("--" ++) longs
in intercalate "," all_opts ++ " " ++ complToText compinfo
) opts ++
map argComplToText args
parseYesNo :: Bool
-> Maybe String
-> Result Bool
parseYesNo v Nothing = return v
parseYesNo _ (Just "yes") = return True
parseYesNo _ (Just "no") = return False
parseYesNo _ (Just s) = fail ("Invalid choice '" ++ s ++
"', pass one of 'yes' or 'no'")
reqWithConversion :: (String -> Result a)
-> (a -> b -> Result b)
-> String
-> ArgDescr (b -> Result b)
reqWithConversion conversion_fn updater_fn =
ReqArg (\string_opt opts -> do
parsed_value <- conversion_fn string_opt
updater_fn parsed_value opts)
maxCmdLen :: Int
maxCmdLen = 60
formatCommands :: (StandardOptions a) => PersonalityList a -> [String]
formatCommands personalities =
concatMap (\(cmd, (_, _, _, desc)) ->
fmtDesc cmd (wrap maxWidth desc) "-" []) $
sortBy (comparing fst) personalities
where mlen = min maxCmdLen . maximum $ map (length . fst) personalities
maxWidth = 79 3 mlen
fmtDesc _ [] _ acc = reverse acc
fmtDesc cmd (d : ds) sep acc =
fmtDesc "" ds " " (printf " %-*s %s %s" mlen cmd sep d : acc)
formatCmdUsage :: (StandardOptions a) => String -> PersonalityList a -> String
formatCmdUsage prog personalities =
let header = [ printf "Usage: %s {command} [options...] [argument...]" prog
, printf "%s <command> --help to see details, or man %s"
prog prog
, ""
, "Commands:"
]
rows = formatCommands personalities
in unlines $ header ++ rows
showCmdUsage :: (StandardOptions a) =>
String
-> PersonalityList a
-> Bool
-> IO b
showCmdUsage prog personalities success = do
let usage = formatCmdUsage prog personalities
putStr usage
if success
then exitSuccess
else exitWith $ ExitFailure C.exitFailure
multiCmdCompletion :: (StandardOptions a) => PersonalityList a -> String
multiCmdCompletion personalities =
argComplToText $
ArgCompletion (OptComplChoices (map fst personalities))
1 (Just 1)
showCmdCompletion :: (StandardOptions a) => PersonalityList a -> IO b
showCmdCompletion personalities =
putStrLn (multiCmdCompletion personalities) >> exitSuccess
parseOpts :: (StandardOptions a) =>
a
-> [String]
-> String
-> [GenericOptType a]
-> [ArgCompletion]
-> IO (a, [String])
parseOpts defaults argv progname options arguments =
case parseOptsInner defaults argv progname options arguments of
Left (code, msg) -> do
hPutStr (if code == ExitSuccess then stdout else stderr) msg
exitWith code
Right result ->
return result
parseOptsCmds :: (StandardOptions a) =>
a
-> [String]
-> String
-> PersonalityList a
-> [GenericOptType a]
-> IO (a, [String], a -> [String] -> IO ())
parseOptsCmds defaults argv progname personalities genopts = do
let usage = showCmdUsage progname personalities
check c = case c of
"--version" -> putStrLn (versionInfo progname) >> exitSuccess
"--help" -> usage True
"--help-completion" -> showCmdCompletion personalities
_ -> return c
(cmd, cmd_args) <- case argv of
cmd:cmd_args -> do
cmd' <- check cmd
return (cmd', cmd_args)
[] -> usage False
case cmd `lookup` personalities of
Nothing -> usage False
Just (mainfn, optdefs, argdefs, _) -> do
optdefs' <- optdefs
(opts, args) <- parseOpts defaults cmd_args progname
(optdefs' ++ genopts) argdefs
return (opts, args, mainfn)
parseOptsInner :: (StandardOptions a) =>
a
-> [String]
-> String
-> [GenericOptType a]
-> [ArgCompletion]
-> Either (ExitCode, String) (a, [String])
parseOptsInner defaults argv progname options arguments =
case getOpt Permute (map fst options) argv of
(opts, args, []) ->
case foldM (flip id) defaults opts of
Bad msg -> Left (ExitFailure 1,
"Error while parsing command line arguments:\n"
++ msg ++ "\n")
Ok parsed ->
select (Right (parsed, args))
[ (helpRequested parsed,
Left (ExitSuccess, usageHelp progname options))
, (verRequested parsed,
Left (ExitSuccess, versionInfo progname))
, (compRequested parsed,
Left (ExitSuccess, completionInfo progname options
arguments))
]
(_, _, errs) ->
Left (ExitFailure 2, "Command line error: " ++ concat errs ++ "\n" ++
usageHelp progname options)
genericMainCmds :: (StandardOptions a) =>
a
-> PersonalityList a
-> [GenericOptType a]
-> IO ()
genericMainCmds defaults personalities genopts = do
cmd_args <- getArgs
prog <- getProgName
(opts, args, fn) <-
parseOptsCmds defaults cmd_args prog personalities genopts
fn opts args