├── .formatter.exs ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── config └── config.exs ├── lib ├── inflectorex.ex └── regexps.ex ├── mix.exs ├── mix.lock └── test ├── inflectorex_test.exs └── test_helper.exs /.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 3 | ] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | 3 | elixir: 4 | - 1.4.1 5 | otp_release: 19.0 6 | 7 | 8 | script: 9 | - mix test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 girish ramnani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Inflectorex 2 | [![Build Status](https://travis-ci.org/girishramnani/inflector.svg?branch=master)](https://travis-ci.org/girishramnani/inflector) 3 | 4 | 5 | 6 | Inflectorex pluralizes and singularizes English nouns. 7 | 8 | 9 | # Usage 10 | 11 | ```elixir 12 | 13 | Inflectorex.singularize("workers") # will give "worker" 14 | 15 | Inflectorex.pluralize("secret") # will give "secrets" 16 | 17 | 18 | ``` 19 | 20 | # Extending the regex lists 21 | 22 | Currently the application has 4 regex lists. 23 | You can extend these lists using the `config` block. To extend each of the lists the 24 | keys are as follows - 25 | 26 | + `@plural_regexps` - `plural` 27 | + `@singular_regexps` - `singular` 28 | + `@singular_uninflected` - `singular_uninflected` 29 | + `@plural_uninflected` - `plural_uninflected` 30 | 31 | 32 | Note - right is the key 33 | 34 | ## Example config 35 | 36 | ```elixir 37 | 38 | config :inflectorex, plural: [ 39 | {~r/developer/ , "developers"}, 40 | {~r/elixir/, "elixirs"} 41 | ] 42 | 43 | 44 | ``` 45 | 46 | 47 | 48 | # Todo 49 | 50 | - [x] Implement caching 51 | - [x] Error handling 52 | - [ ] code commenting 53 | - [x] tests 54 | - [x] publishing to hex.pm 55 | 56 | # Credits 57 | 58 | + CakePHP's inflector 59 | + degex/inflector 60 | 61 | # License 62 | 63 | MIT 64 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :inflector, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:inflector, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /lib/inflectorex.ex: -------------------------------------------------------------------------------- 1 | defmodule Inflectorex do 2 | use Application 3 | 4 | def start(_type, _args) do 5 | :ets.new(:inflectorex_cache, [:set, :public, :named_table]) 6 | {:ok, self()} 7 | end 8 | 9 | def stop(_state) do 10 | :ets.delete(:inflectorex_cache) 11 | end 12 | 13 | def pluralize(word) when is_binary(word) do 14 | case :ets.lookup(:inflectorex_cache, word) do 15 | [{_, :plural, plural_word}] -> 16 | plural_word 17 | 18 | _ -> 19 | plural_word = Inflectorex.Regexps.pluralize(word) 20 | :ets.insert(:inflectorex_cache, {word, :plural, plural_word}) 21 | plural_word 22 | end 23 | end 24 | 25 | def pluralize(_) do 26 | {:error, "Can only pluralize binary strings"} 27 | end 28 | 29 | def singularize(word) when is_binary(word) do 30 | case :ets.lookup(:inflectorex_cache, word) do 31 | [{_, :singular, singular_word}] -> 32 | singular_word 33 | 34 | _ -> 35 | singular_word = Inflectorex.Regexps.singularize(word) 36 | :ets.insert(:inflectorex_cache, {word, :singular, singular_word}) 37 | singular_word 38 | end 39 | end 40 | 41 | def singularize(_) do 42 | {:error, "Can only singularize binary strings"} 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/regexps.ex: -------------------------------------------------------------------------------- 1 | defmodule Inflectorex.Regexps do 2 | @singular_uninflected Application.get_env(:inflectorex, :singular_uniflected, []) ++ 3 | [ 4 | ~r/(?i)(^(?:.*[nrlm]ese))$/, 5 | ~r/(?i)(^(?:.*deer))$/, 6 | ~r/(?i)(^(?:.*fish))$/, 7 | ~r/(?i)(^(?:.*measles))$/, 8 | ~r/(?i)(^(?:.*ois))$/, 9 | ~r/(?i)(^(?:.*pox))$/, 10 | ~r/(?i)(^(?:.*sheep))$/, 11 | ~r/(?i)(^(?:.*ss))$/ 12 | ] 13 | 14 | @plural_uninflected Application.get_env(:inflectorex, :plural_uniflected, []) ++ 15 | [ 16 | ~r/(?i)(^(?:.*[nrlm]ese))$/, 17 | ~r/(?i)(^(?:.*deer))$/, 18 | ~r/(?i)(^(?:.*fish))$/, 19 | ~r/(?i)(^(?:.*measles))$/, 20 | ~r/(?i)(^(?:.*ois))$/, 21 | ~r/(?i)(^(?:.*pox))$/, 22 | ~r/(?i)(^(?:.*sheep))$/, 23 | ~r/(?i)(^(?:people))$/ 24 | ] 25 | 26 | @uninflected ~r/((?i)(^(?:Amoyese))$ 27 | |(?i)(^(?:bison))$ 28 | |(?i)(^(?:Borghese))$ 29 | |(?i)(^(?:bream))$ 30 | |(?i)(^(?:breeches))$ 31 | |(?i)(^(?:britches))$ 32 | |(?i)(^(?:buffalo))$ 33 | |(?i)(^(?:cantus))$ 34 | |(?i)(^(?:carp))$ 35 | |(?i)(^(?:chassis))$ 36 | |(?i)(^(?:clippers))$ 37 | |(?i)(^(?:cod))$ 38 | |(?i)(^(?:coitus))$ 39 | |(?i)(^(?:Congoese))$ 40 | |(?i)(^(?:contretemps))$ 41 | |(?i)(^(?:corps))$ 42 | |(?i)(^(?:debris))$ 43 | |(?i)(^(?:diabetes))$ 44 | |(?i)(^(?:djinn))$ 45 | |(?i)(^(?:eland))$ 46 | |(?i)(^(?:elk))$ 47 | |(?i)(^(?:equipment))$ 48 | |(?i)(^(?:Faroese))$ 49 | |(?i)(^(?:flounder))$ 50 | |(?i)(^(?:Foochowese))$ 51 | |(?i)(^(?:gallows))$ 52 | |(?i)(^(?:Genevese))$ 53 | |(?i)(^(?:Genoese))$ 54 | |(?i)(^(?:Gilbertese))$ 55 | |(?i)(^(?:graffiti))$ 56 | |(?i)(^(?:headquarters))$ 57 | |(?i)(^(?:herpes))$ 58 | |(?i)(^(?:hijinks))$ 59 | |(?i)(^(?:Hottentotese))$ 60 | |(?i)(^(?:information))$ 61 | |(?i)(^(?:innings))$ 62 | |(?i)(^(?:jackanapes))$ 63 | |(?i)(^(?:Kiplingese))$ 64 | |(?i)(^(?:Kongoese))$ 65 | |(?i)(^(?:Lucchese))$ 66 | |(?i)(^(?:mackerel))$ 67 | |(?i)(^(?:Maltese))$ 68 | |(?i)(^(?:.*?media))$ 69 | |(?i)(^(?:mews))$ 70 | |(?i)(^(?:moose))$ 71 | |(?i)(^(?:mumps))$ 72 | |(?i)(^(?:Nankingese))$ 73 | |(?i)(^(?:news))$ 74 | |(?i)(^(?:nexus))$ 75 | |(?i)(^(?:Niasese))$ 76 | |(?i)(^(?:Pekingese))$ 77 | |(?i)(^(?:Piedmontese))$ 78 | |(?i)(^(?:pincers))$ 79 | |(?i)(^(?:Pistoiese))$ 80 | |(?i)(^(?:pliers))$ 81 | |(?i)(^(?:Portuguese))$ 82 | |(?i)(^(?:proceedings))$ 83 | |(?i)(^(?:rabies))$ 84 | |(?i)(^(?:rice))$ 85 | |(?i)(^(?:rhinoceros))$ 86 | |(?i)(^(?:salmon))$ 87 | |(?i)(^(?:Sarawakese))$ 88 | |(?i)(^(?:scissors))$ 89 | |(?i)(^(?:sea[- ]bass))$ 90 | |(?i)(^(?:series))$ 91 | |(?i)(^(?:Shavese))$ 92 | |(?i)(^(?:shears))$ 93 | |(?i)(^(?:siemens))$ 94 | |(?i)(^(?:species))$ 95 | |(?i)(^(?:swine))$ 96 | |(?i)(^(?:testes))$ 97 | |(?i)(^(?:trousers))$ 98 | |(?i)(^(?:trout))$ 99 | |(?i)(^(?:tuna))$ 100 | |(?i)(^(?:Vermontese))$ 101 | |(?i)(^(?:Wenchowese))$ 102 | |(?i)(^(?:whiting))$ 103 | |(?i)(^(?:wildebeest))$ 104 | |(?i)(^(?:Yengeese))$)/mix 105 | 106 | @plural_regexps Application.get_env(:inflectorex, :plural, []) ++ 107 | [ 108 | {~r/(?i)(.*)\b((?:atlas))$/, "atlases"}, 109 | {~r/(?i)(.*)\b((?:beef))$/, "beefs"}, 110 | {~r/(?i)(.*)\b((?:brother))$/, "brothers"}, 111 | {~r/(?i)(.*)\b((?:cafe))$/, "cafes"}, 112 | {~r/(?i)(.*)\b((?:child))$/, "children"}, 113 | {~r/(?i)(.*)\b((?:cookie))$/, "cookies"}, 114 | {~r/(?i)(.*)\b((?:corpus))$/, "corpuses"}, 115 | {~r/(?i)(.*)\b((?:cow))$/, "cows"}, 116 | {~r/(?i)(.*)\b((?:ganglion))$/, "ganglions"}, 117 | {~r/(?i)(.*)\b((?:genie))$/, "genies"}, 118 | {~r/(?i)(.*)\b((?:genus))$/, "genera"}, 119 | {~r/(?i)(.*)\b((?:graffito))$/, "graffiti"}, 120 | {~r/(?i)(.*)\b((?:hoof))$/, "hoofs"}, 121 | {~r/(?i)(.*)\b((?:loaf))$/, "loaves"}, 122 | {~r/(?i)(.*)\b((?:man))$/, "men"}, 123 | {~r/(?i)(.*)\b((?:money))$/, "monies"}, 124 | {~r/(?i)(.*)\b((?:mongoose))$/, "mongooses"}, 125 | {~r/(?i)(.*)\b((?:move))$/, "moves"}, 126 | {~r/(?i)(.*)\b((?:mythos))$/, "mythoi"}, 127 | {~r/(?i)(.*)\b((?:niche))$/, "niches"}, 128 | {~r/(?i)(.*)\b((?:numen))$/, "numina"}, 129 | {~r/(?i)(.*)\b((?:occiput))$/, "occiputs"}, 130 | {~r/(?i)(.*)\b((?:octopus))$/, "octopuses"}, 131 | {~r/(?i)(.*)\b((?:opus))$/, "opuses"}, 132 | {~r/(?i)(.*)\b((?:ox))$/, "oxen"}, 133 | {~r/(?i)(.*)\b((?:penis))$/, "penises"}, 134 | {~r/(?i)(.*)\b((?:person))$/, "people"}, 135 | {~r/(?i)(.*)\b((?:sex))$/, "sexes"}, 136 | {~r/(?i)(.*)\b((?:soliloquy))$/, "soliloquies"}, 137 | {~r/(?i)(.*)\b((?:testis))$/, "testes"}, 138 | {~r/(?i)(.*)\b((?:trilby))$/, "trilbys"}, 139 | {~r/(?i)(.*)\b((?:turf))$/, "turfs"}, 140 | {~r/(?i)(.*)\b((?:potato))$/, "potatoes"}, 141 | {~r/(?i)(.*)\b((?:hero))$/, "heroes"}, 142 | {~r/(?i)(.*)\b((?:tooth))$/, "teeth"}, 143 | {~r/(?i)(.*)\b((?:goose))$/, "geese"}, 144 | {~r/(?i)(.*)\b((?:foot))$/, "feet"}, 145 | {~r/(?i)(s)tatus$/, "\\g{1}\\g{2}tatuses"}, 146 | {~r/(?i)(quiz)$/, "\\g{1}zes"}, 147 | {~r/(?i)^(ox)$/, "\\g{1}\\g{2}en"}, 148 | {~r/(?i)([m|l])ouse$/, "\\g{1}ice"}, 149 | {~r/(?i)(matr|vert|ind)(ix|ex)$/, "\\g{1}ices"}, 150 | {~r/(?i)(x|ch|ss|sh)$/, "\\g{1}es"}, 151 | {~r/(?i)([^aeiouy]|qu)y$/, "\\g{1}ies"}, 152 | {~r/(?i)(hive)$/, "\\1s"}, 153 | {~r/(?i)(?:([^f])fe|([lre])f)$/, "\\g{1}\\g{2}ves"}, 154 | {~r/(?i)sis$/, "ses"}, 155 | {~r/(?i)([ti])um$/, "\\g{1}a"}, 156 | {~r/(?i)(p)erson$/, "\\g{1}eople"}, 157 | {~r/(?i)(m)an$/, "\\g{1}en"}, 158 | {~r/(?i)(c)hild$/, "\\g{1}hildren"}, 159 | {~r/(?i)(buffal|tomat)o$/, "\\g{1}\\g{2}oes"}, 160 | {~r/(?i)(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/, 161 | "\\g{1}i"}, 162 | {~r/(?i)us$/, "uses"}, 163 | {~r/(?i)(alias)$/, "\\g{1}es"}, 164 | {~r/(?i)(ax|cris|test)is$/, "\\g{1}es"}, 165 | {~r/s$/, "s"}, 166 | {~r/^$/, ""}, 167 | {~r/$/, "s"} 168 | ] 169 | 170 | @singular_regexps Application.get_env(:inflectorex, :singular, []) ++ 171 | [ 172 | {~r/(?i)(.*)\b((?:foes))$/, "foe"}, 173 | {~r/(?i)(.*)\b((?:waves))$/, "wave"}, 174 | {~r/(?i)(.*)\b((?:curves))$/, "curve"}, 175 | {~r/(?i)(.*)\b((?:atlases))$/, "atlas"}, 176 | {~r/(?i)(.*)\b((?:beefs))$/, "beef"}, 177 | {~r/(?i)(.*)\b((?:brothers))$/, "brother"}, 178 | {~r/(?i)(.*)\b((?:cafes))$/, "cafe"}, 179 | {~r/(?i)(.*)\b((?:children))$/, "child"}, 180 | {~r/(?i)(.*)\b((?:cookies))$/, "cookie"}, 181 | {~r/(?i)(.*)\b((?:corpuses))$/, "corpus"}, 182 | {~r/(?i)(.*)\b((?:cows))$/, "cow"}, 183 | {~r/(?i)(.*)\b((?:ganglions))$/, "ganglion"}, 184 | {~r/(?i)(.*)\b((?:genies))$/, "genie"}, 185 | {~r/(?i)(.*)\b((?:genera))$/, "genus"}, 186 | {~r/(?i)(.*)\b((?:graffiti))$/, "graffito"}, 187 | {~r/(?i)(.*)\b((?:hoofs))$/, "hoof"}, 188 | {~r/(?i)(.*)\b((?:loaves))$/, "loaf"}, 189 | {~r/(?i)(.*)\b((?:men))$/, "man"}, 190 | {~r/(?i)(.*)\b((?:monies))$/, "money"}, 191 | {~r/(?i)(.*)\b((?:mongooses))$/, "mongoose"}, 192 | {~r/(?i)(.*)\b((?:moves))$/, "move"}, 193 | {~r/(?i)(.*)\b((?:mythoi))$/, "mythos"}, 194 | {~r/(?i)(.*)\b((?:niches))$/, "niche"}, 195 | {~r/(?i)(.*)\b((?:numina))$/, "numen"}, 196 | {~r/(?i)(.*)\b((?:occiputs))$/, "occiput"}, 197 | {~r/(?i)(.*)\b((?:octopuses))$/, "octopus"}, 198 | {~r/(?i)(.*)\b((?:opuses))$/, "opus"}, 199 | {~r/(?i)(.*)\b((?:oxen))$/, "ox"}, 200 | {~r/(?i)(.*)\b((?:penises))$/, "penis"}, 201 | {~r/(?i)(.*)\b((?:people))$/, "person"}, 202 | {~r/(?i)(.*)\b((?:sexes))$/, "sex"}, 203 | {~r/(?i)(.*)\b((?:soliloquies))$/, "soliloquy"}, 204 | {~r/(?i)(.*)\b((?:testes))$/, "testis"}, 205 | {~r/(?i)(.*)\b((?:trilbys))$/, "trilby"}, 206 | {~r/(?i)(.*)\b((?:turfs))$/, "turf"}, 207 | {~r/(?i)(.*)\b((?:potatoes))$/, "potato"}, 208 | {~r/(?i)(.*)\b((?:heroes))$/, "hero"}, 209 | {~r/(?i)(.*)\b((?:teeth))$/, "tooth"}, 210 | {~r/(?i)(.*)\b((?:geese))$/, "goose"}, 211 | {~r/(?i)(.*)\b((?:feet))$/, "foot"}, 212 | {~r/eaus$/, "eau"}, 213 | {~r/(?i)(s)tatuses$/, "\\g{1}\\g{2}tatus"}, 214 | {~r/(?i)^(.*)(menu)s$/, "\\g{1}\\g{2}"}, 215 | {~r/(?i)(quiz)zes$/, "\\1"}, 216 | {~r/(?i)(matr)ices$/, "\\g{1}ix"}, 217 | {~r/(?i)(vert|ind)ices$/, "\\g{1}ex"}, 218 | {~r/(?i)^(ox)en/, "\\1"}, 219 | {~r/(?i)(alias)(es)*$/, "\\1"}, 220 | {~r/(?i)(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/, 221 | "\\g{1}us"}, 222 | {~r/(?i)([ftw]ax)es/, "\\1"}, 223 | {~r/(?i)(cris|ax|test)es$/, "\\g{1}is"}, 224 | {~r/(?i)(shoe|slave)s$/, "\\1"}, 225 | {~r/(?i)(o)es$/, "\\1"}, 226 | {~r/ouses$/, "ouse"}, 227 | {~r/([^a])uses$/, "\\g{1}us"}, 228 | {~r/(?i)([m|l])ice$/, "\\g{1}ouse"}, 229 | {~r/(?i)(x|ch|ss|sh)es$/, "\\1"}, 230 | {~r/(?i)(m)ovies$/, "\\g{1}\\g{2}ovie"}, 231 | {~r/(?i)(s)eries$/, "\\g{1}\\g{2}eries"}, 232 | {~r/(?i)([^aeiouy]|qu)ies$/, "\\g{1}y"}, 233 | {~r/(?i)(tive)s$/, "\\1"}, 234 | {~r/(?i)([lre])ves$/, "\\g{1}f"}, 235 | {~r/(?i)([^fo])ves$/, "\\g{1}fe"}, 236 | {~r/(?i)(hive)s$/, "\\1"}, 237 | {~r/(?i)(drive)s$/, "\\1"}, 238 | {~r/(?i)(^analy)ses$/, "\\g{1}sis"}, 239 | {~r/(?i)(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/, 240 | "\\g{1}\\g{2}sis"}, 241 | {~r/(?i)([ti])a$/, "\\g{1}um"}, 242 | {~r/(?i)(p)eople$/, "\\g{1}\\g{2}erson"}, 243 | {~r/(?i)(m)en$/, "\\g{1}an"}, 244 | {~r/(?i)(c)hildren$/, "\\g{1}\\g{2}hild"}, 245 | {~r/(?i)(n)ews$/, "\\g{1}\\g{2}ews"}, 246 | {~r/^(.*us)$/, "\\1"}, 247 | {~r/(?i)s$/, ""} 248 | ] 249 | 250 | def in_uniflectable?(word, :singular) do 251 | in_uniflect?(word, @singular_uninflected) || Regex.match?(@uninflected, word) 252 | end 253 | 254 | def in_uniflectable?(word, :plural) do 255 | in_uniflect?(word, @plural_uninflected) || Regex.match?(@uninflected, word) 256 | end 257 | 258 | defp in_uniflect?(word, set) do 259 | case Enum.find(set, fn regex -> Regex.match?(regex, word) end) do 260 | nil -> false 261 | _ -> true 262 | end 263 | end 264 | 265 | def pluralize(word) do 266 | if in_uniflectable?(word, :plural) do 267 | word 268 | else 269 | find_replace(word, @plural_regexps) 270 | end 271 | end 272 | 273 | def singularize(word) do 274 | if in_uniflectable?(word, :singular) do 275 | word 276 | else 277 | find_replace(word, @singular_regexps) 278 | end 279 | end 280 | 281 | defp find_replace(word, set) do 282 | # will match to a pattern unless singular passed to singularize and plural 283 | # passed to pluralize 284 | case Enum.find(set, nil, fn {regex, _} -> Regex.match?(regex, word) end) do 285 | # can't try any further lets return the same thing 286 | nil -> word 287 | {regex, repl} -> Regex.replace(regex, word, repl) 288 | end 289 | end 290 | end 291 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Inflectorex.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :inflectorex, 7 | version: "0.1.2", 8 | elixir: "~> 1.4", 9 | build_embedded: Mix.env() == :prod, 10 | start_permanent: Mix.env() == :prod, 11 | deps: deps(), 12 | package: package(), 13 | description: description() 14 | ] 15 | end 16 | 17 | def application do 18 | [extra_applications: [:logger], mod: {Inflectorex, []}] 19 | end 20 | 21 | defp deps do 22 | [{:ex_doc, ">= 0.0.0", only: :dev}] 23 | end 24 | 25 | defp description do 26 | """ 27 | Singularize and pluralize english nouns. 28 | """ 29 | end 30 | 31 | defp package do 32 | [ 33 | name: :inflectorex, 34 | files: ["lib", "mix.exs", "README*", "LICENSE*"], 35 | maintainers: ["Girish Ramnani"], 36 | licenses: ["MIT"], 37 | links: %{"GitHub" => "https://github.com/girishramnani/inflector"} 38 | ] 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"earmark": {:hex, :earmark, "1.2.0", "bf1ce17aea43ab62f6943b97bd6e3dc032ce45d4f787504e3adf738e54b42f3a", [:mix], []}, 2 | "ex_doc": {:hex, :ex_doc, "0.15.0", "e73333785eef3488cf9144a6e847d3d647e67d02bd6fdac500687854dd5c599f", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]}} 3 | -------------------------------------------------------------------------------- /test/inflectorex_test.exs: -------------------------------------------------------------------------------- 1 | defmodule InflectorTest do 2 | use ExUnit.Case 3 | 4 | @pluralize_inputs [ 5 | {"categoria", "categorias"}, 6 | {"house", "houses"}, 7 | {"powerhouse", "powerhouses"}, 8 | {"Bus", "Buses"}, 9 | {"bus", "buses"}, 10 | {"menu", "menus"}, 11 | {"news", "news"}, 12 | {"food_menu", "food_menus"}, 13 | {"Menu", "Menus"}, 14 | {"FoodMenu", "FoodMenus"}, 15 | {"quiz", "quizzes"}, 16 | {"matrix_row", "matrix_rows"}, 17 | {"matrix", "matrices"}, 18 | {"vertex", "vertices"}, 19 | {"index", "indices"}, 20 | {"Alias", "Aliases"}, 21 | {"Aliases", "Aliases"}, 22 | {"Media", "Media"}, 23 | {"NodeMedia", "NodeMedia"}, 24 | {"alumnus", "alumni"}, 25 | {"bacillus", "bacilli"}, 26 | {"cactus", "cacti"}, 27 | {"focus", "foci"}, 28 | {"fungus", "fungi"}, 29 | {"nucleus", "nuclei"}, 30 | {"octopus", "octopuses"}, 31 | {"radius", "radii"}, 32 | {"stimulus", "stimuli"}, 33 | {"syllabus", "syllabi"}, 34 | {"terminus", "termini"}, 35 | {"virus", "viri"}, 36 | {"person", "people"}, 37 | {"people", "people"}, 38 | {"glove", "gloves"}, 39 | {"crisis", "crises"}, 40 | {"tax", "taxes"}, 41 | {"wave", "waves"}, 42 | {"bureau", "bureaus"}, 43 | {"cafe", "cafes"}, 44 | {"roof", "roofs"}, 45 | {"foe", "foes"}, 46 | {"cookie", "cookies"}, 47 | {"wolf", "wolves"}, 48 | {"thief", "thieves"}, 49 | {"potato", "potatoes"}, 50 | {"hero", "heroes"}, 51 | {"buffalo", "buffalo"}, 52 | {"tooth", "teeth"}, 53 | {"goose", "geese"}, 54 | {"foot", "feet"}, 55 | {"objective", "objectives"}, 56 | {"specie", "species"}, 57 | {"species", "species"}, 58 | {"", ""} 59 | ] 60 | 61 | test "pluralize some words" do 62 | assert Enum.all?(@pluralize_inputs, fn {input, output} -> 63 | Inflectorex.pluralize(input) == output 64 | end) == true 65 | end 66 | 67 | @singularize_inputs [ 68 | {"categorias", "categoria"}, 69 | {"menus", "menu"}, 70 | {"news", "news"}, 71 | {"food_menus", "food_menu"}, 72 | {"Menus", "Menu"}, 73 | {"FoodMenus", "FoodMenu"}, 74 | {"houses", "house"}, 75 | {"powerhouses", "powerhouse"}, 76 | {"quizzes", "quiz"}, 77 | {"Buses", "Bus"}, 78 | {"buses", "bus"}, 79 | {"matrix_rows", "matrix_row"}, 80 | {"matrices", "matrix"}, 81 | {"vertices", "vertex"}, 82 | {"indices", "index"}, 83 | {"Aliases", "Alias"}, 84 | {"Alias", "Alias"}, 85 | {"Media", "Media"}, 86 | {"NodeMedia", "NodeMedia"}, 87 | {"alumni", "alumnus"}, 88 | {"bacilli", "bacillus"}, 89 | {"cacti", "cactus"}, 90 | {"foci", "focus"}, 91 | {"fungi", "fungus"}, 92 | {"nuclei", "nucleus"}, 93 | {"octopuses", "octopus"}, 94 | {"radii", "radius"}, 95 | {"stimuli", "stimulus"}, 96 | {"syllabi", "syllabus"}, 97 | {"termini", "terminus"}, 98 | {"viri", "virus"}, 99 | {"people", "person"}, 100 | {"gloves", "glove"}, 101 | {"doves", "dove"}, 102 | {"lives", "life"}, 103 | {"knives", "knife"}, 104 | {"wolves", "wolf"}, 105 | {"slaves", "slave"}, 106 | {"shelves", "shelf"}, 107 | {"taxis", "taxi"}, 108 | {"taxes", "tax"}, 109 | {"Taxes", "Tax"}, 110 | {"AwesomeTaxes", "AwesomeTax"}, 111 | {"faxes", "fax"}, 112 | {"waxes", "wax"}, 113 | {"niches", "niche"}, 114 | {"waves", "wave"}, 115 | {"bureaus", "bureau"}, 116 | {"genetic_analyses", "genetic_analysis"}, 117 | {"doctor_diagnoses", "doctor_diagnosis"}, 118 | {"parantheses", "paranthesis"}, 119 | {"Causes", "Cause"}, 120 | {"colossuses", "colossus"}, 121 | {"diagnoses", "diagnosis"}, 122 | {"bases", "basis"}, 123 | {"analyses", "analysis"}, 124 | {"curves", "curve"}, 125 | {"cafes", "cafe"}, 126 | {"roofs", "roof"}, 127 | {"foes", "foe"}, 128 | {"databases", "database"}, 129 | {"cookies", "cookie"}, 130 | {"thieves", "thief"}, 131 | {"potatoes", "potato"}, 132 | {"heroes", "hero"}, 133 | {"buffalos", "buffalo"}, 134 | {"babies", "baby"}, 135 | {"teeth", "tooth"}, 136 | {"geese", "goose"}, 137 | {"feet", "foot"}, 138 | {"objectives", "objective"}, 139 | {"species", "species"}, 140 | {"", ""} 141 | ] 142 | 143 | test "singularize some words" do 144 | assert Enum.all?(@singularize_inputs, fn {input, output} -> 145 | Inflectorex.singularize(input) == output 146 | end) == true 147 | end 148 | end 149 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------