{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Text.Pandoc.Lua.Marshaling.Version
( peekVersion
, pushVersion
)
where
import Data.Text (Text)
import Data.Maybe (fromMaybe)
import Data.Version (Version (..), makeVersion, parseVersion, showVersion)
import Foreign.Lua (Lua, Optional (..), NumResults,
Peekable, Pushable, StackIndex)
import Foreign.Lua.Types.Peekable (reportValueOnFailure)
import Foreign.Lua.Userdata (ensureUserdataMetatable, pushAnyWithMetatable,
toAnyWithName)
import Safe (atMay, lastMay)
import Text.Pandoc.Lua.Marshaling.AnyValue (AnyValue (..))
import Text.ParserCombinators.ReadP (readP_to_S)
import qualified Foreign.Lua as Lua
import qualified Text.Pandoc.Lua.Util as LuaUtil
pushVersion :: Version -> Lua ()
pushVersion version = pushAnyWithMetatable pushVersionMT version
where
pushVersionMT = ensureUserdataMetatable versionTypeName $ do
LuaUtil.addFunction "__eq" __eq
LuaUtil.addFunction "__le" __le
LuaUtil.addFunction "__lt" __lt
LuaUtil.addFunction "__len" __len
LuaUtil.addFunction "__index" __index
LuaUtil.addFunction "__pairs" __pairs
LuaUtil.addFunction "__tostring" __tostring
instance Pushable Version where
push = pushVersion
peekVersion :: StackIndex -> Lua Version
peekVersion idx = Lua.ltype idx >>= \case
Lua.TypeString -> do
versionStr <- Lua.peek idx
let parses = readP_to_S parseVersion versionStr
case lastMay parses of
Just (v, "") -> return v
_ -> Lua.throwException $ "could not parse as Version: " ++ versionStr
Lua.TypeUserdata ->
reportValueOnFailure versionTypeName
(`toAnyWithName` versionTypeName)
idx
Lua.TypeNumber -> do
n <- Lua.peek idx
return (makeVersion [n])
Lua.TypeTable ->
makeVersion <$> Lua.peek idx
_ ->
Lua.throwException "could not peek Version"
instance Peekable Version where
peek = peekVersion
versionTypeName :: String
versionTypeName = "HsLua Version"
__eq :: Version -> Version -> Lua Bool
__eq v1 v2 = return (v1 == v2)
__le :: Version -> Version -> Lua Bool
__le v1 v2 = return (v1 <= v2)
__lt :: Version -> Version -> Lua Bool
__lt v1 v2 = return (v1 < v2)
__len :: Version -> Lua Int
__len = return . length . versionBranch
__index :: Version -> AnyValue -> Lua NumResults
__index v (AnyValue k) = do
ty <- Lua.ltype k
case ty of
Lua.TypeNumber -> do
n <- Lua.peek k
let versionPart = atMay (versionBranch v) (n - 1)
Lua.push (Lua.Optional versionPart)
return 1
Lua.TypeString -> do
(str :: Text) <- Lua.peek k
if str == "must_be_at_least"
then 1 <$ Lua.pushHaskellFunction must_be_at_least
else 1 <$ Lua.pushnil
_ -> 1 <$ Lua.pushnil
__pairs :: Version -> Lua NumResults
__pairs v = do
Lua.pushHaskellFunction nextFn
Lua.pushnil
Lua.pushnil
return 3
where
nextFn :: AnyValue -> Optional Int -> Lua Lua.NumResults
nextFn _ (Optional key) =
case key of
Nothing -> case versionBranch v of
[] -> 2 <$ (Lua.pushnil *> Lua.pushnil)
n:_ -> 2 <$ (Lua.push (1 :: Int) *> Lua.push n)
Just n -> case atMay (versionBranch v) n of
Nothing -> 2 <$ (Lua.pushnil *> Lua.pushnil)
Just b -> 2 <$ (Lua.push (n + 1) *> Lua.push b)
__tostring :: Version -> Lua String
__tostring v = return (showVersion v)
versionTooOldMessage :: String
versionTooOldMessage = "expected version %s or newer, got %s"
must_be_at_least :: Version -> Version -> Optional String -> Lua NumResults
must_be_at_least actual expected optMsg = do
let msg = fromMaybe versionTooOldMessage (fromOptional optMsg)
if expected <= actual
then return 0
else do
Lua.getglobal' "string.format"
Lua.push msg
Lua.push (showVersion expected)
Lua.push (showVersion actual)
Lua.call 3 1
Lua.error