text-builder-dev-0.4/0000755000000000000000000000000007346545000012732 5ustar0000000000000000text-builder-dev-0.4/LICENSE0000644000000000000000000000204207346545000013735 0ustar0000000000000000Copyright (c) 2022, 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-dev-0.4/library/0000755000000000000000000000000007346545000014376 5ustar0000000000000000text-builder-dev-0.4/library/TextBuilderDev.hs0000644000000000000000000000234007346545000017623 0ustar0000000000000000module TextBuilderDev ( TextBuilder, -- * Accessors toText, toString, isEmpty, -- * Constructors -- ** Transformations force, intercalate, intercalateMap, padFromLeft, padFromRight, -- ** Textual text, lazyText, string, unsafeUtf8ByteString, -- ** Character char, unicodeCodepoint, -- ** Data byteStringHexEncoding, -- ** Integers -- *** Decimal decimal, fixedLengthDecimal, thousandSeparatedDecimal, -- *** Binary binary, prefixedBinary, -- *** Octal octal, prefixedOctal, -- *** Hexadecimal hexadecimal, prefixedHexadecimal, -- ** Real doubleFixedPoint, doubleFixedPointPercent, -- ** Time utcTimeIso8601Timestamp, realFracDdHhMmSsInterval, diffTimeSeconds, picoseconds, -- ** Other approximateDataSize, -- * Classes Isomorphic (..), ) where import TextBuilder import TextBuilderDev.Domains.Digits import TextBuilderDev.Domains.Other import TextBuilderDev.Domains.Padding import TextBuilderDev.Domains.StrictBuilder () import TextBuilderDev.Domains.StrictTextBuilder () import TextBuilderDev.Domains.Time import TextBuilderDev.Isomorphic text-builder-dev-0.4/library/TextBuilderDev/Domains/0000755000000000000000000000000007346545000020662 5ustar0000000000000000text-builder-dev-0.4/library/TextBuilderDev/Domains/Digits.hs0000644000000000000000000000215207346545000022441 0ustar0000000000000000module TextBuilderDev.Domains.Digits where import qualified Data.ByteString as ByteString import qualified Data.List.Split as Split import TextBuilder import TextBuilderDev.Prelude hiding (intercalate) -- | 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 signed #-} signed :: (Ord a, Num a) => (a -> TextBuilder) -> a -> TextBuilder signed onUnsigned i = if i >= 0 then onUnsigned i else unicodeCodepoint 45 <> onUnsigned (negate i) -- | Hexadecimal readable representation of binary data. -- -- >>> byteStringHexEncoding "Hello" -- "4865 6c6c 6f" {-# INLINE byteStringHexEncoding #-} byteStringHexEncoding :: ByteString -> TextBuilder byteStringHexEncoding = intercalate " " . fmap mconcat . Split.chunksOf 2 . fmap hexadecimal . ByteString.unpack text-builder-dev-0.4/library/TextBuilderDev/Domains/Other.hs0000644000000000000000000000573607346545000022312 0ustar0000000000000000module TextBuilderDev.Domains.Other where import TextBuilder import TextBuilderDev.Domains.Digits import TextBuilderDev.Prelude hiding (intercalate) -- | Data size in decimal notation over amount of bytes. -- -- >>> approximateDataSize 999 -- "999B" -- -- >>> approximateDataSize 9999 -- "9.9kB" -- -- >>> approximateDataSize (-9999) -- "-9.9kB" -- -- >>> approximateDataSize 1234567890 -- "1.2GB" -- -- >>> approximateDataSize 10000000000000000000000000000000023 -- "10,000,000,000YB" {-# INLINEABLE approximateDataSize #-} approximateDataSize :: (Integral a) => a -> TextBuilder approximateDataSize = signed \a -> if a < 1000 then decimal a <> "B" else if a < 1000000 then dividedDecimal 100 a <> "kB" else if a < 1000000000 then dividedDecimal 100000 a <> "MB" else if a < 1000000000000 then dividedDecimal 100000000 a <> "GB" else if a < 1000000000000000 then dividedDecimal 100000000000 a <> "TB" else if a < 1000000000000000000 then dividedDecimal 100000000000000 a <> "PB" else if a < 1000000000000000000000 then dividedDecimal 100000000000000000 a <> "EB" else if a < 1000000000000000000000000 then dividedDecimal 100000000000000000000 a <> "ZB" else dividedDecimal 100000000000000000000000 a <> "YB" where dividedDecimal divisor n = let byDivisor = div n divisor byExtraTen = div byDivisor 10 remainder = byDivisor - byExtraTen * 10 separatorChar = ',' in if remainder == 0 || byExtraTen >= 10 then thousandSeparatedDecimal separatorChar byExtraTen else thousandSeparatedDecimal separatorChar byExtraTen <> "." <> decimalDigit remainder -- | Double with a fixed number of decimal places. -- -- >>> doubleFixedPoint 4 0.123456 -- "0.1235" -- -- >>> doubleFixedPoint 2 2.1 -- "2.10" -- -- >>> doubleFixedPoint (-2) 2.1 -- "2" -- -- >>> doubleFixedPoint 2 (-2.1) -- "-2.10" -- -- >>> doubleFixedPoint 2 0 -- "0.00" {-# INLINE doubleFixedPoint #-} doubleFixedPoint :: -- | Amount of decimals after point. Int -> Double -> TextBuilder doubleFixedPoint (max 0 -> decimalPlaces) = fromString . printf ("%." ++ show decimalPlaces ++ "f") -- | Double multiplied by 100 with a fixed number of decimal places applied and followed by a percent-sign. -- -- >>> doubleFixedPointPercent 3 0.123456 -- "12.346%" -- -- >>> doubleFixedPointPercent 0 2 -- "200%" -- -- >>> doubleFixedPointPercent 0 (-2) -- "-200%" {-# INLINE doubleFixedPointPercent #-} doubleFixedPointPercent :: -- | Amount of decimals after point. Int -> Double -> TextBuilder doubleFixedPointPercent decimalPlaces x = doubleFixedPoint decimalPlaces (x * 100) <> "%" text-builder-dev-0.4/library/TextBuilderDev/Domains/Padding.hs0000644000000000000000000000367007346545000022572 0ustar0000000000000000module TextBuilderDev.Domains.Padding where import qualified Data.Text as Text import TextBuilder import TextBuilderDev.Prelude -- | Pad a builder from the left side to the specified length with the specified character. -- -- >>> padFromLeft 5 '0' "123" -- "00123" -- -- >>> padFromLeft 5 '0' "123456" -- "123456" {-# INLINEABLE padFromLeft #-} padFromLeft :: Int -> Char -> TextBuilder -> TextBuilder padFromLeft paddedLength paddingChar = textPaddedFromLeft paddedLength paddingChar . toText -- | Pad a builder from the right side to the specified length with the specified character. -- -- >>> padFromRight 5 ' ' "123" -- "123 " -- -- >>> padFromRight 5 ' ' "123456" -- "123456" {-# INLINEABLE padFromRight #-} padFromRight :: Int -> Char -> TextBuilder -> TextBuilder padFromRight paddedLength paddingChar = textPaddedFromRight paddedLength paddingChar . toText -- | Pad a text from the left side to the specified length with the specified character. -- -- >>> textPaddedFromLeft 5 '0' "123" -- "00123" -- -- >>> textPaddedFromLeft 5 '0' "123456" -- "123456" {-# INLINEABLE textPaddedFromLeft #-} textPaddedFromLeft :: Int -> Char -> Text -> TextBuilder textPaddedFromLeft paddedLength paddingChar input = let actualLength = Text.length input in if paddedLength <= actualLength then text input else foldMap char (replicate (paddedLength - actualLength) paddingChar) <> text input -- | Pad a text from the right side to the specified length with the specified character. -- -- >>> textPaddedFromRight 5 '0' "123" -- "12300" -- -- >>> textPaddedFromRight 5 '0' "123456" -- "123456" {-# INLINEABLE textPaddedFromRight #-} textPaddedFromRight :: Int -> Char -> Text -> TextBuilder textPaddedFromRight paddedLength paddingChar inputText = let actualLength = Text.length inputText in if paddedLength <= actualLength then text inputText else text inputText <> foldMap char (replicate (paddedLength - actualLength) paddingChar) text-builder-dev-0.4/library/TextBuilderDev/Domains/StrictBuilder.hs0000644000000000000000000000124707346545000024001 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# OPTIONS_GHC -Wno-orphans #-} module TextBuilderDev.Domains.StrictBuilder where #if MIN_VERSION_text(2,0,2) && !MIN_VERSION_text(2,1,2) import Data.Text.Internal.StrictBuilder import qualified TextBuilderCore as Core import TextBuilderDev.Isomorphic import TextBuilderDev.Prelude instance Isomorphic StrictBuilder where {-# INLINE from #-} from (StrictBuilder size write) = Core.TextBuilder size ( \array offset -> write array offset $> offset + size ) {-# INLINE to #-} to (Core.TextBuilder size write) = StrictBuilder size ( \array offset -> write array offset $> () ) #endif text-builder-dev-0.4/library/TextBuilderDev/Domains/StrictTextBuilder.hs0000644000000000000000000000123307346545000024641 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# OPTIONS_GHC -Wno-orphans #-} module TextBuilderDev.Domains.StrictTextBuilder where #if MIN_VERSION_text(2,1,2) import Data.Text.Internal.StrictBuilder import qualified TextBuilderCore as Core import TextBuilderDev.Isomorphic import TextBuilderDev.Prelude instance Isomorphic StrictTextBuilder where {-# INLINE from #-} from (StrictTextBuilder size write) = Core.TextBuilder size ( \array offset -> write array offset $> offset + size ) {-# INLINE to #-} to (Core.TextBuilder size write) = StrictTextBuilder size ( \array offset -> write array offset $> () ) #endif text-builder-dev-0.4/library/TextBuilderDev/Domains/Time.hs0000644000000000000000000000607707346545000022126 0ustar0000000000000000module TextBuilderDev.Domains.Time where import TextBuilder import TextBuilderDev.Domains.Padding import TextBuilderDev.Prelude -- | UTC time in ISO8601 format. -- -- >>> utcTimeIso8601Timestamp (read "2021-11-24 12:11:02 UTC") -- "2021-11-24T12:11:02Z" utcTimeIso8601Timestamp :: UTCTime -> TextBuilder utcTimeIso8601Timestamp UTCTime {..} = let (year, month, day) = toGregorian utctDay daySeconds = round utctDayTime (dayMinutes, second) = divMod daySeconds 60 (hour, minute) = divMod dayMinutes 60 in utcTimestampInIso8601 (fromIntegral year) month day hour minute second -- | -- General template for formatting date values according to the ISO8601 standard. -- The format is the following: -- -- Integrations with various time-libraries can be easily derived from that. -- -- >>> utcTimestampInIso8601 2021 11 24 12 11 02 -- "2021-11-24T12:11:02Z" utcTimestampInIso8601 :: -- | Year. Int -> -- | Month. Int -> -- | Day. Int -> -- | Hour. Int -> -- | Minute. Int -> -- | Second. Int -> TextBuilder utcTimestampInIso8601 y mo d h mi s = mconcat [ fixedLengthDecimal 4 y, "-", fixedLengthDecimal 2 mo, "-", fixedLengthDecimal 2 d, "T", fixedLengthDecimal 2 h, ":", fixedLengthDecimal 2 mi, ":", fixedLengthDecimal 2 s, "Z" ] -- | -- Time interval in seconds. -- Directly applicable to 'DiffTime' and 'NominalDiffTime'. -- -- The format is the following: -- -- > DD:HH:MM:SS -- -- >>> realFracDdHhMmSsInterval @Double 59 -- "00:00:00:59" -- -- >>> realFracDdHhMmSsInterval @Double 90 -- "00:00:01:30" -- -- >>> realFracDdHhMmSsInterval @Double 86401 -- "01:00:00:01" -- -- >>> realFracDdHhMmSsInterval @Double (356 * 86400) -- "356:00:00:00" {-# INLINEABLE realFracDdHhMmSsInterval #-} realFracDdHhMmSsInterval :: (RealFrac seconds) => seconds -> TextBuilder realFracDdHhMmSsInterval interval = flip evalState (round interval :: Int) $ do seconds <- state (swap . flip divMod 60) minutes <- state (swap . flip divMod 60) hours <- state (swap . flip divMod 24) days <- get return ( mconcat [ padFromLeft 2 '0' (decimal days), ":", padFromLeft 2 '0' (decimal hours), ":", padFromLeft 2 '0' (decimal minutes), ":", padFromLeft 2 '0' (decimal seconds) ] ) -- | DiffTime in a compact decimal format based on 'picoseconds'. diffTimeSeconds :: DiffTime -> TextBuilder diffTimeSeconds = picoseconds . diffTimeToPicoseconds -- | Amount of picoseconds represented in a compact decimal format using suffixes. -- -- E.g., the following is @1_230_000_000@ picoseconds or 1.23 milliseconds or 1230 microseconds: -- -- > 1230us picoseconds :: Integer -> TextBuilder picoseconds x = attemptOr 1_000_000_000_000 "s" $ attemptOr 1_000_000_000 "ms" $ attemptOr 1_000_000 "us" $ attemptOr 1_000 "ns" $ decimal x <> "ps" where attemptOr factor suffix alternative = if x == divided * factor then decimal divided <> suffix else alternative where divided = div x factor text-builder-dev-0.4/library/TextBuilderDev/0000755000000000000000000000000007346545000017270 5ustar0000000000000000text-builder-dev-0.4/library/TextBuilderDev/Isomorphic.hs0000644000000000000000000000251507346545000021743 0ustar0000000000000000module TextBuilderDev.Isomorphic where import qualified Data.Text.Lazy as TextLazy import qualified Data.Text.Lazy.Builder as TextLazyBuilder import TextBuilder import TextBuilderDev.Prelude -- | -- Evidence that there exists an unambiguous way to convert -- a type to and from "TextBuilder". -- -- The laws are: -- -- - @'from' . 'to' = 'id'@ -- -- - @'to' . 'from' = 'id'@ -- -- This class does not provide implicit rendering, -- such as from integer to its decimal representation. -- There are multiple ways of representing an integer -- as text (e.g., hexadecimal, binary). -- The non-ambiguity is further enforced by the presence of -- the inverse conversion. -- In the integer case there is no way to read it -- from a textual form without a possibility of failing -- (e.g., when the input string cannot be parsed as an integer). class Isomorphic a where -- | Project the type into "TextBuilder". from :: a -> TextBuilder -- | Embed "TextBuilder" into the type. to :: TextBuilder -> a instance Isomorphic TextBuilder where from = id to = id instance Isomorphic Text where from = text to = toText instance Isomorphic TextLazy.Text where from = lazyText to = TextLazy.fromStrict . toText instance Isomorphic TextLazyBuilder.Builder where from = lazyText . TextLazyBuilder.toLazyText to = TextLazyBuilder.fromText . toText text-builder-dev-0.4/library/TextBuilderDev/Prelude.hs0000644000000000000000000000626507346545000021235 0ustar0000000000000000module TextBuilderDev.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-dev-0.4/test/0000755000000000000000000000000007346545000013711 5ustar0000000000000000text-builder-dev-0.4/test/Features.hs0000644000000000000000000000263107346545000016025 0ustar0000000000000000module Features (tests) where import Data.Int import Data.String import Data.Time import Data.Time.Format.ISO8601 (iso8601Show) import Data.Word import qualified Features.StrictBuilder as StrictBuilder import qualified Features.StrictTextBuilder as StrictTextBuilder import Numeric.Natural (Natural) import Test.QuickCheck.Instances () import Test.Tasty import Test.Tasty.QuickCheck import TextBuilderDev import Util.TestTrees import Prelude tests :: [TestTree] tests = [ testGroup "StrictBuilder" StrictBuilder.tests, testGroup "StrictTextBuilder" StrictTextBuilder.tests, testGroup "doubleFixedPoint" $ [ mapsToMonoid (doubleFixedPoint 3) ], testGroup "doubleFixedPointPercent" $ [ mapsToMonoid (doubleFixedPointPercent 3) ], testGroup "utcTimeIso8601Timestamp" $ [ mapsToMonoid utcTimeIso8601Timestamp, testProperty "Same as iso8601Show" $ \x -> let roundedToSecondsTime = x {utctDayTime = (fromIntegral @Int . round . utctDayTime) x} in (fromString . flip mappend "Z" . take 19 . iso8601Show) roundedToSecondsTime === toText (utcTimeIso8601Timestamp roundedToSecondsTime) ], testGroup "approximateDataSize" $ [ testGroup "Word" $ [ mapsToMonoid @Word approximateDataSize ], testGroup "Natural" $ [ mapsToMonoid @Natural approximateDataSize ] ] ] text-builder-dev-0.4/test/Features/0000755000000000000000000000000007346545000015467 5ustar0000000000000000text-builder-dev-0.4/test/Features/StrictBuilder.hs0000644000000000000000000000200207346545000020574 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# OPTIONS_GHC -Wno-orphans #-} module Features.StrictBuilder (tests) where #if MIN_VERSION_text(2,0,2) && !MIN_VERSION_text(2,1,2) import Data.Function import Data.Proxy import qualified Data.Text.Encoding as TextEncoding import Test.QuickCheck.Instances () import Test.Tasty import Test.Tasty.QuickCheck import TextBuilderDev import Util.TestTrees import Prelude tests :: [TestTree] tests = [ isomorphic $ Proxy @TextEncoding.StrictBuilder, testGroup "to" $ [ mapsToMonoid (to @TextEncoding.StrictBuilder), mapsToMonoid (from @TextEncoding.StrictBuilder) ] ] instance Eq TextEncoding.StrictBuilder where a == b = on (==) TextEncoding.strictBuilderToText a b instance Show TextEncoding.StrictBuilder where showsPrec d = showsPrec d . TextEncoding.strictBuilderToText instance Arbitrary TextEncoding.StrictBuilder where arbitrary = TextEncoding.textToStrictBuilder <$> arbitrary #else import Test.Tasty tests :: [TestTree] tests = [] #endif text-builder-dev-0.4/test/Features/StrictTextBuilder.hs0000644000000000000000000000200207346545000021441 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# OPTIONS_GHC -Wno-orphans #-} module Features.StrictTextBuilder (tests) where #if MIN_VERSION_text(2,1,2) import Data.Function import Data.Proxy import qualified Data.Text.Encoding as TextEncoding import Test.QuickCheck.Instances () import Test.Tasty import Test.Tasty.QuickCheck import TextBuilderDev import Util.TestTrees import Prelude tests :: [TestTree] tests = [ isomorphic $ Proxy @TextEncoding.StrictTextBuilder, testGroup "to" $ [ mapsToMonoid (to @TextEncoding.StrictTextBuilder), mapsToMonoid (from @TextEncoding.StrictTextBuilder) ] ] instance Eq TextEncoding.StrictTextBuilder where a == b = on (==) TextEncoding.strictBuilderToText a b instance Show TextEncoding.StrictTextBuilder where showsPrec d = showsPrec d . TextEncoding.strictBuilderToText instance Arbitrary TextEncoding.StrictTextBuilder where arbitrary = TextEncoding.textToStrictBuilder <$> arbitrary #else import Test.Tasty tests :: [TestTree] tests = [] #endif text-builder-dev-0.4/test/Main.hs0000644000000000000000000000202507346545000015130 0ustar0000000000000000module Main where import Data.Proxy import Data.Text (Text) import qualified Data.Text.Lazy as TextLazy import qualified Data.Text.Lazy.Builder as TextLazyBuilder import qualified Features import Test.QuickCheck.Classes import Test.QuickCheck.Instances () import Test.Tasty import qualified TextBuilderDev as B import Util.ExtraInstances () import Util.TestTrees import Prelude main :: IO () main = (defaultMain . testGroup "All") tests tests :: [TestTree] tests = [ testGroup "Features" Features.tests, testGroup "Isomorphic instances" $ [ testGroup "Text" $ [ isomorphic $ Proxy @Text ], testGroup "Lazy Text" $ [ isomorphic $ Proxy @TextLazy.Text ], testGroup "Lazy Text Builder" $ [ isomorphic $ Proxy @TextLazyBuilder.Builder ] ], followsLaws $ showLaws (Proxy @B.TextBuilder), followsLaws $ eqLaws (Proxy @B.TextBuilder), followsLaws $ semigroupLaws (Proxy @B.TextBuilder), followsLaws $ monoidLaws (Proxy @B.TextBuilder) ] text-builder-dev-0.4/test/Util/0000755000000000000000000000000007346545000014626 5ustar0000000000000000text-builder-dev-0.4/test/Util/ExtraInstances.hs0000644000000000000000000000047007346545000020116 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-dev-0.4/test/Util/TestTrees.hs0000644000000000000000000000543407346545000017112 0ustar0000000000000000module Util.TestTrees where import Data.List.NonEmpty (NonEmpty (..)) import Data.Monoid import Data.Proxy import Data.Semigroup import Test.QuickCheck import Test.QuickCheck.Classes import Test.QuickCheck.Instances () import Test.Tasty import Test.Tasty.QuickCheck import TextBuilderDev 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) ] isomorphic :: forall a. (Isomorphic a, Eq a, Show a, Arbitrary a) => Proxy a -> TestTree isomorphic proxy = testGroup "Isomorphic" $ [ testProperty "to . from == id" $ \a -> (to . from) a === asProxyTypeOf a proxy, testProperty "from . to == id" $ \a -> (from . flip asProxyTypeOf proxy . to) a === a, testGroup "from" $ [ mapsToMonoid (from @a) ] ] followsLaws :: Laws -> TestTree followsLaws Laws {..} = testProperties lawsTypeclass lawsProperties text-builder-dev-0.4/text-builder-dev.cabal0000644000000000000000000000561507346545000017111 0ustar0000000000000000cabal-version: 3.0 name: text-builder-dev version: 0.4 category: Text, Builders synopsis: Edge of developments for "text-builder" description: This is a development version of "[text-builder](https://hackage.haskell.org/package/text-builder)". All experimentation and feature development happens here. The API can change drastically. For a more stable API use "text-builder". The packages are compatible, because they both operate on the type defined in "[text-builder-core](https://hackage.haskell.org/package/text-builder-core)". homepage: https://github.com/nikita-volkov/text-builder-dev bug-reports: https://github.com/nikita-volkov/text-builder-dev/issues author: Nikita Volkov maintainer: Nikita Volkov copyright: (c) 2022, Nikita Volkov license: MIT license-file: LICENSE source-repository head type: git location: https://github.com/nikita-volkov/text-builder-dev 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: TextBuilderDev other-modules: TextBuilderDev.Domains.Digits TextBuilderDev.Domains.Other TextBuilderDev.Domains.Padding TextBuilderDev.Domains.StrictBuilder TextBuilderDev.Domains.StrictTextBuilder TextBuilderDev.Domains.Time TextBuilderDev.Isomorphic TextBuilderDev.Prelude build-depends: base >=4.11 && <5, bytestring >=0.10 && <0.13, split >=0.2.3.4 && <0.3, text >=1.2 && <3, text-builder ^>=1, 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: Features Features.StrictBuilder Features.StrictTextBuilder Util.ExtraInstances Util.TestTrees build-depends: QuickCheck >=2.14 && <3, base >=4.11 && <5, 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-dev, time >=1.12 && <2,