waargonaut 0.6.2.0 → 0.8.0.0
raw patch · 42 files changed
+905/−284 lines, 42 filesdep +hw-json-standard-cursordep +records-sopdep −hw-jsondep ~digitdep ~hedgehogdep ~hedgehog-fnPVP ok
version bump matches the API change (PVP)
Dependencies added: hw-json-standard-cursor, records-sop
Dependencies removed: hw-json
Dependency ranges changed: digit, hedgehog, hedgehog-fn, hw-balancedparens, tasty-hedgehog, zippers
API changes (from Hackage documentation)
- Waargonaut.Decode: type SuccinctCursor = JsonCursor ByteString Poppy512 (SimpleBalancedParens (Vector Word64))
- Waargonaut.Decode.Types: instance HaskellWorks.Data.Json.Type.JsonTypeAt Waargonaut.Decode.Types.JCurs
- Waargonaut.Decode.Types: type SuccinctCursor = JsonCursor ByteString Poppy512 (SimpleBalancedParens (Vector Word64))
+ Waargonaut.Decode: type Cursor = GenericCursor ByteString CsPoppy1 RangeMin CsPoppy1
+ Waargonaut.Decode.Types: instance HaskellWorks.Data.Json.Standard.Cursor.Type.JsonTypeAt Waargonaut.Decode.Types.JCurs
+ Waargonaut.Decode.Types: type Cursor = GenericCursor ByteString CsPoppy1 RangeMin CsPoppy1
+ Waargonaut.Encode: onObj' :: Text -> b -> Encoder' b -> JObject WS Json -> JObject WS Json
+ Waargonaut.Generic: FieldNameAsKey :: NewtypeName
+ Waargonaut.Generic: gObjEncoder :: forall t a f xs. (Generic a, Applicative f, HasDatatypeInfo a, All2 (JsonEncode t) (Code a), IsRecord a xs) => Options -> Tagged t (ObjEncoder f a)
- Waargonaut.Decode: JCurs :: SuccinctCursor -> JCurs
+ Waargonaut.Decode: JCurs :: Cursor -> JCurs
- Waargonaut.Decode: [unJCurs] :: JCurs -> SuccinctCursor
+ Waargonaut.Decode: [unJCurs] :: JCurs -> Cursor
- Waargonaut.Decode: cursorRankL :: Lens' (JsonCursor s i p) Count
+ Waargonaut.Decode: cursorRankL :: Lens' Cursor Count
- Waargonaut.Decode.Types: JCurs :: SuccinctCursor -> JCurs
+ Waargonaut.Decode.Types: JCurs :: Cursor -> JCurs
- Waargonaut.Decode.Types: [unJCurs] :: JCurs -> SuccinctCursor
+ Waargonaut.Decode.Types: [unJCurs] :: JCurs -> Cursor
Files
- changelog.md +31/−0
- src/Waargonaut.hs +30/−28
- src/Waargonaut/Attoparsec.hs +3/−3
- src/Waargonaut/Decode.hs +198/−27
- src/Waargonaut/Decode/Error.hs +2/−2
- src/Waargonaut/Decode/Internal.hs +23/−22
- src/Waargonaut/Decode/Runners.hs +7/−3
- src/Waargonaut/Decode/Traversal.hs +15/−15
- src/Waargonaut/Decode/Types.hs +8/−17
- src/Waargonaut/Encode.hs +141/−31
- src/Waargonaut/Encode/Builder.hs +10/−1
- src/Waargonaut/Encode/Builder/CommaSep.hs +5/−1
- src/Waargonaut/Encode/Builder/JArray.hs +4/−0
- src/Waargonaut/Encode/Builder/JChar.hs +4/−0
- src/Waargonaut/Encode/Builder/JNumber.hs +4/−0
- src/Waargonaut/Encode/Builder/JObject.hs +4/−0
- src/Waargonaut/Encode/Builder/JString.hs +4/−0
- src/Waargonaut/Encode/Builder/Types.hs +8/−3
- src/Waargonaut/Encode/Builder/Whitespace.hs +4/−0
- src/Waargonaut/Encode/Types.hs +1/−1
- src/Waargonaut/Generic.hs +94/−25
- src/Waargonaut/Lens.hs +4/−0
- src/Waargonaut/Prettier.hs +4/−4
- src/Waargonaut/Test.hs +1/−1
- src/Waargonaut/Types/CommaSep.hs +7/−6
- src/Waargonaut/Types/CommaSep/Elem.hs +8/−2
- src/Waargonaut/Types/CommaSep/Elems.hs +7/−1
- src/Waargonaut/Types/JArray.hs +4/−1
- src/Waargonaut/Types/JChar.hs +4/−2
- src/Waargonaut/Types/JChar/Escaped.hs +4/−2
- src/Waargonaut/Types/JChar/HexDigit4.hs +5/−2
- src/Waargonaut/Types/JChar/Unescaped.hs +4/−1
- src/Waargonaut/Types/JNumber.hs +5/−2
- src/Waargonaut/Types/JObject.hs +2/−2
- src/Waargonaut/Types/Json.hs +6/−6
- src/Waargonaut/Types/Whitespace.hs +4/−1
- test/Generics.hs +31/−0
- test/Golden.hs +5/−0
- test/Main.hs +3/−0
- test/Prettier/NestedObjs.hs +124/−0
- test/Types/Common.hs +2/−1
- waargonaut.cabal +71/−71
changelog.md view
@@ -1,5 +1,36 @@ # Revision history for waargonaut +## 0.8.0.0 -- 2019-09-04++* Add `onObj'` which is just `onObj` but specialised to `Identity`.++* Add `gObjEncoder` for deriving `ObjEncoder` structures for record types only. Using the+ `IsRecord` constraint from `record-sop` package. This makes it easier to leverage the+ Contravariant functionality of the `ObjEncoder` without losing the benefits of deriving+ more trivial encoders.++* Added the `FieldNameAsKey` option to the newtype options for generic derived enc/decoders.++* Fixes #69 by removing duplicate call to `_optionsFieldName` function. Added regression test.++* Improved the handling of newtype options for generic deriving to give a bit more+ flexibility and avoid strangeness with respect to some combinations of options.++* Change the building of escaped whitespace chars to actually use the+ `escapedWhitespaceChar` function, instead of incorrectly generating an unescaped+ character.+ +* Add haddock to gObjEncoder function++* Correctly bump version to 0.8.0.0 as this is a breaking change because of new+ constructors on an exported sum type.++* Remove some commented out code.++* Add a better failure message to "impossible" error case.++* Regenerate nix after cabal file changes+ ## 0.6.1.0 -- 2019-02-27 * Add `passKeysToValues` decoder for decoding JSON objects where the key should
src/Waargonaut.hs view
@@ -52,7 +52,7 @@ -- } -- @ ----- We'll need to import the 'Decode' module. You may of course use whatever import scheme you like,+-- We'll need to import the @Waargonaut.Decode@ module. You may of course use whatever import scheme you like, -- I prefer this method: -- -- @@@ -60,37 +60,39 @@ -- import qualified Waargonaut.Decode as D -- @ ----- The 'Waargonaut.Decode.Decoder' is based upon a data structure called a 'zipper'. This allows us+-- The 'Waargonaut.Decode.Decoder' is based upon a data structure called a "zipper". This allows us -- to move around the JSON structure using arbitrary movements. Such as -- 'Waargonaut.Decode.moveRight1' to move from a key on an object to the value at that key. Or -- 'Waargonaut.Decode.down' to move into the first element of an array or object. Waargonaut -- provides a suite of these functions to move around and dissect the JSON input. ----- This zipper is combined with a 'StateT' transformer that maintains a history of your movements.+-- This zipper is combined with a 'Control.Monad.State.StateT' transformer that maintains a history+-- of your movements.+-- -- So if the JSON input is not as your 'Waargonaut.Decode.Decoder' expects you are given a complete -- path to where things went awry. -- -- Decoding a JSON value is done by moving the cursor to specific points of interest, then focusing -- on that point with a 'Waargonaut.Decode.Decoder' of the desired value. ----- NB: The 'Monad' constraint is provided as a flexibility for more interesting and nefarious uses+-- NB: The "Monad" constraint is provided as a flexibility for more interesting and nefarious uses -- of 'Waargonaut.Decode.Decoder'. ----- Here is the 'Waargonaut.Decode.Decoder' for our 'Person' data type. It will help to turn on the--- 'OverloadedStrings' language pragma as these functions expect 'Data.Text.Text' input.+-- Here is the 'Waargonaut.Decode.Decoder' for our @Person@ data type. It will help to turn on the+-- @OverloadedStrings@ language pragma as these functions expect 'Data.Text.Text' input. -- -- @ -- personDecoder :: Monad f => Decoder f Person -- personDecoder = D.withCursor $ \\c -> do -- o <- D.down c--- name <- D.fromKey "name" D.text o--- age <- D.fromKey "age" D.int o--- addr <- D.fromKey "address" D.text o--- lotto <- D.fromKey "numbers" (D.list D.int) o+-- name <- D.fromKey \"name\" D.text o+-- age <- D.fromKey \"age\" D.int o+-- addr <- D.fromKey \"address\" D.text o+-- lotto <- D.fromKey \"numbers\" (D.list D.int) o -- pure $ Person name age addr lotto -- @ ----- The 'Waargonaut.Decode.withCursor' function provides our cursor: 'c'. We then move+-- The 'Waargonaut.Decode.withCursor' function provides our cursor: "c". We then move -- 'Waargonaut.Decode.down' into the JSON object. The reasons for this are: -- -- * The initial cursor position is always at the very beginning of the input. On freshly indexed@@ -125,10 +127,10 @@ -- @ -- personDecoder2 :: Monad f => Decoder f Person -- personDecoder2 = Person--- \<$> D.atKey "name" D.text--- \<*> D.atKey "age" D.int--- \<*> D.atKey "address" D.text--- \<*> D.atKey "numbers" (D.list D.int)+-- \<$> D.atKey \"name\" D.text+-- \<*> D.atKey \"age\" D.int+-- \<*> D.atKey \"address\" D.text+-- \<*> D.atKey \"numbers\" (D.list D.int) -- @ -- -- Using the 'Waargonaut.Decode.atKey' function which tries to handle those basic movements for us@@ -140,10 +142,10 @@ -- @ -- -- The next part is being able to apply our 'Waargonaut.Decode.Decoder' to some input. Assuming we--- have some input 'in'. We want to pass it through our 'personDecoder' for a result. Waargonaut uses+-- have some input. We want to pass it through our @personDecoder@ for a result. Waargonaut uses -- the <https://hackage.haskell.org/package/parsers parsers> package to define its parser. This -- allows you to choose your own favourite parsing library to do the heavy lifting. Provided it--- implements the right typeclasses from 'parsers'.+-- implements the right typeclasses from the @parsers@ package. -- -- To apply a 'Waargonaut.Decode.Decoder' to some input you will need one of the -- decoder running functions from 'Waargonaut.Decode'. There are a few different@@ -164,8 +166,8 @@ -- -- -- As well as a parsing function from your parsing library of choice, that also--- has an implementation of the 'CharParsing' typeclass from 'parsers'. We will--- use 'attoparsec' in the examples below.+-- has an implementation of the 'Text.Parser.Char.CharParsing' typeclass from @parsers@. We will+-- use @attoparsec@ in the examples below. -- -- @ -- import qualified Data.Attoparsec.ByteString as AB@@ -175,14 +177,14 @@ -- decodeFromByteString AB.parseOnly personDecode inp -- @ ----- Which will run the 'personDecode' 'Waargonaut.Decode.Decoder' using the parsing function--- (@AB.parseOnly@), starting at the cursor from the top of the 'inp' input.+-- Which will run the @personDecode@ 'Waargonaut.Decode.Decoder' using the parsing function+-- (@AB.parseOnly@), starting at the cursor from the top of the @inp@ input. ----- Again the 'Monad' constraint is there so that you have more options available for utilising the+-- Again the 'Control.Monad.Monad' constraint is there so that you have more options available for utilising the -- 'Waargonaut.Decode.Decoder' in ways we haven't thought of. ----- Or if you don't need the 'Monad' constraint then you may use 'Waargonaut.Decode.pureDecodeFromByteString'.--- This function specialises the 'Monad' constraint to 'Data.Functor.Identity'.:+-- Or if you don't need the 'Control.Monad.Monad' constraint then you may use 'Waargonaut.Decode.pureDecodeFromByteString'.+-- This function specialises the 'Control.Monad.Monad' constraint to 'Data.Functor.Identity'.: -- -- @ -- pureDecodeFromByteString@@ -200,11 +202,11 @@ -- pureDecodeFromByteString AB.parseOnly personDecode inp -- @ ----- Waargonaut provides some default implementations using the <https://hackage.haskell.org/package/attoparsec attoparsec> package in the 'Waargonaut.Attoparsec' module. These functions have exactly the same behaviour as the functions above, without the need to provide the parsing function.+-- Waargonaut provides some default implementations using the <https://hackage.haskell.org/package/attoparsec attoparsec> package in the @Waargonaut.Attoparsec@ module. These functions have exactly the same behaviour as the functions above, without the need to provide the parsing function. -- $basicencode ----- To create an 'Waargonaut.Encode.Encoder' for our 'Person' record, we will encode it as a "map+-- To create an 'Waargonaut.Encode.Encoder' for our @Person@ record, we will encode it as a "map -- like object", that is we have decided that there are no duplicate keys allowed. We can then use -- the following functions to build up the structure we want: --@@ -235,7 +237,7 @@ -- @ -- -- These types may seem pretty wild, but their usage is mundane. The 'Waargonaut.Encode.mapLikeObj'--- function is used when we want to encode some particular type 'i' as a JSON object. In such a way+-- function is used when we want to encode some particular type @i@ as a JSON object. In such a way -- as to prevent duplicate keys from appearing. The 'Waargonaut.Encode.atKey' function is designed -- such that it can be composed with itself to build up an object with multiple keys. --@@ -278,7 +280,7 @@ -- simplePureEncodeByteStringNoSpaces :: Encoder' a -> a -> ByteString -- @ ----- The latter functions specialise the 'f' to be 'Data.Functor.Identity'.+-- The latter functions specialise the @f@ to be 'Data.Functor.Identity'. -- -- Then, like the use of the 'Waargonaut.Decode.Decoder' you select the 'Waargonaut.Encode.Encoder' -- you wish to use and run it against a value of a matching type:
src/Waargonaut/Attoparsec.hs view
@@ -5,9 +5,9 @@ -- different parsing libraries, depending on your needs. This module provides -- some convenient defaults using the <https://hackage.haskell.org/package/attoparsec attoparsec> package. ----- These functions are implemented using the 'decodeFromX' functions in the--- 'Waargonaut.Decode' module. They use the @parseOnly@ function, from either--- the @Text@ or @ByteString@ attoparsec modules.+-- These functions are implemented using the @decodeFromX@ functions in the+-- @Waargonaut.Decode@ module. They use the @parseOnly@ function, from either+-- the 'Data.Attoparsec.Text' or 'Data.Attoparsec.ByteString' attoparsec modules. -- module Waargonaut.Attoparsec ( -- * Decoders
src/Waargonaut/Decode.hs view
@@ -10,11 +10,182 @@ -- -- Types and Functions for turning JSON into Haskell. --+-- We will work through a basic example, using the following type:+--+-- @+-- data Person = Person+-- { _personName :: Text+-- , _personAge :: Int+-- , _personAddress :: Text+-- , _personFavouriteLotteryNumbers :: [Int]+-- }+-- deriving (Eq, Show)+-- @+--+-- Expect the following JSON as input:+--+-- @+-- { \"name\": \"Krag\"+-- , \"age\": 88+-- , \"address\": \"Red House 4, Three Neck Lane, Greentown.\"+-- , \"numbers\": [86,3,32,42,73]+-- }+-- @+--+-- We'll need to import the 'Waargonaut.Decode' module. You may of course use whatever import scheme you like,+-- I prefer this method:+--+-- @+-- import Waargonaut.Decode (Decoder)+-- import qualified Waargonaut.Decode as D+-- @+--+-- The 'Waargonaut.Decode.Decoder' is based upon a data structure called a @zipper@. This allows us+-- to move around the JSON structure using arbitrary movements. Such as+-- 'Waargonaut.Decode.moveRight1' to move from a key on an object to the value at that key. Or+-- 'Waargonaut.Decode.down' to move into the first element of an array or object. Waargonaut+-- provides a suite of these functions to move around and dissect the JSON input.+--+-- This zipper is combined with a 'Control.Monad.State.StateT' transformer that maintains a history of your movements.+-- So if the JSON input is not as your 'Waargonaut.Decode.Decoder' expects you are given a complete+-- path to where things went awry.+--+-- Decoding a JSON value is done by moving the cursor to specific points of interest, then focusing+-- on that point with a 'Waargonaut.Decode.Decoder' of the desired value.+--+-- NB: The "Monad" constraint is provided as a flexibility for more interesting and nefarious uses+-- of 'Waargonaut.Decode.Decoder'.+--+-- Here is the 'Waargonaut.Decode.Decoder' for our @Person@ data type. It will help to turn on the+-- @OverloadedStrings@ language pragma as these functions expect 'Data.Text.Text' input.+--+-- @+-- personDecoder :: Monad f => Decoder f Person+-- personDecoder = D.withCursor $ \\c -> do+-- o <- D.down c+-- name <- D.fromKey "name" D.text o+-- age <- D.fromKey "age" D.int o+-- addr <- D.fromKey "address" D.text o+-- lotto <- D.fromKey "numbers" (D.list D.int) o+-- pure $ Person name age addr lotto+-- @+--+-- The 'Waargonaut.Decode.withCursor' function provides our cursor: @c@. We then move+-- 'Waargonaut.Decode.down' into the JSON object. The reasons for this are:+--+-- * The initial cursor position is always at the very beginning of the input. On freshly indexed+-- JSON input, using our example, the cursor will be at:+--+-- @+-- \<cursor\>{ \"name\": \"Krag\"+-- , \"age\": 88+-- ...+-- @+--+-- * Because of the above reason, our decoder makes an assumption about the placement of the cursor+-- on the JSON input. This sort of assumption is reasonable for reasons we will go over later.+--+-- The cursor output from 'Waargonaut.Decode.down' will located here:+--+-- @+-- { \<cursor\>\"name\": \"Krag\"+-- , \"age\": 88+-- ...+-- @+--+-- Then we use one of the helper functions, 'Waargonaut.Decode.fromKey' to find the "key - value"+-- pair that we're interested in and decode it for us:+--+-- @+-- fromKey :: Monad f => Text -> Decoder f b -> JCurs -> DecodeResult f b+-- @+--+-- We could also write this 'Waargonaut.Decode.Decoder' as:+--+-- @+-- personDecoder2 :: Monad f => Decoder f Person+-- personDecoder2 = Person+-- \<$> D.atKey \"name\" D.text+-- \<*> D.atKey \"age\" D.int+-- \<*> D.atKey \"address\" D.text+-- \<*> D.atKey \"numbers\" (D.list D.int)+-- @+--+-- Using the 'Waargonaut.Decode.atKey' function which tries to handle those basic movements for us+-- and has those assumptions included. Very useful for when the JSON input closely mirrors your data+-- structure.+--+-- @+-- atKey :: Monad f => Text -> Decoder f a -> Decoder f a+-- @+--+-- The next part is being able to apply our 'Waargonaut.Decode.Decoder' to some input. Assuming we+-- have some input. We want to pass it through our @personDecoder@ for a result. Waargonaut uses+-- the <https://hackage.haskell.org/package/parsers parsers> package to define its parser. This+-- allows you to choose your own favourite parsing library to do the heavy lifting. Provided it+-- implements the right typeclasses from the @parsers@ package.+--+-- To apply a 'Waargonaut.Decode.Decoder' to some input you will need one of the+-- decoder running functions from 'Waargonaut.Decode'. There are a few different+-- functions provided for some of the common input text-like types.:+--+-- @+-- decodeFromByteString+-- :: ( CharParsing f+-- , Monad f+-- , Monad g+-- , Show e+-- )+-- => (forall a. f a -> ByteString -> Either e a)+-- -> Decoder g x+-- -> ByteString+-- -> g (Either (DecodeError, CursorHistory) x)+-- @+--+--+-- As well as a parsing function from your parsing library of choice, that also+-- has an implementation of the 'Text.Parser.Char.CharParsing' typeclass from @parsers@. We will+-- use @attoparsec@ in the examples below.+--+-- @+-- import qualified Data.Attoparsec.ByteString as AB+-- @+--+-- @+-- decodeFromByteString AB.parseOnly personDecode inp+-- @+--+-- Which will run the @personDecode@ 'Waargonaut.Decode.Decoder' using the parsing function+-- (@AB.parseOnly@), starting at the cursor from the top of the @inp@ input.+--+-- Again the 'Control.Monad.Monad' constraint is there so that you have more options available for utilising the+-- 'Waargonaut.Decode.Decoder' in ways we haven't thought of.+--+-- Or if you don't need the 'Control.Monad.Monad' constraint then you may use 'Waargonaut.Decode.pureDecodeFromByteString'.+-- This function specialises the 'Control.Monad.Monad' constraint to 'Data.Functor.Identity'.:+--+-- @+-- pureDecodeFromByteString+-- :: ( Monad f+-- , CharParsing f+-- , Show e+-- )+-- => (forall a. f a -> ByteString -> Either e a)+-- -> Decoder Identity x+-- -> ByteString+-- -> Either (DecodeError, CursorHistory) x+-- @+--+-- @+-- pureDecodeFromByteString AB.parseOnly personDecode inp+-- @+--+-- Waargonaut provides some default implementations using the <https://hackage.haskell.org/package/attoparsec attoparsec> package in the @Waargonaut.Attoparsec@ module. These functions have exactly the same behaviour as the functions above, without the need to provide the parsing function. module Waargonaut.Decode ( -- * Types CursorHistory- , SuccinctCursor+ , Cursor , DecodeResult (..) , Decoder (..) , JCurs (..)@@ -165,8 +336,8 @@ import HaskellWorks.Data.Bits ((.?.)) import HaskellWorks.Data.TreeCursor (TreeCursor (..)) -import HaskellWorks.Data.Json.Backend.Standard.Cursor (JsonCursor (..))-import qualified HaskellWorks.Data.Json.Backend.Standard.Cursor as JC+import HaskellWorks.Data.Json.Standard.Cursor.Fast (Cursor)+import qualified HaskellWorks.Data.Json.Standard.Cursor.Generic as JC import Waargonaut.Decode.Error (AsDecodeError (..), DecodeError (..),@@ -182,11 +353,10 @@ Decoder (..), JCurs (..), JsonType (..),- SuccinctCursor, jsonTypeAt, mkCursor) --- | Function to define a 'Decoder' for a specific data type.+-- | Function to define a 'Waargonaut.Decode.Decoder' for a specific data type. -- -- For example, given the following data type: --@@ -200,8 +370,9 @@ -- } -- @ ----- We can use 'withCursor' to write a decoder that will be given a cursor that--- we can use to build the data types that we need.+-- We can use 'Waargonaut.Decode.withCursor' to write a decoder that+-- will be given a cursor that we can use to build the data types that+-- we need. -- -- @ -- imageDecoder :: Monad f => Decoder f Image@@ -214,7 +385,7 @@ -- @ -- -- It's up to you to provide a cursor that is at the correct position for a--- 'Decoder' to operate, but building decoders in this way simplifies creating+-- 'Waargonaut.Decode.Decoder' to operate, but building decoders in this way simplifies creating -- decoders for larger structures, as the smaller pieces contain fewer -- assumptions. This encourages greater reuse of decoders and simplifies the -- debugging process.@@ -228,10 +399,10 @@ -- | Lens for accessing the 'rank' of the 'JsonCursor'. The 'rank' forms part of -- the calculation that is the cursors current position in the index. ---cursorRankL :: Lens' (JsonCursor s i p) Count-cursorRankL = lens JC.cursorRank (\c r -> c { cursorRank = r })+cursorRankL :: Lens' Cursor Count+cursorRankL = lens JC.cursorRank (\c r -> c { JC.cursorRank = r }) --- | Execute the given function 'n' times'.+-- | Execute the given function @n@ times. manyMoves :: Monad m => Natural -> (b -> m b) -> b -> m b manyMoves i g = foldl (>=>) pure (replicate i g) @@ -245,7 +416,7 @@ -- successful. moveCursBasic :: Monad f- => (SuccinctCursor -> Maybe SuccinctCursor)+ => (Cursor -> Maybe Cursor) -> ZipperMove -> JCurs -> DecodeResult f JCurs@@ -326,7 +497,7 @@ then pure $ c & _Wrapped . cursorRankL .~ newRank else throwing _InputOutOfBounds newRank --- | Move the cursor rightwards 'n' times.+-- | Move the cursor rightwards @n@ times. -- -- Starting position: --@@ -374,7 +545,7 @@ in setRank <$> BP.findOpen (JC.balancedParens c) prev <?> InputOutOfBounds prev --- | Move the cursor leftwards 'n' times.+-- | Move the cursor leftwards @n@ times. moveLeftN :: Monad f => Natural@@ -498,8 +669,8 @@ -- -- myRecDecoder :: Decoder f MyRec -- myRecDecoder = MyRec--- \<$> atKey "field_a" text--- \<*> atKey "field_b" int+-- \<$> atKey \"field_a\" text+-- \<*> atKey \"field_b\" int -- @ -- atKey@@ -534,7 +705,7 @@ -- it could be decoded as follows: -- -- @--- join \<$> atKeyOptional "key" (maybeOrNull text)+-- join \<$> atKeyOptional \"key\" (maybeOrNull text) -- @ atKeyOptional :: Monad f@@ -568,7 +739,7 @@ -- withType JsonTypeArray d -- @ ----- 'd' will only be entered if the cursor at the current position is a JSON+-- @d@ will only be entered if the cursor at the current position is a JSON -- array: '[]'. -- withType@@ -675,9 +846,9 @@ -- -- decodeMyEnum :: Monad f => Decoder f MyEnum -- decodeMyEnum = D.oneOf D.text \"MyEnum\"--- [ ("a", A)--- , ("b", B)--- , ("c", C)+-- [ (\"a\", A)+-- , (\"b\", B)+-- , (\"c\", C) -- ] -- -- decodeMyEnumFromInt :: Monad f => Decoder f MyEnum@@ -704,7 +875,7 @@ err = throwing _ConversionFailure l -- | From the current cursor position, move leftwards one position at a time and--- push each 'a' onto the front of some 'Cons' structure.+-- push each @a@ onto the front of some 'Cons' structure. leftwardCons :: ( Monad f , Cons s s a a@@ -717,7 +888,7 @@ foldCursor (flip cons) moveLeft1 -- | From the current cursor position, move rightwards one position at a time,--- and append the 'a' to some 'Snoc' structure.+-- and append the @a@ to some 'Snoc' structure. rightwardSnoc :: ( Monad f , Snoc s s a a@@ -815,7 +986,7 @@ bool :: Monad f => Decoder f Bool bool = atCursor "bool" DI.bool' --- | Given a 'Decoder' for 'a', attempt to decode a 'NonEmpty' list of 'a' at+-- | Given a 'Decoder' for @a@, attempt to decode a 'NonEmpty' list of @a@ at -- the current cursor position. nonemptyAt :: Monad f@@ -832,7 +1003,7 @@ nonempty :: Monad f => Decoder f a -> Decoder f (NonEmpty a) nonempty d = withCursor (nonemptyAt d) --- | Like 'nonemptyAt', this takes a 'Decoder' of 'a' and at the given cursor+-- | Like 'nonemptyAt', this takes a 'Decoder' of @a@ and at the given cursor -- will try to decode a '[a]'. listAt :: Monad f@@ -877,7 +1048,7 @@ withDefault def hasD = fromMaybe def <$> hasD --- | Named to match it's 'Encoder' counterpart, this function will decode an+-- | Named to match it's 'Waargonaut.Encode.Encoder' counterpart, this function will decode an -- optional value. maybeOrNull :: Monad f@@ -886,7 +1057,7 @@ maybeOrNull a = (Just <$> a) <!> (Nothing <$ null) --- | Decode either an 'a' or a 'b', failing if neither 'Decoder' succeeds. The+-- | Decode either an @a@ or a @b@, failing if neither 'Decoder' succeeds. The -- 'Right' decoder is attempted first. either :: Monad f
src/Waargonaut/Decode/Error.hs view
@@ -11,7 +11,7 @@ import GHC.Word (Word64) -import HaskellWorks.Data.Json.Type (JsonType)+import HaskellWorks.Data.Json.Standard.Cursor.Type (JsonType) import Data.Text (Text) @@ -40,7 +40,7 @@ | ParseFailed Text deriving (Show, Eq) --- | Describes the sorts of errors that may be treated as a 'DecodeError', for use with 'lens'.+-- | Describes the sorts of errors that may be treated as a 'DecodeError', for use with 'Control.Lens.Prism's. class AsDecodeError r where _DecodeError :: Prism' r DecodeError _ConversionFailure :: Prism' r Text
src/Waargonaut/Decode/Internal.hs view
@@ -97,9 +97,6 @@ DecodeError (..)) import Waargonaut.Decode.ZipperMove (ZipperMove (..), ppZipperMove) -import Waargonaut.Encode.Builder (bsBuilder)-import Waargonaut.Encode.Builder.JChar (jCharBuilder)--- | -- Track the history of the cursor as we move around the zipper. -- -- It is indexed over the type of the index used to navigate the zipper.@@ -156,7 +153,7 @@ compressHistory = L.over _Wrapped (fromList . L.transform (combineLRMoves . rmKeyJumps) . F.toList) -- |--- Pretty print the given 'CursorHistory'' to a more useful format compared to a 'Seq' of 'i'.+-- Pretty print the given 'CursorHistory'' to a more useful format compared to a 'Seq' of @i@. ppCursorHistory :: CursorHistory' i -> Doc a@@ -210,7 +207,7 @@ . runExceptT . runDecodeResult -- |--- Wrapper type to describe a "Decoder" from something that has a 'Json'ish+-- Wrapper type to describe a "Decoder" from something that has a "Json"ish -- value @c@, to some representation of @a@. -- newtype Decoder' c i e f a = Decoder'@@ -236,7 +233,7 @@ -- Helper function for constructing a 'Decoder''. -- -- This function is used by the implemented decoders to simplify constructing a--- more specific 'Decoder' type.+-- more specific 'Decoder'' type. -- -- @ -- withCursor' $ \curs ->@@ -251,7 +248,7 @@ Decoder' -- |--- Execute a given 'DecoderResultT'.+-- Execute a given 'Waargonaut.Decode.Internal.DecoderResultT'. -- -- If you're building your own decoder structure, this function will take care -- of the 'CursorHistory'' and error handling (via 'ExceptT').@@ -274,14 +271,14 @@ recordZipperMove dir i = L._Wrapped %= (`L.snoc` (dir, i)) -- |--- Attempt a 'Decoder' action that might fail and return a 'Maybe' value+-- Attempt a 'Waargonaut.Decode.Internal.Decoder' action that might fail and return a 'Maybe' value -- instead. -- try :: MonadError e m => m a -> m (Maybe a) try d = catchError (pure <$> d) (const (pure Nothing)) -- |--- Build the basis for a 'Decoder' based on a 'Prism''.+-- Build the basis for a 'Waargonaut.Decode.Internal.Decoder' based on a 'Control.Lens.Prism''. -- prismDOrFail' :: ( AsDecodeError e@@ -295,24 +292,28 @@ prismDOrFail' e p d c = runDecoder' (L.preview p <$> d) c <!?> e --- | Try to decode a 'Text' value from some 'Json' or value. This will fail if+-- | Try to decode a 'Text' value from some 'Waargonaut.Types.Json.Json' or value. This will fail if -- the input value is not a valid UTF-8 'Text' value, as checked by the -- 'Data.Text.Encoding.decodeUtf8'' function. text' :: AsJType a ws a => a -> Maybe Text text' = L.preview (_JStr . _1 . _JStringText) --- | Try to decode a 'String' value from some 'Json' or value.+-- | Try to decode a 'String' value from some 'Waargonaut.Types.Json.Json' or value. string' :: AsJType a ws a => a -> Maybe String string' = L.preview (_JStr . _1 . _Wrapped . L.to (V.toList . V.map jCharToChar)) --- | Try to decode a 'Data.ByteString.ByteString' value from some 'Json' or value.+-- | Try to decode a 'Data.ByteString.ByteString' value from some 'Waargonaut.Types.Json.Json' or value. strictByteString' :: AsJType a ws a => a -> Maybe ByteString strictByteString' = fmap BL.toStrict . lazyByteString' --- | Try to decode a 'Data.ByteString.Lazy.ByteString' value from some 'Json' or value.+-- | Try to decode a 'Data.ByteString.Lazy.ByteString' value from some 'Waargonaut.Types.Json.Json' or value. lazyByteString' :: AsJType a ws a => a -> Maybe BL.ByteString lazyByteString' = L.preview (_JStr . _1 . _Wrapped . L.to mkBS)- where mkBS = BB.toLazyByteString . foldMap (jCharBuilder bsBuilder)+ -- This uses the 'Data.ByteString.Builder.char8' function as parsing has+ -- validated our inputs. If we use 'Data.ByteString.Builder.charUtf8' or+ -- 'Waargonaut.Builder.bsBuilder' the input will be incorrectly "double+ -- encoded" and everything will be wrong. + where mkBS = BB.toLazyByteString . foldMap (BB.char8 . jCharToChar) -- | Decoder for a 'Char' value that cannot contain values in the range U+D800 -- to U+DFFF. This decoder will fail if the 'Char' is outside of this range.@@ -324,27 +325,27 @@ unboundedChar' :: AsJType a ws a => a -> Maybe Char unboundedChar' = L.preview (_JStr . _1 . _Wrapped . L._head . L.to jCharToChar) --- | Try to decode a 'Scientific' value from some 'Json' or value.+-- | Try to decode a 'Scientific' value from some 'Waargonaut.Types.Json.Json' or value. scientific' :: AsJType a ws a => a -> Maybe Scientific scientific' = L.preview (_JNum . _1) >=> jNumberToScientific --- | Try to decode a bounded 'Integral n => n' value from some 'Json' value.+-- | Try to decode a bounded 'Integral n => n' value from some 'Waargonaut.Types.Json.Json' value. integral' :: (Bounded i , Integral i , AsJType a ws a) => a -> Maybe i integral' = scientific' >=> Sci.toBoundedInteger --- | Try to decode an 'Int' from some 'Json' value+-- | Try to decode an 'Int' from some 'Waargonaut.Types.Json.Json' value int' :: AsJType a ws a => a -> Maybe Int int' = integral' --- | Try to decode a 'Bool' from some 'Json' value+-- | Try to decode a 'Bool' from some 'Waargonaut.Types.Json.Json' value bool' :: AsJType a ws a => a -> Maybe Bool bool' = L.preview (_JBool . _1) --- | Try to decode a 'null' value from some 'Json' value+-- | Try to decode a 'null' value from some 'Waargonaut.Types.Json.Json' value null' :: AsJType a ws a => a -> Maybe () null' a = L.preview _JNull a $> () --- | Combined with another decoder function 'f', try to decode a list of 'a' values.+-- | Combined with another decoder function @f@, try to decode a list of @a@ values. -- -- @ -- array' int' :: Json -> [Int]@@ -379,9 +380,9 @@ -- -- Starting from the given cursor position, try to move in the direction -- specified by the given cursor function. Attempting to decode each item at each--- position using the given 'Decoder', until the movement is unsuccessful.+-- position using the given 'Waargonaut.Decode.Internal.Decoder', until the movement is unsuccessful. ----- The following could be used to leverage the 'Snoc' instance of '[]' to build '[Int]'.+-- The following could be used to leverage the 'Control.Lens.Snoc' instance of '[]' to build '[Int]'. -- -- @ -- intList :: Monad f => JCurs -> DecodeResult f [Int]
src/Waargonaut/Decode/Runners.hs view
@@ -1,4 +1,8 @@ {-# LANGUAGE RankNTypes #-}+-- |+--+-- Functions to execution of 'Waargonaut.Decode.Decoder's+-- module Waargonaut.Decode.Runners ( -- * General over @f@@@ -48,14 +52,14 @@ Decoder (..), mkCursor) -- | General decoding function that takes a given parsing function and some--- functions to handle the transition from the input of the 'JCurs' to the+-- functions to handle the transition from the input of the 'Waargonaut.Decoder.Types.JCurs' to the -- desired input type. The indexer and cursor requires a 'ByteString' to work -- efficiently, but this does not preclude the use of other text types, provided -- the right functions are present. -- -- There are some specialised versions of this function provided for 'Text', -- 'String', and 'ByteString'. They are implemented using this function, for--- example to work with 'Text' input and the 'attoparsec' package:+-- example to work with 'Text' input and the @attoparsec@ package: -- -- @ -- import qualified Data.Attoparsec.Text as AT@@ -210,7 +214,7 @@ pureDecodeWithInput decodeFromString -- | Helper function to handle wrapping up a parse failure using the given--- parsing function. Intended to be used with the 'runDecode' or 'simpleDecode'+-- parsing function. Intended to be used with the 'Waargonaut.Decode.runDecode' or 'Waargonaut.Decode.Traversal.simpleDecode' -- functions. -- -- @
src/Waargonaut/Decode/Traversal.hs view
@@ -163,8 +163,8 @@ -- | This is an alias to help explain a type from the zipper that is used to move -- around the 'Json' data structure. 'JCursor h a' represents a "cursor" that is--- currently located on a thing of type 'a', having previously been on a thing--- of type 'h'.+-- currently located on a thing of type @a@, having previously been on a thing+-- of type @h@. -- -- This type will grow as a form of "breadcrumb" trail as the cursor moves -- through the data structure. It may be used interchangably with 'h :>> a' from@@ -281,7 +281,7 @@ a <- mCurs <?> FailedToMove dir a <$ DR.recordZipperMove dir (Z.tooth a) --- | Using a given 'LensLike', try to step down into the 'Json' data structure+-- | Using a given 'Control.Lens.LensLike', try to step down into the 'Json' data structure -- to the location targeted by the lens. -- -- This can be used to move large steps over the data structure, or more@@ -325,7 +325,7 @@ up = moveAndKeepHistory U . pure . Z.upward --- | From the current cursor location, try to move 'n' steps to the left.+-- | From the current cursor location, try to move @n@ steps to the left. moveLeftN :: Monad f => Natural@@ -334,7 +334,7 @@ moveLeftN n cur = moveAndKeepHistory (L n) (Z.jerks Z.leftward (n ^. re _Natural) cur) --- | From the current cursor location, try to move 'n' steps to the right.+-- | From the current cursor location, try to move @n@ steps to the right. moveRightN :: Monad f => Natural@@ -359,7 +359,7 @@ moveRight1 = moveRightN (successor' zero') --- | Provide a 'conversion' function and create a 'Decoder' that uses the+-- | Provide a @conversion@ function and create a 'Decoder' that uses the -- current cursor and runs the given function. Fails with 'ConversionFailure' and -- the given 'Text' description. atCursor@@ -423,8 +423,8 @@ -- -- myRecDecoder :: Decoder f MyRec -- myRecDecoder = MyRec--- <$> atKey "field_a" text--- <*> atKey "field_b" int+-- <$> atKey \"field_a\" text+-- <*> atKey \"field_b\" int -- @ -- atKey@@ -509,7 +509,7 @@ elemD -- | Use the 'Cons' typeclass and move leftwards from the current cursor--- position, 'consing' the values to the 's' as it moves.+-- position, "consing" the values to the @s@ as it moves. leftwardCons :: ( Monad f , Cons s s a a@@ -525,7 +525,7 @@ elemD -- | Use the 'Snoc' typeclass and move rightwards from the current cursor--- position, 'snocing' the values to the 's' as it moves.+-- position, "snocing" the values to the @s@ as it moves. rightwardSnoc :: ( Monad f , Snoc s s a a@@ -540,7 +540,7 @@ (unDecodeResult . moveRight1) elemD --- | Decode a 'NonEmpty' list of 'a' at the given cursor position.+-- | Decode a 'NonEmpty' list of @a@ at the given cursor position. nonEmptyAt :: Monad f => Decoder f a@@ -566,7 +566,7 @@ try (moveAndKeepHistory D (Z.within WT.jsonTraversal c)) >>= Maybe.maybe (pure mempty) (rightwardSnoc mempty elemD) --- | Create a 'Decoder' for a list of 'a'+-- | Create a 'Decoder' for a list of @a@ list :: Monad f => Decoder f b@@ -584,8 +584,8 @@ withDefault def hasD = withCursor (fmap (Maybe.fromMaybe def) . focus hasD) --- | Named to match it's 'Encoder' counterpart, this function will decode an--- optional value.+-- | Named to match it's 'Waargonaut.Encode.Encoder' counterpart, this+-- function will decode an optional value. maybeOrNull :: Monad f => Decoder f a@@ -593,7 +593,7 @@ maybeOrNull hasD = withCursor (try . focus hasD) --- | Decode either an 'a' or a 'b', failing if neither 'Decoder' succeeds. The+-- | Decode either an @a@ or a @b@, failing if neither 'Decoder' succeeds. The -- 'Right' decoder is attempted first. either :: Monad f
src/Waargonaut/Decode/Types.hs view
@@ -9,7 +9,7 @@ -- module Waargonaut.Decode.Types ( ParseFn- , SuccinctCursor+ , Cursor , CursorHistory , Decoder (..) , DecodeResult (..)@@ -33,18 +33,13 @@ import Data.Functor.Alt (Alt (..)) import qualified Data.Text as Text -import GHC.Word (Word64)- import Data.ByteString (ByteString)-import Data.Vector.Storable (Vector) -import HaskellWorks.Data.BalancedParens (SimpleBalancedParens)-import HaskellWorks.Data.Json.Backend.Standard.Cursor (JsonCursor (..))-import HaskellWorks.Data.Json.Backend.Standard.Fast (makeCursor)-import HaskellWorks.Data.Json.Type (JsonType (..),+import HaskellWorks.Data.Json.Standard.Cursor.Fast (Cursor,fromByteStringViaBlanking)+import HaskellWorks.Data.Json.Standard.Cursor.Generic (cursorRank)+import HaskellWorks.Data.Json.Standard.Cursor.Type (JsonType (..), JsonTypeAt (..)) import HaskellWorks.Data.Positioning (Count)-import HaskellWorks.Data.RankSelect.Poppy512 (Poppy512) import Waargonaut.Decode.Internal (CursorHistory', DecodeError (..), DecodeResultT (..),@@ -57,11 +52,7 @@ type CursorHistory = CursorHistory' Count --- | Convenience alias defined for the concrete 'JsonCursor' type.-type SuccinctCursor =- JsonCursor ByteString Poppy512 (SimpleBalancedParens (Vector Word64))---- Another convenience alias for the type of the function we will use to parse+-- | Convenience alias for the type of the function we will use to parse -- the input string into the 'Json' structure. type ParseFn = ByteString -> Either DecodeError Json@@ -100,19 +91,19 @@ -- | Wrapper type for the 'SuccinctCursor' newtype JCurs = JCurs- { unJCurs :: SuccinctCursor+ { unJCurs :: Cursor } deriving JsonTypeAt instance JCurs ~ t => Rewrapped JCurs t instance Wrapped JCurs where- type Unwrapped JCurs = SuccinctCursor+ type Unwrapped JCurs = Cursor _Wrapped' = iso unJCurs JCurs -- | Take a 'ByteString' input and build an index of the JSON structure inside -- mkCursor :: ByteString -> JCurs-mkCursor = JCurs . makeCursor+mkCursor = JCurs . fromByteStringViaBlanking -- | Provide some of the type parameters that the underlying 'DecodeResultT' -- requires. This contains the state and error management as we walk around our
src/Waargonaut/Encode.hs view
@@ -7,6 +7,103 @@ {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-} -- | Types and functions to encode your data types to 'Json'.+--+-- We will work through a basic example, using the following type:+--+-- @+-- data Person = Person+-- { _personName :: Text+-- , _personAge :: Int+-- , _personAddress :: Text+-- , _personFavouriteLotteryNumbers :: [Int]+-- }+-- deriving (Eq, Show)+-- @+--+-- To create an 'Waargonaut.Encode.Encoder' for our @Person@ record, we will encode it as a "map+-- like object", that is we have decided that there are no duplicate keys allowed. We can then use+-- the following functions to build up the structure we want:+--+-- @+-- mapLikeObj+-- :: ( AsJType Json ws a+-- , Semigroup ws -- This library supports GHC 7.10.3 and 'Semigroup' wasn't a superclass of 'Monoid' then.+-- , Monoid ws+-- , Applicative f+-- )+-- => (i -> MapLikeObj ws a -> MapLikeObj ws a)+-- -> Encoder f i+-- @+--+-- And:+--+-- @+-- atKey'+-- :: ( At t+-- , IxValue t ~ Json+-- )+-- => Index t+-- -> Encoder' a+-- -> a+-- -> t+-- -> t+-- @+--+-- These types may seem pretty wild, but their usage is mundane. The 'Waargonaut.Encode.mapLikeObj'+-- function is used when we want to encode some particular type @i@ as a JSON object. In such a way+-- as to prevent duplicate keys from appearing. The 'Waargonaut.Encode.atKey'' function is designed+-- such that it can be composed with itself to build up an object with multiple keys.+--+-- @+-- import Waargonaut.Encode (Encoder)+-- import qualified Waargonaut.Encode as E+-- @+--+-- @+-- personEncoder :: Applicative f => Encoder f Person+-- personEncoder = E.mapLikeObj $ \\p ->+-- E.atKey' \"name\" E.text (_personName p) .+-- E.atKey' \"age\" E.int (_personAge p) .+-- E.atKey' \"address\" E.text (_personAddress p) .+-- E.atKey' \"numbers\" (E.list E.int) (_personFavouriteLotteryNumbers p)+-- @+--+-- The JSON RFC leaves the handling of duplicate keys on an object as a choice. It is up to the+-- implementor of a JSON handling package to decide what they will do. Waargonaut passes on this+-- choice to you. In both encoding and decoding, the handling of duplicate keys is up to you.+-- Waargonaut provides functionality to support /both/ use cases.+--+-- To then turn these values into JSON output:+--+-- @+-- simpleEncodeText :: Applicative f => Encoder f a -> a -> f Text+-- simpleEncodeTextNoSpaces :: Applicative f => Encoder f a -> a -> f Text+--+-- simpleEncodeByteString :: Applicative f => Encoder f a -> a -> f ByteString+-- simpleEncodeByteStringNoSpaces :: Applicative f => Encoder f a -> a -> f ByteString+-- @+--+-- Or+--+-- @+-- simplePureEncodeText :: Encoder' a -> a -> Text+-- simplePureEncodeTextNoSpaces :: Encoder' a -> a -> Text+--+-- simplePureEncodeByteString :: Encoder' a -> a -> ByteString+-- simplePureEncodeByteStringNoSpaces :: Encoder' a -> a -> ByteString+-- @+--+-- The latter functions specialise the @f@ to be 'Data.Functor.Identity'.+--+-- Then, like the use of the 'Waargonaut.Decode.Decoder' you select the 'Waargonaut.Encode.Encoder'+-- you wish to use and run it against a value of a matching type:+--+-- @+-- simplePureEncodeTextNoSpaces personEncoder (Person \"Krag\" 33 \"Red House 4, Three Neck Lane, Greentown.\" [86,3,32,42,73])+-- =+-- "{\"name\":\"Krag\",\"age\":88,\"address\":\"Red House 4, Three Neck Lane, Greentown.\",\"numbers\":[86,3,32,42,73]}"+-- @+-- module Waargonaut.Encode ( -- * Encoder type@@ -93,6 +190,7 @@ , keyValuesAsObj' , json' , asJson'+ , onObj' , generaliseEncoder ) where @@ -160,7 +258,8 @@ import Waargonaut.Encode.Builder.Types (Builder) import Waargonaut.Encode.Builder.Whitespace (wsBuilder, wsRemover) --- | Create an 'Encoder'' for 'a' by providing a function from 'a -> f Json'.++-- | Create an 'Encoder'' for @a@ by providing a function from 'a -> f Json'. encodeA :: (a -> f Json) -> Encoder f a encodeA = jsonEncoder @@ -235,7 +334,7 @@ simplePureEncodeWith builder buildRunner wsB enc = runIdentity . simpleEncodeWith builder buildRunner wsB enc --- | As per 'simpleEncodeText' but specialised the 'f' to 'Data.Functor.Identity'.+-- | As per 'simpleEncodeText' but specialised the @f@ to 'Data.Functor.Identity'. simplePureEncodeText :: Encoder Identity a -> a@@ -243,7 +342,7 @@ simplePureEncodeText enc = runIdentity . simpleEncodeText enc --- | As per 'simpleEncodeTextNoSpaces' but specialised the 'f' to 'Data.Functor.Identity'.+-- | As per 'simpleEncodeTextNoSpaces' but specialised the @f@ to 'Data.Functor.Identity'. simplePureEncodeTextNoSpaces :: Encoder Identity a -> a@@ -251,7 +350,7 @@ simplePureEncodeTextNoSpaces enc = runIdentity . simpleEncodeTextNoSpaces enc --- | As per 'simpleEncodeByteString' but specialised the 'f' to 'Data.Functor.Identity'.+-- | As per 'simpleEncodeByteString' but specialised the @f@ to 'Data.Functor.Identity'. simplePureEncodeByteString :: Encoder Identity a -> a@@ -259,7 +358,7 @@ simplePureEncodeByteString enc = runIdentity . simpleEncodeByteString enc --- | As per 'simpleEncodeByteStringNoSpaces' but specialised the 'f' to 'Data.Functor.Identity'.+-- | As per 'simpleEncodeByteStringNoSpaces' but specialised the @f@ to 'Data.Functor.Identity'. simplePureEncodeByteStringNoSpaces :: Encoder Identity a -> a@@ -359,7 +458,7 @@ . Either.either (runEncoder eA) . runEncoder --- | Encode some 'Traversable' of 'a' into a JSON array.+-- | Encode some 'Traversable' of @a@ into a JSON array. traversable :: ( Applicative f , Traversable t@@ -397,39 +496,39 @@ list = traversable --- | As per 'json' but with the 'f' specialised to 'Data.Functor.Identity'.+-- | As per 'json' but with the @f@ specialised to 'Data.Functor.Identity'. json' :: Encoder' Json json' = json --- | As per 'int' but with the 'f' specialised to 'Data.Functor.Identity'.+-- | As per 'int' but with the @f@ specialised to 'Data.Functor.Identity'. int' :: Encoder' Int int' = int --- | As per 'integral' but with the 'f' specialised to 'Data.Functor.Identity'.+-- | As per 'integral' but with the @f@ specialised to 'Data.Functor.Identity'. integral' :: Integral n => Encoder' n integral' = integral --- | As per 'scientific' but with the 'f' specialised to 'Data.Functor.Identity'.+-- | As per 'scientific' but with the @f@ specialised to 'Data.Functor.Identity'. scientific' :: Encoder' Scientific scientific' = scientific --- | As per 'bool' but with the 'f' specialised to 'Data.Functor.Identity'.+-- | As per 'bool' but with the @f@ specialised to 'Data.Functor.Identity'. bool' :: Encoder' Bool bool' = bool --- | As per 'string' but with the 'f' specialised to 'Data.Functor.Identity'.+-- | As per 'string' but with the @f@ specialised to 'Data.Functor.Identity'. string' :: Encoder' String string' = string --- | As per 'text' but with the 'f' specialised to 'Data.Functor.Identity'.+-- | As per 'text' but with the @f@ specialised to 'Data.Functor.Identity'. text' :: Encoder' Text text' = text --- | As per 'null' but with the 'f' specialised to 'Data.Functor.Identity'.+-- | As per 'null' but with the @f@ specialised to 'Data.Functor.Identity'. null' :: Encoder' () null' = null --- | As per 'maybe' but with the 'f' specialised to 'Data.Functor.Identity'.+-- | As per 'maybe' but with the @f@ specialised to 'Data.Functor.Identity'. maybe' :: Encoder' () -> Encoder' a@@ -437,14 +536,14 @@ maybe' = maybe --- | As per 'maybeOrNull' but with the 'f' specialised to 'Data.Functor.Identity'.+-- | As per 'maybeOrNull' but with the @f@ specialised to 'Data.Functor.Identity'. maybeOrNull' :: Encoder' a -> Encoder' (Maybe a) maybeOrNull' = maybeOrNull --- | As per 'either' but with the 'f' specialised to 'Data.Functor.Identity'.+-- | As per 'either' but with the @f@ specialised to 'Data.Functor.Identity'. either' :: Encoder' a -> Encoder' b@@ -452,21 +551,21 @@ either' = either --- | As per 'nonempty' but with the 'f' specialised to 'Data.Functor.Identity'.+-- | As per 'nonempty' but with the @f@ specialised to 'Data.Functor.Identity'. nonempty' :: Encoder' a -> Encoder' (NonEmpty a) nonempty' = traversable --- | As per 'list' but with the 'f' specialised to 'Data.Functor.Identity'.+-- | As per 'list' but with the @f@ specialised to 'Data.Functor.Identity'. list' :: Encoder' a -> Encoder' [a] list' = traversable --- | Encode some 'a' that is contained with another 't' structure.+-- | Encode some @a@ that is contained with another @t@ structure. encodeWithInner :: ( Applicative f , Traversable t@@ -477,7 +576,7 @@ encodeWithInner f g = jsonEncoder $ fmap f . traverse (runEncoder g) --- | As per 'traversable' but with the 'f' specialised to 'Data.Functor.Identity'.+-- | As per 'traversable' but with the @f@ specialised to 'Data.Functor.Identity'. traversable' :: Traversable t => Encoder' a@@ -485,7 +584,7 @@ traversable' = traversable --- | Using the given function to convert the 'k' type keys to a 'Text' value,+-- | Using the given function to convert the @k@ type keys to a 'Text' value, -- encode a 'Map' as a JSON object. mapToObj' :: Encoder' a@@ -508,6 +607,7 @@ atKey k enc v t = (\v' -> t & at k ?~ v') <$> runEncoder enc v +-- | Optionally encode an @a@ if it is a @Just a@. A @Nothing@ will result in the key being absent from the object. atOptKey :: ( At t , IxValue t ~ Json@@ -521,7 +621,7 @@ atOptKey k enc = Maybe.maybe pure (atKey k enc) --- | Encode an 'a' at the given index on the JSON object.+-- | Encode an @a@ at the given index on the JSON object. atKey' :: ( At t , IxValue t ~ Json@@ -539,8 +639,8 @@ -- -- @ -- encoder = E.mapLikeObj \$ \\a ->--- atKey' "A" E.text (_getterA a)--- atOptKey' "B" E.int (_maybeB a)+-- atKey' \"A\" E.text (_getterA a)+-- atOptKey' \"B\" E.int (_maybeB a) -- -- simplePureEncodeByteString encoder (Foo "bob" (Just 33)) = "{\"A\":\"bob\",\"B\":33}" --@@ -588,7 +688,7 @@ boolAt = flip atKey' bool --- | Encode a 'Foldable' of 'a' at the given index on a JSON object.+-- | Encode a 'Foldable' of @a@ at the given index on a JSON object. traversableAt :: ( At t , Traversable f@@ -709,9 +809,9 @@ floopObj = fromMapLikeObj . f . fst . toMapLikeObj -- |--- Given encoders for things that are represented in JSON as 'objects', and a--- way to get to the 'b' and 'c' from the 'a'. This function lets you create an--- encoder for 'a'. The two objects are combined to make one single JSON object.+-- Given encoders for things that are represented in JSON as @objects@, and a+-- way to get to the @b@ and @c@ from the @a@. This function lets you create an+-- encoder for @a@. The two objects are combined to make one single JSON object. -- -- Given --@@ -723,7 +823,7 @@ -- @ -- -- We can use this function to utilise our already defined 'ObjEncoder'--- structures to give us an encoder for 'A':+-- structures to give us an encoder for @A@: -- -- @ -- combineObjects (\aRecord -> (_foo aRecord, _bar aRecord)) encodeFoo encodeBar :: ObjEncoder f Bar@@ -750,6 +850,16 @@ onObj k b encB o = (\j -> o & _Wrapped L.%~ L.cons j) . JAssoc (_JStringText # k) mempty mempty <$> asJson encB b +-- | As per 'onObj' but the @f@ is specialised to 'Identity'.+onObj'+ :: Text+ -> b+ -> Encoder' b+ -> JObject WS Json+ -> JObject WS Json+onObj' k b encB o = (\j -> o & _Wrapped L.%~ L.cons j)+ . JAssoc (_JStringText # k) mempty mempty $ asJson' encB b+ -- | Encode key value pairs as a JSON object, allowing duplicate keys. keyValuesAsObj :: ( Foldable g@@ -771,7 +881,7 @@ keyValueTupleFoldable eA = encodeA $ fmap (\v -> _JObj # (v,mempty)) . foldrM (\(k,v) o -> onObj k v eA o) (_Empty # ()) --- | As per 'keyValuesAsObj' but with the 'f' specialised to 'Identity'.+-- | As per 'keyValuesAsObj' but with the @f@ specialised to 'Identity'. keyValuesAsObj' :: ( Foldable g , Functor g
src/Waargonaut/Encode/Builder.hs view
@@ -1,4 +1,8 @@ {-# LANGUAGE RankNTypes #-}+-- |+--+-- Builder structures to help with turning 'Json' into a textual encoding.+-- module Waargonaut.Encode.Builder where import Data.String (IsString, fromString)@@ -11,6 +15,7 @@ import Data.ByteString (ByteString) import qualified Data.ByteString.Builder as B+import qualified Data.ByteString.Builder.Prim as BP import Waargonaut.Types.Json (JType (..), Json (..)) import Waargonaut.Types.Whitespace (WS)@@ -21,18 +26,22 @@ import Waargonaut.Encode.Builder.JString (jStringBuilder) import Waargonaut.Encode.Builder.Types (Builder (..)) +-- | A 'T.Text' builder textBuilder :: Builder Text T.Builder textBuilder = Builder T.singleton T.fromText T.decimal +-- | A 'B.ByteString' builder bsBuilder :: Builder ByteString B.Builder bsBuilder = Builder- B.charUtf8+ (BP.primBounded BP.charUtf8) B.byteString B.intDec +-- | A general builder function for working with 'JType' values.+-- jTypesBuilder :: ( IsString t , Monoid b
src/Waargonaut/Encode/Builder/CommaSep.hs view
@@ -1,4 +1,8 @@ {-# LANGUAGE RankNTypes #-}+-- |+--+-- Builder functions for 'CommaSeparated' values.+-- module Waargonaut.Encode.Builder.CommaSep (commaSeparatedBuilder) where import Data.Monoid ((<>))@@ -24,7 +28,7 @@ commaTrailingBuilder bldr wsB = foldMap ((commaBuilder bldr <>) . (wsB bldr) . snd) --- | Using the given builders for the whitespace and elements ('a'), create a+-- | Using the given builders for the whitespace and elements (@a@), create a -- builder for a 'CommaSeparated'. commaSeparatedBuilder :: forall ws a t b. Monoid b
src/Waargonaut/Encode/Builder/JArray.hs view
@@ -1,3 +1,7 @@+-- |+--+-- Builder function for 'JArray'+-- module Waargonaut.Encode.Builder.JArray (jArrayBuilder) where import Waargonaut.Types.JArray (JArray (..))
src/Waargonaut/Encode/Builder/JChar.hs view
@@ -1,3 +1,7 @@+-- |+--+-- Builder structures for 'JChar's+-- module Waargonaut.Encode.Builder.JChar (jCharBuilder) where import Control.Lens (review)
src/Waargonaut/Encode/Builder/JNumber.hs view
@@ -1,3 +1,7 @@+-- | +--+-- Builders for 'JNumber'+-- module Waargonaut.Encode.Builder.JNumber ( jNumberBuilder ) where
src/Waargonaut/Encode/Builder/JObject.hs view
@@ -1,3 +1,7 @@+-- |+--+-- Builder structures for 'JObject's+-- module Waargonaut.Encode.Builder.JObject (jObjectBuilder) where import Data.Monoid ((<>))
src/Waargonaut/Encode/Builder/JString.hs view
@@ -1,3 +1,7 @@+-- |+--+-- Builder structures for 'JString's+-- module Waargonaut.Encode.Builder.JString (jStringBuilder) where import Data.Monoid ((<>))
src/Waargonaut/Encode/Builder/Types.hs view
@@ -1,7 +1,12 @@+-- |+--+-- The structure used to contain the required character and related functions for running a "builder".+-- module Waargonaut.Encode.Builder.Types (Builder (..)) where +-- | The builder data type. data Builder t b = Builder- { fromChar :: Char -> b- , fromChunk :: t -> b- , fromInt :: Int -> b+ { fromChar :: Char -> b -- ^ Create a builder from a Haskell 'Char'+ , fromChunk :: t -> b -- ^ Create a builder from a chunk or piece of @t@+ , fromInt :: Int -> b -- ^ Create a builder from a Haskell 'Int' }
src/Waargonaut/Encode/Builder/Whitespace.hs view
@@ -1,3 +1,7 @@+-- |+--+-- Builder structures for 'Whitespace'+-- module Waargonaut.Encode.Builder.Whitespace ( whitespaceBuilder , wsBuilder
src/Waargonaut/Encode/Types.hs view
@@ -121,7 +121,7 @@ jsonEncoder = EncoderFns id {-# INLINE jsonEncoder #-} --- | Helper function for creating a JSON 'object' 'Encoder'. Provides the+-- | Helper function for creating a JSON @object@ 'Encoder'. Provides the -- default 'finaliseEncoding' function for completing the 'JObject' to the -- necessary 'Json' type. objEncoder :: (a -> f (JObject WS Json)) -> EncoderFns (JObject WS Json) f a
src/Waargonaut/Generic.hs view
@@ -3,7 +3,6 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-}-{-# LANGUAGE KindSignatures #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PolyKinds #-}@@ -46,6 +45,7 @@ -- * Creation , gEncoder , gDecoder+ , gObjEncoder -- * Reexports , module Data.Tagged@@ -54,16 +54,21 @@ ) where import Generics.SOP+import Generics.SOP.Record (IsRecord) -import Control.Lens (findOf, folded, isn't, _Left)+import Control.Lens (findOf, folded, isn't, ( # ),+ _Empty, _Left) import Control.Monad ((>=>)) import Control.Monad.Except (lift, throwError) import Control.Monad.Reader (runReaderT) import Control.Monad.State (modify) import qualified Data.Char as Char+import Data.Function ((&)) import Data.Maybe (fromMaybe) +import Data.Foldable (foldl')+ import Data.List.NonEmpty (NonEmpty) import Data.ByteString (ByteString)@@ -77,11 +82,11 @@ import qualified Data.Tagged as T import Waargonaut (Json)+import Waargonaut.Types (JObject, WS) import Waargonaut.Encode (Encoder, Encoder') import qualified Waargonaut.Encode as E - import HaskellWorks.Data.Positioning (Count) import Waargonaut.Decode (Decoder)@@ -276,6 +281,18 @@ -- Will be encoded as: @ {"Foo":"Fred"} @ -- | ConstructorNameAsKey++ -- | Encode the newtype value as an object, treaing the field accessor as the "key", and+ -- passing that field name through the '_optionsFieldName' function.+ --+ -- @+ -- newtype Foo = Foo { deFoo :: Text }+ --+ -- let x = Foo "Fred"+ -- @+ --+ -- Will be encoded as: @ {"deFoo":"Fred"} @+ | FieldNameAsKey deriving (Show, Eq) -- | The configuration options for creating 'Generic' encoder or decoder values.@@ -335,7 +352,7 @@ -- instance JsonEncode GWaarg Foo where -- mkEncoder = Tagged fooEncoderIWroteEarlier -- @---+ class JsonEncode t a where mkEncoder :: Applicative f => Tagged t (Encoder f a) @@ -406,14 +423,7 @@ JsonZero :: ConstructorName -> JsonInfo '[] JsonOne :: Tag -> JsonInfo '[a] JsonMul :: SListI xs => Tag -> JsonInfo xs- JsonRec :: SListI xs => Tag -> NP (K String) xs -> JsonInfo xs--modFieldName- :: Options- -> String- -> Text-modFieldName opts =- Text.pack . _optionsFieldName opts+ JsonRec :: SListI xs => Tag -> NP (K Text) xs -> JsonInfo xs inObj :: Encoder' a -> String -> Encoder' a inObj en t = E.mapLikeObj' (E.atKey' (Text.pack t) en)@@ -455,8 +465,8 @@ jInfoFor opts _ tag (Record n fs) = JsonRec (tag n) (hliftA fname fs) where- fname :: FieldInfo a -> K String a- fname (FieldInfo name) = K $ _optionsFieldName opts name+ fname :: FieldInfo a -> K Text a+ fname (FieldInfo name) = K . Text.pack $ _optionsFieldName opts name jsonInfo :: forall a.@@ -468,13 +478,13 @@ -> NP JsonInfo (Code a) jsonInfo opts pa = case datatypeInfo pa of- Newtype _ _ c -> JsonOne (newtypename (constructorName c)) :* Nil- ADT _ n cs -> hliftA (jInfoFor opts n (tag cs)) cs- where- newtypename n = case _optionsNewtypeWithConsName opts of- Unwrap -> NoTag- ConstructorNameAsKey -> Tag (_optionsFieldName opts n)+ Newtype _ n c -> case _optionsNewtypeWithConsName opts of+ Unwrap -> JsonOne NoTag :* Nil+ ConstructorNameAsKey -> JsonOne (Tag $ _optionsFieldName opts n) :* Nil+ FieldNameAsKey -> jInfoFor opts n (Tag . _optionsFieldName opts) c :* Nil + ADT _ n cs -> hliftA (jInfoFor opts n (tag cs)) cs+ where tag :: NP ConstructorInfo (Code a) -> ConstructorName -> Tag tag (_ :* Nil) = const NoTag tag _ = Tag@@ -514,6 +524,65 @@ pjE = Proxy :: Proxy (JsonEncode t) pt = Proxy :: Proxy t +-- | Create a 'Tagged' 'ObjEncoder' for type @ a @, tagged by @ t @.+--+-- This isn't compatible with the 'JsonEncode' typeclass because it creates an+-- 'ObjEncoder' and for consistency reasons the 'JsonEncode' typeclass produces+-- 'Encoder's.+--+-- However it lets you more easily access the 'Data.Functor.Contravariant.Contravariant'+-- functionality that is part of the 'ObjEncoder' type.+--+-- @+-- data Foo = Foo { fooA :: Text, fooB :: Int } deriving (Eq, Show)+-- deriveGeneric ''Foo+--+-- objEncFoo :: Applicative f => ObjEncoder f Foo+-- objEncFoo = untag $ gObjEncoder (defaultOps { _optionsFieldName = drop 3 })+--+-- @+--+-- NB: This function overrides the newtype options to use the 'FieldNameAsKey' option to+-- be consistent with the behaviour of the record encoding.+--+gObjEncoder+ :: forall t a f xs.+ ( Generic a+ , Applicative f+ , HasDatatypeInfo a+ , All2 (JsonEncode t) (Code a)+ , IsRecord a xs+ )+ => Options+ -> Tagged t (E.ObjEncoder f a)+gObjEncoder opts = Tagged . E.objEncoder $ \a -> hcollapse $ hcliftA2+ (Proxy :: Proxy (All (JsonEncode t)))+ createObject+ (jsonInfo (opts { _optionsNewtypeWithConsName = FieldNameAsKey }) (Proxy :: Proxy a))+ (unSOP $ from a)+ where+ createObject :: ( All (JsonEncode t) ys+ , Applicative f+ )+ => JsonInfo ys+ -> NP I ys+ -> K (f (JObject WS Json)) ys+ createObject (JsonRec _ fields) cs = K . pure .+ foldl' (&) (_Empty # ()) . hcollapse $ hcliftA2 pjE toObj fields cs++ createObject (JsonOne (Tag t)) (I a :* Nil) = K . pure $+ E.onObj' (Text.pack t) (E.asJson' (T.proxy mkEncoder pt) a) E.json (_Empty # ())++ -- IsRecord constraint should make this impossible.+ createObject _ _ =+ error "The impossible has happened. Please report this as a bug: https://github.com/qfpl/waargonaut"++ toObj :: JsonEncode t x => K Text x -> I x -> K (JObject WS Json -> JObject WS Json) x+ toObj f a = K $ E.onObj' (unK f) (E.asJson' (T.proxy mkEncoder pt) (unI a)) E.json++ pt = Proxy :: Proxy t+ pjE = Proxy :: Proxy (JsonEncode t)+ gEncoder' :: forall xs f t. ( All (JsonEncode t) xs@@ -537,11 +606,11 @@ ik :: JsonEncode t x => I x -> K Json x ik = K . E.asJson' (T.proxy mkEncoder pT) . unI -gEncoder' p pT opts (JsonRec tag fields) cs =+gEncoder' p pT _ (JsonRec tag fields) cs = tagVal tag . enc . hcollapse $ hcliftA2 p tup fields cs where- tup :: JsonEncode t x => K String x -> I x -> K (Text, Json) x- tup f a = K ( modFieldName opts (unK f)+ tup :: JsonEncode t x => K Text x -> I x -> K (Text, Json) x+ tup f a = K ( unK f , E.asJson' (T.proxy mkEncoder pT) (unI a) ) @@ -657,10 +726,10 @@ where err = throwError (ConversionFailure "Generic List Decode Failed") -mkGDecoder2 opts pJDec cursor (JsonRec tag fields) = do+mkGDecoder2 _ pJDec cursor (JsonRec tag fields) = do c' <- D.down cursor hsequenceK $ hcliftA pJDec (mapKK (decodeAtKey c')) fields where decodeAtKey c k = unTagVal tag (- D.withCursor $ D.fromKey (modFieldName opts k) D.rank+ D.withCursor $ D.fromKey k D.rank ) c
src/Waargonaut/Lens.hs view
@@ -4,6 +4,10 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-}+-- |+--+-- Some high level prisms for interacting with something that could be JSON.+-- module Waargonaut.Lens ( -- * Prisms
src/Waargonaut/Prettier.hs view
@@ -77,9 +77,9 @@ newtype IndentStep = IndentStep Natural deriving (Eq, Show) --- | Encode an @a@ directly to a 'ByteString' using the provided 'Encoder', the--- output will have newlines and indentation added based on the 'InlineOption' and--- 'NumSpaces'.+-- | Encode an @a@ directly to a 'Data.Text' using+-- the provided 'Encoder', the output will have newlines and+-- indentation added based on the 'InlineOption' and 'NumSpaces'. -- -- @ -- let two = successor' $ successor' zero'@@ -167,5 +167,5 @@ where longestKey = maybe 1 (+1) $ L.maximumOf (objelems . L.folded . jsonAssocKey . _Wrapped . L.to length) j - setnested = objelems . CS.elemsLast . CS.elemVal . jsonAssocVal %~+ setnested = objelems . traverse . jsonAssocVal %~ prettyJson inlineOpt (IndentStep step) (NumSpaces $ w <> step)
src/Waargonaut/Test.hs view
@@ -18,7 +18,7 @@ import qualified Waargonaut.Decode as D import Waargonaut.Decode.Error (DecodeError) --- | Test a 'Encoder' and 'Decoder' pair are able to maintain the 'round trip'+-- | Test a 'Encoder' and 'Decoder' pair are able to maintain the "round trip" -- property. That is, if you encode a given value, and then decode it, you should -- have the exact same value that you started with. roundTripSimple
src/Waargonaut/Types/CommaSep.hs view
@@ -160,19 +160,19 @@ instance Monoid ws => Witherable (CommaSeparated ws) where --- | Isomorphism between the internal pieces of a 'CommaSeparated' element.+-- | Isomorphism between the internal pieces of a 'Waargonaut.Types.CommaSep.CommaSeparated' element. _CommaSeparated :: Iso (CommaSeparated ws a) (CommaSeparated ws' b) (ws, Maybe (Elems ws a)) (ws', Maybe (Elems ws' b)) _CommaSeparated = iso (\(CommaSeparated ws a) -> (ws,a)) (uncurry CommaSeparated) {-# INLINE _CommaSeparated #-} --- | Cons elements onto a 'CommaSeparated' with provided whitespace information.+-- | Cons elements onto a 'Waargonaut.Types.CommaSep.CommaSeparated' with provided whitespace information. -- If you don't need explicit whitespace then the 'Cons' instance is more straightforward. consCommaSep :: Monoid ws => ((Comma,ws),a) -> CommaSeparated ws a -> CommaSeparated ws a consCommaSep (ews,a) = over (_CommaSeparated . _2) (pure . maybe new (consElems (ews,a))) where new = Elems mempty (Elem a Nothing) {-# INLINE consCommaSep #-} --- | Attempt to "uncons" elements from the front of a 'CommaSeparated' without+-- | Attempt to "uncons" elements from the front of a 'Waargonaut.Types.CommaSep.CommaSeparated' without -- discarding the elements' whitespace information. If you don't need explicit -- whitespace then the 'Cons' instance is more straightforward. unconsCommaSep :: Monoid ws => CommaSeparated ws a -> Maybe ((Maybe (Comma,ws), a), CommaSeparated ws a)@@ -195,17 +195,18 @@ then es & elemsLast . traverse %%~ f else es & elemsElems . ix i . traverse %%~ f --- | Convert a list of 'a' to a 'CommaSeparated' list, with no whitespace.+-- | Convert a list of @a@ to a 'Waargonaut.Types.CommaSep.CommaSeparated' list, with no whitespace. fromList :: (Monoid ws, Semigroup ws) => [a] -> CommaSeparated ws a fromList = foldr cons mempty {-# INLINE fromList #-} --- | Convert a 'CommaSeparated' of 'a' to @[a]@, discarding whitespace.+-- | Convert a 'Waargonaut.Types.CommaSep.CommaSeparated' of @a@ to @[a]@, discarding whitespace. toList :: CommaSeparated ws a -> [a] toList = maybe [] g . (^. _CommaSeparated . _2) where g e = snoc (e ^.. elemsElems . traverse . elemVal) (e ^. elemsLast . elemVal) {-# INLINE toList #-} +-- | Attempt convert a 'CommaSeparated' to some other value using the given functions. fromCommaSep :: Traversal' j (CommaSeparated ws x) -> v@@ -223,7 +224,7 @@ $ traverse decoder els -- Try to decode the values {-# INLINE fromCommaSep #-} --- | Parse a 'CommaSeparated' data structure.+-- | Parse a 'Waargonaut.Types.CommaSep.CommaSeparated' data structure. -- -- >>> testparse (parseCommaSeparated (char '[') (char ']') parseWhitespace charWS) "[]" -- Right (CommaSeparated (WS []) Nothing)
src/Waargonaut/Types/CommaSep/Elem.hs view
@@ -5,6 +5,11 @@ {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoImplicitPrelude #-}+-- |+--+-- Data structures and functions for managing a single element in a+-- 'Waargonaut.Types.CommaSep.CommaSeparated' structure.+-- module Waargonaut.Types.CommaSep.Elem ( -- * Types@@ -78,8 +83,8 @@ parseCommaTrailingMaybe = C.optional . liftA2 (,) parseComma --- | Data type to represent a single element in a 'CommaSeparated' list. Carries--- information about it's own trailing whitespace. Denoted by the 'f'.+-- | Data type to represent a single element in a 'Waargonaut.Types.CommaSep.CommaSeparated' list. Carries+-- information about it's own trailing whitespace. Denoted by the @f@. data Elem f ws a = Elem { _elemVal :: a , _elemTrailing :: f (Comma, ws)@@ -127,6 +132,7 @@ floopId :: Monoid ws => Iso' (Identity (Comma,ws)) (Maybe (Comma,ws)) floopId = iso (Just . runIdentity) (pure . fromMaybe (Comma, mempty)) +-- | 'Control.Lens.Iso' between an 'Elem' that is not on the trailing element and one that is. _ElemTrailingIso :: ( Monoid ws , Monoid ws'
src/Waargonaut/Types/CommaSep/Elems.hs view
@@ -7,6 +7,10 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE TupleSections #-}+-- |+--+-- Data structures and functions for handling the elements contained in a 'Waargonaut.Types.CommaSep.CommaSeparated' structure.+-- module Waargonaut.Types.CommaSep.Elems ( -- * Types@@ -110,10 +114,12 @@ (<>) (Elems as alast) (Elems bs blast) = Elems (snoc as (alast ^. from _ElemTrailingIso) <> bs) blast +-- | Add a value to the beginning of the 'Elems' consElems :: Monoid ws => ((Comma,ws), a) -> Elems ws a -> Elems ws a consElems (ews,a) e = e & elemsElems %~ cons (Elem a (Identity ews)) {-# INLINE consElems #-} +-- | Attempt to remove the initial value off the front of an 'Elems' unconsElems :: Monoid ws => Elems ws a -> ((Maybe (Comma,ws), a), Maybe (Elems ws a)) unconsElems e = maybe (e', Nothing) (\(em, ems) -> (idT em, Just $ e & elemsElems .~ ems)) es' where@@ -122,7 +128,7 @@ idT x = (x ^. elemTrailing . to (Just . runIdentity), x ^. elemVal) {-# INLINE unconsElems #-} --- | Parse the elements of a 'CommaSeparated' list, handling the optional trailing comma and its whitespace.+-- | Parse the elements of a 'Waargonaut.Types.CommaSep.CommaSeparated' list, handling the optional trailing comma and its whitespace. -- -- >>> testparse (parseCommaSeparatedElems parseWhitespace alphaNum) "a, b, c, d" -- Right (Elems {_elemsElems = [Elem {_elemVal = 'a', _elemTrailing = Identity (Comma,WS [Space])},Elem {_elemVal = 'b', _elemTrailing = Identity (Comma,WS [Space])},Elem {_elemVal = 'c', _elemTrailing = Identity (Comma,WS [Space])}], _elemsLast = Elem {_elemVal = 'd', _elemTrailing = Nothing}})
src/Waargonaut/Types/JArray.hs view
@@ -6,7 +6,10 @@ {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeFamilies #-}--- | JSON Array representation and functions.+-- |+--+-- JSON Array representation and functions.+-- module Waargonaut.Types.JArray ( -- * Types
src/Waargonaut/Types/JChar.hs view
@@ -117,10 +117,12 @@ -- instance AsJChar Char HeXDigit where -- Don't implement this, it's not a lawful prism. +-- | Convert a 'JChar' to a Haskell 'Char' jCharToChar :: JChar HeXDigit -> Char jCharToChar (UnescapedJChar uejc) = review _Unescaped uejc jCharToChar (EscapedJChar ejc) = escapedToChar ejc +-- | Attempt to convert a Haskell 'Char' to a JSON acceptable 'JChar' charToJChar :: Char -> Maybe (JChar HeXDigit) charToJChar c = (UnescapedJChar <$> preview _Unescaped c) <|>@@ -131,7 +133,7 @@ | otherwise = Nothing -- | Convert a 'Char' to 'JChar HexDigit' and replace any invalid values with--- @U+FFFD@ as per the 'Text' documentation.+-- @U+FFFD@ as per the 'Data.Text.Text' documentation. -- -- Refer to <https://hackage.haskell.org/package/text/docs/Data-Text.html#g:2 'Text'> documentation for more info. --@@ -140,7 +142,7 @@ where scalarReplacement = EscapedJChar (Hex (HexDigit4 D.xf D.xf D.xf D.xd)) {-# INLINE utf8CharToJChar #-} --- | Try to convert a 'JChar' to a 'Text' safe 'Char' value. Refer to the link for more info:+-- | Try to convert a 'JChar' to a 'Data.Text.Text' safe 'Char' value. Refer to the link for more info: -- https://hackage.haskell.org/package/text-1.2.3.0/docs/Data-Text-Internal.html#v:safe jCharToUtf8Char :: JChar HeXDigit -> Maybe Char jCharToUtf8Char = utf8SafeChar . jCharToChar
src/Waargonaut/Types/JChar/Escaped.hs view
@@ -47,7 +47,7 @@ hexDigit4ToChar, parseHexDigit4) import Waargonaut.Types.Whitespace (Whitespace (..),- unescapedWhitespaceChar,+ escapedWhitespaceChar, _WhitespaceChar) -- $setup@@ -177,15 +177,17 @@ in char '\\' *> (z <|> h) +-- | Convert an 'Escaped' character to a Haskell 'Char' escapedToChar :: Escaped HeXDigit -> Char escapedToChar = \case QuotationMark -> '"' ReverseSolidus -> '\\' Solidus -> '/' Backspace -> '\b'- WhiteSpace wc -> unescapedWhitespaceChar wc+ WhiteSpace wc -> escapedWhitespaceChar wc Hex hd -> hexDigit4ToChar hd +-- | Attempt to convert a Haskell 'Char' to an 'Escaped' JSON character charToEscaped :: Char -> Maybe (Escaped HeXDigit) charToEscaped c = case c of '"' -> Just QuotationMark
src/Waargonaut/Types/JChar/HexDigit4.hs view
@@ -6,7 +6,10 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoImplicitPrelude #-}--- | Types and functions for handling \\u0000 values in JSON.+-- |+--+-- Types and functions for handling \\u0000 values in JSON.+-- module Waargonaut.Types.JChar.HexDigit4 ( -- * Types@@ -91,7 +94,7 @@ hexDigit4ToChar (HexDigit4 a b c d) = chr (D._HeXDigitsIntegral (Right $ a :| [b,c,d])) -- | Try to convert a Haskell 'Char' to a JSON acceptable character. NOTE: This--- cannot preserve the upper or lower casing of any original 'Json' data structure+-- cannot preserve the upper or lower casing of any original 'Waargonaut.Types.Json.Json' data structure -- inputs that may have been used to create this 'Char'. Also the JSON RFC -- specifies a "limited" range of @U+0000@ to @U+FFFF@ as permissible as a six -- character sequence: @\u0000@.
src/Waargonaut/Types/JChar/Unescaped.hs view
@@ -1,4 +1,7 @@--- | Types and functions for handling valid unescaped characters in JSON.+-- |+--+-- Types and functions for handling valid unescaped characters in JSON.+-- module Waargonaut.Types.JChar.Unescaped ( -- * Types
src/Waargonaut/Types/JNumber.hs view
@@ -3,7 +3,10 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE TypeFamilies #-}--- | Representation of a JSON number and its various components.+-- | +--+-- Representation of a JSON number and its various components.+-- module Waargonaut.Types.JNumber ( -- * Types@@ -87,7 +90,7 @@ | JIntInt digit [DecDigit] deriving (Eq, Ord, Show) --- | Type alias to allow us to constrain the first 'digit' type.+-- | Type alias to allow us to constrain the first @digit@ type. type JInt = JInt' DecDigit -- | Prism for JSON zeroes.
src/Waargonaut/Types/JObject.hs view
@@ -127,7 +127,7 @@ bitraverse f g (JObject c) = JObject <$> bitraverse f (bitraverse f g) c -- | Without having an obviously correct "first" or "last" decision on which--- 'JString' key is the "right" one to use, a 'JObject' can only be indexed by a+-- 'Waargonaut.Types.JString' key is the "right" one to use, a 'JObject' can only be indexed by a -- numeric value. instance Monoid ws => Ixed (JObject ws a) where ix i f (JObject cs) = JObject <$> ix i (traverse f) cs@@ -149,7 +149,7 @@ deriving (Eq, Show, Functor, Foldable, Traversable) -- |--- 'Prism' for working with a 'JObject' as a 'MapLikeObj'. This optic will keep+-- 'Control.Lens.Prism' for working with a 'JObject' as a 'MapLikeObj'. This optic will keep -- the first unique key on a given 'JObject' and this information is not -- recoverable. If you want to create a 'MapLikeObj' from a 'JObject' and keep -- what is removed, then use the 'toMapLikeObj' function.
src/Waargonaut/Types/Json.hs view
@@ -176,8 +176,8 @@ _ -> Left x ) --- | Top level Json type, we specialise the whitespace to 'WS' and the 'digit'--- type to 'Digit'. Also defining that our structures can recursively only contain+-- | Top level Json type, we specialise the whitespace to 'WS' and the @digit@+-- type to 'Data.Digit.Digit'. Also defining that our structures can recursively only contain -- 'Json' types. newtype Json = Json (JType WS Json)@@ -210,7 +210,7 @@ jtypeTraversal = bitraverse pure -- |--- A 'Control.Lens.Traversal'' over the 'a' at the given 'Text' key on a JSON object.+-- A 'Control.Lens.Traversal'' over the @a@ at the given 'Text' key on a JSON object. -- -- >>> E.simplePureEncodeTextNoSpaces E.json (obj & oat "c" ?~ E.asJson' E.int 33) -- "{\"c\":33,\"a\":33,\"b\":\"Fred\"}"@@ -221,7 +221,7 @@ oat k = _JObj . _1 . _MapLikeObj . at k -- |--- A 'Control.Lens.Traversal'' over the 'a' at the given 'Int' position in a JSON object.+-- A 'Control.Lens.Traversal'' over the @a@ at the given 'Int' position in a JSON object. -- -- >>> E.simplePureEncodeTextNoSpaces E.json (obj & oix 0 .~ E.asJson' E.int 1) -- "{\"a\":1,\"b\":\"Fred\"}"@@ -231,7 +231,7 @@ oix i = _JObj . _1 . ix i -- |--- A 'Control.Lens.Traversal'' over the 'a' at the given 'Int' position in a JSON array.+-- A 'Control.Lens.Traversal'' over the @a@ at the given 'Int' position in a JSON array. -- -- >>> E.simplePureEncodeTextNoSpaces E.json ((E.asJson' (E.list E.int) [1,2,3]) & aix 0 .~ E.asJson' E.int 99) -- "[99,2,3]"@@ -261,7 +261,7 @@ <$ text "null" <*> ws --- | Parse a 'true' or 'false'.+-- | Parse a @true@ or @false@. -- -- >>> testparse (parseJBool (return ())) "true" -- Right (JBool True ())
src/Waargonaut/Types/Whitespace.hs view
@@ -1,7 +1,10 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-}--- | Parsers and builders for whitespace characters in our JSON.+-- | +--+-- Parsers and builders for whitespace characters in our JSON.+-- module Waargonaut.Types.Whitespace ( Whitespace (..)
+ test/Generics.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE OverloadedStrings #-}+module Generics+ ( genericsTests+ ) where++import Test.Tasty (TestTree, testGroup)+import Test.Tasty.HUnit (Assertion, testCase, (@?=))++import Waargonaut.Encode (Encoder)+import qualified Waargonaut.Encode as E++import qualified Types.Common as C++import qualified Waargonaut.Generic as G++testGenericFieldNameFunctionUsedOnce :: Assertion+testGenericFieldNameFunctionUsedOnce = do+ let+ encFudge :: Applicative f => Encoder f C.Fudge+ encFudge = G.untag . G.gEncoder $ C.fudgeJsonOpts+ { G._optionsFieldName = tail+ }++ expected = "{\"udge\":\"Chocolate\"}"++ E.simplePureEncodeTextNoSpaces encFudge C.testFudge @?= expected++genericsTests :: TestTree+genericsTests = testGroup "Generics"+ [ testCase "Options fieldName function is used only once" testGenericFieldNameFunctionUsedOnce+ ]
test/Golden.hs view
@@ -9,6 +9,7 @@ import System.FilePath (FilePath, takeBaseName) import System.IO (IO, print) +import Data.Semigroup ((<>)) import Data.Either (either) import Data.Function (($)) @@ -22,6 +23,8 @@ import qualified Waargonaut.Decode as D import qualified Waargonaut.Encode as E +import qualified Prettier.NestedObjs as WaargP+ readAndEncodeFile :: FilePath -> IO ByteString readAndEncodeFile = readFile >=> D.decodeFromByteString parseOnly D.json . toStrict@@ -34,4 +37,6 @@ pure . testGroup "Golden Tests" $ [ goldenVsString (takeBaseName input) input (readAndEncodeFile input) | input <- fs+ ] <>+ [ WaargP.testGoldenPrettyNested ]
test/Main.hs view
@@ -31,6 +31,7 @@ import qualified Encoder import qualified Encoder.Laws import qualified Golden+import qualified Generics import qualified Json import qualified Properties @@ -89,6 +90,8 @@ , Decoder.decoderTests , Encoder.encoderTests++ , Generics.genericsTests , goldens ]
+ test/Prettier/NestedObjs.hs view
@@ -0,0 +1,124 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}++-- | Test case from+--+-- https://gist.github.com/tonymorris/420fff469463037f5ed3f76339cb51e2+--+module Prettier.NestedObjs where++import Data.Functor.Identity+import Prelude++import qualified Data.Text.Lazy.Encoding as TLE++import Data.Text.Lazy+import Natural++import Waargonaut.Encode+import Waargonaut.Prettier++import Test.Tasty (TestTree)+import Test.Tasty.Golden (goldenVsString)++data Wibble =+ Wibble {+ _a1 :: String+ , _a2 :: String+ , _a3 :: String+ , _a4 :: String+ } deriving (Eq, Ord, Show)++data Wobble =+ Wobble {+ _b1 :: Wibble+ , _b2 :: Wibble+ } deriving (Eq, Ord, Show)++testWibble1 ::+ Wibble+testWibble1 =+ Wibble+ "hi1"+ "bye1"+ "hello1"+ "goodbye1"++testWibble2 ::+ Wibble+testWibble2 =+ Wibble+ "hi2"+ "bye2"+ "hello2"+ "goodbye2"++testWobble ::+ Wobble+testWobble =+ Wobble+ testWibble1+ testWibble2++wibbleEncoder ::+ Applicative f =>+ Encoder f Wibble+wibbleEncoder =+ mapLikeObj $ \w ->+ atKey' "a1" string (_a1 w) .+ atKey' "a2" string (_a2 w) .+ atKey' "a3" string (_a3 w) .+ atKey' "a4" string (_a4 w)++wobbleEncoder ::+ Applicative f =>+ Encoder f Wobble+wobbleEncoder =+ mapLikeObj $ \w ->+ atKey' "b1" wibbleEncoder (_b1 w) .+ atKey' "b2" wibbleEncoder (_b2 w)++prettyied :: Text+prettyied =+ let two = successor' (successor' zero')+ in runIdentity (simpleEncodePretty ArrayOnly (IndentStep two) (NumSpaces two) wobbleEncoder testWobble)++-- Expected this+{-+{+ "b1": {+ "a1": "hi1",+ "a2": "bye1",+ "a3": "hello1",+ "a4": "goodbye1"+ },+ "b2": {+ "a1": "hi2",+ "a2": "bye2",+ "a3": "hello2",+ "a4": "goodbye2"+ }+}+-}+--+-- Got this+{-+{+ "b1": {+ "a1": "hi1",+ "a2": "bye1",+ "a3": "hello1",+ "a4": "goodbye1"+},+ "b2": {+ "a1": "hi2",+ "a2": "bye2",+ "a3": "hello2",+ "a4": "goodbye2"+ }+}+-}+testGoldenPrettyNested :: TestTree+testGoldenPrettyNested = goldenVsString "'encodePretty' simple nested objects"+ "test/Prettier/pretty_nested_objs.json"+ (pure $ TLE.encodeUtf8 prettyied)
test/Types/Common.hs view
@@ -23,6 +23,7 @@ , testImageDataType , testFudge+ , fudgeJsonOpts , imageDecodeGeneric , imageDecodeSuccinct @@ -132,7 +133,7 @@ instance JsonEncode GWaarg Image where mkEncoder = gEncoder imageOpts instance JsonDecode GWaarg Image where mkDecoder = gDecoder imageOpts -newtype Fudge = Fudge Text+newtype Fudge = Fudge { unCrepe :: Text } deriving (Eq, Show, GHC.Generic) instance Generic Fudge
waargonaut.cabal view
@@ -10,7 +10,7 @@ -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change-version: 0.6.2.0+version: 0.8.0.0 -- A short (one-line) description of the package. synopsis: JSON wrangling@@ -143,41 +143,40 @@ ghc-options: -Wall -- Other library packages from which modules are imported.- build-depends: base >= 4.9 && < 4.13- , lens >= 4.15 && < 4.18- , mtl >= 2.2.2 && < 2.3- , text >= 1.2 && < 1.3- , bytestring >= 0.10.6 && < 0.11- , parsers >= 0.12 && < 0.13- , digit >= 0.7 && < 0.8- , semigroups >= 0.8.4 && < 0.20- , scientific >= 0.3 && < 0.4- , distributive >= 0.5 && < 0.7- , nats >= 1 && <1.2- , zippers >= 0.2 && < 0.3- , vector >= 0.12 && < 0.13- , errors >= 2.2 && < 2.4- , hoist-error >= 0.2 && < 0.3- , containers >= 0.5.6 && < 0.7- , witherable >= 0.2 && < 0.4- , generics-sop >= 0.4 && < 0.5- , mmorph >= 1.1 && < 1.2- , transformers >= 0.4 && < 0.6- , bifunctors >= 5 && < 5.6- , contravariant >= 1.4 && < 1.6- , wl-pprint-annotated >= 0.1 && < 0.2-- , hw-json >= 1.0.0.2 && < 1.2- , hw-prim >= 0.6 && < 0.7- , hw-balancedparens >= 0.2 && < 0.3- , hw-rankselect >= 0.13 && < 0.14- , hw-bits >= 0.7 && < 0.8-- , tagged >= 0.8.5 && < 0.9- , semigroupoids >= 5.2.2 && < 5.4- , natural >= 0.3 && < 0.4- , unordered-containers >= 0.2.9 && < 0.3- , attoparsec >= 0.13 && < 0.15+ build-depends: base >= 4.9 && < 4.13+ , lens >= 4.15 && < 4.18+ , mtl >= 2.2.2 && < 2.3+ , text >= 1.2 && < 1.3+ , bytestring >= 0.10.6 && < 0.11+ , parsers >= 0.12 && < 0.13+ , digit >= 0.7 && < 0.9+ , semigroups >= 0.8.4 && < 0.20+ , scientific >= 0.3 && < 0.4+ , distributive >= 0.5 && < 0.7+ , nats >= 1 && <1.2+ , zippers >= 0.2 && < 0.4+ , vector >= 0.12 && < 0.13+ , errors >= 2.2 && < 2.4+ , hoist-error >= 0.2 && < 0.3+ , containers >= 0.5.6 && < 0.7+ , witherable >= 0.2 && < 0.4+ , generics-sop >= 0.4 && < 0.5+ , records-sop >= 0.1 && < 0.2+ , mmorph >= 1.1 && < 1.2+ , transformers >= 0.4 && < 0.6+ , bifunctors >= 5 && < 5.6+ , contravariant >= 1.4 && < 1.6+ , wl-pprint-annotated >= 0.1 && < 0.2+ , hw-json-standard-cursor >= 0.2.1.1 && < 0.3+ , hw-prim >= 0.6 && < 0.7+ , hw-balancedparens >= 0.2 && < 0.4+ , hw-rankselect >= 0.13 && < 0.14+ , hw-bits >= 0.7 && < 0.8+ , tagged >= 0.8.5 && < 0.9+ , semigroupoids >= 5.2.2 && < 5.4+ , natural >= 0.3 && < 0.4+ , unordered-containers >= 0.2.9 && < 0.3+ , attoparsec >= 0.13 && < 0.15 -- Directories containing source files. hs-source-dirs: src@@ -192,9 +191,9 @@ hs-source-dirs: test build-depends: base >= 4.9 && < 4.13- , hedgehog >= 0.6 && < 0.7+ , hedgehog >= 0.6 && < 1.2 , text >= 1.2 && < 1.3- , digit >= 0.7 && < 0.8+ , digit >= 0.7 && < 0.9 , attoparsec >= 0.13 && < 0.15 , doctest >= 0.9.7 , filepath >= 1.3@@ -214,8 +213,10 @@ , Types.Json , Types.Whitespace + , Prettier.NestedObjs , Properties , Golden+ , Generics , Laws , Utils , Encoder@@ -228,39 +229,38 @@ main-is: Main.hs hs-source-dirs: test - build-depends: base >= 4.9 && < 4.13- , tasty >= 0.11 && < 1.3- , tasty-hunit >= 0.10 && < 0.11- , tasty-expected-failure >= 0.11 && < 0.12- , hedgehog >= 0.6 && < 0.7- , hedgehog-fn >= 0.6 && < 0.7- , tasty-hedgehog >= 0.2 && < 0.3- , lens >= 4.15 && < 4.18- , distributive >= 0.5 && < 0.7- , bytestring >= 0.10.6 && < 0.11- , digit >= 0.7 && < 0.8- , text >= 1.2 && < 1.3- , semigroups >= 0.8.4 && < 0.20- , zippers >= 0.2 && < 0.3- , vector >= 0.12 && < 0.13- , generics-sop >= 0.4 && < 0.5- , attoparsec >= 0.13 && < 0.15- , scientific >= 0.3 && < 0.4- , tagged >= 0.8.5 && < 0.9- , mtl >= 2.2.2 && < 2.3- , semigroupoids >= 5.2.2 && < 5.6- , containers >= 0.5.6 && < 0.7- , natural >= 0.3 && < 0.4- , contravariant >= 1.4 && < 1.6- , filepath >= 1.4 && < 1.5- , tasty-golden >= 2.3 && < 2.4- , unordered-containers >= 0.2.9 && < 0.3-- , hw-balancedparens >= 0.2 && < 0.3- , hw-rankselect >= 0.13 && < 0.14- , hw-bits >= 0.7 && < 0.8- , hw-json >= 1.0.0.2 && < 1.2- , hw-prim >= 0.6 && < 0.7+ build-depends: base >= 4.9 && < 4.13+ , tasty >= 0.11 && < 1.3+ , tasty-hunit >= 0.10 && < 0.11+ , tasty-expected-failure >= 0.11 && < 0.12+ , hedgehog >= 0.6 && < 1.2+ , hedgehog-fn >= 0.6 && < 2+ , tasty-hedgehog >= 0.2 && < 1.2+ , lens >= 4.15 && < 4.18+ , distributive >= 0.5 && < 0.7+ , bytestring >= 0.10.6 && < 0.11+ , digit >= 0.7 && < 0.9+ , text >= 1.2 && < 1.3+ , semigroups >= 0.8.4 && < 0.20+ , zippers >= 0.2 && < 0.4+ , vector >= 0.12 && < 0.13+ , generics-sop >= 0.4 && < 0.5+ , attoparsec >= 0.13 && < 0.15+ , scientific >= 0.3 && < 0.4+ , tagged >= 0.8.5 && < 0.9+ , mtl >= 2.2.2 && < 2.3+ , semigroupoids >= 5.2.2 && < 5.6+ , containers >= 0.5.6 && < 0.7+ , natural >= 0.3 && < 0.4+ , contravariant >= 1.4 && < 1.6+ , filepath >= 1.4 && < 1.5+ , tasty-golden >= 2.3 && < 2.4+ , unordered-containers >= 0.2.9 && < 0.3+ , hw-balancedparens >= 0.2 && < 0.4+ , hw-rankselect >= 0.13 && < 0.14+ , hw-bits >= 0.7 && < 0.8+ , hw-json-standard-cursor >= 0.2.1.1 && < 0.3+ , hw-prim >= 0.6 && < 0.7 , waargonaut