Git Product home page Git Product logo

config-ini's Introduction

config-ini

Hackage stability: stable

The config-ini library is a Haskell library for doing elementary INI file parsing in a quick and painless way.

Basic Usage

The config-ini library exports some simple monadic functions to make parsing INI-like configuration easier. INI files have a two-level structure: the top-level named chunks of configuration, and the individual key-value pairs contained within those chunks. For example, the following INI file has two sections, NETWORK and LOCAL, and each section contains its own key-value pairs separated by either = or :. Comments, which begin with # or ;, are ignored:

[NETWORK]
host = example.com
port = 7878

# here is a comment
[LOCAL]
user = terry

The combinators provided here are designed to write quick and idiomatic parsers for basic INI files. Sections are parsed by IniParser computations, like section and its variations, while the fields within sections are parsed by SectionParser computations, like field and its variations. If we want to parse an INI file like the one above, treating the entire LOCAL section as optional, we can write it like this:

data Config = Config
  { cfNetwork :: NetworkConfig
  , cfLocal :: Maybe LocalConfig
  } deriving (Eq, Show)

data NetworkConfig = NetworkConfig
  { netHost :: String
  , netPort :: Int
  } deriving (Eq, Show)

data LocalConfig = LocalConfig
  { localUser :: Text
  } deriving (Eq, Show)

configParser :: IniParser Config
configParser = do
  netCf <- section "NETWORK" $ do
    host <- fieldOf "host" string
    port <- fieldOf "port" number
    return NetworkConfig { netHost = host, netPort = port }
  locCf <- sectionMb "LOCAL" $
    LocalConfig <$> field "user"
  return Config { cfNetwork = netCf, cfLocal = locCf }

We can run our computation with parseIniFile, which, when run on our example file above, would produce the following:

>>> parseIniFile example configParser
Right (Config {cfNetwork = NetworkConfig {netHost = "example.com", netPort = 7878}, cfLocal = Just (LocalConfig {localUser = "terry"})})

Bidirectional Usage

The above example had an INI file split into two sections (NETWORK and LOCAL) and a data type with a corresponding structure (containing a NetworkConfig and Maybe LocalConfig field), which allowed each section-level parser to construct a chunk of the configuration and then combine them. This works well if our configuration file has the same structure as our data type, but that might not be what we want. Let's imagine we want to construct our Config type as a flat record like this:

data Config = Config
  { _cfHost :: String
  , _cfPort :: Int
  , _cfUser :: Maybe Text
  } deriving (Eq, Show)

In this case, we can't construct a Config value until we've parsed all three fields in two distinct subsections. One way of doing this is to return the intermediate values from our section parsers and construct the Config value at the end, once we have all three of its fields:

configParser :: IniParser Config
configParser = do
  (host, port) <- section "NETWORK" $ do
    h <- fieldOf "host" string
    p <- fieldOf "port" number
    return (h, p)
  user <- section "LOCAL" $ fieldMb "user"
  return (Config host port user)

This is unfortunately awkward and repetitive. An alternative is to flatten it out by repeating invocations of section like below, but this has its own problems, such as unnecessary repetition of the "NETWORK" string literal, unnecessarily repetitive lookups, and general verbosity:

configParser :: IniParser Config
configParser = do
  host <- section "NETWORK" $ fieldOf "host" string
  port <- section "NETWORK" $ fieldOf "port" number
  user <- section "LOCAL" $ fieldMb "user"
  return (Config host port user)

In situations like these, you can instead use the Data.Ini.Config.Bidir module, which provides a slightly different abstraction: the functions exported by this module assume that you start with a default configuration value, and parsing a field allows you to update that configuration with the value of a field. The monads exported by this module have an extra type parameter that represents the type of the value being updated. The easiest way to use this module is by combining lenses with the .= and .=? operators, which take a lens and a description of a field, and produce a SectionSpec value that uses the provided lens to update the underlying type when parsing:

makeLenses ''Config

configParser :: IniSpec Config ()
configParser = do
  section "NETWORK" $ do
    cfHost .=  field "host" string
    cfPort .=  field "port" number
  section "LOCAL" $ do
    cfUser .=? field "user"

