{-# LANGUAGE CPP #-} module Text.Format.ArgKey ( ArgKey (..), topKey, popKey ) where import Control.Arrow import Data.Char (isDigit) import qualified Data.List as L #if MIN_VERSION_base(4, 11, 0) import Data.Semigroup #endif import Text.Format.Error {-| A data type indicates key of format argument ==== The key syntax @ key -> [(int | chars) {"!" (int | chars)}] @ Since the "!" is used to seprate keys, if you need to include a "!" in a named key, it can be escaped by doubling "!!". Note: See 'Format' to learn more about syntax description language Examples >>> read "0" :: ArgKey >>> read "country" :: ArgKey >>> read "coun!!try" :: ArgKey >>> read "country!name" :: ArgKey >>> read "country!cities!10!name" :: ArgKey -} data ArgKey = Index Int -- ^ Refers to a top-level positional -- argument or an element in an list-like -- data type. | Name String -- ^ Refers to a top-level named argument or -- a field of a record data type. | Nest ArgKey ArgKey -- ^ For @Nest k1 k2@, k1 refers to a -- top-level argument or an attribute -- (element or field) of a data type, -- k2 refers an attribute of the data -- referenced by k1. deriving (Eq, Ord) #if MIN_VERSION_base(4, 11, 0) instance Semigroup ArgKey where (<>) = associate #endif instance Monoid ArgKey where -- | @Index -1@ is used as an empty key mempty = Index (-1) #if !MIN_VERSION_base(4, 11, 0) mappend = associate #endif instance Read ArgKey where readsPrec _ "" = [ (mempty, "") ] readsPrec _ cs = [ parse cs ] where parse :: String -> (ArgKey, String) parse cs = case break cs of ("", cs1) -> (undefined, cs1) (_, "!") -> (undefined, "!") (cs1, "") -> (parse1 cs1, "") (cs1, cs2) -> first (mappend $ parse1 cs1) (parse cs2) parse1 :: String -> ArgKey parse1 cs = if all isDigit cs then Index (read cs) else Name cs break :: String -> (String, String) break cs = case L.break (== '!') cs of (cs1, "") -> (cs1, "") (cs1, "!") -> (cs1, "!") (cs1, '!' : '!' : cs2) -> first ((cs1 ++ "!") ++) (break cs2) (cs1, '!' : cs2) -> (cs1, cs2) instance Show ArgKey where show k@(Index i) = if mempty == k then "" else show i show (Name s) = escape s where escape :: String -> String escape "" = "" escape ('!' : cs) = "!!" ++ escape cs escape (c : cs) = (c : escape cs) show (Nest k1 k2) = show k1 ++ "!" ++ show k2 associate :: ArgKey -> ArgKey -> ArgKey associate k (Index (-1)) = k associate (Index (-1)) k = k associate (Nest k11 k12) k2 = associate k11 $ associate k12 k2 associate k1 k2 = Nest k1 k2 {-| Extract the topmost indexed or named key from a key >>> topKey (read "k1!k2!k3") == Name "k1" True >>> topKey (read "name") == Name "name" True >>> topKey (read "123") == Index 123 True >>> topKey mempty *** Exception: vformat: empty arg key -} topKey :: ArgKey -> ArgKey topKey (Nest k@(Nest _ _) _) = topKey k topKey (Nest k _) = k topKey k = if k == mempty then vferror "empty arg key" else k {-| Remove the topmost indexed or named key from a key >>> popKey (read "k1!k2!k3") == read "k2!k3" True >>> popKey (read "name") == mempty True >>> popKey (read "123") == mempty True >>> popKey mempty *** Exception: vformat: empty arg key -} popKey :: ArgKey -> ArgKey popKey (Nest k1@(Nest _ _) k2) = mappend (popKey k1) k2 popKey (Nest _ k) = k popKey k = if k == mempty then vferror "empty arg key" else mempty