countable-inflections (empty) → 0.0.1
raw patch · 9 files changed
+433/−0 lines, 9 filesdep +QuickCheckdep +basedep +bytestringsetup-changed
Dependencies added: QuickCheck, base, bytestring, countable-inflections, exceptions, hspec, pcre-light, text
Files
- CHANGELOG.md +0/−0
- LICENSE.md +21/−0
- README.md +66/−0
- Setup.hs +2/−0
- Text/Countable.hs +141/−0
- Text/Countable/Data.hs +104/−0
- countable-inflections.cabal +54/−0
- test/Spec.hs +1/−0
- test/Text/CountableSpec.hs +44/−0
+ CHANGELOG.md view
+ LICENSE.md view
@@ -0,0 +1,21 @@+The MIT License (MIT)++Copyright (c) 2015 brady.ouren <brady.ouren@gmail.com>++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN+THE SOFTWARE.
+ README.md view
@@ -0,0 +1,66 @@+# Countable Inflections++[](http://opensource.org/licenses/MIT)+[](http://hackage.haskell.org/package/countable-inflections)+[](http://stackage.org/lts/package/countable-inflections)+[](https://circleci.com/gh/tippenein/countable-inflections)++This library implements pluralization and singularization in a similar way to the [rails inflectors](http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html)++It uses regexes to define the non-standard transformations and therefore+doesn't provide much safety. If you need to provide the same pluralization and+singularization which rails does out of the box, this will work the same. If+you want _more_ you should be using+[inflections-hs](https://github.com/stackbuilders/inflections-hs) which uses+megaparsec to give you more guarantees++## Usage++```haskell+λ: pluralize "person"+"people"++λ: singularize "branches"+"branch"+```++These can also be given custom inflection matchers++```+λ: :t singularizeWith+[Inflection] -> Text -> Text+```++There are 3 different types of transformations:++Match (takes a PCRE regex and a replacement string)++```+λ: :t makeMatchMapping+[(RegexPattern, RegexReplace)] -> [Inflection]++λ: let mapping = makeMatchMapping [("(octop)us", "\1i")]+λ: pluralizeWith mapping "octopus"+"octopi"+```++Irregular (from singular to plural with no greater pattern)++```+λ: :t makeIrregularMapping+[(Singular, Plural)] -> [Inflection]++λ: let mapping = makeIrregularMapping [("zombie","zombies")]+λ: pluralizeWith mapping "zombie"+"zombies"+```++Uncountable (doesn't have a mapping, word stays the same) so it has the type:+```+[Text] -> [Inflection]+```+++## License++MIT - see [the LICENSE file](LICENSE.md).
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Text/Countable.hs view
@@ -0,0 +1,141 @@+-- |+-- Module : Text.Countable+-- Copyright : © 2016 Brady Ouren+-- License : MIT+--+-- Maintainer : Brady Ouren <brady@andand.co>+-- Stability : experimental+-- Portability : portable+--+-- pluralization and singularization transformations++{-# LANGUAGE OverloadedStrings #-}++module Text.Countable+ ( pluralize+ , pluralizeWith+ , singularize+ , singularizeWith+ , makeMatchMapping+ , makeIrregularMapping+ , makeUncountableMapping+ )+where++import Data.Maybe (catMaybes, fromMaybe)+import Data.Monoid ((<>))+import Data.Text as T+import Data.Text.Encoding (decodeUtf8, encodeUtf8)+import Text.Countable.Data+import Text.Regex.PCRE.Light++type RegexPattern = T.Text+type RegexReplace = T.Text+type Singular = T.Text+type Plural = T.Text++data Inflection+ = Simple (Singular, Plural)+ | Match (Maybe Regex, RegexReplace)++-- | pluralize a word given a default mapping+pluralize :: T.Text -> T.Text+pluralize = pluralizeWith mapping+ where+ mapping = defaultIrregulars ++ defaultUncountables ++ defaultPlurals++-- | singularize a word given a default mapping+singularize :: T.Text -> T.Text+singularize = singularizeWith mapping+ where+ mapping = defaultIrregulars ++ defaultUncountables ++ defaultSingulars++-- | pluralize a word given a custom mapping.+-- Build the [Inflection] with a combination of+-- `makeUncountableMapping` `makeIrregularMapping` `makeMatchMapping`+pluralizeWith :: [Inflection] -> T.Text -> T.Text+pluralizeWith mapping t = fromMaybe t $ headMaybe matches+ where+ matches = catMaybes $ fmap (pluralLookup t) (Prelude.reverse mapping)++-- | singularize a word given a custom mapping.+-- Build the [Inflection] with a combination of+-- `makeUncountableMapping` `makeIrregularMapping` `makeMatchMapping`+singularizeWith :: [Inflection] -> T.Text -> T.Text+singularizeWith mapping t = fromMaybe t $ headMaybe matches+ where+ matches = catMaybes $ fmap (singularLookup t) (Prelude.reverse mapping)++-- | Makes a simple list of mappings from singular to plural, e.g [("person", "people")]+-- the output of [Inflection] should be consumed by `singularizeWith` or `pluralizeWith`+makeMatchMapping :: [(RegexPattern, RegexReplace)] -> [Inflection]+makeMatchMapping = fmap (\(pattern, rep) -> Match (regexPattern pattern, rep))++-- | Makes a simple list of mappings from singular to plural, e.g [("person", "people")]+-- the output of [Inflection] should be consumed by `singularizeWith` or `pluralizeWith`+makeIrregularMapping :: [(Singular, Plural)] -> [Inflection]+makeIrregularMapping = fmap Simple++-- | Makes a simple list of uncountables which don't have+-- singular plural versions, e.g ["fish", "money"]+-- the output of [Inflection] should be consumed by `singularizeWith` or `pluralizeWith`+makeUncountableMapping :: [T.Text] -> [Inflection]+makeUncountableMapping = fmap (\a -> (Simple (a,a)))+++defaultPlurals :: [Inflection]+defaultPlurals = makeMatchMapping defaultPlurals'++defaultSingulars :: [Inflection]+defaultSingulars = makeMatchMapping defaultSingulars'++defaultIrregulars :: [Inflection]+defaultIrregulars = makeIrregularMapping defaultIrregulars'++defaultUncountables :: [Inflection]+defaultUncountables = makeUncountableMapping defaultUncountables'++pluralLookup :: T.Text -> Inflection -> Maybe T.Text+pluralLookup t (Match (r1,r2)) = runSub (r1,r2) t+pluralLookup t (Simple (a,b)) = if t == a then (Just b) else Nothing++singularLookup :: T.Text -> Inflection -> Maybe T.Text+singularLookup t (Match (r1,r2)) = runSub (r1,r2) t+singularLookup t (Simple (a,b)) = if t == b then (Just a) else Nothing++runSub :: (Maybe Regex, RegexReplace) -> T.Text -> Maybe T.Text+runSub (Nothing, _) _ = Nothing+runSub (Just reg, rep) t = matchWithReplace (reg, rep) t++matchWithReplace :: (Regex, RegexReplace) -> T.Text -> Maybe T.Text+matchWithReplace (reg, rep) t = do+ matched : grouping <- regexMatch t reg+ return $ (t `without` matched) <> groupReplace grouping rep+ where+ without t1 t2 = if t2 == "" then t1 else T.replace t2 "" t1++groupReplace :: [T.Text] -> T.Text -> T.Text+groupReplace g rep =+ case g of+ [] -> rep+ (g':_) -> g' <> additions+ where+ rep' = tailMaybe $ T.split ((==) '\1') rep+ additions = T.concat $ fromMaybe [] rep'++regexMatch :: T.Text -> Regex -> Maybe [T.Text]+regexMatch t r = do+ bs <- match r (encodeUtf8 t) []+ return $ fmap decodeUtf8 bs++regexPattern :: T.Text -> Maybe Regex+regexPattern pat = toMaybe $ compileM (encodeUtf8 pat) [caseless]+ where toMaybe = either (const Nothing) Just++headMaybe :: [a] -> Maybe a+headMaybe [] = Nothing+headMaybe (x:_) = Just x++tailMaybe :: [a] -> Maybe [a]+tailMaybe [] = Nothing+tailMaybe (_:xs) = Just xs
+ Text/Countable/Data.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE OverloadedStrings #-}+-- |+-- Module : Text.Countable+-- Copyright : © 2016 Brady Ouren+-- License : MIT+--+-- Maintainer : Brady Ouren <brady@andand.co>+-- Stability : experimental+-- Portability : portable+--+-- default seed data for transformations++module Text.Countable.Data where++import Data.Text (Text)++-- |These default inflections stolen from the Ruby inflection library - see+-- https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflections.rb+defaultPlurals' :: [(Text, Text)]+defaultPlurals' =+ [ ("$", "s")+ , ("s$", "s")+ , ("^(ax|test)is$", "\1es")+ , ("(octop|vir)us$", "\1i")+ , ("(octop|vir)i$", "\1i")+ , ("(alias|status)$", "\1es")+ , ("(bu)s$", "\1ses")+ , ("(buffal|tomat)o$", "\1oes")+ , ("([ti])um$", "\1a")+ , ("([ti])a$", "\1a")+ , ("sis$", "ses")+ , ("(?:([^f])fe|([lr])f)$", "\1\2ves")+ , ("(hive)$", "\1s")+ , ("([^aeiouy]|qu)y$", "\1ies")+ , ("(x|ch|ss|sh)$", "\1es")+ , ("(matr|vert|ind)(?:ix|ex)$", "\1ices")+ , ("^(m|l)ouse$", "\1ice")+ , ("^(m|l)ice$", "\1ice")+ , ("^(ox)$", "\1en")+ , ("^(oxen)$", "\1")+ , ("(quiz)$", "\1zes")+ ]++-- |These default inflections stolen from the Ruby inflection library - see+-- https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflections.rb+defaultSingulars' :: [(Text, Text)]+defaultSingulars' =+ [ ("s$", "")+ , ("(ss)$", "\1")+ , ("(n)ews$", "\1ews")+ , ("([ti])a$", "\1um")+ , ("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$", "\1sis")+ , ("(^analy)(sis|ses)$", "\1sis")+ , ("([^f])ves$", "\1fe")+ , ("(hive)s$", "\1")+ , ("(tive)s$", "\1")+ , ("([lr])ves$", "\1f")+ , ("([^aeiouy]|qu)ies$", "\1y")+ , ("(s)eries$", "\1eries")+ , ("(m)ovies$", "\1ovie")+ , ("(x|ch|ss|sh)es$", "\1")+ , ("^(m|l)ice$", "\1ouse")+ , ("(bus)(es)?$", "\1")+ , ("(o)es$", "\1")+ , ("(shoe)s$", "\1")+ , ("(cris|test)(is|es)$", "\1is")+ , ("^(a)x[ie]s$", "\1xis")+ , ("(octop|vir)(us|i)$", "\1us")+ , ("(alias|status)(es)?$", "\1")+ , ("^(ox)en", "\1")+ , ("(vert|ind)ices$", "\1ex")+ , ("(matr)ices$", "\1ix")+ , ("(quiz)zes$", "\1")+ , ("(database)s$", "\1")+ ]++-- |These default irregular inflections stolen from the Ruby inflection library - see+-- https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflections.rb+defaultIrregulars' :: [(Text, Text)]+defaultIrregulars' =+ -- from singular to plural+ [ ("person", "people")+ , ("man", "men")+ , ("child", "children")+ , ("sex", "sexes")+ , ("move", "moves")+ , ("zombie", "zombies")+ ]++-- |These default uncountable inflections stolen from the Ruby inflection library - see+-- https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflections.rb+defaultUncountables' :: [Text]+defaultUncountables' =+ [ "equipment"+ , "information"+ , "rice"+ , "money"+ , "species"+ , "series"+ , "fish"+ , "sheep"+ , "jeans"+ , "police"+ ]
+ countable-inflections.cabal view
@@ -0,0 +1,54 @@+name: countable-inflections+version: 0.0.1+synopsis: Countable Text Inflections+description:+ Provides methods for singularizing and pluralizing text.+ The library is based on Rails' inflections.++license: MIT+license-file: LICENSE.md+author: Brady Ouren <brady@andand.co>+homepage: https://github.com/tippenein/countable-inflections+bug-reports: https://github.com/tippenein/countable-inflections/issues+maintainer: Brady Ouren <brady@andand.co>+copyright: 2016 Brady Ouren+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/tippenein/countable-inflections.git++flag dev+ description: Turn on development settings.+ manual: True+ default: False++library+ exposed-modules: Text.Countable+ other-modules: Text.Countable.Data+ ghc-options: -Wall+ build-depends:+ base >= 4.6 && < 4.10+ , exceptions >= 0.6 && < 0.9+ , text >= 0.2 && < 1.3+ , pcre-light+ , bytestring+ default-language: Haskell2010++test-suite test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Spec.hs+ build-depends:+ countable-inflections+ , QuickCheck >= 2.7.6 && < 3.0+ , base >= 4.2 && < 4.10+ , hspec >= 2.0 && < 3.0+ , text >= 0.2 && < 1.3+ ghc-options: -Wall+ default-language: Haskell2010+ other-modules: Text.CountableSpec
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test/Text/CountableSpec.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE OverloadedStrings #-}++module Text.CountableSpec (spec) where++import Test.Hspec++import Text.Countable++spec :: Spec+spec = do+ irregularCases+ matchingCases++matchingCases :: Spec+matchingCases = do+ describe "laws" $ do+ let equality1 a = a `shouldBe` (singularize . pluralize) a+ let equality2 a = a `shouldBe` (pluralize . singularize) a++ it "returns itself when applied to (singularize . pluralize)" $+ mapM_ equality1 ["thing", "branch", "ox"]++ it "returns itself when applied to (pluralize . singularize)" $+ mapM_ equality2 ["things", "branches", "oxen"]++ describe "pluralize" $ do+ it "handles the normal case" $ do+ pluralize "thing" `shouldBe` "things"++ it "pluralizes regex cases" $ do+ pluralize "ox" `shouldBe` "oxen"+ pluralize "vertex" `shouldBe` "vertices"++ describe "singularize" $ do+ it "singularizes regex cases" $ do+ singularize "mice" `shouldBe` "mouse"+ singularize "oxen" `shouldBe` "ox"+ singularize "branches" `shouldBe` "branch"+++irregularCases :: Spec+irregularCases =+ it "can singularize irregulars" $+ singularize "people" `shouldBe` "person"