packages feed

string-interpolate 0.0.1.0 → 0.1.0.0

raw patch · 6 files changed

+445/−183 lines, 6 filesdep +Interpolationdep +interpolatedstring-perl6

Dependencies added: Interpolation, interpolatedstring-perl6

Files

CHANGELOG.md view
@@ -2,6 +2,12 @@  ## Unreleased +## v0.1.0.0 (2019-03-17)+++ Add support for using Text and ByteString `Builder`s as both sinks+  and sources++ Add support for interpolating Chars without the surrounding quotes+ ## v0.0.1.0 (2019-03-10)  + Initial release
README.md view
@@ -1,4 +1,4 @@-# string-interpolate+# string-interpolate [![pipeline status](https://gitlab.com/williamyaoh/string-interpolate/badges/master/pipeline.svg)](https://gitlab.com/williamyaoh/string-interpolate/commits/master) [![hackage version](https://img.shields.io/hackage/v/string-interpolate.svg)](http://hackage.haskell.org/package/string-interpolate) [![license](https://img.shields.io/badge/license-BSD--3-ff69b4.svg)](https://gitlab.com/williamyaoh/string-interpolate/blob/master/LICENSE)  Haskell having 5 different textual types in common use (String, strict and lazy Text, strict and lazy ByteString) means that doing any kind of string@@ -71,16 +71,35 @@ Wrap anything you want to be interpolated with `#{}`:  ```haskell+λ> name = "William"+λ> [i|Hello, #{name}!|] :: String+>>> "Hello, William!"+```++You can interpolate in anything which implements `Show`:++```haskell λ> import Data.Time λ> now <- getCurrentTime λ> [i|The current time is #{now}.|] :: String >>> "The current time is 2019-03-10 18:58:40.573892546 UTC." ``` +...and interpolate into anything which implements `IsString`.+ string-interpolate *must* know what concrete type it's producing; it cannot be used to generate a `IsString a => a`. If you're using string-interpolate from GHCi, make sure to add type signatures to toplevel usages! +Strings and characters are always interpolated without surrounding quotes.++```haskell+λ> verb = 'c'+λ> noun = "sea"+λ> [i|We went to go #{verb} the #{noun}.|] :: String+>>> "We went to go c the sea."+```+ You can also interpolate arbitrary expressions:  ```haskell@@ -105,96 +124,60 @@ * [formatting](https://hackage.haskell.org/package/formatting) * Text.Printf, from base * [neat-interpolation](https://hackage.haskell.org/package/neat-interpolation)+* [Interpolation](http://hackage.haskell.org/package/Interpolation)+* [interpolatedstring-perl6](http://hackage.haskell.org/package/interpolatedstring-perl6-1.0.1)  Of these, Text.Printf isn't exception-safe, and neat-interpolation can only-produce Text values. interpolate and formatting solve the same problem of+produce Text values. interpolate, formatting, Interpolation, and+interpolatedstring-perl6 provide different solutions to the problem of providing a general way of interpolating any value, into any kind of text.  ### Features -|                                          | string-interpolate | interpolate | formatting |-|------------------------------------------|--------------------|-------------|------------|-| String/Text support                      | ✅                  | ✅           | ✅          |-| ByteString support                       | ✅                  | ✅           | ❌          |-| Can interpolate arbitrary Show instances | ✅                  | ✅           | ✅          |-| Unicode-aware                            | ✅                  | ❌           | ✅          |--### Performance--Overall: string-interpolate seems to be on-par with or faster than existing-interpolation libraries for all text cases.--Testing on my local machine, creating small (<100 character) Strings shows-string-interpolate as on-par with interpolate and significantly (> 20X) faster-than formatting:--```-benchmarking Small Strings Bench/string-interpolate-time                 8.548 ns   (8.543 ns .. 8.554 ns)-                     1.000 R²   (1.000 R² .. 1.000 R²)-mean                 8.516 ns   (8.504 ns .. 8.527 ns)-std dev              35.90 ps   (29.23 ps .. 43.63 ps)--benchmarking Small Strings Bench/interpolate-time                 9.047 ns   (9.042 ns .. 9.052 ns)-                     1.000 R²   (1.000 R² .. 1.000 R²)-mean                 9.028 ns   (9.019 ns .. 9.036 ns)-std dev              27.06 ps   (21.88 ps .. 34.20 ps)--benchmarking Small Strings Bench/formatting-time                 222.3 ns   (221.7 ns .. 223.1 ns)-                     1.000 R²   (1.000 R² .. 1.000 R²)-mean                 222.1 ns   (221.6 ns .. 222.8 ns)-std dev              1.902 ns   (1.530 ns .. 2.446 ns)-```--I suspect the poor performance of formatting is caused by using `(++)` to-concatenate Strings instead of ShowS.--Doing the same test, but generating small Text and ByteStrings shows-string-interpolate to be on-par with formatting, and significantly (> 20x) faster-than interpolate.+|                                          	| string-interpolate 	| interpolate 	| formatting 	| Interpolation 	| interpolatedstring-perl6 	|+|------------------------------------------	|--------------------	|-------------	|------------	|---------------	|--------------------------	|+| String/Text support                      	| ✅                  	| ✅           	| ✅          	| ⚠️             	| ✅                        	|+| ByteString support                       	| ✅                  	| ✅           	| ❌          	| ⚠️             	| ✅                        	|+| Can interpolate arbitrary Show instances 	| ✅                  	| ✅           	| ✅          	| ✅             	| ✅                        	|+| Unicode-aware                            	| ✅                  	| ❌           	| ✅          	| ❌             	| ❌                        	|+| Multiline strings                        	| ❌                  	| ❌           	| ❌          	| ✅             	| ✅                        	| -For Text:+⚠ `Interpolation` supports all five textual formats, but doesn't allow you+  to mix and match; that is, you can't interpolate a String into an output+  string of type Text, and vice versa. -```-benchmarking Small Text Bench/string-interpolate-time                 173.7 ns   (173.1 ns .. 174.1 ns)-                     1.000 R²   (1.000 R² .. 1.000 R²)-mean                 173.1 ns   (172.8 ns .. 173.5 ns)-std dev              1.230 ns   (1.042 ns .. 1.486 ns)+### Performance -benchmarking Small Text Bench/interpolate-time                 4.398 μs   (4.371 μs .. 4.425 μs)-                     1.000 R²   (1.000 R² .. 1.000 R²)-mean                 4.403 μs   (4.389 μs .. 4.421 μs)-std dev              54.07 ns   (41.42 ns .. 80.40 ns)+Overall: **string-interpolate** is competitive with the fastest interpolation+libraries, only getting outperformed by **Interpolation** and+**interpolatedstring-perl6**, and even then mostly on ByteStrings. Since+these two libraries don't handle Unicode and **string-interpolate** converts+things to UTF-8, some slowdown is to be expected here. -benchmarking Small Text Bench/formatting-time                 243.1 ns   (242.5 ns .. 243.8 ns)-                     1.000 R²   (1.000 R² .. 1.000 R²)-mean                 242.8 ns   (242.4 ns .. 243.4 ns)-std dev              1.665 ns   (1.443 ns .. 1.982 ns)-```+We run three benchmarks: small string interpolation (<100 chars) with a single+interpolation parameter; small strings with multiple interpolation parameters,+and large string (~100KB) interpolation. Each of these benchmarks is then+run against `String`, strict `Text`, and strict `ByteString`. -For ByteString (formatting doesn't support ByteStrings):+|                          	| **string-interpolate** 	| **interpolate** 	| **formatting** 	| **Interpolation** 	| **interpolatedstring-perl6** 	|+|--------------------------	|------------------------	|-----------------	|----------------	|-------------------	|------------------------------	|+| small String             	| 1x                     	| 1x              	| 2x             	| 1x                	| 1x                           	|+| multi interp, String     	| 1x                     	| 7x              	| 2.3x           	| 0.63x             	| 0.63x                        	|+| small Text               	| 1x                     	| 28x             	| 1.5x           	| 2.2x              	| 2.2x                         	|+| multi interp, Text       	| 1x                     	| 22x             	| 1.6x           	| 2.9x              	| 2.9x                         	|+| large Text               	| 1x                     	| 30,000x         	| 1x             	| 80x               	| 80x                          	|+| small ByteString         	| 1x                     	| 15x             	| N/A            	| 0.35x             	| 0.35x                        	|+| multi interp, ByteString 	| 1x                     	| 10x             	| N/A            	| 0.5x              	| 0.5x                         	|+| large ByteString         	| 1x                     	| 100,000x        	| N/A            	| 1.6x              	| 1.6x                         	| -```-benchmarking Small ByteString Bench/string-interpolate-time                 297.6 ns   (295.8 ns .. 299.8 ns)-                     1.000 R²   (0.999 R² .. 1.000 R²)-mean                 299.8 ns   (298.0 ns .. 302.3 ns)-std dev              7.176 ns   (5.417 ns .. 9.651 ns)-variance introduced by outliers: 33% (moderately inflated)+(We don't bother running tests on large `String`s, because no one is working+with data that large using `String` anyways.) -benchmarking Small ByteString Bench/interpolate-time                 4.389 μs   (4.352 μs .. 4.424 μs)-                     1.000 R²   (0.999 R² .. 1.000 R²)-mean                 4.374 μs   (4.350 μs .. 4.398 μs)-std dev              79.48 ns   (66.66 ns .. 96.14 ns)-variance introduced by outliers: 18% (moderately inflated)-```+In particular, notice that **Interpolation** and **interpolatedstring-perl6**+blow up on large Text; **string-interpolation** and **formatting** have+consistent performance across all benchmarks, with string-interpolation leading+the pack in `Text` cases. -Here interpolate is the poor performer, with the performance loss caused because-it converts all values to String before using `fromString` to convert them to-the target type.+All results were tested on my local machine. If you'd like to attempt to replicate+the results, the benchmarks are in `bench/` and can be run with a simple+`stack bench`.
bench/Bench.hs view
@@ -2,69 +2,196 @@ {-# LANGUAGE PackageImports    #-} {-# LANGUAGE QuasiQuotes       #-} -import Criterion      ( bench, bgroup, whnf )+import Criterion      ( bench, bgroup, env, nf ) import Criterion.Main ( defaultMain )  import qualified Data.ByteString as B import qualified Data.Text       as T -import qualified "string-interpolate" Data.String.Interpolate   as SI-import qualified "interpolate" Data.String.Interpolate.IsString as I-import           "formatting" Formatting                        ( (%) )-import qualified "formatting" Formatting                        as F-import qualified "formatting" Formatting.ShortFormatters        as F+import qualified "string-interpolate" Data.String.Interpolate             as SI+import qualified "interpolate" Data.String.Interpolate.IsString           as I+import           "Interpolation" Data.String.Interpolation                as N+import           "formatting" Formatting                                  ( (%) )+import qualified "formatting" Formatting                                  as F+import qualified "formatting" Formatting.ShortFormatters                  as F+import           "interpolatedstring-perl6" Text.InterpolatedString.Perl6 as P +import Test.QuickCheck+ ----------------------------------------------------------------------------------- Interpolating short Strings+-- Interpolating Strings -------------------------------------------------------------------------------- -shortStringSI :: String -> String-shortStringSI str = [SI.i|A fine day to die, #{str}.|]+stringSI :: String -> String+stringSI str = [SI.i|A fine day to die, #{str}.|] -shortStringI :: String -> String-shortStringI str = [I.i|A fine day to die, #{str}.|]+stringI :: String -> String+stringI str = [I.i|A fine day to die, #{str}.|] -shortStringF :: String -> String-shortStringF = F.formatToString ("A fine day to die, " % F.s % ".")+stringF :: String -> String+stringF = F.formatToString ("A fine day to die, " % F.s % ".") +stringN :: String -> String+stringN s = [str|A fine day to die, $s$.|]++stringP :: String -> String+stringP str = [qc|A fine day to die, {str}.|]+ ----------------------------------------------------------------------------------- Interpolating short Text+-- Interpolating Text -------------------------------------------------------------------------------- -shortTextSI :: T.Text -> T.Text-shortTextSI t = [SI.i|A fine day to die, #{t}.|]+textSI :: T.Text -> T.Text+textSI t = [SI.i|A fine day to die, #{t}.|] -shortTextI :: T.Text -> T.Text-shortTextI t = [I.i|A fine day to die, #{t}.|]+textI :: T.Text -> T.Text+textI t = [I.i|A fine day to die, #{t}.|] -shortTextF :: T.Text -> T.Text-shortTextF = F.sformat ("A fine day to die, " % F.st % ".")+textF :: T.Text -> T.Text+textF = F.sformat ("A fine day to die, " % F.st % ".") +textN :: T.Text -> T.Text+textN t = [str|A fine day to die, $t$.|]++textP :: T.Text -> T.Text+textP t = [qc|A fine day to die, {t}.|]+ ----------------------------------------------------------------------------------- Interpolating short ByteString+-- Interpolating ByteString -------------------------------------------------------------------------------- -shortByteStringSI :: B.ByteString -> B.ByteString-shortByteStringSI b = [SI.i|A fine day to die, #{b}.|]+byteStringSI :: B.ByteString -> B.ByteString+byteStringSI b = [SI.i|A fine day to die, #{b}.|] -shortByteStringI :: B.ByteString -> B.ByteString-shortByteStringI b = [I.i|A fine day to die, #{b}.|]+byteStringI :: B.ByteString -> B.ByteString+byteStringI b = [I.i|A fine day to die, #{b}.|] +byteStringN :: B.ByteString -> B.ByteString+byteStringN b = [str|A fine day to die, $b$.|]++byteStringP :: B.ByteString -> B.ByteString+byteStringP b = [qc|A fine day to die, {b}.|]++--------------------------------------------------------------------------------+-- Multiple String interpolations+--------------------------------------------------------------------------------++multiStringSI :: (Int, String, Bool) -> String+multiStringSI (x, y, z) = [SI.i| foo #{x} bar #{y} baz #{z} quux |]++multiStringI :: (Int, String, Bool) -> String+multiStringI (x, y, z) = [I.i| foo #{x} bar #{y} baz #{z} quux |]++multiStringF :: (Int, String, Bool) -> String+multiStringF (x, y, z) =+  F.formatToString (" foo " % F.d % " bar " % F.s % " baz " % F.sh % " quux ") x y z++multiStringN :: (Int, String, Bool) -> String+multiStringN (x, y, z) = [str| foo $:x$ bar $y$ baz $:z$ quux |]++multiStringP :: (Int, String, Bool) -> String+multiStringP (x, y, z) = [qc| foo {x} bar {y} baz {z} quux |]++--------------------------------------------------------------------------------+-- Multiple Text interpolations+--------------------------------------------------------------------------------++multiTextSI :: (Int, T.Text, Bool) -> T.Text+multiTextSI (x, y, z) = [SI.i| foo #{x} bar #{y} baz #{z} quux |]++multiTextI :: (Int, T.Text, Bool) -> T.Text+multiTextI (x, y, z) = [I.i| foo #{x} bar #{y} baz #{z} quux |]++multiTextF :: (Int, T.Text, Bool) -> T.Text+multiTextF (x, y, z) =+  F.sformat (" foo " % F.d % " bar " % F.st % " baz " % F.sh % " quux ") x y z++multiTextN :: (Int, T.Text, Bool) -> T.Text+multiTextN (x, y, z) = [str| foo $:x$ bar $y$ baz $:z$ quux |]++multiTextP :: (Int, T.Text, Bool) -> T.Text+multiTextP (x, y, z) = [qc| foo {x} bar {y} baz {z} quux |]++--------------------------------------------------------------------------------+-- Multiple ByteString interpolations+--------------------------------------------------------------------------------++multiByteStringSI :: (Int, B.ByteString, Bool) -> B.ByteString+multiByteStringSI (x, y, z) = [SI.i| foo #{x} bar #{y} baz #{z} quux |]++multiByteStringI :: (Int, B.ByteString, Bool) -> B.ByteString+multiByteStringI (x, y, z) = [I.i| foo #{x} bar #{y} baz #{z} quux |]++multiByteStringN :: (Int, B.ByteString, Bool) -> B.ByteString+multiByteStringN (x, y, z) = [str| foo $:x$ bar $y$ baz $:z$ quux |]++multiByteStringP :: (Int, B.ByteString, Bool) -> B.ByteString+multiByteStringP (x, y, z) = [qc| foo {x} bar {y} baz {z} quux |]+ main :: IO ()-main = defaultMain+main = defaultMain $   [ bgroup "Small Strings Bench" $-    [ bench "string-interpolate" $ whnf shortStringSI "William"-    , bench "interpolate"        $ whnf shortStringI "William"-    , bench "formatting"         $ whnf shortStringF "William"+    [ bench "string-interpolate"       $ nf stringSI "William"+    , bench "interpolate"              $ nf stringI "William"+    , bench "formatting"               $ nf stringF "William"+    , bench "Interpolation"            $ nf stringN "William"+    , bench "interpolatedstring-perl6" $ nf stringP "William"     ]   , bgroup "Small Text Bench" $-    [ bench "string-interpolate" $ whnf shortTextSI "William"-    , bench "interpolate"        $ whnf shortTextI "William"-    , bench "formatting"         $ whnf shortTextF "William"+    [ bench "string-interpolate"       $ nf textSI "William"+    , bench "interpolate"              $ nf textI "William"+    , bench "formatting"               $ nf textF "William"+    , bench "Interpolation"            $ nf textN "William"+    , bench "interpolatedstring-perl6" $ nf textP "William"     ]   , bgroup "Small ByteString Bench" $-    [ bench "string-interpolate" $ whnf shortByteStringSI "William"-    , bench "interpolate"        $ whnf shortByteStringI "William"+    [ bench "string-interpolate"       $ nf byteStringSI "William"+    , bench "interpolate"              $ nf byteStringI "William"     -- "formatting" doesn't support ByteStrings.+    , bench "Interpolation"            $ nf byteStringN "William"+    , bench "interpolatedstring-perl6" $ nf byteStringP "William"     ]+  , bgroup "Multiple Interpolations String Bench" $+    [ bench "string-interpolate"       $ nf multiStringSI (42, "CATALLAXY", True)+    , bench "interpolate"              $ nf multiStringI (42, "CATALLAXY", True)+    , bench "formatting"               $ nf multiStringF (42, "CATALLAXY", True)+    , bench "Interpolation"            $ nf multiStringN (42, "CATALLAXY", True)+    , bench "interpolatedstring-perl6" $ nf multiStringP (42, "CATALLAXY", True)+    ]+  , bgroup "Multiple Interpolations Text Bench" $+    [ bench "string-interpolate"       $ nf multiTextSI (42, "CATALLAXY", True)+    , bench "interpolate"              $ nf multiTextI (42, "CATALLAXY", True)+    , bench "formatting"               $ nf multiTextF (42, "CATALLAXY", True)+    , bench "Interpolation"            $ nf multiTextN (42, "CATALLAXY", True)+    , bench "interpolatedstring-perl6" $ nf multiTextP (42, "CATALLAXY", True)+    ]+  , bgroup "Multiple Interpolations ByteString Bench" $+    [ bench "string-interpolate"       $ nf multiByteStringSI (42, "CATALLAXY", True)+    , bench "interpolate"              $ nf multiByteStringI (42, "CATALLAXY", True)+    -- "formatting" doesn't support ByteStrings.+    , bench "Interpolation"            $ nf multiByteStringN (42, "CATALLAXY", True)+    , bench "interpolatedstring-perl6" $ nf multiByteStringP (42, "CATALLAXY", True)+    ]+  , env largeishText $ \ ~t -> bgroup "Largeish Text Bench" $+    [ bench "string-interpolate"       $ nf textSI t+    , bench "interpolate"              $ nf textI t+    , bench "formatting"               $ nf textF t+    , bench "Interpolation"            $ nf textN t+    , bench "interpolatedstring-perl6" $ nf textP t+    ]+  , env largeishByteString $ \ ~bs -> bgroup "Largeish ByteString Bench" $+    [ bench "string-interpolate"       $ nf byteStringSI bs+    , bench "interpolate"              $ nf byteStringI bs+    -- "formatting" doesn't support ByteStrings.+    , bench "Interpolation"            $ nf byteStringN bs+    , bench "interpolatedstring-perl6" $ nf byteStringP bs+    ]   ]++largeishText :: IO T.Text+largeishText =+  generate $ T.pack <$> Prelude.take 100000 <$> infiniteListOf arbitrary++largeishByteString :: IO B.ByteString+largeishByteString =+  generate $ B.pack <$> Prelude.take 100000 <$> infiniteListOf arbitrary
src/lib/Data/String/Interpolate/Conversion.hs view
@@ -24,7 +24,7 @@ import qualified "utf8-string" Data.ByteString.UTF8      as UTF8  import "base" Text.Read ( readMaybe )-import "base" Text.Show ( ShowS, showString )+import "base" Text.Show ( ShowS, showString, showChar )  -- | -- We wrap the builders in B so that we can add a phantom type parameter.@@ -37,8 +37,10 @@ type family IsCustomSink dst where   IsCustomSink T.Text = 'True   IsCustomSink LT.Text = 'True+  IsCustomSink LT.Builder = 'True   IsCustomSink B.ByteString = 'True   IsCustomSink LB.ByteString = 'True+  IsCustomSink LB.Builder = 'True   IsCustomSink _ = 'False  -- | Something that can be interpolated into.@@ -80,6 +82,13 @@   build _ (B l) (B r) = B $ l <> r   finalize _ = LT.toLazyText . unB +instance InterpSink 'True LT.Builder where+  type Builder 'True LT.Builder = LT.Builder++  ofString _ = B . LT.fromString+  build _ (B l) (B r) = B $ l <> r+  finalize _ = unB+ instance InterpSink 'True B.ByteString where   type Builder 'True B.ByteString = LB.Builder @@ -94,71 +103,146 @@   build _ (B l) (B r) = B $ l <> r   finalize _ = LB.toLazyByteString . unB +instance InterpSink 'True LB.Builder where+  type Builder 'True LB.Builder = LB.Builder++  ofString _ = B . LB.lazyByteString . unUTF8 . convertText+  build _ (B l) (B r) = B $ l <> r+  finalize _ = unB+ instance {-# OVERLAPPABLE #-} (Show src, IsString dst, IsCustomSink dst ~ 'False) => Interpolatable 'False src dst where   interpolate _ = B . shows+instance {-# OVERLAPS #-} (IsString dst, IsCustomSink dst ~ 'False) => Interpolatable 'False Char dst where+  interpolate _ = B . showChar instance {-# OVERLAPS #-} (IsString dst, IsCustomSink dst ~ 'False) => Interpolatable 'False String dst where   interpolate _ = B . showString instance {-# OVERLAPS #-} (IsString dst, IsCustomSink dst ~ 'False) => Interpolatable 'False T.Text dst where   interpolate _ = B . showString . T.unpack instance {-# OVERLAPS #-} (IsString dst, IsCustomSink dst ~ 'False) => Interpolatable 'False LT.Text dst where   interpolate _ = B . showString . LT.unpack+instance {-# OVERLAPS #-} (IsString dst, IsCustomSink dst ~ 'False) => Interpolatable 'False LT.Builder dst where+  interpolate _ = B . showString . LT.unpack . LT.toLazyText instance {-# OVERLAPS #-} (IsString dst, IsCustomSink dst ~ 'False) => Interpolatable 'False B.ByteString dst where   interpolate _ = B . showString . UTF8.toString instance {-# OVERLAPS #-} (IsString dst, IsCustomSink dst ~ 'False) => Interpolatable 'False LB.ByteString dst where   interpolate _ = B . showString . LUTF8.toString+instance {-# OVERLAPS #-} (IsString dst, IsCustomSink dst ~ 'False) => Interpolatable 'False LB.Builder dst where+  interpolate _ = B . showString . LUTF8.toString . LB.toLazyByteString  instance {-# OVERLAPPABLE #-} Show src => Interpolatable 'True src T.Text where   interpolate _ = B . LT.fromString . show+instance {-# OVERLAPS #-} Interpolatable 'True Char T.Text where+  interpolate _ = B . LT.singleton instance {-# OVERLAPS #-} Interpolatable 'True String T.Text where   interpolate _ = B . LT.fromString instance {-# OVERLAPS #-} Interpolatable 'True T.Text T.Text where   interpolate _ = B . LT.fromText instance {-# OVERLAPS #-} Interpolatable 'True LT.Text T.Text where   interpolate _ = B . LT.fromLazyText+instance {-# OVERLAPS #-} Interpolatable 'True LT.Builder T.Text where+  interpolate _ = B instance {-# OVERLAPS #-} Interpolatable 'True B.ByteString T.Text where   interpolate _ = B . bsToTextBuilder instance {-# OVERLAPS #-} Interpolatable 'True LB.ByteString T.Text where   interpolate _ = B . lbsToTextBuilder+instance {-# OVERLAPS #-} Interpolatable 'True LB.Builder T.Text where+  interpolate _ = B . lbsToTextBuilder . LB.toLazyByteString  instance {-# OVERLAPPABLE #-} Show src => Interpolatable 'True src LT.Text where   interpolate _ = B . LT.fromString . show+instance {-# OVERLAPS #-} Interpolatable 'True Char LT.Text where+  interpolate _ = B . LT.singleton instance {-# OVERLAPS #-} Interpolatable 'True String LT.Text where   interpolate _ = B . LT.fromString instance {-# OVERLAPS #-} Interpolatable 'True T.Text LT.Text where   interpolate _ = B . LT.fromText instance {-# OVERLAPS #-} Interpolatable 'True LT.Text LT.Text where   interpolate _ = B . LT.fromLazyText+instance {-# OVERLAPS #-} Interpolatable 'True LT.Builder LT.Text where+  interpolate _ = B instance {-# OVERLAPS #-} Interpolatable 'True B.ByteString LT.Text where   interpolate _ = B . bsToTextBuilder instance {-# OVERLAPS #-} Interpolatable 'True LB.ByteString LT.Text where   interpolate _ = B . lbsToTextBuilder+instance {-# OVERLAPS #-} Interpolatable 'True LB.Builder LT.Text where+  interpolate _ = B . lbsToTextBuilder . LB.toLazyByteString +instance {-# OVERLAPPABLE #-} Show src => Interpolatable 'True src LT.Builder where+  interpolate _ = B . LT.fromString . show+instance {-# OVERLAPS #-} Interpolatable 'True Char LT.Builder where+  interpolate _ = B . LT.singleton+instance {-# OVERLAPS #-} Interpolatable 'True String LT.Builder where+  interpolate _ = B . LT.fromString+instance {-# OVERLAPS #-} Interpolatable 'True T.Text LT.Builder where+  interpolate _ = B . LT.fromText+instance {-# OVERLAPS #-} Interpolatable 'True LT.Text LT.Builder where+  interpolate _ = B . LT.fromLazyText+instance {-# OVERLAPS #-} Interpolatable 'True LT.Builder LT.Builder where+  interpolate _ = B+instance {-# OVERLAPS #-} Interpolatable 'True B.ByteString LT.Builder where+  interpolate _ = B . bsToTextBuilder+instance {-# OVERLAPS #-} Interpolatable 'True LB.ByteString LT.Builder where+  interpolate _ = B . lbsToTextBuilder+instance {-# OVERLAPS #-} Interpolatable 'True LB.Builder LT.Builder where+  interpolate _ = B . lbsToTextBuilder . LB.toLazyByteString+ instance {-# OVERLAPPABLE #-} Show src => Interpolatable 'True src B.ByteString where   interpolate _ = B . LB.byteString . unUTF8 . convertText . show+instance {-# OVERLAPS #-} Interpolatable 'True Char B.ByteString where+  interpolate _ = B . encodeCharUTF8 instance {-# OVERLAPS #-} Interpolatable 'True String B.ByteString where   interpolate _ = B . LB.byteString . unUTF8 . convertText instance {-# OVERLAPS #-} Interpolatable 'True T.Text B.ByteString where   interpolate _ = B . LB.byteString . unUTF8 . convertText instance {-# OVERLAPS #-} Interpolatable 'True LT.Text B.ByteString where   interpolate _ = B . LB.byteString . unUTF8 . convertText+instance {-# OVERLAPS #-} Interpolatable 'True LT.Builder B.ByteString where+  interpolate _ = B . LB.byteString . unUTF8 . convertText . LT.toLazyText instance {-# OVERLAPS #-} Interpolatable 'True B.ByteString B.ByteString where   interpolate _ = B . LB.byteString instance {-# OVERLAPS #-} Interpolatable 'True LB.ByteString B.ByteString where   interpolate _ = B . LB.lazyByteString+instance {-# OVERLAPS #-} Interpolatable 'True LB.Builder B.ByteString where+  interpolate _ = B  instance {-# OVERLAPPABLE #-} Show src => Interpolatable 'True src LB.ByteString where   interpolate _ = B . LB.lazyByteString . unUTF8 . convertText . show+instance {-# OVERLAPS #-} Interpolatable 'True Char LB.ByteString where+  interpolate _ = B . encodeCharUTF8 instance {-# OVERLAPS #-} Interpolatable 'True String LB.ByteString where   interpolate _ = B . LB.lazyByteString . unUTF8 . convertText instance {-# OVERLAPS #-} Interpolatable 'True T.Text LB.ByteString where   interpolate _ = B . LB.lazyByteString . unUTF8 . convertText instance {-# OVERLAPS #-} Interpolatable 'True LT.Text LB.ByteString where   interpolate _ = B . LB.lazyByteString . unUTF8 . convertText+instance {-# OVERLAPS #-} Interpolatable 'True LT.Builder LB.ByteString where+  interpolate _ = B . LB.lazyByteString . unUTF8 . convertText . LT.toLazyText instance {-# OVERLAPS #-} Interpolatable 'True B.ByteString LB.ByteString where   interpolate _ = B . LB.byteString instance {-# OVERLAPS #-} Interpolatable 'True LB.ByteString LB.ByteString where   interpolate _ = B . LB.lazyByteString+instance {-# OVERLAPS #-} Interpolatable 'True LB.Builder LB.ByteString where+  interpolate _ = B +instance {-# OVERLAPPABLE #-} Show src => Interpolatable 'True src LB.Builder where+  interpolate _ = B . LB.lazyByteString . unUTF8 . convertText . show+instance {-# OVERLAPS #-} Interpolatable 'True Char LB.Builder where+  interpolate _ = B . encodeCharUTF8+instance {-# OVERLAPS #-} Interpolatable 'True String LB.Builder where+  interpolate _ = B . LB.lazyByteString . unUTF8 . convertText+instance {-# OVERLAPS #-} Interpolatable 'True T.Text LB.Builder where+  interpolate _ = B . LB.lazyByteString . unUTF8 . convertText+instance {-# OVERLAPS #-} Interpolatable 'True LT.Text LB.Builder where+  interpolate _ = B . LB.lazyByteString . unUTF8 . convertText+instance {-# OVERLAPS #-} Interpolatable 'True LT.Builder LB.Builder where+  interpolate _ = B . LB.lazyByteString . unUTF8 . convertText . LT.toLazyText+instance {-# OVERLAPS #-} Interpolatable 'True B.ByteString LB.Builder where+  interpolate _ = B . LB.byteString+instance {-# OVERLAPS #-} Interpolatable 'True LB.ByteString LB.Builder where+  interpolate _ = B . LB.lazyByteString+instance {-# OVERLAPS #-} Interpolatable 'True LB.Builder LB.Builder where+  interpolate _ = B+ -- | -- Convert a strict ByteString into a Text `LT.Builder', converting any invalid -- characters into the Unicode replacement character � (U+FFFD).@@ -170,3 +254,14 @@ -- characters into the Unicode replacement character � (U+FFFD). lbsToTextBuilder :: LB.ByteString -> LT.Builder lbsToTextBuilder = LUTF8.foldr (\char bldr -> LT.singleton char <> bldr) mempty++-- |+-- "Data.ByteString.Builder" provides `charUtf8' to do this, but it doesn't+-- correctly handle invalid characters.+encodeCharUTF8 :: Char -> LB.Builder+encodeCharUTF8 c =+  let normalized = case c of+        '\xFFFE' -> '\xFFFD'+        '\xFFFF' -> '\xFFFD'+        _        -> c+  in LB.charUtf8 normalized
string-interpolate.cabal view
@@ -4,11 +4,11 @@ -- -- see: https://github.com/sol/hpack ----- hash: e19a345342449e9beb74add05e63adf5a16f1e6899912ae637a1b6f8a4bca51e+-- hash: e5e0c8e769850dc5f6451b53b84d5b2e9afce9ef6393e668d971ef3bfe810401  name:           string-interpolate-version:        0.0.1.0-synopsis:       Haskell string interpolation that just works+version:        0.1.0.0+synopsis:       Haskell string/text/bytestring interpolation that just works description:    Unicode-aware string interpolation that handles all textual types.                 .                 See the README at <https://gitlab.com/williamyaoh/string-interpolate.git#string-interpolate> for more info.@@ -69,11 +69,14 @@   hs-source-dirs:       bench   build-depends:-      base ==4.*+      Interpolation+    , QuickCheck+    , base ==4.*     , bytestring     , criterion     , formatting     , interpolate+    , interpolatedstring-perl6     , string-interpolate     , text   default-language: Haskell2010
test/Spec.hs view
@@ -11,9 +11,8 @@ import qualified Data.ByteString.Builder as LB import Data.Foldable ( foldMap ) -import "quickcheck-text" Data.Text.Arbitrary- import "hspec" Test.Hspec+import "hspec" Test.Hspec.QuickCheck import "QuickCheck" Test.QuickCheck import "quickcheck-instances" Test.QuickCheck.Instances.ByteString @@ -21,162 +20,211 @@  main :: IO () main = hspec $ parallel $ do-  describe "i" $ do+  describe "i" $ modifyMaxSuccess (const 10000) $ modifyMaxSize (const 500) $ do     context "when using String as a parameter" $ do-      it "just interpolating should be id" $-        property $ \(str :: String) -> [i|#{str}|] == str+      prop "just interpolating should be id" $+        \(UTF8S str) -> [i|#{str}|] == str -      it "should passthrough a conversion to strict Text and back unchanged" $-        property $ \(str :: String) ->+      prop "should passthrough a conversion to strict Text and back unchanged" $+         \(UTF8S str) ->           let t = [i|#{str}|] :: T.Text               str' = [i|#{t}|] :: String           in str' == str -      it "should passthrough a conversion to lazy Text and back unchanged" $-        property $ \(str :: String) ->+      prop "should passthrough a conversion to lazy Text and back unchanged" $+        \(UTF8S str) ->           let lt = [i|#{str}|] :: LT.Text               str' = [i|#{lt}|] :: String           in str' == str -      it "should passthrough a conversion to strict ByteString and back unchanged" $-        property $ \(str :: String) ->+      prop "should passthrough a conversion to strict ByteString and back unchanged" $+        \(UTF8S str) ->           let b = [i|#{str}|] :: B.ByteString               str' = [i|#{b}|] :: String           in str' == str -      it "should passthrough a conversion to lazy ByteString and back unchanged" $-        property $ \(str :: String) ->+      prop "should passthrough a conversion to lazy ByteString and back unchanged" $+        \(UTF8S str) ->           let lb = [i|#{str}|] :: LB.ByteString               str' = [i|#{lb}|] :: String           in str' == str      context "when using strict Text as a parameter" $ do-      it "just interpolating should be id" $-        property $ \(t :: T.Text) -> [i|#{t}|] == t+      prop "just interpolating should be id" $+        \(t :: T.Text) -> [i|#{t}|] == t -      it "should passthrough a conversion to String and back unchanged" $-        property $ \(t :: T.Text) ->+      prop "should passthrough a conversion to String and back unchanged" $+        \(t :: T.Text) ->           let str = [i|#{t}|] :: String               t' = [i|#{str}|] :: T.Text           in t' == t -      it "should passthrough a conversion to lazy Text and back unchanged" $-        property $ \(t :: T.Text) ->+      prop "should passthrough a conversion to lazy Text and back unchanged" $+        \(t :: T.Text) ->           let lt = [i|#{t}|] :: LT.Text               t' = [i|#{lt}|] :: T.Text           in t' == t -      it "should passthrough a conversion to strict ByteString and back unchanged" $-        property $ \(t :: T.Text) ->+      prop "should passthrough a conversion to strict ByteString and back unchanged" $+        \(t :: T.Text) ->           let b = [i|#{t}|] :: B.ByteString               t' = [i|#{b}|] :: T.Text           in t' == t -      it "should passthrough a conversion to lazy ByteString and back unchanged" $-        property $ \(t :: T.Text) ->+      prop "should passthrough a conversion to lazy ByteString and back unchanged" $+        \(t :: T.Text) ->           let lb = [i|#{t}|] :: LB.ByteString               t' = [i|#{lb}|] :: T.Text           in t' == t      context "when using lazy Text as a parameter" $ do-      it "just interpolating should be id" $-        property $ \(lt :: LT.Text) -> [i|#{lt}|] == lt+      prop "just interpolating should be id" $+        \(lt :: LT.Text) -> [i|#{lt}|] == lt -      it "should passthrough a conversion to String and back unchanged" $-        property $ \(lt :: LT.Text) ->+      prop "should passthrough a conversion to String and back unchanged" $+        \(lt :: LT.Text) ->           let str = [i|#{lt}|] :: String               lt' = [i|#{str}|] :: LT.Text           in lt' == lt -      it "should passthrough a conversion to strict Text and back unchanged" $-        property $ \(lt :: LT.Text) ->+      prop "should passthrough a conversion to strict Text and back unchanged" $+        \(lt :: LT.Text) ->           let t = [i|#{lt}|] :: T.Text               lt' = [i|#{t}|] :: LT.Text           in lt' == lt -      it "should passthrough a conversion to strict ByteString and back unchanged" $-        property $ \(lt :: LT.Text) ->+      prop "should passthrough a conversion to strict ByteString and back unchanged" $+        \(lt :: LT.Text) ->           let b = [i|#{lt}|] :: B.ByteString               lt' = [i|#{b}|] :: LT.Text           in lt' == lt -      it "should passthrough a conversion to lazy ByteString and back unchanged" $-        property $ \(lt :: LT.Text) ->+      prop "should passthrough a conversion to lazy ByteString and back unchanged" $+        \(lt :: LT.Text) ->           let lb = [i|#{lt}|] :: LB.ByteString               lt' = [i|#{lb}|] :: LT.Text           in lt' == lt      context "when using strict ByteString as a parameter" $ do-      it "just interpolating should be id" $-        property $ \(b :: B.ByteString) -> [i|#{b}|] == b+      prop "just interpolating should be id" $+        \(b :: B.ByteString) -> [i|#{b}|] == b -      it "should passthrough a conversion to lazy ByteString and back unchanged" $-        property $ \(b :: B.ByteString) ->+      prop "should passthrough a conversion to lazy ByteString and back unchanged" $+        \(b :: B.ByteString) ->           let lb = [i|#{b}|] :: LB.ByteString               b' = [i|#{lb}|] :: B.ByteString           in b' == b        context "and the ByteString is valid UTF8" $ do-        it "should passthrough a conversion to String and back unchanged" $ do-          property $ \(UTF8 b) ->+        prop "should passthrough a conversion to String and back unchanged" $ do+          \(UTF8BS b) ->             let str = [i|#{b}|] :: String                 b' = [i|#{str}|]             in b' == b -        it "should passthrough a conversion to strict Text and back unchanged" $ do-          property $ \(UTF8 b) ->+        prop "should passthrough a conversion to strict Text and back unchanged" $ do+          \(UTF8BS b) ->             let t = [i|#{b}|] :: T.Text                 b' = [i|#{t}|]             in b' == b -        it "should passthrough a conversion to lazy Text and back unchanged" $ do-          property $ \(UTF8 b) ->+        prop "should passthrough a conversion to lazy Text and back unchanged" $ do+          \(UTF8BS b) ->             let lt = [i|#{b}|] :: LT.Text                 b' = [i|#{lt}|]             in b' == b      context "when using lazy ByteString as a parameter" $ do-      it "just interpolating should be id" $-        property $ \(lb :: LB.ByteString) -> [i|#{lb}|] == lb+      prop "just interpolating should be id" $+        \(lb :: LB.ByteString) -> [i|#{lb}|] == lb -      it "should passthrough a conversion to strict ByteString and back unchanged" $-        property $ \(lb :: LB.ByteString) ->+      prop "should passthrough a conversion to strict ByteString and back unchanged" $+        \(lb :: LB.ByteString) ->           let b = [i|#{lb}|] :: B.ByteString               lb' = [i|#{b}|] :: LB.ByteString           in lb' == lb        context "and the ByteString is valid UTF8" $ do-        it "should passthrough a conversion to String and back unchanged" $-          property $ \(LUTF8 lb) ->+        prop "should passthrough a conversion to String and back unchanged" $+          \(UTF8LBS lb) ->             let str = [i|#{lb}|] :: String                 lb' = [i|#{str}|]             in lb' == lb -        it "should passthrough a conversion to strict Text and back unchanged" $-          property $ \(LUTF8 lb) ->+        prop "should passthrough a conversion to strict Text and back unchanged" $+          \(UTF8LBS lb) ->             let t = [i|#{lb}|] :: T.Text                 lb' = [i|#{t}|]             in lb' == lb -        it "should passthrough a conversion to lazy Text and back unchanged" $-          property $ \(LUTF8 lb) ->+        prop "should passthrough a conversion to lazy Text and back unchanged" $+          \(UTF8LBS lb) ->             let lt = [i|#{lb}|] :: LT.Text                 lb' = [i|#{lt}|]             in lb' == lb -newtype UTF8ByteString = UTF8 { unUTF8 :: B.ByteString }+    context "when using Char as a parameter" $ do+      prop "interpolating into a String shouldn't have quotes" $+        \(UTF8C c) -> [i|#{c}|] == [c]++      prop "interpolating into strict Text shouldn't have quotes" $+        \(UTF8C c) -> [i|#{c}|] == T.singleton c++      prop "interpolating into lazy Text shouldn't have quotes" $+        \(UTF8C c) -> [i|#{c}|] == LT.singleton c++      prop "interpolating into strict ByteString shouldn't have quotes" $+        \(UTF8C c) -> [i|#{c}|] == (LB.toStrict $ LB.toLazyByteString $ LB.charUtf8 c)++      prop "interpolating into lazy ByteString shouldn't have quotes" $+        \(UTF8C c) -> [i|#{c}|] == (LB.toLazyByteString $ LB.charUtf8 c)++-- |+-- The default Arbitrary for Char generates U+FFFF and U+FFFE, which aren't+-- valid Unicode. Sigh...+newtype UTF8Char = UTF8C { unUTF8C :: Char }   deriving newtype (Eq, Show)-newtype LUTF8ByteString = LUTF8 { unLUTF8 :: LB.ByteString }++newtype UTF8String = UTF8S { unUTF8S :: String }   deriving newtype (Eq, Show) -instance Arbitrary LUTF8ByteString where-  arbitrary = LUTF8 . LB.toLazyByteString . foldMap LB.charUtf8 <$> arbitrary @[Char]-instance Arbitrary UTF8ByteString where-  arbitrary = UTF8 . LB.toStrict . LB.toLazyByteString . foldMap LB.charUtf8 <$>-    arbitrary @[Char]+newtype UTF8ByteString = UTF8BS { unUTF8BS :: B.ByteString }+  deriving newtype (Eq, Show)+newtype UTF8LazyByteString = UTF8LBS { unUTF8LBS :: LB.ByteString }+  deriving newtype (Eq, Show) --- Why the hell doesn't `quickcheck-text' not provide this--- instance already? I don't know. Don't ask me.+instance Arbitrary UTF8Char where+  arbitrary = UTF8C <$> arbitrary `suchThat` (\c -> c /= '\xFFFE' && c /= '\xFFFF')+  shrink (UTF8C c) = UTF8C <$> shrink c++instance Arbitrary UTF8String where+  arbitrary = do+    chars <- listOf arbitrary+    pure $ UTF8S (unUTF8C <$> chars)+  shrink (UTF8S str) = case str of+    [] -> []+    (_:[]) -> []+    _ -> let mid = length str `div` 2+         in [UTF8S $ take mid str, UTF8S $ drop mid str]++instance Arbitrary T.Text where+  arbitrary = T.pack . unUTF8S <$> arbitrary+  shrink t = if T.null t || T.length t == 1+    then []+    else let mid = T.length t `div` 2+         in [T.take mid t, T.drop mid t]+ instance Arbitrary LT.Text where-  arbitrary = LT.fromStrict <$> arbitrary-  shrink t = LT.fromStrict <$> shrink (LT.toStrict t)+  arbitrary = LT.pack . unUTF8S <$> arbitrary+  shrink lt = if LT.null lt || LT.length lt == 1+    then []+    else let mid = LT.length lt `div` 2+         in [LT.take mid lt, LT.drop mid lt]++instance Arbitrary UTF8ByteString where+  arbitrary = UTF8BS . LB.toStrict . LB.toLazyByteString . foldMap LB.charUtf8 . unUTF8S+    <$> arbitrary++instance Arbitrary UTF8LazyByteString where+  arbitrary = UTF8LBS . LB.toLazyByteString . foldMap LB.charUtf8 . unUTF8S+    <$> arbitrary