ini 0.3.6 → 0.4.0
raw patch · 3 files changed
+177/−23 lines, 3 filesdep +hspecdep +inidep +semigroupsdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: hspec, ini, semigroups
Dependency ranges changed: base
API changes (from Hackage documentation)
- Data.Ini: [unIni] :: Ini -> HashMap Text (HashMap Text Text)
- Data.Ini: instance Data.Semigroup.Semigroup Data.Ini.Ini
- Data.Ini: newtype Ini
+ Data.Ini: [iniGlobals] :: Ini -> [(Text, Text)]
+ Data.Ini: [iniSections] :: Ini -> HashMap Text [(Text, Text)]
+ Data.Ini: data Ini
+ Data.Ini: instance GHC.Base.Semigroup Data.Ini.Ini
+ Data.Ini: instance GHC.Classes.Eq Data.Ini.Ini
+ Data.Ini: lookupArray :: Text -> Text -> Ini -> Either String [Text]
+ Data.Ini: readArray :: Text -> Text -> (Text -> Either String (a, Text)) -> Ini -> Either String [a]
+ Data.Ini: unIni :: Ini -> HashMap Text (HashMap Text Text)
- Data.Ini: Ini :: HashMap Text (HashMap Text Text) -> Ini
+ Data.Ini: Ini :: HashMap Text [(Text, Text)] -> [(Text, Text)] -> Ini
- Data.Ini: sectionParser :: Parser (Text, HashMap Text Text)
+ Data.Ini: sectionParser :: Parser (Text, [(Text, Text)])
Files
- ini.cabal +12/−1
- src/Data/Ini.hs +98/−22
- test/Main.hs +67/−0
ini.cabal view
@@ -1,5 +1,5 @@ name: ini-version: 0.3.6+version: 0.4.0 synopsis: Quick and easy configuration files in the INI format. description: Quick and easy configuration files in the INI format. license: BSD3@@ -22,6 +22,17 @@ attoparsec, text, unordered-containers+ if !impl(ghc >= 8)+ build-depends: semigroups >= 0.10 && < 0.19++test-suite ini-test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Main.hs+ build-depends: base >= 4 && <5+ , ini+ , hspec+ , unordered-containers source-repository head type: git
src/Data/Ini.hs view
@@ -42,7 +42,9 @@ readIniFile ,parseIni ,lookupValue+ ,lookupArray ,readValue+ ,readArray ,parseValue ,sections ,keys@@ -57,6 +59,7 @@ ,writeIniFileWith -- * Types ,Ini(..)+ ,unIni -- * Parsers ,iniParser ,sectionParser@@ -71,17 +74,34 @@ import Data.Char import Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as M-import Data.Semigroup+import Data.Maybe import Data.Monoid (Monoid)+import Data.Semigroup import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.IO as T import Prelude hiding (takeWhile) -- | An INI configuration.-newtype Ini = Ini { unIni :: HashMap Text (HashMap Text Text) }- deriving (Show, Semigroup, Monoid)+data Ini =+ Ini+ { iniSections :: HashMap Text [(Text, Text)]+ , iniGlobals :: [(Text, Text)]+ }+ deriving (Show, Eq) +instance Semigroup Ini where+ (<>) = mappend++instance Monoid Ini where+ mempty = Ini {iniGlobals = mempty, iniSections = mempty}+ mappend x y =+ Ini {iniGlobals = mempty, iniSections = iniSections x <> iniSections y}++{-# DEPRECATED #-}+unIni :: Ini -> HashMap Text (HashMap Text Text)+unIni = fmap M.fromList . iniSections+ -- | Parse an INI file. readIniFile :: FilePath -> IO (Either String Ini) readIniFile = fmap parseIni . T.readFile@@ -90,36 +110,89 @@ parseIni :: Text -> Either String Ini parseIni = parseOnly iniParser --- | Lookup values in the config.-lookupValue :: Text -> Text -> Ini -> Either String Text-lookupValue name key (Ini ini) =- case M.lookup name ini of+-- | Lookup one value in the config.+--+-- Example:+--+-- >>> parseIni "[SERVER]\nport: 6667\nhostname: localhost" >>= lookupValue "SERVER" "hostname"+-- Right "localhost"+lookupValue :: Text -- ^ Section name+ -> Text -- ^ Key+ -> Ini -> Either String Text+lookupValue name key (Ini {iniSections=secs}) =+ case M.lookup name secs of Nothing -> Left ("Couldn't find section: " ++ T.unpack name) Just section ->- case M.lookup key section of+ case lookup key section of Nothing -> Left ("Couldn't find key: " ++ T.unpack key) Just value -> return value +-- | Lookup one value in the config.+--+-- Example:+--+-- >>> parseIni "[SERVER]\nport: 6667\nhostname: localhost" >>= lookupValue "SERVER" "hostname"+-- Right "localhost"+lookupArray :: Text -- ^ Section name+ -> Text -- ^ Key+ -> Ini -> Either String [Text]+lookupArray name key (Ini {iniSections = secs}) =+ case M.lookup name secs of+ Nothing -> Left ("Couldn't find section: " ++ T.unpack name)+ Just section ->+ case mapMaybe+ (\(k, v) ->+ if k == key+ then Just v+ else Nothing)+ section of+ [] -> Left ("Couldn't find key: " ++ T.unpack key)+ values -> return values+ -- | Get the sections in the config.+--+-- Example:+--+-- >>> sections <$> parseIni "[SERVER]\nport: 6667\nhostname: localhost"+-- Right ["SERVER"] sections :: Ini -> [Text]-sections (Ini ini) = M.keys ini+sections = M.keys . iniSections -- | Get the keys in a section.-keys :: Text -> Ini -> Either String [Text]-keys name (Ini ini) =- case M.lookup name ini of+--+-- Example:+--+-- >>> parseIni "[SERVER]\nport: 6667\nhostname: localhost" >>= keys "SERVER"+-- Right ["hostname","port"]+keys :: Text -- ^ Section name+ -> Ini -> Either String [Text]+keys name i =+ case M.lookup name (iniSections i) of Nothing -> Left ("Couldn't find section: " ++ T.unpack name)- Just section -> Right (M.keys section)+ Just section -> Right (map fst section) -- | Read a value using a reader from "Data.Text.Read".-readValue :: Text -> Text -> (Text -> Either String (a, Text))+readValue :: Text -- ^ Section name+ -> Text -- ^ Key+ -> (Text -> Either String (a, Text)) -> Ini -> Either String a readValue section key f ini = lookupValue section key ini >>= f >>= return . fst +-- | Read an array of values using a reader from "Data.Text.Read".+readArray :: Text -- ^ Section name+ -> Text -- ^ Key+ -> (Text -> Either String (a, Text))+ -> Ini+ -> Either String [a]+readArray section key f ini =+ fmap (map fst) (lookupArray section key ini >>= mapM f)+ -- | Parse a value using a reader from "Data.Attoparsec.Text".-parseValue :: Text -> Text -> Parser a+parseValue :: Text -- ^ Section name+ -> Text -- ^ Key+ -> Parser a -> Ini -> Either String a parseValue section key f ini =@@ -156,11 +229,11 @@ -- | Print an INI config. printIniWith :: WriteIniSettings -> Ini -> Text-printIniWith wis (Ini ini) =- T.concat (map buildSection (M.toList ini))+printIniWith wis i =+ T.concat (map buildSection (M.toList (iniSections i))) where buildSection (name,pairs) = "[" <> name <> "]\n" <>- T.concat (map buildPair (M.toList pairs))+ T.concat (map buildPair pairs) buildPair (name,value) = name <> separator <> value <> "\n" separator = case writeIniKeySeparator wis of@@ -169,10 +242,13 @@ -- | Parser for an INI. iniParser :: Parser Ini-iniParser = fmap (Ini . M.fromList) (many sectionParser)+iniParser =+ (\kv secs -> Ini {iniSections = M.fromList secs, iniGlobals = kv}) <$>+ many keyValueParser <*>+ many sectionParser -- | A section. Format: @[foo]@. Conventionally, @[FOO]@.-sectionParser :: Parser (Text,HashMap Text Text)+sectionParser :: Parser (Text,[(Text, Text)]) sectionParser = do skipEndOfLine skipComments@@ -182,7 +258,7 @@ _ <- char ']' skipEndOfLine values <- many keyValueParser- return (T.strip name, M.fromList values)+ return (T.strip name, values) -- | A key-value pair. Either @foo: bar@ or @foo=bar@. keyValueParser :: Parser (Text,Text)@@ -204,7 +280,7 @@ -- | Skip end of line and whitespace beyond. skipEndOfLine :: Parser ()-skipEndOfLine = skipWhile (\c -> isEndOfLine c)+skipEndOfLine = skipWhile isSpace -- | Skip comments starting at the beginning of the line. skipComments :: Parser ()
+ test/Main.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Simple test suite.++module Main where++import qualified Data.HashMap.Strict as HM+import Data.Ini+import Test.Hspec++main :: IO ()+main =+ hspec+ (do describe+ "Regular files"+ (do it+ "Multi-section file with comments"+ (shouldBe+ (parseIni+ "# Some comment.\n\+ \[SERVER]\n\+ \port=6667\n\+ \hostname=localhost\n\+ \[AUTH]\n\+ \user=hello\n\+ \pass=world\n\+ \# Salt can be an empty string.\n\+ \salt=")+ (Right+ (Ini+ { iniSections =+ HM.fromList+ [ ( "AUTH"+ , [ ("user", "hello")+ , ("pass", "world")+ , ("salt", "")+ ])+ , ( "SERVER"+ , [("port", "6667"), ("hostname", "localhost")])+ ]+ , iniGlobals = []+ })))+ it+ "File with globals"+ (shouldBe+ (parseIni+ "# Some comment.\n\+ \port=6667\n\+ \hostname=localhost\n\+ \[AUTH]\n\+ \user=hello\n\+ \pass=world\n\+ \# Salt can be an empty string.\n\+ \salt=")+ (Right+ (Ini+ { iniSections =+ HM.fromList+ [ ( "AUTH"+ , [ ("user", "hello")+ , ("pass", "world")+ , ("salt", "")+ ])+ ]+ , iniGlobals =+ [("port", "6667"), ("hostname", "localhost")]+ })))))