packages feed

htoml 0.1.0.3 → 0.2.0.0

raw patch · 25 files changed

+417/−266 lines, 25 filesdep ~aesondep ~file-embedPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: aeson, file-embed

API changes (from Hackage documentation)

- Text.Toml.Types: NTArray :: [Table] -> Node
- Text.Toml.Types: NTValue :: TValue -> Node
- Text.Toml.Types: NTable :: Table -> Node
- Text.Toml.Types: commonInsertError :: Node -> [Text] -> Either Text Table
- Text.Toml.Types: data TValue
- Text.Toml.Types: emptyNTable :: Node
- Text.Toml.Types: instance Data.Aeson.Types.Class.ToJSON Text.Toml.Types.TValue
- Text.Toml.Types: instance GHC.Classes.Eq Text.Toml.Types.TValue
- Text.Toml.Types: instance GHC.Show.Show Text.Toml.Types.TValue
- Text.Toml.Types: instance Text.Toml.Types.ToBsJSON Text.Toml.Types.TValue
- Text.Toml.Types: merge :: Table -> Table -> Either [Text] Table
+ Text.Toml.Parser: anyStr' :: Parser Text
+ Text.Toml.Parser: inlineTable :: Parser Node
+ Text.Toml.Parser: maybeDupe :: Ord a => [a] -> Maybe a
+ Text.Toml.Parser: type Parser a = forall s. Parsec Text s a
+ Text.Toml.Types: Explicit :: Explicitness
+ Text.Toml.Types: Implicit :: Explicitness
+ Text.Toml.Types: VTArray :: [Table] -> Node
+ Text.Toml.Types: VTable :: Table -> Node
+ Text.Toml.Types: data Explicitness
+ Text.Toml.Types: isExplicit :: Explicitness -> Bool
- Text.Toml.Parser: anyStr :: Parser TValue
+ Text.Toml.Parser: anyStr :: Parser Node
- Text.Toml.Parser: array :: Parser TValue
+ Text.Toml.Parser: array :: Parser Node
- Text.Toml.Parser: arrayOf :: Parser TValue -> Parser TValue
+ Text.Toml.Parser: arrayOf :: Parser Node -> Parser Node
- Text.Toml.Parser: assignment :: Parser (Text, TValue)
+ Text.Toml.Parser: assignment :: Parser (Text, Node)
- Text.Toml.Parser: basicStr :: Parser TValue
+ Text.Toml.Parser: basicStr :: Parser Text
- Text.Toml.Parser: boolean :: Parser TValue
+ Text.Toml.Parser: boolean :: Parser Node
- Text.Toml.Parser: datetime :: Parser TValue
+ Text.Toml.Parser: datetime :: Parser Node
- Text.Toml.Parser: float :: Parser TValue
+ Text.Toml.Parser: float :: Parser Node
- Text.Toml.Parser: integer :: Parser TValue
+ Text.Toml.Parser: integer :: Parser Node
- Text.Toml.Parser: literalStr :: Parser TValue
+ Text.Toml.Parser: literalStr :: Parser Text
- Text.Toml.Parser: multiBasicStr :: Parser TValue
+ Text.Toml.Parser: multiBasicStr :: Parser Text
- Text.Toml.Parser: multiLiteralStr :: Parser TValue
+ Text.Toml.Parser: multiLiteralStr :: Parser Text
- Text.Toml.Parser: parseOnly :: Parser a -> Text -> Either ParseError a
+ Text.Toml.Parser: parseOnly :: Parsec Text (Set [Text]) a -> Text -> Either ParseError a
- Text.Toml.Parser: tomlDoc :: Parser Table
+ Text.Toml.Parser: tomlDoc :: Parsec Text (Set [Text]) Table
- Text.Toml.Parser: value :: Parser TValue
+ Text.Toml.Parser: value :: Parser Node
- Text.Toml.Types: VArray :: [TValue] -> TValue
+ Text.Toml.Types: VArray :: [Node] -> Node
- Text.Toml.Types: VBoolean :: Bool -> TValue
+ Text.Toml.Types: VBoolean :: !Bool -> Node
- Text.Toml.Types: VDatetime :: UTCTime -> TValue
+ Text.Toml.Types: VDatetime :: !UTCTime -> Node
- Text.Toml.Types: VFloat :: Double -> TValue
+ Text.Toml.Types: VFloat :: !Double -> Node
- Text.Toml.Types: VInteger :: Int64 -> TValue
+ Text.Toml.Types: VInteger :: !Int64 -> Node
- Text.Toml.Types: VString :: Text -> TValue
+ Text.Toml.Types: VString :: !Text -> Node
- Text.Toml.Types: insert :: ([Text], Node) -> Table -> Either Text Table
+ Text.Toml.Types: insert :: Explicitness -> ([Text], Node) -> Table -> Parsec Text (Set [Text]) Table

Files

