summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--MetaTile/Util/ExtensibleState.hs117
-rw-r--r--metatile.cabal2
2 files changed, 119 insertions, 0 deletions
diff --git a/MetaTile/Util/ExtensibleState.hs b/MetaTile/Util/ExtensibleState.hs
new file mode 100644
index 0000000..9aa7fab
--- /dev/null
+++ b/MetaTile/Util/ExtensibleState.hs
@@ -0,0 +1,117 @@
+{-# LANGUAGE PatternGuards #-}
+-----------------------------------------------------------------------------
+-- |
+-- Module : XMonad.Util.ExtensibleState
+-- Copyright : (c) Daniel Schoepe 2009
+-- License : BSD3-style (see LICENSE)
+--
+-- Maintainer : daniel.schoepe@gmail.com
+-- Stability : unstable
+-- Portability : not portable
+--
+-- Module for storing custom mutable state in xmonad.
+--
+-----------------------------------------------------------------------------
+
+module MetaTile.Util.ExtensibleState (
+ -- * Usage
+ -- $usage
+ put
+ , modify
+ , remove
+ , get
+ , gets
+ ) where
+
+import Data.Typeable (typeOf,Typeable,cast)
+import qualified Data.Map as M
+import MetaTile.Core
+import qualified Control.Monad.State as State
+import Data.Maybe (fromMaybe)
+
+-- ---------------------------------------------------------------------
+-- $usage
+--
+-- To utilize this feature in a contrib module, create a data type
+-- and make it an instance of ExtensionClass. You can then use
+-- the functions from this module for storing and retrieving your data:
+--
+-- > {-# LANGUAGE DeriveDataTypeable #-}
+-- > import qualified XMonad.Util.ExtensibleState as XS
+-- >
+-- > data ListStorage = ListStorage [Integer] deriving Typeable
+-- > instance ExtensionClass ListStorage where
+-- > initialValue = ListStorage []
+-- >
+-- > .. XS.put (ListStorage [23,42])
+--
+-- To retrieve the stored value call:
+--
+-- > .. XS.get
+--
+-- If the type can't be inferred from the usage of the retrieved data, you
+-- have to add an explicit type signature:
+--
+-- > .. XS.get :: X ListStorage
+--
+-- To make your data persistent between restarts, the data type needs to be
+-- an instance of Read and Show and the instance declaration has to be changed:
+--
+-- > data ListStorage = ListStorage [Integer] deriving (Typeable,Read,Show)
+-- >
+-- > instance ExtensionClass ListStorage where
+-- > initialValue = ListStorage []
+-- > extensionType = PersistentExtension
+--
+-- One should take care that the string representation of the chosen type
+-- is unique among the stored values, otherwise it will be overwritten.
+-- Normally these string representations contain fully qualified module names
+-- when automatically deriving Typeable, so
+-- name collisions should not be a problem in most cases.
+-- A module should not try to store common datatypes(e.g. a list of Integers)
+-- without a custom data type as a wrapper to avoid collisions with other modules
+-- trying to store the same data type without a wrapper.
+--
+
+-- | Modify the map of state extensions by applying the given function.
+modifyStateExts :: (M.Map String (Either String StateExtension)
+ -> M.Map String (Either String StateExtension))
+ -> X ()
+modifyStateExts f = State.modify $ \st -> st { extensibleState = f (extensibleState st) }
+
+-- | Apply a function to a stored value of the matching type or the initial value if there
+-- is none.
+modify :: ExtensionClass a => (a -> a) -> X ()
+modify f = put . f =<< get
+
+-- | Add a value to the extensible state field. A previously stored value with the same
+-- type will be overwritten. (More precisely: A value whose string representation of its type
+-- is equal to the new one's)
+put :: ExtensionClass a => a -> X ()
+put v = modifyStateExts . M.insert (show . typeOf $ v) . Right . extensionType $ v
+
+-- | Try to retrieve a value of the requested type, return an initial value if there is no such value.
+get :: ExtensionClass a => X a
+get = getState' undefined -- `trick' to avoid needing -XScopedTypeVariables
+ where toValue val = maybe initialValue id $ cast val
+ getState' :: ExtensionClass a => a -> X a
+ getState' k = do
+ v <- State.gets $ M.lookup (show . typeOf $ k) . extensibleState
+ case v of
+ Just (Right (StateExtension val)) -> return $ toValue val
+ Just (Right (PersistentExtension val)) -> return $ toValue val
+ Just (Left str) | PersistentExtension x <- extensionType k -> do
+ let val = fromMaybe initialValue $ cast =<< safeRead str `asTypeOf` Just x
+ put (val `asTypeOf` k)
+ return val
+ _ -> return $ initialValue
+ safeRead str = case reads str of
+ [(x,"")] -> Just x
+ _ -> Nothing
+
+gets :: ExtensionClass a => (a -> b) -> X b
+gets = flip fmap get
+
+-- | Remove the value from the extensible state field that has the same type as the supplied argument
+remove :: ExtensionClass a => a -> X ()
+remove wit = modifyStateExts $ M.delete (show . typeOf $ wit)
diff --git a/metatile.cabal b/metatile.cabal
index 89fc04a..da71120 100644
--- a/metatile.cabal
+++ b/metatile.cabal
@@ -41,6 +41,7 @@ library
MetaTile.ManageHook
MetaTile.Operations
MetaTile.StackSet
+ MetaTile.Util.ExtensibleState
build-depends: base < 5 && >=3, containers, directory, process, filepath, extensible-exceptions,
X11>=1.5 && < 1.7, mtl, unix,
@@ -70,6 +71,7 @@ executable metatile
MetaTile.ManageHook
MetaTile.Operations
MetaTile.StackSet
+ MetaTile.Util.ExtensibleState
if true
ghc-options: -funbox-strict-fields -Wall