text-builder 0.6.7.3 → 0.6.8
raw patch · 11 files changed
+599/−525 lines, 11 filesdep ~rerebase
Dependency ranges changed: rerebase
Files
- CHANGELOG.md +4/−0
- README.md +94/−0
- bench/Main.hs +78/−0
- benchmark-char/Main.hs +0/−58
- benchmark-text/Main.hs +0/−57
- library/Text/Builder.hs +3/−270
- library/Text/Builder/Prelude.hs +0/−70
- library/TextBuilder.hs +274/−0
- library/TextBuilder/Prelude.hs +70/−0
- test/Main.hs +19/−19
- text-builder.cabal +57/−51
+ CHANGELOG.md view
@@ -0,0 +1,4 @@++# 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
+ README.md view
@@ -0,0 +1,94 @@+# Summary++- Efficient monoidal builder of strict textual values+- Text formatting library, an alternative to `printf` or the "formatting" Haskell library++# Performance++The benchmarks show that it's about 2 times faster than the lazy text builder supplied with the "text" package. In the years of existence of this package the collected user feedback proves that's faster in fact.++```+benchmarking Left-biased mappend/1kB/TextBuilder+time 4.567 μs (4.552 μs .. 4.584 μs)+ 1.000 R² (1.000 R² .. 1.000 R²)+mean 4.570 μs (4.551 μs .. 4.592 μs)+std dev 64.42 ns (49.19 ns .. 81.46 ns)+variance introduced by outliers: 12% (moderately inflated)++benchmarking Left-biased mappend/1kB/Data.Text.Lazy.Builder+time 8.855 μs (8.805 μs .. 8.901 μs)+ 1.000 R² (1.000 R² .. 1.000 R²)+mean 8.867 μs (8.821 μs .. 8.908 μs)+std dev 137.9 ns (114.6 ns .. 160.4 ns)+variance introduced by outliers: 13% (moderately inflated)++benchmarking Left-biased mappend/1MB/TextBuilder+time 9.546 ms (9.415 ms .. 9.773 ms)+ 0.998 R² (0.995 R² .. 1.000 R²)+mean 9.407 ms (9.351 ms .. 9.493 ms)+std dev 184.7 μs (92.85 μs .. 333.3 μs)++benchmarking Left-biased mappend/1MB/Data.Text.Lazy.Builder+time 17.04 ms (16.85 ms .. 17.21 ms)+ 0.999 R² (0.999 R² .. 1.000 R²)+mean 17.09 ms (16.99 ms .. 17.19 ms)+std dev 228.4 μs (181.4 μs .. 285.3 μs)++benchmarking Right-biased mappend/1kB/TextBuilder+time 4.481 μs (4.467 μs .. 4.499 μs)+ 1.000 R² (1.000 R² .. 1.000 R²)+mean 4.486 μs (4.470 μs .. 4.508 μs)+std dev 61.86 ns (44.45 ns .. 87.66 ns)+variance introduced by outliers: 11% (moderately inflated)++benchmarking Right-biased mappend/1kB/Data.Text.Lazy.Builder+time 8.751 μs (8.701 μs .. 8.798 μs)+ 1.000 R² (1.000 R² .. 1.000 R²)+mean 8.751 μs (8.697 μs .. 8.805 μs)+std dev 159.2 ns (134.4 ns .. 205.0 ns)+variance introduced by outliers: 17% (moderately inflated)++benchmarking Right-biased mappend/1MB/TextBuilder+time 7.057 ms (7.035 ms .. 7.086 ms)+ 1.000 R² (1.000 R² .. 1.000 R²)+mean 7.071 ms (7.050 ms .. 7.093 ms)+std dev 57.85 μs (43.37 μs .. 83.06 μs)++benchmarking Right-biased mappend/1MB/Data.Text.Lazy.Builder+time 15.10 ms (14.96 ms .. 15.30 ms)+ 0.999 R² (0.999 R² .. 1.000 R²)+mean 15.15 ms (15.04 ms .. 15.25 ms)+std dev 273.0 μs (208.6 μs .. 392.6 μs)++benchmarking mconcat/1kB/TextBuilder+time 4.778 μs (4.756 μs .. 4.800 μs)+ 1.000 R² (1.000 R² .. 1.000 R²)+mean 4.784 μs (4.770 μs .. 4.802 μs)+std dev 58.27 ns (48.36 ns .. 72.11 ns)++benchmarking mconcat/1kB/Data.Text.Lazy.Builder+time 8.046 μs (8.009 μs .. 8.087 μs)+ 1.000 R² (1.000 R² .. 1.000 R²)+mean 8.053 μs (8.021 μs .. 8.084 μs)+std dev 102.3 ns (81.50 ns .. 130.5 ns)++benchmarking mconcat/1MB/TextBuilder+time 8.902 ms (8.856 ms .. 8.931 ms)+ 1.000 R² (1.000 R² .. 1.000 R²)+mean 8.892 ms (8.868 ms .. 8.926 ms)+std dev 81.44 μs (62.67 μs .. 119.7 μs)++benchmarking mconcat/1MB/Data.Text.Lazy.Builder+time 10.56 ms (10.39 ms .. 10.68 ms)+ 0.999 R² (0.998 R² .. 0.999 R²)+mean 10.50 ms (10.42 ms .. 10.60 ms)+std dev 260.2 μs (191.2 μs .. 375.9 μs)+```++# 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.++# What is text-builder-dev?++It is a lower-level library which this one wraps and provides a stable interface for. It serves as a testing ground for new features and design exploration.
+ bench/Main.hs view
@@ -0,0 +1,78 @@+module Main where++import Criterion.Main+import qualified Data.Text.Lazy as C+import qualified Data.Text.Lazy.Builder as B+import qualified TextBuilder as A+import Prelude++main :: IO ()+main =+ defaultMain+ [ bgroup+ "Left-biased mappend"+ let {-# NOINLINE sampleBySize #-}+ sampleBySize :: (Monoid a, IsString a) => Int -> (a -> Text) -> Text+ sampleBySize size compile =+ "фывапролдж"+ & replicate size+ & foldl' (<>) mempty+ & compile+ in [ bgroup+ "1kB"+ let sample = sampleBySize 100+ in [ bench "TextBuilder" (nf sample A.run),+ bench "Data.Text.Lazy.Builder" (nf sample (C.toStrict . B.toLazyText))+ ],+ bgroup+ "1MB"+ let sample = sampleBySize 100_000+ in [ bench "TextBuilder" (nf sample A.run),+ bench "Data.Text.Lazy.Builder" (nf sample (C.toStrict . B.toLazyText))+ ]+ ],+ bgroup+ "Right-biased mappend"+ let {-# NOINLINE sampleBySize #-}+ sampleBySize :: (Monoid a, IsString a) => Int -> (a -> Text) -> Text+ sampleBySize size compile =+ "фывапролдж"+ & replicate size+ & foldl' (flip (<>)) mempty+ & compile+ in [ bgroup+ "1kB"+ let sample = sampleBySize 100+ in [ bench "TextBuilder" (nf sample A.run),+ bench "Data.Text.Lazy.Builder" (nf sample (C.toStrict . B.toLazyText))+ ],+ bgroup+ "1MB"+ let sample = sampleBySize 100_000+ in [ bench "TextBuilder" (nf sample A.run),+ bench "Data.Text.Lazy.Builder" (nf sample (C.toStrict . B.toLazyText))+ ]+ ],+ bgroup+ "mconcat"+ let {-# NOINLINE sampleBySize #-}+ sampleBySize :: (Monoid a, IsString a) => Int -> (a -> Text) -> Text+ sampleBySize size compile =+ "фывапролдж"+ & replicate size+ & mconcat+ & compile+ in [ bgroup+ "1kB"+ let sample = sampleBySize 100+ in [ bench "TextBuilder" (nf sample A.run),+ bench "Data.Text.Lazy.Builder" (nf sample (C.toStrict . B.toLazyText))+ ],+ bgroup+ "1MB"+ let sample = sampleBySize 100_000+ in [ bench "TextBuilder" (nf sample A.run),+ bench "Data.Text.Lazy.Builder" (nf sample (C.toStrict . B.toLazyText))+ ]+ ]+ ]
− benchmark-char/Main.hs
@@ -1,58 +0,0 @@-module Main where--import Criterion.Main-import qualified Data.Text as D-import qualified Data.Text.Lazy as C-import qualified Data.Text.Lazy.Builder as B-import qualified Text.Builder as A-import Prelude--main :: IO ()-main =- defaultMain- $ [ subjectBenchmark "builderSubject" builderSubject,- subjectBenchmark "lazyTextBuilderSubject" lazyTextBuilderSubject,- subjectBenchmark "plainTextPackingSubject" plainTextPackingSubject- ]--subjectBenchmark :: String -> Subject -> Benchmark-subjectBenchmark title subject =- bgroup title- $ [ benchmark "Small input" smallInput subject,- benchmark "Medium input" mediumInput subject,- benchmark "Large input" largeInput subject- ]--benchmark :: String -> [Int] -> Subject -> Benchmark-benchmark title input subject =- bench title $ nf subject $ input--type Subject =- [Int] -> Text--builderSubject :: Subject-builderSubject =- A.run . A.string . map chr--lazyTextBuilderSubject :: Subject-lazyTextBuilderSubject =- C.toStrict . B.toLazyText . B.fromString . map chr--plainTextPackingSubject :: Subject-plainTextPackingSubject =- D.pack . map chr--{-# NOINLINE smallInput #-}-smallInput :: [Int]-smallInput =- map ord ['a', 'b', 'Ф', '漢', chr 0x11000]--{-# NOINLINE mediumInput #-}-mediumInput :: [Int]-mediumInput =- mconcat (replicate 1000 smallInput)--{-# NOINLINE largeInput #-}-largeInput :: [Int]-largeInput =- mconcat (replicate 100000 smallInput)
− benchmark-text/Main.hs
@@ -1,57 +0,0 @@-module Main where--import Criterion.Main-import qualified Data.Text.Lazy as C-import qualified Data.Text.Lazy.Builder as B-import qualified Text.Builder as A-import Prelude--main :: IO ()-main =- defaultMain- $ [ subjectBenchmark "builderSubject" builderSubject,- subjectBenchmark "lazyTextBuilderSubject" lazyTextBuilderSubject- ]--subjectBenchmark :: String -> Subject -> Benchmark-subjectBenchmark title subject =- bgroup title- $ [ benchmark "Small input" smallSample subject,- benchmark "Large input" largeSample subject- ]--benchmark :: String -> Sample -> Subject -> Benchmark-benchmark title sample subject =- bench title $ nf sample $ subject--data Subject- = forall a. Subject (Text -> a) (a -> a -> a) a (a -> Text)--type Sample =- Subject -> Text--builderSubject :: Subject-builderSubject =- Subject A.text mappend mempty A.run--lazyTextBuilderSubject :: Subject-lazyTextBuilderSubject =- Subject B.fromText mappend mempty (C.toStrict . B.toLazyText)--{-# NOINLINE smallSample #-}-smallSample :: Sample-smallSample (Subject text (<>) mempty run) =- run- $ text "abcd"- <> (text "ABCD" <> text "Фываолдж")- <> text "漢"--{-# NOINLINE largeSample #-}-largeSample :: Sample-largeSample (Subject text (<>) mempty run) =- run- $ foldl' (<>) mempty- $ replicate 100000- $ text "abcd"- <> (text "ABCD" <> text "Фываолдж")- <> text "漢"
library/Text/Builder.hs view
@@ -1,274 +1,7 @@ module Text.Builder- ( Builder,-- -- * Accessors- run,- length,- null,-- -- ** Output IO- putToStdOut,- putToStdErr,- putLnToStdOut,- putLnToStdErr,-- -- * Constructors-- -- ** Builder manipulators- intercalate,- padFromLeft,- padFromRight,-- -- ** Textual- text,- lazyText,- string,- asciiByteString,- hexData,-- -- ** Character- char,-- -- *** Low-level character- unicodeCodePoint,- utf16CodeUnits1,- utf16CodeUnits2,- utf8CodeUnits1,- utf8CodeUnits2,- utf8CodeUnits3,- utf8CodeUnits4,-- -- ** Integers-- -- *** Decimal- decimal,- unsignedDecimal,- thousandSeparatedDecimal,- thousandSeparatedUnsignedDecimal,- dataSizeInBytesInDecimal,-- -- *** Binary- unsignedBinary,- unsignedPaddedBinary,-- -- *** Hexadecimal- hexadecimal,- unsignedHexadecimal,-- -- ** Digits- decimalDigit,- hexadecimalDigit,-- -- ** Real- fixedDouble,- doublePercent,-- -- ** Time- intervalInSeconds,+ {-# DEPRECATED "Use TextBuilder instead" #-}+ ( module TextBuilder, ) where -import qualified Data.Text.Lazy as TextLazy-import Text.Builder.Prelude hiding (intercalate, length, null)-import qualified TextBuilderDev as Dev---- |--- Specification of how to efficiently construct strict 'Text'.--- Provides instances of 'Semigroup' and 'Monoid', which have complexity of /O(1)/.-newtype Builder- = Builder Dev.TextBuilder- deriving (Show, IsString, Semigroup, Monoid)---- | Get the amount of characters-{-# INLINE length #-}-length :: Builder -> Int-length = coerce Dev.length---- | Check whether the builder is empty-{-# INLINE null #-}-null :: Builder -> Bool-null = coerce Dev.null---- | Execute a builder producing a strict text-run :: Builder -> Text-run = coerce Dev.buildText---- ** Output IO---- | Put builder, to stdout-putToStdOut :: Builder -> IO ()-putToStdOut = coerce Dev.putToStdOut---- | Put builder, to stderr-putToStdErr :: Builder -> IO ()-putToStdErr = coerce Dev.putToStdErr---- | Put builder, followed by a line, to stdout-putLnToStdOut :: Builder -> IO ()-putLnToStdOut = coerce Dev.putLnToStdOut---- | Put builder, followed by a line, to stderr-putLnToStdErr :: Builder -> IO ()-putLnToStdErr = coerce Dev.putLnToStdErr---- * Constructors---- | Unicode character-{-# INLINE char #-}-char :: Char -> Builder-char = coerce Dev.char---- | Unicode code point-{-# INLINE unicodeCodePoint #-}-unicodeCodePoint :: Int -> Builder-unicodeCodePoint = coerce Dev.unicodeCodePoint---- | Single code-unit UTF-16 character-{-# INLINEABLE utf16CodeUnits1 #-}-utf16CodeUnits1 :: Word16 -> Builder-utf16CodeUnits1 = coerce Dev.utf16CodeUnits1---- | Double code-unit UTF-16 character-{-# INLINEABLE utf16CodeUnits2 #-}-utf16CodeUnits2 :: Word16 -> Word16 -> Builder-utf16CodeUnits2 = coerce Dev.utf16CodeUnits2---- | Single code-unit UTF-8 character-{-# INLINE utf8CodeUnits1 #-}-utf8CodeUnits1 :: Word8 -> Builder-utf8CodeUnits1 = coerce Dev.utf8CodeUnits1---- | Double code-unit UTF-8 character-{-# INLINE utf8CodeUnits2 #-}-utf8CodeUnits2 :: Word8 -> Word8 -> Builder-utf8CodeUnits2 = coerce Dev.utf8CodeUnits2---- | Triple code-unit UTF-8 character-{-# INLINE utf8CodeUnits3 #-}-utf8CodeUnits3 :: Word8 -> Word8 -> Word8 -> Builder-utf8CodeUnits3 = coerce Dev.utf8CodeUnits3---- | UTF-8 character out of 4 code units-{-# INLINE utf8CodeUnits4 #-}-utf8CodeUnits4 :: Word8 -> Word8 -> Word8 -> Word8 -> Builder-utf8CodeUnits4 = coerce Dev.utf8CodeUnits4---- | ASCII byte string-{-# INLINE asciiByteString #-}-asciiByteString :: ByteString -> Builder-asciiByteString = coerce Dev.asciiByteString---- | Strict text-{-# INLINE text #-}-text :: Text -> Builder-text = coerce Dev.text---- | Lazy text-{-# INLINE lazyText #-}-lazyText :: TextLazy.Text -> Builder-lazyText = coerce Dev.lazyText---- | String-{-# INLINE string #-}-string :: String -> Builder-string = coerce Dev.string---- | Decimal representation of an integral value-{-# INLINEABLE decimal #-}-decimal :: (Integral a) => a -> Builder-decimal = coerce . Dev.decimal---- | Decimal representation of an unsigned integral value-{-# INLINEABLE unsignedDecimal #-}-unsignedDecimal :: (Integral a) => a -> Builder-unsignedDecimal = coerce . Dev.unsignedDecimal---- | Decimal representation of an integral value with thousands separated by the specified character-{-# INLINEABLE thousandSeparatedDecimal #-}-thousandSeparatedDecimal :: (Integral a) => Char -> a -> Builder-thousandSeparatedDecimal = fmap coerce . Dev.thousandSeparatedDecimal---- | Decimal representation of an unsigned integral value with thousands separated by the specified character-{-# INLINEABLE thousandSeparatedUnsignedDecimal #-}-thousandSeparatedUnsignedDecimal :: (Integral a) => Char -> a -> Builder-thousandSeparatedUnsignedDecimal = fmap coerce . Dev.thousandSeparatedUnsignedDecimal---- | Data size in decimal notation over amount of bytes.-{-# INLINEABLE dataSizeInBytesInDecimal #-}-dataSizeInBytesInDecimal :: (Integral a) => Char -> a -> Builder-dataSizeInBytesInDecimal = fmap coerce . Dev.dataSizeInBytesInDecimal---- | Unsigned binary number-{-# INLINE unsignedBinary #-}-unsignedBinary :: (Integral a) => a -> Builder-unsignedBinary = coerce . Dev.unsignedBinary---- | Unsigned binary number-{-# INLINE unsignedPaddedBinary #-}-unsignedPaddedBinary :: (Integral a, FiniteBits a) => a -> Builder-unsignedPaddedBinary = coerce . Dev.unsignedPaddedBinary---- | Hexadecimal representation of an integral value-{-# INLINE hexadecimal #-}-hexadecimal :: (Integral a) => a -> Builder-hexadecimal = coerce . Dev.hexadecimal---- | Unsigned hexadecimal representation of an integral value-{-# INLINE unsignedHexadecimal #-}-unsignedHexadecimal :: (Integral a) => a -> Builder-unsignedHexadecimal = coerce . Dev.unsignedHexadecimal---- | Decimal digit-{-# INLINE decimalDigit #-}-decimalDigit :: (Integral a) => a -> Builder-decimalDigit = coerce . Dev.decimalDigit---- | Hexadecimal digit-{-# INLINE hexadecimalDigit #-}-hexadecimalDigit :: (Integral a) => a -> Builder-hexadecimalDigit = coerce . Dev.hexadecimalDigit---- | Intercalate builders-{-# INLINE intercalate #-}-intercalate :: (Foldable foldable) => Builder -> foldable Builder -> Builder-intercalate a b = coerce (Dev.intercalate (coerce a) (foldr ((:) . coerce) [] b))---- | Pad a builder from the left side to the specified length with the specified character-{-# INLINEABLE padFromLeft #-}-padFromLeft :: Int -> Char -> Builder -> Builder-padFromLeft = coerce Dev.padFromLeft---- | Pad a builder from the right side to the specified length with the specified character-{-# INLINEABLE padFromRight #-}-padFromRight :: Int -> Char -> Builder -> Builder-padFromRight = coerce Dev.padFromRight---- |--- Time interval in seconds.--- Directly applicable to 'DiffTime' and 'NominalDiffTime'.-{-# INLINEABLE intervalInSeconds #-}-intervalInSeconds :: (RealFrac seconds) => seconds -> Builder-intervalInSeconds = coerce . Dev.intervalInSeconds---- | Double with a fixed number of decimal places.-{-# INLINE fixedDouble #-}-fixedDouble ::- -- | Amount of decimals after point.- Int ->- Double ->- Builder-fixedDouble = coerce Dev.fixedDouble---- | Double multiplied by 100 with a fixed number of decimal places applied and followed by a percent-sign.-{-# INLINE doublePercent #-}-doublePercent ::- -- | Amount of decimals after point.- Int ->- Double ->- Builder-doublePercent = coerce Dev.doublePercent---- | Hexadecimal readable representation of binary data.-{-# INLINE hexData #-}-hexData :: ByteString -> Builder-hexData = coerce Dev.hexData+import TextBuilder
− library/Text/Builder/Prelude.hs
@@ -1,70 +0,0 @@-module Text.Builder.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 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.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 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, (.))
+ library/TextBuilder.hs view
@@ -0,0 +1,274 @@+module TextBuilder+ ( Builder,++ -- * Accessors+ run,+ length,+ null,++ -- ** Output IO+ putToStdOut,+ putToStdErr,+ putLnToStdOut,+ putLnToStdErr,++ -- * Constructors++ -- ** Builder manipulators+ intercalate,+ padFromLeft,+ padFromRight,++ -- ** Textual+ text,+ lazyText,+ string,+ asciiByteString,+ hexData,++ -- ** Character+ char,++ -- *** Low-level character+ unicodeCodePoint,+ utf16CodeUnits1,+ utf16CodeUnits2,+ utf8CodeUnits1,+ utf8CodeUnits2,+ utf8CodeUnits3,+ utf8CodeUnits4,++ -- ** Integers++ -- *** Decimal+ decimal,+ unsignedDecimal,+ thousandSeparatedDecimal,+ thousandSeparatedUnsignedDecimal,+ dataSizeInBytesInDecimal,++ -- *** Binary+ unsignedBinary,+ unsignedPaddedBinary,++ -- *** Hexadecimal+ hexadecimal,+ unsignedHexadecimal,++ -- ** Digits+ decimalDigit,+ hexadecimalDigit,++ -- ** Real+ fixedDouble,+ doublePercent,++ -- ** Time+ intervalInSeconds,+ )+where++import qualified Data.Text.Lazy as TextLazy+import TextBuilder.Prelude hiding (intercalate, length, null)+import qualified TextBuilderDev as Dev++-- |+-- Specification of how to efficiently construct strict 'Text'.+-- Provides instances of 'Semigroup' and 'Monoid', which have complexity of /O(1)/.+newtype Builder+ = Builder Dev.TextBuilder+ deriving (Show, IsString, Semigroup, Monoid)++-- | Get the amount of characters+{-# INLINE length #-}+length :: Builder -> Int+length = coerce Dev.length++-- | Check whether the builder is empty+{-# INLINE null #-}+null :: Builder -> Bool+null = coerce Dev.null++-- | Execute a builder producing a strict text+run :: Builder -> Text+run = coerce Dev.buildText++-- ** Output IO++-- | Put builder, to stdout+putToStdOut :: Builder -> IO ()+putToStdOut = coerce Dev.putToStdOut++-- | Put builder, to stderr+putToStdErr :: Builder -> IO ()+putToStdErr = coerce Dev.putToStdErr++-- | Put builder, followed by a line, to stdout+putLnToStdOut :: Builder -> IO ()+putLnToStdOut = coerce Dev.putLnToStdOut++-- | Put builder, followed by a line, to stderr+putLnToStdErr :: Builder -> IO ()+putLnToStdErr = coerce Dev.putLnToStdErr++-- * Constructors++-- | Unicode character+{-# INLINE char #-}+char :: Char -> Builder+char = coerce Dev.char++-- | Unicode code point+{-# INLINE unicodeCodePoint #-}+unicodeCodePoint :: Int -> Builder+unicodeCodePoint = coerce Dev.unicodeCodePoint++-- | Single code-unit UTF-16 character+{-# INLINEABLE utf16CodeUnits1 #-}+utf16CodeUnits1 :: Word16 -> Builder+utf16CodeUnits1 = coerce Dev.utf16CodeUnits1++-- | Double code-unit UTF-16 character+{-# INLINEABLE utf16CodeUnits2 #-}+utf16CodeUnits2 :: Word16 -> Word16 -> Builder+utf16CodeUnits2 = coerce Dev.utf16CodeUnits2++-- | Single code-unit UTF-8 character+{-# INLINE utf8CodeUnits1 #-}+utf8CodeUnits1 :: Word8 -> Builder+utf8CodeUnits1 = coerce Dev.utf8CodeUnits1++-- | Double code-unit UTF-8 character+{-# INLINE utf8CodeUnits2 #-}+utf8CodeUnits2 :: Word8 -> Word8 -> Builder+utf8CodeUnits2 = coerce Dev.utf8CodeUnits2++-- | Triple code-unit UTF-8 character+{-# INLINE utf8CodeUnits3 #-}+utf8CodeUnits3 :: Word8 -> Word8 -> Word8 -> Builder+utf8CodeUnits3 = coerce Dev.utf8CodeUnits3++-- | UTF-8 character out of 4 code units+{-# INLINE utf8CodeUnits4 #-}+utf8CodeUnits4 :: Word8 -> Word8 -> Word8 -> Word8 -> Builder+utf8CodeUnits4 = coerce Dev.utf8CodeUnits4++-- | ASCII byte string+{-# INLINE asciiByteString #-}+asciiByteString :: ByteString -> Builder+asciiByteString = coerce Dev.asciiByteString++-- | Strict text+{-# INLINE text #-}+text :: Text -> Builder+text = coerce Dev.text++-- | Lazy text+{-# INLINE lazyText #-}+lazyText :: TextLazy.Text -> Builder+lazyText = coerce Dev.lazyText++-- | String+{-# INLINE string #-}+string :: String -> Builder+string = coerce Dev.string++-- | Decimal representation of an integral value+{-# INLINEABLE decimal #-}+decimal :: (Integral a) => a -> Builder+decimal = coerce . Dev.decimal++-- | Decimal representation of an unsigned integral value+{-# INLINEABLE unsignedDecimal #-}+unsignedDecimal :: (Integral a) => a -> Builder+unsignedDecimal = coerce . Dev.unsignedDecimal++-- | Decimal representation of an integral value with thousands separated by the specified character+{-# INLINEABLE thousandSeparatedDecimal #-}+thousandSeparatedDecimal :: (Integral a) => Char -> a -> Builder+thousandSeparatedDecimal = fmap coerce . Dev.thousandSeparatedDecimal++-- | Decimal representation of an unsigned integral value with thousands separated by the specified character+{-# INLINEABLE thousandSeparatedUnsignedDecimal #-}+thousandSeparatedUnsignedDecimal :: (Integral a) => Char -> a -> Builder+thousandSeparatedUnsignedDecimal = fmap coerce . Dev.thousandSeparatedUnsignedDecimal++-- | Data size in decimal notation over amount of bytes.+{-# INLINEABLE dataSizeInBytesInDecimal #-}+dataSizeInBytesInDecimal :: (Integral a) => Char -> a -> Builder+dataSizeInBytesInDecimal = fmap coerce . Dev.dataSizeInBytesInDecimal++-- | Unsigned binary number+{-# INLINE unsignedBinary #-}+unsignedBinary :: (Integral a) => a -> Builder+unsignedBinary = coerce . Dev.unsignedBinary++-- | Unsigned binary number+{-# INLINE unsignedPaddedBinary #-}+unsignedPaddedBinary :: (Integral a, FiniteBits a) => a -> Builder+unsignedPaddedBinary = coerce . Dev.unsignedPaddedBinary++-- | Hexadecimal representation of an integral value+{-# INLINE hexadecimal #-}+hexadecimal :: (Integral a) => a -> Builder+hexadecimal = coerce . Dev.hexadecimal++-- | Unsigned hexadecimal representation of an integral value+{-# INLINE unsignedHexadecimal #-}+unsignedHexadecimal :: (Integral a) => a -> Builder+unsignedHexadecimal = coerce . Dev.unsignedHexadecimal++-- | Decimal digit+{-# INLINE decimalDigit #-}+decimalDigit :: (Integral a) => a -> Builder+decimalDigit = coerce . Dev.decimalDigit++-- | Hexadecimal digit+{-# INLINE hexadecimalDigit #-}+hexadecimalDigit :: (Integral a) => a -> Builder+hexadecimalDigit = coerce . Dev.hexadecimalDigit++-- | Intercalate builders+{-# INLINE intercalate #-}+intercalate :: (Foldable foldable) => Builder -> foldable Builder -> Builder+intercalate a b = coerce (Dev.intercalate (coerce a) (foldr ((:) . coerce) [] b))++-- | Pad a builder from the left side to the specified length with the specified character+{-# INLINEABLE padFromLeft #-}+padFromLeft :: Int -> Char -> Builder -> Builder+padFromLeft = coerce Dev.padFromLeft++-- | Pad a builder from the right side to the specified length with the specified character+{-# INLINEABLE padFromRight #-}+padFromRight :: Int -> Char -> Builder -> Builder+padFromRight = coerce Dev.padFromRight++-- |+-- Time interval in seconds.+-- Directly applicable to 'DiffTime' and 'NominalDiffTime'.+{-# INLINEABLE intervalInSeconds #-}+intervalInSeconds :: (RealFrac seconds) => seconds -> Builder+intervalInSeconds = coerce . Dev.intervalInSeconds++-- | Double with a fixed number of decimal places.+{-# INLINE fixedDouble #-}+fixedDouble ::+ -- | Amount of decimals after point.+ Int ->+ Double ->+ Builder+fixedDouble = coerce Dev.fixedDouble++-- | Double multiplied by 100 with a fixed number of decimal places applied and followed by a percent-sign.+{-# INLINE doublePercent #-}+doublePercent ::+ -- | Amount of decimals after point.+ Int ->+ Double ->+ Builder+doublePercent = coerce Dev.doublePercent++-- | Hexadecimal readable representation of binary data.+{-# INLINE hexData #-}+hexData :: ByteString -> Builder+hexData = coerce Dev.hexData
+ library/TextBuilder/Prelude.hs view
@@ -0,0 +1,70 @@+module 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 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.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 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, (.))
test/Main.hs view
@@ -6,7 +6,7 @@ import Test.Tasty import Test.Tasty.HUnit import Test.Tasty.QuickCheck-import qualified Text.Builder as B+import qualified TextBuilder as B import Prelude hiding (choose) main :: IO ()@@ -47,10 +47,10 @@ x >= 0 ==> (fromString . showHex x) "" === (B.run . B.hexadecimal) x, testCase "Separated thousands" $ do- assertEqual "" "0" (B.run (B.thousandSeparatedUnsignedDecimal ',' 0))- assertEqual "" "123" (B.run (B.thousandSeparatedUnsignedDecimal ',' 123))- assertEqual "" "1,234" (B.run (B.thousandSeparatedUnsignedDecimal ',' 1234))- assertEqual "" "1,234,567" (B.run (B.thousandSeparatedUnsignedDecimal ',' 1234567)),+ assertEqual "" "0" (B.run (B.thousandSeparatedUnsignedDecimal @Int ',' 0))+ assertEqual "" "123" (B.run (B.thousandSeparatedUnsignedDecimal @Int ',' 123))+ assertEqual "" "1,234" (B.run (B.thousandSeparatedUnsignedDecimal @Int ',' 1234))+ assertEqual "" "1,234,567" (B.run (B.thousandSeparatedUnsignedDecimal @Int ',' 1234567)), testCase "Pad from left" $ do assertEqual "" "00" (B.run (B.padFromLeft 2 '0' "")) assertEqual "" "00" (B.run (B.padFromLeft 2 '0' "0"))@@ -65,22 +65,22 @@ assertEqual "" "123" (B.run (B.padFromRight 2 '0' "123")) assertEqual "" "1 " (B.run (B.padFromRight 3 ' ' "1")), testCase "Hexadecimal"- $ assertEqual "" "1f23" (B.run (B.hexadecimal 0x01f23)),+ $ assertEqual "" "1f23" (B.run (B.hexadecimal @Int 0x01f23)), testCase "Negative Hexadecimal"- $ assertEqual "" "-1f23" (B.run (B.hexadecimal (-0x01f23))),+ $ assertEqual "" "-1f23" (B.run (B.hexadecimal @Int (-0x01f23))), testGroup "Time interval"- $ [ testCase "59s" $ assertEqual "" "00:00:00:59" $ B.run $ B.intervalInSeconds 59,- testCase "minute" $ assertEqual "" "00:00:01:00" $ B.run $ B.intervalInSeconds 60,- testCase "90s" $ assertEqual "" "00:00:01:30" $ B.run $ B.intervalInSeconds 90,- testCase "hour" $ assertEqual "" "00:01:00:00" $ B.run $ B.intervalInSeconds 3600,- testCase "day" $ assertEqual "" "01:00:00:00" $ B.run $ B.intervalInSeconds 86400+ $ [ testCase "59s" $ assertEqual "" "00:00:00:59" $ B.run $ B.intervalInSeconds @Rational 59,+ testCase "minute" $ assertEqual "" "00:00:01:00" $ B.run $ B.intervalInSeconds @Rational 60,+ testCase "90s" $ assertEqual "" "00:00:01:30" $ B.run $ B.intervalInSeconds @Rational 90,+ testCase "hour" $ assertEqual "" "00:01:00:00" $ B.run $ B.intervalInSeconds @Rational 3600,+ testCase "day" $ assertEqual "" "01:00:00:00" $ B.run $ B.intervalInSeconds @Rational 86400 ], testCase "dataSizeInBytesInDecimal" $ do- assertEqual "" "999B" (B.run (B.dataSizeInBytesInDecimal ',' 999))- assertEqual "" "1kB" (B.run (B.dataSizeInBytesInDecimal ',' 1000))- assertEqual "" "1.1kB" (B.run (B.dataSizeInBytesInDecimal ',' 1100))- assertEqual "" "1.1MB" (B.run (B.dataSizeInBytesInDecimal ',' 1150000))- assertEqual "" "9.9MB" (B.run (B.dataSizeInBytesInDecimal ',' 9990000))- assertEqual "" "10MB" (B.run (B.dataSizeInBytesInDecimal ',' 10100000))- assertEqual "" "1,000YB" (B.run (B.dataSizeInBytesInDecimal ',' 1000000000000000000000000000))+ assertEqual "" "999B" (B.run (B.dataSizeInBytesInDecimal @Int ',' 999))+ assertEqual "" "1kB" (B.run (B.dataSizeInBytesInDecimal @Int ',' 1000))+ assertEqual "" "1.1kB" (B.run (B.dataSizeInBytesInDecimal @Int ',' 1100))+ assertEqual "" "1.1MB" (B.run (B.dataSizeInBytesInDecimal @Int ',' 1150000))+ assertEqual "" "9.9MB" (B.run (B.dataSizeInBytesInDecimal @Int ',' 9990000))+ assertEqual "" "10MB" (B.run (B.dataSizeInBytesInDecimal @Int ',' 10100000))+ assertEqual "" "1,000YB" (B.run (B.dataSizeInBytesInDecimal @Integer ',' 1000000000000000000000000000)) ]
text-builder.cabal view
@@ -1,27 +1,32 @@ cabal-version: 3.0-name: text-builder-version: 0.6.7.3-category: Text, Builders-synopsis: Efficient strict text builder-description: Text formatting library and efficient builder library.-homepage: https://github.com/nikita-volkov/text-builder-bug-reports: https://github.com/nikita-volkov/text-builder/issues-author: Nikita Volkov <nikita.y.volkov@mail.ru>-maintainer: Nikita Volkov <nikita.y.volkov@mail.ru>-copyright: (c) 2017, Nikita Volkov-license: MIT-license-file: LICENSE+name: text-builder+version: 0.6.8+category: Text, Builders+synopsis: Efficient strict text builder+description:+ - Efficient monoidal builder of strict textual values+ - Text formatting library, an alternative to `printf` or the "formatting" Haskell library +homepage: https://github.com/nikita-volkov/text-builder+bug-reports: https://github.com/nikita-volkov/text-builder/issues+author: Nikita Volkov <nikita.y.volkov@mail.ru>+maintainer: Nikita Volkov <nikita.y.volkov@mail.ru>+copyright: (c) 2017, Nikita Volkov+license: MIT+license-file: LICENSE+extra-source-files:+ CHANGELOG.md+ README.md+ source-repository head- type: git+ type: git location: https://github.com/nikita-volkov/text-builder common base- default-language: Haskell2010+ default-language: Haskell2010 default-extensions:- NoImplicitPrelude- NoMonomorphismRestriction BangPatterns+ BlockArguments ConstraintKinds DataKinds DefaultSignatures@@ -41,6 +46,9 @@ MagicHash MultiParamTypeClasses MultiWayIf+ NoImplicitPrelude+ NoMonomorphismRestriction+ NumericUnderscores OverloadedStrings ParallelListComp PatternGuards@@ -51,51 +59,49 @@ StandaloneDeriving TemplateHaskell TupleSections+ TypeApplications TypeFamilies TypeOperators UnboxedTuples library- import: base- hs-source-dirs: library- exposed-modules: Text.Builder- other-modules: Text.Builder.Prelude+ import: base+ hs-source-dirs: library+ exposed-modules:+ Text.Builder+ TextBuilder++ other-modules: TextBuilder.Prelude build-depends:- , base >=4.11 && <5- , bytestring >=0.10 && <0.13- , text >=1.2 && <3- , text-builder-dev >=0.3.4.1 && <0.4+ base >=4.11 && <5,+ bytestring >=0.10 && <0.13,+ text >=1.2 && <3,+ text-builder-dev >=0.3.4.1 && <0.4, test-suite test- import: base- type: exitcode-stdio-1.0+ import: base+ type: exitcode-stdio-1.0 hs-source-dirs: test- main-is: Main.hs+ main-is: Main.hs build-depends:- , rerebase <2- , tasty >=1.2.3 && <2- , tasty-hunit >=0.10.0.2 && <0.11- , tasty-quickcheck >=0.10.1 && <0.12- , text-builder+ rerebase <2,+ tasty >=1.2.3 && <2,+ tasty-hunit >=0.10.0.2 && <0.11,+ tasty-quickcheck >=0.10.1 && <0.12,+ text-builder, -benchmark benchmark-text- import: base- type: exitcode-stdio-1.0- hs-source-dirs: benchmark-text- ghc-options: -O2 -threaded -with-rtsopts=-N -funbox-strict-fields- main-is: Main.hs- build-depends:- , criterion >=1.5.6.1 && <2- , rerebase >=1 && <2- , text-builder+benchmark bench+ import: base+ type: exitcode-stdio-1.0+ hs-source-dirs: bench+ ghc-options:+ -O2+ -threaded+ -with-rtsopts=-N+ -funbox-strict-fields -benchmark benchmark-char- import: base- type: exitcode-stdio-1.0- hs-source-dirs: benchmark-char- ghc-options: -O2 -threaded -with-rtsopts=-N -funbox-strict-fields- main-is: Main.hs+ main-is: Main.hs build-depends:- , criterion >=1.5.6.1 && <2- , rerebase >=1 && <2- , text-builder+ criterion >=1.5.6.1 && <2,+ rerebase >=1 && <2,+ text-builder,