├── .gitignore ├── LICENSE ├── README.md ├── Setup.hs ├── changelog.md ├── package.yaml ├── src └── Data │ └── String │ └── Conv.hs ├── stack.yaml ├── stack.yaml.lock └── test └── Main.hs /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | dist-* 3 | TAGS 4 | *.swp 5 | .stack-work/ 6 | string-conv.cabal 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Ozgun Ataman 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Ozgun Ataman nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # string-conv 2 | 3 | A type class to standardize string conversions. With this type class you only 4 | need to remember one function for converting between any two string variants. 5 | This package includes support for String, ByteString, and Text as well as the 6 | Lazy and Strict variants where necessary. 7 | 8 | StringConv's `toS` function is most useful when you have a fully defined 9 | string conversion with a fixed (non-polymorphic) input and output type. Of 10 | course you can still use it when you don't have a fixed type. In that case 11 | you might need to specify a type class constraint such as `StringConv s 12 | String`. 13 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | 3 | main = defaultMain 4 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | 0.2.0 2 | ===== 3 | * Don't set optimization flag for library 4 | * Add property tests and fix to make String <-> ByteString conversion safer [PR 6](https://github.com/Soostone/string-conv/pull/6). 5 | * Add lib-Werror flag for development 6 | 7 | 0.1.1 8 | ===== 9 | * Fixed bug where decoding text from bytestrings would always decode 10 | leniently, even if Strict was used. 11 | 12 | 0.1 13 | === 14 | 15 | * Initial release 16 | -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: string-conv 3 | version: 0.2.0 4 | synopsis: Standardized conversion between string types 5 | description: > 6 | Avoids the need to remember many different functions for converting string 7 | types. Just use one universal function toS for all monomorphic string 8 | conversions. 9 | license: BSD3 10 | license-file: LICENSE 11 | author: Ozgun Ataman 12 | maintainer: ozgun.ataman@soostone.com 13 | copyright: Soostone Inc, 2012-2015 14 | category: Data, String, Text 15 | homepage: https://github.com/Soostone/string-conv 16 | bug-reports: https://github.com/Soostone/string-conv/issues 17 | build-type: Simple 18 | extra-source-files: 19 | - README.md 20 | - changelog.md 21 | github: Soostone/string-conv 22 | 23 | flags: 24 | lib-Werror: 25 | manual: true 26 | default: false 27 | description: > 28 | Turn on -Wall and -Werror. Should always be enabled in development. 29 | 30 | when: 31 | - condition: flag(lib-Werror) 32 | then: 33 | ghc-options: 34 | - -Wall 35 | - -Werror 36 | - -fwarn-redundant-constraints 37 | - -Wincomplete-record-updates 38 | - -Wincomplete-uni-patterns 39 | - -Widentities 40 | else: 41 | ghc-options: 42 | - -Wall 43 | - -fwarn-redundant-constraints 44 | - -Wincomplete-record-updates 45 | - -Widentities 46 | 47 | library: 48 | source-dirs: 49 | - src 50 | dependencies: 51 | - base >= 4.4 && < 5 52 | - bytestring 53 | - text 54 | 55 | tests: 56 | tests: 57 | source-dirs: 58 | - test 59 | main: Main.hs 60 | ghc-options: 61 | - -threaded 62 | - -rtsopts 63 | - -with-rtsopts=-N 64 | dependencies: 65 | - base >= 4.4 && < 5 66 | - bytestring 67 | - quickcheck-instances >= 0.3.17 68 | - string-conv 69 | - tasty 70 | - tasty-quickcheck 71 | - text 72 | -------------------------------------------------------------------------------- /src/Data/String/Conv.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | {-# LANGUAGE MultiParamTypeClasses #-} 3 | 4 | module Data.String.Conv 5 | ( StringConv (..), 6 | toS, 7 | toSL, 8 | convS, 9 | convSL, 10 | Leniency (..), 11 | ) 12 | where 13 | 14 | ------------------------------------------------------------------------------ 15 | import Data.ByteString as B 16 | import Data.ByteString.Lazy as LB 17 | import Data.Text as T 18 | import Data.Text.Encoding as T 19 | import Data.Text.Encoding.Error as T 20 | import Data.Text.Lazy as LT 21 | import Data.Text.Lazy.Encoding as LT 22 | 23 | ------------------------------------------------------------------------------ 24 | 25 | ------------------------------------------------------------------------------ 26 | 27 | -- | Data type representing the two leniency modes defining how decoding 28 | -- failure is handled. 29 | data Leniency = Lenient | Strict 30 | deriving (Eq, Show, Read, Ord, Enum, Bounded) 31 | 32 | ------------------------------------------------------------------------------ 33 | 34 | -- | A type class to standardize string conversions. With this type class you 35 | -- only need to remember one function for converting between any two string 36 | -- variants. This package includes support for String, ByteString, and Text 37 | -- as well as the Lazy and Strict variants where necessary. 38 | -- 39 | -- This type class lets you control how conversion should behave when failure 40 | -- is possible. Strict mode will cause an exception to be thrown when 41 | -- decoding fails. Lenient mode will attempt to recover, inserting a 42 | -- replacement character for invalid bytes. 43 | -- 44 | -- StringConv's `toS` function is most useful when you have a fully defined 45 | -- string conversion with a fixed (non-polymorphic) input and output type. Of 46 | -- course you can still use it when you don't have a fixed type. In that case 47 | -- you might need to specify a type class constraint such as @StringConv 48 | -- s String@. 49 | class StringConv a b where 50 | strConv :: Leniency -> a -> b 51 | 52 | ------------------------------------------------------------------------------ 53 | 54 | -- | Universal string conversion function for strict decoding. 55 | toS :: StringConv a b => a -> b 56 | toS = strConv Strict 57 | 58 | ------------------------------------------------------------------------------ 59 | 60 | -- | Universal string conversion function for lenient decoding. 61 | toSL :: StringConv a b => a -> b 62 | toSL = strConv Lenient 63 | 64 | instance StringConv String String where strConv _ = id 65 | 66 | instance StringConv String B.ByteString where strConv _ = T.encodeUtf8 . T.pack 67 | 68 | instance StringConv String LB.ByteString where strConv _ = LT.encodeUtf8 . LT.pack 69 | 70 | instance StringConv String T.Text where strConv _ = T.pack 71 | 72 | instance StringConv String LT.Text where strConv _ = LT.pack 73 | 74 | instance StringConv B.ByteString String where strConv l = T.unpack . decodeUtf8T l 75 | 76 | instance StringConv B.ByteString B.ByteString where strConv _ = id 77 | 78 | instance StringConv B.ByteString LB.ByteString where strConv _ = LB.fromChunks . return 79 | 80 | instance StringConv B.ByteString T.Text where strConv = decodeUtf8T 81 | 82 | instance StringConv B.ByteString LT.Text where strConv l = strConv l . LB.fromChunks . return 83 | 84 | instance StringConv LB.ByteString String where strConv l = LT.unpack . decodeUtf8LT l 85 | 86 | instance StringConv LB.ByteString B.ByteString where strConv _ = B.concat . LB.toChunks 87 | 88 | instance StringConv LB.ByteString LB.ByteString where strConv _ = id 89 | 90 | instance StringConv LB.ByteString T.Text where strConv l = decodeUtf8T l . strConv l 91 | 92 | instance StringConv LB.ByteString LT.Text where strConv = decodeUtf8LT 93 | 94 | instance StringConv T.Text String where strConv _ = T.unpack 95 | 96 | instance StringConv T.Text B.ByteString where strConv _ = T.encodeUtf8 97 | 98 | instance StringConv T.Text LB.ByteString where strConv l = strConv l . T.encodeUtf8 99 | 100 | instance StringConv T.Text LT.Text where strConv _ = LT.fromStrict 101 | 102 | instance StringConv T.Text T.Text where strConv _ = id 103 | 104 | instance StringConv LT.Text String where strConv _ = LT.unpack 105 | 106 | instance StringConv LT.Text T.Text where strConv _ = LT.toStrict 107 | 108 | instance StringConv LT.Text LT.Text where strConv _ = id 109 | 110 | instance StringConv LT.Text LB.ByteString where strConv _ = LT.encodeUtf8 111 | 112 | instance StringConv LT.Text B.ByteString where strConv l = strConv l . LT.encodeUtf8 113 | 114 | ------------------------------------------------------------------------------ 115 | 116 | -- | Convenience helper for dispatching based on leniency. 117 | decodeUtf8T :: Leniency -> B.ByteString -> T.Text 118 | decodeUtf8T Lenient = T.decodeUtf8With T.lenientDecode 119 | decodeUtf8T Strict = T.decodeUtf8With T.strictDecode 120 | 121 | ------------------------------------------------------------------------------ 122 | 123 | -- | Convenience helper for dispatching based on leniency. 124 | decodeUtf8LT :: Leniency -> LB.ByteString -> LT.Text 125 | decodeUtf8LT Lenient = LT.decodeUtf8With T.lenientDecode 126 | decodeUtf8LT Strict = LT.decodeUtf8With T.strictDecode 127 | 128 | ------------------------------------------------------------------------------ 129 | 130 | -- | A lens for 'toS' to make it slightly more convenient in some scenarios. 131 | convS :: (StringConv a b, StringConv b a, Functor f) => (b -> f b) -> a -> f a 132 | convS f a = fmap toS (f (toS a)) 133 | 134 | ------------------------------------------------------------------------------ 135 | 136 | -- | A lens for 'toSL' to make it slightly more convenient in some scenarios. 137 | convSL :: (StringConv a b, StringConv b a, Functor f) => (b -> f b) -> a -> f a 138 | convSL f a = fmap toSL (f (toSL a)) 139 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-18.27 2 | packages: 3 | - . 4 | flags: 5 | string-conv: 6 | lib-Werror: true 7 | -------------------------------------------------------------------------------- /stack.yaml.lock: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by Stack. 2 | # You should not edit this file by hand. 3 | # For more information, please see the documentation at: 4 | # https://docs.haskellstack.org/en/stable/lock_files 5 | 6 | packages: [] 7 | snapshots: 8 | - completed: 9 | size: 590102 10 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/27.yaml 11 | sha256: 79a786674930a89301b0e908fad2822a48882f3d01486117693c377b8edffdbe 12 | original: lts-18.27 13 | -------------------------------------------------------------------------------- /test/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | import Data.ByteString as B 4 | import Data.ByteString.Lazy as LB 5 | import Data.String.Conv 6 | import Data.Text as T 7 | import Data.Text.Lazy as LT 8 | import Test.QuickCheck.Instances.ByteString () 9 | import Test.QuickCheck.Instances.Text () 10 | import Test.Tasty 11 | import Test.Tasty.QuickCheck 12 | 13 | main :: IO () 14 | main = 15 | defaultMain $ 16 | testGroup 17 | " tests" 18 | [ strictDecoding, 19 | lenientDecoding 20 | ] 21 | 22 | strictDecoding :: TestTree 23 | strictDecoding = 24 | testGroup 25 | "strict decoding (toS method)" 26 | [ testProperty "converting String to String" $ do 27 | \s -> s == (toS (toS (s :: String) :: String)), 28 | testProperty "converting String to strict ByteString" $ do 29 | \s -> s == (toS (toS (s :: String) :: B.ByteString)), 30 | testProperty "converting String to lazy ByteString" $ do 31 | \s -> s == (toS (toS (s :: String) :: LB.ByteString)), 32 | testProperty "converting String to strict Text" $ do 33 | \s -> s == (toS (toS (s :: String) :: T.Text)), 34 | testProperty "converting String to lazy Text" $ do 35 | \s -> s == (toS (toS (s :: String) :: LT.Text)), 36 | testProperty "converting strict ByteString to strict ByteString" $ do 37 | \s -> s == (toS (toS (s :: B.ByteString) :: B.ByteString)), 38 | testProperty "converting strict ByteString to lazy ByteString" $ do 39 | \s -> s == (toS (toS (s :: B.ByteString) :: LB.ByteString)), 40 | testProperty "converting lazy ByteString to lazy ByteString" $ do 41 | \s -> s == (toS (toS (s :: LB.ByteString) :: LB.ByteString)), 42 | testProperty "converting strict Text to lazy ByteString" $ do 43 | \s -> s == (toS (toS (s :: T.Text) :: LB.ByteString)), 44 | testProperty "converting strict Text to strict ByteString" $ do 45 | \s -> s == (toS (toS (s :: LB.ByteString) :: B.ByteString)), 46 | testProperty "converting strict Text to strict Text" $ do 47 | \s -> s == (toS (toS (s :: T.Text) :: T.Text)), 48 | testProperty "converting strict Text to lazy Text" $ do 49 | \s -> s == (toS (toS (s :: T.Text) :: LT.Text)), 50 | testProperty "converting lazy Text to strict ByteString" $ do 51 | \s -> s == (toS (toS (s :: LT.Text) :: B.ByteString)), 52 | testProperty "converting lazy Text to lazy ByteString" $ do 53 | \s -> s == (toS (toS (s :: LT.Text) :: LB.ByteString)), 54 | testProperty "converting lazy Text to lazy Text" $ do 55 | \s -> s == (toS (toS (s :: LT.Text) :: LT.Text)) 56 | ] 57 | 58 | lenientDecoding :: TestTree 59 | lenientDecoding = 60 | testGroup 61 | "lenient decoding (toSL method)" 62 | [ testProperty "converting String to String" $ do 63 | \s -> s == (toSL (toSL (s :: String) :: String)), 64 | testProperty "converting String to strict ByteString" $ do 65 | \s -> s == (toSL (toSL (s :: String) :: B.ByteString)), 66 | testProperty "converting String to lazy ByteString" $ do 67 | \s -> s == (toSL (toSL (s :: String) :: LB.ByteString)), 68 | testProperty "converting String to strict Text" $ do 69 | \s -> s == (toSL (toSL (s :: String) :: T.Text)), 70 | testProperty "converting String to lazy Text" $ do 71 | \s -> s == (toSL (toSL (s :: String) :: LT.Text)), 72 | testProperty "converting strict ByteString to strict ByteString" $ do 73 | \s -> s == (toSL (toSL (s :: B.ByteString) :: B.ByteString)), 74 | testProperty "converting strict ByteString to lazy ByteString" $ do 75 | \s -> s == (toSL (toSL (s :: B.ByteString) :: LB.ByteString)), 76 | testProperty "converting lazy ByteString to lazy ByteString" $ do 77 | \s -> s == (toSL (toSL (s :: LB.ByteString) :: LB.ByteString)), 78 | testProperty "converting strict Text to lazy ByteString" $ do 79 | \s -> s == (toSL (toSL (s :: T.Text) :: LB.ByteString)), 80 | testProperty "converting strict Text to strict ByteString" $ do 81 | \s -> s == (toSL (toSL (s :: LB.ByteString) :: B.ByteString)), 82 | testProperty "converting strict Text to strict Text" $ do 83 | \s -> s == (toSL (toSL (s :: T.Text) :: T.Text)), 84 | testProperty "converting strict Text to lazy Text" $ do 85 | \s -> s == (toSL (toSL (s :: T.Text) :: LT.Text)), 86 | testProperty "converting lazy Text to strict ByteString" $ do 87 | \s -> s == (toSL (toSL (s :: LT.Text) :: B.ByteString)), 88 | testProperty "converting lazy Text to lazy ByteString" $ do 89 | \s -> s == (toSL (toSL (s :: LT.Text) :: LB.ByteString)), 90 | testProperty "converting lazy Text to lazy Text" $ do 91 | \s -> s == (toSL (toSL (s :: LT.Text) :: LT.Text)) 92 | ] 93 | --------------------------------------------------------------------------------