----------------------------------------------------------------------------- -- | -- Module : Setup -- Copyright : (c) David Himmelstrup 2006 -- License : BSD-like -- -- Maintainer : lemmih@gmail.com -- Stability : provisional -- Portability : portable -- -- ----------------------------------------------------------------------------- module Setup ( parseArguments , emptyConfig ) where import Data.List import Data.Char import System.Console.GetOpt import System.Exit import System.Environment import Config emptyConfig :: Config emptyConfig = Config { confVerbose = 1 , confWGetPath = "wget" , confCurlPath = "curl" , confTidyPath = "tidy" , confDryRun = False } cmd_verbose :: OptDescr Flag cmd_verbose = Option "v" ["verbose"] (OptArg verboseFlag "n") "Control verbosity (n is 0-5, normal verbosity level is 1, -v alone is equivalent to -v3)" where verboseFlag mb_s = Verbose (maybe 3 read mb_s) cmd_dryrun :: OptDescr Flag cmd_dryrun = Option "d" ["dry-run"] (OptArg dryrunFlag "bool") "Dry run. Accept values in the line of 'false', '0' and 'no'. Default: false." where dryrunFlag mb_s = DryRun (maybe True (parse.map toLower) mb_s) parse "false" = False parse "0" = False parse "no" = False parse _ = True cmd_help :: OptDescr Flag cmd_help = Option "h?" ["help"] (NoArg HelpFlag) "Show this help text" globalOptions :: [OptDescr Flag] globalOptions = [ cmd_help , cmd_verbose , cmd_dryrun , Option "w" ["wget-path"] (ReqArg WGetPath "path") "Use this command as wget" , Option "t" ["tidy-path"] (ReqArg WGetPath "path") "Use this command as tidy" ] data Cmd = Cmd { cmdName :: String, cmdHelp :: String, -- Short description cmdDescription :: String, -- Long description cmdOptions :: [OptDescr Flag ], cmdAction :: Action, cmdMerge :: Flag -> Action -> Action } commandList :: [Cmd] commandList = [ parseCmd, renameCmd, informCmd, addCmd, removeCmd ] parseCmd = Cmd { cmdName = "parse" , cmdHelp = "Test the parser" , cmdDescription = "" , cmdOptions = [Option "f" ["format"] (ReqArg ParseFormat "format") ("Output format. Default: "++defaultFormat) ] , cmdAction = Parse defaultFormat , cmdMerge = \(ParseFormat fn) a -> a{parseFormat = fn}} renameCmd = Cmd { cmdName = "rename" , cmdHelp = "Rename TV series" , cmdDescription = "" , cmdOptions = [Option "f" ["format"] (ReqArg ParseFormat "format") ("Output format. Default: "++defaultFormat) ] , cmdAction = Rename defaultFormat , cmdMerge = \(ParseFormat fn) a -> a{parseFormat = fn}} informCmd = Cmd { cmdName = "inform" , cmdHelp = "Gather meta information" , cmdDescription = "" , cmdOptions = [Option "f" ["from-file"] (ReqArg (FromFile . parse) "series@file") ("Fetch info from file.")] , cmdAction = Inform [] , cmdMerge = \f a -> case f of FromFile f -> a{fromFiles = f:fromFiles a}} where parse str = let (s,f) = break (=='@') str in (s,drop 1 f) addCmd = Cmd { cmdName = "add" , cmdHelp = "Add new series. Case matters." , cmdDescription = "" , cmdOptions = [] , cmdAction = Add , cmdMerge = \_ -> id } removeCmd = Cmd { cmdName = "remove" , cmdHelp = "Remove series." , cmdDescription = "" , cmdOptions = [] , cmdAction = Remove , cmdMerge = \_ -> id } lookupCommand :: String -> [Cmd] -> Maybe Cmd lookupCommand name = find ((==name) . cmdName) mkConfig :: Flag -> Config -> Config mkConfig (Verbose n) conf = conf { confVerbose = n } mkConfig (WGetPath wget) conf = conf { confWGetPath = wget } mkConfig (TidyPath tidy) conf = conf { confTidyPath = tidy } mkConfig (DryRun d) conf = conf { confDryRun = d } mkConfig _ _ = error "Setup.mkConfig: Invalid flag" hasHelpFlag :: [Flag] -> Bool hasHelpFlag flags = not . null $ [ () | HelpFlag <- flags ] printUsage :: IO () printUsage = do pname <- getProgName let syntax_line = concat [ "Usage: ", pname , " [GLOBAL FLAGS]\n or: COMMAND [FLAGS] " , "\n\nGlobal flags:"] putStrLn (usageInfo syntax_line globalOptions) putStrLn "Commands:" let maxlen = maximum [ length (cmdName cmd) | cmd <- commandList ] sequence_ [ do putStr " " putStr (align maxlen (cmdName cmd)) putStr " " putStrLn (cmdHelp cmd) | cmd <- commandList ] putStrLn $ "\nFor more information about a command, try '" ++ pname ++ " COMMAND --help'." where align n str = str ++ replicate (n - length str) ' ' printCmdUsage :: Cmd -> IO () printCmdUsage cmd = do pname <- getProgName let syntax_line = "Usage: " ++ pname ++ " " ++ cmdName cmd ++ " [FLAGS]\n\nFlags for " ++ cmdName cmd ++ ":" putStrLn (usageInfo syntax_line (cmd_help:cmdOptions cmd)) putStrLn (cmdDescription cmd) parseArguments :: [String] -> IO (Action, [String], Config) parseArguments args = case getOpt' RequireOrder globalOptions args of (flags, _, _, []) | hasHelpFlag flags -> do printUsage exitWith ExitSuccess (flags, cname:cargs, _, []) -> case lookupCommand cname commandList of Just cmd -> case getOpt' Permute (cmd_help:cmdOptions cmd) cargs of (flags, _, _, []) | hasHelpFlag flags -> do printCmdUsage cmd exitWith ExitSuccess (cmdflags, args, _, []) -> do return ( foldr (cmdMerge cmd) (cmdAction cmd) cmdflags , args , foldr mkConfig emptyConfig flags) Nothing -> do putStrLn $ "Unrecognised command: " ++ cname exitWith (ExitFailure 1) (_, [], _, []) -> do putStrLn $ "No command given (try --help)" exitWith (ExitFailure 1) (_, _, _, errs) -> do putStrLn "Errors:" mapM_ putStrLn errs exitWith (ExitFailure 1)