{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RankNTypes #-}
module Waargonaut.Prettier
(
InlineOption (..)
, NumSpaces (..)
, IndentStep (..)
, prettyJson
, simpleEncodePretty
, module Natural
) where
import Prelude (Eq, Show, (+), (-))
import Control.Applicative (Applicative, (<$>))
import Control.Category (id, (.))
import Control.Lens (Traversal', over,
traverseOf, (%~), (.~),
_1, _2, _Just, _Wrapped)
import Natural (Natural, minus,
successor', zero',
_Natural)
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.Builder as TB
import Data.Bool (Bool, bool)
import Data.Foldable (elem, length)
import Data.Function (($))
import Data.Functor (fmap)
import Data.Maybe (maybe)
import Data.Semigroup ((<>))
import Data.Traversable (traverse)
import qualified Data.Vector as V
import qualified Control.Lens as L
import qualified Control.Lens.Plated as P
import Waargonaut.Encode (Encoder, runEncoder)
import Waargonaut.Types.CommaSep (Elems)
import qualified Waargonaut.Types.CommaSep as CS
import Waargonaut.Types.JObject (HasJAssoc (..), JAssoc)
import Waargonaut.Types.Json (AsJType (..), JType (..),
Json, jsonTraversal)
import Waargonaut.Types.Whitespace (WS (..), Whitespace (..))
import Waargonaut.Encode.Builder (textBuilder,
waargonautBuilder)
import Waargonaut.Encode.Builder.Whitespace (wsBuilder)
data InlineOption
= ArrayOnly
| ObjectOnly
| Both
| Neither
deriving (Show, Eq)
newtype NumSpaces = NumSpaces Natural
deriving (Eq, Show)
newtype IndentStep = IndentStep Natural
deriving (Eq, Show)
simpleEncodePretty
:: Applicative f
=> InlineOption
-> IndentStep
-> NumSpaces
-> Encoder f a
-> a
-> f LT.Text
simpleEncodePretty io step ind enc =
fmap (TB.toLazyText . waargonautBuilder wsBuilder textBuilder . prettyJson io step ind)
. runEncoder enc
objelems :: AsJType r WS a => Traversal' r (Elems WS (JAssoc WS a))
objelems = _JObj . _1 . _Wrapped . CS._CommaSeparated . _2 . _Just
immediateTrailingWS :: Traversal' Json WS
immediateTrailingWS f = traverseOf _Wrapped $ \case
JNull ws -> JNull <$> f ws
JBool b ws -> JBool b <$> f ws
JNum n ws -> JNum n <$> f ws
JStr s ws -> JStr s <$> f ws
JArr a ws -> JArr a <$> f ws
JObj o ws -> JObj o <$> f ws
prettyCommaSep
:: L.Traversal' b (CS.CommaSeparated WS a)
-> L.Traversal' a Json
-> Bool
-> Natural
-> Natural
-> b
-> b
prettyCommaSep csWrapper nested inline step w =
setheadleadingws . stepaftercomma
where
spaces x = V.replicate (_Natural L.# x) Space
ws' x = bool (WS (V.singleton NewLine) <>) id inline $ WS (spaces x)
i = ws' (bool w (successor' zero') inline)
l = bool (ws' (w `minus` step)) i inline
setheadleadingws = csWrapper . CS._CommaSeparated . _1 .~ i
stepaftercomma = csWrapper . CS._CommaSeparated . _2 . _Just %~ \es -> es
L.& CS.elemsElems . traverse . CS.elemTrailing . fmap . _2 .~ i
L.& CS.elemsLast . CS.elemTrailing . _Just . _2 .~ l
L.& CS.elemsLast . CS.elemVal . nested . immediateTrailingWS .~ l
prettyJson :: InlineOption -> IndentStep -> NumSpaces -> Json -> Json
prettyJson inlineOpt (IndentStep step) (NumSpaces w) = P.transformOf jsonTraversal (
prettyCommaSep (_JArr . _1 . _Wrapped) id inlineArr step w .
prettyCommaSep (_JObj . _1 . _Wrapped) jsonAssocVal inlineObj step w .
setnested .
alignafterkey
)
where
inlineArr = inlineOpt `elem` [ArrayOnly, Both]
inlineObj = inlineOpt `elem` [ObjectOnly, Both]
spaces x = V.replicate x Space
alignafterkey j = over (objelems . traverse) (\ja ->
let
kl = ja L.^. jsonAssocKey . _Wrapped . L.to length
in
ja L.& jsonAssocValPreceedingWS .~ (WS . spaces $ longestKey - kl)
) j
where
longestKey = maybe 1 (+1) $ L.maximumOf (objelems . L.folded . jsonAssocKey . _Wrapped . L.to length) j
setnested = objelems . traverse . jsonAssocVal %~
prettyJson inlineOpt (IndentStep step) (NumSpaces $ w <> step)