In order to use this as a parser, we will need to provide an existing value of Config so we can apply our updates to it. We combine the IniSpec defined above with a default config

configIni :: Ini Config
configIni =
  let defConfig = Config "localhost" 8080 Nothing
  in ini defConfig configParser

myParseIni :: Text -> Either String Config
myParseIni t = fmap getIniValue (parseIni t configIni)

This approach gives us other advantages, too. Each of the defined fields can be associated with some various pieces of metadata, marking them as optional for the purpose of parsing or associating a comment with them.

configParser' :: IniSpec Config ()
configParser' = do
  section "NETWORK" $ do
    cfHost .=  field "host" string
      & comment ["The desired hostname"]
      & optional
    cfPort .=  field "port" number
      & comment ["The port for the server"]
  section "LOCAL" $ do
    cfUser .=? field "user"
      & comment ["The username"]

When we create an ini from this IniSpec, we can serialize it directly to get a "default" INI file, one which contains the supplied comments on each field. This is useful if our application wants to produce a default configuration from the same declarative specification as before.

This approach also enables another, much more powerful feature: this enables us to perform a diff-minimal update. You'll notice that our parseIni function here doesn't give us back the value directly, but rather yet another Ini value from which we had to extract the value. This is because the Ini value also records incidental formatting choices of the input file: whitespace, comments, specifics of capitalization, and so forth. When we serialize an INI file that was returned by parseIni, we will get out literally the same file that we put in, complete with incidental formatting choices retained.

But we can also use that file and update it using the updateIni function: this takes a configuration value and a previous Ini value and builds a new Ini value such that as much structure as possible is retained from the original Ini. This means that if we parse a file, update a single field, and reserialize, that file should differ only in the field we changed and that's it: fields will stay in the same order (with new fields being added to the end of sections), comments will be retained, incidental whitespace will stay as it is.

This is a useful tool if you're building an application that has both a human-readable configuration as well the ability to set configuration values from within the application itself. This will allow you to rewrite the configuration file while minimizing lossy changes to a possibly-hand-edited possibly-checked-into-git configuration file.

Combinators and Conventions

There are several variations on the same basic functionality that appear in config-ini. All functions that start with section are for parsing section-level chunks of an INI file, while all functions that start with field are for parsing key-value pairs within a section. Because it's reasonably common, there are also special fieldFlag functions which return Bool values, parsed in a relatively loose way.

All functions which end in Mb return a Maybe value, returning Nothing if the section or key was not found. All functions which end in Def take an additional default value, returning it if the section or key was not found. All functions which contain Of take a function of the type Text -> Either String a, which is used to attempt to decode or parse the extracted value.

In total, there are three section-level parsers (section, sectionMb, and sectionDef) and eight field-level parsers (field, fieldOf, fieldMb, fieldMbOf, fieldDef, fieldDefOf, fieldFlag, fieldFlagDef). For the _Of functions, config-ini also provides several built-in parser functions which provide nice error messages on failure.

config-ini's People

Contributors

aisamanra avatar jhrcek avatar jtdaugherty avatar juhp avatar kquick avatar ryanglscott avatar simonmichael avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

config-ini's Issues

CHANGELOG.md and README.md files executable

$ cabal unpack config-ini-0.2.4.0
$ cd config-ini-0.2.4.0/
$ ls -l CHANGELOG.md README.md
-rwxr-xr-x. 1 petersen petersen 1197 Jan  1  1970 CHANGELOG.md
-rwxr-xr-x. 1 petersen petersen 8889 Jan  1  1970 README.md

It would be better to change the permissions in git from executable to regular files.

Apparently this can be done with git update-index --chmod=-x.

doctest failed to run

