text-builder-1.0.0.4/0000755000000000000000000000000007346545000012453 5ustar0000000000000000text-builder-1.0.0.4/CHANGELOG.md0000644000000000000000000000567507346545000014301 0ustar0000000000000000# 1 Major performance optimization achieved by getting rid of the "length" function. Since this was an unavoidable breaking change, other breaking changes were added as well. Major redesign done to declutter the API from experimental features, dropping some and isolating others to "text-builder-dev" to reduce the need for further major releases of this library down the road. Order brought to naming conventions of the formatter-functions. ## Breaking changes and migration instructions - `Text.Builder` module renamed to `TextBuilder` to fully conform to the [convention](https://www.reddit.com/r/haskell/comments/1qrilm/packages_and_namespaces_naming_convention/) of having the root namespace of the library map to its name. - `Builder` type was renamed to `TextBuilder`. - `run` renamed to `toText`. - `length` removed to increase performance of the whole abstraction. - `null` renamed to `isEmpty` to leave declarative naming only for constructor-functions. - `putToStdOut`, `putToStdErr`, `putLnToStdOut`, `putLnToStdErr` removed to reduce the bloat of the API. Just use similar functions on `Text`. - `padFromLeft` and `padFromRight` moved to "text-builder-dev-0.4". - `asciiByteString` renamed to `unsafeUtf8ByteString`. ASCII is a subset of UTF-8 and the previous implementation was unsafe any way. - `hexData` moved to "text-builder-dev-0.4" as `byteStringHexEncoding`. - `unicodeCodePoint` renamed to `unicodeCodepoint`. - `utf16CodeUnits1`, `utf16CodeUnits2`, `utf8CodeUnits1`, `utf8CodeUnits2`, `utf8CodeUnits3`, `utf8CodeUnits4` moved to "text-builder-core-0.1" and removed from the public API to reduce the bloat. Nobody seems to have been using them. If you need them, open a ticket in "text-builder-core". - `unsignedDecimal` - removed because it was unsafe. Use `decimal`. - `thousandSeparatedUnsignedDecimal` - removed because it was unsafe. Use `thousandSeparatedDecimal`. - `dataSizeInBytesInDecimal` moved to "text-builder-dev-0.4" as `approximateDataSize`. - `unsignedBinary` and `unsignedPaddedBinary` replaced with `binary`, which is now safe and handles negative values. - `hexadecimal` and `unsignedHexadecimal` replaced with `hexadecimal`, which is now safe and handles negative values. - `decimalDigit` and `hexadecimalDigit` removed. Seemed to not be useful. If you think otherwise open a ticket. - `fixedDouble` moved to "text-builder-dev-0.4" as `doubleFixedPoint`. It does not have an efficient implementation yet is based on `printf`. - `doublePercent` moved to "text-builder-dev-0.4" as `doubleFixedPointPercent`. Same reasons. - `intervalInSeconds` moved to "text-builder-dev-0.4" as `diffTimeSeconds` and `picoseconds`. ## Non-breaking - `unicodeCodepoint` is now safe. It replaces invalid codepoints with a default one. - `char` is now safe for the same reasons. # 0.6.8 - Migrated to the namespacing convention where the root namespace matches the package name 1-1 with no special cases. The support for previous naming convention is still provided though. text-builder-1.0.0.4/LICENSE0000644000000000000000000000204207346545000013456 0ustar0000000000000000Copyright (c) 2017, Nikita Volkov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. text-builder-1.0.0.4/README.md0000644000000000000000000002632707346545000013744 0ustar0000000000000000# Summary Fast strict text builder and simple type-safe formatting library for Haskell. # Performance The benchmarks measure the monoidal composition of builders constructed from `Text` simulating left- and right-biased appends and `mconcat`. The results show the following: - It's 2-3 times faster than the `Data.Text.Lazy.Builder` supplied with the "text" package. - It's 1.25 times faster than the `Data.Text.Encoding.StrictTextBuilder` which has recently arrived in "text". - It's 1.2-2 times faster than "text-builder-linear". - It's 1.1 times slower than `Data.Text.Text` in case of using `mconcat` over texts. In all other cases `Text` is slower and slows down exponentially, which is not surprising. In the years of existence of this package multiple user-stories have been collected proving the performance boosts after switching to it. ``` All Competition Left-biased mappend 100B TextBuilder.TextBuilder: OK 187 ns ± 14 ns, 960 B allocated, 0 B copied, 6.0 MB peak memory Data.Text.Encoding.StrictTextBuilder: OK 215 ns ± 17 ns, 1.2 KB allocated, 0 B copied, 6.0 MB peak memory Data.Text.Lazy.Builder.Builder: OK 510 ns ± 27 ns, 2.6 KB allocated, 1 B copied, 6.0 MB peak memory Data.Text.Text: OK 216 ns ± 15 ns, 1.6 KB allocated, 0 B copied, 6.0 MB peak memory Data.Text.Lazy.Text: OK 424 ns ± 30 ns, 4.2 KB allocated, 2 B copied, 6.0 MB peak memory Data.Text.Builder.Linear: OK 368 ns ± 26 ns, 1.3 KB allocated, 0 B copied, 6.0 MB peak memory 1kB TextBuilder.TextBuilder: OK 1.54 μs ± 144 ns, 8.3 KB allocated, 8 B copied, 6.0 MB peak memory Data.Text.Encoding.StrictTextBuilder: OK 1.85 μs ± 131 ns, 12 KB allocated, 10 B copied, 6.0 MB peak memory Data.Text.Lazy.Builder.Builder: OK 4.66 μs ± 458 ns, 24 KB allocated, 31 B copied, 6.0 MB peak memory Data.Text.Text: OK 3.80 μs ± 257 ns, 104 KB allocated, 85 B copied, 6.0 MB peak memory Data.Text.Lazy.Text: OK 24.5 μs ± 1.7 μs, 358 KB allocated, 451 B copied, 6.0 MB peak memory Data.Text.Builder.Linear: OK 3.04 μs ± 213 ns, 12 KB allocated, 6 B copied, 6.0 MB peak memory 10kB TextBuilder.TextBuilder: OK 15.5 μs ± 1.2 μs, 82 KB allocated, 175 B copied, 8.0 MB peak memory Data.Text.Encoding.StrictTextBuilder: OK 18.3 μs ± 536 ns, 121 KB allocated, 439 B copied, 8.0 MB peak memory Data.Text.Lazy.Builder.Builder: OK 47.7 μs ± 3.4 μs, 242 KB allocated, 1.9 KB copied, 8.0 MB peak memory Data.Text.Text: OK 355 μs ± 19 μs, 9.6 MB allocated, 19 KB copied, 13 MB peak memory Data.Text.Lazy.Text: OK 2.63 ms ± 251 μs, 34 MB allocated, 264 KB copied, 13 MB peak memory Data.Text.Builder.Linear: OK 30.6 μs ± 1.7 μs, 110 KB allocated, 202 B copied, 13 MB peak memory 100kB TextBuilder.TextBuilder: OK 201 μs ± 14 μs, 1.0 MB allocated, 14 KB copied, 13 MB peak memory Data.Text.Encoding.StrictTextBuilder: OK 195 μs ± 19 μs, 1.2 MB allocated, 39 KB copied, 13 MB peak memory Data.Text.Lazy.Builder.Builder: OK 520 μs ± 30 μs, 2.4 MB allocated, 174 KB copied, 13 MB peak memory Data.Text.Text: OK 28.1 ms ± 2.0 ms, 954 MB allocated, 476 KB copied, 25 MB peak memory Data.Text.Lazy.Text: OK 616 ms ± 34 ms, 4.7 GB allocated, 136 MB copied, 25 MB peak memory Data.Text.Builder.Linear: OK 361 μs ± 33 μs, 1.5 MB allocated, 15 KB copied, 25 MB peak memory Right-biased mappend 100B TextBuilder.TextBuilder: OK 188 ns ± 13 ns, 960 B allocated, 0 B copied, 25 MB peak memory Data.Text.Encoding.StrictTextBuilder: OK 231 ns ± 17 ns, 1.2 KB allocated, 0 B copied, 25 MB peak memory Data.Text.Lazy.Builder.Builder: OK 500 ns ± 34 ns, 2.6 KB allocated, 1 B copied, 25 MB peak memory Data.Text.Text: OK 214 ns ± 18 ns, 1.6 KB allocated, 0 B copied, 25 MB peak memory Data.Text.Lazy.Text: OK 283 ns ± 13 ns, 1.8 KB allocated, 0 B copied, 25 MB peak memory Data.Text.Builder.Linear: OK 363 ns ± 19 ns, 1.3 KB allocated, 0 B copied, 25 MB peak memory 1kB TextBuilder.TextBuilder: OK 1.59 μs ± 123 ns, 8.3 KB allocated, 7 B copied, 25 MB peak memory Data.Text.Encoding.StrictTextBuilder: OK 1.98 μs ± 120 ns, 12 KB allocated, 9 B copied, 25 MB peak memory Data.Text.Lazy.Builder.Builder: OK 4.66 μs ± 281 ns, 24 KB allocated, 30 B copied, 25 MB peak memory Data.Text.Text: OK 3.76 μs ± 72 ns, 104 KB allocated, 79 B copied, 25 MB peak memory Data.Text.Lazy.Text: OK 2.23 μs ± 215 ns, 17 KB allocated, 29 B copied, 25 MB peak memory Data.Text.Builder.Linear: OK 3.03 μs ± 249 ns, 12 KB allocated, 11 B copied, 25 MB peak memory 10kB TextBuilder.TextBuilder: OK 15.6 μs ± 1.0 μs, 82 KB allocated, 205 B copied, 25 MB peak memory Data.Text.Encoding.StrictTextBuilder: OK 32.7 μs ± 2.0 μs, 185 KB allocated, 556 B copied, 25 MB peak memory Data.Text.Lazy.Builder.Builder: OK 47.6 μs ± 4.1 μs, 242 KB allocated, 2.1 KB copied, 25 MB peak memory Data.Text.Text: OK 437 μs ± 14 μs, 9.6 MB allocated, 22 KB copied, 28 MB peak memory Data.Text.Lazy.Text: OK 24.9 μs ± 1.0 μs, 168 KB allocated, 1.9 KB copied, 28 MB peak memory Data.Text.Builder.Linear: OK 30.4 μs ± 1.7 μs, 110 KB allocated, 427 B copied, 28 MB peak memory 100kB TextBuilder.TextBuilder: OK 176 μs ± 11 μs, 820 KB allocated, 18 KB copied, 28 MB peak memory Data.Text.Encoding.StrictTextBuilder: OK 363 μs ± 34 μs, 1.8 MB allocated, 51 KB copied, 28 MB peak memory Data.Text.Lazy.Builder.Builder: OK 561 μs ± 41 μs, 2.4 MB allocated, 191 KB copied, 28 MB peak memory Data.Text.Text: OK 31.4 ms ± 1.5 ms, 954 MB allocated, 379 KB copied, 36 MB peak memory Data.Text.Lazy.Text: OK 315 μs ± 13 μs, 1.6 MB allocated, 182 KB copied, 36 MB peak memory Data.Text.Builder.Linear: OK 333 μs ± 26 μs, 1.3 MB allocated, 34 KB copied, 36 MB peak memory mconcat 100B TextBuilder.TextBuilder: OK 125 ns ± 6.6 ns, 496 B allocated, 0 B copied, 36 MB peak memory Data.Text.Encoding.StrictTextBuilder: OK 166 ns ± 13 ns, 1.0 KB allocated, 0 B copied, 36 MB peak memory Data.Text.Lazy.Builder.Builder: OK 324 ns ± 27 ns, 2.1 KB allocated, 0 B copied, 36 MB peak memory Data.Text.Text: OK 117 ns ± 7.2 ns, 280 B allocated, 0 B copied, 36 MB peak memory Data.Text.Lazy.Text: OK 212 ns ± 17 ns, 1.7 KB allocated, 0 B copied, 36 MB peak memory Data.Text.Builder.Linear: OK 175 ns ± 13 ns, 936 B allocated, 0 B copied, 36 MB peak memory 1kB TextBuilder.TextBuilder: OK 1.21 μs ± 104 ns, 3.6 KB allocated, 1 B copied, 36 MB peak memory Data.Text.Encoding.StrictTextBuilder: OK 1.69 μs ± 117 ns, 9.8 KB allocated, 10 B copied, 36 MB peak memory Data.Text.Lazy.Builder.Builder: OK 3.04 μs ± 301 ns, 20 KB allocated, 16 B copied, 36 MB peak memory Data.Text.Text: OK 1.07 μs ± 74 ns, 2.0 KB allocated, 1 B copied, 36 MB peak memory Data.Text.Lazy.Text: OK 1.76 μs ± 154 ns, 16 KB allocated, 20 B copied, 36 MB peak memory Data.Text.Builder.Linear: OK 1.42 μs ± 58 ns, 8.0 KB allocated, 3 B copied, 36 MB peak memory 10kB TextBuilder.TextBuilder: OK 15.0 μs ± 838 ns, 35 KB allocated, 5 B copied, 36 MB peak memory Data.Text.Encoding.StrictTextBuilder: OK 32.0 μs ± 2.3 μs, 162 KB allocated, 563 B copied, 36 MB peak memory Data.Text.Lazy.Builder.Builder: OK 34.8 μs ± 1.7 μs, 202 KB allocated, 927 B copied, 36 MB peak memory Data.Text.Text: OK 10.6 μs ± 899 ns, 20 KB allocated, 5 B copied, 36 MB peak memory Data.Text.Lazy.Text: OK 22.0 μs ± 1.7 μs, 160 KB allocated, 1.3 KB copied, 36 MB peak memory Data.Text.Builder.Linear: OK 16.3 μs ± 1.4 μs, 71 KB allocated, 13 B copied, 36 MB peak memory 100kB TextBuilder.TextBuilder: OK 176 μs ± 15 μs, 352 KB allocated, 55 B copied, 36 MB peak memory Data.Text.Encoding.StrictTextBuilder: OK 437 μs ± 21 μs, 1.8 MB allocated, 78 KB copied, 36 MB peak memory Data.Text.Lazy.Builder.Builder: OK 333 μs ± 26 μs, 2.0 MB allocated, 84 KB copied, 36 MB peak memory Data.Text.Text: OK 106 μs ± 10 μs, 195 KB allocated, 60 B copied, 36 MB peak memory Data.Text.Lazy.Text: OK 264 μs ± 15 μs, 1.6 MB allocated, 97 KB copied, 36 MB peak memory Data.Text.Builder.Linear: OK 170 μs ± 8.1 μs, 952 KB allocated, 166 B copied, 36 MB peak memory Features intercalate TextBuilder: OK 420 ns ± 28 ns, 3.8 KB allocated, 1 B copied, 36 MB peak memory Text: OK 143 ns ± 9.9 ns, 824 B allocated, 0 B copied, 36 MB peak memory binary: OK 62.8 ns ± 3.7 ns, 152 B allocated, 0 B copied, 36 MB peak memory octal: OK 25.1 ns ± 1.7 ns, 96 B allocated, 0 B copied, 36 MB peak memory decimal: OK 48.5 ns ± 3.8 ns, 560 B allocated, 0 B copied, 36 MB peak memory hexadecimal: OK 20.8 ns ± 2.1 ns, 104 B allocated, 0 B copied, 36 MB peak memory ``` # How it works It constructs text in two phases. In the first one it estimates the size of the byte array and in the second one it allocates it once and populates it in one go. text-builder-1.0.0.4/bench/0000755000000000000000000000000007346545000013532 5ustar0000000000000000text-builder-1.0.0.4/bench/Main.hs0000644000000000000000000000560707346545000014762 0ustar0000000000000000module Main where import Data.Function import qualified Data.Text as E import qualified Data.Text.Builder.Linear import qualified Data.Text.Encoding as D import qualified Data.Text.Lazy as C import qualified Data.Text.Lazy.Builder as B import Test.Tasty.Bench import qualified TextBuilder as A import Prelude main :: IO () main = defaultMain [ bgroup "Competition" competition, bgroup "Features" features ] where competition = [ bgroup "Left-biased mappend" $ byConcat $ foldl' (<>) mempty, bgroup "Right-biased mappend" $ byConcat $ foldl' (flip (<>)) mempty, bgroup "mconcat" $ byConcat $ mconcat ] where byConcat (concat :: forall a. (Monoid a) => [a] -> a) = [ bgroup "100B" $ byTexts $ replicate 10 "фывапролдж", bgroup "1kB" $ byTexts $ replicate 100 "фывапролдж", bgroup "10kB" $ byTexts $ replicate 1_000 "фывапролдж", bgroup "100kB" $ byTexts $ replicate 10_000 "фывапролдж" ] where byTexts texts = [ bench "TextBuilder.TextBuilder" ( whnf (A.toText . concat) (fmap A.text texts) ), bench "Data.Text.Encoding.StrictTextBuilder" ( whnf (D.strictBuilderToText . concat) (fmap D.textToStrictBuilder texts) ), bench "Data.Text.Lazy.Builder.Builder" ( whnf (C.toStrict . B.toLazyText . concat) (fmap B.fromText texts) ), bench "Data.Text.Text" ( whnf concat texts ), bench "Data.Text.Lazy.Text" ( whnf (C.toStrict . concat) (fmap C.fromStrict texts) ), bench "Data.Text.Builder.Linear" ( whnf (Data.Text.Builder.Linear.runBuilder . concat) (fmap Data.Text.Builder.Linear.fromText texts) ) ] features = [ bgroup "intercalate" $ [ bench "TextBuilder" $ whnf (A.toText . A.intercalate "фывапролдж") ["a", "b", "c", "d", "e", "f"], bench "Text" $ whnf (E.intercalate "фывапролдж") ["a", "b", "c", "d", "e", "f"] ], bench "binary" $ whnf (A.toText . A.binary) (123456 :: Int), bench "octal" $ whnf (A.toText . A.octal) (123456 :: Int), bench "decimal" $ whnf (A.toText . A.decimal) (123456 :: Int), bench "hexadecimal" $ whnf (A.toText . A.hexadecimal) (123456 :: Int) ] text-builder-1.0.0.4/library/0000755000000000000000000000000007346545000014117 5ustar0000000000000000text-builder-1.0.0.4/library/TextBuilder.hs0000644000000000000000000000141107346545000016703 0ustar0000000000000000module TextBuilder ( TextBuilder, -- * Accessors toText, toString, isEmpty, -- * Constructors -- ** Transformations force, intercalate, intercalateMap, -- ** Textual text, lazyText, string, unsafeUtf8ByteString, -- ** Character char, unicodeCodepoint, -- ** Integers -- *** Decimal decimal, fixedLengthDecimal, thousandSeparatedDecimal, -- *** Binary binary, prefixedBinary, -- *** Octal octal, prefixedOctal, -- *** Hexadecimal hexadecimal, prefixedHexadecimal, ) where import TextBuilder.Domains.ByteString import TextBuilder.Domains.Combinators import TextBuilder.Domains.Digits import TextBuilder.Domains.Other import TextBuilderCore text-builder-1.0.0.4/library/TextBuilder/Domains/0000755000000000000000000000000007346545000017744 5ustar0000000000000000text-builder-1.0.0.4/library/TextBuilder/Domains/ByteString.hs0000644000000000000000000000242607346545000022376 0ustar0000000000000000{-# LANGUAGE CPP #-} module TextBuilder.Domains.ByteString where import qualified Data.ByteString as ByteString import qualified Data.Text.Array as TextArray import TextBuilder.Prelude import TextBuilderCore #if !MIN_VERSION_text(2,0,0) import qualified Data.Text.Encoding as TextEncoding #endif -- | UTF-8 bytestring. You can use it for converting ASCII values as well. -- -- __Warning:__ It's your responsibility to ensure that the bytestring is properly encoded. -- -- >>> unsafeUtf8ByteString "abc" -- "abc" -- -- >>> import Data.Text.Encoding (encodeUtf8) -- >>> unsafeUtf8ByteString (encodeUtf8 "фывапролдж") == "фывапролдж" -- True {-# INLINEABLE unsafeUtf8ByteString #-} unsafeUtf8ByteString :: ByteString -> TextBuilder #if MIN_VERSION_text(2,0,0) unsafeUtf8ByteString byteString = TextBuilder (ByteString.length byteString) ( \array -> -- TODO: Optimize to use memcpy or something similar. let step byte next index = do TextArray.unsafeWrite array index byte next (succ index) in ByteString.foldr step return byteString ) #else -- Using a suboptimal solution here since the older version of \"text\" is becoming less important with time. unsafeUtf8ByteString = text . TextEncoding.decodeUtf8 #endif text-builder-1.0.0.4/library/TextBuilder/Domains/Combinators.hs0000644000000000000000000000234507346545000022564 0ustar0000000000000000module TextBuilder.Domains.Combinators where import TextBuilder.Prelude hiding (intercalate) import TextBuilderCore -- | -- Run the builder and pack the produced text into a new builder. -- -- Useful to have around builders that you reuse, -- because a forced builder is much faster, -- since it's virtually a single call to @memcopy@. {-# INLINE force #-} force :: TextBuilder -> TextBuilder force = text . toText -- | Intercalate builders. -- -- >>> intercalate ", " ["a", "b", "c"] -- "a, b, c" -- -- >>> intercalate ", " ["a"] -- "a" -- -- >>> intercalate ", " [] -- "" {-# INLINE intercalate #-} intercalate :: (Foldable f) => TextBuilder -> f TextBuilder -> TextBuilder intercalate separator elements = foldr (\element next prefix -> prefix <> element <> next separator) (const mempty) elements mempty -- | Intercalate projecting values to builder. {-# INLINE intercalateMap #-} intercalateMap :: (Foldable f) => TextBuilder -> (a -> TextBuilder) -> f a -> TextBuilder intercalateMap separator mapper = extract . foldl' step init where init = Nothing step acc element = Just $ case acc of Nothing -> mapper element Just acc -> acc <> separator <> mapper element extract = fromMaybe mempty text-builder-1.0.0.4/library/TextBuilder/Domains/Digits.hs0000644000000000000000000002377707346545000021543 0ustar0000000000000000module TextBuilder.Domains.Digits where import qualified Data.Text.Array as TextArray import qualified TextBuilder.Domains.Digits.Codepoints as Codepoints import TextBuilder.Prelude import TextBuilderCore -- | Decimal digit. {-# INLINE decimalDigit #-} decimalDigit :: (Integral a) => a -> TextBuilder decimalDigit (fromIntegral -> n) = unicodeCodepoint (n + 48) -- | Hexadecimal digit. {-# INLINE hexadecimalDigit #-} hexadecimalDigit :: (Integral a) => a -> TextBuilder hexadecimalDigit (fromIntegral -> n) = if n <= 9 then unicodeCodepoint (n + 48) else unicodeCodepoint (n + 87) {-# INLINE customFixedNumeralSystem #-} customFixedNumeralSystem :: (FiniteBits a, Integral a) => -- | Number of bits per digit. Int -> -- | Projection to codepoint with handling of overflow. (a -> a) -> -- | Value. a -> TextBuilder customFixedNumeralSystem bitsPerDigit digitCodepoint val = let size = div (finiteBitSize val + bitsPerDigit - 1) bitsPerDigit in TextBuilder size \array arrayStartIndex -> let go val arrayIndex = if arrayIndex >= arrayStartIndex then do TextArray.unsafeWrite array arrayIndex (fromIntegral (digitCodepoint val)) go (unsafeShiftR val bitsPerDigit) (pred arrayIndex) else return indexAfter indexAfter = arrayStartIndex + size in go val (pred indexAfter) -- | -- [Two's complement](https://en.wikipedia.org/wiki/Two%27s_complement) binary representation of a value. -- -- Bits of a statically sized value padded from the left according to the size. -- If it's a negatable integer, the sign is reflected in the bits. -- -- >>> binary @Int8 0 -- "00000000" -- -- >>> binary @Int8 4 -- "00000100" -- -- >>> binary @Int8 (-1) -- "11111111" -- -- >>> binary @Word8 255 -- "11111111" -- -- >>> binary @Int16 4 -- "0000000000000100" -- -- >>> binary @Int16 (-4) -- "1111111111111100" {-# INLINE binary #-} binary :: (FiniteBits a) => a -> TextBuilder binary val = let size = finiteBitSize val in TextBuilder size \array arrayStartIndex -> let go val arrayIndex = if arrayIndex >= arrayStartIndex then do TextArray.unsafeWrite array arrayIndex if testBit val 0 then 49 else 48 go (unsafeShiftR val 1) (pred arrayIndex) else return indexAfter indexAfter = arrayStartIndex + size in go val (pred indexAfter) -- | -- Same as 'binary', but with the \"0b\" prefix. -- -- >>> prefixedBinary @Int8 0 -- "0b00000000" {-# INLINE prefixedBinary #-} prefixedBinary :: (FiniteBits a) => a -> TextBuilder prefixedBinary = mappend "0b" . binary -- | Octal representation of an integer. -- -- >>> octal @Int32 123456 -- "00000361100" -- -- >>> octal @Int32 (-123456) -- "77777416700" {-# INLINE octal #-} octal :: (FiniteBits a, Integral a) => a -> TextBuilder octal = customFixedNumeralSystem 3 (Codepoints.octalDigit . fromIntegral) -- | -- Same as 'octal', but with the \"0o\" prefix. -- -- >>> prefixedOctal @Int8 0 -- "0o000" {-# INLINE prefixedOctal #-} prefixedOctal :: (FiniteBits a, Integral a) => a -> TextBuilder prefixedOctal = mappend "0o" . octal -- | Integer in hexadecimal notation with a fixed number of digits determined by the size of the type. -- -- >>> hexadecimal @Int8 0 -- "00" -- -- >>> hexadecimal @Int8 4 -- "04" -- -- >>> hexadecimal @Int8 (-128) -- "80" -- -- >>> hexadecimal @Int8 (-1) -- "ff" -- -- >>> hexadecimal @Word8 255 -- "ff" -- -- >>> hexadecimal @Int32 123456 -- "0001e240" -- -- >>> hexadecimal @Int32 (-123456) -- "fffe1dc0" {-# INLINE hexadecimal #-} hexadecimal :: (FiniteBits a, Integral a) => a -> TextBuilder hexadecimal = customFixedNumeralSystem 4 (Codepoints.hexDigit . fromIntegral) -- | -- Same as 'hexadecimal', but with the \"0x\" prefix. -- -- >>> prefixedHexadecimal @Int8 0 -- "0x00" {-# INLINE prefixedHexadecimal #-} prefixedHexadecimal :: (FiniteBits a, Integral a) => a -> TextBuilder prefixedHexadecimal = mappend "0x" . hexadecimal -- * Signed Numbers {-# INLINE signed #-} signed :: (Integral a) => (forall a. (Integral a) => a -> TextBuilder) -> a -> TextBuilder signed onUnsigned a = if a >= 0 then onUnsigned a else unicodeCodepoint 45 <> let negated = negate a in if negated /= a then onUnsigned negated else -- This is a special case for the minimum value of signed types. -- The negation of the minimum value is not representable in the same type. -- For example, for Int8, -128 is not representable as a positive number. onUnsigned (negate (fromIntegral a :: Integer)) -- | Signed decimal representation of an integer. -- -- >>> decimal 123456 -- "123456" -- -- >>> decimal (-123456) -- "-123456" -- -- >>> decimal 0 -- "0" -- -- >>> decimal (-2 :: Int8) -- "-2" -- -- >>> decimal (-128 :: Int8) -- "-128" {-# INLINE decimal #-} decimal :: (Integral a) => a -> TextBuilder decimal = signed unsignedDecimal -- * Unsigned Numbers -- | Render a number in the given radix. {-# INLINE digitsByRadix #-} digitsByRadix :: (Integral a) => a -> (a -> a) -> a -> TextBuilder digitsByRadix radix digitCodepoint = go 0 [] where go !offset !digits x = case divMod x radix of (next, digit) -> if next <= 0 then finish (succ offset) (digit : digits) else go (succ offset) (digit : digits) next finish size digits = TextBuilder size action where action :: TextArray.MArray s -> Int -> ST s Int action array = go digits where go digits offset = case digits of [] -> return offset (digit : digits) -> do TextArray.unsafeWrite array offset (fromIntegral (digitCodepoint digit)) go digits (succ offset) -- | Unsigned octal representation of a non-negative integral value. -- -- __Warning:__ It is your responsibility to ensure that the value is non-negative. -- -- -- >>> unsignedOctal 7 -- "7" -- -- >>> unsignedOctal 9 -- "11" -- -- >>> unsignedOctal 16 -- "20" {-# INLINE unsignedOctal #-} unsignedOctal :: (Integral a) => a -> TextBuilder unsignedOctal = digitsByRadix 8 (+ 48) -- | Unsigned decimal representation of a non-negative integral value. -- -- __Warning:__ It is your responsibility to ensure that the value is non-negative. -- -- >>> unsignedDecimal 123456 -- "123456" -- -- >>> unsignedDecimal 0 -- "0" {-# INLINE unsignedDecimal #-} unsignedDecimal :: (Integral a) => a -> TextBuilder unsignedDecimal = digitsByRadix 10 (+ 48) -- | Unsigned hexadecimal representation of a non-negative integral value. -- -- __Warning:__ It is your responsibility to ensure that the value is non-negative. -- -- >>> unsignedHexadecimal 123456 -- "1e240" -- -- >>> unsignedHexadecimal 0 -- "0" {-# INLINE unsignedHexadecimal #-} unsignedHexadecimal :: (Integral a) => a -> TextBuilder unsignedHexadecimal = digitsByRadix 16 (\digit -> if digit <= 9 then digit + 48 else digit + 87) -- * Other -- | Fixed-length decimal without sign. -- Padded with zeros or trimmed depending on whether it's shorter or longer -- than specified. -- -- >>> fixedLengthDecimal 5 123 -- "00123" -- -- >>> fixedLengthDecimal 5 123456 -- "23456" -- -- >>> fixedLengthDecimal 5 (-123456) -- "23456" -- -- >>> fixedLengthDecimal 7 (-123456) -- "0123456" -- -- >>> fixedLengthDecimal 0 123 -- "" -- -- >>> fixedLengthDecimal (-2) 123 -- "" {-# INLINEABLE fixedLengthDecimal #-} fixedLengthDecimal :: (Integral a) => Int -> a -> TextBuilder fixedLengthDecimal (max 0 -> size) (abs -> val) = TextBuilder size $ \array startOffset -> let offsetAfter = startOffset + size writeValue val offset = if offset >= startOffset then if val /= 0 then case divMod val 10 of (val, digit) -> do TextArray.unsafeWrite array offset $ 48 + fromIntegral digit writeValue val (pred offset) else writePadding offset else return offsetAfter writePadding offset = if offset >= startOffset then do TextArray.unsafeWrite array offset 48 writePadding (pred offset) else return offsetAfter in writeValue val (pred offsetAfter) -- | Decimal representation of an integral value with thousands separated by the specified character. -- -- >>> thousandSeparatedDecimal ',' 1234567890 -- "1,234,567,890" -- -- >>> thousandSeparatedDecimal ' ' (-1234567890) -- "-1 234 567 890" {-# INLINEABLE thousandSeparatedDecimal #-} thousandSeparatedDecimal :: (Integral a) => Char -> a -> TextBuilder thousandSeparatedDecimal separatorChar = signed (unsignedThousandSeparatedDecimal separatorChar) -- | Decimal representation of an unsigned integral value with thousands separated by the specified character. -- -- __Warning:__ It is your responsibility to ensure that the value is non-negative. -- -- >>> unsignedThousandSeparatedDecimal ',' 1234567890 -- "1,234,567,890" -- -- >>> unsignedThousandSeparatedDecimal ' ' 1234567890 -- "1 234 567 890" -- -- >>> unsignedThousandSeparatedDecimal ',' 0 -- "0" {-# INLINEABLE unsignedThousandSeparatedDecimal #-} unsignedThousandSeparatedDecimal :: (Integral a) => Char -> a -> TextBuilder unsignedThousandSeparatedDecimal separatorChar = processRightmostDigit where processRightmostDigit value = case divMod value 10 of (value, digit) -> processAnotherDigit [decimalDigit digit] (1 :: Int) value processAnotherDigit builders index value = if value == 0 then mconcat builders else case divMod value 10 of (value, digit) -> if mod index 3 == 0 then processAnotherDigit (decimalDigit digit : char separatorChar : builders) (succ index) value else processAnotherDigit (decimalDigit digit : builders) (succ index) value text-builder-1.0.0.4/library/TextBuilder/Domains/Digits/0000755000000000000000000000000007346545000021167 5ustar0000000000000000text-builder-1.0.0.4/library/TextBuilder/Domains/Digits/Codepoints.hs0000644000000000000000000000136307346545000023635 0ustar0000000000000000module TextBuilder.Domains.Digits.Codepoints where import TextBuilder.Prelude {-# INLINE octalDigit #-} octalDigit :: (Bits a, Num a) => a -> a octalDigit a = a .&. 7 + 48 -- | Extract the first 4 bits and convert them to a Unicode codepoint of a hexadecimal digit. -- The result is a character in the range of 0-9 or a-f. -- -- >>> chr (fromIntegral (hexDigit 0)) -- '0' -- -- >>> chr (fromIntegral (hexDigit 10)) -- 'a' -- -- >>> chr (fromIntegral (hexDigit 15)) -- 'f' -- -- The overflow is ignored, so the result is always in the range of 0-15: -- >>> chr (fromIntegral (hexDigit 16)) -- '0' {-# INLINE hexDigit #-} hexDigit :: (Bits a, Num a, Ord a) => a -> a hexDigit a = case a .&. 15 of a -> if a < 10 then a + 48 else a + 87 text-builder-1.0.0.4/library/TextBuilder/Domains/Other.hs0000644000000000000000000000041207346545000021356 0ustar0000000000000000module TextBuilder.Domains.Other where import qualified Data.Text as Text import TextBuilder.Prelude import TextBuilderCore -- * Destructors -- | Convert builder to string. {-# INLINE toString #-} toString :: TextBuilder -> String toString = Text.unpack . toText text-builder-1.0.0.4/library/TextBuilder/0000755000000000000000000000000007346545000016352 5ustar0000000000000000text-builder-1.0.0.4/library/TextBuilder/Prelude.hs0000644000000000000000000000626207346545000020314 0ustar0000000000000000module TextBuilder.Prelude ( module Exports, ) where import Control.Applicative as Exports import Control.Arrow as Exports import Control.Category as Exports import Control.Concurrent as Exports import Control.Exception as Exports import Control.Monad as Exports hiding (forM, forM_, mapM, mapM_, msum, sequence, sequence_) import Control.Monad.Fix as Exports hiding (fix) import Control.Monad.IO.Class as Exports import Control.Monad.ST as Exports import Control.Monad.ST.Unsafe as Exports import Control.Monad.Trans.Class as Exports import Control.Monad.Trans.Maybe as Exports hiding (liftListen, liftPass) import Control.Monad.Trans.Reader as Exports hiding (liftCallCC, liftCatch) import Control.Monad.Trans.State.Strict as Exports hiding (liftCallCC, liftCatch, liftListen, liftPass) import Data.Bits as Exports import Data.Bool as Exports import Data.ByteString as Exports (ByteString) import Data.Char as Exports import Data.Coerce as Exports import Data.Complex as Exports import Data.Data as Exports import Data.Dynamic as Exports import Data.Either as Exports import Data.Fixed as Exports import Data.Foldable as Exports import Data.Function as Exports hiding (id, (.)) import Data.Functor as Exports hiding (unzip) import Data.Functor.Identity as Exports import Data.IORef as Exports import Data.Int as Exports import Data.Ix as Exports import Data.List as Exports hiding (all, and, any, concat, concatMap, elem, find, foldl, foldl', foldl1, foldr, foldr1, isSubsequenceOf, mapAccumL, mapAccumR, maximum, maximumBy, minimum, minimumBy, notElem, or, product, sortOn, sum, uncons) import Data.Maybe as Exports import Data.Monoid as Exports hiding (First (..), Last (..), (<>)) import Data.Ord as Exports import Data.Proxy as Exports import Data.Ratio as Exports import Data.STRef as Exports import Data.Semigroup as Exports import Data.String as Exports import Data.Text as Exports (Text) import Data.Time as Exports import Data.Traversable as Exports import Data.Tuple as Exports import Data.Unique as Exports import Data.Version as Exports import Data.Word as Exports import Debug.Trace as Exports import Foreign.ForeignPtr as Exports import Foreign.ForeignPtr.Unsafe as Exports import Foreign.Ptr as Exports import Foreign.StablePtr as Exports import Foreign.Storable as Exports hiding (alignment, sizeOf) import GHC.Conc as Exports hiding (threadWaitRead, threadWaitReadSTM, threadWaitWrite, threadWaitWriteSTM, withMVar) import GHC.Exts as Exports (groupWith, inline, lazy, sortWith) import GHC.Generics as Exports (Generic) import GHC.IO.Exception as Exports import Numeric as Exports import Numeric.Natural as Exports (Natural) import System.Environment as Exports import System.Exit as Exports import System.IO as Exports import System.IO.Error as Exports import System.IO.Unsafe as Exports import System.Mem as Exports import System.Mem.StableName as Exports import System.Timeout as Exports import Text.Printf as Exports (hPrintf, printf) import Text.Read as Exports (Read (..), readEither, readMaybe) import Unsafe.Coerce as Exports import Prelude as Exports hiding (all, and, any, concat, concatMap, elem, foldl, foldl1, foldr, foldr1, id, mapM, mapM_, maximum, minimum, notElem, or, product, sequence, sequence_, sum, (.)) text-builder-1.0.0.4/test/0000755000000000000000000000000007346545000013432 5ustar0000000000000000text-builder-1.0.0.4/test/Main.hs0000644000000000000000000001454407346545000014662 0ustar0000000000000000module Main where import qualified Data.ByteString as ByteString import Data.Char (intToDigit) import Data.Int import Data.Proxy import Data.String import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import Data.Word import Numeric import Numeric.Natural (Natural) import Test.QuickCheck.Classes import Test.QuickCheck.Instances () import Test.Tasty import Test.Tasty.QuickCheck import TextBuilder import Util.ExtraInstances () import Util.TestTrees import Prelude main :: IO () main = (defaultMain . testGroup "All") tests tests :: [TestTree] tests = [ testGroup "Instances" $ [ followsLaws $ showLaws (Proxy @TextBuilder), followsLaws $ eqLaws (Proxy @TextBuilder), followsLaws $ semigroupLaws (Proxy @TextBuilder), followsLaws $ monoidLaws (Proxy @TextBuilder) ], testGroup "Functions" $ [ testGroup "decimal" $ [ testGroup "Int" $ [ mapsToMonoid @Int decimal, testProperty "Encodes the same as show" $ \(x :: Int) -> (fromString . show) x === toText (decimal x) ], testGroup "Int8" $ [ mapsToMonoid @Int8 decimal, testProperty "Encodes the same as show" $ \(x :: Int8) -> (fromString . show) x === toText (decimal x) ], testGroup "Word" $ [ mapsToMonoid @Word decimal, testProperty "Encodes the same as show" $ \(x :: Word) -> (fromString . show) x === toText (decimal x) ], testGroup "Word8" $ [ mapsToMonoid @Word8 decimal, testProperty "Encodes the same as show" $ \(x :: Word8) -> (fromString . show) x === toText (decimal x) ], testGroup "Integer" $ [ mapsToMonoid @Integer decimal, testProperty "Encodes the same as show" $ \(x :: Integer) -> (fromString . show) x === toText (decimal x) ], testGroup "Natural" $ [ mapsToMonoid @Natural decimal, testProperty "Encodes the same as show" $ \(x :: Natural) -> (fromString . show) x === toText (decimal x) ] ], testGroup "fixedLengthDecimal" $ [ testGroup "Word" $ [ mapsToMonoid @Word (fixedLengthDecimal 42) ], testGroup "Natural" $ [ mapsToMonoid @Natural (fixedLengthDecimal 42), testProperty "Encodes the same as printf" $ \(size :: Word8, val :: Natural) -> let rendered = show val renderedLength = length rendered intSize = fromIntegral size padded = if renderedLength > intSize then drop (renderedLength - intSize) rendered else replicate (intSize - renderedLength) '0' <> rendered in fromString padded === toText (fixedLengthDecimal (fromIntegral size) val) ] ], testGroup "thousandSeparatedDecimal" $ [ testGroup "Int" $ [ mapsToMonoid @Int (thousandSeparatedDecimal ','), testProperty "Encodes the same as show" $ \(x :: Int) -> (fromString . show) x === Text.filter (/= ',') (toText (thousandSeparatedDecimal ',' x)) ], testGroup "Int8" $ [ mapsToMonoid @Int8 (thousandSeparatedDecimal ','), testProperty "Encodes the same as show" $ \(x :: Int8) -> (fromString . show) x === Text.filter (/= ',') (toText (thousandSeparatedDecimal ',' x)) ], testGroup "Integer" $ [ mapsToMonoid @Integer (thousandSeparatedDecimal ','), testProperty "Encodes the same as show" $ \(x :: Integer) -> (fromString . show) x === Text.filter (/= ',') (toText (thousandSeparatedDecimal ',' x)) ] ], testGroup "binary" $ [ testGroup "Int" $ [ mapsToMonoid @Int binary, testProperty "Encodes the same as showIntAtBase" $ \(x :: Word32) -> (Text.justifyRight 32 '0' . Text.pack . showIntAtBase 2 intToDigit x) "" === toText (binary x) ] ], testGroup "octal" $ [ testGroup "Int" $ [ mapsToMonoid @Int octal, testProperty "Encodes the same as showIntAtBase" $ \(x :: Word32) -> (Text.justifyRight 11 '0' . Text.pack . showIntAtBase 8 intToDigit x) "" === toText (octal x) ] ], testGroup "hexadecimal" $ [ testGroup "Int" $ [ mapsToMonoid @Int hexadecimal, testProperty "Encodes the same as showHex" $ \(x :: Word32) -> (Text.justifyRight 8 '0' . Text.pack . showHex x) "" === toText (hexadecimal x) ] ], testGroup "unsafeUtf8ByteString" $ [ mapsToMonoid (unsafeUtf8ByteString . Text.encodeUtf8), testProperty "Works on ASCII" $ let gen = listOf do list <- listOf (choose (0, 127)) return (ByteString.pack list) in forAll gen \chunks -> mconcat chunks === Text.encodeUtf8 (toText (foldMap unsafeUtf8ByteString chunks)) ], testGroup "intercalate" $ [ customGenMonoid do sep <- arbitrary texts <- listOf arbitrary return (intercalate (text sep) (fmap text texts)), testProperty "Has the same effect as in Text" $ \separator texts -> Text.intercalate separator texts === toText (intercalate (text separator) (fmap text texts)) ], testGroup "intercalateMap" $ [ customGenMonoid do sep <- arbitrary texts <- listOf arbitrary return (intercalateMap (text sep) text texts), testProperty "intercalateMap sep mapper == intercalate sep . fmap mapper" $ \separator ints -> Text.intercalate separator (fmap (fromString . show @Int) ints) === toText (intercalateMap (text separator) decimal ints) ] ] ] text-builder-1.0.0.4/test/Util/0000755000000000000000000000000007346545000014347 5ustar0000000000000000text-builder-1.0.0.4/test/Util/ExtraInstances.hs0000644000000000000000000000047007346545000017637 0ustar0000000000000000{-# OPTIONS_GHC -Wno-orphans #-} module Util.ExtraInstances where import qualified Data.Text.Lazy.Builder as TextLazyBuilder import Test.QuickCheck import Test.QuickCheck.Instances () import Prelude instance Arbitrary TextLazyBuilder.Builder where arbitrary = TextLazyBuilder.fromLazyText <$> arbitrary text-builder-1.0.0.4/test/Util/TestTrees.hs0000644000000000000000000000452707346545000016635 0ustar0000000000000000module Util.TestTrees where import Data.List.NonEmpty (NonEmpty (..)) import Data.Monoid import Data.Semigroup import Test.QuickCheck import Test.QuickCheck.Classes import Test.QuickCheck.Instances () import Test.Tasty import Test.Tasty.QuickCheck import Prelude -- | Tests mapping from @a@ to @b@ to produce a valid 'Monoid'. -- -- Tests the following properties: -- -- [/Associative/] -- @a '<>' (b '<>' c) ≡ (a '<>' b) '<>' c@ -- [/Semigroup Concatenation/] -- @'sconcat' as ≡ 'foldr1' ('<>') as@ -- [/Times/] -- @'stimes' n a ≡ 'foldr1' ('<>') ('replicate' n a)@ -- [/Left Identity/] -- @mappend mempty a ≡ a@ -- [/Right Identity/] -- @mappend a mempty ≡ a@ -- [/Monoid Concatenation/] -- @mconcat as ≡ foldr mappend mempty as@ mapsToMonoid :: forall a b. (Arbitrary a, Show a, Eq a, Monoid b, Eq b, Show b) => -- | Embed in monoid. (a -> b) -> TestTree mapsToMonoid embed = customGenMonoid (embed <$> arbitrary) -- | Tests mapping from @a@ to @b@ to produce a valid 'Monoid'. -- -- Tests the following properties: -- -- [/Associative/] -- @a '<>' (b '<>' c) ≡ (a '<>' b) '<>' c@ -- [/Semigroup Concatenation/] -- @'sconcat' as ≡ 'foldr1' ('<>') as@ -- [/Times/] -- @'stimes' n a ≡ 'foldr1' ('<>') ('replicate' n a)@ -- [/Left Identity/] -- @mappend mempty a ≡ a@ -- [/Right Identity/] -- @mappend a mempty ≡ a@ -- [/Monoid Concatenation/] -- @mconcat as ≡ foldr mappend mempty as@ customGenMonoid :: (Monoid a, Eq a, Show a) => Gen a -> TestTree customGenMonoid gen = testGroup "Monoid" [ testProperty "Is associative" do x <- gen y <- gen z <- gen pure (x <> (y <> z) === (x <> y) <> z), testProperty "Semigroup concatenation" do xs <- (:|) <$> gen <*> listOf gen pure (sconcat xs === foldr1 (<>) xs), testProperty "Times" do x <- gen Positive n <- arbitrary pure (stimes n x === foldr1 (<>) (replicate n x)), testProperty "Left identity" do x <- gen pure (mempty <> x === x), testProperty "Right identity" do x <- gen pure (x <> mempty === x), testProperty "Monoid concatenation" do xs <- listOf gen pure (mconcat xs === foldr mappend mempty xs) ] followsLaws :: Laws -> TestTree followsLaws Laws {..} = testProperties lawsTypeclass lawsProperties text-builder-1.0.0.4/text-builder.cabal0000644000000000000000000001151507346545000016052 0ustar0000000000000000cabal-version: 3.0 name: text-builder version: 1.0.0.4 category: Text, Builders synopsis: Efficient and flexible strict text builder description: = Summary Fast strict text builder and simple type-safe formatting library. == The Builder The builder abstraction provided by this library is much faster than the standard lazy @Builder@ and even the recently introduced @StrictTextBuilder@ from \"text\". Benchmarks are distributed with the source code. You can see the results in the README file. The abstraction constructs text in two phases. In the first one it estimates the size of the byte array and in the second one it allocates it once and populates it in one go. == Simple and type-safe formatting library The monoidal API of the library provides a simple yet type-safe alternative to formatting strings via @printf@-like tools or more involved solutions like the popular \"[formatting](https://hackage.haskell.org/package/formatting)\" library. == Quality Every bit of the library is heavily covered with tests with CI running tests on a variety of versions of GHC and the \"text\" library. This is crucial because the \"text\" library has made a switch from UTF-16 to UTF-8, leading to drastic changes in its low-level constructs, which builder libraries must rely on, and this library supports both versions of \"text\". = Ecosystem Following is a list of libraries that, alongside this one, make an integrated ecosystem: - "[text-builder-time](https://hackage.haskell.org/package/text-builder-time)" - formatters for the "time" library - "[text-builder-core](https://hackage.haskell.org/package/text-builder-core)" - lower-level unsafe constructs for implementing efficient formatters compatible with this library - "[text-builder-dev](https://hackage.haskell.org/package/text-builder-dev)" - edge of development of new features providing a richer functionality at the cost of more frequent major releases - "[text-builder-lawful-conversions](https://hackage.haskell.org/package/text-builder-lawful-conversions)" - integration with the \"lawful-conversions\" library, providing bidirectional conversions with various types including `String`, `Data.Text.Text`, `Data.Text.Lazy.Text`, `Data.Text.Lazy.Builder.Builder`, `Data.Text.Encoding.StrictTextBuilder`. homepage: https://github.com/nikita-volkov/text-builder bug-reports: https://github.com/nikita-volkov/text-builder/issues author: Nikita Volkov maintainer: Nikita Volkov copyright: (c) 2017, Nikita Volkov license: MIT license-file: LICENSE extra-doc-files: CHANGELOG.md LICENSE README.md source-repository head type: git location: https://github.com/nikita-volkov/text-builder common base default-language: Haskell2010 default-extensions: BangPatterns BlockArguments ConstraintKinds DataKinds DefaultSignatures DeriveDataTypeable DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable DerivingStrategies EmptyDataDecls FlexibleContexts FlexibleInstances FunctionalDependencies GADTs GeneralizedNewtypeDeriving LambdaCase LiberalTypeSynonyms MagicHash MultiParamTypeClasses MultiWayIf NoImplicitPrelude NoMonomorphismRestriction NumericUnderscores OverloadedStrings ParallelListComp PatternGuards QuasiQuotes RankNTypes RecordWildCards ScopedTypeVariables StandaloneDeriving StrictData TemplateHaskell TupleSections TypeApplications TypeFamilies TypeOperators UnboxedTuples ViewPatterns library import: base hs-source-dirs: library exposed-modules: TextBuilder other-modules: TextBuilder.Domains.ByteString TextBuilder.Domains.Combinators TextBuilder.Domains.Digits TextBuilder.Domains.Digits.Codepoints TextBuilder.Domains.Other TextBuilder.Prelude build-depends: base >=4.11 && <5, bytestring >=0.10 && <0.13, text >=1.2 && <3, text-builder-core ^>=0.1.1.1, time >=1.12 && <2, transformers >=0.5 && <0.7, test-suite test import: base type: exitcode-stdio-1.0 hs-source-dirs: test main-is: Main.hs other-modules: Util.ExtraInstances Util.TestTrees build-depends: QuickCheck >=2.14 && <3, base >=4.11 && <5, bytestring >=0.10 && <0.13, quickcheck-classes >=0.6.5 && <0.7, quickcheck-instances >=0.3.32 && <0.4, tasty >=1.5.3 && <2, tasty-quickcheck >=0.11.1 && <0.12, text >=1.2 && <3, text-builder, benchmark bench import: base type: exitcode-stdio-1.0 hs-source-dirs: bench ghc-options: -O2 -threaded -with-rtsopts=-N -with-rtsopts=-A32m -with-rtsopts=-T -fproc-alignment=64 main-is: Main.hs build-depends: base >=4.11 && <5, tasty-bench ^>=0.4.1, text >=2.1.2 && <3, text-builder, text-builder-linear ^>=0.1.3,