hpc-codecov-0.3.0.0: src/Trace/Hpc/Codecov/Options.hs
{-# OPTIONS_HADDOCK hide #-}
-- |
-- Module: Trace.Hpc.Codecov.Options
-- Copyright: (c) 2020 8c6794b6
-- License: BSD3
-- Maintainer: 8c6794b6 <8c6794b6@gmail.com>
--
-- Command line options for generating Codecov test coverage report.
module Trace.Hpc.Codecov.Options
(
-- * The Options type and predefined values
Options(..)
, defaultOptions
, emptyOptions
-- * Command line parser for 'Options'
, parseOptions
-- * Converter
, opt2rpt
-- * Help message and version number
, printHelp
, printVersion
, versionString
) where
-- base
import Control.Exception (throw, throwIO)
import Data.Version (showVersion)
import System.Console.GetOpt (ArgDescr (..), ArgOrder (..),
OptDescr (..), getOpt, usageInfo)
import System.Environment (getProgName)
-- directory
import System.Directory (doesFileExist)
-- Internal
import Paths_hpc_codecov (version)
import Trace.Hpc.Codecov.Discover
import Trace.Hpc.Codecov.Exception
import Trace.Hpc.Codecov.Report
-- | Options for generating test coverage report.
data Options = Options
{ optTix :: FilePath
-- ^ Input tix file.
, optMixDirs :: [FilePath]
-- ^ Directory containing mix files referred by the tix file.
, optSrcDirs :: [FilePath]
-- ^ Directory containing source codes referred by the mix files.
, optExcludes :: [String]
-- ^ Module name strings to exclude from coverage report.
, optOutFile :: Maybe FilePath
-- ^ Output file to write JSON report, if given.
, optVerbose :: Bool
-- ^ Flag for showing verbose message during coverage report
-- generation.
, optRootDir :: FilePath
-- ^ Project root directory for the build tool.
, optBuildDir :: Maybe FilePath
-- ^ Name of the build directory used by the build tool
, optSkipDirs :: [String]
-- ^ Directories to ignore while discovering.
, optShowVersion :: Bool
-- ^ Flag for showing version.
, optShowNumeric :: Bool
-- ^ Flag for showing numeric version.
, optShowHelp :: Bool
-- ^ Flag for showing help message.
}
-- | Empty 'Options'.
emptyOptions :: Options
emptyOptions = Options
{ optTix = throw NoTarget
, optMixDirs = []
, optSrcDirs = []
, optExcludes = []
, optOutFile = Nothing
, optVerbose = False
, optRootDir = ""
, optBuildDir = Nothing
, optSkipDirs = []
, optShowVersion = False
, optShowNumeric = False
, optShowHelp = False
}
-- | The default 'Options'.
defaultOptions :: Options
defaultOptions = emptyOptions
{ optMixDirs = [".hpc"]
, optSrcDirs = [""]
}
-- | Commandline option oracle.
options :: [OptDescr (Options -> Options)]
options =
[ Option ['m'] ["mix"]
(ReqArg (\d o -> o {optMixDirs = uncommas d ++ optMixDirs o})
"DIR")
".mix file directory, can repeat\n\
\(default: .hpc)"
, Option ['s'] ["src"]
(ReqArg (\d o -> o {optSrcDirs = uncommas d ++ optSrcDirs o})
"DIR")
"Source directory, can repeat\n\
\(default: current directory)"
, Option ['x'] ["exclude"]
(ReqArg (\m o -> o {optExcludes = uncommas m ++ optExcludes o})
"MODULE")
"Module name to exclude, can repeat"
, Option ['o'] ["out"]
(ReqArg (\p o -> o {optOutFile = Just p}) "FILE")
"Output file\n\
\(default: stdout)"
, Option ['r'] ["root"]
(ReqArg (\d o -> o {optRootDir = d})
"DIR")
"Project root directory for TOOL\n\
\Usually the directory containing\n\
\'stack.yaml' or 'cabal.project'\n\
\(default: current directory)"
, Option ['b'] ["build"]
(ReqArg (\d o -> o {optBuildDir = Just d})
"DIR")
"Build directory made by the TOOL\n\
\(default:\n\
\ - '.stack-work' for stack\n\
\ - 'dist-newstyle' for cabal)"
, Option ['X'] ["skip"]
(ReqArg (\d o -> o {optSkipDirs = uncommas d ++ optSkipDirs o})
"DIR")
"Basename of directory to skip while\n\
\searching data for TOOL, can repeat"
, Option ['v'] ["verbose"]
(NoArg (\o -> o {optVerbose = True}))
"Show verbose output"
, Option [] ["version"]
(NoArg (\o -> o {optShowVersion = True}))
"Show versoin and exit"
, Option [] ["numeric-version"]
(NoArg (\o -> o {optShowNumeric = True}))
"Show numeric version and exit"
, Option ['h'] ["help"]
(NoArg (\o -> o {optShowHelp = True}))
"Show this help and exit"
]
-- | Parse command line argument and return either error messages or
-- parsed 'Options'.
parseOptions :: [String] -- ^ Command line argument strings.
-> Either [String] Options
parseOptions args =
case getOpt Permute options args of
(flags, rest, []) ->
-- Not returning error messages with missing ".tix" file
-- argument at this point, to show help and version messages
-- without specifying ".tix" file.
let opts0 = foldr ($) emptyOptions flags
opts1 = fillDefaultIfNotGiven opts0
in case rest of
[] -> Right opts1
(tix:_) -> Right (opts1 {optTix = tix})
(_, _, errs) -> Left errs
fillDefaultIfNotGiven :: Options -> Options
fillDefaultIfNotGiven opts = opts
{ optMixDirs = fillIf null optMixDirs
, optSrcDirs = fillIf null optSrcDirs
}
where
fillIf test fld =
let orig = fld opts
in if test orig
then fld defaultOptions
else orig
-- | Representation of @TARGET@ argument.
data Target
= TixFile FilePath
| TestSuite BuildTool String
parseTarget :: String -> IO Target
parseTarget str = do
-- Detecting file existence before separating with ':', to support
-- directory path containing ':' under Windows.
file_found <- doesFileExist str
if file_found
then pure $ TixFile str
else case break (== ':') str of
("cabal", ':':name) -> pure $ TestSuite Cabal name
("stack", ':':name) -> pure $ TestSuite Stack name
(tool, ':':_) -> throwIO $ InvalidBuildTool tool
_ -> pure $ TixFile str
uncommas :: String -> [String]
uncommas = go
where
go str = case break (== ',') str of
(cs, ',':rest) -> cs : go rest
(cs, _) -> [cs]
-- | Make a 'Report' value from 'Optoins'.
opt2rpt :: Options -> IO Report
opt2rpt opt = do
let rpt1 = mempty
{ reportMixDirs = optMixDirs opt
, reportSrcDirs = optSrcDirs opt
, reportExcludes = optExcludes opt
, reportOutFile = optOutFile opt
, reportVerbose = verbose
}
tix = optTix opt
verbose = optVerbose opt
target <- parseTarget tix
case target of
TixFile path -> pure (rpt1 {reportTix = path})
TestSuite tool name -> do
rpt2 <- discover DiscoverArgs
{ da_tool = tool
, da_testsuite = name
, da_rootdir = optRootDir opt
, da_builddir = optBuildDir opt
, da_skipdirs = optSkipDirs opt
, da_verbose = verbose
}
pure $ rpt1 `mappend` rpt2
-- | Print help messages.
printHelp :: IO ()
printHelp = getProgName >>= putStrLn . helpMessage
-- | Print version number of this package.
printVersion :: IO ()
printVersion =
do me <- getProgName
putStrLn (me ++ " version " ++ versionString)
-- | Help message for command line output.
helpMessage :: String -- ^ Executable program name.
-> String
helpMessage name = usageInfo header options ++ footer
where
header = "USAGE: " ++ name ++ " [OPTIONS] TARGET\n\
\\n\
\Generate Codecov JSON coverage report for Haskell source codes\n\
\from .tix and .mix files made with hpc.\n\
\\n\
\TARGET is either a path to .tix file or 'TOOL:TEST_SUITE'.\n\
\Supported TOOL values are 'stack' and 'cabal'. When the TOOL is\n\
\'stack' and building project with multiple packages, use 'all' as\n\
\TEST_SUITE value to refer the combined report.\n\
\\n\
\OPTIONS:\n"
footer = "\
\\n\
\For more info, see:\n\
\\n\
\ https://github.com/8c6794b6/hpc-codecov#readme\n\
\\n"
-- | String representation of the version number of this package.
versionString :: String
versionString = showVersion version