packages feed

from-text (empty) → 0.1

raw patch · 6 files changed

+299/−0 lines, 6 filesdep +basedep +bytestringdep +data-array-byte

Dependencies added: base, bytestring, data-array-byte, filepath, from-text, os-string, tasty, tasty-quickcheck, text

Files

+ CHANGELOG.md view
@@ -0,0 +1,3 @@+## 0.1++* Initial release.
+ LICENSE view
@@ -0,0 +1,29 @@+Copyright (c) 2026, Bodigrim+++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of the copyright holder nor the names of its+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,21 @@+# from-text [![Hackage](http://img.shields.io/hackage/v/from-text.svg)](https://hackage.haskell.org/package/from-text) [![Stackage LTS](http://stackage.org/package/from-text/badge/lts)](http://stackage.org/lts/package/from-text) [![Stackage Nightly](http://stackage.org/package/from-text/badge/nightly)](http://stackage.org/nightly/package/from-text)++This package provides++```haskell+class IsText a where+  fromText :: Text -> a+```++aiming to simplify conversion from `Text` to other textual data types, including `ByteArray`, `ByteString` and `OsPath`. When converting to binary types without an associated encoding, we use UTF-8.++There is an overwhelming number of alternative packages for text conversions. To name a few in no particular order:+* [`text-conversions`](https://hackage.haskell.org/package/text-conversions-0.3.1.1/docs/Data-Text-Conversions.html)+* [`string-conv`](https://hackage.haskell.org/package/string-conv-0.2.0/docs/Data-String-Conv.html)+* [`ttc`](https://hackage-content.haskell.org/package/ttc-1.5.0.1/docs/Data-TTC.html)+* [`text-convert`](https://hackage-content.haskell.org/package/text-convert-0.1.0.1/docs/Text-Convert.html)+* [`lawful-conversions`](https://hackage-content.haskell.org/package/lawful-conversions-0.4.0/docs/LawfulConversions.html)+* [`witch`](https://hackage-content.haskell.org/package/witch-1.3.1.0/docs/Witch-Instances.html)+* [`unwitch`](https://hackage-content.haskell.org/package/unwitch-3.0.0/docs/Unwitch-Convert-Text.html)++At the moment none of them provide conversions from `Text` to `OsPath`. I could not decide which one to contribute such function to, so decided to create a new package.
+ from-text.cabal view
@@ -0,0 +1,57 @@+cabal-version:   3.0+name:            from-text+version:         0.1+license:         BSD-3-Clause+license-file:    LICENSE+maintainer:      andrew.lelechenko@gmail.com+author:          Bodigrim+tested-with:+    ghc ==9.14.1 ghc ==9.12.2 ghc ==9.10.3 ghc ==9.8.4 ghc ==9.6.7+    ghc ==9.4.8 ghc ==9.2.8 ghc ==9.0.2 ghc ==8.10.7 ghc ==8.8.4+    ghc ==8.6.5++synopsis:        Type class to convert from Text+description:+    This package provides @class IsText a where fromText :: Text -> a@,+    aiming to simplify conversion from @Text@ to other textual data types,+    including @ByteArray@, @ByteString@ and @OsPath@.++category:        Text+build-type:      Simple+extra-doc-files:+    CHANGELOG.md+    README.md++source-repository head+    type:     git+    location: https://github.com/Bodigrim/from-text.git++library+    exposed-modules:  Data.Text.From+    hs-source-dirs:   src+    default-language: Haskell2010+    ghc-options:      -Wall+    build-depends:+        base >=4.12 && <5,+        bytestring >=0.12 && <0.13,+        filepath >=1.5 && <1.6,+        os-string >=2.0.2 && <2.1,+        text >=2.1.1 && <2.2++    if impl(ghc <9.4)+        build-depends: data-array-byte >=0.1 && <0.2++test-suite from-text-test+    type:             exitcode-stdio-1.0+    main-is:          Main.hs+    hs-source-dirs:   test+    default-language: Haskell2010+    ghc-options:      -Wall+    build-depends:+        base,+        bytestring,+        from-text,+        os-string,+        tasty,+        tasty-quickcheck,+        text
+ src/Data/Text/From.hs view
@@ -0,0 +1,130 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UnboxedTuples #-}++-- | Convert strict 'T.Text' to other textual types,+-- including t'ByteArray', 'B.ByteString' and 'System.OsPath.OsPath'.+module Data.Text.From (+  IsText (..),+) where++import Data.Array.Byte (ByteArray (..))+import Data.Bits (shiftR, (.&.))+import qualified Data.ByteString as B+import qualified Data.ByteString.Builder as BB+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString.Short as BS+import Data.Char (ord)+import Data.Coerce (coerce)+import Data.Functor.Const (Const (..))+import Data.Functor.Identity (Identity (..))+import qualified Data.Text as T+import qualified Data.Text.Array as TA+import qualified Data.Text.Encoding as TE+import qualified Data.Text.Internal as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Builder as TLB+import qualified Data.Text.Unsafe as TU+import GHC.Exts (sizeofByteArray#, writeWord16Array#)+import GHC.Int (Int (..))+import GHC.ST (ST (..), runST)+import GHC.Word (Word16 (..))+import qualified System.OsString as OS+import qualified System.OsString.Data.ByteString.Short.Internal as OSI+import qualified System.OsString.Internal.Types as OSIT+import qualified System.OsString.Posix as OSP++-- | Convert strict 'T.Text' to other textual types.+--+-- This is modeled after 'Data.String.IsString'+-- with the aim to avoid dealing with 'String' ever.+class IsText a where+  fromText :: T.Text -> a++instance a ~ Char => IsText [a] where+  fromText = T.unpack++instance IsText a => IsText (Identity a) where+  fromText = coerce (fromText @a)++instance IsText a => IsText (Const a b) where+  fromText = coerce (fromText @a)++-- | Encodes as UTF-8.+instance IsText ByteArray where+  fromText (T.Text arr@(ByteArray ba) 0 len)+    | I# (sizeofByteArray# ba) == len = arr+  fromText (T.Text arr off len) = TA.run $ do+    marr <- TA.new len+    TA.copyI len marr 0 arr off+    pure marr++-- | Encodes as UTF-8.+instance IsText B.StrictByteString where+  fromText = TE.encodeUtf8++-- | Encodes as UTF-8.+instance IsText BL.LazyByteString where+  fromText = BL.fromStrict . TE.encodeUtf8++-- | Encodes as UTF-8.+instance IsText BB.Builder where+  fromText = TE.encodeUtf8Builder++-- | Encodes as UTF-8.+instance IsText BS.ShortByteString where+  fromText = coerce (fromText @ByteArray)++instance IsText T.StrictText where+  fromText = id++instance IsText TL.LazyText where+  fromText = TL.fromStrict++instance IsText TLB.Builder where+  fromText = TLB.fromText++-- | Encodes as UTF-8.+instance IsText OSP.PosixString where+  fromText = coerce (fromText @ByteArray)++-- | Encodes as UTF-16 LE.+instance IsText OSIT.WindowsString where+  fromText (T.Text src off len) = runST $ do+    marr <- TA.new (len * 2)+    let go !srcOff !dstOff+          | srcOff >= len + off = do+              TA.shrinkM marr (dstOff * 2)+              arr <- TA.unsafeFreeze marr+              pure $ coerce arr+          | otherwise = do+              let !(TU.Iter c d) = TU.iterArray src srcOff+                  n = ord c+              d' <-+                if n <= 0xFFFF+                  then do+                    writeWord16LE marr dstOff (fromIntegral n)+                    pure 1+                  else do+                    let n1 = n - 0x10000+                    writeWord16LE marr dstOff (fromIntegral $ n1 `shiftR` 10 + 0xD800)+                    writeWord16LE marr (dstOff + 1) (fromIntegral $ n1 .&. 0x3FF + 0xDC00)+                    pure 2+              go (srcOff + d) (dstOff + d')+    go off 0+    where+      writeWord16LE :: TA.MArray s -> Int -> Word16 -> ST s ()+      writeWord16LE (TA.MutableByteArray marr) (I# offset) (W16# w16) = ST $ \s ->+        case writeWord16Array# marr offset (OSI.word16ToLE# w16) s of+          s' -> (# s', () #)++-- | Also known as 'System.OsPath.OsPath'.+instance IsText OS.OsString where+  fromText = case OS.coercionToPlatformTypes of+    Left {} -> coerce (fromText @OSIT.WindowsString)+    Right {} -> coerce (fromText @OSP.PosixString)
+ test/Main.hs view
@@ -0,0 +1,59 @@+{-# OPTIONS_GHC -Wno-orphans #-}++module Main (main) where++import qualified Data.ByteString.Builder as BB+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString.Short as BS+import Data.Functor.Const (Const (..))+import Data.Functor.Identity (Identity (..))+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import Data.Text.From+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Builder as TLB+import qualified System.OsString as OS+import qualified System.OsString.Posix as OSP+import qualified System.OsString.Windows as OSW+import Test.Tasty (defaultMain, testGroup)+import Test.Tasty.QuickCheck (Arbitrary (..), choose, getUnicodeString, testProperty, (===))++main :: IO ()+main =+  defaultMain $+    testGroup+      "IsText"+      [ testProperty "String" $+          \xs -> T.pack (fromText xs) === xs+      , testProperty "Identity" $+          \xs -> fromText xs === Identity (fromText xs :: String)+      , testProperty "Const" $+          \xs -> (fromText xs :: Const String ()) === Const (fromText xs)+      , testProperty "ByteArray" $+          \xs -> TE.decodeUtf8 (BS.fromShort (BS.ShortByteString (fromText xs))) === xs+      , testProperty "StrictByteString" $+          \xs -> TE.decodeUtf8 (fromText xs) === xs+      , testProperty "LazyByteString" $+          \xs -> TE.decodeUtf8 (BL.toStrict (fromText xs)) === xs+      , testProperty "Builder (ByteString)" $+          \xs -> TE.decodeUtf8 (BL.toStrict (BB.toLazyByteString (fromText xs))) === xs+      , testProperty "StrictText" $+          \xs -> fromText xs === xs+      , testProperty "LazyText" $+          \xs -> TL.toStrict (fromText xs) === xs+      , testProperty "Builder (Text)" $+          \xs -> TL.toStrict (TLB.toLazyText (fromText xs)) === xs+      , testProperty "PosixString" $+          \xs -> OSP.decodeUtf (fromText xs) === Just (T.unpack xs)+      , testProperty "WindowsString" $+          \xs -> OSW.decodeUtf (fromText xs) === Just (T.unpack xs)+      , testProperty "OsString" $+          \xs -> OS.decodeUtf (fromText xs) === Just (T.unpack xs)+      ]++instance Arbitrary T.Text where+  arbitrary = do+    t <- T.pack . getUnicodeString <$> arbitrary+    t' <- (`T.drop` t) <$> choose (0, T.length t `quot` 2)+    (`T.take` t) <$> choose (0, T.length t' `quot` 2)+  shrink = map T.pack . shrink . T.unpack