||| command line options module TAP.Options import Data.String import System import System.Console.GetOpt %default total ||| which TAP version to use for output. ||| - `V14` supports subtests ||| - `V13` flattens the tree before running it public export data TAPVersion = V13 | V14 ||| try to read a numeric TAP version number export readVersion : String -> Maybe TAPVersion readVersion "13" = Just V13 readVersion "14" = Just V14 readVersion _ = Nothing ||| prints as just the number export Show TAPVersion where show V13 = "13"; show V14 = "14" ||| command line options ||| ||| apart from these there is also a usage message with ||| `-?`, `-h`, `--help` public export record Options where constructor Opts ||| `-V`, `--version`: ||| which TAP version to output version : TAPVersion ||| `-F`, `--filter`: ||| search for a substring in test or group names. ||| if it is present in a group name then all subtests are run ||| regardless of their own names pattern : Maybe String ||| `-c`, `--color`, `--colour`: ||| colour code test results and a few other things. ||| this is not TAP compliant so it is off by default. color : Bool ||| `-q`, `--skip-comments`: ||| skip printing comments in the output skipComments : Bool ||| default options ||| (version 13 (because of `prove`), no filter, no colour) export defaultOpts : Options defaultOpts = Opts { version = V13, pattern = Nothing, color = False, skipComments = False } ||| value for each option. ||| i'm using the old idiom where each option is a function that updates ||| an accumulated record. with IO because of the error messages being printed public export Mod : Type Mod = Options -> IO Options ||| print the given messages as TAP comments and then say `Bail out!`. ||| so the error is a valid TAP transcript too :3 export failureWith : List String -> IO a failureWith msgs = do traverse_ (\s => putStrLn "# \{s}") msgs putStrLn "\nBail out!" exitFailure private setTapVer : String -> Mod setTapVer ver opts = case readVersion ver of Just v => pure $ {version := v} opts Nothing => failureWith ["unrecognised TAP version '\{ver}'"] private setFilter : String -> Mod setFilter str opts = pure $ {pattern := Just str} opts parameters (header : String) mutual ||| option descriptions export opts : List (OptDescr Mod) opts = [ MkOpt { description = "show this help", shortNames = ['h', '?'], longNames = ["help"], argDescr = NoArg $ const $ failureWith usage }, MkOpt { description = "TAP version to output (13 or 14, default 13)", shortNames = ['V'], longNames = ["version"], argDescr = ReqArg setTapVer "VERSION" }, MkOpt { description = "only run tests containing STR in their group or label", shortNames = ['F'], longNames = ["filter"], argDescr = ReqArg setFilter "STR" }, MkOpt { description = "don't colour-code results (default)", shortNames = ['C'], longNames = ["no-color", "no-colour"], argDescr = NoArg $ pure . {color := False} }, MkOpt { description = "colour-code results (not TAP compliant)", shortNames = ['c'], longNames = ["color", "colour"], argDescr = NoArg $ pure . {color := True} }, MkOpt { description = "skip printing comments in the output", shortNames = ['q'], longNames = ["skip-comments"], argDescr = NoArg $ pure . {skipComments := True} } ] ||| usage message export usage : List String usage = assert_total $ header :: lines (usageInfo "" opts) ||| interpret the result of `getOpt` export makeOpts : List Mod -> IO Options makeOpts = foldlM (\x, f => f x) defaultOpts ||| like `getArgs` but skip the first one, which is the executable name export getArgs1 : IO (List String) getArgs1 = case !getArgs of _ :: args => pure args [] => failureWith ["expected getArgs to start with exe name"] ||| read & interpret the command line arguments. ||| the first argument is the header to print on the usage message. ||| ||| [todo] allow unrecognised things and pass them back out export getTestOpts' : String -> List String -> IO Options getTestOpts' header args = case getOpt Permute (opts header) args of MkResult opts [] [] [] => makeOpts opts res => failureWith $ res.errors ++ usage header ||| interpret some command line arguments passed in ||| ||| [todo] allow unrecognised things and pass them back out export getTestOpts : String -> IO Options getTestOpts header = getTestOpts' header !getArgs1