From 505f5bf5d951a5c4342f7acce9bea5f260dc9d78 Mon Sep 17 00:00:00 2001 From: Albert Krewinkel Date: Sun, 19 May 2019 15:26:00 +0200 Subject: Lua: add Version type to simplify comparisons Version specifiers like `PANDOC_VERSION` and `PANDOC_API_VERSION` are turned into `Version` objects. The objects simplify version-appropriate comparisons while maintaining backward-compatibility. A function `pandoc.types.Version` is added as part of the newly introduced module `pandoc.types`, allowing users to create version objects in scripts. --- src/Text/Pandoc/Lua/Marshaling/Version.hs | 154 ++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 src/Text/Pandoc/Lua/Marshaling/Version.hs (limited to 'src/Text/Pandoc/Lua/Marshaling') diff --git a/src/Text/Pandoc/Lua/Marshaling/Version.hs b/src/Text/Pandoc/Lua/Marshaling/Version.hs new file mode 100644 index 000000000..3c667cbc4 --- /dev/null +++ b/src/Text/Pandoc/Lua/Marshaling/Version.hs @@ -0,0 +1,154 @@ +{-# OPTIONS_GHC -fno-warn-orphans #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NoImplicitPrelude #-} +{- | + Module : Text.Pandoc.Lua.Marshaling.Version + Copyright : © 2019 Albert Krewinkel + License : GNU GPL, version 2 or above + + Maintainer : Albert Krewinkel + Stability : alpha + +Marshaling of @'Version'@s. The marshaled elements can be compared using +default comparison operators (like @>@ and @<=@). +-} +module Text.Pandoc.Lua.Marshaling.Version + ( peekVersion + , pushVersion + ) + where + +import Prelude +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 + +-- | Push a @'Version'@ element to the Lua stack. +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 + +-- | Name used by Lua for the @CommonState@ type. +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) + +-- | Get number of version components. +__len :: Version -> Lua Int +__len = return . length . versionBranch + +-- | Access fields. +__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 <- Lua.peek k + if str == "must_be_at_least" + then 1 <$ Lua.pushHaskellFunction must_be_at_least + else 1 <$ Lua.pushnil + _ -> 1 <$ Lua.pushnil + +-- | Create iterator. +__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) + +-- | Convert to string. +__tostring :: Version -> Lua String +__tostring v = return (showVersion v) + +-- | Default error message when a version is too old. This message is +-- formatted in Lua with the expected and actual versions as arguments. +versionTooOldMessage :: String +versionTooOldMessage = "version too old: expected version %s or newer, got %s" + +-- | Throw an error if this version is older than the given version. +-- FIXME: This function currently requires the string library to be +-- loaded. +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 + -- cgit v1.2.3