word-wrap 0.4.1 → 0.5
raw patch · 4 files changed
+146/−14 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Text.Wrap: FillAfterFirst :: FillScope
+ Text.Wrap: FillAll :: FillScope
+ Text.Wrap: FillIndent :: Int -> FillStrategy
+ Text.Wrap: FillPrefix :: Text -> FillStrategy
+ Text.Wrap: NoFill :: FillStrategy
+ Text.Wrap: [fillScope] :: WrapSettings -> FillScope
+ Text.Wrap: [fillStrategy] :: WrapSettings -> FillStrategy
+ Text.Wrap: data FillScope
+ Text.Wrap: data FillStrategy
+ Text.Wrap: instance GHC.Classes.Eq Text.Wrap.FillScope
+ Text.Wrap: instance GHC.Classes.Eq Text.Wrap.FillStrategy
+ Text.Wrap: instance GHC.Read.Read Text.Wrap.FillScope
+ Text.Wrap: instance GHC.Read.Read Text.Wrap.FillStrategy
+ Text.Wrap: instance GHC.Show.Show Text.Wrap.FillScope
+ Text.Wrap: instance GHC.Show.Show Text.Wrap.FillStrategy
- Text.Wrap: WrapSettings :: Bool -> Bool -> WrapSettings
+ Text.Wrap: WrapSettings :: Bool -> Bool -> FillStrategy -> FillScope -> WrapSettings
Files
- CHANGELOG.md +10/−0
- src/Text/Wrap.hs +71/−11
- tests/Main.hs +62/−0
- word-wrap.cabal +3/−3
CHANGELOG.md view
@@ -1,4 +1,14 @@ +0.5+===++API changes:+ * Line-wrapping now supports optional "filling", i.e., placing prefix+ strings at the start of wrapped lines. The behavior of filling is+ configured by new `WrapSettings` fields: `fillStrategy` (what to+ add to filled lines) and `fillScope` (which lines to affect with+ filling). Thanks to Brent Yorgey for this work!+ 0.4.1 =====
src/Text/Wrap.hs view
@@ -1,5 +1,7 @@ module Text.Wrap- ( WrapSettings(..)+ ( FillStrategy(..)+ , FillScope(..)+ , WrapSettings(..) , defaultWrapSettings , wrapTextToLines@@ -11,6 +13,27 @@ import Data.Char (isSpace) import qualified Data.Text as T +-- | How should wrapped lines be filled (i.e. what kind of prefix+-- should be attached?)+data FillStrategy+ = NoFill -- ^ Don't do any filling (default)+ | FillIndent Int -- ^ Indent by this many spaces+ | FillPrefix T.Text -- ^ Prepend this text+ deriving (Eq, Show, Read)++fillWidth :: FillStrategy -> Int+fillWidth NoFill = 0+fillWidth (FillIndent n) = n+fillWidth (FillPrefix t) = T.length t++-- | To which lines should the fill strategy be applied?+data FillScope+ = FillAfterFirst -- ^ Apply any fill prefix only to lines after+ -- the first line (default)+ | FillAll -- ^ Apply any fill prefix to all lines, even+ -- if there is only one line+ deriving (Eq, Show, Read)+ -- | Settings to control how wrapping is performed. data WrapSettings = WrapSettings { preserveIndentation :: Bool@@ -19,6 +42,12 @@ , breakLongWords :: Bool -- ^ Whether to break in the middle of the first word -- on a line when that word exceeds the wrapping width.+ , fillStrategy :: FillStrategy+ -- ^ What kind of prefix should be applied to lines+ -- after wrapping? (default: none)+ , fillScope :: FillScope+ -- ^ To which lines should the fill strategy be applied?+ -- (default: all but the first) } deriving (Eq, Show, Read) @@ -26,12 +55,35 @@ defaultWrapSettings = WrapSettings { preserveIndentation = False , breakLongWords = False+ , fillStrategy = NoFill+ , fillScope = FillAfterFirst } +-- | Apply a function to the portion of a list of lines indicated by+-- the 'FillScope'.+withScope :: FillScope -> (a -> a) -> [a] -> [a]+withScope FillAfterFirst = onTail+withScope FillAll = map++-- | Map a function over the tail of a list.+onTail :: (a -> a) -> [a] -> [a]+onTail _ [] = []+onTail f (a:as) = a : map f as++-- | Apply the fill specified in the 'WrapSettings' to a list of lines.+applyFill :: WrapSettings -> [T.Text] -> [T.Text]+applyFill settings =+ let scope = fillScope settings+ in case fillStrategy settings of+ NoFill -> id+ FillIndent n -> withScope scope (T.append (T.replicate n (T.pack " ")))+ FillPrefix t -> withScope scope (T.append 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.+-- input text are preserved. Returns the lines of text in wrapped+-- form. New lines introduced due to wrapping will have leading+-- whitespace stripped prior to having any fill applied. Preserved+-- indentation is always placed before any fill. wrapTextToLines :: WrapSettings -> Int -> T.Text -> [T.Text] wrapTextToLines settings amt s = concat $ fmap (wrapLine settings amt) $ T.lines s@@ -73,20 +125,28 @@ -- ^ A single line of text. -> [T.Text] wrapLine settings limit t =- let go _ [] = [T.empty]+ let restFillWidth = fillWidth (fillStrategy settings)+ firstLineFillWidth = if fillScope settings == FillAll then restFillWidth else 0++ firstLineLimit = limit - T.length indent - firstLineFillWidth+ restLimit = limit - T.length indent - restFillWidth++ go _ [] = [T.empty] go _ [WS _] = [T.empty]- go lim ts =- let (firstLine, maybeRest) = breakTokens settings lim ts+ go isFirstLine ts =+ let lim = if isFirstLine then firstLineLimit else restLimit+ (firstLine, maybeRest) = breakTokens settings lim ts firstLineText = T.stripEnd $ T.concat $ fmap tokenContent firstLine in case maybeRest of- Nothing -> [firstLineText]- Just rest -> firstLineText : go lim rest+ Nothing -> [firstLineText]+ Just rest -> firstLineText : go False rest (indent, modifiedText) = if preserveIndentation settings then let i = T.takeWhile isSpace t in (T.take (limit - 1) i, T.drop (T.length i) t) else (T.empty, t)- result = go (limit - T.length indent) (tokenize modifiedText)- in (indent <>) <$> result++ result = go True (tokenize modifiedText)+ in map (indent <>) . applyFill settings $ result -- | 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
tests/Main.hs view
@@ -64,3 +64,65 @@ } wrapTextToLines s 4 " foo bar" `shouldBe` [" f", " o", " o", " b", " a", " r"]++ it "indents all but the first line" $ do+ let s = defaultWrapSettings { fillStrategy = FillIndent 2 }+ wrapTextToLines s 8 "Hello there, World!"+ `shouldBe` ["Hello", " there,", " World!"]++ it "indents all lines" $ do+ let s = defaultWrapSettings { fillStrategy = FillIndent 2+ , fillScope = FillAll+ }+ wrapTextToLines s 8 "Hello there, World!"+ `shouldBe` [" Hello", " there,", " World!"]++ it "fills all lines but the first with a prefix" $ do+ let s = defaultWrapSettings { fillStrategy = FillPrefix "- " }+ wrapTextToLines s 8 "Hello there, World!"+ `shouldBe` ["Hello", "- there,", "- World!"]++ it "fills all lines with a prefix" $ do+ let s = defaultWrapSettings { fillStrategy = FillPrefix "- "+ , fillScope = FillAll+ }+ wrapTextToLines s 8 "Hello there, World!"+ `shouldBe` ["- Hello", "- there,", "- World!"]++ it "takes fill width into account" $ do+ let s = defaultWrapSettings { fillStrategy = FillPrefix "- " }+ wrapTextToLines s 3 "a b c d"+ `shouldBe` ["a b", "- c", "- d"]++ it "takes fill indent into account" $ do+ let s = defaultWrapSettings { fillStrategy = FillIndent 2 }+ wrapTextToLines s 3 "a b c d"+ `shouldBe` ["a b", " c", " d"]++ it "takes fill width into account on all lines" $ do+ let s = defaultWrapSettings { fillStrategy = FillPrefix "- "+ , fillScope = FillAll+ }+ wrapTextToLines s 3 "a b c d"+ `shouldBe` ["- a", "- b", "- c", "- d"]++ it "takes fill indent into account on all lines" $ do+ let s = defaultWrapSettings { fillStrategy = FillIndent 2+ , fillScope = FillAll+ }+ wrapTextToLines s 3 "a b c d"+ `shouldBe` [" a", " b", " c", " d"]++ it "places fill after preserved indent" $ do+ let s = defaultWrapSettings { preserveIndentation = True+ , fillStrategy = FillPrefix "- "+ }+ wrapTextToLines s 5 " a b c d"+ `shouldBe` [" a b", " - c", " - d"]++ it "places fill indent after preserved indent" $ do+ let s = defaultWrapSettings { preserveIndentation = True+ , fillStrategy = FillIndent 2+ }+ wrapTextToLines s 5 " a b c d"+ `shouldBe` [" a b", " c", " d"]
word-wrap.cabal view
@@ -1,5 +1,5 @@ name: word-wrap-version: 0.4.1+version: 0.5 synopsis: A library for word-wrapping description: A library for wrapping long lines of text. license: BSD3@@ -9,7 +9,7 @@ copyright: 2017 Jonathan Daugherty category: Text build-type: Simple-cabal-version: >=1.18+cabal-version: 1.18 Homepage: https://github.com/jtdaugherty/word-wrap/ Bug-reports: https://github.com/jtdaugherty/word-wrap/issues @@ -28,7 +28,7 @@ hs-source-dirs: src default-language: Haskell2010 ghc-options: -Wall- build-depends: base < 5,+ build-depends: base >= 4.8 && < 5, text benchmark word-wrap-benchmarks