The other two tests pass normally. Not sure what causes this :(

Running 3 test suites...
Test suite test-doctest: RUNNING...

src/Data/Ini/Config.hs:97:1: error:
    Could not find module ‘Data.Ini.Config.Raw’
    Use -v to see a list of the files searched for.
   |
97 | import           Data.Ini.Config.Raw
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Test suite test-doctest: FAIL

Have an IniParserT monad transformer?

I'd like to have file path as the value of a config parameter, but load the referenced file during parsing the configuration. The implementation would look similar to how the other common monads are actually type aliases, e.g Reader a is actually ReaderT a Identity etc

can not parse this file

Hello

I try to put some JSON configuration in a key value.

datapath = {"dataPathHklQxQyQz":{"dataPathQxQyQzGeometry":{"tag":"GeometryPathUhv","geometryPathAxes":[{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"scan_data/UHV_MU"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"scan_data/UHV_OMEGA"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"scan_data/UHV_DELTA"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"scan_data/UHV_GAMMA"}]}}],"geometryPathWavelength":{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/Monochromator/wavelength"}]}}},"dataPathQxQyQzAttenuation":{"attenuationCoefficient":0,"attenuationOffset":2,"attenuationPath":{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"scan_data/attenuation"}]}},"tag":"AttenuationPath"},"dataPathQxQyQzDetector":{"detectorPathImage":{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"scan_data/xpad_image"}]}}}},"dataPathHklSample":{"tag":"SamplePath","contents":[{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/A"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/B"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/C"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/Alpha"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/Beta"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/Gamma"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/Ux"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/Uy"}]}},{"tag":"H5RootPath","contents":{"tag":"H5GroupAtPath","contents":[0,{"tag":"H5DatasetPath","contents":"SIXS/I14-C-CX2__EX__DIFF-UHV__#1/Uz"}]}}]}}

but when I try to read and parse this valid json objet, I have only this part of the text

"{\"dataPathHklQxQyQz\":{\"dataPathQxQyQzGeometry\":{\"tag\":\"GeometryPathUhv\",\"geometryPathAxes\":[{\"tag\":\"H5RootPath\",\"contents\":{\"tag\":\"H5GroupAtPath\",\"contents\":[0,{\"tag\":\"H5DatasetPath\",\"contents\":\"scan_data/UHV_MU\"}]"

so it seems to me that the parsing is broken when there is [] in the key.
I checked thaht removing all the '[' and ']' in the value solve this issue. But this is no more my JSON object :))

Cheers

Frederic

Build fails in conjunction with ghc-9.6 and latest stackage nightly

Build fails in conjunction with ghc-9.6 and latest stackage nightly:

/var/stackage/work/unpack-dir/unpacked/config-ini-0.2.5.0-a2138799480e166f8d7ee42bdfa2812de20508451c5bc39d9228443bec79c879/test/ini-compat/Main.hs:85:30: error: [GHC-39999]
           • Ambiguous type variable ‘f0’ arising from a use of ‘Gen.element’
             prevents the constraint ‘(Foldable f0)’ from being solved.
             Probable fix: use a type annotation to specify what ‘f0’ should be.
             Potentially matching instances:
               instance Foldable (Either a) -- Defined in ‘Data.Foldable’
               instance Foldable Seq.Seq -- Defined in ‘Data.Sequence.Internal’
               ...plus 7 others
               ...plus 47 instances involving out-of-scope types
               (use -fprint-potential-instances to see them all)
           • In the second argument of ‘(<$>)’, namely ‘Gen.element ";#"’
             In the first argument of ‘(<*>)’, namely
               ‘I2.CommentLine <$> Gen.element ";#"’
             In the expression:
               I2.CommentLine <$> Gen.element ";#" <*> textChunk
          |
       85 |           I2.CommentLine <$> Gen.element ";#" <*> textChunk

Bidir: setting in optional section with non-default value does not get written

Steps to reproduce:

  • Use sectionOpt to specify an optional section foo
  • Add to foo a setting bar with a default value
  • Have the Bidir API read a config file that does not provide any foo section at all
  • Change the underlying data structure's value for foo to some value other than the default
  • Have the Bidir API write a new config file in the same location as the old one

Expected behavior: the new file now has a foo section with a bar setting indicating the new value.

Observed behavior: the new file still has no foo section and defaults bar to the default value.

doctest failing in 0.2.5.0

Running 3 test suites...
Test suite test-doctest: RUNNING...

