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 +7/−0
- README.md +77/−35
- htoml.cabal +6/−4
- src/Text/Toml.hs +3/−4
- src/Text/Toml/Parser.hs +82/−67
- src/Text/Toml/Types.hs +114/−85
- test/BurntSushi.hs +2/−2
- test/BurntSushi/invalid/key-space.toml +1/−0
- test/BurntSushi/invalid/table-whitespace.toml +1/−0
- test/BurntSushi/invalid/table-with-pound.toml +2/−0
- test/BurntSushi/valid/key-space.toml +1/−1
- test/BurntSushi/valid/key-special-chars.json +1/−1
- test/BurntSushi/valid/key-special-chars.toml +1/−1
- test/BurntSushi/valid/long-integer.json +2/−2
- test/BurntSushi/valid/long-integer.toml +2/−2
- test/BurntSushi/valid/multiline-string.json +7/−7
- test/BurntSushi/valid/multiline-string.toml +7/−7
- test/BurntSushi/valid/string-escapes.json +16/−4
- test/BurntSushi/valid/string-escapes.toml +4/−1
- test/BurntSushi/valid/table-whitespace.toml +1/−1
- test/BurntSushi/valid/table-with-pound.toml +1/−1
- test/BurntSushi/valid/unicode-escape.json +2/−1
- test/BurntSushi/valid/unicode-escape.toml +2/−1
- test/Test.hs +1/−1
- test/Text/Toml/Parser/Spec.hs +74/−38
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 ===== +[](https://travis-ci.org/cies/htoml)+[](https://hackage.haskell.org/package/html)+[](https://hackage.haskell.org/package/html)++[](http://stackage.org/lts-2/package/htoml)+[](http://stackage.org/lts-3/package/htoml)+[](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