packages feed

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 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++[![License MIT](https://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT)+[![Hackage](https://img.shields.io/hackage/v/inflections.svg)](http://hackage.haskell.org/package/inflections)+[![Stackage Nightly](http://stackage.org/package/inflections/badge/nightly)](http://stackage.org/nightly/package/inflections)+[![Stackage LTS](http://stackage.org/package/inflections/badge/lts)](http://stackage.org/lts/package/inflections)+[![Build Status](https://travis-ci.org/stackbuilders/inflections-hs.svg?branch=master)](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"