bishbosh-0.1.3.0: src-lib/BishBosh/Input/CommandLineOption.hs
{-# LANGUAGE LambdaCase #-}
{-
Copyright (C) 2018 Dr. Alistair Ward
This file is part of BishBosh.
BishBosh is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
BishBosh 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with BishBosh. If not, see <http://www.gnu.org/licenses/>.
-}
{- |
[@AUTHOR@] Dr. Alistair Ward
[@DESCRIPTION@] To facilitate processing of command-line options.
-}
module BishBosh.Input.CommandLineOption(
-- * Types
-- ** Type-synonyms
Flag,
-- ** Data-types
CommandLineOption(
-- ConfigLocationParameter,
-- IOAction,
-- OptionsMutator
),
-- * Constants
longFlagPrefix,
-- * Functions
categorise,
getArgs,
-- read',
readArg,
readBoundedIntegral,
-- ** Constructors
mkConfigLocationParameter,
mkIOAction,
mkContextualIOAction,
mkOptionsMutator
) where
import qualified BishBosh.Data.Exception as Data.Exception
import qualified BishBosh.Input.CategorisedCommandLineOptions as Input.CategorisedCommandLineOptions
import qualified BishBosh.Property.Empty as Property.Empty
import qualified Control.Exception
import qualified Data.List
import qualified System.FilePath
-- | Synonym.
type Flag = String
-- | The sum-type of categories of command-line option.
data CommandLineOption options
= ConfigLocationParameter System.FilePath.FilePath -- ^ A command-line option which specifies the location of some configuration.
| IOAction Input.CategorisedCommandLineOptions.IOAction -- ^ A command-line option which requests an IO-action. N.B. it doesn't have access to the configuration.
| ContextualIOAction (Input.CategorisedCommandLineOptions.ContextualIOAction options) -- ^ A command-line option which requests an IO-action, which has access to the configuration.
| OptionsMutator (Input.CategorisedCommandLineOptions.OptionsMutator options) -- ^ A command-line option which directly specifies a configuration-parameter.
-- | Constructor.
mkConfigLocationParameter :: System.FilePath.FilePath -> CommandLineOption options
mkConfigLocationParameter = ConfigLocationParameter
-- | Constructor.
mkIOAction :: Input.CategorisedCommandLineOptions.IOAction -> CommandLineOption options
mkIOAction = IOAction
-- | Constructor.
mkContextualIOAction :: Input.CategorisedCommandLineOptions.ContextualIOAction options -> CommandLineOption options
mkContextualIOAction = ContextualIOAction
-- | Constructor.
mkOptionsMutator :: Input.CategorisedCommandLineOptions.OptionsMutator options -> CommandLineOption options
mkOptionsMutator = OptionsMutator
{- |
* Partition a list of /CommandLineOption/s according to their data-constructor.
* N.B.: preserves the order of all specifications sharing a data-constructor.
-}
categorise :: [CommandLineOption options] -> Input.CategorisedCommandLineOptions.CategorisedCommandLineOptions options
categorise = foldr (
\case
ConfigLocationParameter f -> Input.CategorisedCommandLineOptions.setConfigLocation f -- CAVEAT: overwrites any previous specification.
IOAction a -> Input.CategorisedCommandLineOptions.prependIOAction a
ContextualIOAction a -> Input.CategorisedCommandLineOptions.prependContextualIOAction a
OptionsMutator m -> Input.CategorisedCommandLineOptions.prependOptionsMutator m
) Property.Empty.empty
-- | The prefix used to denote the long form of a command-line flag.
longFlagPrefix :: Flag
longFlagPrefix = "--"
{- |
* Return the list of arguments extracted from the specified command-line, which match the specified flag.
* CAVEAT:
All unique abbreviations must also be supplied.
Doesn't cope with short flags preceded by '-'.
-}
getArgs
:: [Flag] -- ^ The list acceptible abbreviations for the required flag, each preceded by "--".
-> [String] -- ^ The command-line arguments to search.
-> [String] -- ^ The matching argument-values.
getArgs flags = slave where
slave :: [String] -> [String]
slave [] = []
slave (x : xs)
| x `elem` flags = case xs of
s : remainder -> s : slave remainder {-recurse-}
[] -> Control.Exception.throw . Data.Exception.mkInsufficientData . showString "option " $ shows x " requires an argument."
| any (
(`Data.List.isPrefixOf` x) . (++ "=")
) flags = case dropWhile (/= '=') x of
_ : remainder -> remainder : slave xs
[] -> Control.Exception.throw . Data.Exception.mkNullDatum . showString "option " $ shows x " requires a non-null argument."
| otherwise = slave xs -- Recurse.
-- | Parse the specified string, throwing an exception on failure.
read' :: Read a => String -> String -> a
read' errorMessage s = case reads s of
[(x, "")] -> x
_ -> Control.Exception.throw . Data.Exception.mkParseFailure . showString errorMessage $ shows s "."
-- | Parse the specified string, returning the specified explanatory error-message on failure.
readArg :: Read a => String -> a
readArg = read' "failed to parse command-line argument "
-- | Reads a bounded integral value from the command-line, guarding against overflow.
readBoundedIntegral :: Integral i => String -> i
readBoundedIntegral s
| fromIntegral bounded /= unbounded = Control.Exception.throw . Data.Exception.mkOutOfBounds . showString "BishBosh.Input.CommandLineOption.readBoundedIntegral:\tintegral value exceeds permissible bounds; " $ shows unbounded "."
| otherwise = bounded
where
unbounded = readArg s
bounded = fromInteger unbounded