word-wrap (empty) → 0.1
raw patch · 7 files changed
+206/−0 lines, 7 filesdep +basedep +hspecdep +textsetup-changed
Dependencies added: base, hspec, text, word-wrap
Files
- ChangeLog.md +5/−0
- LICENSE +30/−0
- README.md +4/−0
- Setup.hs +2/−0
- src/Text/Wrap.hs +99/−0
- tests/Main.hs +23/−0
- word-wrap.cabal +43/−0
+ ChangeLog.md view
@@ -0,0 +1,5 @@++0.1+===++* First version.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2017, Jonathan Daugherty++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Jonathan Daugherty nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,4 @@+word-wrap+=========++This library provides text-wrapping functionality.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/Text/Wrap.hs view
@@ -0,0 +1,99 @@+module Text.Wrap+ ( wrapTextToLines+ , wrapText+ )+where++import Data.Char (isSpace)+import qualified Data.Text as T++-- | Wrap text at the specified width. Newlines and whitespace in the+-- input text are preserved. Returns the lines of text in wrapped form.+-- New lines introduced due to wrapping will have leading whitespace+-- stripped.+wrapTextToLines :: Int -> T.Text -> [T.Text]+wrapTextToLines amt s = concat $ fmap (wrapLine amt) $ T.lines s++-- | Like 'wrapTextToLines', but returns the wrapped text reconstructed+-- with newlines inserted at wrap points.+wrapText :: Int -> T.Text -> T.Text+wrapText amt s = T.intercalate (T.pack "\n") $ wrapTextToLines amt s++data Token = WS T.Text | NonWS T.Text+ deriving (Show)++tokenLength :: Token -> Int+tokenLength (WS t) = T.length t+tokenLength (NonWS t) = T.length t++tokenContent :: Token -> T.Text+tokenContent (WS t) = t+tokenContent (NonWS t) = t++-- | Tokenize text into whitespace and non-whitespace chunks.+tokenize :: T.Text -> [Token]+tokenize t | T.null t = []+tokenize t =+ let leadingWs = T.takeWhile isSpace t+ leadingNonWs = T.takeWhile (not . isSpace) t+ tok = if T.null leadingWs+ then NonWS leadingNonWs+ else WS leadingWs+ in tok : tokenize (T.drop (tokenLength tok) t)++-- | Wrap a single line of text into a list of lines that all satisfy+-- the wrapping width.+wrapLine :: Int+ -- ^ The wrapping width.+ -> T.Text+ -- ^ A single line of text.+ -> [T.Text]+wrapLine limit t =+ let go [] = []+ go [WS _] = []+ go [tok] = [tokenContent tok]+ go ts =+ let (firstLine, maybeRest) = breakTokens limit ts+ firstLineText = T.stripEnd $ T.concat $ fmap tokenContent firstLine+ in case maybeRest of+ Nothing -> [firstLineText]+ Just rest -> firstLineText : go rest+ in go (tokenize t)++-- | Break a token sequence so that all tokens up to but not exceeding+-- a length limit are included on the left, and if any remain on the+-- right, return Just those too (or Nothing if there weren't any). If+-- this breaks a sequence at at point where the next token after the+-- break point is whitespace, that whitespace token is removed.+breakTokens :: Int -> [Token] -> ([Token], Maybe [Token])+breakTokens _ [] = ([], Nothing)+breakTokens _ [t] = ([t], Nothing)+breakTokens limit ts =+ -- Take enough tokens until we reach the point where taking more+ -- would exceed the line length.+ let go _ [] = []+ -- If the line starts with a token that itself exceeds the+ -- limit, just take that single token as the only one on this+ -- line.+ go acc (tok:_) | acc == 0 && tokenLength tok > limit = [tok]+ -- Otherwise take a token if its length plus the accumulator+ -- doesn't exceed the limit.+ go acc (tok:toks) =+ if tokenLength tok + acc <= limit+ then tok : go (acc + tokenLength tok) toks+ else []++ -- Allowed tokens are the ones we keep on this line.+ allowed = go 0 ts+ -- The rest go on the next line, to be wrapped again.+ rest = maybeTrim $ drop (length allowed) ts++ -- Trim leading whitespace on wrapped lines.+ maybeTrim [] = []+ maybeTrim (WS _:toks) = toks+ maybeTrim toks = toks++ result = if null rest+ then (allowed, Nothing)+ else (allowed, Just rest)+ in result
+ tests/Main.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE OverloadedStrings #-}+module Main where++import Test.Hspec++import Text.Wrap++main :: IO ()+main = hspec $ do+ it "leaves short lines untouched" $ do+ wrapTextToLines 5 "foo" `shouldBe` ["foo"]++ it "wraps long lines" $ do+ wrapTextToLines 7 "Hello, World!" `shouldBe` ["Hello,", "World!"]++ it "preserves leading whitespace" $ do+ wrapTextToLines 10 " Hello, World!" `shouldBe` [" Hello,", "World!"]++ it "honors preexisting newlines" $ do+ wrapTextToLines 100 "Hello,\nWorld!" `shouldBe` ["Hello,", "World!"]++ it "wraps long lines without truncation" $ do+ wrapTextToLines 2 "Hello, World!" `shouldBe` ["Hello,", "World!"]
+ word-wrap.cabal view
@@ -0,0 +1,43 @@+name: word-wrap+version: 0.1+synopsis: A library for word-wrapping+description: A library for wrapping long lines of text.+license: BSD3+license-file: LICENSE+author: Jonathan Daugherty+maintainer: cygnus@foobox.com+copyright: 2017 Jonathan Daugherty+category: Text+build-type: Simple+extra-source-files: ChangeLog.md+cabal-version: >=1.10+Homepage: https://github.com/jtdaugherty/word-wrap/+Bug-reports: https://github.com/jtdaugherty/word-wrap/issues++extra-doc-files:+ README.md+ CHANGELOG.md++Source-Repository head+ type: git+ location: git://github.com/jtdaugherty/word-wrap.git++library+ exposed-modules:+ Text.Wrap++ hs-source-dirs: src+ default-language: Haskell2010+ ghc-options: -Wall+ build-depends: base < 5,+ text++test-suite word-wrap-tests+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ hs-source-dirs: tests+ main-is: Main.hs+ ghc-options: -Wall+ build-depends: base < 5,+ word-wrap,+ hspec >= 2.4