text-conversions 0.1.0 → 0.2.0
raw patch · 6 files changed
+63/−65 lines, 6 files
Files
- CHANGELOG.md +6/−0
- README.md +4/−0
- package.yaml +1/−3
- src/Data/Text/Conversions.hs +27/−40
- test/Data/Text/ConversionsSpec.hs +22/−19
- text-conversions.cabal +3/−3
CHANGELOG.md view
@@ -1,5 +1,11 @@ # Changelog +## 0.2.0 (May 25, 2016)++### Breaking Changes++- The `ConvertText` typeclass has been split into two simpler functions, `convertText` and `decodeConvertText`. This means the vision of a unified function for converting between *any* two textual datatypes is no longer implemented, but the original attempt had too many problems to really be worth the cost. Specifically, instances like `FromText (Maybe Foo)` would cause problems due to the overlapping instances, which have now been removed.+ ## 0.1.0 (May 24, 2016) - Initial release
README.md view
@@ -21,3 +21,7 @@ > convertText (UTF8 ("\xc3\x28" :: ByteString)) :: Maybe Text Nothing ```++[For more details, see the documentation on Hackage.][hackage]++[hackage]: https://hackage.haskell.org/package/text-conversions
package.yaml view
@@ -1,5 +1,5 @@ name: text-conversions-version: '0.1.0'+version: '0.2.0' category: Data synopsis: Safe conversions between textual types description: Safe conversions between textual types@@ -18,11 +18,9 @@ ghc-options: -Wall default-extensions:-- FlexibleContexts - FlexibleInstances - MultiParamTypeClasses - OverloadedStrings-- ScopedTypeVariables library: source-dirs: src
src/Data/Text/Conversions.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE Safe #-} {-# LANGUAGE DeriveFunctor #-} {-|@@ -23,24 +24,25 @@ Most of the time, you will not need to create your own instances or use the underlying functions that make the conversion machinery tick. Instead, just- use the 'convertText' method to automatically convert between two textual- datatypes automatically, whatever they may be.+ use the 'convertText' method to convert between two textual datatypes or the+ 'decodeConvertText' method to perform a conversion that can fail. Examples: >>> convertText ("hello" :: String) :: Text "hello"- >>> convertText (UTF8 ("hello" :: ByteString)) :: Maybe Text+ >>> decodeConvertText (UTF8 ("hello" :: ByteString)) :: Maybe Text Just "hello"- >>> convertText (UTF8 ("\xc3\x28" :: ByteString)) :: Maybe Text+ >>> decodeConvertText (UTF8 ("\xc3\x28" :: ByteString)) :: Maybe Text Nothing -} module Data.Text.Conversions- ( ConvertText(..)- , DecodeText(..)+ ( DecodeText(..) , FromText(..) , ToText(..) , UTF8(..)+ , convertText+ , decodeConvertText ) where import Control.Error.Util (hush)@@ -63,9 +65,8 @@ {-| A simple typeclass that handles converting arbitrary datatypes to 'Data.Text.Text' when the operation cannot fail. If you have a type that- satisfies that requirement, implement this typeclass, /not/ 'ConvertText'.- However, you probably do not want to call 'toText' directly; call- 'convertText', instead.+ satisfies that requirement, implement this typeclass, but if the operation can+ fail, use 'DecodeText' instead. -} class ToText a where toText :: a -> T.Text@@ -81,50 +82,36 @@ {-| A simple typeclass that handles converting arbitrary datatypes to- 'Data.Text.Text' when the operation can fail (the functor in question is- expected to be 'Data.Maybe.Maybe' or 'Data.Either.Either'). If you have a type- that satisfies that requirement, implement this typeclass, /not/- 'ConvertText'. However, you probably do not want to call 'decodeText'- directly; call 'convertText', instead.+ 'Data.Text.Text' when the operation can fail. If you have a type that+ satisfies that requirement, implement this typeclass, but if the operation+ cannot fail, use 'ToText' instead. -} class Functor f => DecodeText f a where decodeText :: a -> f T.Text {-|- A typeclass that provides a way to /safely/ convert between arbitrary textual- datatypes, including conversions that can potentially fail.- __Do not implement this typeclass directly__, implement 'ToText', 'FromText',- or 'DecodeText', instead, which this typeclass defers to. Use the- 'convertText' function to actually perform conversions.-- At a basic level, 'convertText' can convert between textual types, like- between 'String' and 'Data.Text.Text':+ A function that provides a way to /safely/ convert between arbitrary textual+ datatypes where the conversion to text cannot fail. >>> convertText ("hello" :: String) :: Text "hello"+-}+convertText :: (ToText a, FromText b) => a -> b+convertText = fromText . toText - More interestingly, 'convertText' can also convert between binary data and- textual data in the form of 'Data.ByteString.ByteString'. Since binary data- can represent text in many different potential encodings, it is necessary to- use a newtype that picks the particular encoding, like 'UTF8': +{-|+ A function that provides a way to /safely/ convert between arbitrary textual+ datatypes where the conversion to text can fail, such as decoding binary data+ to text. Since binary data can represent text in many different potential+ encodings, it is necessary to use a newtype that picks the particular+ encoding, like 'UTF8':+ >>> convertText (UTF8 ("hello" :: ByteString)) :: Maybe Text Just "hello"-- Note that the result of converting a 'Data.ByteString.ByteString' is a 'Maybe'- 'Data.Text.Text' since the decoding can fail. -}-class ConvertText a b where- convertText :: a -> b--instance (ToText a, FromText b) => ConvertText a b where- convertText = fromText . toText--instance {-# OVERLAPPING #-} (DecodeText Maybe a, FromText b) => ConvertText a (Maybe b) where- convertText = fmap fromText . decodeText--instance {-# OVERLAPPING #-} (DecodeText (Either e) a, FromText b) => ConvertText a (Either e b) where- convertText = fmap fromText . decodeText+decodeConvertText :: (DecodeText f a, FromText b) => a -> f b+decodeConvertText = fmap fromText . decodeText instance ToText T.Text where toText = id instance FromText T.Text where fromText = id
test/Data/Text/ConversionsSpec.hs view
@@ -1,22 +1,14 @@-module Data.Text.ConversionsSpec where+module Data.Text.ConversionsSpec (spec) where import Test.Hspec import Data.Text.Conversions -import Data.String (IsString(..))- import qualified Data.Text as T import qualified Data.Text.Lazy as TL import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL -data BoringString = BoringString- deriving (Eq, Show)--instance IsString BoringString where- fromString _ = BoringString- newtype Upper = Upper T.Text deriving (Eq, Show) newtype Lower = Lower T.Text deriving (Eq, Show) @@ -29,9 +21,15 @@ instance DecodeText Maybe FailableString where decodeText _ = Just "failable" +data FailableRepresentation = FailableRepresentation+ deriving (Eq, Show)++instance FromText (Maybe FailableRepresentation) where+ fromText _ = Just FailableRepresentation+ spec :: Spec spec = do- describe "ConvertText" $ do+ describe "convertText" $ do it "can convert strings to and from text" $ do convertText ("hello" :: String) `shouldBe` ("hello" :: T.Text) convertText ("hello" :: T.Text) `shouldBe` ("hello" :: String)@@ -43,18 +41,23 @@ it "can convert between things with ToText/FromText conversions" $ convertText (Upper "HELLO") `shouldBe` Lower "hello" + it "can convert between things with FromText instances that produce functors" $+ convertText ("hello" :: T.Text) `shouldBe` Just FailableRepresentation++ describe "UTF8" $+ it "properly encodes text as bytestrings" $ do+ convertText ("hello" :: T.Text) `shouldBe` UTF8 ("hello" :: B.ByteString)+ convertText ("hello" :: T.Text) `shouldBe` UTF8 ("hello" :: BL.ByteString)++ describe "decodeConvertText" $ do it "can convert between things in functors with a DecodeText instance" $- convertText FailableString `shouldBe` Just ("failable" :: TL.Text)+ decodeConvertText FailableString `shouldBe` Just ("failable" :: TL.Text) describe "UTF8" $ do it "successfully decodes properly encoded bytestrings" $ do- convertText (UTF8 ("hello" :: B.ByteString)) `shouldBe` Just ("hello" :: T.Text)- convertText (UTF8 ("hello" :: BL.ByteString)) `shouldBe` Just ("hello" :: T.Text)+ decodeConvertText (UTF8 ("hello" :: B.ByteString)) `shouldBe` Just ("hello" :: T.Text)+ decodeConvertText (UTF8 ("hello" :: BL.ByteString)) `shouldBe` Just ("hello" :: T.Text) it "fails to decode improperly encoded bytestrings" $ do- convertText (UTF8 ("invalid \xc3\x28" :: B.ByteString)) `shouldBe` (Nothing :: Maybe T.Text)- convertText (UTF8 ("invalid \xc3\x28" :: BL.ByteString)) `shouldBe` (Nothing :: Maybe T.Text)-- it "properly encodes text as bytestrings" $ do- convertText ("hello" :: T.Text) `shouldBe` UTF8 ("hello" :: B.ByteString)- convertText ("hello" :: T.Text) `shouldBe` UTF8 ("hello" :: BL.ByteString)+ decodeConvertText (UTF8 ("invalid \xc3\x28" :: B.ByteString)) `shouldBe` (Nothing :: Maybe T.Text)+ decodeConvertText (UTF8 ("invalid \xc3\x28" :: BL.ByteString)) `shouldBe` (Nothing :: Maybe T.Text)
text-conversions.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack name: text-conversions-version: 0.1.0+version: 0.2.0 synopsis: Safe conversions between textual types description: Safe conversions between textual types category: Data@@ -30,7 +30,7 @@ library hs-source-dirs: src- default-extensions: FlexibleContexts FlexibleInstances MultiParamTypeClasses OverloadedStrings ScopedTypeVariables+ default-extensions: FlexibleInstances MultiParamTypeClasses OverloadedStrings ghc-options: -Wall build-depends: base >= 4.7 && < 5@@ -46,7 +46,7 @@ main-is: Main.hs hs-source-dirs: test- default-extensions: FlexibleContexts FlexibleInstances MultiParamTypeClasses OverloadedStrings ScopedTypeVariables+ default-extensions: FlexibleInstances MultiParamTypeClasses OverloadedStrings ghc-options: -Wall -rtsopts -threaded -with-rtsopts=-N build-depends: base