CHANGES.md view
@@ -4,6 +4,13 @@ #### dev * ... +#### 0.2.0.0+* Compatible with TOML 0.4.0+* Improve test suite (all test now pass -- thanks @HuwCampbell)+* Slight API breakage (therefore major version bump)+* Use Parsec's parser state to track explicitness of table definitions (thanks @HuwCampbell)+* Clean up docs and code+ #### 0.1.0.3 * GHC 7.10 compatibility fix (thanks @erebe) * Allow time >= 1.5.0, by using some CPP trickery
README.md view
@@ -1,6 +1,15 @@ htoml ===== +[![Build Status](https://travis-ci.org/cies/htoml.svg?branch=master)](https://travis-ci.org/cies/htoml)+[![Latest version on Hackage](https://img.shields.io/hackage/v/htoml.svg)](https://hackage.haskell.org/package/html)+[![Dependencies of latest version on Hackage](https://img.shields.io/hackage-deps/v/htoml.svg)](https://hackage.haskell.org/package/html)++[![htoml on Stackage LTS 2](http://stackage.org/package/htoml/badge/lts-2)](http://stackage.org/lts-2/package/htoml)+[![htoml on Stackage LTS 3](http://stackage.org/package/htoml/badge/lts-3)](http://stackage.org/lts-3/package/htoml)+[![htoml on Stackage Nightly](http://stackage.org/package/htoml/badge/nightly)](http://stackage.org/nightly/package/htoml)++ A [TOML](https://github.com/mojombo/toml) parser library in [Haskell](http://haskell-lang.org). @@ -8,14 +17,14 @@ [Tom Preston-Werner](https://github.com/mojombo). It is an alternative to the [XML](http://www.w3.org/TR/REC-xml/), [YAML](http://www.yaml.org/spec/1.2/spec.html) and-[INI](http://en.wikipedia.org/wiki/INI_file) formats for the purpose of-configuration files, as the first two are too heavy for that prupose,-and the latter is underspecified.-Toml is to configuration files, like what Markdown is for rich-text.+[INI](http://en.wikipedia.org/wiki/INI_file) formats mainly for the purpose of+configuration files. Many will find that XML and YAML are too heavy for+the purpose of configuration files prupose while INI is underspecified.+TOML is to configuration files, like what Markdown is for rich-text.  This library aims to be compatible with the latest version of the [TOML spec](https://github.com/mojombo/toml), currently that is-[v0.3.1](https://github.com/toml-lang/toml/releases/tag/v0.3.1).+[v0.4.0](https://github.com/toml-lang/toml/releases/tag/v0.4.0).  The documentation for this package may (or may not) be found on [Hackage](https://hackage.haskell.org/package/htoml).@@ -23,39 +32,55 @@  ### Quick start -Installing `htoml` is easy.+Installing `htoml` is easy. Either by using+[Stack](http://haskellstack.org) (recommended): +    stack install htoml++Or by using Cabal:+     cabal install htoml  In order to make your project depend on it you can add it as a-dependency in your project's cabal file.+dependency in your project's `.cabal` file, and since it is not+yet on [Stackage](https://www.stackage.org/) you will also have+to add it to the `extra-deps` section of your `stack.yaml` file+when using Stack. -To quickly show some features of `htoml` we start `GHCi` from the-root of the repository so it picks up configuration from the-`.ghci` file that lives there.+To quickly show some features of `htoml` we use Stack to start a+GHCi-based REPL. It picks up configuration from the `.ghci` file+in the root of the repository.      git clone https://github.com/cies/htoml.git     cd htoml-    cabal sandbox init  ;# initialize a cabal sandbox-    cabal install       ;# install the dependencies and build all-    cabal repl          ;# starts a sandbox-aware GHCi+    stack init+    stack --install-ghc ghci -We can immediately start exploring from the `GHCi` prompt.+Add a `--resolver` flag to the `stack init` command to specify+a specific package snapshot, e.g.: `--resolver lts-4.1`. +In case you have missing dependencies (possibly `file-embed`),+they can be added to the `extra-deps` in `stack.yaml`+automatically with:++    stack solver --update-config++We can now start exploring `htoml` from a GHCi REPL.+     > txt <- readFile "benchmarks/example.toml"     > let r = parseTomlDoc "" txt     > r-    Right (fromList [("database",NTable (fromList [("enabled",NTValue (VBoolean True) [...]+    Right (fromList [("database",VTable (fromList [("enabled",VBoolean True),("po [...]      > let Right toml = r     > toJSON toml-    Object (fromList [("database",Object (fromList [("enabled",Bool True) [...]+    Object (fromList [("database",Object (fromList [("enabled",Bool True),("po [...] -    > let Left error = parseTomlDoc "" "== invalid toml =="-    > e+    > let Left err = parseTomlDoc "" "== invalid toml =="+    > err     (line 1, column 1):     unexpected '='-    expecting "#", "[" or end of input+    expecting "#", "\n", "\r\n", letter or digit, "_", "-", "\"", "'", "[" or end of inputr  Notice that some outputs are truncated, indicated by `[...]`. @@ -69,7 +94,7 @@ For example let's consider the following `parseResult`:  ```haskell-Right (fromList [("server",NTable (fromList [("enabled",NTValue (VBoolean True))] ) )] )+Right (fromList [("server",VTable (fromList [("enabled",VBoolean True)] ) )] ) ```  Which could be pattern matched with:@@ -78,7 +103,7 @@ case parseResult of   Left  _ -> "Could not parse file"   Right m -> case m ! "server" of-    NTable mm -> case mm ! "enabled" of+    VTable mm -> case mm ! "enabled" of       VBoolean b -> "Server is " ++ (if b then "enabled" else "disabled")       _ -> "Could not parse server status (Boolean)"     _ -> "TOML file does not contain the 'server' key"@@ -91,10 +116,19 @@ approach.  Other ways to pull data from a parsed TOML document will most likely-exist; maybe the `lens` library can give great results in some cases.-But I have no experience with them.+exist; possible using the `lens` library as+[documented here](https://github.com/cies/htoml/issues/8).  +### Compatibility++Currently we are testing against several versions of GHC with+[Travis CI](https://travis-ci.org/cies/htoml) as defined in the `env` section of our+[`.travis.yml`](https://github.com/cies/htoml/blob/master/.travis.yml).+`lts-2` implies GHC 7.8.4, `lts-3` implies GHC 7.10.2, `lts-4`/`lts-5`+imply GHC 7.10.3, and `nightly` is build with a regularly updated version of GHC.++ ### Version contraints of `htoml`'s dependencies  If you encounter any problems because `htoml`'s dependecies are@@ -108,24 +142,26 @@  ### Tests and benchmarks -The test suite is build by default, `cabal configure --disable-tests` disables them.-The benchmark suite is not run by default, `cabal configure --enable-benchmarks` enables them.+Tests are build and run with: -With `cabal build` both of these suites are build as executables and-put somewhere in `dist/`. Passing `--help` to them will reveal their-options.+    stack test  [BurntSushi's language agnostic test suite](https://github.com/BurntSushi/toml-test) is embedded in the test suite executable.  Using a shell script (that lives in `test/BurntSushi`) the latest tests can be fetched from-BurntSushi's repository.+its Github repository. +The benchmarks, that use the amazing [`criterion`](http://www.serpentine.com/criterion)+library, are build and run with: +    stack build :benchmarks++ ### Contributions  Most welcome! Please raise issues, start discussions, give comments or submit pull-requests.-This is one of the first Haskell libraries I wrote, any feedback is+This is one of the first Haskell libraries I wrote, feedback is much appreciated.  @@ -145,20 +181,26 @@ ### Todo  * Release a stable 1.0 release and submit it to [Stackage](http://stackage.org)+* Once 1.0 is out, keep a compatibility chart showing which versions of htoml are+  compatible with which versions of the TOML spec * More documentation-* Make all tests pass (currently some more obscure corner cases don't pass) * Add property tests with QuickCheck (the internet says it's possible for parsers) * Extensively test error cases-* Try using Vector instead of List (measure performance increase with the benchmarks)-* See how lenses may (or may not) fit into this package+* Try using `Vector` instead of `List` (measure performance increase with the benchmarks)+* See how lenses may (or may not) fit into this package, or an additional package+* Consider moving to [one of the more modern parser combinators](https://www.reddit.com/r/haskell/comments/46u45o/what_is_the_current_state_of_parser_libraries_in)+  in Haskell (`megaparsec` maybe?) -- possibly wait until a clear winner shows  -### Acknoledgements+### Acknowledgements  Originally this project started off by improving the `toml` package by Spiros Eliopoulos. +[HuwCampbell](https://github.com/HuwCampbell) helped a lot by making tests+pass and implementing "explicitness tracking" in Parsec's parser state. + ### Copyright and licensing  This package includes BurntSushi's language agnostic@@ -169,5 +211,5 @@ from Tom Preston-Werner's TOML spec which is MIT licensed.  For all other files in this project the copyrights are specified in the-`htoml.cabal` file, and are distributed under the BSD3 license as found+`htoml.cabal` file, they are distributed under the BSD3 license as found in the `LICENSE` file.
htoml.cabal view
@@ -1,16 +1,16 @@ name:                     htoml-version:                  0.1.0.3+version:                  0.2.0.0 synopsis:                 Parser for TOML files description:              TOML is an obvious and minimal format for config files.                           .                           This package provides a TOML parser,-                          build with the Parsec library, and providing a JSON+                          build with the Parsec library. It exposes a JSON                           interface using the Aeson library. homepage:                 https://github.com/cies/htoml bug-reports:              https://github.com/cies/htoml/issues license:                  BSD3 license-file:             LICENSE-copyright:                (c) 2013-2014 Cies Breijs+copyright:                (c) 2013-2016 Cies Breijs author:                   Cies Breijs maintainer:               Cies Breijs <cies % kde ! nl> category:                 Data, Text, Parser, Configuration, JSON, Language@@ -64,7 +64,7 @@                         , text                         , time                         , bytestring             >= 0.9-                        , file-embed             >= 0.0.5+                        , file-embed             >= 0.0.10                         , Cabal                  >= 1.16.0                         , tasty                  >= 0.10                         , tasty-hspec            >= 0.2@@ -80,6 +80,7 @@   type:                   exitcode-stdio-1.0   default-language:        Haskell2010   build-depends:          base+                        , aeson                         , parsec                         , containers                         , bytestring@@ -94,6 +95,7 @@                         , tasty                         , tasty-hspec                         , tasty-hunit+                        , aeson  benchmark benchmarks   hs-source-dirs:         benchmarks .
src/Text/Toml.hs view
@@ -1,8 +1,7 @@ module Text.Toml where -import           Prelude          hiding (readFile)- import           Data.Text        (Text)+import           Data.Set         (empty) import           Text.Parsec  import           Text.Toml.Parser@@ -10,6 +9,6 @@  -- | Parse a 'Text' that results in 'Either' a 'String' -- containing the error message, or an internal representation--- of the document in the 'Toml' data type.+-- of the document as a 'Table'. parseTomlDoc :: String -> Text -> Either ParseError Table-parseTomlDoc inputName input = parse tomlDoc inputName input+parseTomlDoc inputName input = runParser tomlDoc empty inputName input
src/Text/Toml/Parser.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE FlexibleContexts  #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes        #-} {-# LANGUAGE CPP               #-}  module Text.Toml.Parser@@ -7,10 +8,9 @@   , module Text.Toml.Types   ) where --import           Prelude             hiding (concat, takeWhile)- import           Control.Applicative hiding (many, optional, (<|>))+import           Control.Monad+ import qualified Data.HashMap.Strict as M import qualified Data.List           as L import qualified Data.Set            as S@@ -26,47 +26,61 @@  import           Numeric             (readHex) import           Text.Parsec-import           Text.Parsec.Text  import           Text.Toml.Types +-- Imported as last to fix redundancy warning+import           Prelude             hiding (concat, takeWhile)  +-- | Our very own Parser type.+type Parser a = forall s. Parsec Text s a++ -- | Convenience function for the test suite and GHCI.-parseOnly :: Parser a -> Text -> Either ParseError a-parseOnly p str = parse (p <* eof) "test" str+parseOnly :: Parsec Text (S.Set [Text]) a -> Text -> Either ParseError a+parseOnly p str = runParser (p <* eof)  S.empty "test" str   -- | Parses a complete document formatted according to the TOML spec.-tomlDoc :: Parser Table+tomlDoc :: Parsec Text (S.Set [Text]) Table tomlDoc = do     skipBlanks     topTable <- table     namedSections <- many namedSection-    eof  -- ensures input is completely consumed-    case join topTable (reverse namedSections) of-      Left msg -> fail (unpack msg)  -- TODO: allow Text in Parse Errors-      Right r  -> return $ r-  where-    join tbl []     = Right tbl-    join tbl (x:xs) = case join tbl xs of Left msg -> Left msg-                                          Right r  -> insert x r-+    -- Ensure the input is completely consumed+    eof+    -- Load each named section into the top table+    foldM (flip (insert Explicit)) topTable namedSections  -- | Parses a table of key-value pairs. table :: Parser Table table = do     pairs <- try (many (assignment <* skipBlanks)) <|> (try skipBlanks >> return [])-    case hasDup (map fst pairs) of+    case maybeDupe (map fst pairs) of       Just k  -> fail $ "Cannot redefine key " ++ (unpack k)-      Nothing -> return $ M.fromList (map (\(k, v) -> (k, NTValue v)) pairs)+      Nothing -> return $ M.fromList pairs++-- | Parses an inline table of key-value pairs.+inlineTable :: Parser Node+inlineTable = do+    pairs <- between (char '{') (char '}') (skipSpaces *> separatedValues <* skipSpaces)+    case maybeDupe (map fst pairs) of+      Just k  -> fail $ "Cannot redefine key " ++ (unpack k)+      Nothing -> return $ VTable $ M.fromList pairs   where-    hasDup        :: Ord a => [a] -> Maybe a-    hasDup xs     = dup' xs S.empty-    dup' []     _ = Nothing-    dup' (x:xs) s = if S.member x s then Just x else dup' xs (S.insert x s)+    skipSpaces      = many (satisfy isSpc)+    separatedValues = sepBy (skipSpaces *> assignment <* skipSpaces) comma+    comma           = skipSpaces >> char ',' >> skipSpaces +-- | Find dupes, if any.+maybeDupe :: Ord a => [a] -> Maybe a+maybeDupe xx = dup xx S.empty+  where+    dup []     _ = Nothing+    dup (x:xs) s = if S.member x s then Just x else dup xs (S.insert x s) + -- | Parses a 'Table' or 'TableArray' with its header. -- The resulting tuple has the header's value in the first position, and the -- 'NTable' or 'NTArray' in the second.@@ -76,8 +90,8 @@     skipBlanks     tbl <- table     skipBlanks-    return $ case eitherHdr of Left  ns -> (ns, NTable   tbl )-                               Right ns -> (ns, NTArray [tbl])+    return $ case eitherHdr of Left  ns -> (ns, VTable   tbl )+                               Right ns -> (ns, VTArray [tbl])   -- | Parses a table header.@@ -94,40 +108,38 @@  -- | Parses the value of any header (names separated by dots), into a list of 'Text'. headerValue :: Parser [Text]-headerValue = (pack <$> many1 headerNameChar) `sepBy1` (char '.')+headerValue = ((pack <$> many1 keyChar) <|> anyStr') `sepBy1` (char '.')   where-    headerNameChar = satisfy (\c -> c /= ' ' && c /= '\t' && c /= '\n' &&-                                    c /= '[' && c /= ']'  && c /= '.'  && c /= '#')-+    keyChar = alphaNum <|> char '_' <|> char '-'  -- | Parses a key-value assignment.-assignment :: Parser (Text, TValue)+assignment :: Parser (Text, Node) assignment = do-    k <- pack <$> many1 keyChar-    skipBlanks >> char '=' >> skipBlanks+    k <- (pack <$> many1 keyChar) <|> anyStr'+    many (satisfy isSpc) >> char '=' >> skipBlanks     v <- value     return (k, v)   where     -- TODO: Follow the spec, e.g.: only first char cannot be '['.-    keyChar = satisfy (\c -> c /= ' ' && c /= '\t' && c /= '\n' &&-                             c /= '=' && c /= '#'  && c /= '[')+    keyChar = alphaNum <|> char '_' <|> char '-'   -- | Parses a value.-value :: Parser TValue-value = (try array    <?> "array")-    <|> (try boolean  <?> "boolean")-    <|> (try anyStr   <?> "string")-    <|> (try datetime <?> "datetime")-    <|> (try float    <?> "float")-    <|> (try integer  <?> "integer")+value :: Parser Node+value = (try array       <?> "array")+    <|> (try boolean     <?> "boolean")+    <|> (try anyStr      <?> "string")+    <|> (try datetime    <?> "datetime")+    <|> (try float       <?> "float")+    <|> (try integer     <?> "integer")+    <|> (try inlineTable <?> "inline table")   -- -- | * Toml value parsers -- -array :: Parser TValue+array :: Parser Node array = (try (arrayOf array)    <?> "array of arrays")     <|> (try (arrayOf boolean)  <?> "array of booleans")     <|> (try (arrayOf anyStr)   <?> "array of strings")@@ -136,43 +148,46 @@     <|> (try (arrayOf integer)  <?> "array of integers")  -boolean :: Parser TValue+boolean :: Parser Node boolean = VBoolean <$> ( (try . string $ "true")  *> return True  <|>                          (try . string $ "false") *> return False )  -anyStr :: Parser TValue-anyStr = try multiBasicStr <|> try basicStr <|> try multiLiteralStr <|> try literalStr+anyStr :: Parser Node+anyStr = VString <$> anyStr' +anyStr' :: Parser Text+anyStr' = try multiBasicStr <|> try basicStr <|> try multiLiteralStr <|> try literalStr -basicStr :: Parser TValue-basicStr = VString <$> between dQuote dQuote (fmap pack $ many strChar)++basicStr :: Parser Text+basicStr = between dQuote dQuote (fmap pack $ many strChar)   where     strChar = try escSeq <|> try (satisfy (\c -> c /= '"' && c /= '\\'))     dQuote  = char '\"'  -multiBasicStr :: Parser TValue-multiBasicStr = VString <$> (openDQuote3 *> (fmap pack $ manyTill strChar dQuote3))+multiBasicStr :: Parser Text+multiBasicStr = (openDQuote3 *> escWhiteSpc *> (pack <$> manyTill strChar (try dQuote3)))   where     -- | Parse the a tripple-double quote, with possibly a newline attached     openDQuote3 = try (dQuote3 <* char '\n') <|> try dQuote3     -- | Parse tripple-double quotes     dQuote3     = count 3 $ char '"'     -- | Parse a string char, accepting escaped codes, ignoring escaped white space-    strChar     = escWhiteSpc *> (escSeq <|> (satisfy (/= '\\'))) <* escWhiteSpc+    strChar     = (escSeq <|> (satisfy (/= '\\'))) <* escWhiteSpc     -- | Parse escaped white space, if any     escWhiteSpc = many $ char '\\' >> char '\n' >> (many $ satisfy (\c -> isSpc c || c == '\n'))  -literalStr :: Parser TValue-literalStr = VString <$> between sQuote sQuote (pack <$> many (satisfy (/= '\'')))+literalStr :: Parser Text+literalStr = between sQuote sQuote (pack <$> many (satisfy (/= '\'')))   where     sQuote = char '\''  -multiLiteralStr :: Parser TValue-multiLiteralStr = VString <$> (openSQuote3 *> (fmap pack $ manyTill anyChar sQuote3))+multiLiteralStr :: Parser Text+multiLiteralStr = (openSQuote3 *> (fmap pack $ manyTill anyChar sQuote3))   where     -- | Parse the a tripple-single quote, with possibly a newline attached     openSQuote3 = try (sQuote3 <* char '\n') <|> try sQuote3@@ -180,9 +195,9 @@     sQuote3     = try . count 3 . char $ '\''  -datetime :: Parser TValue+datetime :: Parser Node datetime = do-    d <- manyTill anyChar (try $ char 'Z')+    d <- try $ manyTill anyChar (char 'Z') #if MIN_VERSION_time(1,5,0)     let  mt = parseTimeM True defaultTimeLocale (iso8601DateFormat $ Just "%X") d #else@@ -193,35 +208,35 @@   -- | Attoparsec 'double' parses scientific "e" notation; reimplement according to Toml spec.-float :: Parser TValue+float :: Parser Node float = VFloat <$> do-    n <- intStr-    _ <- char '.'-    d <- uintStr+    n <- intStr <* lookAhead (satisfy (\c -> c == '.' || c == 'e' || c == 'E'))+    d <- try (satisfy (== '.') *> uintStr) <|> return "0"     e <- try (satisfy (\c -> c == 'e' || c == 'E') *> intStr) <|> return "0"     return . read . L.concat $ [n, ".", d, "e", e]   where     sign    = try (string "-") <|> (try (char '+') >> return "") <|> return ""-    uintStr = many1 digit+    uintStr = (:) <$> digit <*> many (optional (char '_') *> digit)     intStr  = do s <- sign                  u <- uintStr                  return . L.concat $ [s, u]  -integer :: Parser TValue-integer = VInteger <$> (signed $ read <$> (many1 digit))--+integer :: Parser Node+integer = VInteger <$> (signed $ read <$> uintStr)+  where+    uintStr :: Parser [Char]+    uintStr = (:) <$> digit <*> many (optional (char '_') *> digit)  -- -- * Utility functions --  -- | Parses the elements of an array, while restricting them to a certain type.-arrayOf :: Parser TValue -> Parser TValue+arrayOf :: Parser Node -> Parser Node arrayOf p = VArray <$> between (char '[') (char ']') (skipBlanks *> separatedValues)   where-    separatedValues = sepEndBy (skipBlanks *> p <* skipBlanks) comma <* skipBlanks+    separatedValues = sepEndBy (skipBlanks *> try p <* skipBlanks) comma <* skipBlanks     comma           = skipBlanks >> char ',' >> skipBlanks  @@ -275,4 +290,4 @@  -- | Parse an EOL, as per TOML spec this is 0x0A a.k.a. '\n' or 0x0D a.k.a. '\r'. eol :: Parser ()-eol = satisfy (\c -> c == '\n' || c == '\r') >> return ()+eol = (string "\n" <|> string "\r\n") >> return ()
src/Text/Toml/Types.hs view
@@ -1,12 +1,24 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} -module Text.Toml.Types where+module Text.Toml.Types (+    Table+  , emptyTable+  , Node (..)+  , Explicitness (..)+  , isExplicit+  , insert+  , ToBsJSON (..)+  ) where +import           Control.Monad       (when)+import           Text.Parsec import           Data.Aeson.Types import qualified Data.HashMap.Strict as M import           Data.Int            (Int64) import           Data.List           (intersect)+import           Data.Set (Set)+import qualified Data.Set            as S import           Data.Text           (Text) import qualified Data.Text           as T import           Data.Time.Clock     (UTCTime)@@ -17,105 +29,129 @@ -- | The 'Table' is a mapping ('HashMap') of 'Text' keys to 'Node' values. type Table = M.HashMap Text Node ---- | A 'Node' may contain a 'TValue', a 'Table' or a table array '[Table]'.-data Node = NTValue TValue-          | NTable  Table-          | NTArray [Table]-  deriving (Eq, Show)----- | A 'TValue' may contain any type of value that can put in a 'VArray'.-data TValue = VString   Text-            | VInteger  Int64-            | VFloat    Double-            | VBoolean  Bool-            | VDatetime UTCTime-            | VArray    [TValue]-  deriving (Eq, Show)-- -- | Contruct an empty 'Table'. emptyTable :: Table emptyTable = M.empty +-- | A 'Node' may contain any type of value that can put in a 'VArray'.+data Node = VTable    Table+          | VTArray   [Table]+          | VString   !Text+          | VInteger  !Int64+          | VFloat    !Double+          | VBoolean  !Bool+          | VDatetime !UTCTime+          | VArray    [Node]+  deriving (Eq, Show) --- | Contruct an empty 'NTable'.-emptyNTable :: Node-emptyNTable = NTable M.empty+-- | To mark whether or not a 'Table' has been explicitly defined.+-- See: https://github.com/toml-lang/toml/issues/376+data Explicitness = Explicit | Implicit +-- | Convenience function to get a boolean value.+isExplicit :: Explicitness -> Bool+isExplicit Explicit = True+isExplicit Implicit = False --- | Inserts a table ('Table') with name ('[Text]') which may be part of--- a table array (when 'Bool' is 'True') into a 'Table'.--- It may result in an error ('Text') on the 'Left' or a modified table--- on the 'Right'.-insert :: ([Text], Node) -> Table -> Either Text Table-insert ([], _)         _ = error "FATAL: Cannot call 'insert' without a name."-insert (_ , NTValue _) _ = error "FATAL: Cannot call 'insert' with a TValue."-insert ([name], node) ttbl =-    -- In case 'name' is final++-- | Inserts a table, 'Table', with the namespaced name, '[Text]', (which+-- may be part of a table array) into a 'Table'.+-- It may result in an error in the 'ParsecT' monad for redefinitions.+insert :: Explicitness -> ([Text], Node) -> Table -> Parsec Text (Set [Text]) Table+insert _ ([], _) _ = parserFail "FATAL: Cannot call 'insert' without a name."+insert ex ([name], node) ttbl =+    -- In case 'name' is final (a top-level name)     case M.lookup name ttbl of-      Nothing           -> Right $ M.insert name node ttbl-      Just (NTable t)   -> case node of-        (NTable nt) -> case merge t nt of-          Left ds -> Left $ T.concat [ "Cannot redefine key(s) (", (T.intercalate ", " ds)-                                     , "), from table named '", name, "'." ]-          Right r -> Right $ M.insert name (NTable r) ttbl-        _         -> commonInsertError node [name]-      Just (NTArray a)  -> case node of-        (NTArray na) -> Right $ M.insert name (NTArray $ a ++ na) ttbl-        _         -> commonInsertError node [name]-      Just _            -> commonInsertError node [name]-insert (fullName@(name:ns), node) ttbl =-    -- In case 'name' is not final, but a sub-name+      Nothing -> do when (isExplicit ex) $ updateExState [name] node+                    return $ M.insert name node ttbl+      Just (VTable t) -> case node of+          (VTable nt) -> case merge t nt of+                  Left ds -> nameInsertError ds name+                  Right r -> do when (isExplicit ex) $+                                  updateExStateOrError [name] node+                                return $ M.insert name (VTable r) ttbl+          _ -> commonInsertError node [name]+      Just (VTArray a) -> case node of+          (VTArray na) -> return $ M.insert name (VTArray $ a ++ na) ttbl+          _ -> commonInsertError node [name]+      Just _ -> commonInsertError node [name]+insert ex (fullName@(name:ns), node) ttbl =+    -- In case 'name' is not final (not a top-level name)     case M.lookup name ttbl of-      Nothing           -> case insert (ns, node) emptyTable of-                             Left msg -> Left msg-                             Right r  -> Right $ M.insert name (NTable r) ttbl-      Just (NTable t)   -> case insert (ns, node) t of-                             Left msg -> Left msg-                             Right tt -> Right $ M.insert name (NTable tt) ttbl-      Just (NTArray []) -> error "FATAL: Call to 'insert' found impossibly empty NTArray."-      Just (NTArray a)  -> case insert (ns, node) (last a) of-                             Left msg -> Left msg-                             Right t  -> Right $ M.insert name (NTArray $ (init a) ++ [t]) ttbl-      Just _            -> commonInsertError node fullName+      Nothing -> do+          r <- insert Implicit (ns, node) emptyTable+          when (isExplicit ex) $ updateExState fullName node+          return $ M.insert name (VTable r) ttbl+      Just (VTable t) -> do+          r <- insert Implicit (ns, node) t+          when (isExplicit ex) $ updateExStateOrError fullName node+          return $ M.insert name (VTable r) ttbl+      Just (VTArray []) ->+          parserFail "FATAL: Call to 'insert' found impossibly empty VArray."+      Just (VTArray a) -> do+          r <- insert Implicit (ns, node) (last a)+          return $ M.insert name (VTArray $ (init a) ++ [r]) ttbl+      Just _ -> commonInsertError node fullName   -- | Merge two tables, resulting in an error when overlapping keys are--- found ('Left' will contian those keys).  When no overlapping keys are+-- found ('Left' will contain those keys).  When no overlapping keys are -- found the result will contain the union of both tables in a 'Right'. merge :: Table -> Table -> Either [Text] Table-merge existing new = case intersect (M.keys existing) (M.keys new) of+merge existing new = case M.keys existing `intersect` M.keys new of                        [] -> Right $ M.union existing new                        ds -> Left  $ ds +-- TOML tables maybe redefined when first definition was implicit.+-- For instance a top-level table `a` can implicitly defined by defining a non top-level+-- table `b` under it (namely with `[a.b]`). Once the table `a` is subsequently defined+-- explicitly (namely with `[a]`), it is then not possible to (re-)define it again.+-- A parser state of all explicitly defined tables is maintained, which allows+-- raising errors for illegal redefinitions of such.+updateExStateOrError :: [Text] -> Node -> Parsec Text (Set [Text]) ()+updateExStateOrError name node@(VTable _) = do+    explicitlyDefinedNames <- getState+    when (S.member name explicitlyDefinedNames) $ tableClashError name+    updateExState name node+updateExStateOrError _ _ = return () --- | Convenience function to construct a common error message for the 'insert' function.-commonInsertError :: Node -> [Text] -> Either Text Table-commonInsertError what name = Left . T.concat $ case what of-    NTValue _ -> ["Cannot insert a value '", n, "'."]-    _         -> ["Cannot insert ", w, " '", n, "' as key already exists."]+-- | Like 'updateExStateOrError' but does not raise errors. Only use this when sure+-- that redefinitions cannot occur.+updateExState :: [Text] -> Node -> Parsec Text (S.Set [Text]) ()+updateExState name (VTable _) = modifyState $ S.insert name+updateExState _ _ = return ()+++-- * Parse errors resulting from invalid TOML++-- | Key(s) redefintion error.+nameInsertError :: [Text] -> Text -> Parsec Text (Set [Text]) a+nameInsertError ns name = parserFail . T.unpack $ T.concat+    [ "Cannot redefine key(s) (", T.intercalate ", " ns+    , "), from table named '", name, "'." ]++-- | Table redefinition error.+tableClashError :: [Text] -> Parsec Text (Set [Text]) a+tableClashError name = parserFail . T.unpack $ T.concat+    [ "Cannot redefine table ('", T.intercalate ", " name , "'." ]++-- | Common redefinition error.+commonInsertError :: Node -> [Text] -> Parsec Text (Set [Text]) a+commonInsertError what name = parserFail . concat $+    [ "Cannot insert ", w, " '", n, "' as key already exists." ]   where-    n = T.intercalate "." name-    w = case what of (NTable _) -> "tables"+    n = T.unpack $ T.intercalate "." name+    w = case what of (VTable _) -> "tables"                      _          -> "array of tables"  - -- * Regular ToJSON instances  -- | 'ToJSON' instances for the 'Node' type that produce Aeson (JSON) -- in line with the TOML specification. instance ToJSON Node where-  toJSON (NTValue v) = toJSON v-  toJSON (NTable v)  = toJSON v-  toJSON (NTArray v) = toJSON v----- | 'ToJSON' instances for the 'TValue' type that produce Aeson (JSON)--- in line with the TOML specification.-instance ToJSON TValue where+  toJSON (VTable v)    = toJSON v+  toJSON (VTArray v)   = toJSON v   toJSON (VString v)   = toJSON v   toJSON (VInteger v)  = toJSON v   toJSON (VFloat v)    = toJSON v@@ -147,21 +183,14 @@   toBsJSON = Object . M.map toBsJSON   {-# INLINE toBsJSON #-} ---- | 'ToBsJSON' instances for the 'Node' type that produce Aeson (JSON)--- in line with BurntSushi's language agnostic TOML test suite.-instance ToBsJSON Node where-  toBsJSON (NTValue v) = toBsJSON v-  toBsJSON (NTable v)  = toBsJSON v-  toBsJSON (NTArray v) = toBsJSON v-- -- | 'ToBsJSON' instances for the 'TValue' type that produce Aeson (JSON) -- in line with BurntSushi's language agnostic TOML test suite. -- -- As seen in this function, BurntSushi's JSON encoding explicitly -- specifies the types of the values.-instance ToBsJSON TValue where+instance ToBsJSON Node where+  toBsJSON (VTable v)    = toBsJSON v+  toBsJSON (VTArray v)   = toBsJSON v   toBsJSON (VString v)   = object [ "type"  .= toJSON ("string" :: String)                                   , "value" .= toJSON v ]   toBsJSON (VInteger v)  = object [ "type"  .= toJSON ("integer" :: String)@@ -172,9 +201,9 @@                                   , "value" .= toJSON (if v then "true" else "false" :: String) ]   toBsJSON (VDatetime v) = object [ "type"  .= toJSON ("datetime" :: String)                                   , "value" .= toJSON (let s = show v-                                                           z = take (length s - 4) s  ++ "Z"+                                                           z = take (length s - 4)  s ++ "Z"                                                            d = take (length z - 10) z-                                                           t = drop (length z - 9) z+                                                           t = drop (length z - 9)  z                                                        in  d ++ "T" ++ t) ]   toBsJSON (VArray v)    = object [ "type"  .= toJSON ("array" :: String)                                   , "value" .= toBsJSON v ]
test/BurntSushi.hs view
@@ -18,7 +18,7 @@   allFiles :: [(FilePath, B.ByteString)]-allFiles = $(embedDir "test/BurntSushi")+allFiles = $(makeRelativeToProject "test/BurntSushi" >>= embedDir)   validPairs :: [(String, (B.ByteString, B.ByteString))]@@ -59,4 +59,4 @@     assertParseFailure f tomlBS =       case parseTomlDoc "test" (decodeUtf8 tomlBS) of         Left _ -> return ()-        Right _ -> assertFailure $ "Parser accepted invalid TOML file: " ++ f ++ ".toml"+        Right _ -> assertFailure $ "Parser accepted invalid TOML file: " ++ f
+ test/BurntSushi/invalid/key-space.toml view
@@ -0,0 +1,1 @@+a b = 1
+ test/BurntSushi/invalid/table-whitespace.toml view
@@ -0,0 +1,1 @@+[invalid key]
+ test/BurntSushi/invalid/table-with-pound.toml view
@@ -0,0 +1,2 @@+[key#group]+answer = 42
test/BurntSushi/valid/key-space.toml view
@@ -1,1 +1,1 @@-   a b    = 1+"a b" = 1
test/BurntSushi/valid/key-special-chars.json view
@@ -1,5 +1,5 @@ {-    "~!@$^&*()_+-`1234567890[]\\|/?><.,;:'": {+    "~!@$^&*()_+-`1234567890[]|/?><.,;:'": {         "type": "integer", "value": "1"     } }
test/BurntSushi/valid/key-special-chars.toml view
@@ -1,1 +1,1 @@-~!@$^&*()_+-`1234567890[]\|/?><.,;:' = 1+"~!@$^&*()_+-`1234567890[]|/?><.,;:'" = 1
test/BurntSushi/valid/long-integer.json view
@@ -1,4 +1,4 @@ {-    "answer": {"type": "integer", "value": "9223372036854776000"},-    "neganswer": {"type": "integer", "value": "-9223372036854776000"}+    "answer": {"type": "integer", "value": "9223372036854775807"},+    "neganswer": {"type": "integer", "value": "-9223372036854775808"} }
test/BurntSushi/valid/long-integer.toml view
@@ -1,2 +1,2 @@-answer = 9223372036854776000-neganswer = -9223372036854776000+answer = 9223372036854775807+neganswer = -9223372036854775808
test/BurntSushi/valid/multiline-string.json view
@@ -1,29 +1,29 @@ {-    "multiline empty one": {+    "multiline_empty_one": {         "type": "string",         "value": ""     },-    "multiline empty two": {+    "multiline_empty_two": {         "type": "string",         "value": ""     },-    "multiline empty three": {+    "multiline_empty_three": {         "type": "string",         "value": ""     },-    "multiline empty four": {+    "multiline_empty_four": {         "type": "string",         "value": ""     },-    "equivalent one": {+    "equivalent_one": {         "type": "string",         "value": "The quick brown fox jumps over the lazy dog."     },-    "equivalent two": {+    "equivalent_two": {         "type": "string",         "value": "The quick brown fox jumps over the lazy dog."     },-    "equivalent three": {+    "equivalent_three": {         "type": "string",         "value": "The quick brown fox jumps over the lazy dog."     }
test/BurntSushi/valid/multiline-string.toml view
@@ -1,22 +1,22 @@-multiline empty one = """"""-multiline empty two = """+multiline_empty_one = """"""+multiline_empty_two = """ """-multiline empty three = """\+multiline_empty_three = """\     """-multiline empty four = """\+multiline_empty_four = """\    \    \    """ -equivalent one = "The quick brown fox jumps over the lazy dog."-equivalent two = """+equivalent_one = "The quick brown fox jumps over the lazy dog."+equivalent_two = """ The quick brown \     fox jumps over \     the lazy dog.""" -equivalent three = """\+equivalent_three = """\        The quick brown \        fox jumps over \        the lazy dog.\
test/BurntSushi/valid/string-escapes.json view
@@ -23,12 +23,24 @@         "type": "string",         "value": "This string has a \u0022 quote character."     },-    "slash": {-        "type": "string",-        "value": "This string has a \u002F slash character."-    },     "backslash": {         "type": "string",         "value": "This string has a \u005C backslash character."+    },+    "notunicode1": {+        "type": "string",+        "value": "This string does not have a unicode \\u escape."+    },+    "notunicode2": {+        "type": "string",+        "value": "This string does not have a unicode \u005Cu escape."+    },+    "notunicode3": {+        "type": "string",+        "value": "This string does not have a unicode \\u0075 escape."+    },+    "notunicode4": {+        "type": "string",+        "value": "This string does not have a unicode \\\u0075 escape."     } }
test/BurntSushi/valid/string-escapes.toml view
@@ -4,5 +4,8 @@ formfeed = "This string has a \f form feed character." carriage = "This string has a \r carriage return character." quote = "This string has a \" quote character."-slash = "This string has a \/ slash character." backslash = "This string has a \\ backslash character."+notunicode1 = "This string does not have a unicode \\u escape."+notunicode2 = "This string does not have a unicode \u005Cu escape."+notunicode3 = "This string does not have a unicode \\u0075 escape."+notunicode4 = "This string does not have a unicode \\\u0075 escape."
test/BurntSushi/valid/table-whitespace.toml view
@@ -1,1 +1,1 @@-[valid key]+["valid key"]
test/BurntSushi/valid/table-with-pound.toml view
@@ -1,2 +1,2 @@-[key#group]+["key#group"] answer = 42
test/BurntSushi/valid/unicode-escape.json view
@@ -1,3 +1,4 @@ {-    "answer": {"type": "string", "value": "\u03B4"}+    "answer4": {"type": "string", "value": "\u03B4"},+    "answer8": {"type": "string", "value": "\u03B4"} }
test/BurntSushi/valid/unicode-escape.toml view
@@ -1,1 +1,2 @@-answer = "\u03B4"+answer4 = "\u03B4"+answer8 = "\U000003B4"
test/Test.hs view
@@ -1,6 +1,6 @@ module Main where -+import           Prelude               hiding (readFile)  -- needed for GHCi, see `.ghci` import           Test.Tasty            (defaultMain, testGroup)  import qualified BurntSushi
test/Text/Toml/Parser/Spec.hs view
@@ -22,11 +22,11 @@      it "should parse non-empty tomlDocs that do not end with a newline" $       testParser tomlDoc "number = 123" $-        fromList [("number", NTValue $ VInteger 123)]+        fromList [("number", VInteger 123)]      it "should parse when tomlDoc ends in a comment" $       testParser tomlDoc "q = 42  # understood?" $-        fromList [("q", NTValue $ VInteger 42)]+        fromList [("q", VInteger 42)]      it "should not parse re-assignment of key" $       testParserFails tomlDoc "q=42\nq=42"@@ -39,24 +39,28 @@      it "should parse simple named table" $       testParser tomlDoc "[a]\naa = 108" $-        fromList [("a", NTable (fromList [("aa", NTValue $ VInteger 108)] ))]+        fromList [("a", VTable (fromList [("aa", VInteger 108)] ))]      it "should not parse redefined table header (key already exists at scope)" $-      testParser tomlDoc "[a]\n[a]" $ fromList [("a", emptyNTable)]+      testParserFails tomlDoc "[a]\n[a]"      it "should parse redefinition of implicit key" $       testParser tomlDoc "[a.b]\n[a]" $-        fromList [("a", NTable (fromList [("b", emptyNTable)] ))]+        fromList [("a", VTable (fromList [("b", VTable emptyTable)] ))]      it "should parse redefinition of implicit key, with table contents" $       testParser tomlDoc "[a.b]\nb=3\n[a]\na=4" $-        fromList [("a", NTable (fromList [("b", NTable (fromList [("b", NTValue $ VInteger 3)])),-                                          ("a", NTValue $ VInteger 4)]))]+        fromList [("a", VTable (fromList [("b", VTable (fromList [("b", VInteger 3)])),+                                          ("a", VInteger 4)]))]      it "should parse redefinition by implicit table header" $       testParser tomlDoc "[a]\n[a.b]" $-        fromList [("a", NTable (fromList [("b", emptyNTable)] ))]+        fromList [("a", VTable (fromList [("b", VTable emptyTable)] ))] +    it "should parse inline tables the same as normal tables" $+      testParser tomlDoc "[a]\nb={}" $+        fromList [("a", VTable (fromList [("b", VTable emptyTable)] ))]+     it "should not parse redefinition key" $       testParserFails tomlDoc "[a]\nb=1\n[a.b]" @@ -65,26 +69,26 @@      it "should parse a simple empty table array" $       testParser tomlDoc "[[a]]\n[[a]]" $-        fromList [("a", NTArray [ fromList []+        fromList [("a", VTArray [ fromList []                                 , fromList [] ] )]      it "should parse a simple table array with content" $       testParser tomlDoc "[[a]]\na1=1\n[[a]]\na2=2" $-        fromList [("a", NTArray [ fromList [("a1", NTValue $ VInteger 1)]-                                , fromList [("a2", NTValue $ VInteger 2)] ] )]+        fromList [("a", VTArray [ fromList [("a1", VInteger 1)]+                                , fromList [("a2", VInteger 2)] ] )]      it "should not allow a simple table array to be inserted into a non table array" $       testParserFails tomlDoc "a = [1,2,3]\n[[a]]"      it "should parse a simple empty nested table array" $       testParser tomlDoc "[[a.b]]\n[[a.b]]" $-        fromList [("a", NTable (fromList [("b", NTArray [ emptyTable+        fromList [("a", VTable (fromList [("b", VTArray [ emptyTable                                                         , emptyTable ] )] ) )]      it "should parse a simple non empty table array" $       testParser tomlDoc "[[a.b]]\na1=1\n[[a.b]]\na2=2" $-        fromList [("a", NTable (fromList [("b", NTArray [ fromList [("a1", NTValue $ VInteger 1)]-                                                        , fromList [("a2", NTValue $ VInteger 2)]+        fromList [("a", VTable (fromList [("b", VTArray [ fromList [("a1", VInteger 1)]+                                                        , fromList [("a2", VInteger 2)]                                                         ] )] ) )]      it "should parse redefined implicit table header" $@@ -92,7 +96,7 @@      it "should parse redefinition by implicit table header" $       testParser tomlDoc "[[a]]\n[[a.b]]" $-        fromList [("a", NTArray [ fromList [("b", NTArray [ fromList [] ])] ] )]+        fromList [("a", VTArray [ fromList [("b", VTArray [ fromList [] ])] ] )]     describe "Parser.tomlDoc (mixed named tables and tables arrays)" $ do@@ -108,27 +112,27 @@      it "should parse redefined implicit table header (array by table)" $       testParser tomlDoc "[[a.b]]\n[a]" $-        fromList [("a", NTable (fromList [("b", NTArray [ fromList [] ])] ) )]+        fromList [("a", VTable (fromList [("b", VTArray [ fromList [] ])] ) )]      it "should not parse redefined implicit table header (array by table), when keys collide" $       testParserFails tomlDoc "[[a.b]]\n[a]\nb=1"      it "should insert sub-key of regular table in most recently defined table array" $       testParser tomlDoc "[[a]]\ni=0\n[[a]]\ni=1\n[a.b]" $-        fromList [("a", NTArray [ fromList [ ("i", NTValue $ VInteger 0) ]-                                , fromList [ ("b", NTable  $ fromList [] )-                                           , ("i", NTValue $ VInteger 1) ]+        fromList [("a", VTArray [ fromList [ ("i", VInteger 0) ]+                                , fromList [ ("b", VTable  $ fromList [] )+                                           , ("i", VInteger 1) ]                                 ] )]      it "should insert sub-key of table array" $       testParser tomlDoc "[a]\n[[a.b]]" $-        fromList [("a", NTable (fromList [("b", NTArray [fromList []])] ) )]+        fromList [("a", VTable (fromList [("b", VTArray [fromList []])] ) )]      it "should insert sub-key (with content) of table array" $       testParser tomlDoc "[a]\nq=42\n[[a.b]]\ni=0" $-        fromList [("a", NTable (fromList [ ("q", NTValue $ VInteger 42),-                                           ("b", NTArray [-                                                   fromList [("i", NTValue $ VInteger 0)]+        fromList [("a", VTable (fromList [ ("q", VInteger 42),+                                           ("b", VTArray [+                                                   fromList [("i", VInteger 0)]                                                    ]) ]) )]    describe "Parser.headerValue" $ do@@ -187,13 +191,7 @@     it "should parse when value on next line" $       testParser assignment "a =\n108" ("a", VInteger 108) -    it "should parse when assignment operator and value are on the next line" $-      testParser assignment "a\n= 108" ("a", VInteger 108) -    it "should parse when key, value and assignment operator are on separate lines" $-      testParser assignment "a\n=\n108" ("a", VInteger 108)--   describe "Parser.boolean" $ do      it "should parse true" $@@ -209,10 +207,10 @@   describe "Parser.basicStr" $ do      it "should parse the common escape sequences in basic strings" $-      testParser basicStr "\"123\\b\\t\\n\\f\\r\\\"\\/\\\\\"" $ VString "123\b\t\n\f\r\"/\\"+      testParser basicStr "\"123\\b\\t\\n\\f\\r\\\"\\/\\\\\"" $ "123\b\t\n\f\r\"/\\"      it "should parse the simple unicode value from the example" $-      testParser basicStr "\"中国\"" $ VString "中国"+      testParser basicStr "\"中国\"" $ "中国"      it "should parse escaped 4 digit unicode values" $       testParser assignment "special_k = \"\\u0416\"" ("special_k", VString "Ж")@@ -227,16 +225,19 @@   describe "Parser.multiBasicStr" $ do      it "should parse simple example" $-      testParser multiBasicStr "\"\"\"thorrough\"\"\"" $ VString "thorrough"+      testParser multiBasicStr "\"\"\"thorrough\"\"\"" $  "thorrough" +    it "should parse text containing a quote" $+      testParser multiBasicStr "\"\"\"is \"it\" complete\"\"\"" $ "is \"it\" complete"+     it "should parse with newlines" $-      testParser multiBasicStr "\"\"\"One\nTwo\"\"\"" $ VString "One\nTwo"+      testParser multiBasicStr "\"\"\"One\nTwo\"\"\"" $  "One\nTwo"      it "should parse with escaped newlines" $-      testParser multiBasicStr "\"\"\"One\\\nTwo\"\"\"" $ VString "OneTwo"+      testParser multiBasicStr "\"\"\"One\\\nTwo\"\"\"" $  "OneTwo"      it "should parse newlines, ignoring 1 leading newline" $-      testParser multiBasicStr "\"\"\"\nOne\\\nTwo\"\"\"" $ VString "OneTwo"+      testParser multiBasicStr "\"\"\"\nOne\\\nTwo\"\"\"" $  "OneTwo"      it "should parse with espaced whitespace" $       testParser multiBasicStr "\"\"\"\\\n\@@ -244,14 +245,17 @@                                \\\\n\                                \Jumped \\\n\                                \Lazy\\\n\-                               \ \"\"\"" $ VString "Quick Jumped Lazy"+                               \ \"\"\"" $  "Quick Jumped Lazy" +    it "should parse espaced on first" $+      testParser multiBasicStr "\"\"\"\\\nQuick \\\n\\\nJumped \\\nLazy\\\n\"\"\"" $  "Quick Jumped Lazy" +   describe "Parser.literalStr" $ do      it "should parse literally" $       testParser literalStr "'\"Your\" folder: \\\\User\\new\\tmp\\'" $-                            VString "\"Your\" folder: \\\\User\\new\\tmp\\"+                             "\"Your\" folder: \\\\User\\new\\tmp\\"      it "has no notion of 'escaped single quotes'" $       testParserFails tomlDoc "q = 'I don\\'t know.'"  -- string terminates before the "t"@@ -262,7 +266,7 @@     it "should parse literally" $       testParser multiLiteralStr         "'''\nFirst newline is dropped.\n   Other whitespace,\n  is preserved -- isn't it?'''"-        $ VString "First newline is dropped.\n   Other whitespace,\n  is preserved -- isn't it?"+        $  "First newline is dropped.\n   Other whitespace,\n  is preserved -- isn't it?"     describe "Parser.datetime" $ do@@ -304,7 +308,18 @@     it "should not accept floats without any decimals" $       testParserFails float "5." +    it "should accept floats which contain underscores" $+      testParser float "5_3.4_5" $ VFloat 53.45 +    it "should not accept floats which end with underscores" $+      testParserFails float "5_3.4_5_"++    it "should not accept floats which start with underscores" $+      testParserFails float "_5_3.4_5"++    it "should not accept floats which have two consecutive underscores" $+      testParserFails float "5__3.4__5"+   describe "Parser.integer" $ do      it "should parse positive integers" $@@ -319,7 +334,18 @@     it "should parse integers prefixed with a plus" $       testParser integer "+42" $ VInteger 42 +    it "should accept integers which contain underscores" $+      testParser integer "4_2" $ VInteger 42 +    it "should not accept integers which end with underscores" $+      testParserFails integer "4_2_"++    it "should not accept integers which start with underscores" $+      testParserFails integer "_4_2"++    it "should not accept integers which have two consecutive underscores" $+      testParserFails integer "4__2"+   describe "Parser.tomlDoc arrays" $ do      it "should parse an empty array" $@@ -384,6 +410,16 @@     it "should parse terminating commas in arrays(2)" $       testParser array "[1,2,]" $ VArray [ VInteger 1, VInteger 2 ] +  describe "Parser.tomlDoc inline tables" $ do++    it "should parse an empty inline table" $+      testParser inlineTable "{}" $ VTable (fromList [])++    it "should parse simple inline tables" $+      testParser inlineTable "{ a = 8 , b = \"things\" }" $ VTable (fromList [ ("a" , VInteger 8) , ("b", VString "things") ])++    it "should not parse simple inline tables with newline " $+      testParserFails inlineTable "{ a = 8 , \n b = \"things\" }"    where     testParser p str success = case parseOnly p str of Left  _ -> False