multiarg-0.30.0.0: lib/Multiarg/Mode/Internal.hs
-- | Internal functions used by "Multiarg.Mode". You don't have to
-- worry about \"breaking\" anything by using this module; it is
-- separate from "Multiarg.Mode" primarily to tidy up the
-- documentation in that module. The functions in "Multiarg.Mode"
-- should satisfy most use cases. However, if you want more control
-- over error handling, you can use this module.
module Multiarg.Mode.Internal where
import Data.Either (partitionEithers)
import Multiarg.Maddash
import Multiarg.Internal
import Multiarg.Util
import Multiarg.Types
newtype ModeName = ModeName String
deriving (Eq, Ord, Show)
data ParsedMode a
= ModeGood a
| ModeError [OptionError] (Either OptionError OptName)
-- ^ There was an error. There may be zero or more initial
-- OptionError. There must be at least one error, which is either
-- an OptionError or the name of an option, if the error is that
-- there were not enough words following the option to provide it
-- with its necessary arguments.
deriving (Eq, Ord, Show)
instance Functor ParsedMode where
fmap f (ModeGood a) = ModeGood (f a)
fmap _ (ModeError ls ei) = ModeError ls ei
-- | A 'Mode' represents a single command line mode, such as @check@
-- for @ghc-pkg check@. It contains the name of the mode, as well as
-- a parser that handles all /options/ and /positional arguments/ for
-- the mode. Ordinarily you will create a 'Mode' using the 'mode'
-- function rather than by using the constructor directly.
data Mode r = Mode ModeName ([Word] -> ParsedMode r)
instance Functor Mode where
fmap f (Mode s p) = Mode s (fmap (fmap f) p)
parsedCommandLineToParsedMode
:: ([a] -> r)
-> ParsedCommandLine a
-> ParsedMode r
parsedCommandLineToParsedMode fMode (ParsedCommandLine ls mayOpt)
= case mayOpt of
Nothing -> case mayLast errs of
Nothing -> ModeGood (fMode goods)
Just (errs1st, errsLst) -> ModeError errs1st (Left errsLst)
Just opt -> ModeError errs (Right opt)
where
(errs, goods) = partitionEithers ls
-- | Creates a new 'Mode'.
mode
:: String
-- ^ Mode name. For instance, for the @check@ mode of @ghc-pkg@,
-- this would be @check@.
-> [OptSpec a]
-- ^ Mode /options/
-> (String -> a)
-- ^ Parses /positional arguments/
-> ([a] -> r)
-- ^ Processes the result of all mode /options/
-> Mode r
mode name opts fPos fMode
= Mode (ModeName name)
$ parsedCommandLineToParsedMode fMode
. parseCommandLinePure opts fPos
. map (\(Word s) -> s)
data GlobalLocalEnd a
= GlobalInsufficientOptArgs OptName
| ModeNotFound String [String]
| NoMode
| ModeFound (ParsedMode a)
deriving (Eq, Ord, Show)
data GlobalLocal g r
= GlobalLocal [Either OptionError g] (GlobalLocalEnd r)
deriving (Eq, Ord, Show)
-- | The result of parsing a mode command line.
data ModeResult g r
= ModeResult [g] (Either [String] r)
-- ^ @ModeResult a b@ is a successfully parsed mode command line,
-- where:
--
-- @a@ is a list of all global options parsed; and
--
-- @b@ indicates the result of parsing the mode. It is @Either c
-- d@, where @Left c@ indicates that no mode was parsed. This
-- arises under two circumstances. If the user did not include any
-- /words/ after the global /options/, then @c@ will be the empty
-- list, @[]@. If the user did include /words/ after the global
-- options, but the first /word/ was not recognized as a mode, then
-- this list will contain the first /word/ and any subsequent /words/.
-- Therefore, note that if the user attempted to use a mode that
-- does not exist (e.g. she misspelled it), this is not treated as
-- an error. It's up to the client code to deal with this issue
-- (for instance, your program might not view this situation as
-- being an error.)
--
-- If @b@ is @Right d@, this indicates that the user entered a
-- recognized mode, and the result is @d@.
deriving (Eq, Ord, Show)
getModeResult
:: GlobalLocal g r
-> Either (String, [String]) (ModeResult g r)
getModeResult (GlobalLocal eis end)
= combine global (endToModeResult end)
where
(glblErrs, glblGoods) = partitionEithers eis
global = case glblErrs of
[] -> Right glblGoods
x:xs -> Left (x, xs)
combine
:: Either (OptionError, [OptionError]) [g]
-- ^ Global result. Contains either one or more errors, or global
-- /option/ results.
-> Either (String, [String]) (Either [String] r)
-- ^ Result of parsing mode /word/, and the mode /options/ and
-- /positional arguments/. May be @Left a@, where @a@ is one or
-- more errors, or @Right b@, where @b@ is a good result. A good
-- result @b@ may be either @Left c@, where @c@ is a list of
-- /positional arguments/, or @Right d@, where @d@ is the mode
-- result. @c@ indicates that no mode was recognized and may be
-- either @[]@, which indicates that the user passed no /words/ at
-- all after the global /options/, or @x:xs@, indicating that the
-- user did pass /words/ after the global /options/, but the first
-- /word/ was not recognized as a mode.
-> Either (String, [String]) (ModeResult g r)
combine (Left (oe1, oes)) (Left (me1, mes)) =
Left ( globalOptErrorToString oe1
, map globalOptErrorToString oes ++ (me1 : mes) )
combine (Left (oe1, oes)) (Right _) =
Left (globalOptErrorToString oe1, map globalOptErrorToString oes)
combine (Right _) (Left (me1, mes)) = Left (me1, mes)
combine (Right glbls) (Right r) =
Right (ModeResult glbls r)
endToModeResult
:: GlobalLocalEnd a
-> Either (String, [String]) (Either [String] a)
endToModeResult end = case end of
GlobalInsufficientOptArgs on -> Left
(labeledInsufficientOptArgs "global" on, [])
ModeNotFound s ss -> Right (Left $ s:ss)
NoMode -> Right (Left [])
ModeFound pm -> extractParsedMode pm
extractParsedMode
:: ParsedMode a
-> Either (String, [String]) (Either b a)
extractParsedMode (ModeGood g) = Right . Right $ g
extractParsedMode (ModeError es lst) = Left $ case es of
[] -> (eiToError lst, [])
(x:xs) ->
( modeOptErrorToString x
, (map modeOptErrorToString xs) ++ [eiToError lst] )
globalOptErrorToString :: OptionError -> String
globalOptErrorToString = optErrorToString "global"
modeOptErrorToString :: OptionError -> String
modeOptErrorToString = optErrorToString "mode"
optErrorToString :: String -> OptionError -> String
optErrorToString lbl oe = case oe of
BadOption opt ->
"unrecognized " ++ lbl ++ " option: " ++ optNameToString opt
LongArgumentForZeroArgumentOption lng arg ->
"argument given for " ++ lbl ++ " option that takes no arguments. "
++ "option: --" ++ longNameToString lng
++ " argument: " ++ optArgToString arg
eiToError :: Either OptionError OptName -> String
eiToError ei = case ei of
Left oe -> modeOptErrorToString oe
Right on -> labeledInsufficientOptArgs "mode" on
labeledInsufficientOptArgs :: String -> OptName -> String
labeledInsufficientOptArgs lbl on = "insufficient option arguments "
++ "given for " ++ lbl ++ " option: " ++ optNameToString on
-- | Parses a command line that may contain modes.
parseModeLine
:: [OptSpec g]
-- ^ Global /options/. This might, for example, include a @--help@
-- /option/.
-> [Mode r]
-- ^ All modes
-> [String]
-- ^ All command line /words/
-> Either (String, [String]) (ModeResult g r)
-- ^ Returns @Either a b@. @Left a@ represents an error. Each
-- String represents a single error (this is returned as a pair
-- because there must be at least one error; a simple list would not
-- reflect this requirement.)
--
-- @Right b@ indicates that parsing proceeded successfully; consult
-- 'ModeResult' to see what is returned.
parseModeLine glbl mds =
getModeResult
. parseModeLineWithErrors glbl mds
parseModeLineWithErrors
:: [OptSpec g]
-- ^ Global options
-> [Mode r]
-- ^ All modes
-> [String]
-- ^ All command line tokens
-> GlobalLocal g r
parseModeLineWithErrors glbl mds tokStrings = GlobalLocal lsErrsGoods end
where
toks = map Word tokStrings
(shorts, longs) = splitOptSpecs glbl
(outs, eiOptTok) = processWords shorts longs toks
lsErrsGoods = map f . concat $ outs
where
f (Good a) = Right a
f (OptionError e) = Left e
end = case eiOptTok of
Left (opt, _) -> GlobalInsufficientOptArgs opt
Right [] -> NoMode
Right (x:xs) -> case findExactMode x mds of
Nothing -> ModeNotFound (unWord x) (map unWord xs)
where
unWord (Word t) = t
Just (Mode _ f) -> ModeFound (f xs)
-- | Takes a token and a list of all modes; returns the matching mode
-- if there is one, or Nothing if there is no match.
findExactMode
:: Word
-> [Mode a]
-> Maybe (Mode a)
findExactMode _ [] = Nothing
findExactMode tok@(Word s) (m@(Mode (ModeName n) _) : ms)
| s == n = Just m
| otherwise = findExactMode tok ms