From b783faef1a3fc76682b586352043a148f8f6091f Mon Sep 17 00:00:00 2001 From: Alexander Kobjolke Date: Fri, 25 Aug 2023 23:13:00 +0200 Subject: [PATCH] Implement wordWrap and paginate --- hcat.cabal | 1 + package.yaml | 2 +- src/HCat/Internal.hs | 39 +++++++++++++++++++++++++++++++++++++++ test/spec/HCatSpec.hs | 25 +++++++++++++++++++++---- 4 files changed, 62 insertions(+), 5 deletions(-) diff --git a/hcat.cabal b/hcat.cabal index 3857735..0923336 100644 --- a/hcat.cabal +++ b/hcat.cabal @@ -70,4 +70,5 @@ test-suite spec , base >=4.13 && <5 , hcat , hspec + , quickcheck-instances default-language: GHC2021 diff --git a/package.yaml b/package.yaml index e364b22..40a373f 100644 --- a/package.yaml +++ b/package.yaml @@ -52,7 +52,7 @@ tests: - hcat - hspec - QuickCheck - # - quickcheck-instances + - quickcheck-instances # - quickcheck-text build-tools: hspec-discover verbatim: diff --git a/src/HCat/Internal.hs b/src/HCat/Internal.hs index 69f0493..d28a0ff 100644 --- a/src/HCat/Internal.hs +++ b/src/HCat/Internal.hs @@ -1,6 +1,9 @@ -- | Internal module in order to facilitate testability. module HCat.Internal where +import Data.Text (Text) +import Data.Text qualified as T + -- | @parseArgs@ takes a list of strings and returns a single FilePath if there was exactly one element. -- -- >>> parseArgs ["foo"] @@ -28,3 +31,39 @@ chunksOf n xs@(_ : _) | otherwise = let (chunk, rest) = splitAt n xs in chunk : chunksOf n rest + +-- | @wordWrap@ splits the given Text if it is longer than the given margin. +-- +-- >>> :set -XOverloadedStrings +-- >>> wordWrap 3 "abcdef" +-- ["abc","def"] +-- +-- >>> wordWrap 3 "abc" +-- ["abc"] +-- +-- >>> wordWrap 3 "ab" +-- ["ab"] +wordWrap :: Int -> Text -> [Text] +wordWrap n text + | n > 0 && T.length text > n = + let (line, rest) = T.splitAt n text + in line : wordWrap n rest + | otherwise = pure text + +data ScreenDimensions = ScreenDimensions + { screenRows :: Int + , screenColumns :: Int + } + deriving (Show) + +-- | @paginate@ transforms a given piece of Text into pages of lines that fit +-- onto the screen. +-- +-- >>> paginate ScreenDimensions{screenRows = 2, screenColumns = 2} "foo bar baz" +-- ["fo\no \n","ba\nr \n","ba\nz\n"] +paginate :: ScreenDimensions -> Text -> Pages +paginate (ScreenDimensions rows cols) = + fmap T.unlines . chunksOf rows . concatMap (wordWrap cols) . T.lines + +type Pages = [Page] +type Page = Text diff --git a/test/spec/HCatSpec.hs b/test/spec/HCatSpec.hs index 0054f16..cd8255c 100644 --- a/test/spec/HCatSpec.hs +++ b/test/spec/HCatSpec.hs @@ -1,12 +1,12 @@ --- {-# LANGUAGE GHC2021 #-} - module HCatSpec (spec) where -import HCat.Internal (chunksOf, parseArgs) +import HCat.Internal import Test.Hspec import Test.Hspec.QuickCheck (prop) -import Test.QuickCheck (Positive (getPositive)) + +import Test.QuickCheck (Positive (getPositive), getNonPositive) +import Test.QuickCheck.Instances.Text () spec :: Spec spec = do @@ -15,6 +15,7 @@ spec = do parseArgs ["foo"] `shouldBe` Right "foo" parseArgs [] `shouldBe` Left "No filename given!" parseArgs ["foo", "bar"] `shouldBe` Left "Only a single file is supported" + describe "chunksOf" $ do prop "each chunk contains at most N items" $ \n xs -> let chunkLengths = length <$> chunksOf n (xs :: [Int]) @@ -25,3 +26,19 @@ spec = do prop "the sum of all lengths is equal to the length of the input" $ \n xs -> let chunkLengths = length <$> chunksOf (getPositive n) (xs :: [Int]) in sum chunkLengths `shouldBe` length xs + + describe "wordWrap" do + prop "non positive margins result in empty result" $ \margin -> + wordWrap (getNonPositive margin) "abc" `shouldBe` ["abc"] + + it "wraps the given text at the given margin" do + wordWrap 4 "abc" `shouldBe` ["abc"] + wordWrap 4 "abcdefg" `shouldBe` ["abcd", "efg"] + + prop "concatenating the result equals the input" $ \txt -> + let wrapped = wordWrap 4 txt + in mconcat wrapped `shouldBe` txt + + describe "paginate" do + it "wraps the given text at the given margin" do + paginate (ScreenDimensions 3 2) "abcdefghijkl" `shouldBe` ["ab\ncd\nef\n", "gh\nij\nkl\n"]