text-manipulate 0.1.1 → 0.1.2
raw patch · 7 files changed
+284/−239 lines, 7 files
Files
- src/Data/Text/Lazy/Manipulate.hs +30/−8
- src/Data/Text/Manipulate.hs +33/−10
- src/Data/Text/Manipulate/Fusion.hs +0/−177
- src/Data/Text/Manipulate/Internal/Fusion.hs +177/−0
- src/Data/Text/Manipulate/Internal/Types.hs +40/−0
- src/Data/Text/Manipulate/Types.hs +0/−40
- text-manipulate.cabal +4/−4
src/Data/Text/Lazy/Manipulate.hs view
@@ -47,6 +47,10 @@ , indentLines , prependLines + -- * Ellipsis+ , toEllipsis+ , toEllipsisWith+ -- * Acronyms , toAcronym @@ -67,16 +71,17 @@ , isWordBoundary ) where -import qualified Data.Char as Char-import Data.List (intersperse)+import qualified Data.Char as Char+import Data.Int+import Data.List (intersperse) import Data.Monoid import Data.Text.Buildable-import Data.Text.Manipulate.Fusion (lazy)-import qualified Data.Text.Manipulate.Fusion as Fusion-import Data.Text.Manipulate.Types-import Data.Text.Lazy (Text)-import qualified Data.Text.Lazy as LText-import Data.Text.Lazy.Builder (toLazyText)+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as LText+import Data.Text.Lazy.Builder (toLazyText)+import Data.Text.Manipulate.Internal.Fusion (lazy)+import qualified Data.Text.Manipulate.Internal.Fusion as Fusion+import Data.Text.Manipulate.Internal.Types -- $strict -- This library provides functions for manipulating both strict and lazy Text types.@@ -124,6 +129,23 @@ -- | Prepend newlines with the given separator prependLines :: Text -> Text -> Text prependLines sep = mappend sep . LText.unlines . intersperse sep . LText.lines++-- | O(n) Truncate text to a specific length.+-- If the text was truncated the ellipsis sign "..." will be appended.+--+-- /See:/ 'toEllipsisWith'+toEllipsis :: Int64 -> Text -> Text+toEllipsis n = toEllipsisWith n "..."++-- | O(n) Truncate text to a specific length.+-- If the text was truncated the given ellipsis sign will be appended.+toEllipsisWith :: Int64 -- ^ Length.+ -> Text -- ^ Ellipsis.+ -> Text+ -> Text+toEllipsisWith n suf x+ | LText.length x > n = LText.take n x <> suf+ | otherwise = x -- | O(n) Returns the first word, or the original text if no word -- boundary is encountered. /Subject to fusion./
src/Data/Text/Manipulate.hs view
@@ -47,6 +47,10 @@ , indentLines , prependLines + -- * Ellipsis+ , toEllipsis+ , toEllipsisWith+ -- * Acronyms , toAcronym @@ -67,16 +71,16 @@ , isWordBoundary ) where -import qualified Data.Char as Char-import Data.List (intersperse)+import qualified Data.Char as Char+import Data.List (intersperse) import Data.Monoid-import Data.Text (Text)-import qualified Data.Text as Text-import qualified Data.Text.Lazy as LText-import qualified Data.Text.Lazy.Manipulate as LMan-import Data.Text.Manipulate.Fusion (strict)-import qualified Data.Text.Manipulate.Fusion as Fusion-import Data.Text.Manipulate.Types+import Data.Text (Text)+import qualified Data.Text as Text+import qualified Data.Text.Lazy as LText+import qualified Data.Text.Lazy.Manipulate as LMan+import Data.Text.Manipulate.Internal.Fusion (strict)+import qualified Data.Text.Manipulate.Internal.Fusion as Fusion+import Data.Text.Manipulate.Internal.Types -- $strict -- This library provides functions for manipulating both strict and lazy Text types.@@ -86,7 +90,7 @@ -- $unicode -- While this library supports Unicode in a similar fashion to the -- underlying <http://hackage.haskell.org/package/text text> library,--- more Unicode specific handling of word boundaries can be found in the+-- more explicit Unicode handling of word boundaries can be found in the -- <http://hackage.haskell.org/package/text-icu text-icu> library. -- $fusion@@ -126,12 +130,31 @@ Nothing -> x -- | Indent newlines by the given number of spaces.+--+-- /See:/ 'prependLines' indentLines :: Int -> Text -> Text indentLines n = prependLines (Text.replicate n " ") -- | Prepend newlines with the given separator prependLines :: Text -> Text -> Text prependLines sep = mappend sep . Text.unlines . intersperse sep . Text.lines++-- | O(n) Truncate text to a specific length.+-- If the text was truncated the ellipsis sign "..." will be appended.+--+-- /See:/ 'toEllipsisWith'+toEllipsis :: Int -> Text -> Text+toEllipsis n = toEllipsisWith n "..."++-- | O(n) Truncate text to a specific length.+-- If the text was truncated the given ellipsis sign will be appended.+toEllipsisWith :: Int -- ^ Length.+ -> Text -- ^ Ellipsis.+ -> Text+ -> Text+toEllipsisWith n suf x+ | Text.length x > n = Text.take n x <> suf+ | otherwise = x -- | O(n) Returns the first word, or the original text if no word -- boundary is encountered. /Subject to fusion./
− src/Data/Text/Manipulate/Fusion.hs
@@ -1,177 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE ViewPatterns #-}-{-# LANGUAGE RankNTypes #-}---- Module : Data.Text.Manipulate.Fusion--- Copyright : (c) 2014 Brendan Hay <brendan.g.hay@gmail.com>--- License : This Source Code Form is subject to the terms of--- the Mozilla Public License, v. 2.0.--- A copy of the MPL can be found in the LICENSE file or--- you can obtain it at http://mozilla.org/MPL/2.0/.--- Maintainer : Brendan Hay <brendan.g.hay@gmail.com>--- Stability : experimental--- Portability : non-portable (GHC extensions)--module Data.Text.Manipulate.Fusion where--import qualified Data.Char as Char-import Data.Text (Text)-import qualified Data.Text.Internal.Fusion as Fusion-import Data.Text.Internal.Fusion.CaseMapping (upperMapping, lowerMapping)-import Data.Text.Internal.Fusion.Common-import Data.Text.Internal.Fusion.Types-import qualified Data.Text.Internal.Lazy.Fusion as LFusion-import qualified Data.Text.Lazy as LText-import Data.Text.Manipulate.Types--takeWord :: Stream Char -> Stream Char-takeWord = transform (const Done) yield . tokenise-{-# INLINE [0] takeWord #-}--dropWord :: Stream Char -> Stream Char-dropWord (tokenise -> Stream next0 s0 len) = Stream next (True :*: s0) len- where- next (skip :*: s) =- case next0 s of- Done -> Done- Skip s' -> Skip (skip :*: s')- Yield t s' ->- case t of- B '\0' -> Skip (False :*: s')- B _ | skip -> Skip (False :*: s')- B c -> Yield c (False :*: s')- _ | skip -> Skip (skip :*: s')- U c -> Yield c (skip :*: s')- L c -> Yield c (skip :*: s')-{-# INLINE [0] dropWord #-}--toTitle :: Stream Char -> Stream Char-toTitle = mapHead toUpper . transformWith (yield ' ') upper lower . tokenise-{-# INLINE [0] toTitle #-}--toCamel :: Stream Char -> Stream Char-toCamel = mapHead toLower . transformWith skip' upper lower . tokenise-{-# INLINE [0] toCamel #-}--toPascal :: Stream Char -> Stream Char-toPascal = mapHead toUpper . transformWith skip' upper lower . tokenise-{-# INLINE [0] toPascal #-}--toSnake :: Stream Char -> Stream Char-toSnake = transform (yield '_') lower . tokenise-{-# INLINE [0] toSnake #-}--toSpinal :: Stream Char -> Stream Char-toSpinal = transform (yield '-') lower . tokenise-{-# INLINE [0] toSpinal #-}--toTrain :: Stream Char -> Stream Char-toTrain = mapHead toUpper . transformWith (yield '-') upper lower . tokenise-{-# INLINE [0] toTrain #-}--strict :: (Stream Char -> Stream Char) -> Text -> Text-strict f t = Fusion.unstream (f (Fusion.stream t))-{-# INLINE [0] strict #-}--lazy :: (Stream Char -> Stream Char) -> LText.Text -> LText.Text-lazy f t = LFusion.unstream (f (LFusion.stream t))-{-# INLINE [0] lazy #-}--skip' :: forall s. s -> Step (CC s) Char-skip' s = Skip (CC s '\0' '\0')--yield, upper, lower :: forall s. Char -> s -> Step (CC s) Char-yield !c s = Yield c (CC s '\0' '\0')-upper !c s = upperMapping c s-lower !c s = lowerMapping c s---- | Step across word boundaries using a custom action, and transform--- both subsequent uppercase and lowercase characters uniformly.------ /See:/ 'transformWith'-transform :: (forall s. s -> Step (CC s) Char) -- ^ Boundary action.- -> (forall s. Char -> s -> Step (CC s) Char) -- ^ Character mapping.- -> Stream Token -- ^ Input stream.- -> Stream Char-transform s m = transformWith s m m-{-# INLINE [0] transform #-}---- | Step across word boundaries using a custom action, and transform--- subsequent characters after the word boundary is encountered with a mapping--- depending on case.-transformWith :: (forall s. s -> Step (CC s) Char) -- ^ Boundary action.- -> (forall s. Char -> s -> Step (CC s) Char) -- ^ Boundary mapping.- -> (forall s. Char -> s -> Step (CC s) Char) -- ^ Subsequent character mapping.- -> Stream Token -- ^ Input stream.- -> Stream Char-transformWith md mu mc (Stream next0 s0 len) =- -- HINT: len incorrect when the boundary replacement yields a char.- Stream next (CC (False :*: False :*: s0) '\0' '\0') len- where- next (CC (up :*: prev :*: s) '\0' _) =- case next0 s of- Done -> Done- Skip s' -> Skip (CC (up :*: prev :*: s') '\0' '\0')- Yield t s' ->- case t of- B _ -> md (False :*: True :*: s')- U c | prev -> mu c (True :*: False :*: s')- L c | prev -> mu c (False :*: False :*: s')- U c | up -> mu c (True :*: False :*: s')- U c -> mc c (True :*: False :*: s')- L c -> mc c (False :*: False :*: s')-- next (CC s a b) = Yield a (CC s b '\0')-{-# INLINE [0] transformWith #-}---- | A token representing characters and boundaries in a stream.-data Token- = B {-# UNPACK #-} !Char -- ^ Word boundary.- | U {-# UNPACK #-} !Char -- ^ Upper case character.- | L {-# UNPACK #-} !Char -- ^ Lower case character.- deriving (Show)---- | Tokenise a character stream using the default 'isBoundary' predicate.------ /See:/ 'tokeniseWith'-tokenise :: Stream Char -- ^ Input stream.- -> Stream Token-tokenise = tokeniseWith isBoundary-{-# INLINE [0] tokenise #-}---- | Tokenise a character stream using a custom boundary predicate.-tokeniseWith :: (Char -> Bool) -- ^ Boundary predicate.- -> Stream Char -- ^ Input stream.- -> Stream Token-tokeniseWith f (Stream next0 s0 len) =- -- HINT: len incorrect if there are adjacent boundaries, which are skipped.- Stream next (CC (True :*: False :*: False :*: s0) '\0' '\0') len- where- next (CC (start :*: up :*: prev :*: s) '\0' _) =- case next0 s of- Done -> Done- Skip s' -> Skip (CC (start :*: up :*: prev :*: s') '\0' '\0')- Yield c s'- | not b, start -> push- | up -> push- | b, prev -> Skip (step start)- | otherwise -> push- where- push | b = Yield (B c) (step False)- | u, skip = Yield (U c) (step False)- | u = Yield (B '\0') (CC (False :*: u :*: b :*: s') c '\0')- | otherwise = Yield (L c) (step False)-- step p = CC (p :*: u :*: b :*: s') '\0' '\0'-- skip = up || start || prev-- b = f c- u = Char.isUpper c-- next (CC s a b) = Yield (U a) (CC s b '\0')-{-# INLINE [0] tokeniseWith #-}--mapHead :: (Stream Char -> Stream Char) -> Stream Char -> Stream Char-mapHead f s = maybe s (\(x, s') -> f (singleton x) `append` s') (uncons s)-{-# INLINE [0] mapHead #-}
+ src/Data/Text/Manipulate/Internal/Fusion.hs view
@@ -0,0 +1,177 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE RankNTypes #-}++-- Module : Data.Text.Manipulate.Internal.Fusion+-- Copyright : (c) 2014 Brendan Hay <brendan.g.hay@gmail.com>+-- License : This Source Code Form is subject to the terms of+-- the Mozilla Public License, v. 2.0.+-- A copy of the MPL can be found in the LICENSE file or+-- you can obtain it at http://mozilla.org/MPL/2.0/.+-- Maintainer : Brendan Hay <brendan.g.hay@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)++module Data.Text.Manipulate.Internal.Fusion where++import qualified Data.Char as Char+import Data.Text (Text)+import qualified Data.Text.Internal.Fusion as Fusion+import Data.Text.Internal.Fusion.CaseMapping (upperMapping, lowerMapping)+import Data.Text.Internal.Fusion.Common+import Data.Text.Internal.Fusion.Types+import qualified Data.Text.Internal.Lazy.Fusion as LFusion+import qualified Data.Text.Lazy as LText+import Data.Text.Manipulate.Internal.Types++takeWord :: Stream Char -> Stream Char+takeWord = transform (const Done) yield . tokenise+{-# INLINE [0] takeWord #-}++dropWord :: Stream Char -> Stream Char+dropWord (tokenise -> Stream next0 s0 len) = Stream next (True :*: s0) len+ where+ next (skip :*: s) =+ case next0 s of+ Done -> Done+ Skip s' -> Skip (skip :*: s')+ Yield t s' ->+ case t of+ B '\0' -> Skip (False :*: s')+ B _ | skip -> Skip (False :*: s')+ B c -> Yield c (False :*: s')+ _ | skip -> Skip (skip :*: s')+ U c -> Yield c (skip :*: s')+ L c -> Yield c (skip :*: s')+{-# INLINE [0] dropWord #-}++toTitle :: Stream Char -> Stream Char+toTitle = mapHead toUpper . transformWith (yield ' ') upper lower . tokenise+{-# INLINE [0] toTitle #-}++toCamel :: Stream Char -> Stream Char+toCamel = mapHead toLower . transformWith skip' upper lower . tokenise+{-# INLINE [0] toCamel #-}++toPascal :: Stream Char -> Stream Char+toPascal = mapHead toUpper . transformWith skip' upper lower . tokenise+{-# INLINE [0] toPascal #-}++toSnake :: Stream Char -> Stream Char+toSnake = transform (yield '_') lower . tokenise+{-# INLINE [0] toSnake #-}++toSpinal :: Stream Char -> Stream Char+toSpinal = transform (yield '-') lower . tokenise+{-# INLINE [0] toSpinal #-}++toTrain :: Stream Char -> Stream Char+toTrain = mapHead toUpper . transformWith (yield '-') upper lower . tokenise+{-# INLINE [0] toTrain #-}++strict :: (Stream Char -> Stream Char) -> Text -> Text+strict f t = Fusion.unstream (f (Fusion.stream t))+{-# INLINE [0] strict #-}++lazy :: (Stream Char -> Stream Char) -> LText.Text -> LText.Text+lazy f t = LFusion.unstream (f (LFusion.stream t))+{-# INLINE [0] lazy #-}++skip' :: forall s. s -> Step (CC s) Char+skip' s = Skip (CC s '\0' '\0')++yield, upper, lower :: forall s. Char -> s -> Step (CC s) Char+yield !c s = Yield c (CC s '\0' '\0')+upper !c s = upperMapping c s+lower !c s = lowerMapping c s++-- | Step across word boundaries using a custom action, and transform+-- both subsequent uppercase and lowercase characters uniformly.+--+-- /See:/ 'transformWith'+transform :: (forall s. s -> Step (CC s) Char) -- ^ Boundary action.+ -> (forall s. Char -> s -> Step (CC s) Char) -- ^ Character mapping.+ -> Stream Token -- ^ Input stream.+ -> Stream Char+transform s m = transformWith s m m+{-# INLINE [0] transform #-}++-- | Step across word boundaries using a custom action, and transform+-- subsequent characters after the word boundary is encountered with a mapping+-- depending on case.+transformWith :: (forall s. s -> Step (CC s) Char) -- ^ Boundary action.+ -> (forall s. Char -> s -> Step (CC s) Char) -- ^ Boundary mapping.+ -> (forall s. Char -> s -> Step (CC s) Char) -- ^ Subsequent character mapping.+ -> Stream Token -- ^ Input stream.+ -> Stream Char+transformWith md mu mc (Stream next0 s0 len) =+ -- HINT: len incorrect when the boundary replacement yields a char.+ Stream next (CC (False :*: False :*: s0) '\0' '\0') len+ where+ next (CC (up :*: prev :*: s) '\0' _) =+ case next0 s of+ Done -> Done+ Skip s' -> Skip (CC (up :*: prev :*: s') '\0' '\0')+ Yield t s' ->+ case t of+ B _ -> md (False :*: True :*: s')+ U c | prev -> mu c (True :*: False :*: s')+ L c | prev -> mu c (False :*: False :*: s')+ U c | up -> mu c (True :*: False :*: s')+ U c -> mc c (True :*: False :*: s')+ L c -> mc c (False :*: False :*: s')++ next (CC s a b) = Yield a (CC s b '\0')+{-# INLINE [0] transformWith #-}++-- | A token representing characters and boundaries in a stream.+data Token+ = B {-# UNPACK #-} !Char -- ^ Word boundary.+ | U {-# UNPACK #-} !Char -- ^ Upper case character.+ | L {-# UNPACK #-} !Char -- ^ Lower case character.+ deriving (Show)++-- | Tokenise a character stream using the default 'isBoundary' predicate.+--+-- /See:/ 'tokeniseWith'+tokenise :: Stream Char -- ^ Input stream.+ -> Stream Token+tokenise = tokeniseWith isBoundary+{-# INLINE [0] tokenise #-}++-- | Tokenise a character stream using a custom boundary predicate.+tokeniseWith :: (Char -> Bool) -- ^ Boundary predicate.+ -> Stream Char -- ^ Input stream.+ -> Stream Token+tokeniseWith f (Stream next0 s0 len) =+ -- HINT: len incorrect if there are adjacent boundaries, which are skipped.+ Stream next (CC (True :*: False :*: False :*: s0) '\0' '\0') len+ where+ next (CC (start :*: up :*: prev :*: s) '\0' _) =+ case next0 s of+ Done -> Done+ Skip s' -> Skip (CC (start :*: up :*: prev :*: s') '\0' '\0')+ Yield c s'+ | not b, start -> push+ | up -> push+ | b, prev -> Skip (step start)+ | otherwise -> push+ where+ push | b = Yield (B c) (step False)+ | u, skip = Yield (U c) (step False)+ | u = Yield (B '\0') (CC (False :*: u :*: b :*: s') c '\0')+ | otherwise = Yield (L c) (step False)++ step p = CC (p :*: u :*: b :*: s') '\0' '\0'++ skip = up || start || prev++ b = f c+ u = Char.isUpper c++ next (CC s a b) = Yield (U a) (CC s b '\0')+{-# INLINE [0] tokeniseWith #-}++mapHead :: (Stream Char -> Stream Char) -> Stream Char -> Stream Char+mapHead f s = maybe s (\(x, s') -> f (singleton x) `append` s') (uncons s)+{-# INLINE [0] mapHead #-}
+ src/Data/Text/Manipulate/Internal/Types.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ViewPatterns #-}++-- Module : Data.Text.Manipulate.Internal.Types+-- Copyright : (c) 2014 Brendan Hay <brendan.g.hay@gmail.com>+-- License : This Source Code Form is subject to the terms of+-- the Mozilla Public License, v. 2.0.+-- A copy of the MPL can be found in the LICENSE file or+-- you can obtain it at http://mozilla.org/MPL/2.0/.+-- Maintainer : Brendan Hay <brendan.g.hay@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)++module Data.Text.Manipulate.Internal.Types where++import Control.Monad+import qualified Data.Char as Char+import Data.Monoid+import Data.Text.Buildable++newtype Ordinal a = Ordinal a++instance Integral a => Buildable (Ordinal a) where+ build (Ordinal (toInteger -> n)) = build n <> suf+ where+ suf | x `elem` [11..13] = "th"+ | y == 1 = "st"+ | y == 2 = "nd"+ | y == 3 = "rd"+ | otherwise = "th"++ (flip mod 100 -> x, flip mod 10 -> y) = join (,) (abs n)++-- | Returns 'True' for any boundary or uppercase character.+isWordBoundary :: Char -> Bool+isWordBoundary c = Char.isUpper c || isBoundary c++-- | Returns 'True' for any boundary character.+isBoundary :: Char -> Bool+isBoundary = not . Char.isAlphaNum
− src/Data/Text/Manipulate/Types.hs
@@ -1,40 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ViewPatterns #-}---- Module : Data.Text.Manipulate.Types--- Copyright : (c) 2014 Brendan Hay <brendan.g.hay@gmail.com>--- License : This Source Code Form is subject to the terms of--- the Mozilla Public License, v. 2.0.--- A copy of the MPL can be found in the LICENSE file or--- you can obtain it at http://mozilla.org/MPL/2.0/.--- Maintainer : Brendan Hay <brendan.g.hay@gmail.com>--- Stability : experimental--- Portability : non-portable (GHC extensions)--module Data.Text.Manipulate.Types where--import Control.Monad-import qualified Data.Char as Char-import Data.Monoid-import Data.Text.Buildable--newtype Ordinal a = Ordinal a--instance Integral a => Buildable (Ordinal a) where- build (Ordinal (toInteger -> n)) = build n <> suf- where- suf | x `elem` [11..13] = "th"- | y == 1 = "st"- | y == 2 = "nd"- | y == 3 = "rd"- | otherwise = "th"-- (flip mod 100 -> x, flip mod 10 -> y) = join (,) (abs n)---- | Returns 'True' for any boundary or uppercase character.-isWordBoundary :: Char -> Bool-isWordBoundary c = Char.isUpper c || isBoundary c---- | Returns 'True' for any boundary character.-isBoundary :: Char -> Bool-isBoundary = not . Char.isAlphaNum
text-manipulate.cabal view
@@ -1,6 +1,6 @@ name: text-manipulate-version: 0.1.1-synopsis: Case conversion, word boundary manipulation, and miscellanous textual subjugation.+version: 0.1.2+synopsis: Case conversion, word boundary manipulation, and textual subjugation. homepage: https://github.com/brendanhay/text-manipulate license: OtherLicense license-file: LICENSE@@ -40,8 +40,8 @@ , Data.Text.Lazy.Manipulate other-modules:- Data.Text.Manipulate.Fusion- , Data.Text.Manipulate.Types+ Data.Text.Manipulate.Internal.Fusion+ , Data.Text.Manipulate.Internal.Types build-depends: base >= 4.5 && < 5.0