yaml-light-lens-0.3.3.3: src/Data/Yaml/YamlLight/Lens.hs
{-# LANGUAGE CPP, FlexibleContexts, FlexibleInstances,
MultiParamTypeClasses, RankNTypes, TypeFamilies #-}
-- | Lenses for working with YAML structures.
module Data.Yaml.YamlLight.Lens (
-- * Traversals
nth, key, key',
-- * Yaml parsing prism
_Yaml, AsYaml(..),
-- * Numeric parsers
yamlInt, yamlReal) where
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative
#endif
import Control.Lens
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Lex.Integral as I
import qualified Data.ByteString.Lex.Fractional as F
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Traversable (sequenceA)
import Data.Yaml.YamlLight
-- $setup
-- >>> :set -XOverloadedStrings
-- | The two indexable types of YAML data are sequences and mappings.
data YamlIx = ArrIx Int | ObjIx YamlLight
type instance Index YamlLight = YamlIx
type instance IxValue YamlLight = YamlLight
instance Ixed YamlLight where
ix k@(ArrIx i) f (YSeq xs) | i < 0 = pure (YSeq xs)
| otherwise = YSeq <$> go xs i where
go [] _ = pure []
go (y:ys) 0 = (:ys) <$> indexed f k y
go (y:ys) i' = (y:) <$> (go ys $! i' - 1)
ix k@(ObjIx k') f (YMap m) = case Map.lookup k' m of
Just v -> YMap . flip (Map.insert k') m <$> indexed f k v
Nothing -> pure (YMap m)
ix _ _ y = pure y
instance At YamlLight where
at k@(ObjIx k') f (YMap m) = YMap . aux <$> indexed f k mv
where aux Nothing = maybe m (const (Map.delete k' m)) mv
aux (Just v) = Map.insert k' v m
mv = Map.lookup k' m
at k f y = const y <$> indexed f k Nothing
instance Each YamlLight YamlLight YamlLight YamlLight where
each f (YSeq xs) = YSeq <$> traverse (uncurry $ indexed f)
(zip (map ArrIx [0..]) xs)
each f (YMap m) = YMap <$> sequenceA (Map.mapWithKey (indexed f . ObjIx) m)
each _ y = pure y
instance Plated YamlLight where
plate f (YSeq xs) = YSeq <$> traverse f xs
plate f (YMap m) = YMap <$> traverse f m
plate _f y = pure y
noRemainder :: (a, ByteString) -> Maybe a
noRemainder (x, bs) = if BC.null bs then Just x else Nothing
-- | Try to parse an 'Integral' value from a 'YamlLight'.
yamlInt :: Integral b => YamlLight -> Maybe b
yamlInt (YStr s) = I.readSigned I.readDecimal s >>= noRemainder
yamlInt _ = Nothing
-- | Try to parse a 'Fractional' value from a 'YamlLight'.
yamlReal :: Fractional b => YamlLight -> Maybe b
yamlReal (YStr s) = F.readSigned F.readDecimal s >>= noRemainder
yamlReal _ = Nothing
-- | Lens into a sequence.
--
-- >>> YSeq [YStr "a", YStr "b", YStr "c"] ^? nth 1
-- Just (YStr "b")
--
-- >>> YSeq [YStr "a", YStr "b", YStr "c"] & nth 1 .~ YStr "B"
-- YSeq [YStr "a",YStr "B",YStr "c"]
--
-- >>> YSeq [YStr "a", YStr "b", YStr "c"] ^? nth 2 . _Yaml :: Maybe String
-- Just "c"
nth :: Int -> Traversal' YamlLight YamlLight
nth = ix . ArrIx
-- | Lens into a mapping. 'ByteString's are used as keys directly. If
-- you wish to use a complex mapping key, see 'key''.
--
-- >>> let m = YMap $ Map.fromList [(YStr "name", YStr "Tony Stark"), (YStr "sequels", YStr "2")]
-- >>> m & key "sequels" . _Yaml +~ 1
-- YMap (fromList [(YStr "name",YStr "Tony Stark"),(YStr "sequels",YStr "3")])
key :: ByteString -> Traversal' YamlLight YamlLight
key = key' . YStr
-- | Lens into a mapping using a complex key.
key' :: YamlLight -> Traversal' YamlLight YamlLight
key' = ix . ObjIx
-- | Convert between YAML values and common types of Haskell values.
class AsYaml a where
fromYaml :: YamlLight -> Maybe a
toYaml :: a -> YamlLight
instance AsYaml (Map YamlLight YamlLight) where
fromYaml (YMap m) = Just m
fromYaml _ = Nothing
toYaml = YMap
instance AsYaml [YamlLight] where
fromYaml (YSeq a) = Just a
fromYaml _ = Nothing
toYaml = YSeq
instance AsYaml ByteString where
fromYaml (YStr s) = Just s
fromYaml _ = Nothing
toYaml = YStr
instance AsYaml String where
fromYaml (YStr s) = Just $ BC.unpack s
fromYaml _ = Nothing
toYaml = YStr . BC.pack
instance AsYaml Int where
fromYaml x@(YStr _) = yamlInt x
fromYaml _ = Nothing
toYaml x = YStr $ if x < 0 then BC.cons '-' bs else bs
where Just bs = I.packDecimal $ abs x
-- toYaml = YStr . BC.pack . show
instance AsYaml Integer where
fromYaml x@(YStr _) = yamlInt x
fromYaml _ = Nothing
toYaml x = YStr $ if x < 0 then BC.cons '-' bs else bs
where Just bs = I.packDecimal $ abs x
-- toYaml = YStr . BC.pack . show
instance AsYaml Double where
fromYaml x@(YStr _) = yamlReal x
fromYaml _ = Nothing
toYaml = YStr . BC.pack . show
instance AsYaml Bool where
fromYaml (YStr s) = case () of
_ | s == BC.pack "true" -> Just True
| s == BC.pack "false" -> Just False
| otherwise -> Nothing
fromYaml _ = Nothing
toYaml True = YStr $ BC.pack "true"
toYaml False = YStr $ BC.pack "false"
-- | Convert between YAML values and corresponding common Haskell
-- values.
--
-- >>> YStr "-2.3" ^? _Yaml :: Maybe Double
-- Just (-2.3)
--
-- >>> YStr "7b.3" ^? _Yaml :: Maybe Double
-- Nothing
--
-- >>> YStr "-23" ^? _Yaml :: Maybe Int
-- Just (-23)
--
-- >>> YStr "Help, I'm trapped in a haddock factory!" ^? _Yaml :: Maybe String
-- Just "Help, I'm trapped in a haddock factory!"
--
-- >>> YStr "An integer" ^? _Yaml :: Maybe Integer
-- Nothing
--
-- If we just want to pull out those values that were successfully
-- parsed,
--
-- >>> let nums = YSeq [YStr "3", YStr "2a", YStr "1"]
-- >>> nums ^.. each._Yaml :: [Int]
-- [3,1]
--
-- Alternately, we may want to fail the entire parse if any element
-- fails to parse.
--
-- >>> sequenceA $ map (preview _Yaml) (nums ^.. each) :: Maybe [Int]
-- Nothing
-- >>> let nums' = YSeq [YStr "3", YStr "2", YStr "1"]
-- >>> sequenceA $ map (preview _Yaml) (nums' ^.. each) :: Maybe [Int]
-- Just [3,2,1]
_Yaml :: AsYaml a => Prism' YamlLight a
_Yaml = prism' toYaml fromYaml