{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE ScopedTypeVariables #-}
{- |
   Module      : Text.Pandoc.Lua.Packages
   Copyright   : Copyright © 2017-2019 Albert Krewinkel
   License     : GNU GPL, version 2 or above

   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
   Stability   : alpha

Pandoc module for lua.
-}
module Text.Pandoc.Lua.Packages
  ( LuaPackageParams (..)
  , installPandocPackageSearcher
  ) where

import Prelude
import Control.Monad (forM_)
import Data.ByteString (ByteString)
import Foreign.Lua (Lua, NumResults, liftIO)
import Text.Pandoc.Class (readDataFile, runIO, setUserDataDir)

import qualified Foreign.Lua as Lua
import Text.Pandoc.Lua.Module.Pandoc as Pandoc
import Text.Pandoc.Lua.Module.MediaBag as MediaBag
import Text.Pandoc.Lua.Module.System as System
import Text.Pandoc.Lua.Module.Types as Types
import Text.Pandoc.Lua.Module.Utils as Utils

-- | Parameters used to create lua packages/modules.
data LuaPackageParams = LuaPackageParams
  { LuaPackageParams -> Maybe FilePath
luaPkgDataDir :: Maybe FilePath
  }

-- | Insert pandoc's package loader as the first loader, making it the default.
installPandocPackageSearcher :: LuaPackageParams -> Lua ()
installPandocPackageSearcher :: LuaPackageParams -> Lua ()
installPandocPackageSearcher luaPkgParams :: LuaPackageParams
luaPkgParams = do
  FilePath -> Lua ()
Lua.getglobal' "package.searchers"
  Lua ()
shiftArray
  (FilePath -> Lua NumResults) -> Lua ()
forall a. ToHaskellFunction a => a -> Lua ()
Lua.pushHaskellFunction (LuaPackageParams -> FilePath -> Lua NumResults
pandocPackageSearcher LuaPackageParams
luaPkgParams)
  StackIndex -> Integer -> Lua ()
Lua.rawseti (CInt -> StackIndex
Lua.nthFromTop 2) 1
  StackIndex -> Lua ()
Lua.pop 1           -- remove 'package.searchers' from stack
 where
  shiftArray :: Lua ()
shiftArray = [Integer] -> (Integer -> Lua ()) -> Lua ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [4, 3, 2, 1] ((Integer -> Lua ()) -> Lua ()) -> (Integer -> Lua ()) -> Lua ()
forall a b. (a -> b) -> a -> b
$ \i :: Integer
i -> do
    StackIndex -> Integer -> Lua ()
Lua.rawgeti (-1) Integer
i
    StackIndex -> Integer -> Lua ()
Lua.rawseti (-2) (Integer
i Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ 1)

-- | Load a pandoc module.
pandocPackageSearcher :: LuaPackageParams -> String -> Lua NumResults
pandocPackageSearcher :: LuaPackageParams -> FilePath -> Lua NumResults
pandocPackageSearcher pkgParams :: LuaPackageParams
pkgParams pkgName :: FilePath
pkgName =
  case FilePath
pkgName of
    "pandoc"          -> let datadir :: Maybe FilePath
datadir = LuaPackageParams -> Maybe FilePath
luaPkgDataDir LuaPackageParams
pkgParams
                         in Lua NumResults -> Lua NumResults
forall a b. (ToHaskellFunction a, Num b) => a -> Lua b
pushWrappedHsFun (Maybe FilePath -> Lua NumResults
Pandoc.pushModule Maybe FilePath
datadir)
    "pandoc.mediabag" -> Lua NumResults -> Lua NumResults
forall a b. (ToHaskellFunction a, Num b) => a -> Lua b
pushWrappedHsFun Lua NumResults
MediaBag.pushModule
    "pandoc.system"   -> Lua NumResults -> Lua NumResults
forall a b. (ToHaskellFunction a, Num b) => a -> Lua b
pushWrappedHsFun Lua NumResults
System.pushModule
    "pandoc.types"    -> Lua NumResults -> Lua NumResults
forall a b. (ToHaskellFunction a, Num b) => a -> Lua b
pushWrappedHsFun Lua NumResults
Types.pushModule
    "pandoc.utils"    -> let datadir :: Maybe FilePath
datadir = LuaPackageParams -> Maybe FilePath
luaPkgDataDir LuaPackageParams
pkgParams
                         in Lua NumResults -> Lua NumResults
forall a b. (ToHaskellFunction a, Num b) => a -> Lua b
pushWrappedHsFun (Maybe FilePath -> Lua NumResults
Utils.pushModule Maybe FilePath
datadir)
    _ -> Lua NumResults
searchPureLuaLoader
 where
  pushWrappedHsFun :: a -> Lua b
pushWrappedHsFun f :: a
f = do
    a -> Lua ()
forall a. ToHaskellFunction a => a -> Lua ()
Lua.pushHaskellFunction a
f
    b -> Lua b
forall (m :: * -> *) a. Monad m => a -> m a
return 1
  searchPureLuaLoader :: Lua NumResults
searchPureLuaLoader = do
    let filename :: FilePath
filename = FilePath
pkgName FilePath -> FilePath -> FilePath
forall a. [a] -> [a] -> [a]
++ ".lua"
    Maybe ByteString
modScript <- IO (Maybe ByteString) -> Lua (Maybe ByteString)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (Maybe FilePath -> FilePath -> IO (Maybe ByteString)
dataDirScript (LuaPackageParams -> Maybe FilePath
luaPkgDataDir LuaPackageParams
pkgParams) FilePath
filename)
    case Maybe ByteString
modScript of
      Just script :: ByteString
script -> Lua NumResults -> Lua NumResults
forall a b. (ToHaskellFunction a, Num b) => a -> Lua b
pushWrappedHsFun (FilePath -> ByteString -> Lua NumResults
loadStringAsPackage FilePath
pkgName ByteString
script)
      Nothing -> do
        FilePath -> Lua ()
forall a. Pushable a => a -> Lua ()
Lua.push ("\n\tno file '" FilePath -> FilePath -> FilePath
forall a. [a] -> [a] -> [a]
++ FilePath
filename FilePath -> FilePath -> FilePath
forall a. [a] -> [a] -> [a]
++ "' in pandoc's datadir")
        NumResults -> Lua NumResults
forall (m :: * -> *) a. Monad m => a -> m a
return 1

loadStringAsPackage :: String -> ByteString -> Lua NumResults
loadStringAsPackage :: FilePath -> ByteString -> Lua NumResults
loadStringAsPackage pkgName :: FilePath
pkgName script :: ByteString
script = do
  Status
status <- ByteString -> Lua Status
Lua.dostring ByteString
script
  if Status
status Status -> Status -> Bool
forall a. Eq a => a -> a -> Bool
== Status
Lua.OK
    then NumResults -> Lua NumResults
forall (m :: * -> *) a. Monad m => a -> m a
return (1 :: NumResults)
    else do
      FilePath
msg <- Lua FilePath
forall a. Peekable a => Lua a
Lua.popValue
      FilePath -> Lua NumResults
forall a. Pushable a => a -> Lua NumResults
Lua.raiseError ("Error while loading `" FilePath -> FilePath -> FilePath
forall a. Semigroup a => a -> a -> a
<> FilePath
pkgName FilePath -> FilePath -> FilePath
forall a. Semigroup a => a -> a -> a
<> "`.\n" FilePath -> FilePath -> FilePath
forall a. Semigroup a => a -> a -> a
<> FilePath
msg)

-- | Get the ByteString representation of the pandoc module.
dataDirScript :: Maybe FilePath -> FilePath -> IO (Maybe ByteString)
dataDirScript :: Maybe FilePath -> FilePath -> IO (Maybe ByteString)
dataDirScript datadir :: Maybe FilePath
datadir moduleFile :: FilePath
moduleFile = do
  Either PandocError ByteString
res <- PandocIO ByteString -> IO (Either PandocError ByteString)
forall a. PandocIO a -> IO (Either PandocError a)
runIO (PandocIO ByteString -> IO (Either PandocError ByteString))
-> PandocIO ByteString -> IO (Either PandocError ByteString)
forall a b. (a -> b) -> a -> b
$ Maybe FilePath -> PandocIO ()
forall (m :: * -> *). PandocMonad m => Maybe FilePath -> m ()
setUserDataDir Maybe FilePath
datadir PandocIO () -> PandocIO ByteString -> PandocIO ByteString
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> FilePath -> PandocIO ByteString
forall (m :: * -> *). PandocMonad m => FilePath -> m ByteString
readDataFile FilePath
moduleFile
  Maybe ByteString -> IO (Maybe ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe ByteString -> IO (Maybe ByteString))
-> Maybe ByteString -> IO (Maybe ByteString)
forall a b. (a -> b) -> a -> b
$ case Either PandocError ByteString
res of
    Left _ -> Maybe ByteString
forall a. Maybe a
Nothing
    Right s :: ByteString
s -> ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just ByteString
s