Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HsYAML 0.2 #1

Merged
merged 6 commits into from
Aug 26, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Revision history for HsYAML-aeson

## 0.2.0.0

* Changes in accordance with HsYAML-0.2.0.0 (#1)
* New convinence function `decode1Strict`
* New functions`encode1`, `encode1Strict`, `encodeValue`, `encodeValue'` for encoding JSON Values

## 0.1.0.0

* First release. Released on an unsuspecting world.
7 changes: 5 additions & 2 deletions HsYAML-aeson.cabal
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cabal-version: 2.2
name: HsYAML-aeson
version: 0.1.0.0
version: 0.2.0.0
license: GPL-2.0-or-later
license-file: LICENSE.GPLv2
author: Herbert Valerio Riedel
Expand Down Expand Up @@ -30,12 +30,15 @@ source-repository head
library
exposed-modules: Data.YAML.Aeson
build-depends:
, HsYAML ^>= 0.1.1.0
, HsYAML ^>= 0.2
, aeson ^>= 1.4.0.0
, base >= 4.5 && < 4.13
, bytestring ^>= 0.9.2.1 || ^>= 0.10.0.2
, containers >=0.4.2 && <0.7
, mtl ^>= 2.2.1
, scientific ^>= 0.3.6.2
, text ^>= 1.2.3.1
, unordered-containers >= 0.2 && < 0.3
vijayphoenix marked this conversation as resolved.
Show resolved Hide resolved
, vector ^>= 0.12.0.2

hs-source-dirs: src
Expand Down
8 changes: 8 additions & 0 deletions cabal.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
packages: .

source-repository-package
vijayphoenix marked this conversation as resolved.
Show resolved Hide resolved
type: git
location: https://github.com/hvr/HsYAML.git

package HsYAML-aeson
flags: +exe
102 changes: 88 additions & 14 deletions src/Data/YAML/Aeson.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE Trustworthy #-}

Expand All @@ -21,20 +22,32 @@ module Data.YAML.Aeson
-- ** High-level parsing/decoding via 'FromJSON' instances
decode1
, decode1'
, decode1Strict
-- ** Parsing into JSON AST ('J.Value')
, decodeValue
, decodeValue'
, scalarToValue
-- ** Encoding/Dumping
, encode1
, encode1Strict
, encodeValue
, encodeValue'
) where

import Control.Applicative as Ap
import Control.Monad.Identity (runIdentity)
import Data.Aeson as J
import qualified Data.Aeson.Types as J
import qualified Data.ByteString.Lazy as BS.L
import qualified Data.ByteString as BS
import Data.Text (Text)
import qualified Data.Vector as V
import Data.YAML as Y
import Data.YAML as Y hiding (decode1, decode1Strict, encode1, encode1Strict)
import Data.YAML.Event (Pos)
import qualified Data.YAML.Token as YT
import Data.Scientific
import qualified Data.Map as Map
import qualified Data.HashMap.Strict as HM

-- | Parse a single YAML document using the 'coreSchemaResolver' and decode to Haskell types using 'FromJSON' instances.
--
Expand All @@ -46,21 +59,27 @@ import Data.YAML as Y
-- decoder configuration.
--
decode1 :: FromJSON v => BS.L.ByteString -> Either String v
decode1 bs = do
vs <- decodeValue bs
case vs of
decode1 bs = case decodeValue bs of
Left (_ ,err) -> Left err
Right vs -> case vs of
[] -> Left "No documents found in YAML stream"
(_:_:_) -> Left "Multiple documents encountered in YAML stream"
[v1] -> do
case J.fromJSON v1 of
J.Success v2 -> Right $! v2
J.Error err -> Left ("fromJSON: " ++ err)

-- | Like 'decode1' but takes a strict 'BS.ByteString'
--
-- @since 0.2.0
decode1Strict :: FromJSON v => BS.ByteString -> Either String v
decode1Strict = decode1 . BS.L.fromChunks . (:[])

-- | Variant of 'decode1' allowing for customization. See 'decodeValue'' for documentation of parameters.
decode1' :: FromJSON v => SchemaResolver -> (J.Value -> Either String Text) -> BS.L.ByteString -> Either String v
decode1' schema keyconv bs = do
vs <- decodeValue' schema keyconv bs
case vs of
decode1' schema keyconv bs = case decodeValue' schema keyconv bs of
Left (_ ,err) -> Left err
Right vs -> case vs of
[] -> Left "No documents found in YAML stream"
(_:_:_) -> Left "Multiple documents encountered in YAML stream"
[v1] -> do
Expand All @@ -83,7 +102,7 @@ decode1' schema keyconv bs = do
-- which performs no conversion and will fail when encountering YAML
-- Scalars that have not been resolved to a text Scalar (according to
-- the respective YAML schema resolver).
decodeValue :: BS.L.ByteString -> Either String [J.Value]
decodeValue :: BS.L.ByteString -> Either (Pos, String) [J.Value]
decodeValue = decodeValue' coreSchemaResolver identityKeyConv
where
identityKeyConv :: J.Value -> Either String Text
Expand All @@ -103,15 +122,15 @@ decodeValue' :: SchemaResolver -- ^ YAML Schema resolver to use
-- ^ JSON object key conversion function. This operates on the YAML node as resolved by the 'SchemaResolver' and subsequently converted into a JSON Value according to the 'scalarToValue' conversion. See 'decodeValue' documentation for an example.

