module Main where
import Prelude hiding (splitAt,lines,words)
import Data.Char
import Data.String hiding (lines,words)
import Data.Monoid
import Data.Bifunctor
import qualified Data.List (intersperse,splitAt)
import qualified Data.List.Split as Split
import qualified Data.Monoid.Factorial as SFM
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import qualified Control.Foldl as L
import Control.Foldl.Transduce
import Control.Foldl.Transduce.Text
import Control.Foldl.Transduce.Textual
{- $quickcheck
Notes for quickchecking on the REPL:
cabal repl tests
:t sample
sample :: Show a => Gen a -> IO ()
sample (arbitrary :: Gen WordA)
-}
main :: IO ()
main = defaultMain tests
testCaseEq :: (Eq a, Show a) => TestName -> a -> a -> TestTree
testCaseEq name a1 a2 = testCase name (assertEqual "" a1 a2)
blank :: T.Text -> Bool
blank = T.all isSpace
nl :: T.Text
nl = T.pack "\n"
sp :: T.Text
sp = T.pack " "
c :: T.Text
c = T.pack "c"
{- $words
-}
newtype WordA = WordA { getWord :: T.Text } deriving (Show)
instance Arbitrary WordA where
arbitrary = do
firstChar <- oneof [pure ' ', pure '\n', arbitrary]
lastChar <- oneof [pure ' ', pure '\n', arbitrary]
middle <- listOf (frequency [(1,pure ' '),(4,arbitrary)])
return (WordA (T.pack (firstChar : (middle ++ [lastChar]))))
{- $paragraphs
-}
newtype TextChunksA = TextChunksA { getChunks :: [T.Text] } deriving (Show)
instance Arbitrary TextChunksA where
arbitrary = flip suchThat (not . blank . mconcat . getChunks) (do
TextChunksA <$> partz)
where
chunkz = frequency [
(20::Int, flip T.replicate sp <$> choose (1,40))
, (20, flip T.replicate sp <$> choose (1,3))
, (50, pure nl)
, (20, flip T.replicate c <$> choose (1,30))
, (20, flip T.replicate c <$> choose (1,3))
]
combined = mconcat <$> vectorOf 40 chunkz
partitions = infiniteListOf (choose (1::Int,7))
partz = partition [] <$> combined <*> partitions
partition :: [T.Text] -> T.Text -> [Int] -> [T.Text]
partition accum text (x:xs) =
if x >= T.length text
then reverse (text:accum)
else
let (point,rest) = T.splitAt x text
in
partition (point:accum) rest xs
partition _ _ [] = error "never happens"
shrink (TextChunksA texts) =
let removeIndex i xs =
let (xs',xs'') = Data.List.splitAt i xs
in xs' ++ tail xs''
l = length texts
in
if l == 1
then []
else map (\i -> TextChunksA (removeIndex i texts)) [0..l-1]
paragraphsBaseline
:: T.Text -> [T.Text]
paragraphsBaseline =
map (T.unlines . map T.stripStart . T.lines)
. map mconcat
. map (`mappend` [nl])
. map (Data.List.intersperse nl)
. filter (not . null)
. Split.splitWhen blank
. T.lines
ignoreLastNewline :: [T.Text] -> [T.Text]
ignoreLastNewline ts =
let
lastt = last ts
lastt' = if T.last lastt == '\n' then T.init lastt else lastt
in init ts ++ [lastt']
-- (paragraphs,chunks)
splittedParagraphs :: T.Text -> [Int] -> [([T.Text],[T.Text])]
splittedParagraphs txt splitsizes =
let
splitted = paragraphsBaseline txt
in
zip (repeat splitted) (map (flip T.chunksOf txt) splitsizes)
paragraphsUnderTest
:: [T.Text] -> [T.Text]
paragraphsUnderTest txt =
map mconcat (L.fold (folds paragraphs L.list L.list) txt)
paragraph01 :: T.Text
paragraph01 =
T.pack
" \n \n\n \n \n \
\a aa aaa \nb bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb bb \n \
\ ccccccccccccccccccccccc cccccccc \n\n \n \n\n ccc\
\ \n \n \nd\n\n\ne \
\\n"
paragraph02 :: T.Text
paragraph02 = T.pack " cc "
tests :: TestTree
tests =
testGroup "Tests"
[
testGroup "surround"
[
testCaseEq "surroundempty"
"prefixsuffix"
(L.fold (transduce (surround "prefix" "suffix") L.list) "")
],
testGroup "chunksOf"
[
testCaseEq "emptyList3"
([[]]::[[Int]])
(L.fold (folds (chunksOf 3) L.list L.list) [])
,
testCaseEq "size1"
([[1],[2],[3],[4],[5],[6],[7]]::[[Int]])
(L.fold (folds (chunksOf 1) L.list L.list) [1..7])
,
testCaseEq "size3"
([[1,2,3],[4,5,6],[7]]::[[Int]])
(L.fold (folds (chunksOf 3) L.list L.list) [1..7])
],
testGroup "textualBreak"
[
testCaseEq "beginwithdot"
".bb"
(L.fold (bisect (textualBreak (=='.')) ignore (reify id) L.mconcat) ["aa",".bb"])
,
testCaseEq "endwithdot"
"."
(L.fold (bisect (textualBreak (=='.')) ignore (reify id) L.mconcat) ["aa","bb."])
],
testGroup "newline"
[
testCaseEq "newlineempty"
(T.pack "\n")
(mconcat (L.fold (transduce newline L.list) (map T.pack [])))
,
testCaseEq "newlinenull"
(T.pack "\n")
(mconcat (L.fold (transduce newline L.list) (map T.pack [""])))
],
testGroup "words"
[
testGroup "quickcheck"
[
testProperty "quickcheck1" (\chunks -> -- list of words
let tchunks = fmap getWord chunks
in
(case TL.words (TL.fromChunks tchunks) of
[] -> [mempty]
x -> x) ==
(fmap TL.fromChunks (L.fold (folds words L.list L.list) tchunks)))
]
],
testGroup "paragraphs"
[
testCase "paragraphs01"
(mapM_
(\(x,y) -> assertEqual "" (ignoreLastNewline x) (ignoreLastNewline (paragraphsUnderTest y)))
(splittedParagraphs paragraph01 [1..7])),
testCaseEq "newlineAtEnd"
(map T.pack ["aa\n"])
(paragraphsUnderTest (map T.pack ["a","a","\n"])),
testCaseEq "noNewlineAtEnd"
(map T.pack ["aa"])
(paragraphsUnderTest (map T.pack ["a","a"])),
testGroup "quickcheck"
[
testProperty "quickcheck1" (\(TextChunksA chunks) ->
ignoreLastNewline (paragraphsUnderTest chunks)
==
ignoreLastNewline (paragraphsBaseline (mconcat chunks)))
]
],
testGroup "quiesceWith"
[
testCase "collectAfterFailure" (do
let
foldthatfails =
transduceM utf8E (L.generalize L.list)
inputs =
map fromString ["invalid \xc3\x28 sequence","xxx","zzz","___"]
fallbackfold =
bisectM (chunkedSplitAt 4) (reifyM id) ignore (L.generalize L.list)
r <- L.foldM (quiesceWith fallbackfold foldthatfails) inputs
assertEqual
mempty
(Left ('C',4))
(first (bimap (head . show) (SFM.length . mconcat)) r))
]
]