Skip to content
This repository was archived by the owner on Oct 7, 2020. It is now read-only.

Commit 0fc1c72

Browse files
gdziadkiewiczfendor
authored andcommitted
Redesign option parsing for executables. Fix #1578
1 parent f340cf1 commit 0fc1c72

File tree

4 files changed

+120
-29
lines changed

4 files changed

+120
-29
lines changed

app/MainHie.hs

+5-5
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ main = do
8484

8585
let plugins' = plugins (optExamplePlugin opts)
8686

87-
if optLsp opts
88-
then do
87+
case optMode opts of
88+
LspMode -> do
8989
-- Start up in LSP mode
9090
logm $ "Run entered for HIE(" ++ progName ++ ") " ++ hieVersion
9191
logm $ "Operating as a LSP server on stdio"
@@ -106,7 +106,7 @@ main = do
106106
-- launch the dispatcher.
107107
scheduler <- newScheduler plugins' initOpts
108108
server scheduler origDir plugins' (optCaptureFile opts)
109-
else do
109+
ProjectLoadingMode projectLoadingOpts -> do
110110
-- Provide debug info
111111
cliOut $ "Running HIE(" ++ progName ++ ")"
112112
cliOut $ " " ++ hieVersion
@@ -128,7 +128,7 @@ main = do
128128
cliOut $ "Project Ghc version: " ++ projGhc
129129
cliOut $ "Libdir: " ++ show mlibdir
130130
cliOut "Searching for Haskell source files..."
131-
targets <- case optFiles opts of
131+
targets <- case optFiles projectLoadingOpts of
132132
[] -> findAllSourceFiles origDir
133133
xs -> concat <$> mapM findAllSourceFiles xs
134134

@@ -138,7 +138,7 @@ main = do
138138
mapM_ cliOut targets
139139
cliOut ""
140140

141-
unless (optDryRun opts) $ do
141+
unless (optDryRun projectLoadingOpts) $ do
142142
cliOut "\nLoad them all now. This may take a very long time.\n"
143143
loadDiagnostics <- runServer mlibdir plugins' targets
144144

haskell-ide-engine.cabal

+2
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ test-suite unit-test
203203
HsImportSpec
204204
JsonSpec
205205
LiquidSpec
206+
OptionsSpec
206207
PackagePluginSpec
207208
Spec
208209
-- Technically cabal-helper should be a 'run-tool-depends', but that doesn't exist yet
@@ -225,6 +226,7 @@ test-suite unit-test
225226
, hie-plugin-api
226227
, hoogle > 5.0.11
227228
, hspec
229+
, optparse-applicative
228230
, process
229231
, quickcheck-instances
230232
, text

src/Haskell/Ide/Engine/Options.hs

+43-24
Original file line numberDiff line numberDiff line change
@@ -14,40 +14,66 @@ import System.IO
1414
import qualified System.Log.Logger as L
1515
import Data.Foldable
1616

17+
data ProjectLoadingOpts = ProjectLoadingOpts
18+
{ optDryRun :: Bool
19+
, optFiles :: [FilePath]
20+
} deriving (Show, Eq)
21+
22+
data RunMode = LspMode | ProjectLoadingMode ProjectLoadingOpts
23+
deriving (Show, Eq)
24+
1725
data GlobalOpts = GlobalOpts
1826
{ optDebugOn :: Bool
1927
, optLogFile :: Maybe String
20-
, optLsp :: Bool
2128
, projectRoot :: Maybe String
2229
, optBiosVerbose :: Bool
2330
, optCaptureFile :: Maybe FilePath
2431
, optExamplePlugin :: Bool
25-
, optDryRun :: Bool
26-
, optFiles :: [FilePath]
27-
} deriving (Show)
32+
, optMode :: RunMode
33+
} deriving (Show, Eq)
2834

2935
-- | Introduced as the common prefix of app/HieWrapper.hs/main and app/MainHie.hs/main
3036
initApp :: String -> IO GlobalOpts
3137
initApp namedesc = do
3238
hSetBuffering stderr LineBuffering
33-
let numericVersion :: Parser (a -> a)
34-
numericVersion = infoOption (showVersion Meta.version)
35-
(long "numeric-version" <> help "Show only version number")
36-
compiler :: Parser (a -> a)
37-
compiler = infoOption hieGhcDisplayVersion
38-
(long "compiler" <> help "Show only compiler and version supported")
3939
-- Parse the options and run
4040
(opts, ()) <- simpleOptions
4141
hieVersion
4242
namedesc
4343
""
44-
(numericVersion <*> compiler <*> globalOptsParser)
44+
optionParser
4545
empty
4646
Core.setupLogger (optLogFile opts) ["hie", "hie-bios"]
4747
$ if optDebugOn opts then L.DEBUG else L.INFO
4848
traverse_ setCurrentDirectory $ projectRoot opts
4949
return opts
5050

