Skip to content

Commit a2d3778

Browse files
committed
Merge pull request #1534 from harendra-kumar/error-messages
Provide better error messages for external command and interpreter errors
2 parents c813c83 + 5b6a1dc commit a2d3778

File tree

2 files changed

+72
-33
lines changed

2 files changed

+72
-33
lines changed

src/Options/Applicative/Complicated.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ complicatedParser commonParser commandParser =
132132
hsubparser' :: Mod CommandFields a -> Parser a
133133
hsubparser' m = mkParser d g rdr
134134
where
135-
Mod _ d g = m `mappend` metavar "COMMAND"
135+
Mod _ d g = m `mappend` metavar "COMMAND|FILE"
136136
(cmds, subs) = mkCommand m
137137
rdr = CmdReader cmds (fmap add_helper . subs)
138138
add_helper pinfo = pinfo

src/main/Main.hs

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ module Main (main) where
1414
import Control.Exception
1515
import qualified Control.Exception.Lifted as EL
1616
import Control.Monad hiding (mapM, forM)
17-
import Control.Monad.Catch (MonadThrow)
1817
import Control.Monad.IO.Class
1918
import Control.Monad.Logger
2019
import Control.Monad.Reader (ask, asks, runReaderT)
@@ -47,7 +46,7 @@ import GHC.IO.Encoding (mkTextEncoding, textEncodingName)
4746
import Network.HTTP.Client
4847
import Options.Applicative
4948
import Options.Applicative.Args
50-
import Options.Applicative.Help(errorHelp,stringChunk)
49+
import Options.Applicative.Help (errorHelp, stringChunk, vcatChunks)
5150
import Options.Applicative.Builder.Extra
5251
import Options.Applicative.Complicated
5352
#ifdef USE_GIT_INFO
@@ -94,7 +93,7 @@ import qualified System.Directory as Directory (findExecutable)
9493
import System.Environment (getEnvironment, getProgName, getArgs, withArgs)
9594
import System.Exit
9695
import System.FileLock (lockFile, tryLockFile, unlockFile, SharedExclusive(Exclusive), FileLock)
97-
import System.FilePath (searchPathSeparator)
96+
import System.FilePath (pathSeparator, searchPathSeparator)
9897
import System.IO (hIsTerminalDevice, stderr, stdin, stdout, hSetBuffering, BufferMode(..), hPutStrLn, Handle, hGetEncoding, hSetEncoding)
9998
import System.Process.Read
10099

@@ -167,6 +166,12 @@ main = do
167166
printExceptionStderr e
168167
exitFailure
169168

169+
-- Vertically combine only the error component of the first argument with the
170+
-- error component of the second.
171+
vcatErrorHelp :: ParserHelp -> ParserHelp -> ParserHelp
172+
vcatErrorHelp (ParserHelp e1 _ _ _ _) (ParserHelp e2 h2 u2 b2 f2) =
173+
ParserHelp (vcatChunks [e2, e1]) h2 u2 b2 f2
174+
170175
commandLineHandler
171176
:: String
172177
-> Bool
@@ -181,19 +186,29 @@ commandLineHandler progName isInterpreter = complicatedOptions
181186
(addCommands (globalOpts True) isInterpreter)
182187
where
183188
failureCallback f args =
184-
case stripPrefix "Invalid argument" (fst (renderFailure f "")) of
185-
Just _ -> if isInterpreter
186-
then handleParseResult (Failure f)
187-
else secondaryCommandHandler args
188-
>>= maybe (interpreterHandler f args) id
189-
Nothing -> handleParseResult (Failure f)
189+
case stripPrefix "Invalid argument" (fst (renderFailure f "")) of
190+
Just _ -> if isInterpreter
191+
then parseResultHandler args f
192+
else secondaryCommandHandler args f
193+
>>= interpreterHandler args
194+
Nothing -> parseResultHandler args f
195+
196+
parseResultHandler args f =
197+
if isInterpreter
198+
then do
199+
let hlp = errorHelp $ stringChunk
200+
(unwords ["Error executing interpreter command:"
201+
, progName
202+
, unwords args])
203+
handleParseResult (overFailure (vcatErrorHelp hlp) (Failure f))
204+
else handleParseResult (Failure f)
190205

