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

Commit 369ccfe

Browse files
committed
Redesign option parsing for executables. Fix #1578
1 parent c584f98 commit 369ccfe

File tree

5 files changed

+168
-29
lines changed

5 files changed

+168
-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+
)

stack-8.8.3.yaml

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
resolver: ghc-8.8.3
2+
packages:
3+
- .
4+
- hie-plugin-api
5+
6+
extra-deps:
7+
# - ./submodules/HaRe
8+
9+
- apply-refact-0.7.0.0
10+
- brittany-0.12.1.1
11+
- bytestring-trie-0.2.5.0
12+
- cabal-helper-1.0.0.0
13+
- clock-0.7.2
14+
- constrained-dynamic-0.1.0.0
15+
- floskell-0.10.2
16+
- ghc-lib-parser-ex-8.8.4.0
17+
- git: https://github.com/haskell/haddock.git
18+
commit: be8b02c4e3cffe7d45b3dad0a0f071d35a274d65
19+
subdirs:
20+
- haddock-api
21+
# - haddock-api-2.23.0
22+
- haddock-library-1.8.0
23+
- haskell-lsp-0.20.0.0
24+
- haskell-lsp-types-0.20.0.0
25+
- lsp-test-0.10.1.0
26+
- haskell-src-exts-1.21.1
27+
- hie-bios-0.4.0
28+
- hlint-2.2.10
29+
- hoogle-5.0.17.11
30+
- hsimport-0.11.0
31+
- ilist-0.3.1.0
32+
- monad-dijkstra-0.1.1.2
33+
- ormolu-0.0.3.1
34+
- semigroups-0.18.5
35+
- temporary-1.2.1.1
36+
37+
flags:
38+
haskell-ide-engine:
39+
pedantic: true
40+
hie-plugin-api:
41+
pedantic: true
42+
43+
# allow-newer: true
44+
45+
nix:
46+
packages: [ icu libcxx zlib ]
47+
48+
concurrent-tests: false

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)