text-conversions-0.3.1.1/0000755000000000000000000000000007346545000013375 5ustar0000000000000000text-conversions-0.3.1.1/CHANGELOG.md0000644000000000000000000000161407346545000015210 0ustar0000000000000000# Changelog ## 0.3.1.1 (May 2nd, 2022) - Eliminated dependency on the `errors` package. ## 0.3.1 (September 29th, 2020) - Added support for `base16-bytestring-1.0`. ## 0.3.0 (June 9, 2016) ### New Features - The `Base16` and `Base64` newtypes are now provided for managing safe conversions between binary data and Base16 and Base64 textual encodings of that data. ## 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 text-conversions-0.3.1.1/LICENSE0000644000000000000000000000135007346545000014401 0ustar0000000000000000Copyright CJ Affiliate by Conversant (c) 2016 Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. text-conversions-0.3.1.1/README.md0000644000000000000000000000260507346545000014657 0ustar0000000000000000# text-conversions [![Build Status](https://img.shields.io/github/workflow/status/cjdev/text-conversions/build/master)](https://github.com/cjdev/text-conversions/actions/workflows/build.yml) [![Hackage](https://img.shields.io/badge/hackage-0.3.1.1-5e5184)][hackage] This is a small library to ease the pain when converting between the many different string types in Haskell. Unlike some other libraries that attempt to solve the same problem, text-conversions is: - **Safe.** This library treats binary data (aka `ByteString`) like binary data, and it does not assume a particular encoding, nor does it ever throw exceptions when failing to decode. It does, however, provide failable conversions between binary data and textual data. - **Extensible.** It’s easy to add or derive your own instances of the typeclasses to use your own types through the same interface. Here’s an example of using text-conversions to convert between textual types: ```haskell > convertText ("hello" :: String) :: Text "hello" ``` And here’s an example of converting from UTF-8 encoded binary data to a textual format: ```haskell > decodeConvertText (UTF8 ("hello" :: ByteString)) :: Maybe Text Just "hello" > decodeConvertText (UTF8 ("\xc3\x28" :: ByteString)) :: Maybe Text Nothing ``` [For more details, see the documentation on Hackage.][hackage] [hackage]: https://hackage.haskell.org/package/text-conversions text-conversions-0.3.1.1/Setup.hs0000644000000000000000000000050607346545000015032 0ustar0000000000000000-- This script is used to build and install your package. Typically you don't -- need to change it. The Cabal documentation has more information about this -- file: . import qualified Distribution.Simple main :: IO () main = Distribution.Simple.defaultMain text-conversions-0.3.1.1/src/Data/Text/0000755000000000000000000000000007346545000015761 5ustar0000000000000000text-conversions-0.3.1.1/src/Data/Text/Conversions.hs0000644000000000000000000001607707346545000020640 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveFunctor #-} {-| Module: Data.Text.Conversions This module provides a set of typeclasses for safely converting between textual data. The built-in 'String' type, as well as strict 'T.Text' and lazy 'TL.Text', are safely convertible between one another. The 'B.ByteString' type is frequently treated in much the same manner, but this is unsafe for two reasons: * Since 'B.ByteString' encodes binary data, it does not specify a particular encoding, so assuming a particular encoding like UTF-8 would be incorrect. * Furthermore, decoding binary data into text given a particular encoding can fail. Most systems simply use 'T.decodeUtf8' and similar functions, which will dangerously throw exceptions when given invalid data. This module addresses both problems by providing a 'DecodeText' typeclass for decoding binary data in a way that can fail and by providing a 'UTF8' wrapper type for selecting the desired encoding. 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 convert between two textual datatypes or the 'decodeConvertText' method to perform a conversion that can fail. Examples: >>> convertText ("hello" :: String) :: Text "hello" >>> decodeConvertText (UTF8 ("hello" :: ByteString)) :: Maybe Text Just "hello" >>> decodeConvertText (UTF8 ("\xc3\x28" :: ByteString)) :: Maybe Text Nothing -} module Data.Text.Conversions ( -- * Conversion typeclasses and functions FromText(..) , ToText(..) , DecodeText(..) , convertText , decodeConvertText -- * Encoding newtypes , UTF8(..) , Base16(..) , Base64(..) ) where import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Encoding as TL import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Base16 as Base16 import qualified Data.ByteString.Base16.Lazy as Base16L import qualified Data.ByteString.Base64 as Base64 import qualified Data.ByteString.Base64.Lazy as Base64L {-| Simple wrapper type that is used to select a desired encoding when encoding or decoding text from binary data, such as 'B.ByteString's. The conversion is not partial; it will result in 'Nothing' when a 'B.ByteString' is provided with data that is not valid in UTF-8. >>> convertText ("hello" :: Text) :: UTF8 ByteString UTF8 "hello" >>> decodeConvertText (UTF8 ("hello" :: ByteString)) :: Maybe Text Just "hello" >>> decodeConvertText (UTF8 ("invalid \xc3\x28" :: ByteString)) :: Maybe Text Nothing -} newtype UTF8 a = UTF8 { unUTF8 :: a } deriving (Eq, Show, Functor) {-| Wrapper type used to select a base 16 encoding when encoding or decoding binary data. Safe because base 16 encoding will always produce ASCII output. -} newtype Base16 a = Base16 { unBase16 :: a } deriving (Eq, Show, Functor) {-| Wrapper type used to select a base 64 encoding when encoding or decoding binary data. Safe because base 64 encoding will always produce ASCII output. -} newtype Base64 a = Base64 { unBase64 :: a } deriving (Eq, Show, Functor) {-| A simple typeclass that handles converting arbitrary datatypes to 'T.Text' when the operation cannot fail. If you have a type that satisfies that requirement, implement this typeclass, but if the operation can fail, use 'DecodeText' instead. -} class ToText a where toText :: a -> T.Text {-| A simple typeclass that handles converting 'T.Text' to arbitrary datatypes. If you have a type that can be produced from text, implement this typeclass. However, you probably do not want to call 'fromText' directly; call 'convertText', instead. -} class FromText a where fromText :: T.Text -> a {-| A simple typeclass that handles converting arbitrary datatypes to 'T.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 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 {-| 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': >>> decodeConvertText (UTF8 ("hello" :: ByteString)) :: Maybe Text Just "hello" -} decodeConvertText :: (DecodeText f a, FromText b) => a -> f b decodeConvertText = fmap fromText . decodeText hush :: Either a b -> Maybe b hush (Left _) = Nothing hush (Right x) = Just x instance ToText T.Text where toText = id instance FromText T.Text where fromText = id instance ToText String where toText = T.pack instance FromText String where fromText = T.unpack instance ToText TL.Text where toText = TL.toStrict instance FromText TL.Text where fromText = TL.fromStrict instance DecodeText Maybe (UTF8 B.ByteString) where decodeText = hush . T.decodeUtf8' . unUTF8 instance FromText (UTF8 B.ByteString) where fromText = UTF8 . T.encodeUtf8 instance DecodeText Maybe (UTF8 BL.ByteString) where decodeText = hush . fmap TL.toStrict . TL.decodeUtf8' . unUTF8 instance FromText (UTF8 BL.ByteString) where fromText = UTF8 . TL.encodeUtf8 . TL.fromStrict instance ToText (Base16 B.ByteString) where toText = T.decodeUtf8 . Base16.encode . unBase16 instance FromText (Maybe (Base16 B.ByteString)) where #if MIN_VERSION_base16_bytestring(1,0,0) fromText txt = case Base16.decode (T.encodeUtf8 txt) of Right bs -> Just $ Base16 bs Left _ -> Nothing #else fromText txt = case Base16.decode (T.encodeUtf8 txt) of (bs, "") -> Just $ Base16 bs (_, _) -> Nothing #endif instance ToText (Base64 B.ByteString) where toText = T.decodeUtf8 . Base64.encode . unBase64 instance FromText (Maybe (Base64 B.ByteString)) where fromText = fmap Base64 . hush . Base64.decode . T.encodeUtf8 instance ToText (Base16 BL.ByteString) where toText = TL.toStrict . TL.decodeUtf8 . Base16L.encode . unBase16 instance FromText (Maybe (Base16 BL.ByteString)) where #if MIN_VERSION_base16_bytestring(1,0,0) fromText txt = case Base16L.decode (TL.encodeUtf8 $ TL.fromStrict txt) of Right bs -> Just $ Base16 bs Left _ -> Nothing #else fromText txt = case Base16L.decode (TL.encodeUtf8 $ TL.fromStrict txt) of (bs, "") -> Just $ Base16 bs (_, _) -> Nothing #endif instance ToText (Base64 BL.ByteString) where toText = TL.toStrict . TL.decodeUtf8 . Base64L.encode . unBase64 instance FromText (Maybe (Base64 BL.ByteString)) where fromText = fmap Base64 . hush . Base64L.decode . TL.encodeUtf8 . TL.fromStrict text-conversions-0.3.1.1/test/Data/Text/0000755000000000000000000000000007346545000016151 5ustar0000000000000000text-conversions-0.3.1.1/test/Data/Text/ConversionsSpec.hs0000644000000000000000000001002307346545000021624 0ustar0000000000000000module Data.Text.ConversionsSpec (spec) where import Test.Hspec import Data.Text.Conversions 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 newtype Upper = Upper T.Text deriving (Eq, Show) newtype Lower = Lower T.Text deriving (Eq, Show) instance ToText Upper where toText (Upper txt) = txt instance FromText Lower where fromText = Lower . T.toLower data FailableString = FailableString deriving (Eq, Show) 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 it "can convert strings to and from text" $ do convertText ("hello" :: String) `shouldBe` ("hello" :: T.Text) convertText ("hello" :: T.Text) `shouldBe` ("hello" :: String) it "converts between strict and lazy text" $ do convertText ("hello" :: T.Text) `shouldBe` ("hello" :: TL.Text) convertText ("hello" :: TL.Text) `shouldBe` ("hello" :: T.Text) 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 "Base16" $ do it "encodes bytestrings as base 16 encoded text" $ do convertText (Base16 ("hello" :: B.ByteString)) `shouldBe` ("68656c6c6f" :: T.Text) convertText (Base16 ("hello" :: BL.ByteString)) `shouldBe` ("68656c6c6f" :: T.Text) it "decodes properly encoded base 16 text as bytestrings" $ do convertText ("68656c6c6f" :: T.Text) `shouldBe` Just (Base16 ("hello" :: B.ByteString)) convertText ("68656c6c6f" :: T.Text) `shouldBe` Just (Base16 ("hello" :: BL.ByteString)) it "fails to decode improperly encoded base 16 text as bytestrings" $ do convertText ("not base 16" :: T.Text) `shouldBe` (Nothing :: Maybe (Base16 B.ByteString)) convertText ("not base 16" :: T.Text) `shouldBe` (Nothing :: Maybe (Base16 BL.ByteString)) describe "Base64" $ do it "encodes bytestrings as base 64 encoded text" $ do convertText (Base64 ("hello" :: B.ByteString)) `shouldBe` ("aGVsbG8=" :: T.Text) convertText (Base64 ("hello" :: BL.ByteString)) `shouldBe` ("aGVsbG8=" :: T.Text) it "decodes properly encoded base 64 text as bytestrings" $ do convertText ("aGVsbG8=" :: T.Text) `shouldBe` Just (Base64 ("hello" :: B.ByteString)) convertText ("aGVsbG8=" :: T.Text) `shouldBe` Just (Base64 ("hello" :: BL.ByteString)) it "fails to decode improperly encoded base 64 text as bytestrings" $ do convertText ("not base 16" :: T.Text) `shouldBe` (Nothing :: Maybe (Base64 B.ByteString)) convertText ("not base 16" :: T.Text) `shouldBe` (Nothing :: Maybe (Base64 BL.ByteString)) describe "decodeConvertText" $ do it "can convert between things in functors with a DecodeText instance" $ decodeConvertText FailableString `shouldBe` Just ("failable" :: TL.Text) describe "UTF8" $ do it "successfully decodes properly encoded bytestrings" $ do 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 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-0.3.1.1/test/0000755000000000000000000000000007346545000014354 5ustar0000000000000000text-conversions-0.3.1.1/test/Main.hs0000644000000000000000000000005407346545000015573 0ustar0000000000000000{-# OPTIONS_GHC -F -pgmF hspec-discover #-} text-conversions-0.3.1.1/text-conversions.cabal0000644000000000000000000000263107346545000017715 0ustar0000000000000000cabal-version: 2.4 name: text-conversions version: 0.3.1.1 category: Data build-type: Simple synopsis: Safe conversions between textual types description: Safe conversions between textual types author: Alexis King maintainer: Alexis King license: ISC license-file: LICENSE extra-source-files: README.md CHANGELOG.md LICENSE homepage: https://github.com/cjdev/text-conversions bug-reports: https://github.com/cjdev/text-conversions/issues source-repository head type: git location: https://github.com/cjdev/text-conversions common common default-language: Haskell2010 default-extensions: FlexibleInstances MultiParamTypeClasses OverloadedStrings ghc-options: -Wall if impl(ghc >= 8.0.1) ghc-options: -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints library import: common hs-source-dirs: src exposed-modules: Data.Text.Conversions build-depends: , base >=4.7 && <5 , base16-bytestring <2 , base64-bytestring <2 , bytestring <1 , text <3 test-suite text-conversions-test-suite import: common type: exitcode-stdio-1.0 hs-source-dirs: test main-is: Main.hs other-modules: Data.Text.ConversionsSpec ghc-options: -rtsopts -threaded -with-rtsopts=-N build-depends: , base , bytestring , hspec , text , text-conversions build-tool-depends: hspec-discover:hspec-discover