packages feed

settings 0.2.2.0 → 0.3.0.0

raw patch · 8 files changed

+299/−231 lines, 8 filesdep +textdep −aesondep −aeson-prettydep −bytestring

Dependencies added: text

Dependencies removed: aeson, aeson-pretty, bytestring, time-units

Files

− NEWS
@@ -1,115 +0,0 @@-This file lists the user-visible interesting changes between releases. For a-full list of changes to the source, see the ChangeLog.----settings 0.2.2.0 -- 2015-12-17-==============================--General, build and documentation changes:--* (None)--New APIs, features and enhancements:--* Support arbitrary esc/sep chars in option paths, and allow escaping them.--Bug fixes:--* (None)--Dependency changes:--* (None)------settings 0.2.1.0 -- 2015-10-17-==============================--General, build and documentation changes:--* (None)--New APIs, features and enhancements:--* Add insertSub and insertOpt functions for section editing--Bug fixes:--* (None)--Dependency changes:--* (None)------settings 0.2.0.0 -- 2015-09-17-==============================--General, build and documentation changes:--* (None)--New APIs, features and enhancements:--* Remove persistence API, this is now provided by the json-state package--Bug fixes:--* (None)--Dependency changes:--* (None)------settings 0.1.0.1 -- 2015-09-14-==============================--General, build and documentation changes:--* Write a tutorial, see Data.Settings module--New APIs, features and enhancements:--* (None)--Bug fixes:--* (None)--Dependency changes:--* (None)------settings 0.1.0.0 -- 2015-09-10-==============================--General, build and documentation changes:--* (This is the first release, so everything is new)--New APIs, features and enhancements:--* (This is the first release, so everything is a new feature)--Bug fixes:--* (This is just the first release)--Dependency changes:--* (This is the first release)
+ NEWS.md view
@@ -0,0 +1,139 @@+This file lists the user-visible interesting changes between releases. For a+full list of changes to the source, see the ChangeLog.++++settings 0.3.0.0 -- 2016-01-27+==============================++General, build and documentation changes:++* Add missing doc comments++New APIs, features and enhancements:++* Move the entire API from `String` to strict `Text`++Bug fixes:++* (None)++Dependency changes:++* Remove unused dependencies which were left there by mistake, e.g. aeson+* Add text >= 1++++++settings 0.2.2.0 -- 2015-12-17+==============================++General, build and documentation changes:++* (None)++New APIs, features and enhancements:++* Support arbitrary esc/sep chars in option paths, and allow escaping them.++Bug fixes:++* (None)++Dependency changes:++* (None)++++++settings 0.2.1.0 -- 2015-10-17+==============================++General, build and documentation changes:++* (None)++New APIs, features and enhancements:++* Add insertSub and insertOpt functions for section editing++Bug fixes:++* (None)++Dependency changes:++* (None)++++++settings 0.2.0.0 -- 2015-09-17+==============================++General, build and documentation changes:++* (None)++New APIs, features and enhancements:++* Remove persistence API, this is now provided by the json-state package++Bug fixes:++* (None)++Dependency changes:++* (None)++++++settings 0.1.0.1 -- 2015-09-14+==============================++General, build and documentation changes:++* Write a tutorial, see Data.Settings module++New APIs, features and enhancements:++* (None)++Bug fixes:++* (None)++Dependency changes:++* (None)++++++settings 0.1.0.0 -- 2015-09-10+==============================++General, build and documentation changes:++* (This is the first release, so everything is new)++New APIs, features and enhancements:++* (This is the first release, so everything is a new feature)++Bug fixes:++* (This is just the first release)++Dependency changes:++* (This is the first release)
settings.cabal view
@@ -1,5 +1,5 @@ name:                settings-version:             0.2.2.0+version:             0.3.0.0 synopsis:            Runtime-editable program settings. description:   This library aims to be a tool for constructing a settings management UI on@@ -22,7 +22,7 @@ copyright:           ♡ Copying is an act of love. Please copy, reuse and share. category:            Data, User Interfaces build-type:          Simple-extra-source-files:  AUTHORS ChangeLog COPYING INSTALL NEWS README.md+extra-source-files:  AUTHORS ChangeLog COPYING INSTALL NEWS.md README.md cabal-version:       >=1.10  source-repository head@@ -38,11 +38,9 @@                      , Data.Settings.Types   -- other-modules:          -- other-extensions:    -  build-depends:       aeson-                     , aeson-pretty         >=0.7-                     , base                 >=4.7 && <5-                     , bytestring-                     , time-units           >=1+  build-depends:       base                 >=4.7 && <5+                     , text                 >=1                      , unordered-containers >=0.2.5   hs-source-dirs:      src   default-language:    Haskell2010+  ghc-options:         -Wall
src/Data/Settings.hs view
@@ -56,16 +56,16 @@ -- -- > data Settings = Settings -- >     { setsTabWidth    :: Int--- >     , setsFont        :: String+-- >     , setsFont        :: T.Text -- >     , setsTextSize    :: Int--- >     , setsColorScheme :: String+-- >     , setsColorScheme :: T.Text -- >     } -- -- 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]+-- >     { appOpenFiles :: [FilePath] -- >     , appUI        :: Widget -- >     , appSettings  :: Settings -- >     }@@ -116,11 +116,15 @@ -- -- The 'Option' fields are monadic actions in our application monad, @App@. --+-- > {-# LANGUAGE OverloadedStrings #-}+-- > -- > import Control.Monad.Trans.State--- > import qualified Data.HashMap.Lazy as M -- > import Data.Settings.Types -- > import Text.Read (readMaybe) -- >+-- > import qualified Data.HashMap.Lazy as M+-- > import qualified Data.Text as T+-- > -- > -- Convenience wrappers to make the code shorter -- > -- Perhaps a good chance to use lens? -- > getS = gets appSettings@@ -132,9 +136,9 @@ -- >     { secOpts = M.fromList -- >         [ ( "tab-width" -- >           , Option--- >               { optGet   = liftM (show . setsTabWidth) getS+-- >               { optGet   = liftM (T.pack . show . setsTabWidth) getS -- >               , optSet   = \ val ->--- >                     case readMaybe val of+-- >                     case readMaybe $ T.unpack val of -- >                         Just n -> do -- >                             modifyS $ \ s -> s { setsTabWidth = n } -- >                             return Nothing@@ -219,8 +223,8 @@ -- UI designs. -- -- > instace OptionValue Int where--- >     readOption = readMaybe--- >     showOption = show+-- >     readOption = readMaybe . T.unpack+-- >     showOption = T.pack . show -- >     typeName = const "Integer" -- -- And here's an instance for 'Bool':@@ -230,8 +234,8 @@ -- >         | 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+-- >         where sl = T.toLower s+-- >     showOption = bool "False" "True" -- >     typeName = const "Boolean" -- -- Now, using @mkOptionV@, and this time also using the @MonadSettings@@@ -275,5 +279,5 @@ -- 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).+-- (from @unordered-containers@ package). module Data.Settings where
src/Data/Settings/Interface.hs view
@@ -36,14 +36,21 @@ import Data.Settings.Types import Prelude hiding (lookup) +import qualified Data.Text as T+ ------------------------------------------------------------------------------- -- By Route ------------------------------------------------------------------------------- --- | TODO+-- | Get what the settings tree contains at the given route.+--+-- * Return an error description if there is no such route in the tree.+-- * If there is a section at the route, return a list of subsection and option+--   names under it.+-- * If there is an option at the route, return its value. queryR :: MonadSettings m s        => OptRoute-       -> m (Either SettingsError (Either ([SecName], [OptName]) String))+       -> m (Either SettingsError (Either ([SecName], [OptName]) T.Text)) queryR route = do     t <- getSTree     case lookup route t of@@ -52,7 +59,12 @@         Just (Right opt) -> liftM (Right . Right) $ optGet opt         Nothing          -> return . Left $ NoSuchNode route --- | TODO+-- | Get info about a settings section at the given route, if there is one.+--+-- * Return an error description if there is no such route in the tree or it+--   doesn't point to a section.+-- * If there is a section at the route, return a list of subsection and option+--   names under it. querySectionR :: MonadSettings m s               => OptRoute               -> m (Either SettingsError ([SecName], [OptName]))@@ -63,10 +75,14 @@         Right (Left secsOpts) -> Right secsOpts         Right (Right _val)    -> Left $ NoSuchSection route --- | TODO+-- | Get the value of the option at the given route.+--+-- * Return an error description if there is no such route in the tree or it+--   doesn't point to an option.+-- * If there is an option at the route, return its value. queryOptionR :: MonadSettings m s              => OptRoute-             -> m (Either SettingsError String)+             -> m (Either SettingsError T.Text) queryOptionR route = do     result <- queryR route     return $ case result of@@ -74,10 +90,12 @@         Right (Left _secsOpts) -> Left $ NoSuchOption route         Right (Right val)      -> Right val --- | TODO+-- | Change the value of the option at the given route. Return an error+-- description if there is no such route in the tree, or it doesn't point to an+-- option, or the value given is invalid. updateOptionR :: MonadSettings m s               => OptRoute-              -> String+              -> T.Text               -> m (Maybe SettingsError) updateOptionR route val = do     t <- getSTree@@ -85,7 +103,9 @@         Just opt -> optSet opt val         Nothing  -> return $ Just $ NoSuchOption route --- | TODO+-- | Reset the value of the option at the given route to the default. Return an+-- error description if there is no such route in the tree or it doesn't point+-- to an option. resetOptionR :: MonadSettings m s => OptRoute -> m (Maybe SettingsError) resetOptionR route = do     t <- getSTree@@ -115,29 +135,34 @@         Just route -> f route         Nothing    -> return $ Left $ InvalidPath path --- | TODO+-- | Like 'queryR', but takes a path and first tries to parse it into a route,+-- returning an error description if the path is invalid. query :: MonadSettings m s       => OptPath-      -> m (Either SettingsError (Either ([SecName], [OptName]) String))+      -> m (Either SettingsError (Either ([SecName], [OptName]) T.Text)) query = byPathEither queryR --- | TODO+-- | Like 'querySectionR', but takes a path and first tries to parse it into a+-- route, returning an error description if the path is invalid. querySection :: MonadSettings m s              => OptPath              -> m (Either SettingsError ([SecName], [OptName])) querySection = byPathEither querySectionR --- | TODO-queryOption :: MonadSettings m s => OptPath -> m (Either SettingsError String)+-- | Like 'queryOptionR', but takes a path and first tries to parse it into a+-- route, returning an error description if the path is invalid.+queryOption :: MonadSettings m s => OptPath -> m (Either SettingsError T.Text) queryOption = byPathEither queryOptionR --- | TODO+-- | Like 'updateOptionR', but takes a path and first tries to parse it into a+-- route, returning an error description if the path is invalid. updateOption :: MonadSettings m s              => OptPath-             -> String+             -> T.Text              -> m (Maybe SettingsError) updateOption path val = byPathMaybe (flip updateOptionR val) path --- | TODO+-- | Like 'resetOptionR', but takes a path and first tries to parse it into a+-- route, returning an error description if the path is invalid. resetOption :: MonadSettings m s => OptPath -> m (Maybe SettingsError) resetOption = byPathMaybe resetOptionR
src/Data/Settings/Route.hs view
@@ -21,41 +21,52 @@     ) where -import Data.List (intercalate) import Data.Settings.Types +import qualified Data.Text as T+ -- | Split a path string into its components, if it's a valid path -- syntactically. parseRoute :: OptPath -> Maybe OptRoute parseRoute = parseRoute' '\\' '.'-{-parseRoute "" = Just []-parseRoute s  =-    case break (== '.') s of-        ("", _)     -> Nothing-        (p, "")     -> Just [p]-        (p, (c:cs)) ->-            case parseRoute cs of-                Nothing -> Nothing-                Just ps -> Just $ p : ps-}  -- | Like 'parseRoute', but allows to choose the escape character (e.g. @'\'@) -- and the path separator character (e.g. @'.'@). parseRoute' :: Char -> Char -> OptPath -> Maybe OptRoute-parseRoute' esc sep = f [] ""+parseRoute' esc sep = f [] T.empty     where-    f []    ""   "" = Just []+    {-f []    ""   "" = Just []     f route ""   "" = Nothing-    f route part "" = Just $ reverse $ reverse part : route+    f route part "" = Just $ reverse $ part : route     f route part [c]         | c == esc             = Nothing         | c == sep             = Nothing-        | otherwise            = f route (c : part) ""+        | otherwise            = f route (part `T.snoc` c) ""     f route part (a:r@(b:s))-        | a == esc && b == esc = f route (esc : part) s-        | a == esc && b == sep = f route (sep : part) s+        | a == esc && b == esc = f route (part `T.snoc` esc) s+        | a == esc && b == sep = f route (part `T.snoc` sep) s         | a == esc             = Nothing-        | a == sep             = f (reverse part : route) "" r-        | otherwise            = f route (a : part) r+        | a == sep             = f (part : route) "" r+        | otherwise            = f route (part `T.snoc` a) r+    -}+    f route part path =+        case T.uncons path of+            Nothing ->+                if T.null part+                    then if null route then Just [] else Nothing+                    else Just $ reverse $ part : route+            Just (a, r) ->+                case T.uncons r of+                    Nothing+                        | a == esc  -> Nothing+                        | a == sep  -> Nothing+                        | otherwise -> f route (part `T.snoc` a) T.empty+                    Just (b, s)+                        | a == esc && b == esc -> f route (part `T.snoc` esc) s+                        | a == esc && b == sep -> f route (part `T.snoc` sep) s+                        | a == esc             -> Nothing+                        | a == sep             -> f (part : route) T.empty r+                        | otherwise            -> f route (part `T.snoc` a) r  -- | Create a string representation of a path, with the parts separated by -- periods, and literal periods escaped using backslashes.@@ -65,10 +76,10 @@ -- | Like 'showRoute', but allows to choose the escape character (e.g. @'\\'@) -- and the path separator character (e.g. @'.'@). showRoute' :: Char -> Char -> OptRoute -> OptPath-showRoute' esc sep = intercalate [sep] . map escape+showRoute' esc sep = T.intercalate (T.singleton sep) . map escape     where-    escape = foldr f ""-    f c s-        | c == esc  = esc : esc : s-        | c == sep  = esc : sep : s-        | otherwise = c : s+    escape = T.foldl f T.empty+    f s c+        | c == esc  = s `T.snoc` esc `T.snoc` esc+        | c == sep  = s `T.snoc` esc `T.snoc` sep+        | otherwise = s `T.snoc` c
src/Data/Settings/Section.hs view
@@ -33,7 +33,6 @@     , lookupOpt     , lookupSub       -- * Modification-    , insert     , insertOpt     , insertSub     , deleteOpt@@ -43,10 +42,11 @@ where  import Data.Either (isRight)-import qualified Data.HashMap.Lazy as M import Data.Settings.Types import Prelude hiding (lookup, null) +import qualified Data.HashMap.Lazy as M+ -- | Construct an empty section, no options and no subsections. empty :: Section m empty = Section M.empty M.empty@@ -90,8 +90,8 @@     let (m, o) = member route sec     in  m && not o --- | Return 'Just' the section or option at the specified path, or 'Nothing' if--- this section contains no such path.+-- | Return 'Just' the section or option at the specified route, or 'Nothing'+-- if this section contains no such route. lookup :: OptRoute -> Section m -> Maybe (Either (Section m) (Option m)) lookup []     sec = Just $ Left sec lookup [name] sec =@@ -104,36 +104,31 @@         Just s  -> lookup ns s         Nothing -> Nothing --- | 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)-lookupOpt path sec =-    case lookup path sec of+-- | Return 'Just' the option at the specified route, or 'Nothing' if this+-- section doesn't contain an option at this route.+lookupOpt :: OptRoute -> Section m -> Maybe (Option m)+lookupOpt route sec =+    case lookup route sec of         Just (Right o) -> Just o         _              -> Nothing --- | 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)-lookupSub path sec =-    case lookup path sec of+-- | Return 'Just' the section at the specified route, or 'Nothing' if this+-- section doesn't contain a subsection at this route.+lookupSub :: OptRoute -> Section m -> Maybe (Section m)+lookupSub route sec =+    case lookup route sec of         Just (Left s) -> Just s         _             -> Nothing ---- | Alias for 'insertOpt'.-insert :: [String] -> Option m -> Section m -> Section m-insert = insertOpt---- | 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+-- | Add the specified option at the specified route under this section. If the+-- section previously contained an option for this route, the old value is -- replaced.-insertOpt :: [String]  -- ^ Path at which to place the option+insertOpt :: OptRoute  -- ^ Route at which to place the option           -> Option m  -- ^ Option to insert           -> Section m -- ^ Root section under which to insert           -> Section m-insertOpt path opt s@(Section opts subs) =-    case path of+insertOpt route opt s@(Section opts subs) =+    case route of         []     -> s         [name] -> Section (M.insert name opt opts) subs         (n:ns) ->@@ -145,15 +140,15 @@                 subs' = M.insert n sub' subs             in  Section opts subs' --- | Add the specified subsection at the specified path under this section. If--- the section previously contained a subsection for this path, the old value+-- | Add the specified subsection at the specified route under this section. If+-- the section previously contained a subsection for this route, the old value -- is replaced.-insertSub :: [String]  -- ^ Path at which to place the subsection+insertSub :: OptRoute  -- ^ Route at which to place the subsection           -> Section m -- ^ Subsection to insert           -> Section m -- ^ Root section under which to insert           -> Section m-insertSub path sec s@(Section opts subs) =-    case path of+insertSub route sec s@(Section opts subs) =+    case route of         []     -> s         [name] -> Section opts (M.insert name sec subs)         (n:ns) ->@@ -165,27 +160,27 @@                 subs' = M.insert n sub' subs             in  Section opts subs' -deleteImpl :: Bool -> Bool -> [String] -> Section m -> Section m-deleteImpl delO delS path sec = d path sec+deleteImpl :: Bool -> Bool -> OptRoute -> Section m -> Section m+deleteImpl delO delS = d     where-    d path sec@(Section opts subs) =-        case path of+    d route sec@(Section opts subs) =+        case route of             []     -> sec             [name] -> Section                         (if delO then M.delete name opts else opts)                         (if delS then M.delete name subs else subs)             (n:ns) -> Section opts $ M.adjust (d ns) n subs --- | 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 option at the specified route, if present. If there is a+-- section under this route, it won't be removed.+deleteOpt :: OptRoute -> Section m -> Section m deleteOpt = deleteImpl True False --- | 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 section at the specified route, if present. If there is an+-- option under this route, it won't be removed.+deleteSub :: OptRoute -> Section m -> Section m deleteSub = deleteImpl False True --- | Remove the option or section at the specified path, if present.-delete :: [String] -> Section m -> Section m+-- | Remove the option or section at the specified route, if present.+delete :: OptRoute -> Section m -> Section m delete = deleteImpl True True
src/Data/Settings/Types.hs view
@@ -28,45 +28,56 @@     ) where -import qualified Data.HashMap.Lazy as M+import Data.HashMap.Lazy (HashMap)+import Data.Text (Text)  -- | 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-    { -- |-      optGet   :: m String-      -- |-    , optSet   :: String -> m (Maybe SettingsError)-      -- |+    { -- | A monadic action which returns the value of the option.+      optGet   :: m Text+      -- | A monadic action which tries to set the option to the given valie,+      -- and returns whether it succeeded.+    , optSet   :: Text -> m (Maybe SettingsError)+      -- | A monadic action which resets the option to its default value.     , optReset :: m ()     } +-- | A settings section in the settings UI tree. data Section m = Section-    { secOpts :: M.HashMap String (Option m)-    , secSubs :: M.HashMap String (Section m)+    { -- | The options located under this section.+      secOpts :: HashMap Text (Option m)+      -- | Subsections located under this section.+    , secSubs :: HashMap Text (Section m)     } -type OptName = String+-- | An opion name.+type OptName = Text -type SecName = String+-- | A section name.+type SecName = Text -type OptPath = String+-- | An option path string.+type OptPath = Text -type OptRoute = [String]+-- | An option route, i.e. a list of section names ending with an option name,+-- which describes a path in the settings UI tree leading to that option.+type OptRoute = [Text] +-- | An error occuring during an operation on the settings. data SettingsError     = InvalidPath OptPath     | NoSuchOption OptRoute     | NoSuchSection OptRoute     | NoSuchNode OptRoute-    | InvalidValueForType String -- the value as a string-    | InvalidValue String        -- error description+    | InvalidValueForType Text -- the value as a string+    | InvalidValue Text        -- error description  class OptionValue v where-    readOption :: String -> Maybe v-    showOption :: v -> String-    typeName   :: v -> String+    readOption :: Text -> Maybe v+    showOption :: v -> Text+    typeName   :: v -> Text  class Monad m => MonadSettings m s | m -> s where     getSettings    :: m s