51+
optionParser :: Parser GlobalOpts
52+
optionParser = numericVersion <*> compiler <*> globalOptsParser
53+
54+
numericVersion :: Parser (a -> a)
55+
numericVersion = infoOption (showVersion Meta.version)
56+
(long "numeric-version" <> help "Show only version number")
57+
58+
compiler :: Parser (a -> a)
59+
compiler = infoOption hieGhcDisplayVersion
60+
(long "compiler" <> help "Show only compiler and version supported")
61+
62+
projectLoadingModeParser :: Parser RunMode
63+
projectLoadingModeParser =
64+
ProjectLoadingMode
65+
<$> (ProjectLoadingOpts
66+
<$> flag False True
67+
( long "dry-run"
68+
<> help "Perform a dry-run of loading files. Only searches for Haskell source files to load. Does nothing if run as LSP server."
69+
)
70+
<*> many
71+
( argument str
72+
( metavar "FILES..."
73+
<> help "Directories and Filepaths to load. Does nothing if run as LSP server.")
74+
)
75+
)
76+
5177
globalOptsParser :: Parser GlobalOpts
5278
globalOptsParser = GlobalOpts
5379
<$> switch
@@ -61,9 +87,6 @@ globalOptsParser = GlobalOpts
6187
<> metavar "LOGFILE"
6288
<> help "File to log to, defaults to stdout"
6389
))
64-
<*> flag False True
65-
( long "lsp"
66-
<> help "Start HIE as an LSP server. Otherwise it dumps debug info to stdout")
6790
<*> optional (strOption
6891
( long "project-root"
6992
<> short 'r'
@@ -88,13 +111,9 @@ globalOptsParser = GlobalOpts
88111
<*> switch
89112
( long "example"
90113
<> help "Enable Example2 plugin. Useful for developers only")
91-
<*> flag False True
92-
( long "dry-run"
93-
<> help "Perform a dry-run of loading files. Only searches for Haskell source files to load. Does nothing if run as LSP server."
94-
)
95-
<*> many
96-
( argument str
97-
( metavar "FILES..."
98-
<> help "Directories and Filepaths to load. Does nothing if run as LSP server.")
99-
)
100-
114+
<*> (flag' LspMode
115+
( long "lsp"
116+
<> help "Start HIE as an LSP server. Otherwise it dumps debug info to stdout")
117+
<|>
118+
projectLoadingModeParser
119+
)

test/unit/OptionsSpec.hs

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
module OptionsSpec where
2+
3+
import Prelude hiding (unzip)
4+
import Data.List.NonEmpty(unzip)
5+
import Test.Hspec
6+
import Options.Applicative
7+
import Haskell.Ide.Engine.Options(GlobalOpts(..), RunMode(..), ProjectLoadingOpts(..), optionParser)
8+
import System.Exit(ExitCode(..))
9+
import Data.List(isPrefixOf)
10+
11+
main :: IO ()
12+
main = hspec spec
13+
14+
spec :: Spec
15+
spec = do
16+
let defaultGlobalOptions = GlobalOpts False Nothing Nothing False Nothing False (ProjectLoadingMode $ ProjectLoadingOpts False [])
17+
let getParseFailure (Failure x) = Just (renderFailure x "hie")
18+
getParseFailure _ = Nothing
19+
let sut = optionParser
20+
let parserInfo = info sut mempty
21+
let parserPrefs = prefs mempty
22+
let runSut :: [String] -> ParserResult GlobalOpts
23+
runSut = execParserPure parserPrefs parserInfo
24+
25+
describe "cmd option parsing" $ do
26+
describe "compiler flag" $ do
27+
let input = ["--compiler"]
28+
let result = runSut input
29+
let (maybeMessage, maybeStatusCode) = unzip $ getParseFailure result
30+
31+
it "should return ghc version" $
32+
maybeMessage `shouldSatisfy` any ("ghc" `isPrefixOf`)
33+
it "should return exit code 0" $
34+
maybeStatusCode `shouldBe` Just ExitSuccess
35+
36+
describe "numeric version flag" $ do
37+
let input = ["--numeric-version"]
38+
let result = runSut input
39+
let (maybeMessage, maybeStatusCode) = unzip $ getParseFailure result
40+
41+
it "should return version" $
42+
maybeMessage `shouldBe` Just "1.1"
43+
it "shoud return exit code 0" $
44+
maybeStatusCode `shouldBe` Just ExitSuccess
45+
46+
describe "not providing arguments" $ do
47+
let input = []
48+
let result = runSut input
49+
let maybeGlobalOptions = getParseResult result
50+
51+
it "should result in default options" $
52+
maybeGlobalOptions `shouldBe` Just defaultGlobalOptions
53+
54+
describe "lsp flag" $ do
55+
let input = ["--lsp"]
56+
let result = runSut input
57+
let maybeGlobalOptions = getParseResult result
58+
59+
it "should result in default lsp options" $
60+
maybeGlobalOptions `shouldBe` Just (GlobalOpts False Nothing Nothing False Nothing False LspMode)
61+
62+
describe "providing two unmatching arguments" $ do
63+
let input = ["--lsp", "--dry-run"]
64+
let result = runSut input
65+
let (maybeMessage, maybeStatusCode) = unzip $ getParseFailure result
66+
67+
it "should return expected error message" $
68+
maybeMessage `shouldSatisfy` any ("Invalid option `--dry-run'" `isPrefixOf`)
69+
it "should return error exit code 1" $
70+
maybeStatusCode `shouldBe` Just (ExitFailure 1)

0 commit comments

Comments
 (0)