packages feed

ini 0.4.2 → 0.5.1

raw patch · 5 files changed

+238/−91 lines, 5 filesdep +QuickChecknew-uploaderPVP ok

version bump matches the API change (PVP)

Dependencies added: QuickCheck

API changes (from Hackage documentation)

+ Data.Ini: instance GHC.Enum.Bounded Data.Ini.KeySeparator
+ Data.Ini: instance GHC.Enum.Enum Data.Ini.KeySeparator

Files

CHANGELOG.md view
@@ -1,3 +1,27 @@+## 0.5.1++_2025-12-20, Jan Hrček_++- Fixed a parser bug where parsing would fail in presence of comments at the end of file+[#12](https://github.com/jhrcek/ini/pull/12)++## 0.5.0++_2023-02-08, Chris Martin_++The behavior of `(<>)` for the `Ini` type has changed+[#2](https://github.com/andreasabel/ini/issues/2)++- `<>` previously discarded all `iniGlobals`. Now it concatenates+  the globals from the two `Ini` values.++- When two `Ini` values contained `iniSections` with the same name,+  `<>` previously returned the section from the left value and+  discarded the section of the same name from the right value.+  Now it concatenates the sections of the same name.++Tested with GHC 7.0 - ghc-9.6.0.20230128.+ ## 0.4.2  _2022-07-26, Andreas Abel_
README.md view
@@ -1,7 +1,7 @@ [![Hackage](https://img.shields.io/hackage/v/ini.svg?color=informational)](https://hackage.haskell.org/package/ini) [![ini on Stackage Nightly](https://stackage.org/package/ini/badge/nightly)](https://stackage.org/nightly/package/ini) [![Stackage LTS version](https://www.stackage.org/package/ini/badge/lts?label=Stackage)](https://www.stackage.org/package/ini)-[![Haskell CI](https://github.com/andreasabel/ini/actions/workflows/haskell.yml/badge.svg)](https://github.com/andreasabel/ini/actions/workflows/haskell.yml)+[![Haskell CI](https://github.com/jhrcek/ini/actions/workflows/haskell-ci.yml/badge.svg)](https://github.com/jhrcek/ini/actions/workflows/haskell-ci.yml)  ini ===
ini.cabal view
@@ -1,34 +1,32 @@ cabal-version:       >= 1.10 name:                ini-version:             0.4.2+version:             0.5.1 synopsis:            Configuration files in the INI format. description:         Quick and easy configuration files in the INI format. license:             BSD3 license-file:        LICENSE author:              Chris Done-maintainer:          Andreas Abel-homepage:            https://github.com/andreasabel/ini-bug-reports:         https://github.com/andreasabel/ini/issues+maintainer:          Jan Hrček+homepage:            https://github.com/jhrcek/ini+bug-reports:         https://github.com/jhrcek/ini/issues copyright:           2013 Chris Done category:            Data, Configuration build-type:          Simple  tested-with:-  GHC == 9.4.1-  GHC == 9.2.3-  GHC == 9.0.2-  GHC == 8.10.7-  GHC == 8.8.4-  GHC == 8.6.5-  GHC == 8.4.4-  GHC == 8.2.2-  GHC == 8.0.2-  GHC == 7.10.3-  GHC == 7.8.4-  GHC == 7.6.3-  GHC == 7.4.2-  GHC == 7.2.2-  GHC == 7.0.4+  GHC == 9.12.2+   || == 9.10.3+   || == 9.8.4+   || == 9.6.7+   || == 9.4.8+   || == 9.2.8+   || == 9.0.2+   || == 8.10.7+   || == 8.8.4+   || == 8.6.5+   || == 8.4.4+   || == 8.2.2+   || == 8.0.2  extra-source-files:   CHANGELOG.md@@ -56,13 +54,16 @@   type: exitcode-stdio-1.0   hs-source-dirs: test   main-is: Main.hs+  ghc-options:     -Wall   build-depends:     base >= 4 && <5+                   , QuickCheck                    , ini                    , hspec+                   , text                    , unordered-containers    default-language:  Haskell98  source-repository head   type:     git-  location: https://github.com/andreasabel/ini.git+  location: https://github.com/jhrcek/ini.git
src/Data/Ini.hs view
@@ -1,5 +1,4 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE OverloadedStrings #-}  -- | Clean configuration files in the INI format. --@@ -89,8 +88,13 @@     }   deriving (Show, Eq) +-- | '<>' concatenates the lists of entries within each section (since @ini-0.5.0@) instance Semigroup Ini where-  x <> y = Ini {iniGlobals = mempty, iniSections = iniSections x <> iniSections y}+  x <> y =+    Ini+      { iniGlobals = iniGlobals x ++ iniGlobals y+      , iniSections = M.unionWith (++) (iniSections x) (iniSections y)+      }  instance Monoid Ini where   mempty = Ini {iniGlobals = mempty, iniSections = mempty}@@ -209,7 +213,7 @@ data KeySeparator   = ColonKeySeparator   | EqualsKeySeparator-  deriving (Eq, Show)+  deriving (Bounded, Enum, Eq, Show)  -- | Settings determining how an INI file is written. data WriteIniSettings = WriteIniSettings@@ -229,8 +233,8 @@ -- | Print an INI config. printIniWith :: WriteIniSettings -> Ini -> Text printIniWith wis i =-  T.concat $ (map buildPair (iniGlobals i)) ++-             (map buildSection (M.toList (iniSections i)))+  T.concat $ map buildPair (iniGlobals i) +++             map buildSection (M.toList (iniSections i))   where buildSection (name,pairs) =           "[" <> name <> "]\n" <>           T.concat (map buildPair pairs)@@ -246,6 +250,7 @@   (\kv secs -> Ini {iniSections = M.fromList secs, iniGlobals = kv}) <$>   many keyValueParser <*>   many sectionParser <*+  skipComments <*   (endOfInput <|> (fail . T.unpack =<< takeWhile (not . isControl)))  -- | A section. Format: @[foo]@. Conventionally, @[FOO]@.
test/Main.hs view
@@ -1,74 +1,191 @@ {-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wno-orphans #-}  -- | Simple test suite.  module Main where +import           Data.Char (isControl, isSpace) import qualified Data.HashMap.Strict as HM-import           Data.Ini-import           Test.Hspec+import           Data.Ini (Ini (..), KeySeparator (..), WriteIniSettings (..), parseIni, printIniWith)+import           Data.Text (Text)+import qualified Data.Text as T+import           Test.Hspec (describe, hspec, it, shouldBe)+import           Test.QuickCheck  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")]-                         })))-              it-                "File with invalid keys"-                (shouldBe-                   (parseIni-                      "Name=Foo\n\-                      \Name[en_GB]=Fubar")-                   (Left "Failed reading: Name[en_GB]=Fubar"))))+    hspec $ do+        describe "parseIni" $ do+            it "parses multi-section file with comments" $+                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="+                    `shouldBe` Right+                        ( Ini+                            { iniSections =+                                HM.fromList+                                    [+                                        ( "AUTH"+                                        ,+                                            [ ("user", "hello")+                                            , ("pass", "world")+                                            , ("salt", "")+                                            ]+                                        )+                                    ,+                                        ( "SERVER"+                                        , [("port", "6667"), ("hostname", "localhost")]+                                        )+                                    ]+                            , iniGlobals = []+                            }+                        )++            it "parses file with globals" $+                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="+                    `shouldBe` Right+                        ( Ini+                            { iniSections =+                                HM.fromList+                                    [+                                        ( "AUTH"+                                        ,+                                            [ ("user", "hello")+                                            , ("pass", "world")+                                            , ("salt", "")+                                            ]+                                        )+                                    ]+                            , iniGlobals =+                                [("port", "6667"), ("hostname", "localhost")]+                            }+                        )++            it "fails to parse file with invalid keys" $+                parseIni+                    "Name=Foo\n\+                    \Name[en_GB]=Fubar"+                    `shouldBe` Left "Failed reading: Name[en_GB]=Fubar"++            it "parses file ending with comments" $+                parseIni+                    "[default]\n\+                    \a = 1\n\+                    \\n\+                    \#[staging-PI]\n\+                    \#a = 2\n"+                    `shouldBe` Right+                        ( Ini+                            { iniSections = HM.fromList [("default", [("a", "1")])]+                            , iniGlobals = []+                            }+                        )++            it "parses file with globals only" $+                parseIni+                    "global1 = hello\n\+                    \global2 = 123\n\+                    \# An end of file comment here"+                    `shouldBe` Right+                        ( Ini+                            { iniSections = HM.empty+                            , iniGlobals = [("global1", "hello"), ("global2", "123")]+                            }+                        )++            it "parses empty file" $+                parseIni "" `shouldBe` Right mempty++            it "roundtrips with printIniWith" $+                property $ \ini settings ->+                    let printed = printIniWith settings ini+                        parsed = parseIni printed+                     in counterexample (T.unpack printed) $+                            parsed === Right ini++genKey :: Gen Text+genKey = do+    firstChar <- arbitrary `suchThat` isValidFirstKeyChar+    restChars <- listOf $ arbitrary `suchThat` isValidKeyChar+    pure $ T.pack (firstChar : restChars)+  where+    isValidKeyChar c = not (c == '=' || c == ':' || c == '[' || c == ']' || isControl c || isSpace c)+    isValidFirstKeyChar c = isValidKeyChar c && c /= ';' && c /= '#'++genSectionName :: Gen Text+genSectionName = do+    chars <- listOf1 $ arbitrary `suchThat` isValidSectionChar+    let name = T.strip $ T.pack chars+    if T.null name+        then genSectionName -- retry if stripping results in empty+        else pure name+  where+    isValidSectionChar c = c /= '[' && c /= ']' && not (isControl c)++genValue :: Gen Text+genValue = do+    chars <- listOf $ arbitrary `suchThat` (not . isControl)+    pure $ T.strip $ T.pack chars++genKeyValue :: Gen (Text, Text)+genKeyValue = (,) <$> genKey <*> genValue++instance Arbitrary KeySeparator where+    arbitrary = arbitraryBoundedEnum+    shrink ColonKeySeparator = []+    shrink EqualsKeySeparator = [ColonKeySeparator]++instance Arbitrary Ini where+    arbitrary = do+        numSections <- choose (0, 2)+        sections <- vectorOf numSections $ do+            name <- genSectionName+            numPairs <- choose (0, 2)+            pairs <- vectorOf numPairs genKeyValue+            pure (name, pairs)+        numGlobals <- choose (0, 3)+        globals <- vectorOf numGlobals genKeyValue+        pure $+            Ini+                { iniSections = HM.fromList sections+                , iniGlobals = globals+                }+    shrink ini =+        -- Shrink by removing sections+        [ ini{iniSections = HM.fromList secs'}+        | secs' <- shrinkList (const []) (HM.toList (iniSections ini))+        ]+            +++            -- Shrink by removing key-value pairs from sections+            [ ini{iniSections = HM.fromList secs'}+            | secs' <- shrinkOne shrinkSection (HM.toList (iniSections ini))+            ]+            +++            -- Shrink by removing globals+            [ ini{iniGlobals = globals'}+            | globals' <- shrinkList (const []) (iniGlobals ini)+            ]+      where+        shrinkSection (name, pairs) = [(name, pairs') | pairs' <- shrinkList (const []) pairs]+        shrinkOne _ [] = []+        shrinkOne f (x : xs) = [x' : xs | x' <- f x] ++ [x : xs' | xs' <- shrinkOne f xs]++instance Arbitrary WriteIniSettings where+    arbitrary = WriteIniSettings <$> arbitrary+    shrink (WriteIniSettings sep) = WriteIniSettings <$> shrink sep