text-conversions-0.3.0/src/0000755000000000000000000000000012720641541014023 5ustar0000000000000000text-conversions-0.3.0/src/Data/0000755000000000000000000000000012720641361014674 5ustar0000000000000000text-conversions-0.3.0/src/Data/Text/0000755000000000000000000000000012720641445015623 5ustar0000000000000000text-conversions-0.3.0/test/0000755000000000000000000000000012720651104014207 5ustar0000000000000000text-conversions-0.3.0/test/Data/0000755000000000000000000000000012720651104015060 5ustar0000000000000000text-conversions-0.3.0/test/Data/Text/0000755000000000000000000000000012720651146016012 5ustar0000000000000000text-conversions-0.3.0/src/Data/Text/Conversions.hs0000644000000000000000000001451712726350646020505 0ustar0000000000000000{-# 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 'Data.Text.Text' and lazy 'Data.Text.Lazy.Text', are safely convertible between one another. The 'Data.ByteString.ByteString' type is frequently treated in much the same manner, but this is unsafe for two reasons: * Since 'Data.ByteString.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 'Data.Text.Encoding.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 ( Base16(..) , Base64(..) , DecodeText(..) , FromText(..) , ToText(..) , UTF8(..) , convertText , decodeConvertText ) where import Control.Error.Util (hush) 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 'Data.ByteString.ByteString's. -} 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 'Data.Text.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 'Data.Text.Text' to arbitrary datatypes. If you have a type that can be produced from text, implement this typeclass, /not/ 'ConvertText'. 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 '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 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': >>> convertText (UTF8 ("hello" :: ByteString)) :: Maybe Text Just "hello" -} 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 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 fromText txt = case Base16.decode (T.encodeUtf8 txt) of (bs, "") -> Just $ Base16 bs (_, _) -> Nothing 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 fromText txt = case Base16L.decode (TL.encodeUtf8 $ TL.fromStrict txt) of (bs, "") -> Just $ Base16 bs (_, _) -> Nothing 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.0/test/Main.hs0000644000000000000000000000005412720650773015441 0ustar0000000000000000{-# OPTIONS_GHC -F -pgmF hspec-discover #-} text-conversions-0.3.0/test/Data/Text/ConversionsSpec.hs0000644000000000000000000001002312726350620021464 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.0/LICENSE0000644000000000000000000000135012720641153014237 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.0/Setup.hs0000644000000000000000000000050612720636763014703 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.0/text-conversions.cabal0000644000000000000000000000304012726351133017550 0ustar0000000000000000-- This file has been generated from package.yaml by hpack version 0.14.0. -- -- see: https://github.com/sol/hpack name: text-conversions version: 0.3.0 synopsis: Safe conversions between textual types description: Safe conversions between textual types category: Data homepage: https://github.com/cjdev/text-conversions#readme bug-reports: https://github.com/cjdev/text-conversions/issues author: Alexis King maintainer: lexi.lambda@gmail.com license: ISC license-file: LICENSE build-type: Simple cabal-version: >= 1.10 extra-source-files: CHANGELOG.md LICENSE package.yaml README.md stack.yaml source-repository head type: git location: https://github.com/cjdev/text-conversions library hs-source-dirs: src default-extensions: FlexibleInstances MultiParamTypeClasses OverloadedStrings ghc-options: -Wall build-depends: base >= 4.7 && < 5 , bytestring , base16-bytestring , base64-bytestring , errors , text exposed-modules: Data.Text.Conversions default-language: Haskell2010 test-suite text-conversions-test-suite type: exitcode-stdio-1.0 main-is: Main.hs hs-source-dirs: test default-extensions: FlexibleInstances MultiParamTypeClasses OverloadedStrings ghc-options: -Wall -rtsopts -threaded -with-rtsopts=-N build-depends: base , text-conversions , bytestring , hspec , hspec-discover , text other-modules: Data.Text.ConversionsSpec default-language: Haskell2010 text-conversions-0.3.0/CHANGELOG.md0000644000000000000000000000104512721364274015053 0ustar0000000000000000# 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 text-conversions-0.3.0/LICENSE0000644000000000000000000000135012720641153014237 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.0/package.yaml0000644000000000000000000000152712726351034015521 0ustar0000000000000000name: text-conversions version: '0.3.0' category: Data synopsis: Safe conversions between textual types description: Safe conversions between textual types license: ISC author: Alexis King maintainer: lexi.lambda@gmail.com github: cjdev/text-conversions extra-source-files: - README.md - CHANGELOG.md - LICENSE - package.yaml - stack.yaml ghc-options: -Wall default-extensions: - FlexibleInstances - MultiParamTypeClasses - OverloadedStrings library: source-dirs: src dependencies: - base >= 4.7 && < 5 - bytestring - base16-bytestring - base64-bytestring - errors - text tests: text-conversions-test-suite: source-dirs: test main: Main.hs ghc-options: - -rtsopts - -threaded - -with-rtsopts=-N dependencies: - base - text-conversions - bytestring - hspec - hspec-discover - text text-conversions-0.3.0/README.md0000644000000000000000000000221612721364710014515 0ustar0000000000000000# text-conversions 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.0/stack.yaml0000644000000000000000000000012412721162055015221 0ustar0000000000000000resolver: lts-5.18 packages: ['.'] extra-deps: [] flags: {} extra-package-dbs: []