module System.Console.GetOpt.Generics.Modifier (
Modifier(..),
Modifiers,
mkModifiers,
mkShortOptions,
mkLongOption,
hasPositionalArgumentsField,
isPositionalArgumentsField,
getPositionalArgumentType,
getHelpText,
getVersion,
deriveShortOptions,
mkShortModifiers,
insertWith,
) where
import Prelude ()
import Prelude.Compat
import Data.Char
import Data.List (find, foldl')
import Data.Maybe
import Generics.SOP
import System.Console.GetOpt.Generics.FieldString
data Modifier
= AddShortOption String Char
| RenameOption String String
| RenameOptions (String -> Maybe String)
| UseForPositionalArguments String String
| AddOptionHelp String String
| AddVersionFlag String
data Modifiers = Modifiers {
_shortOptions :: [(String, [Char])],
_renaming :: FieldString -> FieldString,
positionalArgumentsField :: [(String, String)],
helpTexts :: [(String, String)],
version :: Maybe String
}
mkModifiers :: [Modifier] -> Modifiers
mkModifiers = foldl' inner empty
where
empty :: Modifiers
empty = Modifiers [] id [] [] Nothing
inner :: Modifiers -> Modifier -> Modifiers
inner (Modifiers shorts renaming args help version) modifier = case modifier of
(AddShortOption option short) ->
Modifiers (insertWith (++) option [short] shorts) renaming args help version
(RenameOption from to) ->
let newRenaming :: FieldString -> FieldString
newRenaming option = if from `matches` option
then mkFieldString to
else option
in Modifiers shorts (renaming . newRenaming) args help version
(RenameOptions newRenaming) ->
Modifiers shorts (renaming `combineRenamings` newRenaming) args help version
(UseForPositionalArguments option typ) ->
Modifiers shorts renaming ((option, map toUpper typ) : args) help version
(AddOptionHelp option helpText) ->
Modifiers shorts renaming args (insert option helpText help) version
(AddVersionFlag v) ->
Modifiers shorts renaming args help (Just v)
combineRenamings :: (FieldString -> FieldString) -> (String -> Maybe String)
-> FieldString -> FieldString
combineRenamings old new fieldString =
(old . renameUnnormalized new) fieldString
lookupMatching :: [(String, a)] -> FieldString -> Maybe a
lookupMatching list option = fmap snd $ find (\ (from, _) -> from `matches` option) list
mkShortOptions :: Modifiers -> FieldString -> [Char]
mkShortOptions (Modifiers shortMap _ _ _ _) option = fromMaybe [] (lookupMatching shortMap option)
mkLongOption :: Modifiers -> FieldString -> String
mkLongOption (Modifiers _ renaming _ _ _) option =
normalized (renaming option)
hasPositionalArgumentsField :: Modifiers -> Bool
hasPositionalArgumentsField = not . null . positionalArgumentsField
isPositionalArgumentsField :: Modifiers -> FieldString -> Bool
isPositionalArgumentsField modifiers field =
any (`matches` field) (map fst (positionalArgumentsField modifiers))
getPositionalArgumentType :: Modifiers -> Maybe String
getPositionalArgumentType = fmap snd . listToMaybe . positionalArgumentsField
getHelpText :: Modifiers -> FieldString -> String
getHelpText modifiers field = fromMaybe "" $ lookupMatching (helpTexts modifiers) field
getVersion :: Modifiers -> Maybe String
getVersion modifiers = version modifiers
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 datatypeInfo 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 =
let withShorts = mapMaybe (\ field -> (field, ) <$> toShort field) fields
allShorts = map snd withShorts
isUnique c = case filter (== c) allShorts of
[_] -> True
_ -> False
in (flip mapMaybe) withShorts $ \ (field, short) ->
if isUnique short
then Just (AddShortOption field short)
else Nothing
where
toShort :: String -> Maybe Char
toShort s = case dropWhile (\ c -> not (isAscii c && isAlpha c)) s of
[] -> Nothing
(a : _) -> Just (toLower a)
insertWith :: Eq a => (b -> b -> b) -> a -> b -> [(a, b)] -> [(a, b)]
insertWith _ key value [] = [(key, value)]
insertWith combine key value ((a, b) : r) =
if a == key
then (key, b `combine` value) : r
else (a, b) : insertWith combine key value r
insert :: Eq a => a -> b -> [(a, b)] -> [(a, b)]
insert key value [] = [(key, value)]
insert key value ((a, b) : r) =
if a == key
then (key, value) : r
else (a, b) : insert key value r