-> BS.L.ByteString -- ^ YAML document to parse
-> Either String [J.Value]
-> Either (Pos, String) [J.Value]
decodeValue' SchemaResolver{..} keyconv bs0
= runIdentity (decodeLoader failsafeLoader bs0)
where
failsafeLoader = Loader { yScalar = \t s v -> pure $! schemaResolverScalar t s v >>= mkScl
, ySequence = \t vs -> pure $! schemaResolverSequence t >>= \_ -> mkArr vs
, yMapping = \t kvs -> pure $! schemaResolverMapping t >>= \_ -> mkObj kvs
, yAlias = \_ c n -> pure $! if c then Left "cycle detected" else Right n
, yAnchor = \_ n -> Ap.pure $! Right $! n
failsafeLoader = Loader { yScalar = \t s v _ -> pure $! schemaResolverScalar t s v >>= mkScl
, ySequence = \t vs _ -> pure $! schemaResolverSequence t >>= \_ -> mkArr vs
, yMapping = \t kvs _ -> pure $! schemaResolverMapping t >>= \_ -> mkObj kvs
, yAlias = \_ c n _ -> pure $! if c then Left "cycle detected" else Right n
, yAnchor = \_ n _ -> Ap.pure $! Right $! n
}

mkObj :: [(J.Value, J.Value)] -> Either String J.Value
Expand Down Expand Up @@ -141,3 +160,58 @@ scalarToValue (Y.SFloat x) = Just $! J.Number (realToFrac x)
scalarToValue (Y.SInt i) = Just $! J.Number (fromInteger i)
scalarToValue (SStr t) = Just $! J.String t
scalarToValue (SUnknown _ _) = Nothing


-- | Equivalent to the fuction Data.ByteString.toStrict.
-- O(n) Convert a lazy ByteString into a strict ByteString.
{-# INLINE bsToStrict #-}
bsToStrict :: BS.L.ByteString -> BS.ByteString
#if MIN_VERSION_bytestring(0,10,0)
bsToStrict = BS.L.toStrict
#else
bsToStrict = BS.concat . BS.L.toChunks
#endif

-- | @since 0.2.0
instance ToYAML J.Value where
vijayphoenix marked this conversation as resolved.
Show resolved Hide resolved
toYAML J.Null = Scalar () SNull
toYAML (J.Bool b) = toYAML b
toYAML (J.String txt) = toYAML txt
toYAML (J.Number sc) = case floatingOrInteger sc :: Either Double Integer of
Right d -> toYAML d
Left int -> toYAML int
toYAML (J.Array a) = toYAML (V.toList a)
toYAML (J.Object o) = toYAML (Map.fromList (HM.toList o))

-- | Serialize JSON Value using the YAML 1.2 Core schema to a lazy 'BS.L.ByteString'.
--
-- 'encode1' emits exactly one YAML document.
--
-- See 'encodeValue' for more information about this functions' YAML
-- encoder configuration.
--
-- @since 0.2.0
encode1 :: ToJSON v => v -> BS.L.ByteString
encode1 a = encodeValue [J.toJSON a]

-- | Like 'encode1' but outputs 'BS.ByteString'
--
-- @since 0.2.0
encode1Strict :: ToJSON v => v -> BS.ByteString
encode1Strict = bsToStrict . encode1

-- | Dump YAML Nodes as a lazy 'BS.L.ByteString'
--
-- Each YAML 'Node' is emitted as a individual YAML Document where each Document is terminated by a 'DocumentEnd' indicator.
--
-- This is a convenience wrapper over `encodeNode'`
--
-- @since 0.2.0
encodeValue :: [J.Value] -> BS.L.ByteString
encodeValue = encodeValue' coreSchemaEncoder YT.UTF8

-- | Customizable variant of 'encodeNode'
--
-- @since 0.2.0
encodeValue' :: SchemaEncoder -> YT.Encoding -> [J.Value] -> BS.L.ByteString
encodeValue' schemaEncoder encoding values = Y.encodeNode' schemaEncoder encoding (map (Doc. toYAML) values)