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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
|
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{- |
Module : Text.Pandoc.Lua.Module.MediaBag
Copyright : Copyright © 2017-2024 Albert Krewinkel
License : GNU GPL, version 2 or above
Maintainer : Albert Krewinkel <[email protected]>
The Lua module @pandoc.mediabag@.
-}
module Text.Pandoc.Lua.Module.MediaBag
( documentedModule
) where
import Prelude hiding (lookup)
import Data.Maybe (fromMaybe)
import Data.Version (makeVersion)
import HsLua ( LuaE, DocumentedFunction, Module (..)
, (<#>), (###), (=#>), (=?>), (#?), defun, functionResult
, opt, parameter, since, stringParam, textParam)
import Text.Pandoc.Class ( fetchItem, fillMediaBag, getMediaBag, setMediaBag )
import Text.Pandoc.Class.IO (writeMedia)
import Text.Pandoc.Error (PandocError)
import Text.Pandoc.Lua.Marshal.Pandoc (peekPandoc, pushPandoc)
import Text.Pandoc.Lua.Marshal.List (pushPandocList)
import Text.Pandoc.Lua.Orphans ()
import Text.Pandoc.Lua.PandocLua (unPandocLua)
import Text.Pandoc.MIME (MimeType)
import Text.Pandoc.SelfContained (makeDataURI)
import qualified Data.ByteString.Lazy as BL
import qualified Data.Text as T
import qualified HsLua as Lua
import qualified Text.Pandoc.MediaBag as MB
--
-- MediaBag submodule
--
documentedModule :: Module PandocError
documentedModule = Module
{ moduleName = "pandoc.mediabag"
, moduleDescription = T.unlines
[ "The `pandoc.mediabag` module allows accessing pandoc's media"
, "storage. The \"media bag\" is used when pandoc is called with the"
, "`--extract-media` or (for HTML only) `--embed-resources` option."
, ""
, "The module is loaded as part of module `pandoc` and can either"
, "be accessed via the `pandoc.mediabag` field, or explicitly"
, "required, e.g.:"
, ""
, " local mb = require 'pandoc.mediabag'"
]
, moduleFields = []
, moduleFunctions =
[ delete `since` makeVersion [2,7,3]
, empty `since` makeVersion [2,7,3]
, fetch `since` makeVersion [2,0]
, fill `since` makeVersion [2,19]
, insert `since` makeVersion [2,0]
, items `since` makeVersion [2,7,3]
, list `since` makeVersion [2,0]
, lookup `since` makeVersion [2,0]
, make_data_uri `since` makeVersion [3,7,1]
, write `since` makeVersion [3,0]
]
, moduleOperations = []
, moduleTypeInitializers = []
}
-- | Delete a single item from the media bag.
delete :: DocumentedFunction PandocError
delete = defun "delete"
### (\fp -> unPandocLua $ do
mb <- getMediaBag
setMediaBag $ MB.deleteMedia fp mb)
<#> stringParam "filepath"
("Filename of the item to deleted. The media bag will be " <>
"left unchanged if no entry with the given filename exists.")
=#> []
#? "Removes a single entry from the media bag."
-- | Delete all items from the media bag.
empty :: DocumentedFunction PandocError
empty = defun "empty"
### unPandocLua (setMediaBag mempty)
=#> []
#? "Clear-out the media bag, deleting all items."
-- | Fill the mediabag with all images in the document that aren't
-- present yet.
fill :: DocumentedFunction PandocError
fill = defun "fill"
### unPandocLua . fillMediaBag
<#> parameter peekPandoc "Pandoc" "doc"
"document from which to fill the mediabag"
=#> functionResult pushPandoc "Pandoc" "modified document"
#? ("Fills the mediabag with the images in the given document.\n" <>
"An image that cannot be retrieved will be replaced with a Span\n" <>
"of class \"image\" that contains the image description.\n" <>
"\n" <>
"Images for which the mediabag already contains an item will\n" <>
"not be processed again.")
-- | Insert a new item into the media bag.
insert :: DocumentedFunction PandocError
insert = defun "insert"
### (\fp mmime contents -> unPandocLua $ do
mb <- getMediaBag
setMediaBag $ MB.insertMedia fp mmime contents mb
return (Lua.NumResults 0))
<#> stringParam "filepath" "filename and path relative to the output folder."
<#> opt (textParam "mimetype"
"the item's MIME type; omit if unknown or unavailable.")
<#> parameter Lua.peekLazyByteString "string" "contents"
"the binary contents of the file."
=#> []
#? T.unlines
[ "Adds a new entry to pandoc's media bag. Replaces any existing"
, "media bag entry the same `filepath`."
, ""
, "Usage:"
, ""
, " local fp = 'media/hello.txt'"
, " local mt = 'text/plain'"
, " local contents = 'Hello, World!'"
, " pandoc.mediabag.insert(fp, mt, contents)"
]
-- | Returns iterator values to be used with a Lua @for@ loop.
items :: DocumentedFunction PandocError
items = defun "items"
### (do
mb <- unPandocLua getMediaBag
let pushItem (fp, mimetype, contents) = do
Lua.pushString fp
Lua.pushText mimetype
Lua.pushByteString $ BL.toStrict contents
return (Lua.NumResults 3)
Lua.pushIterator pushItem (MB.mediaItems mb))
=?> T.unlines
[ "Iterator triple:"
, ""
, "- The iterator function; must be called with the iterator"
, " state and the current iterator value."
, "- Iterator state -- an opaque value to be passed to the"
, " iterator function."
, "- Initial iterator value."
]
#? T.unlines
[ "Returns an iterator triple to be used with Lua's generic `for`"
, "statement. The iterator returns the filepath, MIME type, and"
, "content of a media bag item on each invocation. Items are"
, "processed one-by-one to avoid excessive memory use."
, ""
, "This function should be used only when full access to all items,"
, "including their contents, is required. For all other cases,"
, "[`list`](#pandoc.mediabag.list) should be preferred."
, ""
, "Usage:"
, ""
, " for fp, mt, contents in pandoc.mediabag.items() do"
, " -- print(fp, mt, contents)"
, " end"
]
-- | Function to lookup a value in the mediabag.
lookup :: DocumentedFunction PandocError
lookup = defun "lookup"
### (\fp -> unPandocLua (MB.lookupMedia fp <$> getMediaBag))
<#> stringParam "filepath" "name of the file to look up."
=#> mconcat
[ functionResult
(maybe Lua.pushnil (Lua.pushText . MB.mediaMimeType))
"string"
"The entry's MIME type, or nil if the file was not found."
, functionResult
(maybe Lua.pushnil (Lua.pushLazyByteString . MB.mediaContents))
"string"
"Contents of the file, or nil if the file was not found."
]
#? T.unlines
[ "Lookup a media item in the media bag, and return its MIME type"
, "and contents."
, ""
, "Usage:"
, ""
, " local filename = 'media/diagram.png'"
, " local mt, contents = pandoc.mediabag.lookup(filename)"
]
-- | Function listing all mediabag items.
list :: DocumentedFunction PandocError
list = defun "list"
### (unPandocLua (MB.mediaDirectory <$> getMediaBag))
=#> functionResult (pushPandocList pushEntry) "table"
("A list of elements summarizing each entry in the media\n" <>
"bag. The summary item contains the keys `path`, `type`, and\n" <>
"`length`, giving the filepath, MIME type, and length of\n" <>
"contents in bytes, respectively.")
#? T.unlines
[ "Get a summary of the current media bag contents."
, ""
, "Usage:"
, ""
, " -- calculate the size of the media bag."
, " local mb_items = pandoc.mediabag.list()"
, " local sum = 0"
, " for i = 1, #mb_items do"
, " sum = sum + mb_items[i].length"
, " end"
, " print(sum)"
]
where
pushEntry :: (FilePath, MimeType, Int) -> LuaE PandocError ()
pushEntry (fp, mimeType, contentLength) = do
Lua.newtable
Lua.pushName "path" *> Lua.pushString fp *> Lua.rawset (-3)
Lua.pushName "type" *> Lua.pushText mimeType *> Lua.rawset (-3)
Lua.pushName "length" *> Lua.pushIntegral contentLength *> Lua.rawset (-3)
-- | Lua function to retrieve a new item.
fetch :: DocumentedFunction PandocError
fetch = defun "fetch"
### (unPandocLua . fetchItem)
<#> textParam "source" "path to a resource; either a local file path or URI"
=#> ( functionResult (Lua.pushText . fromMaybe "" . snd) "string"
"The entry's MIME type, or `nil` if the file was not found."
<>
functionResult (Lua.pushByteString . fst) "string"
"Contents of the file, or `nil` if the file was not found."
)
#? T.unlines
[ "Fetches the given source from a URL or local file. Returns two"
, "values: the contents of the file and the MIME type (or an empty"
, "string)."
, ""
, "The function will first try to retrieve `source` from the"
, "mediabag; if that fails, it will try to download it or read it"
, "from the local file system while respecting pandoc's \"resource"
, "path\" setting."
, ""
, "Usage:"
, ""
, " local diagram_url = 'https://pandoc.org/diagram.jpg'"
, " local mt, contents = pandoc.mediabag.fetch(diagram_url)"
]
make_data_uri :: DocumentedFunction PandocError
make_data_uri = defun "make_data_uri"
### (\mime raw -> pure $ makeDataURI (mime, raw))
<#> parameter Lua.peekText "string" "mime_type" "MIME type of the data"
<#> parameter Lua.peekByteString "string" "raw_data" "data to encode"
=#> functionResult Lua.pushText "string" "data uri"
#? T.unlines
[ "Convert the input data into a data URI as defined by RFC 2397."
, ""
, "Example:"
, ""
, " -- Embed an unofficial pandoc logo"
, " local pandoc_logo_url = 'https://raw.githubusercontent.com/'"
, " .. 'tarleb/pandoc-logo/main/pandoc.svg'"
, ""
, " local datauri = pandoc.mediabag.make_data_uri("
, " pandoc.mediabag.fetch(pandoc_logo_url)"
, " )"
, ""
, " local image = pandoc.Image('Logo', datauri)"
]
-- | Extract the mediabag or just a single entry.
write :: DocumentedFunction PandocError
write = defun "write"
### (\dir mfp -> do
mb <- unPandocLua getMediaBag
case mfp of
Nothing -> unPandocLua $ mapM_ (writeMedia dir) (MB.mediaItems mb)
Just fp -> do
case MB.lookupMedia fp mb of
Nothing -> Lua.failLua ("Resource not in mediabag: " <> fp)
Just item -> unPandocLua $ do
let triple = ( MB.mediaPath item
, MB.mediaMimeType item
, MB.mediaContents item
)
writeMedia dir triple)
<#> stringParam "dir" "path of the target directory"
<#> opt (stringParam "fp" "canonical name (relative path) of resource")
=#> []
#? T.unlines
[ "Writes the contents of mediabag to the given target directory. If"
, "`fp` is given, then only the resource with the given name will be"
, "extracted. Omitting that parameter means that the whole mediabag"
, "gets extracted. An error is thrown if `fp` is given but cannot be"
, "found in the mediabag."
]
`since` makeVersion [3, 0]
|