packages feed

text-conversions (empty) → 0.1.0

raw patch · 10 files changed

+364/−0 lines, 10 filesdep +basedep +bytestringdep +errorssetup-changed

Dependencies added: base, bytestring, errors, hspec, hspec-discover, text, text-conversions

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Changelog++## 0.1.0 (May 24, 2016)++- Initial release
+ LICENSE view
@@ -0,0 +1,13 @@+Copyright 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.
+ README.md view
@@ -0,0 +1,23 @@+# 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 through the same exact interface as conversions between purely 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+> convertText (UTF8 ("hello" :: ByteString)) :: Maybe Text+Just "hello"+> convertText (UTF8 ("\xc3\x28" :: ByteString)) :: Maybe Text+Nothing+```
+ Setup.hs view
@@ -0,0 +1,7 @@+-- 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: <https://www.haskell.org/cabal/users-guide/installing-packages.html>.+import qualified Distribution.Simple++main :: IO ()+main = Distribution.Simple.defaultMain
+ package.yaml view
@@ -0,0 +1,49 @@+name: text-conversions+version: '0.1.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:+- FlexibleContexts+- FlexibleInstances+- MultiParamTypeClasses+- OverloadedStrings+- ScopedTypeVariables++library:+  source-dirs: src+  dependencies:+  - base >= 4.7 && < 5+  - 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
+ src/Data/Text/Conversions.hs view
@@ -0,0 +1,139 @@+{-# 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 automatically convert between two textual+  datatypes automatically, whatever they may be.++  Examples:++  >>> convertText ("hello" :: String) :: Text+  "hello"+  >>> convertText (UTF8 ("hello" :: ByteString)) :: Maybe Text+  Just "hello"+  >>> convertText (UTF8 ("\xc3\x28" :: ByteString)) :: Maybe Text+  Nothing+-}+module Data.Text.Conversions+  ( ConvertText(..)+  , DecodeText(..)+  , FromText(..)+  , ToText(..)+  , UTF8(..)+  ) 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++{-|+  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)++{-|+  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.+-}+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 (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.+-}+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':++  >>> convertText ("hello" :: String) :: Text+  "hello"++  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':++  >>> 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++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
+ stack.yaml view
@@ -0,0 +1,7 @@+resolver: lts-5.18++packages: ['.']+extra-deps: []++flags: {}+extra-package-dbs: []
+ test/Data/Text/ConversionsSpec.hs view
@@ -0,0 +1,60 @@+module Data.Text.ConversionsSpec 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)++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"++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 in functors with a DecodeText instance" $+      convertText 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)++      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)
+ test/Main.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ text-conversions.cabal view
@@ -0,0 +1,60 @@+-- 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.1.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: FlexibleContexts FlexibleInstances MultiParamTypeClasses OverloadedStrings ScopedTypeVariables+  ghc-options: -Wall+  build-depends:+      base >= 4.7 && < 5+    , 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: FlexibleContexts FlexibleInstances MultiParamTypeClasses OverloadedStrings ScopedTypeVariables+  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