hslua-module-text 1.0.2 → 1.0.3
raw patch · 4 files changed
+160/−14 lines, 4 filesdep ~hslua-coredep ~tasty-luaPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: hslua-core, tasty-lua
API changes (from Hackage documentation)
+ HsLua.Module.Text: fromencoding :: LuaError e => DocumentedFunction e
+ HsLua.Module.Text: toencoding :: LuaError e => DocumentedFunction e
- HsLua.Module.Text: documentedModule :: Module e
+ HsLua.Module.Text: documentedModule :: LuaError e => Module e
Files
- CHANGELOG.md +10/−0
- hslua-module-text.cabal +7/−9
- src/HsLua/Module/Text.hs +90/−5
- test/test-text.lua +53/−0
CHANGELOG.md view
@@ -2,6 +2,16 @@ `hslua-module-text` uses [PVP Versioning][]. +## hslua-module-text-1.0.3++Released 2023-01-03.++- Added new functions `fromencoding` and `toencoding`. These can+ be used to convert from or to a different (non UTF-8)+ encoding. This is particularly helpful when opening files on+ system that don't use UTF-8 for their file system, most+ notably Windows.+ ## hslua-module-text-1.0.2 Released 2022-02-19.
hslua-module-text.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: hslua-module-text-version: 1.0.2+version: 1.0.3 synopsis: Lua module for text description: UTF-8 aware subset of Lua's `string` module. .@@ -13,18 +13,16 @@ license-file: LICENSE author: Albert Krewinkel maintainer: albert+hslua@zeitkraut.de-copyright: © 2017–2022 Albert Krewinkel+copyright: © 2017–2023 Albert Krewinkel category: Foreign extra-source-files: CHANGELOG.md , test/test-text.lua-tested-with: GHC == 8.0.2- , GHC == 8.2.2- , GHC == 8.4.4+tested-with: GHC == 8.4.4 , GHC == 8.6.5 , GHC == 8.8.3 , GHC == 8.10.7- , GHC == 9.0.1- , GHC == 9.2.1+ , GHC == 9.0.2+ , GHC == 9.2.3 source-repository head type: git@@ -34,7 +32,7 @@ common common-options default-language: Haskell2010 build-depends: base >= 4.8 && < 5- , hslua-core >= 2.1 && < 2.3+ , hslua-core >= 2.1 && < 2.4 , hslua-packaging >= 2.1 && < 2.3 , text >= 1.2 && < 2.1 ghc-options: -Wall@@ -66,5 +64,5 @@ build-depends: hslua-module-text , tasty >= 0.11 , tasty-hunit >= 0.9- , tasty-lua >= 1.0 && < 1.1+ , tasty-lua >= 1.0 && < 1.2 , text
src/HsLua/Module/Text.hs view
@@ -1,12 +1,13 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeApplications #-} {-| Module : HsLua.Module.Text-Copyright : © 2017–2021 Albert Krewinkel+Copyright : © 2017–2023 Albert Krewinkel License : MIT Maintainer : Albert Krewinkel <tarleb+hslua@zeitkraut.de> Stability : alpha-Portability : ForeignFunctionInterface+Portability : GHC only Provides a Lua module containing a selection of useful Text functions. -}@@ -14,30 +15,43 @@ ( -- * Module documentedModule -- ** Functions+ , fromencoding , len , lower , reverse , sub+ , toencoding , upper ) where import Prelude hiding (reverse) import Data.Text (Text) import Data.Maybe (fromMaybe)+import Foreign.Marshal.Alloc (alloca)+import HsLua.Core (LuaError) import HsLua.Packaging+import Lua (lua_pushlstring, lua_tolstring)+import System.IO.Error (tryIOError) import qualified Data.Text as T+import qualified Foreign.Storable as F+import qualified GHC.Foreign as GHC+import qualified GHC.IO.Encoding as GHC+import qualified HsLua.Core as Lua+import qualified HsLua.Marshalling as Lua -- | The @text@ module.-documentedModule :: Module e+documentedModule :: LuaError e => Module e documentedModule = Module { moduleName = "text" , moduleOperations = [] , moduleFields = [] , moduleFunctions =- [ len+ [ fromencoding+ , len , lower , reverse , sub+ , toencoding , upper ] , moduleDescription =@@ -48,6 +62,39 @@ -- Functions -- +-- | Recodes a string as UTF-8.+fromencoding :: LuaError e => DocumentedFunction e+fromencoding = defun "fromencoding"+ ### (\strIdx menc -> do+ l <- Lua.state+ result <- Lua.liftIO . tryIOError $ do+ encoding <- maybe getFileSystemEncoding GHC.mkTextEncoding menc+ alloca $ \lenPtr -> do+ cstr <- lua_tolstring l strIdx lenPtr+ -- cstr cannot be NULL, or stringIndex would have failed.+ cstrLen <- F.peek lenPtr+ GHC.peekCStringLen encoding (cstr, fromIntegral cstrLen)+ case result of+ Right s -> pure $ T.pack s+ Left err -> Lua.failLua (show err))+ <#> parameter stringIndex "string" "s" "string to be converted"+ <#> opt (stringParam "encoding" "target encoding")+ =#> functionResult Lua.pushText "string" "UTF-8 string"+ #? T.unlines+ [ "Converts a string from a different encoding to UTF-8. On Windows,"+ , "the `encoding` parameter defaults to the current ANSI code page; on"+ , "other platforms the function will try to use the file system's"+ , "encoding."+ , ""+ , "See `toencoding` for more info on supported encodings."+ ]+ where+ stringIndex idx = do+ isstr <- Lua.liftLua (Lua.isstring idx)+ if isstr+ then pure idx+ else Lua.typeMismatchMessage "string" idx >>= Lua.failPeek+ -- | Wrapper for @'T.length'@. len :: DocumentedFunction e len = defun "len"@@ -89,6 +136,33 @@ fromEnd = if j < 0 then -j - 1 else T.length s - j in T.dropEnd fromEnd . T.drop fromStart $ s +-- | Converts a UTF-8 string to a different encoding.+toencoding :: LuaError e => DocumentedFunction e+toencoding = defun "toencoding"+ ### (\s menc -> do+ l <- Lua.state+ result <- Lua.liftIO . tryIOError $ do+ encoding <- maybe getFileSystemEncoding GHC.mkTextEncoding menc+ GHC.withCStringLen encoding (T.unpack s) $ \(sPtr, sLen) ->+ lua_pushlstring l sPtr (fromIntegral sLen)+ case result of+ Right () -> pure ()+ Left err -> Lua.failLua (show err))+ <#> textParam "s" "UTF-8 string"+ <#> opt (stringParam "enc" "target encoding")+ =#> functionResult (const (pure ())) "string" "re-encoded string"+ #? T.unlines+ [ "Converts a UTF-8 string to a different encoding. On Windows, the"+ , "`encoding` parameter defaults to the current ANSI code page; on"+ , "other platforms the function will try to use the file system's"+ , "encoding."+ , ""+ , "The set of known encodings is system dependent, but includes at"+ , "least `UTF-8`, `UTF-16BE`, `UTF-16LE`, `UTF-32BE`, and `UTF-32LE`."+ , "Note that the prefix `CP` allows to access code page on Windows,"+ , "e.g. `CP0` (the current ANSI code page) or `CP1250`."+ ]+ -- | Wrapper for @'T.toUpper'@. upper :: DocumentedFunction e upper = defun "upper"@@ -106,3 +180,14 @@ -> Text -- ^ parameter description -> Parameter e Int textIndex = integralParam @Int++--+-- Helpers+--+getFileSystemEncoding :: IO GHC.TextEncoding+getFileSystemEncoding =+#if defined(mingw32_HOST_OS)+ GHC.mkTextEncoding "CP0" -- a.k.a CP_ACP+#else+ GHC.getFileSystemEncoding+#endif
test/test-text.lua view
@@ -74,5 +74,58 @@ assert.are_equal(text.sub('☢ radioactive', 0, 1), '☢') assert.are_equal(text.sub('☢ radioactive', -11, -1), 'radioactive') end)+ },++ group 'fromencoding' {+ test('decode UTF-16, big endian', function ()+ local utf16be = '\0S\0t\0r\0a\0\xdf\0e'+ local decoded = text.fromencoding(utf16be, 'utf16be')+ assert.are_equal(decoded, "Straße")+ end),+ test('throws error for unknown encoding', function ()+ assert.error_matches(+ function () text.toencoding('a', 'utf9') end,+ "unknown encoding"+ )+ end),+ test('throws error if input cannot be decoded', function ()+ assert.error_matches(+ function () text.fromencoding('\xff\xff\xff\ff', 'utf16le') end,+ "invalid byte sequence"+ )+ end),+ test('throws error if input is not a string', function ()+ assert.error_matches(+ function () text.fromencoding({}, 'utf16le') end,+ "string expected, got table"+ )+ end),+ },++ group 'toencoding' {+ test('encode as UTF-16, big endian', function ()+ local encoded = text.toencoding('Straße', 'utf16be')+ assert.are_equal(encoded, '\0S\0t\0r\0a\0\xdf\0e')+ end),+ test('encode as UTF-16, little endian', function ()+ local encoded = text.toencoding('Straße', 'utf16le')+ assert.are_equal(encoded, 'S\0t\0r\0a\0\xdf\0e\0')+ end),+ test('encode as UTF-32, little endian', function ()+ local encoded = text.toencoding('Straße', 'UTF-32LE')+ assert.are_equal(encoded, 'S\0\0\0t\0\0\0r\0\0\0a\0\0\0\xdf\0\0\0e\0\0\0')+ end),+ test('throws error for unknown encoding', function ()+ assert.error_matches(+ function () text.toencoding('a', 'utf9') end,+ "unknown encoding"+ )+ end),+ test('throws error if input cannot be encoded', function ()+ assert.error_matches(+ function () text.toencoding('😊', 'latin1') end,+ "invalid character"+ )+ end), } }