diff --git a/src/GHCMod/Options/DocUtils.hs b/Language/Haskell/GhcMod/Options/DocUtils.hs similarity index 96% rename from src/GHCMod/Options/DocUtils.hs rename to Language/Haskell/GhcMod/Options/DocUtils.hs index 95fad26..c81dec8 100644 --- a/src/GHCMod/Options/DocUtils.hs +++ b/Language/Haskell/GhcMod/Options/DocUtils.hs @@ -14,7 +14,7 @@ -- You should have received a copy of the GNU Affero General Public License -- along with this program. If not, see . -module GHCMod.Options.DocUtils ( +module Language.Haskell.GhcMod.Options.DocUtils ( ($$), ($$$), (<=>), diff --git a/src/GHCMod/Options/Help.hs b/Language/Haskell/GhcMod/Options/Help.hs similarity index 97% rename from src/GHCMod/Options/Help.hs rename to Language/Haskell/GhcMod/Options/Help.hs index 9e33194..d43b6fb 100644 --- a/src/GHCMod/Options/Help.hs +++ b/Language/Haskell/GhcMod/Options/Help.hs @@ -15,7 +15,7 @@ -- along with this program. If not, see . {-# LANGUAGE OverloadedStrings, FlexibleInstances, GeneralizedNewtypeDeriving #-} -module GHCMod.Options.Help where +module Language.Haskell.GhcMod.Options.Help where import Options.Applicative import Options.Applicative.Help.Pretty (Doc) diff --git a/Language/Haskell/GhcMod/Options/Options.hs b/Language/Haskell/GhcMod/Options/Options.hs new file mode 100644 index 0000000..7d4aa3a --- /dev/null +++ b/Language/Haskell/GhcMod/Options/Options.hs @@ -0,0 +1,173 @@ +-- ghc-mod: Making Haskell development *more* fun +-- Copyright (C) 2015 Nikolay Yakimov +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU Affero General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU Affero General Public License for more details. +-- +-- You should have received a copy of the GNU Affero General Public License +-- along with this program. If not, see . +{-# LANGUAGE OverloadedStrings #-} +{-# OPTIONS_GHC -fno-warn-unused-do-bind #-} + +module Language.Haskell.GhcMod.Options.Options ( + globalArgSpec + , parseCmdLineOptions +) where + +import Options.Applicative +import Options.Applicative.Types +import Language.Haskell.GhcMod.Types +import Control.Arrow +import Data.Char (toUpper, toLower) +import Data.List (intercalate) +import Language.Haskell.GhcMod.Read +import Language.Haskell.GhcMod.Options.DocUtils +import Language.Haskell.GhcMod.Options.Help +import Data.Monoid +import Prelude + +-- | Parse a set of arguments according to the ghc-mod CLI flag spec, producing +-- @Options@ set accordingly. +parseCmdLineOptions :: [String] -> Maybe Options +parseCmdLineOptions = getParseResult . execParserPure (prefs mempty) (info globalArgSpec mempty) + +splitOn :: Eq a => a -> [a] -> ([a], [a]) +splitOn c = second (drop 1) . break (==c) + +logLevelParser :: Parser GmLogLevel +logLevelParser = + logLevelSwitch <*> + logLevelOption + <||> silentSwitch + where + logLevelOption = + option parseLL + $$ long "verbose" + <=> metavar "LEVEL" + <=> value GmWarning + <=> showDefaultWith showLL + <=> help' $$$ do + "Set log level (" + <> int' (fromEnum (minBound :: GmLogLevel)) + <> "-" + <> int' (fromEnum (maxBound :: GmLogLevel)) + <> ")" + "You can also use strings (case-insensitive):" + para' + $ intercalate ", " + $ map showLL ([minBound..maxBound] :: [GmLogLevel]) + logLevelSwitch = + repeatAp succ' . length <$> many $$ flag' () + $$ short 'v' + <=> help "Increase log level" + silentSwitch = flag' GmSilent + $$ long "silent" + <=> short 's' + <=> help "Be silent, set log level to 'silent'" + showLL = drop 2 . map toLower . show + repeatAp f n = foldr (.) id (replicate n f) + succ' x | x == maxBound = x + | otherwise = succ x + parseLL = do + v <- readerAsk + let + il'= toEnum . min maxBound <$> readMaybe v + ll' = readMaybe ("Gm" ++ capFirst v) + maybe (readerError $ "Not a log level \"" ++ v ++ "\"") return $ ll' <|> il' + capFirst (h:t) = toUpper h : map toLower t + capFirst [] = [] + +outputOptsSpec :: Parser OutputOpts +outputOptsSpec = OutputOpts + <$> logLevelParser + <*> flag PlainStyle LispStyle + $$ long "tolisp" + <=> short 'l' + <=> help "Format output as an S-Expression" + <*> LineSeparator <$$> strOption + $$ long "boundary" + <=> long "line-separator" + <=> short 'b' + <=> metavar "SEP" + <=> value "\0" + <=> showDefault + <=> help "Output line separator" + <*> optional $$ splitOn ',' <$$> strOption + $$ long "line-prefix" + <=> metavar "OUT,ERR" + <=> help "Output prefixes" + +programsArgSpec :: Parser Programs +programsArgSpec = Programs + <$> strOption + $$ long "with-ghc" + <=> value "ghc" + <=> showDefault + <=> help "GHC executable to use" + <*> strOption + $$ long "with-ghc-pkg" + <=> value "ghc-pkg" + <=> showDefault + <=> help "ghc-pkg executable to use (only needed when guessing from GHC path fails)" + <*> strOption + $$ long "with-cabal" + <=> value "cabal" + <=> showDefault + <=> help "cabal-install executable to use" + <*> strOption + $$ long "with-stack" + <=> value "stack" + <=> showDefault + <=> help "stack executable to use" + +-- | An optparse-applicative @Parser@ sepcification for @Options@ so that +-- applications making use of the ghc-mod API can have a consistent way of +-- parsing global options. +globalArgSpec :: Parser Options +globalArgSpec = Options + <$> outputOptsSpec + <*> programsArgSpec + <*> many $$ strOption + $$ long "ghcOpt" + <=> long "ghc-option" + <=> short 'g' + <=> metavar "OPT" + <=> help "Option to be passed to GHC" + <*> many fileMappingSpec + <*> strOption + $$ long "encoding" + <=> value "UTF-8" + <=> showDefault + <=> help "I/O encoding" + where + fileMappingSpec = + getFileMapping . splitOn '=' <$> strOption + $$ long "map-file" + <=> metavar "MAPPING" + <=> fileMappingHelp + fileMappingHelp = help' $ do + "Redirect one file to another" + "--map-file \"file1.hs=file2.hs\"" + indent 4 $ do + "can be used to tell ghc-mod" + \\ "that it should take source code" + \\ "for `file1.hs` from `file2.hs`." + "`file1.hs` can be either full path," + \\ "or path relative to project root." + "`file2.hs` has to be either relative to project root," + \\ "or full path (preferred)" + "--map-file \"file.hs\"" + indent 4 $ do + "can be used to tell ghc-mod that it should take" + \\ "source code for `file.hs` from stdin. File end" + \\ "marker is `\\n\\EOT\\n`, i.e. `\\x0A\\x04\\x0A`." + \\ "`file.hs` may or may not exist, and should be" + \\ "either full path, or relative to project root." + getFileMapping = second (\i -> if null i then Nothing else Just i) diff --git a/ghc-mod.cabal b/ghc-mod.cabal index 85f0738..c7b1f9f 100644 --- a/ghc-mod.cabal +++ b/ghc-mod.cabal @@ -153,6 +153,10 @@ Library Language.Haskell.GhcMod.Types Language.Haskell.GhcMod.Utils Language.Haskell.GhcMod.World + + Language.Haskell.GhcMod.Options.Options + Language.Haskell.GhcMod.Options.DocUtils + Language.Haskell.GhcMod.Options.Help Other-Modules: Paths_ghc_mod Utils Data.Binary.Generic @@ -188,6 +192,7 @@ Library , extra == 1.4.* , pipes == 4.1.* , safe < 0.4 && >= 0.3.9 + , optparse-applicative >=0.11.0 && <0.13.0 , template-haskell , syb if impl(ghc < 7.8) @@ -200,9 +205,7 @@ Executable ghc-mod , GHCMod.Options , GHCMod.Options.Commands , GHCMod.Version - , GHCMod.Options.DocUtils , GHCMod.Options.ShellParse - , GHCMod.Options.Help GHC-Options: -Wall -fno-warn-deprecations -threaded Default-Extensions: ConstraintKinds, FlexibleContexts HS-Source-Dirs: src diff --git a/src/GHCMod/Options.hs b/src/GHCMod/Options.hs index e654c7e..c3cf263 100644 --- a/src/GHCMod/Options.hs +++ b/src/GHCMod/Options.hs @@ -25,14 +25,10 @@ module GHCMod.Options ( import Options.Applicative import Options.Applicative.Types import Language.Haskell.GhcMod.Types -import Control.Arrow -import Data.Char (toUpper, toLower) -import Data.List (intercalate) -import Language.Haskell.GhcMod.Read import GHCMod.Options.Commands import GHCMod.Version -import GHCMod.Options.DocUtils -import GHCMod.Options.Help +import Language.Haskell.GhcMod.Options.DocUtils +import Language.Haskell.GhcMod.Options.Options import GHCMod.Options.ShellParse parseArgs :: IO (Options, GhcModCommands) @@ -74,133 +70,3 @@ helpVersion = argAndCmdSpec :: Parser (Options, GhcModCommands) argAndCmdSpec = (,) <$> globalArgSpec <*> commandsSpec -splitOn :: Eq a => a -> [a] -> ([a], [a]) -splitOn c = second (drop 1) . break (==c) - -logLevelParser :: Parser GmLogLevel -logLevelParser = - logLevelSwitch <*> - logLevelOption - <||> silentSwitch - where - logLevelOption = - option parseLL - $$ long "verbose" - <=> metavar "LEVEL" - <=> value GmWarning - <=> showDefaultWith showLL - <=> help' $$$ do - "Set log level (" - <> int' (fromEnum (minBound :: GmLogLevel)) - <> "-" - <> int' (fromEnum (maxBound :: GmLogLevel)) - <> ")" - "You can also use strings (case-insensitive):" - para' - $ intercalate ", " - $ map showLL ([minBound..maxBound] :: [GmLogLevel]) - logLevelSwitch = - repeatAp succ' . length <$> many $$ flag' () - $$ short 'v' - <=> help "Increase log level" - silentSwitch = flag' GmSilent - $$ long "silent" - <=> short 's' - <=> help "Be silent, set log level to 'silent'" - showLL = drop 2 . map toLower . show - repeatAp f n = foldr (.) id (replicate n f) - succ' x | x == maxBound = x - | otherwise = succ x - parseLL = do - v <- readerAsk - let - il'= toEnum . min maxBound <$> readMaybe v - ll' = readMaybe ("Gm" ++ capFirst v) - maybe (readerError $ "Not a log level \"" ++ v ++ "\"") return $ ll' <|> il' - capFirst (h:t) = toUpper h : map toLower t - capFirst [] = [] - -outputOptsSpec :: Parser OutputOpts -outputOptsSpec = OutputOpts - <$> logLevelParser - <*> flag PlainStyle LispStyle - $$ long "tolisp" - <=> short 'l' - <=> help "Format output as an S-Expression" - <*> LineSeparator <$$> strOption - $$ long "boundary" - <=> long "line-separator" - <=> short 'b' - <=> metavar "SEP" - <=> value "\0" - <=> showDefault - <=> help "Output line separator" - <*> optional $$ splitOn ',' <$$> strOption - $$ long "line-prefix" - <=> metavar "OUT,ERR" - <=> help "Output prefixes" - -programsArgSpec :: Parser Programs -programsArgSpec = Programs - <$> strOption - $$ long "with-ghc" - <=> value "ghc" - <=> showDefault - <=> help "GHC executable to use" - <*> strOption - $$ long "with-ghc-pkg" - <=> value "ghc-pkg" - <=> showDefault - <=> help "ghc-pkg executable to use (only needed when guessing from GHC path fails)" - <*> strOption - $$ long "with-cabal" - <=> value "cabal" - <=> showDefault - <=> help "cabal-install executable to use" - <*> strOption - $$ long "with-stack" - <=> value "stack" - <=> showDefault - <=> help "stack executable to use" - -globalArgSpec :: Parser Options -globalArgSpec = Options - <$> outputOptsSpec - <*> programsArgSpec - <*> many $$ strOption - $$ long "ghcOpt" - <=> long "ghc-option" - <=> short 'g' - <=> metavar "OPT" - <=> help "Option to be passed to GHC" - <*> many fileMappingSpec - <*> strOption - $$ long "encoding" - <=> value "UTF-8" - <=> showDefault - <=> help "I/O encoding" - where - fileMappingSpec = - getFileMapping . splitOn '=' <$> strOption - $$ long "map-file" - <=> metavar "MAPPING" - <=> fileMappingHelp - fileMappingHelp = help' $ do - "Redirect one file to another" - "--map-file \"file1.hs=file2.hs\"" - indent 4 $ do - "can be used to tell ghc-mod" - \\ "that it should take source code" - \\ "for `file1.hs` from `file2.hs`." - "`file1.hs` can be either full path," - \\ "or path relative to project root." - "`file2.hs` has to be either relative to project root," - \\ "or full path (preferred)" - "--map-file \"file.hs\"" - indent 4 $ do - "can be used to tell ghc-mod that it should take" - \\ "source code for `file.hs` from stdin. File end" - \\ "marker is `\\n\\EOT\\n`, i.e. `\\x0A\\x04\\x0A`." - \\ "`file.hs` may or may not exist, and should be" - \\ "either full path, or relative to project root." - getFileMapping = second (\i -> if null i then Nothing else Just i) diff --git a/src/GHCMod/Options/Commands.hs b/src/GHCMod/Options/Commands.hs index 2e1f60a..a2ab3c0 100644 --- a/src/GHCMod/Options/Commands.hs +++ b/src/GHCMod/Options/Commands.hs @@ -23,8 +23,8 @@ import Options.Applicative.Types import Options.Applicative.Builder.Internal import Language.Haskell.GhcMod.Types import Language.Haskell.GhcMod.Read -import GHCMod.Options.DocUtils -import GHCMod.Options.Help +import Language.Haskell.GhcMod.Options.DocUtils +import Language.Haskell.GhcMod.Options.Help type Symbol = String type Expr = String