    {-# LANGUAGE OverloadedStrings, RecordWildCards, CPP #-}

    -- |Aeson-compatible pretty-printing of JSON 'Value's.
    module Data.Aeson.Encode.Pretty (
        -- * Simple Pretty-Printing
        encodePretty, encodePrettyToTextBuilder,

        -- * Pretty-Printing with Configuration Options
        encodePretty', encodePrettyToTextBuilder',
        Config (..), defConfig,
        Indent(..), NumberFormat(..),
        -- ** Sorting Keys in Objects
        -- |With the Aeson library, the order of keys in objects is undefined due to
        --  objects being implemented as HashMaps. To allow user-specified key
        --  orders in the pretty-printed JSON, 'encodePretty'' can be configured
        --  with a comparison function. These comparison functions can be composed
        --  using the 'Monoid' interface. Some other useful helper functions to keep
        --  in mind are 'comparing' and 'on'.
        --
        --  Consider the following deliberately convoluted example, demonstrating
        --  the use of comparison functions:
        --
        --  An  object might pretty-print as follows
        --
        --  > {
        --  >   "baz": ...,
        --  >   "bar": ...,
        --  >   "foo": ...,
        --  >   "quux": ...,
        --  > }
        --
        --  which is clearly a confusing order of keys. By using a comparison
        --  function such as
        --
        --  > comp :: Text -> Text -> Ordering
        --  > comp = keyOrder ["foo","bar"] `mappend` comparing length
        --
        --  we can achieve the desired neat result:
        --
        --  > {
        --  >   "foo": ...,
        --  >   "bar": ...,
        --  >   "baz": ...,
        --  >   "quux": ...,
        --  > }
        --

        mempty,
        -- |Serves as an order-preserving (non-)sort function. Re-exported from
        --  "Data.Monoid".
        compare,
        -- |Sort keys in their natural order, i.e. by comparing character codes.
        -- Re-exported from the Prelude and "Data.Ord"
        keyOrder
    ) where

    #if MIN_VERSION_aeson(2,0,0)
    import qualified Data.Aeson.Key as AK
    import qualified Data.Aeson.KeyMap as AKM
    #endif
    import Data.Aeson (Value(..), ToJSON(..))
    import qualified Data.Aeson.Text as Aeson
    import Data.ByteString.Lazy (ByteString)
    import Data.Function (on)
    #if !MIN_VERSION_aeson(2,0,0)
    import qualified Data.HashMap.Strict as H (toList)
    #endif
    import Data.List (intersperse, sortBy, elemIndex)
    import Data.Maybe (fromMaybe)
    #if !MIN_VERSION_base(4,13,0)
    import Data.Semigroup ((<>))
    #endif
    import qualified Data.Scientific as S (Scientific, FPFormat(..))
    import Data.Ord (comparing)
    import Data.Text (Text)
    import Data.Text.Lazy.Builder (Builder, toLazyText)
    import Data.Text.Lazy.Builder.Scientific (formatScientificBuilder)
    import Data.Text.Lazy.Encoding (encodeUtf8)
    import qualified Data.Vector as V (toList)
    import Prelude ()
    import Prelude.Compat

    data PState = PState { PState -> Int
    pLevel     :: Int
                         , PState -> Builder
    pIndent    :: Builder
                         , PState -> Builder
    pNewline   :: Builder
                         , PState -> Builder
    pItemSep   :: Builder
                         , PState -> Builder
    pKeyValSep :: Builder
                         , PState -> NumberFormat
    pNumFormat :: NumberFormat
                         , PState -> [(Text, Value)] -> [(Text, Value)]
    pSort      :: [(Text, Value)] -> [(Text, Value)]
                         }

    -- | Indentation per level of nesting. @'Spaces' 0@ removes __all__ whitespace
    --   from the output.
    data Indent = Spaces Int | Tab

    data NumberFormat
      -- | The standard behaviour of the 'Aeson.encode' function. Uses
      --   integer literals for integers (1, 2, 3...), simple decimals
      --   for fractional values between 0.1 and 9,999,999, and scientific
      --   notation otherwise.
      = Generic
      -- | Scientific notation (e.g. 2.3e123).
      | Scientific
      -- | Standard decimal notation
      | Decimal
      -- | Custom formatting function
      | Custom (S.Scientific -> Builder)

    data Config = Config
        { Config -> Indent
    confIndent  :: Indent
          -- ^ Indentation per level of nesting
        , Config -> Text -> Text -> Ordering
    confCompare :: Text -> Text -> Ordering
          -- ^ Function used to sort keys in objects
        , Config -> NumberFormat
    confNumFormat :: NumberFormat
        , Config -> Bool
    confTrailingNewline :: Bool
          -- ^ Whether to add a trailing newline to the output
        }

    -- |Sort keys by their order of appearance in the argument list.
    --
    --  Keys that are not present in the argument list are considered to be greater
    --  than any key in the list and equal to all keys not in the list. I.e. keys
    --  not in the argument list are moved to the end, while their order is
    --  preserved.
    keyOrder :: [Text] -> Text -> Text -> Ordering
    keyOrder :: [Text] -> Text -> Text -> Ordering
    keyOrder [Text]
    ks = (Text -> Int) -> Text -> Text -> Ordering
    forall a b. Ord a => (b -> a) -> b -> b -> Ordering
    comparing ((Text -> Int) -> Text -> Text -> Ordering)
    -> (Text -> Int) -> Text -> Text -> Ordering
    forall a b. (a -> b) -> a -> b
    $ \Text
    k -> Int -> Maybe Int -> Int
    forall a. a -> Maybe a -> a
    fromMaybe Int
    forall a. Bounded a => a
    maxBound (Text -> [Text] -> Maybe Int
    forall a. Eq a => a -> [a] -> Maybe Int
    elemIndex Text
    k [Text]
    ks)

    -- |The default configuration: indent by four spaces per level of nesting, do
    --  not sort objects by key, do not add trailing newline.
    --
    --  > defConfig = Config { confIndent = Spaces 4, confCompare = mempty, confNumFormat = Generic, confTrailingNewline = False }
    defConfig :: Config
    defConfig :: Config
    defConfig =
      Config {confIndent :: Indent
    confIndent = Int -> Indent
    Spaces Int
    4, confCompare :: Text -> Text -> Ordering
    confCompare = Text -> Text -> Ordering
    forall a. Monoid a => a
    mempty, confNumFormat :: NumberFormat
    confNumFormat = NumberFormat
    Generic, confTrailingNewline :: Bool
    confTrailingNewline = Bool
    False}

    -- |A drop-in replacement for aeson's 'Aeson.encode' function, producing
    --  JSON-ByteStrings for human readers.
    --
    --  Follows the default configuration in 'defConfig'.
    encodePretty :: ToJSON a => a -> ByteString
    encodePretty :: forall a. ToJSON a => a -> ByteString
    encodePretty = Config -> a -> ByteString
    forall a. ToJSON a => Config -> a -> ByteString
    encodePretty' Config
    defConfig

    -- |A variant of 'encodePretty' that takes an additional configuration
    --  parameter.
    encodePretty' :: ToJSON a => Config -> a -> ByteString
    encodePretty' :: forall a. ToJSON a => Config -> a -> ByteString
    encodePretty' Config
    conf = Text -> ByteString
    encodeUtf8 (Text -> ByteString) -> (a -> Text) -> a -> ByteString
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Builder -> Text
    toLazyText (Builder -> Text) -> (a -> Builder) -> a -> Text
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Config -> a -> Builder
    forall a. ToJSON a => Config -> a -> Builder
    encodePrettyToTextBuilder' Config
    conf

    -- |A drop-in replacement for aeson's 'Aeson.encodeToTextBuilder' function,
    --  producing JSON-ByteStrings for human readers.
    --
    --  Follows the default configuration in 'defConfig'.
    encodePrettyToTextBuilder :: ToJSON a => a -> Builder
    encodePrettyToTextBuilder :: forall a. ToJSON a => a -> Builder
    encodePrettyToTextBuilder = Config -> a -> Builder
    forall a. ToJSON a => Config -> a -> Builder
    encodePrettyToTextBuilder' Config
    defConfig

    -- |A variant of 'Aeson.encodeToTextBuilder' that takes an additional configuration
    --  parameter.
    encodePrettyToTextBuilder' :: ToJSON a => Config -> a -> Builder
    encodePrettyToTextBuilder' :: forall a. ToJSON a => Config -> a -> Builder
    encodePrettyToTextBuilder' Config{Bool
    NumberFormat
    Indent
    Text -> Text -> Ordering
    confIndent :: Config -> Indent
    confCompare :: Config -> Text -> Text -> Ordering
    confNumFormat :: Config -> NumberFormat
    confTrailingNewline :: Config -> Bool
    confIndent :: Indent
    confCompare :: Text -> Text -> Ordering
    confNumFormat :: NumberFormat
    confTrailingNewline :: Bool
    ..} a
    x = PState -> Value -> Builder
    fromValue PState
    st (a -> Value
    forall a. ToJSON a => a -> Value
    toJSON a
    x) Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> Builder
    trail
      where
        st :: PState
    st      = Int
    -> Builder
    -> Builder
    -> Builder
    -> Builder
    -> NumberFormat
    -> ([(Text, Value)] -> [(Text, Value)])
    -> PState
    PState Int
    0 Builder
    indent Builder
    newline Builder
    itemSep Builder
    kvSep NumberFormat
    confNumFormat [(Text, Value)] -> [(Text, Value)]
    forall {b}. [(Text, b)] -> [(Text, b)]
    sortFn
        indent :: Builder
    indent  = case Indent
    confIndent of
                    Spaces Int
    n -> [Builder] -> Builder
    forall a. Monoid a => [a] -> a
    mconcat (Int -> Builder -> [Builder]
    forall a. Int -> a -> [a]
    replicate Int
    n Builder
    " ")
                    Indent
    Tab      -> Builder
    "\t"
        newline :: Builder
    newline = case Indent
    confIndent of
                    Spaces Int
    0 -> Builder
    ""
                    Indent
    _        -> Builder
    "\n"
        itemSep :: Builder
    itemSep = Builder
    ","
        kvSep :: Builder
    kvSep   = case Indent
    confIndent of
                    Spaces Int
    0 -> Builder
    ":"
                    Indent
    _        -> Builder
    ": "
        sortFn :: [(Text, b)] -> [(Text, b)]
    sortFn  = ((Text, b) -> (Text, b) -> Ordering) -> [(Text, b)] -> [(Text, b)]
    forall a. (a -> a -> Ordering) -> [a] -> [a]
    sortBy (Text -> Text -> Ordering
    confCompare (Text -> Text -> Ordering)
    -> ((Text, b) -> Text) -> (Text, b) -> (Text, b) -> Ordering
    forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
    `on` (Text, b) -> Text
    forall a b. (a, b) -> a
    fst)
        trail :: Builder
    trail   = if Bool
    confTrailingNewline then Builder
    "\n" else Builder
    ""

    fromValue :: PState -> Value -> Builder
    fromValue :: PState -> Value -> Builder
    fromValue st :: PState
    st@PState{Int
    Builder
    NumberFormat
    [(Text, Value)] -> [(Text, Value)]
    pLevel :: PState -> Int
    pIndent :: PState -> Builder
    pNewline :: PState -> Builder
    pItemSep :: PState -> Builder
    pKeyValSep :: PState -> Builder
    pNumFormat :: PState -> NumberFormat
    pSort :: PState -> [(Text, Value)] -> [(Text, Value)]
    pLevel :: Int
    pIndent :: Builder
    pNewline :: Builder
    pItemSep :: Builder
    pKeyValSep :: Builder
    pNumFormat :: NumberFormat
    pSort :: [(Text, Value)] -> [(Text, Value)]
    ..} Value
    val = Value -> Builder
    go Value
    val
      where
        go :: Value -> Builder
    go (Array Array
    v)  = PState
    -> (Builder, Builder)
    -> (PState -> Value -> Builder)
    -> [Value]
    -> Builder
    forall a.
    PState
    -> (Builder, Builder) -> (PState -> a -> Builder) -> [a] -> Builder
    fromCompound PState
    st (Builder
    "[",Builder
    "]") PState -> Value -> Builder
    fromValue (Array -> [Value]
    forall a. Vector a -> [a]
    V.toList Array
    v)
        go (Object Object
    m) = PState
    -> (Builder, Builder)
    -> (PState -> (Text, Value) -> Builder)
    -> [(Text, Value)]
    -> Builder
    forall a.
    PState
    -> (Builder, Builder) -> (PState -> a -> Builder) -> [a] -> Builder
    fromCompound PState
    st (Builder
    "{",Builder
    "}") PState -> (Text, Value) -> Builder
    fromPair ([(Text, Value)] -> [(Text, Value)]
    pSort (Object -> [(Text, Value)]
    forall {b}. KeyMap b -> [(Text, b)]
    toList' Object
    m))
        go (Number Scientific
    x) = PState -> Scientific -> Builder
    fromNumber PState
    st Scientific
    x
        go Value
    v          = Value -> Builder
    forall a. ToJSON a => a -> Builder
    Aeson.encodeToTextBuilder Value
    v

    #if MIN_VERSION_aeson(2,0,0)
        toList' :: KeyMap b -> [(Text, b)]
    toList' = ((Key, b) -> (Text, b)) -> [(Key, b)] -> [(Text, b)]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (\(Key
    k, b
    v) -> (Key -> Text
    AK.toText Key
    k, b
    v)) ([(Key, b)] -> [(Text, b)])
    -> (KeyMap b -> [(Key, b)]) -> KeyMap b -> [(Text, b)]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . KeyMap b -> [(Key, b)]
    forall v. KeyMap v -> [(Key, v)]
    AKM.toList
    #else
        toList' = H.toList
    #endif

    fromCompound :: PState
                 -> (Builder, Builder)
                 -> (PState -> a -> Builder)
                 -> [a]
                 -> Builder
    fromCompound :: forall a.
    PState
    -> (Builder, Builder) -> (PState -> a -> Builder) -> [a] -> Builder
    fromCompound st :: PState
    st@PState{Int
    Builder
    NumberFormat
    [(Text, Value)] -> [(Text, Value)]
    pLevel :: PState -> Int
    pIndent :: PState -> Builder
    pNewline :: PState -> Builder
    pItemSep :: PState -> Builder
    pKeyValSep :: PState -> Builder
    pNumFormat :: PState -> NumberFormat
    pSort :: PState -> [(Text, Value)] -> [(Text, Value)]
    pLevel :: Int
    pIndent :: Builder
    pNewline :: Builder
    pItemSep :: Builder
    pKeyValSep :: Builder
    pNumFormat :: NumberFormat
    pSort :: [(Text, Value)] -> [(Text, Value)]
    ..} (Builder
    delimL,Builder
    delimR) PState -> a -> Builder
    fromItem [a]
    items = [Builder] -> Builder
    forall a. Monoid a => [a] -> a
    mconcat
        [ Builder
    delimL
        , if [a] -> Bool
    forall a. [a] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null [a]
    items then Builder
    forall a. Monoid a => a
    mempty
            else Builder
    pNewline Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> Builder
    items' Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> Builder
    pNewline Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> PState -> Builder
    fromIndent PState
    st
        , Builder
    delimR
        ]
      where
        items' :: Builder
    items' = [Builder] -> Builder
    forall a. Monoid a => [a] -> a
    mconcat ([Builder] -> Builder)
    -> ([Builder] -> [Builder]) -> [Builder] -> Builder
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Builder -> [Builder] -> [Builder]
    forall a. a -> [a] -> [a]
    intersperse (Builder
    pItemSep Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> Builder
    pNewline) ([Builder] -> Builder) -> [Builder] -> Builder
    forall a b. (a -> b) -> a -> b
    $
                    (a -> Builder) -> [a] -> [Builder]
    forall a b. (a -> b) -> [a] -> [b]
    map (\a
    item -> PState -> Builder
    fromIndent PState
    st' Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> PState -> a -> Builder
    fromItem PState
    st' a
    item)
                        [a]
    items
        st' :: PState
    st' = PState
    st { pLevel = pLevel + 1}

    fromPair :: PState -> (Text, Value) -> Builder
    fromPair :: PState -> (Text, Value) -> Builder
    fromPair PState
    st (Text
    k,Value
    v) =
      Value -> Builder
    forall a. ToJSON a => a -> Builder
    Aeson.encodeToTextBuilder (Text -> Value
    forall a. ToJSON a => a -> Value
    toJSON Text
    k) Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> PState -> Builder
    pKeyValSep PState
    st Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> PState -> Value -> Builder
    fromValue PState
    st Value
    v

    fromIndent :: PState -> Builder
    fromIndent :: PState -> Builder
    fromIndent PState{Int
    Builder
    NumberFormat
    [(Text, Value)] -> [(Text, Value)]
    pLevel :: PState -> Int
    pIndent :: PState -> Builder
    pNewline :: PState -> Builder
    pItemSep :: PState -> Builder
    pKeyValSep :: PState -> Builder
    pNumFormat :: PState -> NumberFormat
    pSort :: PState -> [(Text, Value)] -> [(Text, Value)]
    pLevel :: Int
    pIndent :: Builder
    pNewline :: Builder
    pItemSep :: Builder
    pKeyValSep :: Builder
    pNumFormat :: NumberFormat
    pSort :: [(Text, Value)] -> [(Text, Value)]
    ..} = [Builder] -> Builder
    forall a. Monoid a => [a] -> a
    mconcat (Int -> Builder -> [Builder]
    forall a. Int -> a -> [a]
    replicate Int
    pLevel Builder
    pIndent)

    fromNumber :: PState -> S.Scientific -> Builder
    fromNumber :: PState -> Scientific -> Builder
    fromNumber PState
    st Scientific
    x = case PState -> NumberFormat
    pNumFormat PState
    st of
      NumberFormat
    Generic
        | (Scientific
    x Scientific -> Scientific -> Bool
    forall a. Ord a => a -> a -> Bool
    > Scientific
    1.0e19 Bool -> Bool -> Bool
    || Scientific
    x Scientific -> Scientific -> Bool
    forall a. Ord a => a -> a -> Bool
    < -Scientific
    1.0e19) -> FPFormat -> Maybe Int -> Scientific -> Builder
    formatScientificBuilder FPFormat
    S.Exponent Maybe Int
    forall a. Maybe a
    Nothing Scientific
    x
        | Bool
    otherwise -> Value -> Builder
    forall a. ToJSON a => a -> Builder
    Aeson.encodeToTextBuilder (Value -> Builder) -> Value -> Builder
    forall a b. (a -> b) -> a -> b
    $ Scientific -> Value
    Number Scientific
    x
      NumberFormat
    Scientific -> FPFormat -> Maybe Int -> Scientific -> Builder
    formatScientificBuilder FPFormat
    S.Exponent Maybe Int
    forall a. Maybe a
    Nothing Scientific
    x
      NumberFormat
    Decimal    -> FPFormat -> Maybe Int -> Scientific -> Builder
    formatScientificBuilder FPFormat
    S.Fixed Maybe Int
    forall a. Maybe a
    Nothing Scientific
    x
      Custom Scientific -> Builder
    f   -> Scientific -> Builder
    f Scientific
    x