hslua-module-version (empty) → 1.0.0
raw patch · 7 files changed
+435/−0 lines, 7 filesdep +basedep +filepathdep +hslua
Dependencies added: base, filepath, hslua, hslua-marshalling, hslua-module-version, hslua-packaging, tasty, tasty-hunit, tasty-lua, text
Files
- CHANGELOG.md +7/−0
- LICENSE +23/−0
- README.md +15/−0
- hslua-module-version.cabal +72/−0
- src/HsLua/Module/Version.hs +155/−0
- test/test-hslua-module-version.hs +53/−0
- test/test-version.lua +110/−0
+ CHANGELOG.md view
@@ -0,0 +1,7 @@+# Changelog++`hslua-module-version` uses [PVP Versioning](https://pvp.haskell.org).++## 0.0.1++Release pending
+ LICENSE view
@@ -0,0 +1,23 @@+Copyright © 1994-2020 Lua.org, PUC-Rio.+Copyright © 2007-2012 Gracjan Polak+Copyright © 2012-2015 Ömer Sinan Ağacan+Copyright © 2016-2021 Albert Krewinkel++Permission is hereby granted, free of charge, to any person obtaining a+copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,15 @@+# hslua-module-version++[![GitHub CI][CI badge]](https://github.com/hslua/hslua-module-version/actions)+[![Hackage][Hackage badge]](https://hackage.haskell.org/package/hslua-module-version)+[![Stackage Lts][Stackage Lts badge]](http://stackage.org/lts/package/hslua-module-version)+[![Stackage Nightly][Stackage Nightly badge]](http://stackage.org/nightly/package/hslua-module-version)+[![MIT license][License badge]](LICENSE)++[CI badge]: https://github.com/hslua/hslua-module-version/workflows/CI/badge.svg+[Hackage badge]: https://img.shields.io/hackage/v/hslua-module-version.svg?logo=haskell+[Stackage Lts badge]: http://stackage.org/package/hslua-module-version/badge/lts+[Stackage Nightly badge]: http://stackage.org/package/hslua-module-version/badge/nightly+[License badge]: https://img.shields.io/badge/license-MIT-blue.svg++Lua module to work with version specifiers.
+ hslua-module-version.cabal view
@@ -0,0 +1,72 @@+cabal-version: 2.2+name: hslua-module-version+version: 1.0.0+synopsis: Lua module to work with version specifiers.+description: Wrapper for the Data.Version.Version Haskell type.+homepage: https://github.com/hslua/hslua-module-version+bug-reports: https://github.com/hslua/hslua-module-version/issues+license: MIT+license-file: LICENSE+author: Albert Krewinkel+maintainer: Albert Krewinkel <albert@zeitkraut.de>+copyright: © 2019-2021 Albert Krewinkel+category: Foreign+build-type: Simple+extra-doc-files: README.md+ CHANGELOG.md+extra-source-files: test/test-version.lua+tested-with: GHC == 8.0.2+ , GHC == 8.2.2+ , GHC == 8.4.4+ , GHC == 8.6.5+ , GHC == 8.8.4+ , GHC == 8.10.4+ , GHC == 9.0.1++source-repository head+ type: git+ location: https://github.com/hslua/hslua-module-version.git++common common-options+ build-depends: base >= 4.9.1 && < 5+ , filepath >= 1.4 && < 1.5+ , hslua >= 2.0 && < 2.1+ , hslua-marshalling >= 2.0 && < 2.1+ , hslua-packaging >= 2.0 && < 2.1+ , text >= 1.0 && < 1.3++ ghc-options: -Wall+ -Wcompat+ -Widentities+ -Wincomplete-uni-patterns+ -Wincomplete-record-updates+ if impl(ghc >= 8.0)+ ghc-options: -Wredundant-constraints+ if impl(ghc >= 8.2)+ ghc-options: -fhide-source-paths+ if impl(ghc >= 8.4)+ ghc-options: -Wmissing-export-lists+ -Wpartial-fields+ if impl(ghc >= 8.8)+ ghc-options: -Wmissing-deriving-strategies++ default-language: Haskell2010++library+ import: common-options+ hs-source-dirs: src+ exposed-modules: HsLua.Module.Version++test-suite hslua-module-version-test+ import: common-options+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: test-hslua-module-version.hs+ build-depends: base+ , hslua-module-version+ , tasty+ , tasty-hunit+ , tasty-lua >= 1.0 && < 1.1+ ghc-options: -threaded+ -rtsopts+ -with-rtsopts=-N
+ src/HsLua/Module/Version.hs view
@@ -0,0 +1,155 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-|+Module : HsLua.Module.Version+Copyright : © 2019-2021 Albert Krewinkel+License : MIT+Maintainer : Albert Krewinkel <albert+hslua@zeitkraut.de>++Lua module to work with file paths.+-}+module HsLua.Module.Version (+ -- * Module+ documentedModule+ -- * Version objects+ , typeVersion+ , peekVersion+ , pushVersion+ , peekVersionFuzzy+ )+where++import Prelude hiding (error)+import Data.Maybe (fromMaybe)+import Data.Version+ ( Version, makeVersion, parseVersion, showVersion, versionBranch )+import Data.List.NonEmpty as NonEmpty (last, nonEmpty)+#if !MIN_VERSION_base(4,12,0)+import Data.Semigroup (Semigroup ((<>)))+#endif+import Data.Text (Text)+import HsLua.Core+ ( LuaError, Type (..) , call, dostring, error, ltype )+import HsLua.Marshalling+ ( Peeker, Pusher, failPeek, liftLua, peekIntegral, peekList, peekString+ , pushBool, pushIntegral, pushIterator, pushString, retrieving )+import HsLua.Packaging+import Text.ParserCombinators.ReadP (readP_to_S)++import qualified HsLua.Core.Utf8 as UTF8++-- | The @path@ module specification.+documentedModule :: LuaError e => Module e+documentedModule = Module+ { moduleName = "Version"+ , moduleDescription = "Version specifier handling"+ , moduleFields = []+ , moduleFunctions = [must_be_at_least]+ , moduleOperations =+ [ operation Call $ lambda+ ### liftPure2 (\_ v -> v)+ <#> parameter (const $ pure ()) "table" "module table" "ignored"+ <#> versionParam "version" "version-like object"+ =#> functionResult (pushUD typeVersion) "Version" "new Version object"+ ]+ }++-- | Type definition of Lua Version values.+typeVersion :: LuaError e => DocumentedTypeWithList e Version Int+typeVersion = deftype' "Version"+ [ operation Eq $ versionComparison (==) "true iff v1 == v2"+ , operation Le $ versionComparison (<=) "true iff v1 <= v2"+ , operation Lt $ versionComparison (<) "true iff v1 < v2"+ , operation Len $ lambda+ ### liftPure (length . versionBranch)+ <#> versionParam "version" ""+ =#> functionResult pushIntegral "integer" "number of version components"+ , operation Pairs $ lambda+ ### pushIterator (\(i, n) -> 2 <$ pushIntegral i <* pushIntegral n)+ . zip [(1 :: Int) ..] . versionBranch+ <#> versionParam "version" ""+ =?> "iterator values"+ , operation Tostring $ lambda+ ### liftPure showVersion+ <#> versionParam "version" ""+ =#> functionResult pushString "string" "stringified version"+ ]+ [ method must_be_at_least ]+ (Just (versionBranch, pushIntegral))+ where+ versionComparison f descr = lambda+ ### liftPure2 f+ <#> versionParam "v1" ""+ <#> versionParam "v2" ""+ =#> functionResult pushBool "boolean" descr++-- | Push a @'Version'@ element to the Lua stack.+pushVersion :: LuaError e => Pusher e Version+pushVersion = pushUD typeVersion++-- | Retrieve a @'Version'@ object from the top of the stack.+peekVersion :: LuaError e => Peeker e Version+peekVersion = peekUD typeVersion++-- | Retrieve a Version-like object from the top of the stack.+--+-- This function uses these heuristics, depending on the Lua object+-- type.+--+-- * string: object is parsed as a version specifier.+--+-- * table: value is expected to be a list of integers, with each+-- index specifying a version branch.+--+-- * userdata: assumes the value to be a Version userdata object.+--+-- * number: parses the number as an integer value.+--+-- Otherwise, or if the object fails to meet an expectation, peeking+-- fails.+peekVersionFuzzy :: LuaError e => Peeker e Version+peekVersionFuzzy idx = retrieving "Version" $ liftLua (ltype idx) >>= \case+ TypeUserdata -> peekVersion idx+ TypeString -> do+ versionStr <- peekString idx+ let parses = readP_to_S parseVersion versionStr+ case NonEmpty.last <$> NonEmpty.nonEmpty parses of+ Just (v, "") -> return v+ _ -> failPeek $+ "could not parse as Version: " <> UTF8.fromString versionStr+ TypeNumber -> makeVersion . (:[]) <$> peekIntegral idx+ TypeTable -> makeVersion <$> peekList peekIntegral idx+ _ ->+ failPeek "could not peek Version"++-- | Parameter that takes a Version-like object.+versionParam :: LuaError e => Text -> Text -> Parameter e Version+versionParam = parameter peekVersionFuzzy "Version"++-- | Throw an error if this version is older than the given version. This+-- function currently the string library to be loaded.+must_be_at_least :: LuaError e => DocumentedFunction e+must_be_at_least =+ defun "must_be_at_least"+ ### (\actual expected mMsg -> do+ -- Default error message when a version is too old. This+ -- message is formatted in Lua with the expected and actual+ -- versions as arguments.+ let versionTooOldMessage = "expected version %s or newer, got %s"+ let msg = fromMaybe versionTooOldMessage mMsg+ if expected <= actual+ then return 0+ else do+ _ <- dostring "return string.format"+ pushString msg+ pushString (showVersion expected)+ pushString (showVersion actual)+ call 3 1+ error)+ <#> versionParam "self" "version to check"+ <#> versionParam "reference" "minimum version"+ <#> optionalParameter peekString "string" "msg" "alternative message"+ =?> mconcat [ "Returns no result, and throws an error if this "+ , "version is older than `reference`."+ ]
+ test/test-hslua-module-version.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}+{-|+Module : Main+Copyright : © 2021 Albert Krewinkel+License : MIT+Maintainer : Albert Krewinkel <albert+hslua@zeitkraut.de>++Tests for the `version` Lua module.+-}+module Main (main) where++import Control.Monad (void)+import HsLua (Lua)+import HsLua.Packaging.Module (preloadModule, preloadModuleWithName, pushModule)+import HsLua.Module.Version (documentedModule)+import Test.Tasty (TestTree, defaultMain, testGroup)+import Test.Tasty.HUnit (assertEqual, testCase)+import Test.Tasty.Lua (translateResultsFromFile)++import qualified HsLua as Lua++main :: IO ()+main = do+ luaTestResults <- Lua.run @Lua.Exception $ do+ Lua.openlibs+ Lua.registerModule documentedModule+ Lua.pop 1+ translateResultsFromFile "test/test-version.lua"+ defaultMain $ testGroup "hslua-module-version" [tests, luaTestResults]++-- | HSpec tests for the Lua 'version' module+tests :: TestTree+tests = testGroup "HsLua version module"+ [ testCase "version module can be pushed to the stack" $+ Lua.run (void (pushModule documentedModule) :: Lua ())++ , testCase "version module can be added to the preloader" . Lua.run $ do+ Lua.openlibs+ preloadModule documentedModule+ assertEqual' "function not added to preloader" Lua.TypeFunction =<< do+ Lua.getglobal' "package.preload.Version"+ Lua.ltype (-1)++ , testCase "version module can be loaded as hsversion" . Lua.run $ do+ Lua.openlibs+ preloadModuleWithName documentedModule "hsversion"+ assertEqual' "loading the module fails " Lua.OK =<<+ Lua.dostring "require 'hsversion'"+ ]++assertEqual' :: (Show a, Eq a) => String -> a -> a -> Lua ()+assertEqual' msg expected = Lua.liftIO . assertEqual msg expected
+ test/test-version.lua view
@@ -0,0 +1,110 @@+--+-- Tests for the `version` module+--+local Version = require 'Version'+local tasty = require 'tasty'++local group = tasty.test_group+local test = tasty.test_case+local assert = tasty.assert++return {+ group 'constructor' {+ test('has type `userdata`', function ()+ assert.are_same(type(Version {2}), 'userdata')+ end),+ test('accepts list of integers', function ()+ assert.are_same(type(Version {2, 7, 3}), 'userdata')+ end),+ test('accepts a single integer', function ()+ assert.are_same(Version(5), Version {5})+ end),+ test('accepts version as string', function ()+ assert.are_same(+ Version '4.45.1',+ Version {4, 45, 1}+ )+ end),+ test('non-version string is rejected', function ()+ local success, msg = pcall(function () return Version '11friends' end)+ assert.is_falsy(success)+ assert.is_truthy(tostring(msg):match('11friends'))+ end)+ },++ group 'comparison' {+ test('smaller (equal) than', function ()+ assert.is_truthy(Version {2, 58, 3} < Version {2, 58, 4})+ assert.is_falsy(Version {2, 60, 1} < Version {2, 59, 2})+ assert.is_truthy(Version {0, 14, 3} < Version {0, 14, 3, 1})+ assert.is_truthy(Version {3, 58, 3} <= Version {4})+ assert.is_truthy(Version {0, 14, 3} <= Version {0, 14, 3, 1})+ end),+ test('larger (equal) than', function ()+ assert.is_truthy(Version{2,58,3} > Version {2, 57, 4})+ assert.is_truthy(Version{2,58,3} > Version {2, 58, 2})+ assert.is_truthy(Version {0, 8} >= Version {0, 8})+ assert.is_falsy(Version {0, 8} >= Version {0, 8, 2})+ end),+ test('equality', function ()+ assert.is_truthy(Version '8.8', Version {8, 8})+ end),+ test('second argument can be a version string', function ()+ assert.is_truthy(Version '8' < '9.1')+ assert.is_falsy(Version '8.8' < '8.7')+ end),+ },++ group 'conversion to string' {+ test('converting from and to string is a noop', function ()+ local version_string = '1.19.4'+ assert.are_equal(tostring(Version(version_string)), version_string)+ end)+ },++ group 'convenience functions' {+ test('throws error if version is too old', function ()+ local actual = Version {2, 8}+ local expected = Version {2, 9}+ assert.error_matches(+ function () actual:must_be_at_least(expected) end,+ 'expected version 2.9 or newer, got 2.8'+ )+ end),+ test('does nothing if expected version is older than actual', function ()+ local actual = Version '2.9'+ local expected = Version '2.8'+ actual:must_be_at_least(expected)+ end),+ test('does nothing if expected version equals to actual', function ()+ local actual = Version '2.8'+ local expected = Version '2.8'+ actual:must_be_at_least(expected)+ end)+ },++ group 'list-like behavior' {+ test('can access version component numbers', function ()+ local version = Version '2.7.3'+ assert.is_nil(version[0])+ assert.are_equal(version[1], 2)+ assert.are_equal(version[2], 7)+ assert.are_equal(version[3], 3)+ end),+ test('can be iterated over', function ()+ local version_list = {2, 7, 3}+ local final_index = 0+ for i, v in pairs(Version(version_list)) do+ assert.are_equal(v, version_list[i])+ final_index = i+ end+ assert.are_equal(final_index, 3)+ end),+ test('length is the number of components', function ()+ assert.are_equal(#(Version '0'), 1)+ assert.are_equal(#(Version '1.6'), 2)+ assert.are_equal(#(Version '8.7.5'), 3)+ assert.are_equal(#(Version '2.9.1.5'), 4)+ end)+ }+}