packages feed

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 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),   } }