#!/usr/bin/env runhaskell ----------------------------------------------------------------------------- -- | -- Module : mkrr.hs -- -- Maintainer : adam.smyczek@gmail.com -- Stability : experimental -- Portability : portable -- -- @mkrr@ is a sample application demonstrating the usage of ReviewBoard bindings. -- Using @mkrr@ new review requests can be submitted as easy as executing: -- -- > svn diff | mkrr -r [reviewers] -- -- from the local repository copy. Most of the required/optional parameters -- may be pre-defined in @~/.mkrrrc@ config file. The supported parameters are: -- -- * url - ReviewBoard server URL -- -- * user - User name -- -- * password - Password -- -- * repository - Repository path, e.g. -- -- * basedir - Diff base dir, e.g. @/trunk@ in case @svn diff@ was executed -- in @/trunk@ directory -- -- * publish - Publish immediately -- -- Run @mkrr --help@ and see ReviewBoard documentation at -- . -- ----------------------------------------------------------------------------- module Main (main) where import System.Environment import System.Console.GetOpt import System.IO import System.Directory import Control.Exception(finally) import Data.List import Data.Maybe import Data.Char import ReviewBoard.Api import Control.Monad.Error import qualified ReviewBoard.Response as R import System.Exit -- | Main -- main :: IO () main = do -- Parse and validate command line options (opts, _) <- getArgs >>= parseOpts validateRequired opts -- Create the review request action ... let action = mkrrAction (fromJust . optRepository $ opts) (fromJust . optBasedir $ opts) (fromJust . optReviewers $ opts) (optDescription opts) (optSummary opts) (optScreenshot opts) (optPublish opts) -- ... and run it execAction (fromJust . optUrl $ opts) (fromJust . optUser $ opts) (fromJust . optPassword $ opts) action -- | Run new review request action -- execAction :: String -> String -> String -> (String -> RBAction ()) -> IO () execAction url user pass action = do -- Get diff from stdin diff <- hGetContents stdin -- Write diff to temp file td <- catch (getTemporaryDirectory) (\_ -> return ".") (tfn, tfh) <- openTempFile td "rbDiff.diff" mapM (hPutStrLn tfh) $ lines diff hClose tfh -- Run action and delete temp file finally (execRBAction url user pass $ action tfn) (do removeFile tfn) -- | New review request action -- mkrrAction :: String -> String -> String -> Maybe String -> Maybe String -> Maybe String -> Bool -> String -> RBAction () mkrrAction rep basedir revs dsc sum ss pub tfn = do -- Create new review request and get the id rsp <- reviewRequestNew rep Nothing case rsp of RBok r -> do let id = R.id . R.review_request $ r -- Set some fields reviewRequestSetField id TARGET_PEOPLE revs setOptional id DESCRIPTION dsc setOptional id SUMMARY sum -- Upload the diff reviewRequestDiffNew id basedir tfn -- Upload screenshot if provided when (isJust ss) (reviewRequestScreenshotNew id (fromJust ss) >> return ()) -- And store the review request draft reviewRequestSaveDraft id -- Publish if enabled when (pub) (httpGet ("/r/" ++ show id ++ "/publish/") [] >> return ()) liftIO $ print "Done." RBerr e -> throwError e where setOptional id f (Just v) = reviewRequestSetField id f v >>= \_ -> return () setOptional id f Nothing = return() -- --------------------------------------------------------------------------- -- Option handling -- | Parse options -- parseOpts :: [String] -> IO (Options, [String]) parseOpts argv = do dos <- defaultOpts case getOpt Permute options argv of (o, n, []) -> foldM (flip id) dos o >>= \os -> return (os, n) (_, _, es) -> ioError (userError (concat es ++ usageInfo header options)) -- | Usage info header -- header = "Usage: mkrr [OPTION...]" -- | Option set -- data Options = Options { optUrl :: Maybe String , optUser :: Maybe String , optPassword :: Maybe String , optRepository :: Maybe String , optBasedir :: Maybe String , optReviewers :: Maybe String , optDescription :: Maybe String , optSummary :: Maybe String , optScreenshot :: Maybe String , optPublish :: Bool } deriving Show -- | Option descriptor list -- options :: [OptDescr (Options -> IO Options)] options = [ Option ['u'] [] (ReqArg (\ u opts -> return opts { optUrl = Just u }) "URL") "URL to ReviewBoard server" , Option ['U'] [] (ReqArg (\ u opts -> return opts { optUser = Just u }) "USER") "USER name" , Option ['P'] [] (ReqArg (\ p opts -> return opts { optPassword = Just p }) "PASSWORD") "User PASSWORD" , Option ['R'] [] (ReqArg (\ r opts -> return opts { optRepository = Just r }) "REROSITORY") "Repository path" , Option ['b'] [] (ReqArg (\ r opts -> return opts { optBasedir = Just r }) "BASEDIR") "Diff base dir" , Option ['r'] [] (ReqArg (\ r opts -> return opts { optReviewers = Just r }) "REVIEWERS") "Comma separated REVIEWERS list" , Option ['d'] [] (ReqArg (\ r opts -> return opts { optDescription = Just r }) "DESCRIPTION") "Optional request description" , Option ['s'] [] (ReqArg (\ s opts -> return opts { optSummary = Just s }) "SUMMARY") "Optional request summary" , Option ['S'] [] (ReqArg (\ f opts -> return opts { optScreenshot = Just f }) "SCREENSHOT") "attach SCREENSHOT" , Option ['l'] [] (NoArg (\ opts -> return opts { optPublish = True })) "Publish review" , Option ['h'] ["help"] (NoArg (\ opts -> putStr (usageInfo header options) >> exitWith ExitSuccess)) "Print this help" ] -- | Load default options from configuration file -- defaultOpts :: IO Options defaultOpts = do hd <- getHomeDirectory cf <- readFile $ hd ++ "/.mkrrrc" return $ foldl parseOpt initOpts $ lines cf where parseOpt os l | isPrefixOf "url" l = os { optUrl = Just $ parseValue l } | isPrefixOf "user" l = os { optUser = Just $ parseValue l } | isPrefixOf "password" l = os { optPassword = Just $ parseValue l } | isPrefixOf "repository" l = os { optRepository = Just $ parseValue l } | isPrefixOf "basedir" l = os { optBasedir = Just $ parseValue l } | isPrefixOf "publish" l = os { optPublish = read $ parseValue l } | otherwise = os parseValue :: String -> String parseValue = takeWhile (not . isSeparator) . dropWhile isSeparator . drop 1 . dropWhile (/= '=') -- | Initial option set -- initOpts :: Options initOpts = Options { optUrl = Nothing , optUser = Nothing , optPassword = Nothing , optRepository = Nothing , optBasedir = Nothing , optReviewers = Nothing , optDescription = Nothing , optSummary = Nothing , optScreenshot = Nothing , optPublish = False } -- | Validate required options -- validateRequired :: Options -> IO () validateRequired opts = do -- TODO: this is imperative, improve this when (isNothing . optUrl $ opts) $ error $ "URL required!" ++ help when (isNothing . optUser $ opts) $ error $ "USER required!" ++ help when (isNothing . optPassword $ opts) $ error $ "PASSWORD required!" ++ help when (isNothing . optRepository $ opts) $ error $ "REPOSITORY required!" ++ help when (isNothing . optBasedir $ opts) $ error $ "BASEDIR required!" ++ help when (isNothing . optReviewers $ opts) $ error $ "REVIEWERS required!" ++ help where help = " Try mkrr --help"