191206
globalOpts hide =
192-
extraHelpOption hide progName (Docker.dockerCmdName ++ "*") Docker.dockerHelpOptName <*>
193-
extraHelpOption hide progName (Nix.nixCmdName ++ "*") Nix.nixHelpOptName <*>
194-
globalOptsParser hide (if isInterpreter
195-
then Just $ LevelOther "silent"
196-
else Nothing)
207+
extraHelpOption hide progName (Docker.dockerCmdName ++ "*") Docker.dockerHelpOptName <*>
208+
extraHelpOption hide progName (Nix.nixCmdName ++ "*") Nix.nixHelpOptName <*>
209+
globalOptsParser hide (if isInterpreter
210+
then Just $ LevelOther "silent"
211+
else Nothing)
197212

198213
globalFooter :: String
199214
globalFooter = "Run 'stack --help' for global options that apply to all subcommands."
@@ -442,42 +457,66 @@ addCommands globalOpts isInterpreter = do
442457
addSubCommands cmd title globalFooter globalOpts
443458

444459
secondaryCommandHandler
445-
:: (MonadIO m, MonadThrow m, MonadBaseControl IO m)
446-
=> [String]
447-
-> IO (Maybe (m a))
460+
:: [String]
461+
-> ParserFailure ParserHelp
462+
-> IO (ParserFailure ParserHelp)
448463

449464
-- fall-through to external executables in `git` style if they exist
450465
-- (i.e. `stack something` looks for `stack-something` before
451466
-- failing with "Invalid argument `something'")
452-
secondaryCommandHandler args = do
467+
secondaryCommandHandler args f =
468+
-- don't even try when the argument looks like a path
469+
if elem pathSeparator cmd
470+
then return f
471+
else do
472+
mExternalExec <- Directory.findExecutable cmd
473+
case mExternalExec of
474+
Just ex -> do
475+
menv <- getEnvOverride buildPlatform
476+
-- TODO show the command in verbose mode
477+
-- hPutStrLn stderr $ unwords $
478+
-- ["Running", "[" ++ ex, unwords (tail args) ++ "]"]
479+
_ <- runNoLoggingT (exec menv ex (tail args))
480+
return f
481+
Nothing -> return $ fmap (vcatErrorHelp (noSuchCmd cmd)) f
482+
where
453483
-- FIXME this is broken when any options are specified before the command
454484
-- e.g. stack --verbosity silent cmd
455-
mExternalExec <- Directory.findExecutable ("stack-" ++ head args)
456-
case mExternalExec of
457-
Just ex -> do
458-
menv <- getEnvOverride buildPlatform
459-
return (Just $ runNoLoggingT (exec menv ex (tail args)))
460-
Nothing -> return Nothing
485+
cmd = stackProgName ++ "-" ++ (head args)
486+
noSuchCmd name = errorHelp $ stringChunk
487+
("Auxiliary command not found in path `" ++ name ++ "'")
461488

462489
interpreterHandler
463490
:: Monoid t
464-
=> ParserFailure ParserHelp
465-
-> [String]
491+
=> [String]
492+
-> ParserFailure ParserHelp
466493
-> IO (GlobalOptsMonoid, (GlobalOpts -> IO (), t))
467-
interpreterHandler f args = do
468-
let file = head args
494+
interpreterHandler args f = do
469495
isFile <- doesFileExist file
470496
if isFile
471497
then runInterpreterCommand file
472-
else parseResultHandler (flip mappend (noSuchFile file))
498+
else parseResultHandler (errorCombine (noSuchFile file))
473499
where
500+
file = head args
501+
502+
-- if the filename contains a path separator then we know that it is not a
503+
-- command it is a file to be interpreted. In that case we only show the
504+
-- interpreter error message and exclude the command related error messages.
505+
errorCombine =
506+
if elem pathSeparator file
507+
then overrideErrorHelp
508+
else vcatErrorHelp
509+
510+
overrideErrorHelp (ParserHelp e1 _ _ _ _) (ParserHelp _ h2 u2 b2 f2) =
511+
ParserHelp e1 h2 u2 b2 f2
512+
474513
parseResultHandler fn = handleParseResult (overFailure fn (Failure f))
475514
noSuchFile name = errorHelp $ stringChunk
476-
("\nNo such source file to interpret `" ++ name ++ "\'")
515+
("File does not exist or is not a regular file `" ++ name ++ "'")
477516

478-
runInterpreterCommand file = do
517+
runInterpreterCommand path = do
479518
progName <- getProgName
480-
iargs <- getInterpreterArgs file
519+
iargs <- getInterpreterArgs path
481520
let parseCmdLine = commandLineHandler progName True
482521
let cmdArgs = iargs ++ "--" : args
483522
-- TODO show the command in verbose mode

0 commit comments

Comments
 (0)