-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Runtime-editable program settings. -- @package settings @version 0.2.0.0 module Data.Settings.Types -- | A settings option. The option value itself is held as usual in regular -- application state, not here. What is held here is functions -- applied to that state to get or set the value. data Option m Option :: m String -> (String -> m (Maybe SettingsError)) -> m () -> Option m optGet :: Option m -> m String optSet :: Option m -> String -> m (Maybe SettingsError) optReset :: Option m -> m () data Section m Section :: HashMap String (Option m) -> HashMap String (Section m) -> Section m secOpts :: Section m -> HashMap String (Option m) secSubs :: Section m -> HashMap String (Section m) type OptName = String type SecName = String type OptPath = String type OptRoute = [String] data SettingsError InvalidPath :: OptPath -> SettingsError NoSuchOption :: OptRoute -> SettingsError NoSuchSection :: OptRoute -> SettingsError NoSuchNode :: OptRoute -> SettingsError InvalidValueForType :: String -> SettingsError InvalidValue :: String -> SettingsError class OptionValue v readOption :: OptionValue v => String -> Maybe v showOption :: OptionValue v => v -> String typeName :: OptionValue v => v -> String class Monad m => MonadSettings m s | m -> s getSettings :: MonadSettings m s => m s putSettings :: MonadSettings m s => s -> m () modifySettings :: MonadSettings m s => (s -> s) -> m () getSTree :: MonadSettings m s => m (Section m) module Data.Settings.Option mkOptionV :: (Monad m, OptionValue v) => m v -> (v -> m Bool) -> m () -> Option m mkOptionS :: (MonadSettings m s, OptionValue v) => (s -> v) -> (v -> s -> Maybe s) -> (s -> (Maybe v, s)) -> (v -> m ()) -> Option m -- | This module provides functions work working with the Section -- type, i.e. option trees. The style is similar to the APIs for -- HashMaps and Maps. You can use these functions to -- construct a custom settings tree UI. Before you do that, try the -- Data.Settings.Interface module, which may already offer what -- you need. module Data.Settings.Section -- | Construct an empty section, no options and no subsections. empty :: Section m -- | Construct a section with a single option. singleton :: SecName -> Option m -> Section m -- | Return True if this section contains any options, False -- otherwise. hasOpts :: Section m -> Bool -- | Return True if this section contains any subsections, -- False otherwise. hasSubs :: Section m -> Bool -- | Return True if this section is empty (no options, no -- subsections), False otherwise. null :: Section m -> Bool -- | Return True if an option or a subsection is present at the -- specified path, False otherwise. member :: OptRoute -> Section m -> (Bool, Bool) -- | Return True if an option is present at the specified path, -- False otherwise. memberOpt :: OptRoute -> Section m -> Bool -- | Return True if a subsection is present at the specified path, -- False otherwise. memberSub :: OptRoute -> Section m -> Bool -- | Return Just the section or option at the specified path, or -- Nothing if this section contains no such path. lookup :: OptRoute -> Section m -> Maybe (Either (Section m) (Option m)) -- | Return Just the option at the specified path, or Nothing -- if this section doesn't contain an option at this path. lookupOpt :: [String] -> Section m -> Maybe (Option m) -- | Return Just the section at the specified path, or -- Nothing if this section doesn't contain a subsection at this -- path. lookupSub :: [String] -> Section m -> Maybe (Section m) -- | Add the specified option at the specified path under this section. If -- the section previously contained an option for this path, the old -- value is replaced. insert :: [String] -> Option m -> Section m -> Section m -- | Remove the option at the specified path, if present. If there is a -- section under this path, it won't be removed. deleteOpt :: [String] -> Section m -> Section m -- | Remove the section at the specified path, if present. If there is an -- option under this path, it won't be removed. deleteSub :: [String] -> Section m -> Section m -- | Remove the option or section at the specified path, if present. delete :: [String] -> Section m -> Section m module Data.Settings.Route -- | Split a path string into its components, if it's a valid path -- syntactically. parseRoute :: OptPath -> Maybe OptRoute -- | Create a string representation of a path, with the parts separated by -- periods. showRoute :: OptRoute -> OptPath module Data.Settings.Interface -- | TODO queryR :: MonadSettings m s => OptRoute -> m (Either SettingsError (Either ([SecName], [OptName]) String)) -- | TODO querySectionR :: MonadSettings m s => OptRoute -> m (Either SettingsError ([SecName], [OptName])) -- | TODO queryOptionR :: MonadSettings m s => OptRoute -> m (Either SettingsError String) -- | TODO updateOptionR :: MonadSettings m s => OptRoute -> String -> m (Maybe SettingsError) -- | TODO resetOptionR :: MonadSettings m s => OptRoute -> m (Maybe SettingsError) -- | TODO query :: MonadSettings m s => OptPath -> m (Either SettingsError (Either ([SecName], [OptName]) String)) -- | TODO querySection :: MonadSettings m s => OptPath -> m (Either SettingsError ([SecName], [OptName])) -- | TODO queryOption :: MonadSettings m s => OptPath -> m (Either SettingsError String) -- | TODO updateOption :: MonadSettings m s => OptPath -> String -> m (Maybe SettingsError) -- | TODO resetOption :: MonadSettings m s => OptPath -> m (Maybe SettingsError) -- | This top-level module contains just a tutorial, which you can read -- below. It will help you figure out which of the sub-modules you need, -- and how to use them. -- --

