aboutsummaryrefslogtreecommitdiff
path: root/pandoc-lua-engine/src/Text/Pandoc/Lua/Module/JSON.hs
blob: 6b309fbd81129913050d6a01fc7ad609bedd8ca1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE OverloadedStrings #-}
{-|
Module      : Text.Pandoc.Lua.Module.JSON
Copyright   : © 2022-2023 Albert Krewinkel
License     : MIT
Maintainer  : Albert Krewinkel <[email protected]>

Lua module to work with JSON.
-}
module Text.Pandoc.Lua.Module.JSON (
  -- * Module
    documentedModule

  -- ** Functions
  , decode
  , encode
  )
where

import Prelude hiding (null)
import Data.Maybe (fromMaybe)
import Data.Monoid (Alt (..))
import Data.Version (makeVersion)
import HsLua.Aeson
import HsLua.Core
import HsLua.Marshalling
import HsLua.Packaging
import Text.Pandoc.Error (PandocError)
import Text.Pandoc.Lua.PandocLua ()
import Text.Pandoc.Lua.Marshal.AST

import qualified Data.Aeson as Aeson
import qualified Data.Text as T

-- | The @aeson@ module specification.
documentedModule :: Module PandocError
documentedModule = Module
  { moduleName = "pandoc.json"
  , moduleDescription = "JSON module based on the Aeson Haskell package."
  , moduleFields = fields
  , moduleFunctions = functions
  , moduleOperations = []
  , moduleTypeInitializers = []
  }

--
-- Fields
--

-- | Exported fields.
fields :: LuaError e => [Field e]
fields =
  [ null
  ]

-- | The value used to represent the JSON @null@.
null :: LuaError e => Field e
null = Field
  { fieldName = "null"
  , fieldType = "userdata"
  , fieldDescription = "Value used to represent the `null` JSON value."
  , fieldPushValue = pushValue Aeson.Null
  }

--
-- Functions
--

functions :: [DocumentedFunction PandocError]
functions =
  [ decode `since` makeVersion [3, 1, 1]
  , encode `since` makeVersion [3, 1, 1]
  ]

-- | Decode a JSON string into a Lua object.
decode :: DocumentedFunction PandocError
decode = defun "decode"
  ### (\str usePandocTypes ->
         fromMaybe pushnil . getAlt . mconcat . map Alt $
         (if usePandocTypes == Just False
          then []
          else [ pushInline  <$> Aeson.decode str
               , pushBlock   <$> Aeson.decode str
               , pushPandoc  <$> Aeson.decode str
               , pushInlines <$> Aeson.decode str
               , pushBlocks  <$> Aeson.decode str
               ])
         ++ [pushValue <$> Aeson.decode str])
  <#> parameter peekLazyByteString "string" "str" "JSON string"
  <#> opt (parameter peekBool "boolean" "pandoc_types"
           "whether to use pandoc types when possible.")
  =#> functionResult pure "any" "decoded object"
  #? T.unlines
     [ "Creates a Lua object from a JSON string. The function returns an"
     , "[Inline], [Block], [Pandoc], [Inlines], or [Blocks] element if the"
     , "input can be decoded into represent any of those types. Otherwise"
     , "the default decoding is applied, using tables, booleans, numbers,"
     , "and [null](#pandoc.json.null) to represent the JSON value."
     , ""
     , "The special handling of AST elements can be disabled by setting"
     , "`pandoc_types` to `false`."
     ]

-- | Encode a Lua object as JSON.
encode :: LuaError e => DocumentedFunction e
encode = defun "encode"
  ### (\idx -> do
          -- ensure that there are no other objects on the stack.
          settop (nthBottom 1)
          getmetafield idx "__tojson" >>= \case
            TypeNil -> do
              -- No metamethod, use default encoder.
              value <- forcePeek $ peekValue idx
              pushLazyByteString $ Aeson.encode value
            _ -> do
              -- Try to use the field value as function
              insert (nth 2)
              call 1 1
              ltype top >>= \case
                TypeString -> pure ()
                _ -> failLua
                     "Call to __tojson metamethod did not yield a string")
  <#> parameter pure "any" "object" "object to convert"
  =#> functionResult pure "string" "JSON encoding of `object`"
  #? T.unlines
     ["Encodes a Lua object as JSON string."
     , ""
     , "If the object has a metamethod with name `__tojson`, then the"
     , "result is that of a call to that method with `object` passed as"
     , "the sole argument. The result of that call is expected to be a"
     , "valid JSON string, but this not checked."
     ]