inflections 0.2.0.1 → 0.3.0.0
raw patch · 38 files changed
+1345/−804 lines, 38 filesdep +exceptionsdep +hspecdep +hspec-megaparsecdep −HUnitdep −containersdep −parsecdep ~QuickCheckdep ~basenew-uploaderPVP ok
version bump matches the API change (PVP)
Dependencies added: exceptions, hspec, hspec-megaparsec, megaparsec, text, unordered-containers
Dependencies removed: HUnit, containers, parsec, test-framework, test-framework-hunit, test-framework-quickcheck2
Dependency ranges changed: QuickCheck, base
API changes (from Hackage documentation)
- Text.Inflections: defaultMap :: Map Char String
- Text.Inflections.Parse.Types: Acronym :: String -> Word
- Text.Inflections.Parse.Types: Word :: String -> Word
- Text.Inflections.Parse.Types: data Word
- Text.Inflections.Parse.Types: instance GHC.Classes.Eq Text.Inflections.Parse.Types.Word
- Text.Inflections.Parse.Types: instance GHC.Show.Show Text.Inflections.Parse.Types.Word
- Text.Inflections.Parse.Types: mapWord :: (String -> String) -> Word -> Word
+ Text.Inflections: Acronym :: WordType
+ Text.Inflections: InflectionInvalidAcronym :: Text -> InflectionException
+ Text.Inflections: InflectionInvalidWord :: Text -> InflectionException
+ Text.Inflections: InflectionParsingFailed :: (ParseError Char Dec) -> InflectionException
+ Text.Inflections: Normal :: WordType
+ Text.Inflections: [SomeWord] :: (Transformable (Word t), Show (Word t)) => Word t -> SomeWord
+ Text.Inflections: betterThrow :: MonadThrow m => Either (ParseError Char Dec) a -> m a
+ Text.Inflections: data InflectionException
+ Text.Inflections: data SomeWord
+ Text.Inflections: data Word (t :: WordType)
+ Text.Inflections: data WordType
+ Text.Inflections: defaultTransliterations :: Transliterations
+ Text.Inflections: mkAcronym :: MonadThrow m => Text -> m (Word Acronym)
+ Text.Inflections: mkWord :: MonadThrow m => Text -> m (Word Normal)
+ Text.Inflections: unSomeWord :: (Text -> Text) -> SomeWord -> Text
+ Text.Inflections: unWord :: Word t -> Text
- Text.Inflections: camelize :: [Word] -> String
+ Text.Inflections: camelize :: [SomeWord] -> Text
- Text.Inflections: camelizeCustom :: Bool -> [Word] -> String
+ Text.Inflections: camelizeCustom :: Bool -> [SomeWord] -> Text
- Text.Inflections: dasherize :: [Word] -> String
+ Text.Inflections: dasherize :: [SomeWord] -> Text
- Text.Inflections: humanize :: [Word] -> String
+ Text.Inflections: humanize :: [SomeWord] -> Text
- Text.Inflections: ordinal :: Integer -> String
+ Text.Inflections: ordinal :: Integral a => a -> Text
- Text.Inflections: ordinalize :: Integer -> String
+ Text.Inflections: ordinalize :: (Integral a, Show a) => a -> Text
- Text.Inflections: parameterize :: String -> String
+ Text.Inflections: parameterize :: Text -> Text
- Text.Inflections: parameterizeCustom :: Transliterations -> String -> String
+ Text.Inflections: parameterizeCustom :: Transliterations -> Text -> Text
- Text.Inflections: parseCamelCase :: [String] -> String -> Either ParseError [Word]
+ Text.Inflections: parseCamelCase :: (Foldable f, Functor f) => f (Word Acronym) -> Text -> Either (ParseError Char Dec) [SomeWord]
- Text.Inflections: parseSnakeCase :: [String] -> String -> Either ParseError [Word]
+ Text.Inflections: parseSnakeCase :: (Foldable f, Functor f) => f (Word Acronym) -> Text -> Either (ParseError Char Dec) [SomeWord]
- Text.Inflections: titleize :: [Word] -> String
+ Text.Inflections: titleize :: [SomeWord] -> Text
- Text.Inflections: toCamelCased :: Bool -> String -> String
+ Text.Inflections: toCamelCased :: Bool -> Text -> Either (ParseError Char Dec) Text
- Text.Inflections: toDashed :: String -> String
+ Text.Inflections: toDashed :: Text -> Either (ParseError Char Dec) Text
- Text.Inflections: toUnderscore :: String -> String
+ Text.Inflections: toUnderscore :: Text -> Either (ParseError Char Dec) Text
- Text.Inflections: transliterate :: String -> String
+ Text.Inflections: transliterate :: Text -> Text
- Text.Inflections: transliterateCustom :: String -> Transliterations -> String -> String
+ Text.Inflections: transliterateCustom :: String -> Transliterations -> Text -> Text
- Text.Inflections: type Transliterations = Map Char String
+ Text.Inflections: type Transliterations = HashMap Char String
- Text.Inflections: underscore :: [Word] -> String
+ Text.Inflections: underscore :: [SomeWord] -> Text
Files
- CHANGELOG.md +97/−0
- README.md +73/−0
- Text/Inflections.hs +168/−148
- Text/Inflections/Camelize.hs +45/−24
- Text/Inflections/Dasherize.hs +27/−12
- Text/Inflections/Data.hs +27/−5
- Text/Inflections/Humanize.hs +37/−19
- Text/Inflections/Ordinal.hs +40/−8
- Text/Inflections/Parameterize.hs +41/−83
- Text/Inflections/Parse/Acronym.hs +0/−16
- Text/Inflections/Parse/CamelCase.hs +66/−19
- Text/Inflections/Parse/Parameterizable.hs +0/−42
- Text/Inflections/Parse/SnakeCase.hs +55/−18
- Text/Inflections/Parse/Types.hs +0/−18
- Text/Inflections/Titleize.hs +26/−14
- Text/Inflections/Transliterate.hs +46/−22
- Text/Inflections/Types.hs +143/−0
- Text/Inflections/Underscore.hs +27/−14
- inflections.cabal +57/−43
- test/Spec.hs +1/−0
- test/Suite.hs +0/−18
- test/Text/Inflections/DasherizeSpec.hs +21/−0
- test/Text/Inflections/HumanizeSpec.hs +27/−0
- test/Text/Inflections/HumanizeTest.hs +0/−28
- test/Text/Inflections/OrdinalSpec.hs +42/−0
- test/Text/Inflections/OrdinalTest.hs +0/−65
- test/Text/Inflections/ParametrizeSpec.hs +39/−0
- test/Text/Inflections/Parse/CamelCaseSpec.hs +47/−0
- test/Text/Inflections/Parse/SnakeCaseSpec.hs +35/−0
- test/Text/Inflections/Tests.hs +0/−107
- test/Text/Inflections/TitleizeSpec.hs +21/−0
- test/Text/Inflections/TitleizeTest.hs +0/−28
- test/Text/Inflections/TransliterateSpec.hs +29/−0
- test/Text/Inflections/TypesSpec.hs +51/−0
- test/Text/Inflections/UnderscoreSpec.hs +18/−0
- test/Text/Inflections/UnderscoreTest.hs +0/−21
- test/Text/InflectionsSpec.hs +39/−0
- test/Text/InflectionsTest.hs +0/−32
+ CHANGELOG.md view
@@ -0,0 +1,97 @@+## Inflections 0.3.0.0++* A more type-safe API forbidding creation of invalid words.++* Made the API use `Text` instead of `String` (which significally improved+ speed).++* Switched to Megaparsec 5 for parsing.++* Renamed `defaultMap` to `defaultTransliterations`.++* Words now can contain digits (recognized by all parsers).++* `parseSnakeCase` now is not confused when a word happens to have prefix+ coinciding with an acronym. This is harder to fix for `parseCamelCase`+ because acronym may contain capital letters, so old behavior is preserved+ for `parseCamelCase` for now.++* `parseCamelCase` and `parseSnakeCase` take any instance of `Foldable` as a+ collection of acronyms, not just lists.++* Added the `CHANGELOG.md` file.++* Switched test suite to Hspec.++* The `toUnderscore`, `toDashed`, and `toCamelCased` are not partial+ anymore. They return parse error in `Left` just like parsing functions,+ but this result can be lifted into any instance of `MonadThrow` with+ `betterThrow` helper.++* Improved documentation.++## Inflections 0.2.0.1++* Support for GHC 8.0.++## Inflections 0.2.0.0++* Added `other-modules` to test suite.++## Inflections 0.1.0.10++* Support for GHC 7.10.++## Inflections 0.1.0.9++* Support for GHC 7.8.++## Inflections 0.1.0.8++* Fixed a typo in docs of `humanize`.++* Added `toUnderscore`, `toDashed`, and `toCamelCased`.++## Inflections 0.1.0.7++* Support for `base-4.7`.++* Improved documentation.++## Inflections 0.1.0.6++* Added `titleize` and `humanize`.++* Improved documentation.++## Inflections 0.1.0.5++* Added module documentation for `Text.Inflections`.++## Inflections 0.1.0.4++* Reduced number of public modules to one: `Text.Inflections`.++* Added `ordinal` and `ordinalize`.++* Improved documentation.++## Inflections 0.1.0.3++* Added `camelize`, `camelizeCustom`, `underscore`, and `underscoreCustom`.++* Made the word parser accept empty input.++* Improved documentation.++## Inflections 0.1.0.2++* Added the `transliterate` and `transliterateCustom` functions.++## Inflections 0.1.0.1++* No changes.++## Inflections 0.1.0.0++* Initial release.
+ README.md view
@@ -0,0 +1,73 @@+# String Inflections for Haskell++[](http://opensource.org/licenses/MIT)+[](http://hackage.haskell.org/package/inflections)+[](http://stackage.org/nightly/package/inflections)+[](http://stackage.org/lts/package/inflections)+[](https://travis-ci.org/stackbuilders/inflections-hs)++This library is a partial port of the+[String Inflector](http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html)+from Ruby on Rails. It currently implements methods such as `parameterize`,+`transliterate`, `camelize`, `underscore` and `dasherize`. Please see the+haddock documentation for a complete list of the functions implemented by this+library.++Unlike the ActiveSupport (Rails) and Ember implementations of inflections, this+library uses a parser to verify the input to functions like `camelize`. This is+done to ensure that the output strings adhere to the syntax that they are+supposed to generate. You can read more about the philosophy behind this library+in the+[Haddock documentation](http://hackage.haskell.org/package/inflections/docs/Text-Inflections.html).++## Usage++The following examples demonstrate usage of the `parameterize`, `transliterate`+and `camelize` functions:++```haskell+λ: parameterize "Hola. ¿Cómo estás?"+"hola-como-estas"++λ: transliterate "Hola. ¿Cómo estás?"+"Hola. ?Como estas?"++λ: import Control.Monad (liftM)+λ: liftM camelize $ parseSnakeCase "hey_there"+"HeyThere"+```++## Customization++Part of parameterizing strings is approximating all characters in the input+encoding to ASCII characters. This library copies the character approximation+table from the Ruby i18n library. This data structure is provided as+`defaultCharacterTransliterations`. You can provide your own transliteration map+by passing a Map structure (from Data.Map) to the `parameterizeCustom` function.++If you want to specify a custom default replacement or approximation table for+the `transliterate` function, you should instead call the `transliterateCustom`+function which accepts a String for replacements and a Map for substitution.++## TODO++I'd like this library to implement other functions found in the Rails+inflections library. If you need one of those functions, please submit a pull+request!++## Further documentation++For more information, please see the the+[Haddock docs for this module](http://hackage.haskell.org/package/inflections/docs/Text-Inflections.html).++## Author++Justin Leitgeb <justin@stackbuilders.com>++## Contributing++You may submit pull requests to this repository on GitHub. Tests are appreciated with your contribution.++## License++MIT - see [the LICENSE file](LICENSE).
Text/Inflections.hs view
@@ -1,160 +1,180 @@-{- |-Module : Text.Inflections-Description : Rails-like inflections library for common String transformations.-Copyright : (c) Justin Leitgeb-License : MIT--Maintainer : justin@stackbuilders.com-Stability : unstable-Portability : portable--This module provides methods for common String transformations, similar to the-"Inflections" library found in Rails:--<http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html>--While many of the functions in this library are the same as in implementations-in Rails' ActiveSupport, the philosophy of this library is fundamentally-different. Where Rails tries to be as permissive as possible, and return a-String when given any input, this library tries to output strings that make-sense according to the function that is called.--When you look closely at many of the functions in Rails' inflections-library, you will notice that many of them are partial. That is, they only-have well-defined output for some of the possible inputs to the function allowed-by the type system. As an example, let's take the @underscore@ function. In-Rails, it works like this:-->>> "fooBar".underscore-"foo_bar"--Looks ok so far. However, it's also easy to produce less expected results:-->>> "foo bar".underscore-"foo bar"--The output isn't underscored - it contains a space! It turns out that some of-the functions from Inflections in ActiveSupport are /partial/. Ie., the outputs-are really only specified for a certain range of the inputs allowed by the-String type.--In the Haskell inflections library, we aim to deliver more predictable results-by separating the parsing of strings into tokens from the application of-transformations. Let's see an example.--First, we tokenize an underscored String using 'parseSnakeCase':-->>> parseSnakeCase [] "foo_bar"-Right [Word "foo",Word "bar"]--We can chain together the tokenization of the input String and the-transformation to CamelCase by using 'Control.Monad.LiftM':-->>> import Control.Monad (liftM)->>> liftM camelize $ parseSnakeCase "foo_bar"--By separating out the tokenization from the application of inflections, we also-end up with useful libraries for validating input which can be used-independently:-->>> parseSnakeCase [] "fooBar"-Left "(unknown)" (line 1, column 4):-unexpected 'B'-expecting lowercase letter, "_" or end of input--This library is still a work-in-progress, and contributions are welcome for-missing pieces and to fix bugs. Please see the Github page to contribute with-code or bug reports:--<https://github.com/stackbuilders/inflections-hs>+-- |+-- Module : Text.Inflections+-- Description : Rails-like inflections library for common Text transformations.+-- Copyright : (c) Justin Leitgeb+-- License : MIT+--+-- Maintainer : justin@stackbuilders.com+-- Stability : unstable+-- Portability : portable+--+-- This module provides methods for common 'Text' transformations, similar+-- to the Inflections library found in Rails:+--+-- <http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html>+--+-- While many of the functions in this library are the same as in+-- implementations in Rails' ActiveSupport, the philosophy of this library+-- is fundamentally different. Where Rails tries to be as permissive as+-- possible, and return a String when given any input, this library tries to+-- output 'Text' that makes sense according to the function that is called.+--+-- When you look closely at many of the functions in Rails' inflections+-- library, you will notice that many of them are partial. That is, they+-- only have well-defined output for some of the possible inputs to the+-- function allowed by the type system. As an example, let's take the+-- @underscore@ function. In Rails, it works like this:+--+-- >>> "fooBar".underscore+-- "foo_bar"+--+-- Looks OK so far. However, it's also easy to produce less expected results:+--+-- >>> "foo bar".underscore+-- "foo bar"+--+-- The output isn't underscored — it contains a space! It turns out that+-- some of the functions from Inflections in ActiveSupport are /partial/.+-- I.e., the outputs are really only specified for a certain range of the+-- inputs allowed by the String type.+--+-- In the Haskell inflections library, we aim to deliver more predictable+-- results by separating the parsing of strings into tokens from the+-- application of transformations. Let's see an example.+--+-- First, we tokenize an underscored 'Text' using 'parseSnakeCase':+--+-- >>> parseSnakeCase [] "foo_bar"+-- Right [Word "foo",Word "bar"]+--+-- We can chain together the tokenization of the input String and the+-- transformation to CamelCase by using 'fmap':+--+-- >>> camelize <$> parseSnakeCase [] "foo_bar"+-- Right "FooBar"+--+-- By separating out the tokenization from the application of inflections,+-- we also end up with useful libraries for validating input which can be+-- used independently:+--+-- >>> parseSnakeCase [] "fooBar"+-- 1:4:+-- unexpected 'B'+-- expecting '_', end of input, or lowercase letter+--+-- As of version 0.3.0.0, we don't permit creation of invalid 'Word's by+-- using of the smart constructors 'mkWord' and 'mkAcronym'. This is done+-- because not every 'Text' value is a valid 'Word', as it should not+-- contain whitespace, for example. Normal words have the type @'Word'+-- 'Normal'@, while acronyms have the type @'Word' 'Acronym'@. If you need+-- to have several words\/acronyms in a single list, use the existential+-- wrapper 'SomeWord'. Parsing functions now produce 'SomeWord's.+--+-- This library is still a work-in-progress, and contributions are welcome+-- for missing pieces and to fix bugs. Please see the Github page to+-- contribute with code or bug reports:+--+-- <https://github.com/stackbuilders/inflections-hs> --}+{-# LANGUAGE CPP #-} module Text.Inflections- ( camelize- , camelizeCustom-- , dasherize-- , humanize-- , underscore-- , titleize-- , defaultMap-- , parameterize- , parameterizeCustom-- , transliterate- , transliterateCustom-- , ordinal- , ordinalize-- , parseSnakeCase- , parseCamelCase- , Transliterations+ ( -- * Types and helpers+ Word+ , WordType (..)+ , mkWord+ , mkAcronym+ , unWord+ , SomeWord (..)+ , unSomeWord+ , InflectionException (..)+ -- * Parsing+ , parseSnakeCase+ , parseCamelCase+ -- * Rendering+ , camelize+ , camelizeCustom+ , dasherize+ , humanize+ , underscore+ , titleize+ , Transliterations+ , defaultTransliterations+ , parameterize+ , parameterizeCustom+ , transliterate+ , transliterateCustom+ , ordinalize+ , ordinal -- * Often used combinators- , toUnderscore- , toDashed- , toCamelCased- )+ , toUnderscore+ , toDashed+ , toCamelCased+ , betterThrow ) where --import Text.Inflections.Data ( defaultMap )--import Text.Inflections.Parameterize ( Transliterations- , 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 Control.Monad.Catch (MonadThrow (..))+import Data.Text (Text)+import Text.Inflections.Camelize (camelize, camelizeCustom)+import Text.Inflections.Dasherize (dasherize)+import Text.Inflections.Data (Transliterations, defaultTransliterations)+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.Titleize (titleize)+import Text.Inflections.Transliterate (transliterate, transliterateCustom)+import Text.Inflections.Types+import Text.Inflections.Underscore (underscore)+import Text.Megaparsec -import Data.Char ( toLower )+#if MIN_VERSION_base(4,8,0)+import Prelude hiding (Word)+#endif --- | Transforms CamelCasedString to--- snake_cased_string_with_underscores. Throws exception if parsing failed-toUnderscore :: String -> String-toUnderscore =- underscore- . either (error . ("toUnderscore: " ++) . show) id- . parseCamelCase []+-- | Transforms CamelCasedString to snake_cased_string_with_underscores.+--+-- > toUnderscore = fmap underscore . parseCamelCase []+--+-- >>> toUnderscore "FooBarBazz"+-- "foo_bar_bazz"+toUnderscore :: Text -> Either (ParseError Char Dec) Text+toUnderscore = fmap underscore . parseCamelCase [] --- | Transforms CamelCasedString to snake-cased-string-with-dashes. Throws--- exception if parsing failed.-toDashed :: String -> String-toDashed =- dasherize- . map (mapWord (map toLower))- . either (error . ("toDashed: " ++) . show) id- . parseCamelCase []+-- | Transforms CamelCasedString to snake-cased-string-with-dashes.+--+-- > toDashed = fmap dasherize . parseCamelCase []+--+-- >>> toDashed "FooBarBazz"+-- "foo-bar-bazz"+toDashed :: Text -> Either (ParseError Char Dec) Text+toDashed = fmap dasherize . parseCamelCase [] -- | 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-toCamelCased :: Bool -> String -> String-toCamelCased t =- camelizeCustom t- . either (error . ("toCamelCased: " ++) . show) id- . parseSnakeCase []+-- 'False' then firstCharacter will be in lower case.+--+-- > toCamelCased t = fmap (camelizeCustom t) . parseSnakeCase []+--+-- >>> toCamelCased True "foo_bar_bazz"+-- "FooBarBazz"+-- >>> toCamelCased False "foo_bar_bazz"+-- "fooBarBazz"+toCamelCased+ :: Bool -- ^ Capitalize the first character+ -> Text -- ^ Input+ -> Either (ParseError Char Dec) Text -- ^ Ouput+toCamelCased t = fmap (camelizeCustom t) . parseSnakeCase []++-- | Lift something of type @'Either' ('ParseError' 'Char' 'Dec') a@ to+-- an instance of 'MonadThrow'. Useful when you want to shortcut on parsing+-- failures and you're in an instance of 'MonadThrow'.+--+-- This throws 'InflectionParsingFailed' if given value is inside 'Left'.+--+-- /since 0.3.0.0/++betterThrow :: MonadThrow m => Either (ParseError Char Dec) a -> m a+betterThrow (Left err) = throwM (InflectionParsingFailed err)+betterThrow (Right x) = return x
Text/Inflections/Camelize.hs view
@@ -1,32 +1,53 @@-module Text.Inflections.Camelize ( camelize, camelizeCustom ) where+-- |+-- Module : Text.Inflections.Camelize+-- Copyright : © 2016 Justin Leitgeb+-- License : MIT+--+-- Maintainer : Justin Leitgeb <justin@stackbuilders.com>+-- Stability : experimental+-- Portability : portable+--+-- Conversion to CamelCased phrases. -import Text.Inflections.Parse.Types (Word(..))+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-} -import Data.Char (toUpper, toLower)+module Text.Inflections.Camelize+ ( camelize+ , camelizeCustom )+where -import Prelude (String, Bool(..), concatMap, (.), zip, ($), repeat)+import Data.Text (Text)+import Text.Inflections.Types+import qualified Data.Text as T --- |Turns a an input Word List in into CamelCase. Returns the CamelCase String.+#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif++-- | Turn an input word list in into CamelCase.+--+-- >>> foo <- SomeWord <$> mkWord "foo"+-- >>> bar <- SomeWord <$> mkAcronym "bar"+-- >>> bazz <- SomeWord <$> mkWord "bazz"+-- >>> camelize [foo,bar,bazz]+-- "FoobarBazz" camelize- :: [Word] -- ^ Input Words to separate with underscores- -> String -- ^ The camelized String+ :: [SomeWord] -- ^ Input words+ -> Text -- ^ The camelized 'Text' camelize = camelizeCustom True --- |Turns an input Word List into a CamelCase String.+-- | Turn an input word list into a CamelCase String.+--+-- >>> foo <- SomeWord <$> mkWord "foo"+-- >>> bar <- SomeWord <$> mkAcronym "bar"+-- >>> bazz <- SomeWord <$> mkWord "bazz"+-- >>> camelizeCustom False [foo,bar,bazz]+-- "foobarBazz" 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+ :: Bool -- ^ Whether to capitalize the first character in the output String+ -> [SomeWord] -- ^ The input Words+ -> Text -- ^ The camelized 'Text'+camelizeCustom _ [] = ""+camelizeCustom c (x:xs) = T.concat $+ unSomeWord (if c then T.toTitle else T.toLower) x : (unSomeWord T.toTitle <$> xs)
Text/Inflections/Dasherize.hs view
@@ -1,17 +1,32 @@-module Text.Inflections.Dasherize ( dasherize ) where+-- |+-- Module : Text.Inflections.Dasherize+-- Copyright : © 2016 Justin Leitgeb+-- License : MIT+--+-- Maintainer : Justin Leitgeb <justin@stackbuilders.com>+-- Stability : experimental+-- Portability : portable+--+-- Conversion to dasherized phrases. -import Text.Inflections.Parse.Types (Word(..))+{-# LANGUAGE OverloadedStrings #-} -import Data.List (intercalate)+module Text.Inflections.Dasherize+ ( dasherize )+where -import Prelude (String, (.), map)+import Data.Text (Text)+import Text.Inflections.Types+import qualified Data.Text as T --- | Replaces underscores in a snake_cased string with dashes (hyphens).+-- | Produce a string with words separated by dashes (hyphens).+--+-- >>> foo <- SomeWord <$> mkWord "foo"+-- >>> bar <- SomeWord <$> mkAcronym "bar"+-- >>> bazz <- SomeWord <$> mkWord "bazz"+-- >>> dasherize [foo,bar,bazz]+-- "foo-bar-bazz" dasherize- :: [Word] -- ^ Input Words to separate with dashes- -> String -- ^ The dasherized String-dasherize = intercalate "-" . map toString--toString :: Word -> String-toString (Acronym s) = s-toString (Word s) = s+ :: [SomeWord] -- ^ Input words to separate with dashes+ -> Text -- ^ The dasherized 'Text'+dasherize = T.intercalate "-" . fmap (unSomeWord T.toLower)
Text/Inflections/Data.hs view
@@ -1,11 +1,33 @@-module Text.Inflections.Data where+-- |+-- Module : Text.Inflections.Data+-- Copyright : © 2016 Justin Leitgeb+-- License : MIT+--+-- Maintainer : Justin Leitgeb <justin@stackbuilders.com>+-- Stability : experimental+-- Portability : portable+--+-- Auxiliary data used in the library. -import Data.Map (Map, fromList)+module Text.Inflections.Data+ ( Transliterations+ , defaultTransliterations )+where --- |These default transliterations stolen from the Ruby i18n library - see+import Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as M++-- | A 'HashMap' containing mappings from international characters to+-- sequences approximating these characters within the ASCII range.+type Transliterations = HashMap Char String++-- | These default transliterations are stolen from the Ruby i18n library -+-- see -- <https://github.com/svenfuchs/i18n/blob/master/lib/i18n/backend/transliterator.rb#L41:L69>.-defaultMap :: Map Char String-defaultMap = fromList [+--+-- NOTE: before version 0.3.0.0 this was called @defaultMap@.+defaultTransliterations :: Transliterations+defaultTransliterations = M.fromList [ ('À', "A"), ('Á', "A"), ('Â', "A"), ('Ã', "A"), ('Ä', "A"), ('Å', "A"), ('Æ', "AE"), ('Ç', "C"), ('È', "E"), ('É', "E"), ('Ê', "E"), ('Ë', "E"), ('Ì', "I"), ('Í', "I"), ('Î', "I"), ('Ï', "I"), ('Ð', "D"), ('Ñ', "N"),
Text/Inflections/Humanize.hs view
@@ -1,24 +1,42 @@-module Text.Inflections.Humanize (humanize) where--import Text.Inflections.Parse.Types (Word(..))+-- |+-- Module : Text.Inflections.Humanize+-- Copyright : © 2016 Justin Leitgeb+-- License : MIT+--+-- Maintainer : Justin Leitgeb <justin@stackbuilders.com>+-- Stability : experimental+-- Portability : portable+--+-- Conversion to “humanized” phrases. -import Data.Char (toUpper)+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-} -import Prelude (String, Bool(..), (.), map, zip, ($), unwords, repeat)+module Text.Inflections.Humanize+ ( humanize )+where --- |Capitalizes the first word and turns underscores into spaces. Like titleize,--- this is meant for creating pretty output.-humanize- :: [Word] -- ^ List of Words, first of which will be capitalized- -> String -- ^ The humanized output-humanize = unwords . map caseForWord . isFirstList+import Data.Text (Text)+import Text.Inflections.Types+import qualified Data.Text as T --- |Returns list with Bool indicating if an element is first.-isFirstList :: [a] -> [(a, Bool)]-isFirstList xs = zip xs $ True : repeat False+#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif -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+-- | Capitalize the first word and separate words with spaces. Like+-- 'Text.Inflections.Titleize.titleize', this is meant for creating pretty+-- output.+--+-- >>> foo <- SomeWord <$> mkWord "foo"+-- >>> bar <- SomeWord <$> mkAcronym "bar"+-- >>> bazz <- SomeWord <$> mkWord "bazz"+-- >>> humanize [foo,bar,bazz]+-- "Foo bar bazz"+humanize+ :: [SomeWord] -- ^ List of words, first of which will be capitalized+ -> Text -- ^ The humanized output+humanize xs' =+ case unSomeWord (T.replace "_" " ") <$> xs' of+ [] -> ""+ (x:xs) -> T.unwords $ T.toTitle x : (T.toLower <$> xs)
Text/Inflections/Ordinal.hs view
@@ -1,9 +1,47 @@-module Text.Inflections.Ordinal (ordinal, ordinalize)+-- |+-- Module : Text.Inflections.Ordinal+-- Copyright : © 2016 Justin Leitgeb+-- License : MIT+--+-- Maintainer : Justin Leitgeb <justin@stackbuilders.com>+-- Stability : experimental+-- Portability : portable+--+-- Conversion to spelled ordinal numbers.++{-# LANGUAGE OverloadedStrings #-}++module Text.Inflections.Ordinal+ ( ordinalize+ , ordinal ) where +import Data.Monoid ((<>))+import Data.Text (Text)+import qualified Data.Text as T++-- |Turns a number into an ordinal string used to denote the position in an+-- ordered sequence such as 1st, 2nd, 3rd, 4th.+--+-- >>> ordinalize 1+-- "1st"+-- >>> ordinalize 2+-- "2nd"+-- >>> ordinalize 10+-- "10th"+ordinalize :: (Integral a, Show a) => a -> Text+ordinalize n = T.pack (show n) <> ordinal n+ -- |Returns the suffix that should be added to a number to denote the position -- in an ordered sequence such as 1st, 2nd, 3rd, 4th.-ordinal :: Integer -> String+--+-- >>> ordinal 1+-- "st"+-- >>> ordinal 2+-- "nd"+-- >>> ordinal 10+-- "th"+ordinal :: Integral a => a -> Text ordinal number | remainder100 `elem` [11..13] = "th" | remainder10 == 1 = "st"@@ -13,9 +51,3 @@ where abs_number = abs number remainder10 = abs_number `mod` 10 remainder100 = abs_number `mod` 100---- |Turns a number into an ordinal string used to denote the position in an--- ordered sequence such as 1st, 2nd, 3rd, 4th.-ordinalize :: Integer -> String-ordinalize n = show n ++ ordinal n-
Text/Inflections/Parameterize.hs view
@@ -1,90 +1,48 @@+-- |+-- Module : Text.Inflections.Parametrize+-- Copyright : © 2016 Justin Leitgeb+-- License : MIT+--+-- Maintainer : Justin Leitgeb <justin@stackbuilders.com>+-- Stability : experimental+-- Portability : portable+--+-- Parametrization for strings, useful for transliteration.++{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+ module Text.Inflections.Parameterize ( parameterize- , parameterizeCustom- , Transliterations )+ , parameterizeCustom ) where -import qualified Data.Map as Map-import Control.Monad (guard)-import Data.Maybe (mapMaybe)--import Data.Char (toLower)--import Data.List (group)-import qualified Text.Parsec as P--import Text.Inflections.Data (defaultMap)-import Text.Inflections.Parse.Parameterizable ( PChar(..)- , parser- , isValidParamChar )---- |A 'Data.Map.Map' containing mappings from international characters to--- sequences approximating these characters within the ASCII range.-type Transliterations = Map.Map Char String---- |Replaces special characters in a string so that it may be used as part of a--- 'pretty' URL. Uses the default transliterations in this library-parameterize :: String -> String-parameterize = parameterizeCustom defaultMap---- |Transliterate a String with a custom transliteration table.-parameterizeCustom :: Transliterations -> String -> String-parameterizeCustom ts s =- case parsed of- Right ast -> (concatMap pCharToC . squeezeSeparators .- trimUnwanted wanted . mapMaybe (parameterizeChar ts))- ast-- -- Note that this should never fail, since we accommodate all Unicode- -- characters as valid input.- Left err -> fail $ "Parse failed, please report a bug! Error: " ++- show err-- where parsed = P.parse parser "" s- wanted :: [PChar] -- All valid URL chars - we shouldn't trim these.- wanted = Underscore :- map (Acceptable . (: [])) (['a'..'z'] ++ ['0'..'9'])---- |Look up character in transliteration list. Accepts a Transliteration map--- which has Chars as keys and Strings as values for approximating common--- international Unicode characters within the ASCII range.-transliteratePCharCustom :: Transliterations -> Char -> Maybe PChar-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- guard (all isValidParamChar v)- return (Acceptable v)---- |Given a Transliteration table and a PChar, returns Maybe PChar indicating--- how this character should appear in a URL.-parameterizeChar :: Transliterations -> PChar -> Maybe PChar-parameterizeChar _ (UCase c) = Just $ Acceptable [toLower c]-parameterizeChar _ (Acceptable c) = Just $ Acceptable c-parameterizeChar _ Separator = Just Separator-parameterizeChar _ Underscore = Just Underscore-parameterizeChar _ (OtherAscii _) = Just Separator-parameterizeChar ts (NonAscii c) = transliteratePCharCustom ts c+import Data.Char (isAscii, isAlphaNum, isPunctuation, toLower)+import Data.Text (Text)+import Text.Inflections.Data+import qualified Data.HashMap.Strict as M+import qualified Data.Text as T --- |Turns PChar tokens into their String representation.-pCharToC :: PChar -> String-pCharToC (UCase c) = [c]-pCharToC (Acceptable str) = str-pCharToC Separator = "-"-pCharToC Underscore = "_"-pCharToC (OtherAscii c) = [c]-pCharToC (NonAscii c) = [c]+#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif --- |Reduce sequences of separators down to only one separator.-squeezeSeparators :: [PChar] -> [PChar]-squeezeSeparators ps = concatMap squashSeparatorGroup $ group ps- where squashSeparatorGroup g = case head g of- Separator -> [Separator] -- only take head- _ -> g -- don't change+-- | Replace special characters in a string so that it may be used as part+-- of a 'pretty' URL. Uses the 'defaultTransliterations'.+parameterize :: Text -> Text+parameterize = parameterizeCustom defaultTransliterations+{-# INLINE parameterize #-} --- |Trim non-wanted elements from the beginning and end of list.-trimUnwanted :: Eq a => [a] -> [a] -> [a]-trimUnwanted wanted = dropWhile notWanted . reverse . dropWhile notWanted- . reverse- where notWanted = (`notElem` wanted)+-- | Transliterate 'Text' with a custom transliteration table.+parameterizeCustom :: Transliterations -> Text -> Text+parameterizeCustom m txt = (T.intercalate "-" . T.words) (T.unfoldr f ("", txt))+ where+ f ("", t) = uncurry g <$> T.uncons t+ f (x:xs, t) = Just (x, (xs, t))+ g x xs+ | (isAscii x && isAlphaNum x) || x == '_' = (toLower x, ("", xs))+ | isPunctuation x = (' ', ("", xs))+ | otherwise =+ case toLower <$> M.lookupDefault " " x m of+ "" -> (' ', ("",xs))+ (y:ys) -> (y, (ys,xs))
− Text/Inflections/Parse/Acronym.hs
@@ -1,16 +0,0 @@-{-# LANGUAGE FlexibleContexts, NoMonomorphismRestriction #-}--module Text.Inflections.Parse.Acronym ( acronym ) where--import qualified Text.ParserCombinators.Parsec.Char as C-import qualified Text.Parsec as P-import qualified Text.Parsec.Prim as Prim--import Text.Inflections.Parse.Types--import Control.Applicative ((<$>))--import Prelude (Char, String, (.), map)--acronym :: P.Stream s m Char => [String] -> P.ParsecT s u m Word-acronym as = Acronym <$> P.choice (map (Prim.try . C.string) as)
Text/Inflections/Parse/CamelCase.hs view
@@ -1,28 +1,75 @@-{-# LANGUAGE FlexibleContexts, NoMonomorphismRestriction #-}+-- |+-- Module : Text.Inflections.Parse.CamelCase+-- Copyright : © 2016 Justin Leitgeb+-- License : MIT+--+-- Maintainer : Justin Leitgeb <justin@stackbuilders.com>+-- Stability : experimental+-- Portability : portable+--+-- Parser for camel case “symbols”. -module Text.Inflections.Parse.CamelCase ( parseCamelCase )-where+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-} -import Text.Parsec+module Text.Inflections.Parse.CamelCase+ ( parseCamelCase )+where -import Text.Inflections.Parse.Types (Word(..))-import Text.Inflections.Parse.Acronym (acronym)+import Control.Applicative+import Data.Text (Text)+import Text.Inflections.Types+import Text.Megaparsec+import Text.Megaparsec.Text+import qualified Data.Text as T -import Prelude (Char, String, Either, return, ($))+#if MIN_VERSION_base(4,8,0)+import Prelude hiding (Word)+#else+import Data.Foldable+import Prelude hiding (elem)+#endif -parseCamelCase :: [String] -> String -> Either ParseError [Word]-parseCamelCase acronyms = parse (parser acronyms) "(unknown)"+-- | Parse a CamelCase string.+--+-- >>> bar <- mkAcronym "bar"+-- >>> parseCamelCase [bar] "FooBarBazz"+-- Right [Word "Foo",Acronym "Bar",Word "Bazz"]+--+-- >>> parseCamelCase [] "foo_bar_bazz"+-- 1:4:+-- unexpected '_'+-- expecting end of input, lowercase letter, or uppercase letter+parseCamelCase :: (Foldable f, Functor f)+ => f (Word 'Acronym) -- ^ Collection of acronyms+ -> Text -- ^ Input+ -> Either (ParseError Char Dec) [SomeWord] -- ^ Result of parsing+parseCamelCase acronyms = parse (parser acronyms) "" +parser :: (Foldable f, Functor f)+ => f (Word 'Acronym) -- ^ Collection of acronyms+ -> Parser [SomeWord] -- ^ CamelCase parser+parser acronyms = many (a <|> n) <* eof+ where+ n = SomeWord <$> word+ a = SomeWord <$> acronym acronyms --- |Recognizes an input String in CamelCase.-parser :: Stream s m Char => [String] -> ParsecT s u m [Word]-parser acronyms = do- ws <- many $ choice [ acronym acronyms, word ]- eof- return ws+acronym :: (Foldable f, Functor f)+ => f (Word 'Acronym)+ -> Parser (Word 'Acronym)+acronym acronyms = do+ x <- T.pack <$> choice (string . T.unpack . unWord <$> acronyms)+ case mkAcronym x of+ Nothing -> empty -- cannot happen if the system is sound+ Just acr -> return acr+{-# INLINE acronym #-} -word :: Stream s m Char => ParsecT s u m Word+word :: Parser (Word 'Normal) word = do- firstChar <- upper <|> lower- restChars <- many lower- return $ Word $ firstChar : restChars+ firstChar <- upperChar <|> lowerChar+ restChars <- many $ lowerChar <|> digitChar+ case (mkWord . T.pack) (firstChar : restChars) of+ Nothing -> empty -- cannot happen if the system is sound+ Just wrd -> return wrd+{-# INLINE word #-}
− Text/Inflections/Parse/Parameterizable.hs
@@ -1,42 +0,0 @@-{-# LANGUAGE FlexibleContexts, NoMonomorphismRestriction #-}--module Text.Inflections.Parse.Parameterizable- ( parser- , isValidParamChar- , PChar(..) )-where--import Data.Char (isAsciiLower, isAsciiUpper, isAscii, isDigit)-import Control.Applicative-import qualified Text.Parsec as P-import qualified Text.ParserCombinators.Parsec.Char as C--data PChar = UCase Char- -- Since some of the transliterating approximations expand from- -- one Unicode to two ASCII chars (eg., œ to oe), we represent- -- this as a String.- | Acceptable String- | Separator- | Underscore- | OtherAscii Char- | NonAscii Char- deriving (Eq, Show)---- |Matches 'acceptable' characters for parameterization purposes.-acceptableParser :: P.Stream s m Char => P.ParsecT s u m PChar-acceptableParser = do- c <- C.satisfy isValidParamChar- return $ Acceptable [c]--parser :: P.Stream s m Char => P.ParsecT s u m [PChar]-parser = P.many $ P.choice [- acceptableParser- , UCase <$> C.satisfy isAsciiUpper- , Separator <$ C.char '-'- , Underscore <$ C.char '_'- , OtherAscii <$> C.satisfy isAscii- , NonAscii <$> C.satisfy (not . isAscii)- ]--isValidParamChar :: Char -> Bool-isValidParamChar c = isAsciiLower c || isDigit c
Text/Inflections/Parse/SnakeCase.hs view
@@ -1,25 +1,62 @@-{-# LANGUAGE FlexibleContexts, NoMonomorphismRestriction #-}--module Text.Inflections.Parse.SnakeCase ( parseSnakeCase )-where+-- |+-- Module : Text.Inflections.Parse.SnakeCase+-- Copyright : © 2016 Justin Leitgeb+-- License : MIT+--+-- Maintainer : Justin Leitgeb <justin@stackbuilders.com>+-- Stability : experimental+-- Portability : portable+--+-- Parser for snake case “symbols”. -import Control.Applicative ((<$>))-import Text.Parsec+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-} -import Text.Inflections.Parse.Types (Word(..))-import Text.Inflections.Parse.Acronym (acronym)+module Text.Inflections.Parse.SnakeCase+ ( parseSnakeCase )+where -import Prelude (Char, String, Either, return)+import Control.Applicative+import Data.Text (Text)+import Text.Inflections.Types+import Text.Megaparsec+import Text.Megaparsec.Text+import qualified Data.Text as T -parseSnakeCase :: [String] -> String -> Either ParseError [Word]-parseSnakeCase acronyms = parse (parser acronyms) "(unknown)"+#if MIN_VERSION_base(4,8,0)+import Prelude hiding (Word)+#else+import Data.Foldable+import Prelude hiding (elem)+#endif +-- | Parse a snake_case string.+--+-- >>> bar <- mkAcronym "bar"+-- >>> parseSnakeCase [bar] "foo_bar_bazz"+-- Right [Word "foo",Acronym "bar",Word "bazz"]+--+-- >>> parseSnakeCase [] "fooBarBazz"+-- 1:4:+-- unexpected 'B'+-- expecting '_', end of input, or lowercase letter+parseSnakeCase :: (Foldable f, Functor f)+ => f (Word 'Acronym) -- ^ Collection of acronyms+ -> Text -- ^ Input+ -> Either (ParseError Char Dec) [SomeWord] -- ^ Result of parsing+parseSnakeCase acronyms = parse (parser acronyms) "" -parser :: Stream s m Char => [String] -> ParsecT s u m [Word]-parser acronyms = do- ws <- (acronym acronyms <|> word) `sepBy` char '_'- eof- return ws+parser :: (Foldable f, Functor f)+ => f (Word 'Acronym)+ -> Parser [SomeWord]+parser acronyms = (pWord acronyms `sepBy` char '_') <* eof -word :: Stream s m Char => ParsecT s u m Word-word = Word <$> (many1 lower <|> many1 digit)+pWord :: (Foldable f, Functor f)+ => f (Word 'Acronym)+ -> Parser SomeWord+pWord acronyms = do+ let acs = unWord <$> acronyms+ r <- T.pack <$> some alphaNumChar+ if r `elem` acs+ then maybe empty (return . SomeWord) (mkAcronym r)+ else maybe empty (return . SomeWord) (mkWord r)
− Text/Inflections/Parse/Types.hs
@@ -1,18 +0,0 @@-module Text.Inflections.Parse.Types ( Word(..), mapWord ) where--import Prelude (String, Show, Eq, ($))---- | A 'String' that should be kept whole through applied inflections-data Word-- -- | A word that may be transformed by inflection- = Word String-- -- | A word that may not be transformed by inflections- | Acronym String-- deriving (Show, Eq)--mapWord :: (String -> String) -> Word -> Word-mapWord f (Word s) = Word $ f s-mapWord f (Acronym s) = Acronym $ f s
Text/Inflections/Titleize.hs view
@@ -1,18 +1,30 @@-module Text.Inflections.Titleize (titleize) where--import Text.Inflections.Parse.Types (Word(..))+-- |+-- Module : Text.Inflections.Titleize+-- Copyright : © 2016 Justin Leitgeb+-- License : MIT+--+-- Maintainer : Justin Leitgeb <justin@stackbuilders.com>+-- Stability : experimental+-- Portability : portable+--+-- Conversion to titleized phrases. -import Data.Char (toUpper)+module Text.Inflections.Titleize+ ( titleize )+where -import Prelude (String, unwords, map, ($))+import Data.Text (Text)+import Text.Inflections.Types+import qualified Data.Text as T --- | Capitalizes all the Words in the input 'Data.List'.+-- | Capitalize all the Words in the input list.+--+-- >>> foo <- SomeWord <$> mkWord "foo"+-- >>> bar <- SomeWord <$> mkAcronym "bar"+-- >>> bazz <- SomeWord <$> mkWord "bazz"+-- >>> titleize [foo,bar,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--upperCaseWord :: Word -> String-upperCaseWord (Word (c:cs)) = toUpper c : cs-upperCaseWord (Word []) = []-upperCaseWord (Acronym s) = s -- Acronyms are left intact+ :: [SomeWord] -- ^ List of words, first of which will be capitalized+ -> Text -- ^ The titleized 'Text'+titleize = T.unwords . fmap (unSomeWord T.toTitle)
Text/Inflections/Transliterate.hs view
@@ -1,30 +1,54 @@+-- |+-- Module : Text.Inflections.Transliterate+-- Copyright : © 2016 Justin Leitgeb+-- License : MIT+--+-- Maintainer : Justin Leitgeb <justin@stackbuilders.com>+-- Stability : experimental+-- Portability : portable+--+-- Support for transliteration.++{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+ module Text.Inflections.Transliterate ( transliterate- , transliterateCustom- )+ , transliterateCustom ) where -import Text.Inflections.Parameterize ( Transliterations )-import Text.Inflections.Data (defaultMap)- import Data.Char (isAscii)-import Data.Maybe(fromMaybe)+import Data.Text (Text)+import Text.Inflections.Data+import qualified Data.HashMap.Strict as M+import qualified Data.Text as T -import qualified Data.Map as Map+#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 :: String -> String-transliterate = transliterateCustom "?" defaultMap+-- | Returns a 'Text' 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 "?" defaultTransliterations --- |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 'Text' 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))
+ Text/Inflections/Types.hs view
@@ -0,0 +1,143 @@+-- |+-- Module : Text.Inflections.Types+-- Copyright : © 2016 Justin Leitgeb+-- License : MIT+--+-- Maintainer : Justin Leitgeb <justin@stackbuilders.com>+-- Stability : experimental+-- Portability : portable+--+-- Types used in the library. Usually you don't need to import this module+-- and "Text.Inflections" should be enough.++{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}++module Text.Inflections.Types+ ( Word+ , WordType (..)+ , mkWord+ , mkAcronym+ , unWord+ , SomeWord (..)+ , unSomeWord+ , InflectionException (..) )+where++import Control.Monad.Catch+import Data.Char (isAlphaNum)+import Data.Data (Data)+import Data.Text (Text)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import Text.Megaparsec+import qualified Data.Text as T++#if MIN_VERSION_base(4,8,0)+import Prelude hiding (Word)+#endif++-- | Create a word from given 'Text'. The input should consist of only+-- alpha-numeric characters (no white spaces or punctuation)+-- 'InflectionInvalidWord' will be thrown.+--+-- /since 0.3.0.0/++mkWord :: MonadThrow m => Text -> m (Word 'Normal)+mkWord txt =+ if T.all isAlphaNum txt+ then return (Word txt)+ else throwM (InflectionInvalidWord txt)++-- | Create an acronym from given 'Text'. The input should consist of only+-- alpha-numeric characters 'InflectionInvalidAcronym' will be thrown.+-- Acronym is different from normal word by that it may not be transformed+-- by inflections (also see 'unSomeWord').+--+-- /since 0.3.0.0/++mkAcronym :: MonadThrow m => Text -> m (Word 'Acronym)+mkAcronym txt =+ if T.all isAlphaNum txt+ then return (Word txt)+ else throwM (InflectionInvalidAcronym txt)++-- | A 'Text' value that should be kept whole through applied inflections.++data Word (t :: WordType) = Word Text+ deriving (Eq, Ord)++instance Show (Word 'Normal) where+ show (Word x) = "Word " ++ show x++instance Show (Word 'Acronym) where+ show (Word x) = "Acronym " ++ show x++-- | A type-level tag for words.+--+-- /since 0.3.0.0/++data WordType = Normal | Acronym++-- | Get a 'Text' value from 'Word'.+--+-- /since 0.3.0.0/++unWord :: Word t -> Text+unWord (Word s) = s++-- | An existential wrapper that allows to keep words and acronyms in single+-- list for example. The only thing that receiver of 'SomeWord' can do is to+-- apply 'unWord' on it, of course. This is faciliated by 'unSomeWord'.+--+-- /since 0.3.0.0/++data SomeWord where+ SomeWord :: (Transformable (Word t), Show (Word t)) => Word t -> SomeWord+ -- NOTE The constraint is only needed because GHC is not smart enough+ -- (yet?) to figure out that t cannot be anything other than Normal and+ -- Acronym and thus all cases are already covered by the instances+ -- provided below.++instance Show SomeWord where+ show (SomeWord w) = show w++instance Eq SomeWord where+ x == y = unSomeWord id x == unSomeWord id y++-- | Extract 'Text' from 'SomeWord' and apply given function only if the+-- word inside wasn't an acronym.+--+-- /since 0.3.0.0/++unSomeWord :: (Text -> Text) -> SomeWord -> Text+unSomeWord f (SomeWord w) = transform f w++-- | Non public stuff.++class Transformable a where+ transform :: (Text -> Text) -> a -> Text++instance Transformable (Word 'Normal) where+ transform f = f . unWord++instance Transformable (Word 'Acronym) where+ transform _ = unWord++-- | The exceptions that is thrown when parsing of input fails.+--+-- /since 0.3.0.0/++data InflectionException+ = InflectionParsingFailed (ParseError Char Dec)+ | InflectionInvalidWord Text+ | InflectionInvalidAcronym Text+ deriving (Eq, Show, Typeable, Data, Generic)++instance Exception InflectionException
Text/Inflections/Underscore.hs view
@@ -1,19 +1,32 @@-module Text.Inflections.Underscore ( underscore ) where+-- |+-- Module : Text.Inflections.Underscore+-- Copyright : © 2016 Justin Leitgeb+-- License : MIT+--+-- Maintainer : Justin Leitgeb <justin@stackbuilders.com>+-- Stability : experimental+-- Portability : portable+--+-- Conversion to phrases separated by underscores. -import Text.Inflections.Parse.Types (Word(..))+{-# LANGUAGE OverloadedStrings #-} -import Data.Char (toLower)-import Data.List (intercalate)+module Text.Inflections.Underscore+ ( underscore )+where -import Prelude (String, (.), map)+import Data.Text (Text)+import Text.Inflections.Types+import qualified Data.Text as T --- |Turns a CamelCase string into an underscore_separated String.+-- | Separate given words by underscores.+--+-- >>> foo <- SomeWord <$> mkWord "foo"+-- >>> bar <- SomeWord <$> mkAcronym "bar"+-- >>> bazz <- SomeWord <$> mkWord "bazz"+-- >>> underscore [foo,bar,bazz]+-- "foo_bar_bazz" underscore- :: [Word] -- ^ Input Words to separate with underscores- -> String -- ^ The underscored String-underscore = intercalate "_" . map toDowncasedString---toDowncasedString :: Word -> String-toDowncasedString (Acronym s) = map toLower s-toDowncasedString (Word s) = map toLower s+ :: [SomeWord] -- ^ Input words to separate with underscores+ -> Text -- ^ The underscored String+underscore = T.intercalate "_" . fmap (unSomeWord T.toLower)
inflections.cabal view
@@ -1,5 +1,5 @@ name: inflections-version: 0.2.0.1+version: 0.3.0.0 synopsis: Inflections library for Haskell description: Inflections provides methods for singularization, pluralization,@@ -7,63 +7,77 @@ license: MIT license-file: LICENSE-author: Justin Leitgeb+author: Justin Leitgeb <justin@stackbuilders.com> homepage: https://github.com/stackbuilders/inflections-hs-maintainer: justin@stackbuilders.com-copyright: 2014 Justin Leitgeb+bug-reports: https://github.com/stackbuilders/inflections-hs/issues+maintainer: Justin Leitgeb <justin@stackbuilders.com>+copyright: 2014–2016 Justin Leitgeb category: Text build-type: Simple cabal-version: >=1.10+extra-source-files: CHANGELOG.md+ , README.md source-repository head type: git location: https://github.com/stackbuilders/inflections-hs.git -library- exposed-modules: Text.Inflections- , Text.Inflections.Parse.Types--- other-modules: Text.Inflections.Data- , Text.Inflections.Parameterize- , Text.Inflections.Humanize- , Text.Inflections.Underscore- , Text.Inflections.Camelize- , Text.Inflections.Dasherize- , Text.Inflections.Ordinal- , Text.Inflections.Titleize- , Text.Inflections.Transliterate+flag dev+ description: Turn on development settings.+ manual: True+ default: False - , Text.Inflections.Parse.Acronym- , Text.Inflections.Parse.Parameterizable+library+ exposed-modules: Text.Inflections - , Text.Inflections.Parse.SnakeCase- , Text.Inflections.Parse.CamelCase+ other-modules: Text.Inflections.Data+ , Text.Inflections.Camelize+ , Text.Inflections.Dasherize+ , Text.Inflections.Humanize+ , Text.Inflections.Ordinal+ , Text.Inflections.Parameterize+ , Text.Inflections.Parse.CamelCase+ , Text.Inflections.Parse.SnakeCase+ , Text.Inflections.Titleize+ , Text.Inflections.Transliterate+ , Text.Inflections.Types+ , Text.Inflections.Underscore - ghc-options: -Wall- build-depends: base >=4.2 && <4.10, parsec, containers+ if flag(dev)+ ghc-options: -Wall -Werror+ else+ ghc-options: -O2 -Wall+ build-depends: base >= 4.6 && < 5.0+ , 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 type: exitcode-stdio-1.0 hs-source-dirs: test- main-is: Suite.hs- build-depends:- inflections- , base >=4.2 && <4.10- , test-framework- , HUnit- , QuickCheck- , test-framework-hunit- , test-framework-quickcheck2- , parsec- , containers- ghc-options: -Wall+ main-is: Spec.hs+ build-depends: inflections+ , QuickCheck >= 2.7.6 && < 3.0+ , base >= 4.6 && < 5.0+ , hspec >= 2.0 && < 3.0+ , hspec-megaparsec >= 0.3 && < 0.4+ , megaparsec >= 5.1 && < 6.0+ , text >= 0.2 && < 1.3+ if flag(dev)+ ghc-options: -Wall -Werror+ else+ ghc-options: -O2 -Wall default-language: Haskell2010- other-modules:- Text.InflectionsTest- Text.Inflections.HumanizeTest- Text.Inflections.OrdinalTest- Text.Inflections.Tests- Text.Inflections.TitleizeTest- Text.Inflections.UnderscoreTest+ other-modules: Text.Inflections.DasherizeSpec+ , Text.Inflections.HumanizeSpec+ , Text.Inflections.OrdinalSpec+ , Text.Inflections.ParametrizeSpec+ , Text.Inflections.Parse.CamelCaseSpec+ , Text.Inflections.Parse.SnakeCaseSpec+ , Text.Inflections.TitleizeSpec+ , Text.Inflections.TransliterateSpec+ , Text.Inflections.TypesSpec+ , Text.Inflections.UnderscoreSpec+ , Text.InflectionsSpec
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
− test/Suite.hs
@@ -1,18 +0,0 @@-module Main where--import Test.Framework (defaultMain)--import qualified Text.InflectionsTest-import qualified Text.Inflections.Tests-import qualified Text.Inflections.UnderscoreTest-import qualified Text.Inflections.OrdinalTest-import qualified Text.Inflections.HumanizeTest-import qualified Text.Inflections.TitleizeTest--main :: IO ()-main = defaultMain $ Text.InflectionsTest.tests ++- Text.Inflections.Tests.tests ++- Text.Inflections.UnderscoreTest.tests ++- Text.Inflections.OrdinalTest.tests ++- Text.Inflections.HumanizeTest.tests ++- Text.Inflections.TitleizeTest.tests
+ test/Text/Inflections/DasherizeSpec.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}++module Text.Inflections.DasherizeSpec+ ( spec )+where++import Test.Hspec++import Text.Inflections++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif++spec :: Spec+spec = describe "dasherize" $+ it "dasherizes a collection of words sentence" $ do+ foo <- SomeWord <$> mkWord "foo"+ bar <- SomeWord <$> mkWord "bar"+ dasherize [foo,bar] `shouldBe` "foo-bar"
+ test/Text/Inflections/HumanizeSpec.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}++module Text.Inflections.HumanizeSpec (spec) where++import Test.Hspec++import Text.Inflections++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif++spec :: Spec+spec = describe "humazine" $ do+ it "converts snake case to a human-readable string" $ do+ employee <- SomeWord <$> mkWord "employee"+ salary <- SomeWord <$> mkWord "salary"+ humanize [employee,salary] `shouldBe` "Employee salary"+ it "turns underscores into spaces" $ do+ employee <- SomeWord <$> mkWord "employee"+ has <- SomeWord <$> mkWord "has"+ salary <- SomeWord <$> mkWord "salary"+ humanize [employee, has, salary] `shouldBe` "Employee has salary"+ it "capitalizes the first word of a sentence" $ do+ underground <- SomeWord <$> mkWord "underground"+ humanize [underground] `shouldBe` "Underground"
− test/Text/Inflections/HumanizeTest.hs
@@ -1,28 +0,0 @@-module Text.Inflections.HumanizeTest where--import Test.HUnit hiding (Test)--import Test.Framework.Providers.HUnit (testCase)--import Test.Framework (Test, testGroup)--import Text.Inflections (humanize)-import Text.Inflections.Parse.Types (Word(..))--{-# ANN module "HLint: ignore Use camelCase" #-}--tests :: [Test]-tests = [ testGroup "humanize"- [ testCase "employee_salary -> Employee salary" test_humanize1- , testCase "underground -> underground" test_humanize2- ]- ]--------------------------------------------------------test_humanize1 :: Assertion-test_humanize1 = "Employee salary" @?=- humanize [Word "employee", Word "salary"]--test_humanize2 :: Assertion-test_humanize2 = "Underground" @?= humanize [Word "underground"]
+ test/Text/Inflections/OrdinalSpec.hs view
@@ -0,0 +1,42 @@+{-# 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)++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif++spec :: Spec+spec = do+ describe "ordinal" $ do+ it "returns the ordinal for 1" $+ ordinal (1 :: Integer) `shouldBe` "st"+ it "returns the ordinal for 2" $+ ordinal (2 :: Integer) `shouldBe` "nd"+ it "returns the ordinal for 1002" $+ ordinal (1002 :: Integer) `shouldBe` "nd"+ it "returns the ordinal for 1003" $+ ordinal (1003 :: Integer) `shouldBe` "rd"+ it "returns the ordinal for -11" $+ ordinal (-11 :: Integer) `shouldBe` "th"+ it "returns the ordinal for -1021" $+ ordinal (-1021 :: Integer) `shouldBe` "st"+ it "never returns empty output" $ property $+ property <$> not . T.null . (ordinal :: Integer -> Text)+ describe "ordinalize" $ do+ it "returns the full ordinal for 1" $+ ordinalize (1 :: Integer) `shouldBe` "1st"+ it "returns the full ordinal for -1021" $+ ordinalize (-1021 :: Integer) `shouldBe` "-1021st"+ it "always returns the number as prefix of the result" $+ property $ \n ->+ let s = show (n :: Integer)+ in T.pack s == T.take (length s) (ordinalize n)
− test/Text/Inflections/OrdinalTest.hs
@@ -1,65 +0,0 @@-module Text.Inflections.OrdinalTest where--import Test.HUnit hiding (Test)--import Test.Framework.Providers.QuickCheck2 (testProperty)-import Test.Framework.Providers.HUnit (testCase)--import Test.Framework (Test, testGroup)--import Text.Inflections (ordinal, ordinalize)--{-# ANN module "HLint: ignore Use camelCase" #-}--tests :: [Test]-tests = [ testGroup "ordinal"- [ testCase "1 -> st" test_ordinal1- , testCase "2 -> nd" test_ordinal2- , testCase "1002 -> nd" test_ordinal1002- , testCase "1003 -> rd" test_ordinal1003- , testCase "-11 -> th" test_ordinalNegative11- , testCase "-1021 -> st" test_ordinalNegative1021- , testProperty "notEmpty" prop_ordinalReturnsNotEmpty- ]- , testGroup "ordinalize"- [ testCase "1 -> st" test_ordinalize1- , testCase "-1021 -> st" test_ordinalizeNegative1021- , testProperty "result contains number" prop_ordinalizeSamePrefix- ]- ]--------------------------------------------------------test_ordinal1 :: Assertion-test_ordinal1 = "st" @?= ordinal 1--test_ordinal2 :: Assertion-test_ordinal2 = "nd" @?= ordinal 2--test_ordinal1002 :: Assertion-test_ordinal1002 = "nd" @?= ordinal 1002--test_ordinal1003 :: Assertion-test_ordinal1003 = "rd" @?= ordinal 1003--test_ordinalNegative11 :: Assertion-test_ordinalNegative11 = "th" @?= ordinal (-11)--test_ordinalNegative1021 :: Assertion-test_ordinalNegative1021 = "st" @?= ordinal (-1021)--------------------------------------------------------test_ordinalize1 :: Assertion-test_ordinalize1 = "1st" @?= ordinalize 1--test_ordinalizeNegative1021 :: Assertion-test_ordinalizeNegative1021 = "-1021st" @?= ordinalize (-1021)--------------------------------------------------------prop_ordinalReturnsNotEmpty :: Integer -> Bool-prop_ordinalReturnsNotEmpty = not . null . ordinal--prop_ordinalizeSamePrefix :: Integer -> Bool-prop_ordinalizeSamePrefix n = show n == take (length $ show n) (ordinalize n)
+ test/Text/Inflections/ParametrizeSpec.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE OverloadedStrings #-}++module Text.Inflections.ParametrizeSpec (spec) where++import Data.Char (toLower)+import Data.List (group)+import Test.Hspec+import Test.QuickCheck+import Text.Inflections+import qualified Data.Text as T++spec :: Spec+spec =+ describe "parameterize" $ do+ it "returns only valid characters" $ property $ \sf ->+ T.all (`elem` (alphaNumerics ++ "-_")) $ parameterize (T.pack sf)+ it "never returns a string beginning with a separator" $ property $ \s ->+ let parameterized = parameterize (T.pack s)+ in (not . T.null) parameterized ==> T.head parameterized /= '-'+ it "never returns a string ending with a separator" $ property $ \s ->+ let parameterized = parameterize (T.pack s) in+ (not . T.null) parameterized ==> T.last parameterized /= '-'+ it "returns every alphanumeric character from the input" $ property $ \s ->+ let parameterized = parameterize (T.pack s)+ in all (\c -> c `notElem` alphaNumerics ||+ c `elem` (alphaNumerics ++ "-") &&+ c `elem` T.unpack parameterized) $ map toLower s+ it "never returns a string with a sequence of dashes" $ property $ \s ->+ let parameterized = parameterize (T.pack s)+ in longestSequenceOf '-' (T.unpack parameterized) <= 1++longestSequenceOf :: Char -> String -> Int+longestSequenceOf _ [] = 0+longestSequenceOf c s =+ if null subseqLengths then 0 else maximum subseqLengths+ where subseqLengths = (map length . filter (\str -> head str == c) . group) s++alphaNumerics :: String+alphaNumerics = ['a'..'z'] ++ ['0'..'9']
+ test/Text/Inflections/Parse/CamelCaseSpec.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE OverloadedStrings #-}++module Text.Inflections.Parse.CamelCaseSpec+ ( spec )+where++import Test.Hspec+import Test.Hspec.Megaparsec+import Text.Inflections++spec :: Spec+spec =+ describe "parseCamelCase" $ do+ context "when given no acronyms" $ do+ context "when first word is capitalized" $+ it "parses CamelCase correctly" $ do+ r <- mapM (fmap SomeWord . mkWord) ["One","Two","Three"]+ parseCamelCase [] "OneTwoThree" `shouldParse` r+ context "when first word is not capitalized" $+ it "parses camelCase correctly" $ do+ r <- mapM (fmap SomeWord . mkWord) ["one","Two","Three"]+ parseCamelCase [] "oneTwoThree" `shouldParse` r+ context "when there are digits in the words" $+ it "parses CamelCase correctly" $ do+ r <- mapM (fmap SomeWord . mkWord) ["one1two","Three3"]+ parseCamelCase [] "one1twoThree3" `shouldParse` r+ context "when given some acronyms" $ do+ context "when first word is capitalized" $+ it "parses CamelCase correctly" $ do+ a <- mkAcronym "BOO"+ r <- mapM (fmap SomeWord . mkWord) ["One","BOO","One"]+ parseCamelCase [a] "OneBOOOne" `shouldParse` r+ context "when first word is not capitalized" $+ it "parses camelCase correctly" $ do+ a <- mkAcronym "BOO"+ r <- mapM (fmap SomeWord . mkWord) ["one","Two","BOO"]+ parseCamelCase [a] "oneTwoBOO" `shouldParse` r+ context "when there are digits in the words" $+ it "parses CamelCase correctly" $ do+ a <- mkAcronym "BOO"+ r <- mapM (fmap SomeWord . mkWord) ["one1two","Three3"]+ parseCamelCase [a] "one1twoThree3" `shouldParse` r+ context "when a word has a suffix coinciding with a acronym" $+ it "is still parsed correctly as a normala word" $ do+ a <- mkAcronym "boo"+ r <- mapM (fmap SomeWord . mkWord) ["fooboo","Bar"]+ parseCamelCase [a] "foobooBar" `shouldParse` r
+ test/Text/Inflections/Parse/SnakeCaseSpec.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE OverloadedStrings #-}++module Text.Inflections.Parse.SnakeCaseSpec+ ( spec )+where++import Test.Hspec+import Test.Hspec.Megaparsec+import Text.Inflections++spec :: Spec+spec =+ describe "parseSnakeCase" $ do+ context "when given no acronyms" $ do+ it "parses snake_case correctly" $ do+ r <- mapM (fmap SomeWord . mkWord) ["OneTwo","Three"]+ parseSnakeCase [] "OneTwo_Three" `shouldParse` r+ it "handles digits in the words correctly" $ do+ r <- mapM (fmap SomeWord . mkWord) ["one4a","two00"]+ parseSnakeCase [] "one4a_two00" `shouldParse` r+ context "when given some acronyms" $ do+ it "parses snake_case correctly" $ do+ a <- mkAcronym "BOO"+ r <- mapM (fmap SomeWord . mkWord) ["BOO","one","one"]+ parseSnakeCase [a] "BOO_one_one" `shouldParse` r+ context "when acronym happens to be a prefix of other word" $+ it "parses the word correctly" $ do+ a <- mkAcronym "BOO"+ r <- mapM (fmap SomeWord . mkWord) ["one","BOOtwo"]+ parseSnakeCase [a] "one_BOOtwo" `shouldParse` r+ context "when acronym happens to be a suffix of other word" $+ it "parses the word correctly" $ do+ a <- mkAcronym "BOO"+ r <- mapM (fmap SomeWord . mkWord) ["oneBOO","two"]+ parseSnakeCase [a] "oneBOO_two" `shouldParse` r
− test/Text/Inflections/Tests.hs
@@ -1,107 +0,0 @@-module Text.Inflections.Tests where--import Test.HUnit hiding (Test)--import Test.Framework.Providers.QuickCheck2 (testProperty)-import Test.Framework.Providers.HUnit (testCase)--import Test.QuickCheck--import Test.Framework (Test, testGroup)--import Data.List (group)-import Data.Char (toLower)--import Text.Inflections-import Text.Inflections.Parse.Types (Word(..))--{-# ANN module "HLint: ignore Use camelCase" #-}--tests :: [Test]-tests = [testGroup "dasherize"- [ testCase "foo bar -> foo-bar" test_dasherize1- ],-- testGroup "transliterate"- [ testCase "Without substitutions" test_correctTransliterationWithoutSubs- , testCase "With substitutions" test_correctTransliterationWithSubs- , testCase "Missing subs" test_correctTransliterationMissingSubs- ],-- testGroup "parameterize"- [ testProperty "Contains only valid chars"- prop_parameterize1- , testProperty "Does not begin with a separator character"- prop_parameterize2- , testProperty "Does not end in a separator character"- prop_parameterize3- , testProperty "All alphanumerics in input exist in output"- prop_parameterize4- , testProperty "Doesn't have subsequences of more than one hyphen"- prop_parameterize5- ]- ]--test_correctTransliterationWithoutSubs :: Assertion-test_correctTransliterationWithoutSubs =- transliterate "this is a test" @?= "this is a test"--test_correctTransliterationWithSubs :: Assertion-test_correctTransliterationWithSubs =- transliterate "Feliz año nuevo" @?= "Feliz ano nuevo"--test_correctTransliterationMissingSubs :: Assertion-test_correctTransliterationMissingSubs =- transliterate "Have a ❤ ñ!" @?= "Have a ? n!"--fromRight :: Either a b -> b-fromRight (Left _) =- error "Either.Unwrap.fromRight: Argument takes form 'Left _'"-fromRight (Right x) = x--isRight :: Either a b -> Bool-isRight (Left _) = False-isRight (Right _) = True--test_dasherize1 :: Assertion-test_dasherize1 = "foo-bar" @?= dasherize [Word "foo", Word "bar"]--prop_parameterize1 :: String -> Bool-prop_parameterize1 sf = all (`elem` (alphaNumerics ++ "-_")) $- parameterize sf--prop_parameterize2 :: String -> Property-prop_parameterize2 s =- (not . null) parameterized ==> head parameterized /= '-'- where parameterized = parameterize s--prop_parameterize3 :: String -> Property-prop_parameterize3 s =- (not . null) parameterized ==> last parameterized /= '-'- where parameterized = parameterize s--prop_parameterize4 :: String -> Bool-prop_parameterize4 s = all (\c -> c `notElem` alphaNumerics ||- c `elem` (alphaNumerics ++ "-") &&- c `elem` parameterized) $ map toLower s- where parameterized = parameterize s--prop_parameterize5 :: String -> Bool-prop_parameterize5 s = longestSequenceOf '-' parameterized <= 1- where parameterized = parameterize s----- Helper functions and shared tests--longestSequenceOf :: Char -> String -> Int-longestSequenceOf _ [] = 0-longestSequenceOf c s =- if null subseqLengths then 0 else maximum subseqLengths-- where subseqLengths = (map length . filter (\str -> head str == c) . group) s--numMatching :: Eq a => a -> [a] -> Int-numMatching char str = length $ filter (== char) str--alphaNumerics :: String-alphaNumerics = ['a'..'z'] ++ ['0'..'9']
+ test/Text/Inflections/TitleizeSpec.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}++module Text.Inflections.TitleizeSpec (spec) where++import Test.Hspec+import Text.Inflections++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif++spec :: Spec+spec = describe "titleize" $ do+ it "converts two words to title case" $ do+ employee <- SomeWord <$> mkWord "Employee"+ salary <- SomeWord <$> mkWord "Salary"+ titleize [employee,salary] `shouldBe` "Employee Salary"+ it "converts one word to title case" $ do+ underground <- SomeWord <$> mkWord "underground"+ titleize [underground] `shouldBe` "Underground"
− test/Text/Inflections/TitleizeTest.hs
@@ -1,28 +0,0 @@-module Text.Inflections.TitleizeTest where--import Test.HUnit hiding (Test)--import Test.Framework.Providers.HUnit (testCase)--import Test.Framework (Test, testGroup)--import Text.Inflections (titleize)-import Text.Inflections.Parse.Types (Word(..))--{-# ANN module "HLint: ignore Use camelCase" #-}--tests :: [Test]-tests = [ testGroup "titleize"- [ testCase "employee_salary -> Employee Salary" test_titleize1- , testCase "underground -> Underground" test_titleize2- ]- ]--------------------------------------------------------test_titleize1 :: Assertion-test_titleize1 = "Employee Salary" @?=- titleize [Word "Employee", Word "Salary"]--test_titleize2 :: Assertion-test_titleize2 = "Underground" @?= titleize [Word "underground"]
+ test/Text/Inflections/TransliterateSpec.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE OverloadedStrings #-}++module Text.Inflections.TransliterateSpec+ ( spec )+where++import Test.Hspec+import Text.Inflections (transliterate)++spec :: Spec+spec = describe "transliterate" $ do+ withSubstitutions+ withoutSubstitutions+ missingSubstitutions++withoutSubstitutions :: Spec+withoutSubstitutions =+ it "transliterates without subsitutions" $+ transliterate "this is a test" `shouldBe` "this is a test"++withSubstitutions :: Spec+withSubstitutions =+ it "transliterates with substitution" $+ transliterate "Feliz año nuevo" `shouldBe` "Feliz ano nuevo"++missingSubstitutions :: Spec+missingSubstitutions =+ it "transliterates with missing substitutions" $+ transliterate "Have a ❤ ñ!" `shouldBe` "Have a ? n!"
+ test/Text/Inflections/TypesSpec.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}++module Text.Inflections.TypesSpec+ ( spec )+where++import Test.Hspec+import Text.Inflections+import qualified Data.Text as T++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif++spec :: Spec+spec = do+ describe "mkWord" $ do+ context "when provided a correct Text value" $+ it "creates a normal word" $ do+ x <- mkWord "foo"+ unWord x `shouldBe` "foo"+ context "when provided an incorrect Text value" $+ it "throws the correct exception" $ do+ mkWord "foo bar" `shouldThrow` (== InflectionInvalidWord "foo bar")+ mkWord "foo_" `shouldThrow` (== InflectionInvalidWord "foo_")+ mkWord "&$?%" `shouldThrow` (== InflectionInvalidWord "&$?%")+ describe "mkAcronym" $ do+ context "when provided a correct Text value" $+ it "creates an acronym" $ do+ x <- mkAcronym "foo"+ unWord x `shouldBe` "foo"+ context "when providde an incorrect Text value" $+ it "throws the correct exception" $ do+ mkAcronym "foo bar" `shouldThrow` (== InflectionInvalidAcronym "foo bar")+ mkAcronym "foo_" `shouldThrow` (== InflectionInvalidAcronym "foo_")+ mkAcronym "&$?%" `shouldThrow` (== InflectionInvalidAcronym "&$?%")+ describe "unWord" $+ it "extracts the inner Text value" $ do+ (unWord <$> mkWord "foo") `shouldReturn` "foo"+ (unWord <$> mkWord "bar") `shouldReturn` "bar"+ (unWord <$> mkAcronym "baz") `shouldReturn` "baz"+ describe "unSomeWord" $ do+ context "when inner value is a normal word" $+ it "Text is extracted and the given function applied" $ do+ x <- SomeWord <$> mkWord "word"+ unSomeWord T.toUpper x `shouldBe` "WORD"+ context "when inner value is an acronym" $+ it "Text is extracted, but the function is not applied" $ do+ x <- SomeWord <$> mkAcronym "acronym"+ unSomeWord T.toUpper x `shouldBe` "acronym"
+ test/Text/Inflections/UnderscoreSpec.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}++module Text.Inflections.UnderscoreSpec (spec) where++import Test.Hspec+import Text.Inflections++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif++spec :: Spec+spec = describe "underscore" $+ it "converts a word list to snake case" $ do+ test <- SomeWord <$> mkWord "test"+ this <- SomeWord <$> mkWord "this"+ underscore [test, this] `shouldBe` "test_this"
− test/Text/Inflections/UnderscoreTest.hs
@@ -1,21 +0,0 @@-module Text.Inflections.UnderscoreTest where--import Test.HUnit hiding (Test)--import Test.Framework.Providers.HUnit (testCase)--import Test.Framework (Test, testGroup)--import Text.Inflections (underscore)-import Text.Inflections.Parse.Types (Word(..))--{-# ANN module "HLint: ignore Use camelCase" #-}--tests :: [Test]-tests = [testGroup "underscore"- [ testCase "testThis -> test_this" test_underscore- ]- ]--test_underscore :: Assertion-test_underscore = "test_this" @?= underscore [Word "test", Word "this"]
+ test/Text/InflectionsSpec.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE OverloadedStrings #-}++module Text.InflectionsSpec (spec) where++import Test.Hspec+import Test.QuickCheck+import Text.Inflections++spec :: Spec+spec = do++ describe "toUnderscore" $ do+ it "converts camel case to snake case" $+ toUnderscore "camelCasedText" `shouldBe` Right "camel_cased_text"+ it "converts camel case to snake case with numbers" $+ toUnderscore "ipv4Address" `shouldBe` Right "ipv4_address"++ describe "toDashed" $+ it "converts camel case to dashed" $+ toDashed "camelCasedText" `shouldBe` Right "camel-cased-text"++ describe "toCamelCased" $ do+ context "when the first argument is False" $+ it "converts snake case to camel case" $+ toCamelCased False "underscored_text" `shouldBe` Right "underscoredText"+ context "when the first argument is True" $+ it "converts snake case to camel case with the first word capitalized" $+ toCamelCased True "underscored_text" `shouldBe` Right "UnderscoredText"++ describe "betterThrow" $ do+ context "when given a parse error" $+ it "throws the correct exception" $+ property $ \err ->+ betterThrow (Left err) `shouldThrow`+ (== InflectionParsingFailed err)+ context "when given a value in Right" $+ it "returns the value" $+ property $ \x ->+ betterThrow (Right x) `shouldReturn` (x :: Int)
− test/Text/InflectionsTest.hs
@@ -1,32 +0,0 @@-module Text.InflectionsTest where--import Test.HUnit hiding (Test)--import Test.Framework.Providers.HUnit (testCase)--import Test.Framework (Test, testGroup)--import Text.Inflections (toUnderscore, toDashed, toCamelCased)--{-# ANN module "HLint: ignore Use camelCase" #-}--tests :: [Test]-tests = [ testGroup "toUnderscore"- [ testCase "camelCasedText -> camel_cased_text" test_to_underscore- ]- , testGroup "toDashed"- [ testCase "camelCasedText -> camel-cased-text" test_to_dashed- ]- , testGroup "toCamelCased"- [ testCase "underscored_text -> camelCasedText" test_to_camel_cased- ]- ]--test_to_underscore :: Assertion-test_to_underscore = "camel_cased_text" @?= toUnderscore "camelCasedText"--test_to_dashed :: Assertion-test_to_dashed = "camel-cased-text" @?= toDashed "camelCasedText"--test_to_camel_cased :: Assertion-test_to_camel_cased = "underscoredText" @?= toCamelCased False "underscored_text"