lib/kaffy/inflector.ex

defmodule Kaffy.Inflector do
  @moduledoc """
  Module holding functions related to inflection of words. It does not attempt to
  be perfect, rather aiming at being "good enough".
  """

  @exceptions %{
    "Person" => "People",
    "Roof" => "Roofs",
    "Belief" => "Beliefs",
    "Chef" => "Chefs",
    "Chief" => "Chiefs",
    "Index" => "Indices",
    "Sheep" => "Sheep",
    "Series" => "Series",
    "Species" => "Species",
    "Deer" => "Deer",
    "Child" => "Children",
    "Man" => "Men",
    "Woman" => "Women",
    "Goose" => "Geese",
    "Tooth" => "Teeth",
    "Foot" => "Feet",
    "Mouse" => "Mice"
  }

  @vowels ["a", "e", "i", "o", "u", "y"]

  @doc """
  Attempts to create a correct plural version by following rules outlined at:
  https://www.grammarly.com/blog/plural-nouns/

  Some of these rules are conscously ommited as being rare and hard to implement.
  """
  def pluralize(noun) when is_binary(noun) do
    case Map.has_key?(@exceptions, noun) do
      true -> @exceptions[noun]
      false -> try_to_pluralize(noun)
    end
  end

  defp try_to_pluralize(noun) do
    reversed_noun = String.reverse(noun)

    case reversed_noun do
      <<"si", rest::binary>> ->
        String.reverse(rest) <> "es"

      <<"s", _rest::binary>> ->
        noun <> "es"

      <<"hs", _rest::binary>> ->
        noun <> "es"

      <<"hc", _rest::binary>> ->
        noun <> "es"

      <<"x", _rest::binary>> ->
        noun <> "es"

      <<"z", _rest::binary>> ->
        noun <> "es"

      <<"ef", rest::binary>> ->
        String.reverse(rest) <> "ves"

      <<"f", rest::binary>> ->
        String.reverse(rest) <> "ves"

      <<"y", preceding::binary-size(1), rest::binary>> ->
        case preceding in @vowels do
          true -> noun <> "s"
          false -> String.reverse(rest) <> preceding <> "ies"
        end

      _ ->
        noun <> "s"
    end
  end
end