packages feed

text-convert (empty) → 0.1.0.0

raw patch · 6 files changed

+542/−0 lines, 6 filesdep +QuickCheckdep +basedep +bytestring

Dependencies added: QuickCheck, base, bytestring, hspec, text, text-convert

Files

+ LICENSE view
@@ -0,0 +1,26 @@+Copyright 2025 Daniel Brice++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++1.  Redistributions of source code must retain the above copyright notice, this+    list of conditions and the following disclaimer.++2.  Redistributions in binary form must reproduce the above copyright notice,+    this list of conditions and the following disclaimer in the documentation+    and/or other materials provided with the distribution.++3.  Neither the name of the copyright holder nor the names of its contributors+    may be used to endorse or promote products derived from this software+    without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,49 @@+# text-convert++Convert between various types representing textual data. This library differs+from the similar 'string-conversions' library in that this library exports class+methods that are monomorphic in their return type. This enhances readability and+aids type inference.++![Haskell textual type conversions](haskell-textual-type-conversions.png)++The module `Text.Convert` exports seven one-method type classes. The methods are+designed to be input-type polymorphic and output-type monomorphic, to aid with+readability and type inference. The classes are:+- `ToString`, with method `asString`;+- `ToByteString`, with method `asByteString`;+- `ToLazyByteString`, with method `asLazyByteString`;+- `ToByteStringBuilder`, with method `asByteStringBuilder`;+- `ToText`, with method `asText`;+- `ToLazyText`, with method `asLazyText`; and+- `ToTextBuilder`, with method `asTextBuilder`.++Design goals in order of importance:+1. correctness (including totality);+2. efficiency; and+3. ease of use.+The author hopes that the design achieves these goals to a high degree.++To comment on correctness, we are using lenient UTF-8 decoding when converting+between `ByteString`s and `Text`s, which means the conversions are correct in+the sense that they are total, but they are incorrect in the sense that they may+replace some unicode characters with the usual replacement character. Since this+library serves a very general purpose, users will not necessarily place any+restrictions on the inputs of these conversion functions, so the author thought+that totality was important. Notably, this choice agrees with the behavior of+many text editors and readers that use similar decoding strategies.++To comment on efficiency, we avoid unnecessary intermediate conversions where+possible. For example, instead of converting `Text` to `ByteString` through+`String` (a design admirable in that it is simple, correct, and a quantity of+code that is linear in number of supported datatypes), we choose to use a+bespoke conversion function for every pair of supported datatypes wherever+possible (resulting in a quadratic quantity of code). We also avoid unnecessary+allocation. For example, instead of converting `LazyByteString` to `Text`+through `LazyText` (an approach that would entail creating a new rope of `Text`+that would immediately be converted into a single array), we convert+`LazyByteString` to `Text` through `ByteString` (which is a single array). We+don't have any benchmarks to support this design choice, admittedly, as we+consider the cost of creating a benchmark suite overrides the benefits of having+one at this time. Should interest in this library grow, we may revisit this+decision.
+ src/tests-main/tests-main.hs view
@@ -0,0 +1,2 @@+module Main (main) where+import TestsMain (main)
+ src/tests/TestsMain.hs view
@@ -0,0 +1,77 @@+module TestsMain where++import Text.Convert++import Data.ByteString.Builder qualified as BB+import Data.ByteString.Char8 qualified as BC+import Data.ByteString.Lazy.Char8 qualified as BLC+import Data.Text qualified as T+import Data.Text.Encoding qualified as TE+import Data.Text.Encoding.Error qualified as TEE+import Data.Text.Lazy qualified as TL+import Data.Text.Lazy.Builder qualified as TLB+import Data.Text.Lazy.Encoding qualified as TLE+import Data.Typeable (Typeable, typeRep)+import Test.Hspec (Spec, describe, hspec, shouldBe)+import Test.Hspec.QuickCheck (prop)+import Test.QuickCheck.Arbitrary (Arbitrary)++main :: IO ()+main = hspec $ do+  describe "asString" $ do+    identityProp asString id id "asString"+    weakInverseProp asString id id "BC.pack" BC.pack+    weakInverseProp asString id id "BLC.pack" BLC.pack+    weakInverseProp asString id id "T.pack" T.pack+    weakInverseProp asString id id "TL.pack" TL.pack+    weakInverseProp asString id id "TLB.fromString" TLB.fromString++  describe "asByteString" $ do+    identityProp asByteString BC.pack id "asByteString"+    weakInverseProp asByteString BC.pack id "BC.unpack" BC.unpack+    weakInverseProp asByteString BC.pack id "BLC.fromStrict" BLC.fromStrict+    weakInverseProp asByteString BC.pack id "TE.decodeUtf8With TEE.lenientDecode" (TE.decodeUtf8With TEE.lenientDecode)+    weakInverseProp asByteString BC.pack BB.toLazyByteString "BB.byteString" BB.byteString++  describe "asLazyByteString" $ do+    identityProp asLazyByteString BLC.pack id "asLazyByteString"+    weakInverseProp asLazyByteString BLC.pack id "id" BLC.unpack+    weakInverseProp asLazyByteString BLC.pack id "id" BLC.toStrict+    weakInverseProp asLazyByteString BLC.pack id "TLE.decodeUtf8With TEE.lenientDecode" (TLE.decodeUtf8With TEE.lenientDecode)+    weakInverseProp asLazyByteString BLC.pack BB.toLazyByteString "BB.lazyByteString" BB.lazyByteString++  describe "asText" $ do+    identityProp asText T.pack id "asText"+    weakInverseProp asText T.pack id "T.unpack" T.unpack+    weakInverseProp asText T.pack id "TE.encodeUtf8" TE.encodeUtf8+    weakInverseProp asText T.pack id "TL.fromStrict" TL.fromStrict+    weakInverseProp asText T.pack id "TLB.fromText" TLB.fromText++  describe "asLazyText" $ do+    identityProp asLazyText TL.pack id "asLazyText"+    weakInverseProp asLazyText TL.pack id "TL.unpack" TL.unpack+    weakInverseProp asLazyText TL.pack id "TLE.encodeUtf8" TLE.encodeUtf8+    weakInverseProp asLazyText TL.pack id "TL.toStrict" TL.toStrict+    weakInverseProp asLazyText TL.pack id "TLB.fromLazyText" TLB.fromLazyText++  describe "asTextBuilder" $ do+    identityProp asTextBuilder TLB.fromString id "asTextBuilder"+    weakInverseProp asTextBuilder TLB.fromString id "TLB.toLazyText" TLB.toLazyText++  describe "asByteStringBuilder" $ do+    identityProp asByteStringBuilder BB.stringUtf8 BB.toLazyByteString "asByteStringBuilder"+    weakInverseProp asByteStringBuilder BB.stringUtf8 id "BB.toLazyByteString" BB.toLazyByteString++identityProp :: forall a c t.+  (Arbitrary a, Typeable t, Show a, Show c, Eq c) =>+  (t -> t) -> (a -> t) -> (t -> c) -> String -> Spec+identityProp testFn gen cmp lbl =+  prop (lbl <> " should be the identity on " <> show (typeRep $ Nothing @t)) $+    \x -> (cmp . testFn . gen) x `shouldBe` (cmp . gen) x++weakInverseProp :: forall a c s t.+  (Arbitrary a, Typeable s, Show a, Show c, Eq c) =>+  (s -> t) -> (a -> t) -> (s -> c) -> String -> (t -> s) -> Spec+weakInverseProp testFn gen cmp lbl inv =+  prop (lbl <> " should be a weak inverse through " <> show (typeRep $ Nothing @s)) $+    \x -> (cmp . inv . testFn . inv . gen) x `shouldBe` (cmp . inv . gen) x
+ src/text-convert/Text/Convert.hs view
@@ -0,0 +1,303 @@+{- | Convert between various textual representation.++This module exports seven one-method type classes. The methods are+designed to be input-type polymorphic and output-type monomorphic, to aid with+readability and type inference. The classes are:+- `ToString`, with method `asString`;+- `ToByteString`, with method `asByteString`;+- `ToLazyByteString`, with method `asLazyByteString`;+- `ToByteStringBuilder`, with method `asByteStringBuilder`;+- `ToText`, with method `asText`;+- `ToLazyText`, with method `asLazyText`; and+- `ToTextBuilder`, with method `asTextBuilder`.++Design goals in order of importance:+1. correctness (including totality);+2. efficiency; and+3. ease of use.+The author hopes that the design achieves these goals to a high degree.++To comment on correctness, we are using lenient UTF-8 decoding when converting+between `ByteString`s and `Text`s, which means the conversions are correct in+the sense that they are total, but they are incorrect in the sense that they may+replace some unicode characters with the usual replacement character. Since this+library serves a very general purpose, users will not necessarily place any+restrictions on the inputs of these conversion functions, so the author thought+that totality was important. Notably, this choice agrees with the behavior of+many text editors and readers that use similar decoding strategies.++To comment on efficiency, we avoid unnecessary intermediate conversions where+possible. For example, instead of converting `Text` to `ByteString` through+`String` (a design admirable in that it is simple, correct, and a quantity of+code that is linear in number of supported datatypes), we choose to use a+bespoke conversion function for every pair of supported datatypes wherever+possible (resulting in a quadratic quantity of code). We also avoid unnecessary+allocation. For example, instead of converting `LazyByteString` to `Text`+through `LazyText` (an approach that would entail creating a new rope of `Text`+that would immediately be converted into a single array), we convert+`LazyByteString` to `Text` through `ByteString` (which is a single array). We+don't have any benchmarks to support this design choice, admittedly, as we+consider the cost of creating a benchmark suite overrides the benefits of having+one at this time. Should interest in this library grow, we may revisit this+decision.+-}+module Text.Convert (+  ToString(..),+  ToByteString(..),+  ToLazyByteString(..),+  ToByteStringBuilder(..),+  ToText(..),+  ToLazyText(..),+  ToTextBuilder(..),+  String,+  ByteString,+  LazyByteString,+  Text,+  LazyText,+) where++import Data.Text (Text)+import Data.ByteString (ByteString)+import Data.ByteString.Builder qualified as BB+import Data.ByteString.Char8 qualified as BC+import Data.ByteString.Lazy.Char8 qualified as BLC+import Data.Text qualified as T+import Data.Text.Encoding qualified as TE+import Data.Text.Encoding.Error qualified as TEE+import Data.Text.Lazy qualified as TL+import Data.Text.Lazy.Builder qualified as TLB+import Data.Text.Lazy.Encoding qualified as TLE++type LazyByteString = BLC.ByteString+type LazyText = TL.Text++class ToString a where+  asString :: a -> String++class ToByteString a where+  asByteString :: a -> BC.ByteString++class ToLazyByteString a where+  asLazyByteString :: a -> BLC.ByteString++class ToByteStringBuilder a where+  asByteStringBuilder :: a -> BB.Builder++class ToText a where+  asText :: a -> T.Text++class ToLazyText a where+  asLazyText :: a -> TL.Text++class ToTextBuilder a where+  asTextBuilder :: a -> TLB.Builder++-- ToString instances++instance ToString String where+  asString = id+  {-# INLINE asString #-}++instance ToString BC.ByteString where+  asString = BC.unpack+  {-# INLINE asString #-}++instance ToString BLC.ByteString where+  asString = BLC.unpack+  {-# INLINE asString #-}++instance ToString BB.Builder where+  asString = BLC.unpack . BB.toLazyByteString+  {-# INLINE asString #-}++instance ToString T.Text where+  asString = T.unpack+  {-# INLINE asString #-}++instance ToString TL.Text where+  asString = TL.unpack+  {-# INLINE asString #-}++instance ToString TLB.Builder where+  asString = TL.unpack . TLB.toLazyText+  {-# INLINE asString #-}++-- ToByteString instances++instance ToByteString String where+  asByteString = BC.pack+  {-# INLINE asByteString #-}++instance ToByteString BC.ByteString where+  asByteString = id+  {-# INLINE asByteString #-}++instance ToByteString BLC.ByteString where+  asByteString = BLC.toStrict+  {-# INLINE asByteString #-}++instance ToByteString BB.Builder where+  asByteString = BLC.toStrict . BB.toLazyByteString+  {-# INLINE asByteString #-}++instance ToByteString T.Text where+  asByteString = TE.encodeUtf8+  {-# INLINE asByteString #-}++instance ToByteString TL.Text where+  asByteString = TE.encodeUtf8 . TL.toStrict+  {-# INLINE asByteString #-}++instance ToByteString TLB.Builder where+  asByteString = TE.encodeUtf8 . TL.toStrict . TLB.toLazyText+  {-# INLINE asByteString #-}++-- ToLazyByteString instances++instance ToLazyByteString String where+  asLazyByteString = BLC.pack+  {-# INLINE asLazyByteString #-}++instance ToLazyByteString BC.ByteString where+  asLazyByteString = BLC.fromStrict+  {-# INLINE asLazyByteString #-}++instance ToLazyByteString BLC.ByteString where+  asLazyByteString = id+  {-# INLINE asLazyByteString #-}++instance ToLazyByteString BB.Builder where+  asLazyByteString = BB.toLazyByteString+  {-# INLINE asLazyByteString #-}++instance ToLazyByteString T.Text where+  asLazyByteString = BLC.fromStrict . TE.encodeUtf8+  {-# INLINE asLazyByteString #-}++instance ToLazyByteString TL.Text where+  asLazyByteString = TLE.encodeUtf8+  {-# INLINE asLazyByteString #-}++instance ToLazyByteString TLB.Builder where+  asLazyByteString = TLE.encodeUtf8 . TLB.toLazyText+  {-# INLINE asLazyByteString #-}++-- ToByteStringBuilder instances++instance ToByteStringBuilder String where+  asByteStringBuilder = BB.stringUtf8+  {-# INLINE asByteStringBuilder #-}++instance ToByteStringBuilder BC.ByteString where+  asByteStringBuilder = BB.byteString+  {-# INLINE asByteStringBuilder #-}++instance ToByteStringBuilder BLC.ByteString where+  asByteStringBuilder = BB.lazyByteString+  {-# INLINE asByteStringBuilder #-}++instance ToByteStringBuilder BB.Builder where+  asByteStringBuilder = id+  {-# INLINE asByteStringBuilder #-}++instance ToByteStringBuilder T.Text where+  asByteStringBuilder = BB.byteString . TE.encodeUtf8+  {-# INLINE asByteStringBuilder #-}++instance ToByteStringBuilder TL.Text where+  asByteStringBuilder = BB.lazyByteString . TLE.encodeUtf8+  {-# INLINE asByteStringBuilder #-}++instance ToByteStringBuilder TLB.Builder where+  asByteStringBuilder = BB.lazyByteString . TLE.encodeUtf8 . TLB.toLazyText+  {-# INLINE asByteStringBuilder #-}++-- ToText instances++instance ToText String where+  asText = T.pack+  {-# INLINE asText #-}++instance ToText BC.ByteString where+  asText = TE.decodeUtf8With TEE.lenientDecode+  {-# INLINE asText #-}++instance ToText BLC.ByteString where+  asText = undefined+  {-# INLINE asText #-}++instance ToText BB.Builder where+  asText = undefined+  {-# INLINE asText #-}++instance ToText T.Text where+  asText = id+  {-# INLINE asText #-}++instance ToText TL.Text where+  asText = TL.toStrict+  {-# INLINE asText #-}++instance ToText TLB.Builder where+  asText = TL.toStrict . TLB.toLazyText+  {-# INLINE asText #-}++-- ToLazyText instances++instance ToLazyText String where+  asLazyText = TL.pack+  {-# INLINE asLazyText #-}++instance ToLazyText BC.ByteString where+  asLazyText = TL.fromStrict . TE.decodeUtf8With TEE.lenientDecode+  {-# INLINE asLazyText #-}++instance ToLazyText BLC.ByteString where+  asLazyText = TLE.decodeUtf8With TEE.lenientDecode+  {-# INLINE asLazyText #-}++instance ToLazyText BB.Builder where+  asLazyText = TLE.decodeUtf8With TEE.lenientDecode . BB.toLazyByteString+  {-# INLINE asLazyText #-}++instance ToLazyText T.Text where+  asLazyText = TL.fromStrict+  {-# INLINE asLazyText #-}++instance ToLazyText TL.Text where+  asLazyText = id+  {-# INLINE asLazyText #-}++instance ToLazyText TLB.Builder where+  asLazyText = TLB.toLazyText+  {-# INLINE asLazyText #-}++-- ToTextBuilder instances++instance ToTextBuilder String where+  asTextBuilder = TLB.fromString+  {-# INLINE asTextBuilder #-}++instance ToTextBuilder BC.ByteString where+  asTextBuilder = TLB.fromText . TE.decodeUtf8With TEE.lenientDecode+  {-# INLINE asTextBuilder #-}++instance ToTextBuilder BLC.ByteString where+  asTextBuilder = TLB.fromLazyText . TLE.decodeUtf8With TEE.lenientDecode+  {-# INLINE asTextBuilder #-}++instance ToTextBuilder BB.Builder where+  asTextBuilder = TLB.fromLazyText . TLE.decodeUtf8With TEE.lenientDecode . BB.toLazyByteString+  {-# INLINE asTextBuilder #-}++instance ToTextBuilder T.Text where+  asTextBuilder = TLB.fromText+  {-# INLINE asTextBuilder #-}++instance ToTextBuilder TL.Text where+  asTextBuilder = TLB.fromLazyText+  {-# INLINE asTextBuilder #-}++instance ToTextBuilder TLB.Builder where+  asTextBuilder = id+  {-# INLINE asTextBuilder #-}
+ text-convert.cabal view
@@ -0,0 +1,85 @@+cabal-version:      2.2+name:               text-convert+version:            0.1.0.0+category:           Data+synopsis:           Convert between various textual representation.+description:+  Convert between various types representing textual data. This library differs+  from the similar 'string-conversions' library in that this library exports+  class methods that are monomorphic in their return type. This enhances+  readability and aids type inference.++homepage:           https://github.com/friedbrice/text-convert#readme+bug-reports:        https://github.com/friedbrice/text-convert/issues+author:             Daniel Brice+maintainer:         danielbrice@gmail.com+copyright:          2025 Daniel Brice+license:            BSD-3-Clause+license-file:       LICENSE+extra-source-files: README.md+build-type:         Simple+tested-with:+  GHC ==9.2.8+   || ==9.4.8+   || ==9.6.7+   || ==9.8.4+   || ==9.10.2+   || ==9.12.2++source-repository head+  type:     git+  location: https://github.com/friedbrice/text-convert++common dialect+  default-language:   Haskell2010+  ghc-options:        -Wall+  default-extensions:+    FlexibleInstances+    ImportQualifiedPost+    ScopedTypeVariables+    TypeApplications++  build-depends:+    , base        >=4.16 && <5+    , bytestring  >=0.11 && <0.13+    , text        >=1.2  && <2.2++common deps-tests+  build-depends:+    , hspec+    , QuickCheck+    , text-convert++library+  import:          dialect+  hs-source-dirs:  src/text-convert++  -- cabal-fmt: expand src/text-convert+  exposed-modules: Text.Convert++test-suite tests+  import:         dialect+  import:         deps-tests+  type:           exitcode-stdio-1.0+  build-depends:  text-convert+  hs-source-dirs: src/tests src/tests-main+  main-is:        tests-main.hs++  -- cabal-fmt: expand src/tests+  other-modules:  TestsMain++test-suite dev+  import:             dialect+  import:             deps-tests+  type:               exitcode-stdio-1.0+  hs-source-dirs:     src/text-convert src/tests src/tests-main+  main-is:            tests-main.hs+  default-extensions:+    InstanceSigs+    PartialTypeSignatures++  -- cabal-fmt: expand src/text-convert+  -- cabal-fmt: expand src/tests+  other-modules:+    TestsMain+    Text.Convert