packages feed

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 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++[![License MIT](https://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT)+[![Hackage](https://img.shields.io/hackage/v/countable-inflections.svg)](http://hackage.haskell.org/package/countable-inflections)+[![Stackage LTS](http://stackage.org/package/countable-inflections/badge/lts)](http://stackage.org/lts/package/countable-inflections)+[![Build Status](https://circleci.com/gh/tippenein/countable-inflections.svg?style=shield&circle-token=<TOKEN HERE>)](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"