getopt-generics-0.2: src/System/Console/GetOpt/Generics/Modifier.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
module System.Console.GetOpt.Generics.Modifier (
Modifier(..),
deriveShortOptions,
mkShortOptions,
mkLongOptions,
) where
import Data.List
import Data.Maybe
import Generics.SOP
import System.Console.GetOpt.Generics.Internal
-- | 'Modifier's can be used to customize the command line parser.
data Modifier
= AddShortOption String Char
-- ^ @AddShortOption fieldName c@ adds the 'Char' @c@ as a short option for
-- the field addressed by @fieldName@.
| RenameOption String String
-- ^ @RenameOption fieldName customName@ renames the option generated
-- through the @fieldName@ by @customName@.
deriving (Show, Eq, Ord)
-- | Derives 'AddShortOption's for all fields of the datatype that start with a
-- unique character.
deriveShortOptions :: (HasDatatypeInfo a, SingI (Code a)) =>
Proxy a -> [Modifier]
deriveShortOptions proxy =
mkShortModifiers (flags proxy)
flags :: (SingI (Code a), HasDatatypeInfo a) =>
Proxy a -> [String]
flags proxy = case normalizedDatatypeInfo proxy of
ADT _ _ ci -> fromNPConstructorInfo ci
Newtype _ _ ci -> fromConstructorInfo ci
where
fromNPConstructorInfo :: NP ConstructorInfo xs -> [String]
fromNPConstructorInfo Nil = []
fromNPConstructorInfo (a :* r) =
fromConstructorInfo a ++ fromNPConstructorInfo r
fromConstructorInfo :: ConstructorInfo x -> [String]
fromConstructorInfo (Constructor _) = []
fromConstructorInfo (Infix _ _ _) = []
fromConstructorInfo (Record _ fields) =
fromFields fields
fromFields :: NP FieldInfo xs -> [String]
fromFields (FieldInfo name :* r) = name : fromFields r
fromFields Nil = []
mkShortModifiers :: [String] -> [Modifier]
mkShortModifiers fields =
mapMaybe inner fields
where
inner :: String -> Maybe Modifier
inner field@(short : _) =
case filter ([short] `isPrefixOf`) fields of
[_] -> Just $ AddShortOption field short
_ -> Nothing
inner [] = Nothing
mkShortOptions :: [Modifier] -> String -> [Char]
mkShortOptions modifiers option =
mapMaybe inner modifiers
where
inner :: Modifier -> Maybe Char
inner (AddShortOption modifierOption short)
| matchesField modifierOption option
= Just short
| otherwise
= Nothing
inner _ = Nothing
mkLongOptions :: [Modifier] -> String -> [String]
mkLongOptions modifiers option =
inner (reverse modifiers)
where
inner (RenameOption renameOption newName : _)
| renameOption `matchesField` option = [newName]
inner [] = [option]
inner (_ : r) = inner r
matchesField :: String -> String -> Bool
matchesField modifierOption option =
modifierOption == option || slugify modifierOption == option