src/Data/Ini/Config/Raw.hs:167:8: error:
    • Ambiguous type variable ‘f1’ arising from a use of ‘oneOf’
      prevents the constraint ‘(Foldable f1)’ from being solved.
      Probable fix: use a type annotation to specify what ‘f1’ should be.
      These potential instances exist:
        instance Foldable (Either a) -- Defined in ‘Data.Foldable’
        instance Foldable Seq -- Defined in ‘Data.Sequence.Internal’
        instance Foldable Seq.ViewL -- Defined in ‘Data.Sequence.Internal’
        ...plus five others
        ...plus 33 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In a stmt of a 'do' block: c <- oneOf ";#"
      In the expression:
        do c <- oneOf ";#"
           txt <- T.pack `fmap` manyTill anySingle eol
           return (CommentLine c txt)
      In an equation for ‘sComment’:
          sComment
            = do c <- oneOf ";#"
                 txt <- T.pack `fmap` manyTill anySingle eol
                 return (CommentLine c txt)

src/Data/Ini/Config/Raw.hs:167:14: error:
    • Ambiguous type variable ‘f1’ arising from the literal ‘";#"’
      prevents the constraint ‘(Data.String.IsString
                                  (f1 Char))’ from being solved.
      Probable fix: use a type annotation to specify what ‘f1’ should be.
      These potential instances exist:
        instance (a ~ Char) => Data.String.IsString (Seq a)
          -- Defined in ‘Data.Sequence.Internal’
        instance [safe] (a ~ Tokens s, Data.String.IsString a, Eq a,
                         Stream s, Ord e) =>
                        Data.String.IsString (ParsecT e s m a)
          -- Defined in ‘Text.Megaparsec.Internal’
        instance (a ~ Char) => Data.String.IsString [a]
          -- Defined in ‘Data.String’
        ...plus two instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the first argument of ‘oneOf’, namely ‘";#"’
      In a stmt of a 'do' block: c <- oneOf ";#"
      In the expression:
        do c <- oneOf ";#"
           txt <- T.pack `fmap` manyTill anySingle eol
           return (CommentLine c txt)

src/Data/Ini/Config/Raw.hs:179:31: error:
    • Ambiguous type variable ‘f3’ arising from a use of ‘noneOf’
      prevents the constraint ‘(Foldable f3)’ from being solved.
      Probable fix: use a type annotation to specify what ‘f3’ should be.
      These potential instances exist:
        instance Foldable (Either a) -- Defined in ‘Data.Foldable’
        instance Foldable Seq -- Defined in ‘Data.Sequence.Internal’
        instance Foldable Seq.ViewL -- Defined in ‘Data.Sequence.Internal’
        ...plus five others
        ...plus 33 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the first argument of ‘some’, namely ‘(noneOf "[]")’
      In the second argument of ‘fmap’, namely ‘some (noneOf "[]")’
      In a stmt of a 'do' block: name <- T.pack `fmap` some (noneOf "[]")

src/Data/Ini/Config/Raw.hs:179:38: error:
    • Ambiguous type variable ‘f3’ arising from the literal ‘"[]"’
      prevents the constraint ‘(Data.String.IsString
                                  (f3 Char))’ from being solved.
      Probable fix: use a type annotation to specify what ‘f3’ should be.
      These potential instances exist:
        instance (a ~ Char) => Data.String.IsString (Seq a)
          -- Defined in ‘Data.Sequence.Internal’
        instance [safe] (a ~ Tokens s, Data.String.IsString a, Eq a,
                         Stream s, Ord e) =>
                        Data.String.IsString (ParsecT e s m a)
          -- Defined in ‘Text.Megaparsec.Internal’
        instance (a ~ Char) => Data.String.IsString [a]
          -- Defined in ‘Data.String’
        ...plus two instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the first argument of ‘noneOf’, namely ‘"[]"’
      In the first argument of ‘some’, namely ‘(noneOf "[]")’
      In the second argument of ‘fmap’, namely ‘some (noneOf "[]")’

src/Data/Ini/Config/Raw.hs:214:30: error:
    • Ambiguous type variable ‘f0’ arising from a use of ‘noneOf’
      prevents the constraint ‘(Foldable f0)’ from being solved.
      Probable fix: use a type annotation to specify what ‘f0’ should be.
      These potential instances exist:
        instance Foldable (Either a) -- Defined in ‘Data.Foldable’
        instance Foldable Seq -- Defined in ‘Data.Sequence.Internal’
        instance Foldable Seq.ViewL -- Defined in ‘Data.Sequence.Internal’
        ...plus five others
        ...plus 33 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the first argument of ‘some’, namely ‘(noneOf "[]=:")’
      In the second argument of ‘fmap’, namely ‘some (noneOf "[]=:")’
      In a stmt of a 'do' block:
        key <- T.pack `fmap` some (noneOf "[]=:")

