repl-toolkit 0.3.0.0 → 0.3.1.0
raw patch · 3 files changed
+96/−5 lines, 3 filesdep +aesondep +bytestringdep +data-defaultPVP ok
version bump matches the API change (PVP)
Dependencies added: aeson, bytestring, data-default, directory, system-filepath
API changes (from Hackage documentation)
+ System.REPL.Config: NoParseError :: Text -> NoParseError
+ System.REPL.Config: data NoParseError
+ System.REPL.Config: instance Eq NoParseError
+ System.REPL.Config: instance Exception NoParseError
+ System.REPL.Config: instance Read NoParseError
+ System.REPL.Config: instance Show NoParseError
+ System.REPL.Config: instance Typeable NoParseError
+ System.REPL.Config: readConfigFile :: (MonadThrow m, Functor m, MonadIO m, Default a, Exception e) => FilePath -> (ByteString -> Either e a) -> (a -> ByteString) -> m a
+ System.REPL.Config: readConfigJSON :: (MonadThrow m, Functor m, MonadIO m, Default a, ToJSON a, FromJSON a) => FilePath -> m a
+ System.REPL.Config: readConfigShow :: (MonadThrow m, Functor m, MonadIO m, Default a, Show a, Read a) => FilePath -> m a
Files
- System/REPL/Config.hs +88/−0
- changelog.txt +2/−0
- repl-toolkit.cabal +6/−5
+ System/REPL/Config.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE DeriveDataTypeable #-} + +-- |Contains logic for reading configuration files. +module System.REPL.Config ( + readConfigFile, + readConfigJSON, + readConfigShow, + NoParseError(..), + ) where + +import Prelude hiding ((++), FilePath) +import qualified Prelude as Pr + +import Control.Monad.Catch +import Control.Monad.IO.Class +import Data.Aeson +import qualified Data.ByteString.Lazy as BL +import Data.Default +import Data.Functor.Monadic +import Data.ListLike (ListLike(append), StringLike(fromString)) +import Data.Text.Lazy.Encoding (decodeUtf8, encodeUtf8) +import qualified Data.Text.Lazy as T +import Data.Typeable +import qualified Filesystem.Path.CurrentOS as Fp +import System.Directory +import Text.Read (readMaybe) + +-- |Indicates that some string was not able to be parsed. +data NoParseError = NoParseError T.Text deriving (Show, Eq, Read, Typeable) + +instance Exception NoParseError + +-- |Creates a NoParseError out of a 'Fp.FilePath'. +noParseError :: Fp.FilePath -> NoParseError +noParseError = NoParseError . T.pack . Fp.encodeString + +-- |Variant of 'readConfigFile' that uses 'Show' and 'Read' for (de)serialization. +-- +-- If the file's content's can't be parsed, a 'NoParseError' will be thrown. +readConfigShow :: forall m a. + (MonadThrow m, Functor m, MonadIO m, Default a, Show a, + Read a) + => Fp.FilePath + -> m a +readConfigShow path = readConfigFile path readEither showBL + where + showBL = encodeUtf8 . T.pack . show + readEither = maybe (Left $ noParseError path) Right . readMaybe . T.unpack . decodeUtf8 + +-- |Variant of 'readConfigFile' that uses JSON for (de)serialization. +-- +-- If the file's content's can't be parsed, a 'NoParseError' will be thrown. +readConfigJSON :: forall m a. + (MonadThrow m, Functor m, MonadIO m, Default a, ToJSON a, + FromJSON a) + => Fp.FilePath + -> m a +readConfigJSON path = readConfigFile path decodeEither encode + where + decodeEither = maybe (Left $ noParseError path) Right . decode + +-- |Tries to read a configuration from file. If the file is missing, +-- a default instance is written to file and returned. The following +-- exceptions may be thrown: +-- +-- * @IOException@, if the IO operations associated with reading or creating the +-- configuration file fail, and +-- * An exception of type @e@ if the configuration file is present, but its +-- contents can't be parsed. +readConfigFile :: forall e m a. + (MonadThrow m, Functor m, MonadIO m, Default a, Exception e) + => Fp.FilePath -- ^Path of the configuration file. + -> (BL.ByteString -> Either e a) + -- ^Parser for the file's contents. + -> (a -> BL.ByteString) + -- ^Encoder for the default value. If the given configuration + -- file does not exist, a default value will be serialized + -- using this function. + -> m a +readConfigFile path parser writer = do + let pathT = Fp.encodeString path + liftIO $ createDirectoryIfMissing True $ Fp.encodeString $ Fp.parent path + exists <- liftIO $ doesFileExist $ Fp.encodeString path + content <- if not exists then do liftIO $ BL.writeFile pathT (writer (def :: a)) + return $ Right def + else liftIO (BL.readFile pathT) >$> parser + either throwM return content
changelog.txt view
@@ -1,1 +1,3 @@+0.3.1 Added functionality for reading configuration files. + 0.3 Ditched MonadError in favour of MonadThrow. This should make the functions much easier to use.
repl-toolkit.cabal view
@@ -2,8 +2,8 @@ -- documentation, see http://haskell.org/cabal/users-guide/ name: repl-toolkit -version: 0.3.0.0 -synopsis: Toolkit for quickly whipping up command-line interfaces. +version: 0.3.1.0 +synopsis: Toolkit for quickly whipping up config files and command-line interfaces. description: A simple toolkit for quickly whipping up REPLs, input validation and sets of commands included. homepage: https://github.com/ombocomp/repl-toolkit license: Apache-2.0 @@ -20,8 +20,9 @@ location: git://github.com/ombocomp/repl-toolkit.git library - exposed-modules: System.REPL, System.REPL.State, System.REPL.Command - other-extensions: OverloadedStrings, DeriveDataTypeable, FlexibleContexts, LambdaCase - build-depends: base >=4.7 && <5, functor-monadic >=0.1, text >=1.1, ListLike >=4.1, exceptions >=0.4, parsec >=3.1, numericpeano >= 0.1, listsafe >= 0.1, monad-loops >= 0.3, mtl >= 2.2, transformers >= 0.3 + exposed-modules: System.REPL, System.REPL.State, System.REPL.Command, + System.REPL.Config + other-extensions: OverloadedStrings, DeriveDataTypeable, FlexibleContexts, LambdaCase, ScopedTypeVariables + build-depends: base >=4.7 && <5, functor-monadic >=0.1, text >=1.1, ListLike >=4.1, exceptions >=0.4, parsec >=3.1, numericpeano >= 0.1, listsafe >= 0.1, monad-loops >= 0.3, mtl >= 2.2, transformers >= 0.3, directory >= 1.2.1, system-filepath >= 0.4.13, bytestring >= 0.10, data-default >= 0.5.3, aeson >=0.8.0.2 default-language: Haskell2010