packages feed

kdl-hs-1.0.0: test/kdl-hs-test-decoder.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}

import Data.Aeson ((.=))
import Data.Aeson qualified as Aeson
import Data.ByteString.Lazy.Char8 qualified as ByteStringL
import Data.Either (partitionEithers)
import Data.Map qualified as Map
import Data.Scientific qualified as Scientific
import Data.Text qualified as Text
import Data.Text.IO qualified as Text
import KDL qualified
import System.Exit (exitFailure)
import System.IO (stderr)

main :: IO ()
main = do
  input <- Text.getContents
  doc <-
    case KDL.parse input of
      Right doc -> pure doc
      Left e -> do
        Text.hPutStrLn stderr e
        exitFailure
  ByteStringL.putStrLn . Aeson.encode $ encodeNodeList doc

encodeNodeList :: KDL.NodeList -> Aeson.Value
encodeNodeList nodes = Aeson.toJSON . map encodeNode $ nodes.nodes

encodeNode :: KDL.Node -> Aeson.Value
encodeNode node =
  Aeson.object
    [ "type" .= (encodeAnn <$> node.ann)
    , "name" .= encodeIdentifier node.name
    , "args" .= args
    , "props" .= Map.fromList props
    , "children" .= maybe (Aeson.Array mempty) encodeNodeList node.children
    ]
 where
  (args, props) =
    partitionEithers
      [ case entry.name of
          Nothing -> Left val
          Just name -> Right (name.value, val)
      | entry <- node.entries
      , let val = encodeEntry entry
      ]

encodeEntry :: KDL.Entry -> Aeson.Value
encodeEntry entry =
  Aeson.object
    [ "type" .= (encodeAnn <$> entry.value.ann)
    , "value" .= encodeValueData entry.value.data_
    ]

encodeValueData :: KDL.ValueData -> Aeson.Value
encodeValueData = \case
  KDL.String s -> val "string" (Text.unpack s)
  KDL.Number x -> val "number" (Scientific.formatScientific Scientific.Fixed Nothing x)
  KDL.Bool x -> val "boolean" (if x then "true" else "false")
  KDL.Inf -> val "number" "inf"
  KDL.NegInf -> val "number" "-inf"
  KDL.NaN -> val "number" "nan"
  KDL.Null -> Aeson.object ["type" .= Text.pack "null"]
 where
  val :: String -> String -> Aeson.Value
  val ty v = Aeson.object ["type" .= ty, "value" .= v]

encodeAnn :: KDL.Ann -> Aeson.Value
encodeAnn = encodeIdentifier . (.identifier)

encodeIdentifier :: KDL.Identifier -> Aeson.Value
encodeIdentifier = Aeson.toJSON . (.value)