src/Data/Ini/Config/Raw.hs:214:37: error:
    • Ambiguous type variable ‘f0’ arising from the literal ‘"[]=:"’
      prevents the constraint ‘(Data.String.IsString
                                  (f0 Char))’ from being solved.
      Probable fix: use a type annotation to specify what ‘f0’ should be.
      These potential instances exist:
        instance (a ~ Char) => Data.String.IsString (Seq a)
          -- Defined in ‘Data.Sequence.Internal’
        instance [safe] (a ~ Tokens s, Data.String.IsString a, Eq a,
                         Stream s, Ord e) =>
                        Data.String.IsString (ParsecT e s m a)
          -- Defined in ‘Text.Megaparsec.Internal’
        instance (a ~ Char) => Data.String.IsString [a]
          -- Defined in ‘Data.String’
        ...plus two instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the first argument of ‘noneOf’, namely ‘"[]=:"’
      In the first argument of ‘some’, namely ‘(noneOf "[]=:")’
      In the second argument of ‘fmap’, namely ‘some (noneOf "[]=:")’

src/Data/Ini/Config/Raw.hs:215:12: error:
    • Ambiguous type variable ‘f2’ arising from a use of ‘oneOf’
      prevents the constraint ‘(Foldable f2)’ from being solved.
      Probable fix: use a type annotation to specify what ‘f2’ should be.
      These potential instances exist:
        instance Foldable (Either a) -- Defined in ‘Data.Foldable’
        instance Foldable Seq -- Defined in ‘Data.Sequence.Internal’
        instance Foldable Seq.ViewL -- Defined in ‘Data.Sequence.Internal’
        ...plus five others
        ...plus 33 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In a stmt of a 'do' block: delim <- oneOf ":="
      In the expression:
        do pos <- getCurrentLine
           key <- T.pack `fmap` some (noneOf "[]=:")
           delim <- oneOf ":="
           val <- T.pack `fmap` manyTill anySingle eol
           ....
      In an equation for ‘pPair’:
          pPair leading
            = do pos <- getCurrentLine
                 key <- T.pack `fmap` some (noneOf "[]=:")
                 delim <- oneOf ":="
                 ....

src/Data/Ini/Config/Raw.hs:215:18: error:
    • Ambiguous type variable ‘f2’ arising from the literal ‘":="’
      prevents the constraint ‘(Data.String.IsString
                                  (f2 Char))’ from being solved.
      Probable fix: use a type annotation to specify what ‘f2’ should be.
      These potential instances exist:
        instance (a ~ Char) => Data.String.IsString (Seq a)
          -- Defined in ‘Data.Sequence.Internal’
        instance [safe] (a ~ Tokens s, Data.String.IsString a, Eq a,
                         Stream s, Ord e) =>
                        Data.String.IsString (ParsecT e s m a)
          -- Defined in ‘Text.Megaparsec.Internal’
        instance (a ~ Char) => Data.String.IsString [a]
          -- Defined in ‘Data.String’
        ...plus two instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the first argument of ‘oneOf’, namely ‘":="’
      In a stmt of a 'do' block: delim <- oneOf ":="
      In the expression:
        do pos <- getCurrentLine
           key <- T.pack `fmap` some (noneOf "[]=:")
           delim <- oneOf ":="
           val <- T.pack `fmap` manyTill anySingle eol
           ....