Tutorial

-- --

Concepts

-- -- For a real usage example, see the funbot package. -- -- This library works with 2 components of your application state: -- --
    --
  1. Application settings value, of any type you like. Usually this is -- a record of a type you define specifically for your application. It -- can be a value just for settings, or, if modifiable settings are -- stored in various parts of your state, it can be the state value -- itself.
  2. --
  3. A settings tree, of type Section (defined in -- Data.Settings.Types). This is a user interface component for -- accessing the settings values as a tree with labeled nodes. If your -- settings tree never changes, you can use a Haskell value directly for -- it. It if changes, add it to your application state so that it can be -- modified as needed during run time.
  4. --
-- -- The idea is that you freely use whatever you like for the settings -- values, and the settings tree is a UI component added on top without -- interfering with your program logic code. Persistence using simple -- periodic exports to JSON is available in the json-state -- package, but you can use any other solution as needed, e.g. the -- acid-state package. -- --

Settings Tree Basics

-- -- In order to understand the layers of the API, we'll examine it -- bottom-up. We'll start with the generic flexible parts and move -- towards the more specific but simpler and more convenient ones. You'll -- likely need a bit of both sides, so it's probably best to taste both. -- -- Suppose we're writing a terminal based text editor, like nano -- or vim. The UI allows the user to enter commands like get -- x.y.z or set x.y.z val which manipulate the settings. -- -- Let's define a type for settings. It may look like this: -- --
--   data Settings = Settings
--       { setsTabWidth    :: Int
--       , setsFont        :: String
--       , setsTextSize    :: Int
--       , setsColorScheme :: String
--       }
--   
-- -- For simplicity, suppose the settings tree won't be changing, so all we -- need in our application state is the settings. Let's use this: -- --
--   data AppState = AppState
--       { appOpenFiles :: [String]
--       , appUI        :: Widget
--       , appSettings  :: Settings
--       }
--   
-- -- If we wanted to allow the settings tree structure to change, we'd have -- a field for it too in the app state record. -- -- This will be our monad: -- --
--   type App = StateT AppState IO
--   
-- -- Now let's define a settings tree. A settings tree is the top-level -- section of it. Each such section consists of two things: A set -- of settings options, and a set of subsections. An empty tree looks -- like this: -- --
--   import Data.Settings.Section (empty)
--   
--   stree :: Section App
--   stree = empty
--   
-- -- Which is equivalent to: -- --
--   import qualified Data.HashMap.Lazy as M
--   
--   stree :: Section App
--   stree = Section
--       { secOpts = M.empty
--       , secSubs = M.empty
--       }
--   
-- -- The secOpts field is a map between option names and -- Option values. The secSubs field is a map between -- subsection names and Section values. We can then refer to a -- specific tree node using period-separated syntax. For example, if we -- have a tree with a single top-level option "a", we can refer -- to it in the UI simply a "a". If we have a tree with a -- subsection "s" and under it an option "a", we refer -- to that section as "a" and to the option under it as -- "s.a". And so on, we can have arbitrarily deep nesting of -- sections and options, e.g. "s.t.u.v.w.x.a". -- -- The low-level flexible way to define a settings tree is by using -- Option value contructors directly. Let's define a simple flat -- tree with 4 options and no subsections. -- -- The Option fields are monadic actions in our application -- monad, App. -- --
--   import Control.Monad.Trans.State
--   import qualified Data.HashMap.Lazy as M
--   import Data.Settings.Types
--   import Text.Read (readMaybe)
--   
--   -- Convenience wrappers to make the code shorter
--   -- Perhaps a good chance to use lens?
--   getS = gets appSettings
--   putS sets = modify $ \ app -> app { appSettings = sets }
--   modifyS f = modify $ \ app -> app { appSettings = f $ appSettings app }
--   
--   stree :: Section App
--   stree = Section
--       { secOpts = M.fromList
--           [ ( "tab-width"
--             , Option
--                 { optGet   = liftM (show . setsTabWidth) getS
--                 , optSet   = \ val ->
--                       case readMaybe val of
--                           Just n -> do
--                               modifyS $ \ s -> s { setsTabWidth = n }
--                               return Nothing
--                           Nothing -> return $ Just $ InvalidValueForType val
--                 , optReset = modifyS $ \ s -> s { setsTabWidth = 4 }
--                 }
--             )
--           , ( "font"
--             , Option {- ... similar fashion ... -}
--             )
--           , ( "text-size"
--             , Option {- ... similar fashion ... -}
--             )
--           , ( "color-scheme"
--             , Option {- ... similar fashion ... -}
--             )
--           ]
--       , secSubs = M.empty
--       }
--   
-- --

