mirror of
https://github.com/jgm/pandoc.git
synced 2026-09-23 16:05:54 +00:00
wasm: change interface.
We now provide two functions: convert and query. extensions_for_format has been dropped, as query can do the work. convert is what pandoc used to be. query takes a JSON object describing the information needed, and returns information. Currently only "extensions-for-format" is implemented. Also: the use of multiple main-is causes problems with cabal. Instead, we use pandoc.hs as main throughout, but change the hs-src-dir for wasm.
This commit is contained in:
@@ -71,21 +71,20 @@ common common-executable
|
||||
|
||||
executable pandoc
|
||||
import: common-executable
|
||||
hs-source-dirs: src
|
||||
main-is: pandoc.hs
|
||||
buildable: True
|
||||
-- Note: we always link to an exact version of pandoc, with the
|
||||
-- same version as this package:
|
||||
build-depends: pandoc == 3.8.3
|
||||
build-depends: pandoc == 3.8.3, text
|
||||
other-modules: PandocCLI.Lua
|
||||
, PandocCLI.Server
|
||||
|
||||
if arch(wasm32)
|
||||
main-is: pandoc-wasm.hs
|
||||
hs-source-dirs: wasm
|
||||
build-depends: aeson, containers, bytestring
|
||||
ghc-options: -optl-Wl,--export=__wasm_call_ctors,--export=hs_init_with_rtsopts,--export=malloc,--export=convert,--export=get_extensions_for_format
|
||||
ghc-options: -optl-Wl,--export=__wasm_call_ctors,--export=hs_init_with_rtsopts,--export=malloc,--export=convert,--export=query
|
||||
else
|
||||
main-is: pandoc.hs
|
||||
build-depends: text
|
||||
hs-source-dirs: src
|
||||
|
||||
if flag(nightly)
|
||||
cpp-options: -DNIGHTLY
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
{- |
|
||||
Module : Main
|
||||
@@ -25,13 +26,14 @@ import PandocCLI.Lua
|
||||
import Control.Exception
|
||||
import Foreign
|
||||
import Foreign.C
|
||||
import qualified Data.Aeson as Aeson
|
||||
import Data.Aeson as Aeson
|
||||
import qualified Text.Pandoc.UTF8 as UTF8
|
||||
import qualified Data.ByteString.Lazy as BL
|
||||
import qualified Data.Text as T
|
||||
|
||||
foreign export ccall "convert" convert :: Ptr CChar -> Int -> IO ()
|
||||
|
||||
-- | The parameters are a pointer and length for a C string
|
||||
-- | The parameters are a pointer and length for a C string
|
||||
-- containing JSON-encoded pandoc options (isomorphic to a defaults
|
||||
-- file). The calling program should set up a virtual file system
|
||||
-- containing @/stdin@ (input), @/stdout@ (output), and @/warnings@ (output).
|
||||
@@ -59,22 +61,41 @@ convert ptr len =
|
||||
}
|
||||
convertWithOpts engine opts'
|
||||
|
||||
foreign export ccall "get_extensions_for_format" get_extensions_for_format
|
||||
:: Ptr CChar -> Int -> IO ()
|
||||
foreign export ccall "query" query :: Ptr CChar -> Int -> IO ()
|
||||
|
||||
-- | Given a C string with the format name, returnsa JSON object
|
||||
-- mapping extensions relevant for this format to true or false,
|
||||
-- depending on whether they are enabled by default.
|
||||
get_extensions_for_format :: Ptr CChar -> Int -> IO ()
|
||||
get_extensions_for_format ptr len = do
|
||||
formatName <- readMaybe <$> getCString ptr len
|
||||
case formatName of
|
||||
Just fmt -> do
|
||||
let allExts = getAllExtensions fmt
|
||||
let defExts = getDefaultExtensions fmt
|
||||
let addExt x = M.insert (drop 4 (show x)) (extensionEnabled x defExts)
|
||||
BL.writeFile "/stdout" $ Aeson.encode $ foldr addExt mempty (extensionsToList allExts)
|
||||
Nothing -> writeFile "/stdout" "{}"
|
||||
-- | The parameters are a pointer and length for a C string
|
||||
-- containing a JSON-encoded query, e.g.,
|
||||
-- @{ "query": "extensions-for-format", "argument": "markdown" }@.
|
||||
-- The calling program should set up a virtual file system
|
||||
-- containing @/stdout@, which will be used to hold the output,
|
||||
-- and @/stderr@, which will be used to hold any error messages.
|
||||
query :: Ptr CChar -> Int -> IO ()
|
||||
query ptr len =
|
||||
E.catch act (\(err :: SomeException) ->
|
||||
writeFile "/stderr" ("ERROR: " <> displayException err))
|
||||
where
|
||||
act = do
|
||||
args <- getCString ptr len
|
||||
case Aeson.eitherDecode (UTF8.fromStringLazy args) of
|
||||
Left e -> error e
|
||||
Right (ExtensionsForFormat fmt) -> do
|
||||
let allExts = getAllExtensions fmt
|
||||
let defExts = getDefaultExtensions fmt
|
||||
let addExt x = M.insert (drop 4 (show x))
|
||||
(extensionEnabled x defExts)
|
||||
BL.writeFile "/stdout" $ Aeson.encode $
|
||||
foldr addExt mempty (extensionsToList allExts)
|
||||
|
||||
data Query =
|
||||
ExtensionsForFormat T.Text
|
||||
deriving (Show)
|
||||
|
||||
instance FromJSON Query where
|
||||
parseJSON = withObject "Query" $ \o -> do
|
||||
queryType <- o .: "query"
|
||||
case queryType of
|
||||
"extensions-for-format" -> ExtensionsForFormat <$> o .: "format"
|
||||
_ -> fail $ "Unknown query type " <> queryType
|
||||
|
||||
getCString :: Ptr CChar -> Int -> IO String
|
||||
getCString ptr len = peekCStringLen (ptr, len) <* free ptr
|
||||
+4
-4
@@ -1,11 +1,11 @@
|
||||
import { zipSync, unzipSync, strToU8, strFromU8 } from 'https://esm.sh/fflate@0.8.2';
|
||||
import { convert, getExtensionsForFormat } from "./pandoc.js?sha1=SHA1_PANDOC_JS";
|
||||
import { convert, query } from "./pandoc.js?sha1=SHA1_PANDOC_JS";
|
||||
|
||||
// Make fflate available globally
|
||||
window.fflate = { zipSync, unzipSync, strToU8, strFromU8 };
|
||||
|
||||
// Make pandoc available globally for the app
|
||||
window.pandocModule = { convert, getExtensionsForFormat };
|
||||
window.pandocModule = { convert, query };
|
||||
|
||||
// Lazy-load typst library only when needed
|
||||
let typstLoaded = false;
|
||||
@@ -1014,7 +1014,7 @@ window.pandocApp = function() {
|
||||
|
||||
if (this.inputFormat !== 'auto') {
|
||||
try {
|
||||
const extData = await window.pandocModule.getExtensionsForFormat(this.inputFormat);
|
||||
const extData = await window.pandocModule.query({ query: "extensions-for-format", format: this.inputFormat });
|
||||
this.inputExtensionsList = Object.entries(extData)
|
||||
.map(([name, defaultOn]) => ({ name, defaultOn }))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
@@ -1024,7 +1024,7 @@ window.pandocApp = function() {
|
||||
const outFmt = this.outputFormat === 'pdf-typst' ? 'typst' : this.outputFormat;
|
||||
if (outFmt !== 'auto') {
|
||||
try {
|
||||
const extData = await window.pandocModule.getExtensionsForFormat(outFmt);
|
||||
const extData = await window.pandocModule.query({ query: "extensions-for-format", format: outFmt});
|
||||
this.outputExtensionsList = Object.entries(extData)
|
||||
.map(([name, defaultOn]) => ({ name, defaultOn }))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
+7
-3
@@ -70,7 +70,7 @@ memory_data_view().setUint32(argv_ptr, argv, true);
|
||||
|
||||
instance.exports.hs_init_with_rtsopts(argc_ptr, argv_ptr);
|
||||
|
||||
export async function getExtensionsForFormat(options) {
|
||||
export async function query(options) {
|
||||
const opts_str = JSON.stringify(options);
|
||||
const opts_ptr = instance.exports.malloc(opts_str.length);
|
||||
new TextEncoder().encodeInto(
|
||||
@@ -83,11 +83,15 @@ export async function getExtensionsForFormat(options) {
|
||||
const err_file = new File(new Uint8Array(), { readonly: false });
|
||||
fileSystem.set("stdout", out_file);
|
||||
fileSystem.set("stderr", err_file);
|
||||
instance.exports.get_extensions_for_format(opts_ptr, opts_str.length);
|
||||
instance.exports.query(opts_ptr, opts_str.length);
|
||||
|
||||
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(out_file.data));
|
||||
const err_text = new TextDecoder("utf-8", { fatal: true }).decode(err_file.data);
|
||||
if (err_text) console.log(err_text);
|
||||
const out_text = new TextDecoder("utf-8", { fatal: true }).decode(out_file.data);
|
||||
return JSON.parse(out_text);
|
||||
}
|
||||
|
||||
|
||||
export async function convert(options, stdin, files) {
|
||||
const opts_str = JSON.stringify(options);
|
||||
const opts_ptr = instance.exports.malloc(opts_str.length);
|
||||
|
||||
Reference in New Issue
Block a user