src/Data/Ini/Config.hs:504: failure in expression `:{
 data LocalConfig = LocalConfig
   { localUser :: Text }
     deriving (Eq, Show)
:}'
expected:
 but got:
          ^
          <interactive>:35:19: error:
              Not in scope: type constructor or class ‘Text’

Examples: 52  Tried: 2  Errors: 0  Failures: 1
Test suite test-doctest: FAIL
Test suite logged to: dist/test/config-ini-0.2.5.0-test-doctest.log
Test suite test-prewritten: RUNNING...
Passed: basic.ini
Passed: unicode.ini
Test suite test-prewritten: PASS
Test suite logged to: dist/test/config-ini-0.2.5.0-test-prewritten.log
Test suite test-ini-compat: RUNNING...
━━━ Test.Example ━━━
  ✓ propIniEquiv passed 100 tests.
  ✓ propRevIniEquiv passed 100 tests.
  ✓ propIniSelfEquiv passed 100 tests.
  ✓ 3 succeeded.
Test suite test-ini-compat: PASS
Test suite logged to: dist/test/config-ini-0.2.5.0-test-ini-compat.log
2 of 3 test suites (2 of 3 test cases) passed.

multiline comments

Hi!

It seems hacky to specify comments that span more than one line. Two naive attempts fail:
If I insert "\n" into the comment string, then the serialization would break at that spot, but not insert another "# "

If I would just add another & comment [...] combinator, then the last one is taken and the first is ignored.

It works in the end by not only breaking with newline, but also adding "# " manually.

To illustrate with the example from the docs:

configParser' :: IniSpec Config ()
configParser' = do
  section "NETWORK" $ do
    cfHost .=  field "host" string
      & comment ["The desired hostname\n# some other comment line:"]

Whitespace not handled

Hi!

It's unclear to me how to handle a single space as a string. If I provide it as a default via a default config, like { _bla = " " }, then this will end up in the .ini as bla = (2 spaces after =).

This then gets read back into the datastructure as the empty string.

How can I handle this? Quoting doesn't work, since that just includes the quotes itself in the string.

Sections with all optional fields could themselves be optional

Let's say I specify a section in my config parser (bidir), and I specify that all of the fields to appear in that section have default values. Right now, it is only legal to omit the options but not the section header, so a legal config that falls back to all defaults must look like

[section]

but I'd love it if I could also omit the section header, too.

Feature request: SectionParser that produces all entries

I have an INI section where the field set is not fixed, and so I'd like to parse that whole section into a Map without a priori knowledge of the field names.

I'm envisioning something like this:

-- | Retrieve all fields in the section.
fieldMap :: SectionParser (Map Text Text)

-- | Retrieve all fields in the section and use the
-- supplied parser to parse each value, failing if the
-- parser fails to produce a value for any of the fields.
fieldMapOf :: (Text -> Either String a) -> SectionParser (Map Text a)

test/ini-compat/Main.hs failed to compile

Testsuite build failure in current Stackage Nightly:

Building test suite 'test-ini-compat' for config-ini-0.2.4.0..
[1 of 1] Compiling Main             ( test/ini-compat/Main.hs, dist/build/test-ini-compat/test-ini-compat-tmp/Main.o )

test/ini-compat/Main.hs:53:8: error:
    • The constructor ‘I1.Ini’ should have 2 arguments, but has been given 1
    • In the pattern: I1.Ini ini
      In an equation for ‘lower’:
          lower (I1.Ini ini)
            = go (fmap go ini)
            where
                go hm = HM.fromList [(T.toLower k, v) | (k, v) <- HM.toList hm]
   |
53 | lower (I1.Ini ini) = go (fmap go ini)
   |        ^^^^^^^^^^

test/ini-compat/Main.hs:74:3: error:
    • Couldn't match type ‘[(Text, Text)] -> I1.Ini’ with ‘I1.Ini’
      Expected type: GenT Data.Functor.Identity.Identity I1.Ini
        Actual type: GenT
                       Data.Functor.Identity.Identity ([(Text, Text)] -> I1.Ini)
    • In a stmt of a 'do' block: return (I1.Ini (HM.fromList ss))
      In the expression:
        do ss <- Gen.list (Range.linear 0 10)
                   $ do name <- textChunk
                        section <- Gen.list (Range.linear 0 10)
                                     $ (,) <$> textChunk <*> textChunk
                        ....
           return (I1.Ini (HM.fromList ss))
      In an equation for ‘mkIni’:
          mkIni
            = do ss <- Gen.list (Range.linear 0 10)
                         $ do name <- textChunk
                              ....
                 return (I1.Ini (HM.fromList ss))
   |
74 |   return (I1.Ini (HM.fromList ss))
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

test/ini-compat/Main.hs:74:19: error:
    • Couldn't match type ‘HashMap Text Text’ with ‘[(Text, Text)]’
      Expected type: HashMap Text [(Text, Text)]
        Actual type: HashMap Text (HashMap Text Text)
    • In the first argument of ‘I1.Ini’, namely ‘(HM.fromList ss)’
      In the first argument of ‘return’, namely
        ‘(I1.Ini (HM.fromList ss))’
      In a stmt of a 'do' block: return (I1.Ini (HM.fromList ss))
   |
74 |   return (I1.Ini (HM.fromList ss))
   |                   ^^^^^^^^^^^^^^

Why does config-ini-0.2.2.0 exclude ghc-8.6.1?

The latest release of config-ini has a constraint containers ==0.5.* that prevents us from building the package with ghc-8.6.1. When that constraint is lifted, however, the build succeeds just fine. Could you please lift that constraint on Hackage?

Brilliant

Hi @aisamanra,

I know this is not the place for such a message, but since I don't know where to post, I'll abuse this Github tracker. Please delete this message after reading.

Your package config-ini is absolutely brilliant. I have used it for the first time in a project, and everything was a breeze. API is perfectly designed, it is sober and it works exactly as expected. I wish all packages were of the same quality.

I want to give you a big Thank you for that. Best regards,

Compile failure with GHC < 8

You're using ghc flags that are supported only starting with GHC 8.0, and yet your package declares to be compatible with base versions prior to base-4.9:

Configuring component lib from config-ini-0.1.0.0
Preprocessing library config-ini-0.1.0.0...

src/Data/Ini/Config.hs:4:16:
    unknown flag in  {-# OPTIONS_GHC #-} pragma: -fno-warn-redundant-constraints

I suggest guarding the option with e.g. #if __GLASGOW_HASKELL__ >= 800 if you want to support versions prior to GHC 8.0

Configuration setting not preserved

I don't understand why this is happening, but in this case I expect database to be assigned the value foo in the updated version, rather than janadb:

*Data.Ini.Config.Bidir Data.Text> updateIniFile defaultConfig parseConfig (pack "[misc]\ndatabase = foo\n") defaultUpdatePolicy
Right "[misc]\ndatabase = janadb\n"

Config rewriting introduces newlines

I haven't tracked it down yet, but sometimes when the bidirectional support regenerates a config, newlines are introduced between the section name and the first setting in a section, e.g.,

[section]


setting = foo

[section2]




otherSetting = bar

emitIniFile produces output with redundant newlines

Observed behavior: use of emitIniFile results in output that has two newlines per line.

Expected behavior: one newline per line.

> emitIniFile defaultConfig parseConfig
"[section]\n\nfoo=1\n\nbar=2\n\n..."

Merge bidir to master

I took a look at this since I'm to the point where I'd like to start using bidir via Hackage, but it looks like at least merging master to bidir is non-trivial for me since I'm not familiar with what changed.

Until this is merged to Hackage, I can use bidir, though.

add exhaustiveness to the old parse-only API

Right now, the parse-only API parses what's provided and ignores everything else. This is probably not desirable in the long-term: for example, if I have a section defined as sectionMb "what" and accidentally mistype my section name as [waht], then the parse will succeed (because the section is optional) but also silently ignore the user-provided section. This is not ideal in general.

My first-pass approach is probably going to be simply removing a section from the INI file structure as we parse it out, and ensuring that the final parsed structure is empty, but it's probably a good idea to think about whether this is sufficient or desirable in all cases, or whether there should be an opt-in way of ignoring sections instead.

Optional settings only reinstated in explicit sections

Observed behavior: enabling default settings to be emitted only does so for sections that are already present in the input.

Expected/assumed behavior: that doing this without explicitly mentioning all sections would result in the emission of all other sections along with their defaults to form a fully-populated configuration.

(There are two sections in the specification that are not present in the output below.)

*Data.Ini.Config.Bidir Data.Text> updateIniFile defaultConfig parseConfig (pack "[misc]\ndatabase = foo\n") (UpdatePolicy True False CommentPolicyNone)
Right "[misc]\ndatabase = janadb\nbackend = any\ntmux-session-name = jana\nescrow-path = spdz-keys\nproject-root = .\nlog-directory = logs\n"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.