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 +0/−115
- NEWS.md +139/−0
- settings.cabal +5/−7
- src/Data/Settings.hs +15/−11
- src/Data/Settings/Interface.hs +41/−16
- src/Data/Settings/Route.hs +35/−24
- src/Data/Settings/Section.hs +36/−41
- src/Data/Settings/Types.hs +28/−17
− 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