{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE UnicodeSyntax #-} -- | -- Module: Configuration.Utils.Monoid -- Description: Configuration of Monoids -- Copyright: Copyright © 2015 PivotCloud, Inc. -- License: MIT -- Maintainer: Lars Kuhtz -- Stability: experimental -- -- The distinction between appending on the left and appending on -- the right is important for monoids that are sensitive to ordering -- such as 'List'. It is also of relevance for monoids with set semantics -- with non-extensional equality such as `HashMap`. -- module Configuration.Utils.Monoid ( LeftMonoidalUpdate , leftMonoidalUpdate , fromLeftMonoidalUpdate , pLeftMonoidalUpdate , RightMonoidalUpdate , rightMonoidalUpdate , fromRightMonoidalUpdate , pRightMonoidalUpdate ) where import Configuration.Utils.CommandLine import Configuration.Utils.Internal import Control.Monad.Writer hiding (mapM_) import Data.Aeson import Data.Semigroup import qualified Options.Applicative.Types as O import Prelude hiding (concatMap, mapM_, any) import Prelude.Unicode -- | Update a value by appending on the left. Under normal -- circumstances you'll never use this type directly but only -- its 'FromJSON' instance. See the 'leftMonoidalUpdate' for an example. -- newtype LeftMonoidalUpdate a = LeftMonoidalUpdate { _getLeftMonoidalUpdate ∷ a } deriving (Semigroup, Monoid) -- | Update a value by appending on the left. -- -- > newtype RoutingTable = RoutingTable { _routingTableMap ∷ HashMap T.Text T.Text } -- > -- > $(makeLenses ''RoutingTable) -- > -- > instance FromJSON (RoutingTable → RoutingTable) where -- > parseJSON = withObject "RoutingTable" $ \o → id -- > <$< routingTableMap . from leftMonoidalUpdate %.: "route_map" % o -- leftMonoidalUpdate ∷ Iso (LeftMonoidalUpdate a) (LeftMonoidalUpdate b) a b leftMonoidalUpdate = iso _getLeftMonoidalUpdate LeftMonoidalUpdate -- | This is the same as @from leftMonoidalUpdate@ but doesn't depend on -- the lens Library. -- fromLeftMonoidalUpdate ∷ Iso a b (LeftMonoidalUpdate a) (LeftMonoidalUpdate b) fromLeftMonoidalUpdate = iso LeftMonoidalUpdate _getLeftMonoidalUpdate instance (FromJSON a, Monoid a) ⇒ FromJSON (LeftMonoidalUpdate a → LeftMonoidalUpdate a) where parseJSON = fmap (mappend ∘ LeftMonoidalUpdate) ∘ parseJSON -- | Update a value by appending on the left. -- -- > newtype RoutingTable = RoutingTable { _routingTableMap ∷ HashMap T.Text T.Text } -- > -- > $(makeLenses ''RoutingTable) -- > -- > pRoutingTable ∷ MParser RoutingTable -- > pRoutingTable = routingTableMap %:: pLeftMonoidalUpdate pRoute -- > where -- > pRoute = option (eitherReader readRoute) -- > % long "route" -- > <> help "add a route to the routing table; the APIROUTE part must not contain a colon character" -- > <> metavar "APIROUTE:APIURL" -- > -- > readRoute s = case break (== ':') s of -- > (a,':':b) → fmapL T.unpack $ do -- > validateNonEmpty "APIROUTE" a -- > validateHttpOrHttpsUrl "APIURL" b -- > return $ HM.singleton (T.pack a) (T.pack b) -- > _ → Left "missing colon between APIROUTE and APIURL" -- > -- > fmapL f = either (Left . f) Right -- pLeftMonoidalUpdate ∷ Monoid a ⇒ O.Parser a → MParser a pLeftMonoidalUpdate pElement = mappend ∘ mconcat ∘ reverse <$> many pElement -- | Update a value by appending on the right. Under normal -- circumstances you'll never use this type directly but only -- its 'FromJSON' instance. See the 'leftMonoidalUpdate' for an example. -- newtype RightMonoidalUpdate a = RightMonoidalUpdate { _getRightMonoidalUpdate ∷ a } deriving (Semigroup, Monoid) -- | Update a value by appending on the right. See 'leftMonoidalUpdate' for -- an usage example. -- rightMonoidalUpdate ∷ Iso (RightMonoidalUpdate a) (RightMonoidalUpdate b) a b rightMonoidalUpdate = iso _getRightMonoidalUpdate RightMonoidalUpdate -- | This is the same as @from rightMonoidalUpdate@ but doesn't depend on -- the lens Library. -- fromRightMonoidalUpdate ∷ Iso a b (RightMonoidalUpdate a) (RightMonoidalUpdate b) fromRightMonoidalUpdate = iso RightMonoidalUpdate _getRightMonoidalUpdate instance (FromJSON a, Monoid a) ⇒ FromJSON (RightMonoidalUpdate a → RightMonoidalUpdate a) where parseJSON = fmap (flip mappend ∘ RightMonoidalUpdate) ∘ parseJSON -- | Update a value by appending on the right. See 'pLeftMonoidalUpdate' -- for an usage example. -- pRightMonoidalUpdate ∷ Monoid a ⇒ O.Parser a → MParser a pRightMonoidalUpdate pElement = flip mappend ∘ mconcat <$> many pElement