diff --git a/CHANGELOG.md b/CHANGELOG.md index 8732403..41d06bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ * Switched to Megaparsec 5 for parsing. +* Made the API use `Text` instead of `String` (which significally improved + speed). + +* The `toUnderscore`, `toDashed`, and `toCamelCased` are not partial + anymore, now they operate in `MonadThrow`. + * Improved documentation. ## Inflections 0.2.0.1 diff --git a/Text/Inflections.hs b/Text/Inflections.hs index 5e48b06..401da2f 100644 --- a/Text/Inflections.hs +++ b/Text/Inflections.hs @@ -103,66 +103,57 @@ module Text.Inflections ) where -import Text.Inflections.Data ( Transliterations, defaultMap ) +import Control.Monad (liftM) +import Control.Monad.Catch (MonadThrow (..)) +import Data.Text (Text) +import Text.Inflections.Camelize (camelize, camelizeCustom) +import Text.Inflections.Dasherize (dasherize) +import Text.Inflections.Data (Transliterations, defaultMap) +import Text.Inflections.Humanize (humanize) +import Text.Inflections.Ordinal (ordinal, ordinalize) +import Text.Inflections.Parameterize (parameterize, parameterizeCustom) +import Text.Inflections.Parse.CamelCase (parseCamelCase) +import Text.Inflections.Parse.SnakeCase (parseSnakeCase) +import Text.Inflections.Parse.Types +import Text.Inflections.Titleize (titleize) +import Text.Inflections.Transliterate (transliterate, transliterateCustom) +import Text.Inflections.Underscore (underscore) +import Text.Megaparsec -import Text.Inflections.Parameterize ( parameterize, parameterizeCustom ) - -import Text.Inflections.Underscore ( underscore ) - -import Text.Inflections.Camelize ( camelize, camelizeCustom ) - -import Text.Inflections.Humanize ( humanize ) - -import Text.Inflections.Titleize ( titleize ) - -import Text.Inflections.Transliterate ( transliterate, transliterateCustom ) - -import Text.Inflections.Dasherize ( dasherize ) - -import Text.Inflections.Ordinal ( ordinal, ordinalize ) - -import Text.Inflections.Parse.SnakeCase ( parseSnakeCase ) - -import Text.Inflections.Parse.Types ( mapWord ) - -import Text.Inflections.Parse.CamelCase ( parseCamelCase ) - -import Data.Char ( toLower ) - --- | Transforms CamelCasedString to --- snake_cased_string_with_underscores. Throws exception if parsing failed +-- | Transforms CamelCasedString to snake_cased_string_with_underscores. In +-- case of failed parsing 'InflectionException' is thrown. -- -- >>> toUnderscore "FooBarBazz" -- "foo_bar_bazz" -toUnderscore :: String -> String -toUnderscore = - underscore - . either (error . ("toUnderscore: " ++) . show) id - . parseCamelCase [] +toUnderscore :: MonadThrow m => Text -> m Text +toUnderscore = liftM underscore . handleEither . parseCamelCase [] +{-# INLINE toUnderscore #-} --- | Transforms CamelCasedString to snake-cased-string-with-dashes. Throws --- exception if parsing failed. +-- | Transforms CamelCasedString to snake-cased-string-with-dashes. In case +-- of failed parsing 'InflectionException' is thrown. -- -- >>> toDashed "FooBarBazz" -- "foo-bar-bazz" -toDashed :: String -> String -toDashed = - dasherize - . map (mapWord (map toLower)) - . either (error . ("toDashed: " ++) . show) id - . parseCamelCase [] +toDashed :: MonadThrow m => Text -> m Text +toDashed = liftM dasherize . handleEither . parseCamelCase [] +{-# INLINE toDashed #-} -- | Transforms underscored_text to CamelCasedText. If first argument is -- 'True' then FirstCharacter in result string will be in upper case. If --- 'False' then firstCharacter will be in lower case. Throws exception if --- parsing failed +-- 'False' then firstCharacter will be in lower case. In case of failed +-- parsing 'InflectionException' is thrown. -- -- >>> toCamelCased True "foo_bar_bazz" -- "FooBarBazz" -- >>> toCamelCased False "foo_bar_bazz" -- "fooBarBazz" -toCamelCased :: Bool -> String -> String -toCamelCased t = - camelizeCustom t - . either (error . ("toCamelCased: " ++) . show) id - . parseSnakeCase [] +toCamelCased :: MonadThrow m => Bool -> Text -> m Text +toCamelCased t = liftM (camelizeCustom t) . handleEither . parseSnakeCase [] +{-# INLINE toCamelCased #-} + +-- | Take an 'Either' that can contain a parser error and throw it if +-- necessary. If everything is OK, just return 'Right' value. +handleEither :: MonadThrow m => Either (ParseError Char Dec) a -> m a +handleEither (Left err) = throwM (InflectionParsingFailed err) +handleEither (Right x) = return x +{-# INLINE handleEither #-} diff --git a/Text/Inflections/Camelize.hs b/Text/Inflections/Camelize.hs index 95c2d2b..f902291 100644 --- a/Text/Inflections/Camelize.hs +++ b/Text/Inflections/Camelize.hs @@ -9,13 +9,23 @@ -- -- Conversion to CamelCased phrases. -module Text.Inflections.Camelize ( camelize, camelizeCustom ) where +{-# LANGUAGE CPP #-} +{-# LANGUAGE OverloadedStrings #-} -import Text.Inflections.Parse.Types (Word(..)) +module Text.Inflections.Camelize + ( camelize + , camelizeCustom ) +where -import Data.Char (toUpper, toLower) +import Data.Text (Text) +import Text.Inflections.Parse.Types +import qualified Data.Text as T -import Prelude (String, Bool(..), concatMap, (.), zip, ($), repeat) +#if MIN_VERSION_base(4,8,0) +import Prelude hiding (Word) +#else +import Control.Applicative +#endif -- |Turns a an input Word List in into CamelCase. Returns the CamelCase String. -- @@ -23,8 +33,9 @@ import Prelude (String, Bool(..), concatMap, (.), zip, ($), repeat) -- "FoobarBazz" camelize :: [Word] -- ^ Input Words to separate with underscores - -> String -- ^ The camelized String + -> Text -- ^ The camelized 'Text' camelize = camelizeCustom True +{-# INLINE camelize #-} -- |Turns an input Word List into a CamelCase String. -- @@ -33,16 +44,8 @@ camelize = camelizeCustom True camelizeCustom :: Bool -- ^ Whether to capitalize the first character in the output String -> [Word] -- ^ The input Words - -> String -- ^ The camelized String -camelizeCustom isFirstCap = concatMap (caseForWord isFirstCap) . isFirstList - -caseForWord :: Bool -> (Word, Bool) -> String -caseForWord True (Word (c:cs), True) = toUpper c : cs -caseForWord False (Word (c:cs), True) = toLower c : cs -caseForWord _ (Word (c:cs), _) = toUpper c : cs -caseForWord _ (Word [], _) = [] -caseForWord _ (Acronym s, _) = s - --- |Returns list with Bool indicating if an element is first. -isFirstList :: [a] -> [(a, Bool)] -isFirstList xs = zip xs $ True : repeat False + -> Text -- ^ The camelized 'Text' +camelizeCustom _ [] = "" +camelizeCustom c (x:xs) = T.concat $ + (mapWord (if c then T.toTitle else T.toLower) x) : (mapWord T.toTitle <$> xs) +{-# INLINE camelizeCustom #-} diff --git a/Text/Inflections/Dasherize.hs b/Text/Inflections/Dasherize.hs index 7c6e260..4746e96 100644 --- a/Text/Inflections/Dasherize.hs +++ b/Text/Inflections/Dasherize.hs @@ -9,12 +9,14 @@ -- -- Conversion to dasherized phrases. -{-# LANGUAGE CPP #-} +{-# LANGUAGE CPP #-} +{-# LANGUAGE OverloadedStrings #-} module Text.Inflections.Dasherize ( dasherize ) where -import Data.List (intercalate) +import Data.Text (Text) import Text.Inflections.Parse.Types +import qualified Data.Text as T #if MIN_VERSION_base(4,8,0) import Prelude hiding (Word) @@ -26,5 +28,6 @@ import Prelude hiding (Word) -- "foo-bar-bazz" dasherize :: [Word] -- ^ Input Words to separate with dashes - -> String -- ^ The dasherized String -dasherize = intercalate "-" . fmap unWord + -> Text -- ^ The dasherized String +dasherize = T.intercalate "-" . fmap (mapWord T.toLower) +{-# INLINE dasherize #-} diff --git a/Text/Inflections/Data.hs b/Text/Inflections/Data.hs index 3f57ae9..50b91b2 100644 --- a/Text/Inflections/Data.hs +++ b/Text/Inflections/Data.hs @@ -11,12 +11,12 @@ module Text.Inflections.Data where -import Data.Map (Map) -import qualified Data.Map as M +import Data.HashMap.Strict (HashMap) +import qualified Data.HashMap.Strict as M -- |A 'Data.Map.Map' containing mappings from international characters to -- sequences approximating these characters within the ASCII range. -type Transliterations = Map Char String +type Transliterations = HashMap Char String -- |These default transliterations stolen from the Ruby i18n library - see -- . diff --git a/Text/Inflections/Humanize.hs b/Text/Inflections/Humanize.hs index eff8539..e25765d 100644 --- a/Text/Inflections/Humanize.hs +++ b/Text/Inflections/Humanize.hs @@ -9,32 +9,34 @@ -- -- Conversion to “humanized” phrases. -module Text.Inflections.Humanize (humanize) where +{-# LANGUAGE CPP #-} +{-# LANGUAGE OverloadedStrings #-} -import Text.Inflections.Parse.Types (Word(..)) +module Text.Inflections.Humanize + ( humanize ) +where -import Data.Char (toUpper) +import Data.Text (Text) +import Text.Inflections.Parse.Types +import qualified Data.Text as T -import Prelude (String, Bool(..), (.), map, zip, ($), unwords, repeat) +#if MIN_VERSION_base(4,8,0) +import Prelude hiding (Word) +#else +import Control.Applicative +#endif --- |Capitalizes the first word and turns underscores into spaces. Like titleize, --- this is meant for creating pretty output. +-- |Capitalizes the first word and turns underscores into spaces. Like +-- 'Text.Inflections.Titleize.titleize', this is meant for creating pretty +-- output. -- -- >>> humanize [ Word "foo", Acronym "bar", Word "bazz" ] -- "Foo bar bazz" humanize :: [Word] -- ^ List of Words, first of which will be capitalized - -> String -- ^ The humanized output -humanize = unwords . map caseForWord . isFirstList - --- |Returns list with Bool indicating if an element is first. -isFirstList :: [a] -> [(a, Bool)] -isFirstList xs = zip xs $ True : repeat False - --- | Convert given 'Word' to capitalized 'String' when associated Boolean --- value is 'True'. -caseForWord :: (Word, Bool) -> String -caseForWord (Word (c:cs), True) = toUpper c : cs -caseForWord (Word s, False) = s -caseForWord (Word [], _) = [] -caseForWord (Acronym s, _) = s -- Acronyms are left intact + -> Text -- ^ The humanized output +humanize xs' = + case mapWord (T.replace "_" " ") <$> xs' of + [] -> "" + (x:xs) -> T.unwords $ T.toTitle x : (T.toLower <$> xs) +{-# INLINE humanize #-} diff --git a/Text/Inflections/Ordinal.hs b/Text/Inflections/Ordinal.hs index fe46675..ab424de 100644 --- a/Text/Inflections/Ordinal.hs +++ b/Text/Inflections/Ordinal.hs @@ -9,9 +9,17 @@ -- -- Conversion to spelled ordinal numbers. -module Text.Inflections.Ordinal (ordinal, ordinalize) +{-# LANGUAGE OverloadedStrings #-} + +module Text.Inflections.Ordinal + ( ordinal + , ordinalize ) where +import Data.Monoid ((<>)) +import Data.Text (Text) +import qualified Data.Text as T + -- |Returns the suffix that should be added to a number to denote the position -- in an ordered sequence such as 1st, 2nd, 3rd, 4th. -- @@ -21,7 +29,7 @@ where -- "nd" -- >>> ordinal 10 -- "th" -ordinal :: Integral a => a -> String +ordinal :: Integral a => a -> Text ordinal number | remainder100 `elem` [11..13] = "th" | remainder10 == 1 = "st" @@ -41,5 +49,6 @@ ordinal number -- "2nd" -- >>> ordinalize 10 -- "10th" -ordinalize :: (Integral a, Show a) => a -> String -ordinalize n = show n ++ ordinal n +ordinalize :: (Integral a, Show a) => a -> Text +ordinalize n = T.pack (show n) <> ordinal n +{-# INLINE ordinalize #-} diff --git a/Text/Inflections/Parameterize.hs b/Text/Inflections/Parameterize.hs index 52b7ad6..9896d65 100644 --- a/Text/Inflections/Parameterize.hs +++ b/Text/Inflections/Parameterize.hs @@ -23,7 +23,7 @@ import Data.Maybe (mapMaybe) import Text.Inflections.Data import Text.Megaparsec import Text.Megaparsec.String -import qualified Data.Map as Map +import qualified Data.HashMap.Strict as M #if !MIN_VERSION_base(4,8,0) import Control.Applicative @@ -82,7 +82,7 @@ transliteratePCharCustom ts c = do -- We may have expanded into multiple characters during -- transliteration, so check validity of all characters in -- result. - v <- Map.lookup c ts + v <- M.lookup c ts guard (all isValidParamChar v) return (Acceptable v) diff --git a/Text/Inflections/Parse/Acronym.hs b/Text/Inflections/Parse/Acronym.hs index 21edea9..cadf723 100644 --- a/Text/Inflections/Parse/Acronym.hs +++ b/Text/Inflections/Parse/Acronym.hs @@ -11,17 +11,22 @@ {-# LANGUAGE CPP #-} -module Text.Inflections.Parse.Acronym ( acronym ) where +module Text.Inflections.Parse.Acronym + ( acronym ) +where +import Data.Text (Text) import Text.Inflections.Parse.Types import Text.Megaparsec -import Text.Megaparsec.String +import Text.Megaparsec.Text +import qualified Data.Text as T #if MIN_VERSION_base(4,8,0) import Prelude hiding (Word) #endif --- | Parser that accepts a string from given collection and turns it into +-- | Parser that accepts a 'Text' from given collection and turns it into -- an 'Acronym'. -acronym :: [String] -> Parser Word -acronym = fmap Acronym . choice . fmap string +acronym :: [Text] -> Parser Word +acronym = fmap (Acronym . T.pack) . choice . fmap (string . T.unpack) +{-# INLINE acronym #-} diff --git a/Text/Inflections/Parse/CamelCase.hs b/Text/Inflections/Parse/CamelCase.hs index 6c5b92c..9586695 100644 --- a/Text/Inflections/Parse/CamelCase.hs +++ b/Text/Inflections/Parse/CamelCase.hs @@ -9,15 +9,19 @@ -- -- Parser for camel case “symbols”. -{-# LANGUAGE CPP #-} +{-# LANGUAGE CPP #-} +{-# LANGUAGE OverloadedStrings #-} -module Text.Inflections.Parse.CamelCase ( parseCamelCase ) +module Text.Inflections.Parse.CamelCase + ( parseCamelCase ) where +import Data.Text (Text) import Text.Inflections.Parse.Acronym (acronym) import Text.Inflections.Parse.Types (Word(..)) import Text.Megaparsec -import Text.Megaparsec.String +import Text.Megaparsec.Text +import qualified Data.Text as T #if MIN_VERSION_base(4,8,0) import Prelude hiding (Word) @@ -25,7 +29,7 @@ import Prelude hiding (Word) import Control.Applicative #endif --- |Parses a CamelCase string. +-- |Parse a CamelCase string. -- -- >>> parseCamelCase ["Bar"] "FooBarBazz" -- Right [Word "Foo",Acronym "Bar",Word "Bazz"] @@ -33,18 +37,20 @@ import Control.Applicative -- Left "(unknown)" (line 1, column 4): -- unexpected '_' parseCamelCase - :: [String] -- ^ Collection of acronyms - -> String -- ^ Input + :: [Text] -- ^ Collection of acronyms + -> Text -- ^ Input -> Either (ParseError Char Dec) [Word] -- ^ Result of parsing parseCamelCase acronyms = parse (parser acronyms) "" parser - :: [String] -- ^ Collection of acronyms + :: [Text] -- ^ Collection of acronyms -> Parser [Word] -- ^ CamelCase parser parser acronyms = many (acronym acronyms <|> word) <* eof +{-# INLINE parser #-} word :: Parser Word word = do firstChar <- upperChar <|> lowerChar restChars <- many lowerChar - return . Word $ firstChar : restChars + return . Word . T.pack $ firstChar : restChars +{-# INLINE word #-} diff --git a/Text/Inflections/Parse/SnakeCase.hs b/Text/Inflections/Parse/SnakeCase.hs index 7cf69b4..0d9b207 100644 --- a/Text/Inflections/Parse/SnakeCase.hs +++ b/Text/Inflections/Parse/SnakeCase.hs @@ -11,13 +11,16 @@ {-# LANGUAGE CPP #-} -module Text.Inflections.Parse.SnakeCase ( parseSnakeCase ) +module Text.Inflections.Parse.SnakeCase + ( parseSnakeCase ) where +import Data.Text (Text) import Text.Inflections.Parse.Acronym (acronym) import Text.Inflections.Parse.Types (Word(..)) import Text.Megaparsec -import Text.Megaparsec.String +import Text.Megaparsec.Text +import qualified Data.Text as T #if MIN_VERSION_base(4,8,0) import Prelude hiding (Word) @@ -33,17 +36,20 @@ import Control.Applicative -- Left "(unknown)" (line 1, column 4): -- unexpected 'B' parseSnakeCase - :: [String] -- ^ Collection of acronyms - -> String -- ^ Input + :: [Text] -- ^ Collection of acronyms + -> Text -- ^ Input -> Either (ParseError Char Dec) [Word] -- ^ Result of parsing parseSnakeCase acronyms = parse (parser acronyms) "" parser - :: [String] -> Parser [Word] + :: [Text] + -> Parser [Word] parser acronyms = do ws <- (acronym acronyms <|> word) `sepBy` char '_' eof return ws +{-# INLINE parser #-} word :: Parser Word -word = Word <$> (some lowerChar <|> some digitChar) +word = Word . T.pack <$> (some lowerChar <|> some digitChar) +{-# INLINE word #-} diff --git a/Text/Inflections/Parse/Types.hs b/Text/Inflections/Parse/Types.hs index f4735a7..7951e28 100644 --- a/Text/Inflections/Parse/Types.hs +++ b/Text/Inflections/Parse/Types.hs @@ -9,32 +9,46 @@ -- -- Types used in the library. -{-# LANGUAGE CPP #-} +{-# LANGUAGE CPP #-} +{-# LANGUAGE DeriveDataTypeable #-} module Text.Inflections.Parse.Types ( Word (..) -- FIXME we should not export the constructor , unWord - , mapWord ) + , mapWord + , InflectionException (..) ) where +import Control.Monad.Catch (Exception) +import Data.Text (Text) +import Data.Typeable (Typeable) +import Text.Megaparsec + #if MIN_VERSION_base(4,8,0) import Prelude hiding (Word) #endif -- | A 'String' that should be kept whole through applied inflections data Word - = Word String -- ^ A word that may be transformed by inflection - | Acronym String -- ^ A word that may not be transformed by inflections + = Word Text -- ^ A word that may be transformed by inflection + | Acronym Text -- ^ A word that may not be transformed by inflections deriving (Show, Eq) --- | Get a 'String' from 'Word'. -unWord :: Word -> String +-- | Get a 'Text' value from 'Word'. +unWord :: Word -> Text unWord (Word s) = s unWord (Acronym s) = s {-# INLINE unWord #-} --- | Apply 'String' transforming function to a 'Word'. -mapWord :: (String -> String) -> Word -> Word -mapWord f (Word s) = Word (f s) -mapWord f (Acronym s) = Acronym (f s) +-- | Apply 'Text' transforming function to a 'Word' unless it's a 'Acronym'. +mapWord :: (Text -> Text) -> Word -> Text +mapWord f (Word s) = f s +mapWord _ (Acronym s) = s {-# INLINE mapWord #-} + +-- | The exceptions that is thrown when parsing of input fails. + +data InflectionException = InflectionParsingFailed (ParseError Char Dec) + deriving (Eq, Show, Typeable) + +instance Exception InflectionException diff --git a/Text/Inflections/Titleize.hs b/Text/Inflections/Titleize.hs index 12e032b..ee6429b 100644 --- a/Text/Inflections/Titleize.hs +++ b/Text/Inflections/Titleize.hs @@ -9,25 +9,26 @@ -- -- Conversion to titleized phrases. -module Text.Inflections.Titleize (titleize) where +{-# LANGUAGE CPP #-} -import Text.Inflections.Parse.Types (Word(..)) +module Text.Inflections.Titleize + ( titleize ) +where -import Data.Char (toUpper) +import Data.Text (Text) +import Text.Inflections.Parse.Types +import qualified Data.Text as T -import Prelude (String, unwords, map, ($)) +#if MIN_VERSION_base(4,8,0) +import Prelude hiding (Word) +#endif --- | Capitalizes all the Words in the input list. +-- | Capitalize all the Words in the input list. -- -- >>> titleize [ Word "foo", Acronym "bar", Word "bazz" ] -- "Foo bar Bazz" titleize :: [Word] -- ^ List of Words, first of which will be capitalized - -> String -- ^ The titleized String -titleize s = unwords $ map upperCaseWord s - --- | Transform 'Word' into an upper-cased 'String'. -upperCaseWord :: Word -> String -upperCaseWord (Word (c:cs)) = toUpper c : cs -upperCaseWord (Word []) = [] -upperCaseWord (Acronym s) = s -- Acronyms are left intact + -> Text -- ^ The titleized String +titleize = T.unwords . fmap (mapWord T.toTitle) +{-# INLINE titleize #-} diff --git a/Text/Inflections/Transliterate.hs b/Text/Inflections/Transliterate.hs index 3cf43c4..2d5e274 100644 --- a/Text/Inflections/Transliterate.hs +++ b/Text/Inflections/Transliterate.hs @@ -9,30 +9,47 @@ -- -- Support for transliteration. +{-# LANGUAGE CPP #-} +{-# LANGUAGE OverloadedStrings #-} + module Text.Inflections.Transliterate ( transliterate - , transliterateCustom - ) + , transliterateCustom ) where import Data.Char (isAscii) -import Data.Maybe(fromMaybe) +import Data.Text (Text) import Text.Inflections.Data -import qualified Data.Map as Map +import qualified Data.HashMap.Strict as M +import qualified Data.Text as T --- |Returns a String after default approximations for changing Unicode characters --- to a valid ASCII range are applied. If you want to supplement the default --- approximations with your own, you should use the transliterateCustom --- function instead of transliterate. -transliterate :: String -> String +#if !MIN_VERSION_base(4,8,0) +import Control.Applicative +#endif + +-- |Returns a String after default approximations for changing Unicode +-- characters to a valid ASCII range are applied. If you want to supplement +-- the default approximations with your own, you should use the +-- 'transliterateCustom' function instead of 'transliterate'. +transliterate :: Text -> Text transliterate = transliterateCustom "?" defaultMap +{-# INLINE transliterate #-} --- |Returns a String after default approximations for changing Unicode characters --- to a valid ASCII range are applied. -transliterateCustom :: String -> Transliterations -> String -> String -transliterateCustom replacement ts = concatMap lookupCharTransliteration - where lookupCharTransliteration c = - if isAscii c then -- Don't bother looking up Chars in ASCII range - [c] - else - fromMaybe replacement (Map.lookup c ts) +-- |Returns a String after default approximations for changing Unicode +-- characters to a valid ASCII range are applied. +transliterateCustom + :: String -- ^ The default replacement + -> Transliterations -- ^ The table of transliterations + -> Text -- ^ The input + -> Text -- ^ The output +transliterateCustom replacement m txt = T.unfoldr f ("", txt) + where + f ("", t) = uncurry g <$> T.uncons t + f ((x:xs), t) = Just (x, (xs, t)) + g x xs = + if isAscii x + then (x, ("", xs)) + else + case M.lookupDefault replacement x m of + "" -> ('?', ("",xs)) + (y:ys) -> (y, (ys,xs)) diff --git a/Text/Inflections/Underscore.hs b/Text/Inflections/Underscore.hs index abddba9..b965bd1 100644 --- a/Text/Inflections/Underscore.hs +++ b/Text/Inflections/Underscore.hs @@ -9,25 +9,27 @@ -- -- Conversion to phrases separated by underscores. -module Text.Inflections.Underscore ( underscore ) where +{-# LANGUAGE CPP #-} +{-# LANGUAGE OverloadedStrings #-} -import Text.Inflections.Parse.Types (Word(..)) +module Text.Inflections.Underscore + ( underscore ) +where -import Data.Char (toLower) -import Data.List (intercalate) +import Data.Text (Text) +import Text.Inflections.Parse.Types +import qualified Data.Text as T -import Prelude (String, (.), map) +#if MIN_VERSION_base(4,8,0) +import Prelude hiding (Word) +#endif --- |Turns a CamelCase string into an underscore_separated String. +-- |Turns a CamelCase string into an underscore_separated 'Text'. -- -- >>> underscore [ Word "foo", Acronym "bar", Word "bazz" ] -- "foo_bar_bazz" underscore :: [Word] -- ^ Input Words to separate with underscores - -> String -- ^ The underscored String -underscore = intercalate "_" . map toDowncasedString - --- | Transform 'Word' into a down-cased 'String'. -toDowncasedString :: Word -> String -toDowncasedString (Acronym s) = map toLower s -toDowncasedString (Word s) = map toLower s + -> Text -- ^ The underscored String +underscore = T.intercalate "_" . fmap (mapWord T.toLower) +{-# INLINE underscore #-} diff --git a/inflections.cabal b/inflections.cabal index 0e39b59..66821ae 100644 --- a/inflections.cabal +++ b/inflections.cabal @@ -50,7 +50,10 @@ library ghc-options: -O2 -Wall build-depends: base >= 4.6 && < 4.10 , containers >= 0.5 && < 0.6 + , exceptions >= 0.6 && < 0.9 , megaparsec >= 5.0 && < 6.0 + , text >= 0.2 && < 1.3 + , unordered-containers >= 0.2.7 && < 0.3 default-language: Haskell2010 test-suite test @@ -58,11 +61,11 @@ test-suite test hs-source-dirs: test main-is: Spec.hs build-depends: inflections - , base >= 4.2 && < 4.10 , QuickCheck >= 2.7.6 && < 3.0 - , containers >= 0.5 && < 0.6 + , base >= 4.2 && < 4.10 , hspec >= 2.0 && < 3.0 , megaparsec >= 5.0 && < 6.0 + , text >= 0.2 && < 1.3 if flag(dev) ghc-options: -Wall -Werror else diff --git a/test/Text/Inflections/HumanizeSpec.hs b/test/Text/Inflections/HumanizeSpec.hs index d9703a6..d804953 100644 --- a/test/Text/Inflections/HumanizeSpec.hs +++ b/test/Text/Inflections/HumanizeSpec.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE OverloadedStrings #-} + module Text.Inflections.HumanizeSpec (spec) where import Test.Hspec diff --git a/test/Text/Inflections/OrdinalSpec.hs b/test/Text/Inflections/OrdinalSpec.hs index 910eb94..bd77bac 100644 --- a/test/Text/Inflections/OrdinalSpec.hs +++ b/test/Text/Inflections/OrdinalSpec.hs @@ -1,9 +1,12 @@ -{-# LANGUAGE CPP #-} +{-# LANGUAGE CPP #-} +{-# LANGUAGE OverloadedStrings #-} module Text.Inflections.OrdinalSpec (spec) where +import Data.Text (Text) import Test.Hspec import Test.QuickCheck.Property +import qualified Data.Text as T import Text.Inflections (ordinal, ordinalize) @@ -57,11 +60,12 @@ fullOrdinals = do ordinalReturnsNotEmpty :: Spec ordinalReturnsNotEmpty = it "never returns empty" $ property $ - property <$> not . null . (ordinal :: Integer -> String) + property <$> not . T.null . (ordinal :: Integer -> Text) ordinalizeContainsTheSameNumber :: Spec ordinalizeContainsTheSameNumber = it "always returns the number as part of the result" $ property ordinalizeSamePrefix ordinalizeSamePrefix :: Integer -> Bool -ordinalizeSamePrefix n = show n == take (length $ show n) (ordinalize n) +ordinalizeSamePrefix n = T.pack s == T.take (length s) (ordinalize n) + where s = show n diff --git a/test/Text/Inflections/PropertiesSpec.hs b/test/Text/Inflections/PropertiesSpec.hs index 87cb8db..59c07d4 100644 --- a/test/Text/Inflections/PropertiesSpec.hs +++ b/test/Text/Inflections/PropertiesSpec.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE OverloadedStrings #-} + module Text.Inflections.PropertiesSpec (spec) where import Data.Char (toLower) diff --git a/test/Text/Inflections/TitleizeSpec.hs b/test/Text/Inflections/TitleizeSpec.hs index 9614e39..b1940f2 100644 --- a/test/Text/Inflections/TitleizeSpec.hs +++ b/test/Text/Inflections/TitleizeSpec.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE OverloadedStrings #-} + module Text.Inflections.TitleizeSpec (spec) where import Test.Hspec diff --git a/test/Text/Inflections/UnderscoreSpec.hs b/test/Text/Inflections/UnderscoreSpec.hs index 3181647..ecab319 100644 --- a/test/Text/Inflections/UnderscoreSpec.hs +++ b/test/Text/Inflections/UnderscoreSpec.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE OverloadedStrings #-} + module Text.Inflections.UnderscoreSpec (spec) where import Test.Hspec diff --git a/test/Text/InflectionsSpec.hs b/test/Text/InflectionsSpec.hs index dc7dda0..f3b7225 100644 --- a/test/Text/InflectionsSpec.hs +++ b/test/Text/InflectionsSpec.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE OverloadedStrings #-} + module Text.InflectionsSpec (spec) where import Test.Hspec @@ -12,14 +14,14 @@ spec = do camelCaseToSnakeCase :: Spec camelCaseToSnakeCase = it "converts camel case snake case" $ - toUnderscore "camelCasedText" `shouldBe` "camel_cased_text" + toUnderscore "camelCasedText" `shouldReturn` "camel_cased_text" camelCaseToDashed :: Spec camelCaseToDashed = it "converts camel case to dashed" $ - toDashed "camelCasedText" `shouldBe` "camel-cased-text" + toDashed "camelCasedText" `shouldReturn` "camel-cased-text" snakeCaseToCamelCase :: Spec snakeCaseToCamelCase = it "converts snake case to camel case" $ - toCamelCased False "underscored_text" `shouldBe` "underscoredText" + toCamelCased False "underscored_text" `shouldReturn` "underscoredText"