packages feed

hs-0.1.0.0: src/HS/CLI/OptParse.hs

{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE RecordWildCards     #-}
{-# LANGUAGE ScopedTypeVariables #-}

module HS.CLI.OptParse where

import           Control.Applicative
import           Data.Char
import           Data.Default
import           Data.Maybe
import           Data.Possibly
import qualified Data.Text                   as T
import           Fmt
import           HS.CLI.ToolArgs
import qualified Options.Applicative         as OP
import           Options.Applicative.Builder
import           System.Environment
import           Text.Enum.Text


-- | the OA parser
type Psr a = OP.Parser a

-- | the OA optional operator
opt :: Psr a -> Psr (Maybe a)
opt = OP.optional

-- | the OA Kleene closure operator
mny :: Psr a -> Psr [a]
mny = OP.many


--------------------------------------------------------------------------------
-- parseArgs
--------------------------------------------------------------------------------

-- | main OA driver function
parseArgs :: forall a . (ToolArgs->Psr a) -> IO a
parseArgs psr = prs =<< getArgs
  where
    prs :: [String] -> IO a
    prs as0 = parseIO (psr tas) as
      where
        tas = ToolArgs $ map T.pack $ tail' dd_tas
        (as,dd_tas) = break (=="--") as0

        tail' :: [b] -> [b]
        tail' []    = []
        tail' (_:t) = t


--------------------------------------------------------------------------------
-- the low-level drivers
--------------------------------------------------------------------------------

parserPrefs :: OP.ParserPrefs
parserPrefs = OP.prefs showHelpOnEmpty

-- | making an IO parser
parseIO :: Psr a -> [String] -> IO a
parseIO psr as = OP.handleParseResult $
    OP.execParserPure parserPrefs (hsParserInfo $ psr) as

-- | making a functional parser
pureParse :: Psr a -> [String] -> Maybe a
pureParse p =
    OP.getParseResult . OP.execParserPure parserPrefs (hsParserInfo p)

-- | testing CLI parsers
testCLI :: Show a => Psr a -> [String] -> IO ()
testCLI psr ss = do
    x <- OP.handleParseResult $
              OP.execParserPure parserPrefs (hsParserInfo psr) ss
    print x


--------------------------------------------------------------------------------
-- hsParserInfo
--------------------------------------------------------------------------------

-- | given a 'Psr' makes up a corresponding @ParserInfo@
hsParserInfo :: Psr a -> OP.ParserInfo a
hsParserInfo p =
    OP.info (OP.helper <*> p)
         $  fullDesc
         <> progDesc "GHC installation manager manager"
         <> header   "towards a unified Haskell Development Environment"
         <> footer   "see --help for details of each sub-command"


--------------------------------------------------------------------------------
-- cmd
--------------------------------------------------------------------------------

-- | construct a sub-command parser from command name, description and parser
cmd :: String -> String -> Psr a -> OP.Mod OP.CommandFields a
cmd nme dsc psr = command nme $ info (OP.helper <*> psr) $ progDesc dsc


--------------------------------------------------------------------------------
-- parser builders
--------------------------------------------------------------------------------

cmd_et_p :: EnumText a => String -> (a->String) -> Psr a
cmd_et_p hlp c_hlp = subparser $ mconcat $
    commandGroup hlp :
      [ cmd (fmt $ build c) (c_hlp c) $ pure c
        | c <- [minBound..maxBound]
        ]

-- | parsing an argument EnumText argument
arg_et_optd :: forall a . EnumText a => String -> a -> Psr a
arg_et_optd var df = fromMaybe df <$> ss_p
  where
    ss_p :: Psr (Maybe a)
    ss_p = opt $ arg_et_p var

-- | parsing an argument EnumText argument
arg_et_p :: forall a . (Bounded a,Enum a,Buildable a,TextParsable a) => String -> Psr a
arg_et_p var = arg_p var hlp
  where
    hlp = T.unpack $ T.intercalate "|" $
                map (fmt . build) [minBound..maxBound :: a]

-- | pasring an EnumText option
opt_et_p :: forall a . EnumText a => Char -> String -> Psr a
opt_et_p c var = opt_p c var hlp
  where
    hlp = T.unpack $ T.intercalate "|" $
                map (fmt . build) [minBound..maxBound :: a]

-- | pasring a TextParsable argument
arg_p :: TextParsable a => String -> String -> Psr a
arg_p = arg_p' parseText

-- | pasring an argument ParseText, when passed the parser explicitly
arg_p' :: (T.Text->Possibly a) -> String -> String -> Psr a
arg_p' prs var hlp = argument (eitherReader $ prs . T.pack)
      $  metavar var
      <> help    hlp

-- | parsing a TextParsable option
opt_p :: TextParsable a => Char -> String -> String -> Psr a
opt_p ch nme hlp = option (eitherReader parseString)
      $  metavar var
      <> short   ch
      <> long    lng
      <> help    hlp
  where
    var = map toUpper nme
    lng = map toLower nme

enum_switches_with_def_p :: forall a . (Default a,EnumText a) => Psr a
enum_switches_with_def_p = fmap (fromMaybe def) $ opt $ short_enum_switches_p $ const Nothing

enum_switches_p :: forall a . EnumText a => Psr a
enum_switches_p = short_enum_switches_p $ const Nothing

short_enum_switches_p :: forall a . EnumText a => (a->Maybe Char) -> Psr a
short_enum_switches_p sh_f = foldr (<|>) empty $ map mk [minBound..maxBound]
  where
    mk :: a -> Psr a
    mk x = OP.flag' x $ (long $ fmt $ build x) <> shrt
      where
        shrt = case sh_f x of
          Nothing -> mempty
          Just c  -> short c

parseString :: TextParsable a => String -> Possibly a
parseString = parseText . T.pack