Building a Settings UI

-- -- We'll see higher level alternatives later. Let's see how to contruct -- the settings UI now. The Data.Settings.Iterface provides a set -- of high-level functions you can use on your UI code. You just need to -- wrap them with UI actions like error message (e.g. invalid value) and -- feedback for successful operations. -- -- Before we can use those functions, we need to make our application -- monad an instance of the MonadSettings (multi-parameter) -- typeclass: -- --
--   instance MonadSettings App Settings where
--       getSettings = getS
--       putSettings = putS
--       modifySettings = modifyS
--       getSTree = return stree
--   
-- -- Now, suppose the user enters the command get x.y.z in our -- text editor's command input line. This should return a friendly -- result. If x.y.z is a valid path in our settings tree leading -- to an option value, display that value. If it's a section, display a -- list of the options and subsections it contains. If it's neither, i.e. -- the path is invalid, report the error. -- -- Such a UI can easily be constructed using functions in -- Data.Settings.Interface, e.g. see the query function. -- Using the values it returns, you can construct UI strings to display -- on the screen. -- -- For example, in our case we'd want get to display the -- top-level tree contents, get tab-width to display a number (4 -- by default) and get foo to display an error no such option -- or section. -- --

Settings Tree Definition Tools

-- -- Let's go back to defining the settings tree. Some things we could -- improve: -- -- -- -- Let's start with the second point, wrapping typed settings values with -- UI, e.g. like the example given for booleans above. The -- Data.Settings.Option module provides the mkOptionV -- function. This function wraps the type details for us, if we supply -- instances of the OptionValue class. Let's define an instance -- for Int, which is the type of 2 out of the 4 fields in our -- Settings type. Generally, you'd want to define instances for -- all the relevant field types in your settings type, e.g. perhaps also -- Bool and Float and custom enum tyes and so on, depending -- on your requirements and UI designs. -- --
--   instace OptionValue Int where
--       readOption = readMaybe
--       showOption = show
--       typeName = const "Integer"
--   
-- -- And here's an instance for Bool: -- --
--   instace OptionValue Bool where
--       readOption s
--           | sl `elem` ["true, "yes", "on", "1"]   = Just True
--           | sl `elem` ["false", "no", "off", "0"] = Just False
--           | otherwise                             = Nothing
--           where sl = map toLower s
--       showOption = show
--       typeName = const "Boolean"
--   
-- -- Now, using mkOptionV, and this time also using the -- MonadSettings functions, we can redefine the tab width option -- like this: -- --
--   mkOptionV
--       (liftM setsTabWidth getSettings)
--       (\ n -> do
--           modifySettings $ \ s -> s { setsTabWidth = n }
--           return True
--       )
--       (modifySettings $ \ s -> s { setsTabWidth = 4 })
--   
-- -- Now let's improve further. This will be the highest level of the API. -- Given a MonadSettings instance, the repetitive parts of the -- code can be cleaned further, by using the mkOptionS function. -- --
--   mkOptionS
--       setsTabWidth
--       (\ n s -> Just s { setsTabWidth = n })
--       (\ s -> (Just 4, s { setsTabWidth = 4 }))
--       (const $ return ())
--   
-- -- Perhaps a bit cleaner form removing duplication is this: -- --
--   mkOptionS
--       setsTabWidth
--       (\ n s -> Just $ set n s)
--       (\ s -> (Just defval, set defval s))
--       (const $ return ())
--       where
--       set n s = s { setsTabWidth = n }
--       defval = 4
--   
-- -- The last argument is a callback action to be run when a successful set -- or reset of the value occurs. -- --

Settings Tree Dynamic Modification

-- -- Modification simply requires holding the tree as application state, -- and changing as needed. Removing sections, adding options and so on. -- There is an API in Data.Settings.Section for working with the -- settings tree, and since unordered maps are being used, you may also -- find Data.HashMap.Lazy useful (from unordered-containers -- package). module Data.Settings