[
  {
    "path": ".coveragerc",
    "content": "[run]\nomit =\n\t# leading `*/` for pytest-dev/pytest-cov#456\n\t*/.tox/*\ndisable_warnings =\n\tcouldnt-parse\n\n[report]\nshow_missing = True\nexclude_also =\n\t# Exclude common false positives per\n\t# https://coverage.readthedocs.io/en/latest/excluding.html#advanced-exclusion\n\t# Ref jaraco/skeleton#97 and jaraco/skeleton#135\n\tclass .*\\bProtocol\\):\n\tif TYPE_CHECKING:\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nindent_style = tab\nindent_size = 4\ninsert_final_newline = true\nend_of_line = lf\n\n[*.py]\nindent_style = space\nmax_line_length = 88\n\n[*.{yml,yaml}]\nindent_style = space\nindent_size = 2\n\n[*.rst]\nindent_style = space\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "tidelift: pypi/inflect\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "name: tests\n\non:\n  merge_group:\n  push:\n    branches-ignore:\n    # temporary GH branches relating to merge queues (jaraco/skeleton#93)\n    - gh-readonly-queue/**\n    tags:\n    # required if branches-ignore is supplied (jaraco/skeleton#103)\n    - '**'\n  pull_request:\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\nenv:\n  # Environment variable to support color support (jaraco/skeleton#66)\n  FORCE_COLOR: 1\n\n  # Suppress noisy pip warnings\n  PIP_DISABLE_PIP_VERSION_CHECK: 'true'\n  PIP_NO_WARN_SCRIPT_LOCATION: 'true'\n\n  # Ensure tests can sense settings about the environment\n  TOX_OVERRIDE: >-\n    testenv.pass_env+=GITHUB_*,FORCE_COLOR\n\n\njobs:\n  test:\n    strategy:\n      # https://blog.jaraco.com/efficient-use-of-ci-resources/\n      matrix:\n        python:\n        - \"3.10\"\n        - \"3.13\"\n        platform:\n        - ubuntu-latest\n        - macos-latest\n        - windows-latest\n        include:\n        - python: \"3.11\"\n          platform: ubuntu-latest\n        - python: \"3.12\"\n          platform: ubuntu-latest\n        - python: \"3.14\"\n          platform: ubuntu-latest\n        - python: \"3.15\"\n          platform: ubuntu-latest\n        - python: pypy3.10\n          platform: ubuntu-latest\n        - python: \"3.x\"\n          platform: ubuntu-latest\n    runs-on: ${{ matrix.platform }}\n    continue-on-error: ${{ matrix.python == '3.15' }}\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install build dependencies\n        # Install dependencies for building packages on pre-release Pythons\n        # jaraco/skeleton#161\n        if: matrix.python == '3.15' && matrix.platform == 'ubuntu-latest'\n        run: |\n          sudo apt update\n          sudo apt install -y libxml2-dev libxslt-dev\n      - name: Setup Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: ${{ matrix.python }}\n          allow-prereleases: true\n      - name: Install tox\n        run: python -m pip install tox\n      - name: Run\n        run: tox\n\n  collateral:\n    strategy:\n      fail-fast: false\n      matrix:\n        job:\n        - diffcov\n        - docs\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - name: Setup Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: 3.x\n      - name: Install tox\n        run: python -m pip install tox\n      - name: Eval ${{ matrix.job }}\n        run: tox -e ${{ matrix.job }}\n\n  check:  # This job does nothing and is only used for the branch protection\n    if: always()\n\n    needs:\n    - test\n    - collateral\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Decide whether the needed jobs succeeded or failed\n      uses: re-actors/alls-green@release/v1\n      with:\n        jobs: ${{ toJSON(needs) }}\n\n  release:\n    permissions:\n      contents: write\n    needs:\n    - check\n    if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: 3.x\n      - name: Install tox\n        run: python -m pip install tox\n      - name: Run\n        run: tox -e release\n        env:\n          TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "*.pyc\nMANIFEST\ndist/*\ncover/*\nhtmlcov/*\n.tox/*\n*.egg-info\n.coverage\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n- repo: https://github.com/astral-sh/ruff-pre-commit\n  rev: v0.12.0\n  hooks:\n  - id: ruff-check\n    args: [--fix, --unsafe-fixes]\n  - id: ruff-format\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "version: 2\npython:\n  install:\n  - path: .\n    extra_requirements:\n      - doc\n\nsphinx:\n  configuration: docs/conf.py\n\n# required boilerplate readthedocs/readthedocs.org#10401\nbuild:\n  os: ubuntu-lts-latest\n  tools:\n    python: latest\n  # post-checkout job to ensure the clone isn't shallow jaraco/skeleton#114\n  jobs:\n    post_checkout:\n    - git fetch --unshallow || true\n"
  },
  {
    "path": "NEWS.rst",
    "content": "v7.5.0\n======\n\nFeatures\n--------\n\n- Updated `ast` classes for Python 3.14 compatibility. (#225)\n\n\nv7.4.0\n======\n\nFeatures\n--------\n\n- Handle a single apostrophe more gracefully. (#218)\n\n\nv7.3.1\n======\n\nBugfixes\n--------\n\n- Set minimum version of more-itertools to 8.5 (#215)\n\n\nv7.3.0\n======\n\nFeatures\n--------\n\n- Restricted typing_extensions to Python 3.8. (#211)\n\n\nv7.2.1\n======\n\nBugfixes\n--------\n\n- Refactored number_to_words toward reduced complexity.\n\n\nv7.2.0\n======\n\nFeatures\n--------\n\n- Replace pydantic with typeguard (#195)\n\n\nv7.1.0\n======\n\nFeatures\n--------\n\n- Now handle 'pair of x' in pl_sb_uninflected_complete (#188)\n\n\nv7.0.0\n======\n\nFeatures\n--------\n\n- Refine type hint for ``singular_noun`` to indicate a literal return type for ``False``. (#186)\n\n\nDeprecations and Removals\n-------------------------\n\n- Removed methods renamed in 0.2.0.\n\n\nv6.2.0\n======\n\nFeatures\n--------\n\n- Project now supports Pydantic 2 while retaining support for Pydantic 1. (#187)\n\n\nBugfixes\n--------\n\n- Added validation of user-defined words and amended the type declarations to match, allowing for null values but not empty strings. (#187)\n\n\nv6.1.1\n======\n\nBugfixes\n--------\n\n- ``ordinal`` now handles float types correctly without first coercing them to strings. (#178)\n\n\nv6.1.0\n======\n\nFeatures\n--------\n\n- Require Python 3.8 or later.\n\n\nv6.0.5\n======\n\n* #187: Pin to Pydantic 1 to avoid breaking in Pydantic 2.\n\nv6.0.4\n======\n\n* Internal cleanup.\n\nv6.0.3\n======\n\n* #136: A/an support now more correctly honors leading\n  capitalized words and abbreviations.\n\n* #178: Improve support for ordinals for floats.\n\nv6.0.2\n======\n\n* #169: Require pydantic 1.9.1 to avoid ``ValueError``.\n\nv6.0.1\n======\n\n* Minor tweaks and packaging refresh.\n\nv6.0.0\n======\n\n* #157: ``compare`` methods now validate their inputs\n  and will raise a more meaningful exception if an\n  empty string or None is passed. This expectation is now\n  documented.\n\n* Many public methods now perform validation on arguments.\n  An empty string is no longer allowed for words or text.\n  Callers are expected to pass non-empty text or trap\n  the validation errors that are raised. The exceptions\n  raised are ``pydantic.error_wrappers.ValidationError``,\n  which are currently a subclass of ``ValueError``, but since\n  that\n  `may change <https://pydantic-docs.helpmanual.io/usage/validation_decorator/#validation-exception>`_,\n  tests check for a generic ``Exception``.\n\nv5.6.2\n======\n\n* #15: Fixes to plural edge case handling.\n\nv5.6.1\n======\n\n* Packaging refresh and docs update.\n\nv5.6.0\n======\n\n* #153: Internal refactor to simplify and unify\n  ``_plnoun`` and ``_sinoun``.\n\nv5.5.2\n======\n\n* Fixed badges.\n\nv5.5.1\n======\n\n* #150: Rewrite to satisfy type checkers.\n\nv5.5.0\n======\n\n* #147: Enhanced type annotations.\n\nv5.4.0\n======\n\n* #133: Add a ``py.typed`` file so mypy recognizes type annotations.\n* Misc fixes in #128, #134, #135, #137, #138, #139, #140, #142,\n  #143, #144.\n* Require Python 3.7 or later.\n\nv5.3.0\n======\n\n* #108: Add support for pluralizing open compound nouns.\n\nv5.2.0\n======\n\n* #121: Modernized the codebase. Added a lot of type annotations.\n\nv5.1.0\n======\n\n* #113: Add support for uncountable nouns.\n\nv5.0.3\n======\n\n* Refreshed package metadata.\n\nv5.0.2\n======\n\n* #102: Inflect withdraws from `Jazzband <https://jazzband.co>`_\n  in order to continue to participate in sustained maintenance\n  and enterprise support through `Tidelift <https://tidelift.com>`_.\n  The project continues to honor the guidelines and principles\n  behind Jazzband and welcomes contributors openly.\n\nv5.0.1\n======\n\n* Identical release validating release process.\n\nv5.0.0\n======\n\n* Module no longer exposes a ``__version__`` attribute. Instead\n  to query the version installed, use\n  `importlib.metadata <https://docs.python.org/3/library/importlib.metadata.html>`_\n  or `its backport <https://pypi.org/project/importlib_metadata>`_\n  to query::\n\n    importlib.metadata.version('inflect')\n\nv4.1.1\n======\n\n* Refreshed package metadata.\n\nv4.1.0\n======\n\n* #95: Certain operations now allow ignore arbitrary leading words.\n\nv4.0.0\n======\n\n* Require Python 3.6 or later.\n\nv3.0.2\n======\n\n* #88: Distribution no longer includes root ``tests`` package.\n\nv3.0.1\n======\n\n* Project now builds on jaraco/skeleton for shared package\n  management.\n\nv3.0.0\n======\n\n* #75: Drop support for Python 3.4.\n\nv2.1.0\n======\n\n* #29: Relicensed under the more permissive MIT License.\n\nv2.0.1\n======\n\n* #57: Fix pluralization of taco.\n\nv2.0.0\n======\n\n* #37: fix inconsistencies with the inflect method\n\n  We now build and parse AST to extract function arguments instead of relying\n  on regular expressions. This also adds support for keyword arguments and\n  built-in constants when calling functions in the string.\n  Unfortunately, this is not backwards compatible in some cases:\n* Strings should now be wrapped in single or double quotes\n  p.inflect(\"singular_noun(to them)\") should now be p.inflect(\"singular_noun('to them')\")\n* Empty second argument to a function will now be parsed as None instead of ''.\n  p.inflect(\"num(%d,) eggs\" % 2) now prints \"2 eggs\" instead of \" eggs\"\n  Since None, True and False are now supported, they can be passed explicitly:\n  p.inflect(\"num(%d, False) eggs\" % 2) will print \" eggs\"\n  p.inflect(\"num(%d, True) eggs\" % 2) will print \"2 eggs\"\n\nv1.0.2\n======\n\n* #53: Improved unicode handling.\n* #5 and #40 via #55: Fix capitalization issues in processes where\n  more than one word is involved.\n* #56: Handle correctly units containing 'degree' and 'per'.\n\nv1.0.1\n======\n\n* #31: fix extraneous close parentheses.\n\nv1.0.0\n======\n\n* Dropped support for Python 3.3.\n\nv0.3.1\n======\n\n* Fixed badges in readme.\n\nv0.3.0\n======\n\n* Moved hosting to the `jazzband project on GitHub <https://github.com/jazzband/inflect>`_.\n\nv0.2.5\n======\n\n* Fixed TypeError while parsing compounds (by yavarhusain)\n* Fixed encoding issue in setup.py on Python 3\n\n\nv0.2.4\n======\n\n* new maintainer (Alex Grönholm)\n* added Python 3 compatibility (by Thorben Krüger)\n\n\nv0.2.3\n======\n\n* fix a/an for dishonor, Honolulu, mpeg, onetime, Ugandan, Ukrainian,\n  Unabomber, unanimous, US\n* merge in 'subspecies' fix by UltraNurd\n* add arboretum to classical plurals\n* prevent crash with singular_noun('ys')\n\n\nv0.2.2\n======\n\n* change numwords to number_to_words in strings\n* improve some docstrings\n* comment out imports for unused .inflectrc\n* remove unused exception class\n\n\nv0.2.1\n======\n\n* remove incorrect gnome_sudoku import\n\n\nv0.2.0\n======\n\n* add gender() to select the gender of singular pronouns\n* replace short named methods with longer methods. shorted method now print a message and raise DecrecationWarning\n\n  pl -> plural\n\n  plnoun -> plural_noun\n\n  plverb -> plural_verb\n\n  pladj -> plural_adjective\n\n  sinoun -> singular_noun\n\n  prespart -> present_participle\n\n  numwords -> number_to_words\n\n  plequal -> compare\n\n  plnounequal -> compare_nouns\n\n  plverbequal -> compare_verbs\n\n  pladjequal -> compare_adjs\n\n  wordlist -> join\n* change classical() to only accept keyword args: only one way to do it\n* fix bug in numwords where hundreds was giving the wrong number when group=3\n\n\nv0.1.8\n======\n\n* add line to setup showing that this provides 'inflect' so that\n  inflect_dj can require it\n* add the rest of the tests from the Perl version\n\n\nv0.1.7\n======\n\n* replace most of the regular expressions in _plnoun and _sinoun. They run several times faster now.\n\n\nv0.1.6\n======\n\n* add method sinoun() to generate the singular of a plural noun. Phew!\n* add changes from new Perl version: 1.892\n* start adding tests from Perl version\n* add test to check sinoun(plnoun(word)) == word\n  Can now use word lists to check these methods without needing to have\n  a list of plurals. ;-)\n* fix die -> dice\n"
  },
  {
    "path": "README.rst",
    "content": ".. image:: https://img.shields.io/pypi/v/inflect.svg\n   :target: https://pypi.org/project/inflect\n\n.. image:: https://img.shields.io/pypi/pyversions/inflect.svg\n\n.. image:: https://github.com/jaraco/inflect/actions/workflows/main.yml/badge.svg\n   :target: https://github.com/jaraco/inflect/actions?query=workflow%3A%22tests%22\n   :alt: tests\n\n.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json\n    :target: https://github.com/astral-sh/ruff\n    :alt: Ruff\n\n.. image:: https://readthedocs.org/projects/inflect/badge/?version=latest\n   :target: https://inflect.readthedocs.io/en/latest/?badge=latest\n\n.. image:: https://img.shields.io/badge/skeleton-2026-informational\n   :target: https://blog.jaraco.com/skeleton\n\n.. image:: https://tidelift.com/badges/package/pypi/inflect\n   :target: https://tidelift.com/subscription/pkg/pypi-inflect?utm_source=pypi-inflect&utm_medium=readme\n\nNAME\n====\n\ninflect.py - Accurately generate plurals, singular nouns, ordinals, indefinite articles, and word-based representations of numbers. This functionality is limited to English.\n\nSYNOPSIS\n========\n\n.. code-block:: python\n    \n    >>> import inflect\n    >>> p = inflect.engine()\n\nSimple example with pluralization and word-representation of numbers:\n\n.. code-block:: python\n    \n    >>> count=1\n    >>> print('There', p.plural_verb('was', count), p.number_to_words(count), p.plural_noun('person', count), 'by the door.')\n    There was one person by the door.\n\nWhen ``count=243``, the same code will generate:\n\n.. code-block:: python\n    \n    There were two hundred and forty-three people by the door.\n\n\nMethods\n=======\n\n- ``plural``, ``plural_noun``, ``plural_verb``, ``plural_adj``, ``singular_noun``, ``no``, ``num``\n- ``compare``, ``compare_nouns``, ``compare_nouns``, ``compare_adjs``\n- ``a``, ``an``\n- ``present_participle``\n- ``ordinal``, ``number_to_words``\n- ``join``\n- ``inflect``, ``classical``, ``gender``\n- ``defnoun``, ``defverb``, ``defadj``, ``defa``, ``defan``\n\nPlurality/Singularity\n---------------------\nUnconditionally Form the Plural\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code-block:: python\n    \n    >>> \"the plural of person is \" + p.plural(\"person\")\n    'the plural of person is people'\n\nConditionally Form the Plural\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code-block:: python\n    \n    >>> \"the plural of 1 person is \" + p.plural(\"person\", 1)\n    'the plural of 1 person is person'\n\nForm Plurals for Specific Parts of Speech\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code-block:: python\n    \n    >>> p.plural_noun(\"I\", 2)\n    'we'\n    >>> p.plural_verb(\"saw\", 1)\n    'saw'\n    >>> p.plural_adj(\"my\", 2)\n    'our'\n    >>> p.plural_noun(\"saw\", 2)\n    'saws'\n\nForm the Singular of Plural Nouns\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code-block:: python\n    \n    >>> \"The singular of people is \" + p.singular_noun(\"people\")\n    'The singular of people is person'\n\nSelect the Gender of Singular Pronouns\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code-block:: python\n    \n    >>> p.singular_noun(\"they\")\n    'it'\n    >>> p.gender(\"feminine\")\n    >>> p.singular_noun(\"they\")\n    'she'\n\nDeal with \"0/1/N\" -> \"no/1/N\" Translation\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code-block:: python\n    \n    >>> errors = 1\n    >>> \"There \", p.plural_verb(\"was\", errors), p.no(\" error\", errors)\n    ('There ', 'was', ' 1 error')\n    >>> errors = 2\n    >>> \"There \", p.plural_verb(\"was\", errors), p.no(\" error\", errors)\n    ('There ', 'were', ' 2 errors')\n\nUse Default Counts\n^^^^^^^^^^^^^^^^^^\n\n.. code-block:: python\n    \n    >>> p.num(1, \"\")\n    ''\n    >>> p.plural(\"I\")\n    'I'\n    >>> p.plural_verb(\" saw\")\n    ' saw'\n    >>> p.num(2)\n    '2'\n    >>> p.plural_noun(\" saw\")\n    ' saws'\n    >>> \"There \", p.num(errors, \"\"), p.plural_verb(\"was\"), p.no(\" error\")\n    ('There ', '', 'were', ' 2 errors')\n\nCompare Two Words Number-Intensitively\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code-block:: python\n    \n    >>> p.compare('person', 'person')\n    'eq'\n    >>> p.compare('person', 'people')\n    's:p'\n    >>> p.compare_nouns('person', 'people')\n    's:p'\n    >>> p.compare_verbs('run', 'ran')\n    False\n    >>> p.compare_verbs('run', 'running')\n    False\n    >>> p.compare_verbs('run', 'run')\n    'eq'\n    >>> p.compare_adjs('my', 'mine')\n    False\n    >>> p.compare_adjs('my', 'our')\n    's:p'\n\nAdd Correct *a* or *an* for a Given Word\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code-block:: python\n    \n    >>> \"Did you want \", p.a('thing'), \" or \", p.a('idea')\n    ('Did you want ', 'a thing', ' or ', 'an idea')\n\nConvert Numerals into Ordinals\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code-block:: python\n    \n    >>> \"It was\", p.ordinal(1), \" from the left\"\n    ('It was', '1st', ' from the left')\n    >>> \"It was\", p.ordinal(2), \" from the left\"\n    ('It was', '2nd', ' from the left')\n    >>> \"It was\", p.ordinal(3), \" from the left\"\n    ('It was', '3rd', ' from the left')\n    >>> \"It was\", p.ordinal(347), \" from the left\"\n    ('It was', '347th', ' from the left')\n\nConvert Numerals to Words\n^^^^^^^^^^^^^^^^^^^^^^^^^\nNote: This returns a single string.\n\n.. code-block:: python\n    \n    >>> p.number_to_words(1)\n    'one'\n    >>> p.number_to_words(38)\n    'thirty-eight'\n    >>> p.number_to_words(1234)\n    'one thousand, two hundred and thirty-four'\n    >>> p.number_to_words(p.ordinal(1234))\n    'one thousand, two hundred and thirty-fourth'\n\nRetrieve Words as List of Parts\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code-block:: python\n    \n    >>> p.number_to_words(1234, wantlist=True)\n    ['one thousand', 'two hundred and thirty-four']\n\nGrouping Options\n^^^^^^^^^^^^^^^^\n\n.. code-block:: python\n    \n    >>> p.number_to_words(12345, group=1)\n    'one, two, three, four, five'\n    >>> p.number_to_words(12345, group=2)\n    'twelve, thirty-four, five'\n    >>> p.number_to_words(12345, group=3)\n    'one twenty-three, forty-five'\n    >>> p.number_to_words(1234, andword=\"\")\n    'one thousand, two hundred thirty-four'\n    >>> p.number_to_words(1234, andword=\", plus\")\n    'one thousand, two hundred, plus thirty-four'\n    >>> p.number_to_words(555_1202, group=1, zero=\"oh\")\n    'five, five, five, one, two, oh, two'\n    >>> p.number_to_words(555_1202, group=1, one=\"unity\")\n    'five, five, five, unity, two, zero, two'\n    >>> p.number_to_words(123.456, group=1, decimal=\"mark\")\n    'one, two, three, mark, four, five, six'\n\nApply Threshold for Word-Representation of Numbers\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nAbove provided threshold, numberals will remain numerals\n\n.. code-block:: python\n    \n    >>> p.number_to_words(9, threshold=10)\n    'nine'\n    >>> p.number_to_words(10, threshold=10)\n    'ten'\n    >>> p.number_to_words(11, threshold=10)\n    '11'\n    >>> p.number_to_words(1000, threshold=10)\n    '1,000'\n\nJoin Words into a List\n^^^^^^^^^^^^^^^^^^^^^^\n\n.. code-block:: python\n    \n    >>> p.join((\"apple\", \"banana\", \"carrot\"))\n    'apple, banana, and carrot'\n    >>> p.join((\"apple\", \"banana\"))\n    'apple and banana'\n    >>> p.join((\"apple\", \"banana\", \"carrot\"), final_sep=\"\")\n    'apple, banana and carrot'\n    >>> p.join(('apples', 'bananas', 'carrots'), conj='and even')\n    'apples, bananas, and even carrots'\n    >>> p.join(('apple', 'banana', 'carrot'), sep='/', sep_spaced=False, conj='', conj_spaced=False)\n    'apple/banana/carrot'\n    \nRequire Classical Plurals\n^^^^^^^^^^^^^^^^^^^^^^^^^\nAdhere to conventions from Classical Latin and Classical Greek\n\n.. code-block:: python\n        \n    >>> p.classical()\n    >>> p.plural_noun(\"focus\", 2)\n    'foci'\n    >>> p.plural_noun(\"cherubim\", 2)\n    'cherubims'\n    >>> p.plural_noun(\"cherub\", 2)\n    'cherubim'\n\nOther options for classical plurals:\n\n.. code-block:: python\n    \n    p.classical(all=True)  # USE ALL CLASSICAL PLURALS\n    p.classical(all=False)  # SWITCH OFF CLASSICAL MODE\n    \n    p.classical(zero=True)  #  \"no error\" INSTEAD OF \"no errors\"\n    p.classical(zero=False)  #  \"no errors\" INSTEAD OF \"no error\"\n    \n    p.classical(herd=True)  #  \"2 buffalo\" INSTEAD OF \"2 buffalos\"\n    p.classical(herd=False)  #  \"2 buffalos\" INSTEAD OF \"2 buffalo\"\n    \n    p.classical(persons=True)  # \"2 chairpersons\" INSTEAD OF \"2 chairpeople\"\n    p.classical(persons=False)  # \"2 chairpeople\" INSTEAD OF \"2 chairpersons\"\n    \n    p.classical(ancient=True)  # \"2 formulae\" INSTEAD OF \"2 formulas\"\n    p.classical(ancient=False)  # \"2 formulas\" INSTEAD OF \"2 formulae\"\n\n\nSupport for interpolation\n^^^^^^^^^^^^^^^^^^^^^^^^^\nSupports string interpolation with the following functions: ``plural()``, ``plural_noun()``, ``plural_verb()``, ``plural_adj()``, ``singular_noun()``, ``a()``, ``an()``, ``num()`` and ``ordinal()``.\n\n.. code-block:: python\n    \n    >>> p.inflect(\"The plural of {0} is plural('{0}')\".format('car'))\n    'The plural of car is cars'\n    >>> p.inflect(\"The singular of {0} is singular_noun('{0}')\".format('car'))\n    'The singular of car is car'\n    >>> p.inflect(\"I saw {0} plural('cat',{0})\".format(3))\n    'I saw 3 cats'\n    >>> p.inflect(\n    ...     \"plural('I',{0}) \"\n    ...     \"plural_verb('saw',{0}) \"\n    ...     \"plural('a',{1}) \"\n    ...     \"plural_noun('saw',{1})\".format(1, 2)\n    ... )\n    'I saw some saws'\n    >>> p.inflect(\n    ...     \"num({0}, False)plural('I') \"\n    ...     \"plural_verb('saw') \"\n    ...     \"num({1}, False)plural('a') \"\n    ...     \"plural_noun('saw')\".format(N1, 1)\n    ... )\n    'I saw a saw'\n    >>> p.inflect(\n    ...     \"num({0}, False)plural('I') \"\n    ...     \"plural_verb('saw') \"\n    ...     \"num({1}, False)plural('a') \"\n    ...     \"plural_noun('saw')\".format(2, 2)\n    ... )\n    'we saw some saws'\n    >>> p.inflect(\"I saw num({0}) plural('cat')\\nnum()\".format(cat_count))\n    'I saw 3 cats\\n'\n    >>> p.inflect(\"There plural_verb('was',{0}) no('error',{0})\".format(errors))\n    'There were 2 errors'\n    >>> p.inflect(\"There num({0}, False)plural_verb('was') no('error')\".format(errors))\n    'There were 2 errors'\n    >>> p.inflect(\"Did you want a('{0}') or an('{1}')\".format(thing, idea))\n    'Did you want a thing or an idea'\n    >>> p.inflect(\"It was ordinal('{0}') from the left\".format(2))\n    'It was 2nd from the left'\n\nAdd User-Defined Inflections\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nAllows for overriding default rules.\n\nOverride noun defaults:\n\n.. code-block:: python\n        \n    p.defnoun(\"VAX\", \"VAXen\")  # SINGULAR => PLURAL\n\nOverride Verb defaults:\n\n.. code-block:: python\n    \n    p.defverb(\n        \"will\",  # 1ST PERSON SINGULAR\n        \"shall\",  # 1ST PERSON PLURAL\n        \"will\",  # 2ND PERSON SINGULAR\n        \"will\",  # 2ND PERSON PLURAL\n        \"will\",  # 3RD PERSON SINGULAR\n        \"will\",  # 3RD PERSON PLURAL\n    )\n\nOverride adjective defaults:\n\n.. code-block:: python\n    \n    >>> p.defadj('hir', 'their')\n    1\n    >>> p.plural_adj('hir', 2)\n    'their'\n\nOverride the words that use the indefinite articles \"a\" or \"an\":\n\n.. code-block:: python\n    \n    >>> p.a('ape', 1)\n    'an ape'\n    >>> p.defa('a')\n    1\n    >>> p.a('ape', 1)\n    'an ape'\n    >>> p.defa('ape')\n    1\n    >>> p.a('ape', 1)\n    'a ape'\n    >>> p.defan('horrendous.*')\n    1\n    >>> p.a('horrendous affectation', 1)\n    'an horrendous affectation'\n    >>> \n\n\nDESCRIPTION\n===========\n\nThe methods of the class ``engine`` in module ``inflect.py`` provide plural\ninflections, singular noun inflections, \"a\"/\"an\" selection for English words,\nand manipulation of numbers as words.\n\nPlural forms of all nouns, most verbs, and some adjectives are\nprovided. Where appropriate, \"classical\" variants (for example: \"brother\" ->\n\"brethren\", \"dogma\" -> \"dogmata\", etc.) are also provided.\n\nSingle forms of nouns are also provided. The gender of singular pronouns\ncan be chosen (for example \"they\" -> \"it\" or \"she\" or \"he\" or \"they\").\n\nPronunciation-based \"a\"/\"an\" selection is provided for all English\nwords, and most initialisms.\n\nIt is also possible to inflect numerals (1,2,3) to ordinals (1st, 2nd, 3rd)\nor to English words (\"one\", \"two\", \"three\").\n\nIn generating these inflections, ``inflect.py`` follows the Oxford\nEnglish Dictionary and the guidelines in Fowler's Modern English\nUsage, preferring the former where the two disagree.\n\nThe module is built around standard British spelling, but is designed\nto cope with common American variants as well. Slang, jargon, and\nother English dialects are *not* explicitly catered for.\n\nWhere two or more inflected forms exist for a single word (typically a\n\"classical\" form and a \"modern\" form), ``inflect.py`` prefers the\nmore common form (typically the \"modern\" one), unless \"classical\"\nprocessing has been specified\n(see `MODERN VS CLASSICAL INFLECTIONS`).\n\nFORMING PLURALS AND SINGULARS\n=============================\n\nInflecting Plurals and Singulars\n--------------------------------\n\nAll of the ``plural...`` plural inflection methods take the word to be\ninflected as their first argument and return the corresponding inflection.\nNote that all such methods expect the *singular* form of the word. The\nresults of passing a plural form are undefined (and unlikely to be correct).\nSimilarly, the ``si...`` singular inflection method expects the *plural*\nform of the word.\n\nThe ``plural...`` methods also take an optional second argument,\nwhich indicates the grammatical \"number\" of the word (or of another word\nwith which the word being inflected must agree). If the \"number\" argument is\nsupplied and is not ``1`` (or ``\"one\"`` or ``\"a\"``, or some other adjective that\nimplies the singular), the plural form of the word is returned. If the\n\"number\" argument *does* indicate singularity, the (uninflected) word\nitself is returned. If the number argument is omitted, the plural form\nis returned unconditionally.\n\nThe ``si...`` method takes a second argument in a similar fashion. If it is\nsome form of the number ``1``, or is omitted, the singular form is returned.\nOtherwise the plural is returned unaltered.\n\n\nThe various methods of ``inflect.engine`` are:\n\n\n\n``plural_noun(word, count=None)``\n\n The method ``plural_noun()`` takes a *singular* English noun or\n pronoun and returns its plural. Pronouns in the nominative (\"I\" ->\n \"we\") and accusative (\"me\" -> \"us\") cases are handled, as are\n possessive pronouns (\"mine\" -> \"ours\").\n\n\n``plural_verb(word, count=None)``\n\n The method ``plural_verb()`` takes the *singular* form of a\n conjugated verb (that is, one which is already in the correct \"person\"\n and \"mood\") and returns the corresponding plural conjugation.\n\n\n``plural_adj(word, count=None)``\n\n The method ``plural_adj()`` takes the *singular* form of\n certain types of adjectives and returns the corresponding plural form.\n Adjectives that are correctly handled include: \"numerical\" adjectives\n (\"a\" -> \"some\"), demonstrative adjectives (\"this\" -> \"these\", \"that\" ->\n \"those\"), and possessives (\"my\" -> \"our\", \"cat's\" -> \"cats'\", \"child's\"\n -> \"childrens'\", etc.)\n\n\n``plural(word, count=None)``\n\n The method ``plural()`` takes a *singular* English noun,\n pronoun, verb, or adjective and returns its plural form. Where a word\n has more than one inflection depending on its part of speech (for\n example, the noun \"thought\" inflects to \"thoughts\", the verb \"thought\"\n to \"thought\"), the (singular) noun sense is preferred to the (singular)\n verb sense.\n\n Hence ``plural(\"knife\")`` will return \"knives\" (\"knife\" having been treated\n as a singular noun), whereas ``plural(\"knifes\")`` will return \"knife\"\n (\"knifes\" having been treated as a 3rd person singular verb).\n\n The inherent ambiguity of such cases suggests that,\n where the part of speech is known, ``plural_noun``, ``plural_verb``, and\n ``plural_adj`` should be used in preference to ``plural``.\n\n\n``singular_noun(word, count=None)``\n\n The method ``singular_noun()`` takes a *plural* English noun or\n pronoun and returns its singular. Pronouns in the nominative (\"we\" ->\n \"I\") and accusative (\"us\" -> \"me\") cases are handled, as are\n possessive pronouns (\"ours\" -> \"mine\"). When third person\n singular pronouns are returned they take the neuter gender by default\n (\"they\" -> \"it\"), not (\"they\"-> \"she\") nor (\"they\" -> \"he\"). This can be\n changed with ``gender()``.\n\nNote that all these methods ignore any whitespace surrounding the\nword being inflected, but preserve that whitespace when the result is\nreturned. For example, ``plural(\" cat  \")`` returns \" cats  \".\n\n\n``gender(genderletter)``\n\n The third person plural pronoun takes the same form for the female, male and\n neuter (e.g. \"they\"). The singular however, depends upon gender (e.g. \"she\",\n \"he\", \"it\" and \"they\" -- \"they\" being the gender neutral form.) By default\n ``singular_noun`` returns the neuter form, however, the gender can be selected with\n the ``gender`` method. Pass the first letter of the gender to\n ``gender`` to return the f(eminine), m(asculine), n(euter) or t(hey)\n form of the singular. e.g.\n gender('f') followed by singular_noun('themselves') returns 'herself'.\n\nNumbered plurals\n----------------\n\nThe ``plural...`` methods return only the inflected word, not the count that\nwas used to inflect it. Thus, in order to produce \"I saw 3 ducks\", it\nis necessary to use:\n\n.. code-block:: python\n\n    print(\"I saw\", N, p.plural_noun(animal, N))\n\nSince the usual purpose of producing a plural is to make it agree with\na preceding count, inflect.py provides a method\n(``no(word, count)``) which, given a word and a(n optional) count, returns the\ncount followed by the correctly inflected word. Hence the previous\nexample can be rewritten:\n\n.. code-block:: python\n\n    print(\"I saw \", p.no(animal, N))\n\nIn addition, if the count is zero (or some other term which implies\nzero, such as ``\"zero\"``, ``\"nil\"``, etc.) the count is replaced by the\nword \"no\". Hence, if ``N`` had the value zero, the previous example\nwould print (the somewhat more elegant)::\n\n    I saw no animals\n\nrather than::\n\n    I saw 0 animals\n\nNote that the name of the method is a pun: the method\nreturns either a number (a *No.*) or a ``\"no\"``, in front of the\ninflected word.\n\n\nReducing the number of counts required\n--------------------------------------\n\nIn some contexts, the need to supply an explicit count to the various\n``plural...`` methods makes for tiresome repetition. For example:\n\n.. code-block:: python\n\n    print(\n        plural_adj(\"This\", errors),\n        plural_noun(\" error\", errors),\n        plural_verb(\" was\", errors),\n        \" fatal.\",\n    )\n\ninflect.py therefore provides a method\n(``num(count=None, show=None)``) which may be used to set a persistent \"default number\"\nvalue. If such a value is set, it is subsequently used whenever an\noptional second \"number\" argument is omitted. The default value thus set\ncan subsequently be removed by calling ``num()`` with no arguments.\nHence we could rewrite the previous example:\n\n.. code-block:: python\n\n    p.num(errors)\n    print(p.plural_adj(\"This\"), p.plural_noun(\" error\"), p.plural_verb(\" was\"), \"fatal.\")\n    p.num()\n\nNormally, ``num()`` returns its first argument, so that it may also\nbe \"inlined\" in contexts like:\n\n.. code-block:: python\n\n    print(p.num(errors), p.plural_noun(\" error\"), p.plural_verb(\" was\"), \" detected.\")\n    if severity > 1:\n        print(\n            p.plural_adj(\"This\"), p.plural_noun(\" error\"), p.plural_verb(\" was\"), \"fatal.\"\n        )\n\nHowever, in certain contexts (see `INTERPOLATING INFLECTIONS IN STRINGS`)\nit is preferable that ``num()`` return an empty string. Hence ``num()``\nprovides an optional second argument. If that argument is supplied (that is, if\nit is defined) and evaluates to false, ``num`` returns an empty string\ninstead of its first argument. For example:\n\n.. code-block:: python\n\n    print(p.num(errors, 0), p.no(\"error\"), p.plural_verb(\" was\"), \" detected.\")\n    if severity > 1:\n        print(\n            p.plural_adj(\"This\"), p.plural_noun(\" error\"), p.plural_verb(\" was\"), \"fatal.\"\n        )\n\n\n\nNumber-insensitive equality\n---------------------------\n\ninflect.py also provides a solution to the problem\nof comparing words of differing plurality through the methods\n``compare(word1, word2)``, ``compare_nouns(word1, word2)``,\n``compare_verbs(word1, word2)``, and ``compare_adjs(word1, word2)``.\nEach  of these methods takes two strings, and  compares them\nusing the corresponding plural-inflection method (``plural()``, ``plural_noun()``,\n``plural_verb()``, and ``plural_adj()`` respectively).\n\nThe comparison returns true if:\n\n- the strings are equal, or\n- one string is equal to a plural form of the other, or\n- the strings are two different plural forms of the one word.\n\n\nHence all of the following return true:\n\n.. code-block:: python\n\n    p.compare(\"index\", \"index\")  # RETURNS \"eq\"\n    p.compare(\"index\", \"indexes\")  # RETURNS \"s:p\"\n    p.compare(\"index\", \"indices\")  # RETURNS \"s:p\"\n    p.compare(\"indexes\", \"index\")  # RETURNS \"p:s\"\n    p.compare(\"indices\", \"index\")  # RETURNS \"p:s\"\n    p.compare(\"indices\", \"indexes\")  # RETURNS \"p:p\"\n    p.compare(\"indexes\", \"indices\")  # RETURNS \"p:p\"\n    p.compare(\"indices\", \"indices\")  # RETURNS \"eq\"\n\nAs indicated by the comments in the previous example, the actual value\nreturned by the various ``compare`` methods encodes which of the\nthree equality rules succeeded: \"eq\" is returned if the strings were\nidentical, \"s:p\" if the strings were singular and plural respectively,\n\"p:s\" for plural and singular, and \"p:p\" for two distinct plurals.\nInequality is indicated by returning an empty string.\n\nIt should be noted that two distinct singular words which happen to take\nthe same plural form are *not* considered equal, nor are cases where\none (singular) word's plural is the other (plural) word's singular.\nHence all of the following return false:\n\n.. code-block:: python\n\n    p.compare(\"base\", \"basis\")  # ALTHOUGH BOTH -> \"bases\"\n    p.compare(\"syrinx\", \"syringe\")  # ALTHOUGH BOTH -> \"syringes\"\n    p.compare(\"she\", \"he\")  # ALTHOUGH BOTH -> \"they\"\n\n    p.compare(\"opus\", \"operas\")  # ALTHOUGH \"opus\" -> \"opera\" -> \"operas\"\n    p.compare(\"taxi\", \"taxes\")  # ALTHOUGH \"taxi\" -> \"taxis\" -> \"taxes\"\n\nNote too that, although the comparison is \"number-insensitive\" it is *not*\ncase-insensitive (that is, ``plural(\"time\",\"Times\")`` returns false. To obtain\nboth number and case insensitivity, use the ``lower()`` method on both strings\n(that is, ``plural(\"time\".lower(), \"Times\".lower())`` returns true).\n\nRelated Functionality\n=====================\n\nShout out to these libraries that provide related functionality:\n\n* `WordSet <https://jaracotext.readthedocs.io/en/latest/#jaraco.text.WordSet>`_\n  parses identifiers like variable names into sets of words suitable for re-assembling\n  in another form.\n\n* `word2number <https://pypi.org/project/word2number/>`_ converts words to\n  a number.\n\n\nFor Enterprise\n==============\n\nAvailable as part of the Tidelift Subscription.\n\nThis project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use.\n\n`Learn more <https://tidelift.com/subscription/pkg/pypi-PROJECT?utm_source=pypi-PROJECT&utm_medium=referral&utm_campaign=github>`_.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Contact\n\nTo report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.\n"
  },
  {
    "path": "docs/conf.py",
    "content": "from __future__ import annotations\n\nextensions = [\n    'sphinx.ext.autodoc',\n    'jaraco.packaging.sphinx',\n]\n\nmaster_doc = \"index\"\nhtml_theme = \"furo\"\n\n# Link dates and other references in the changelog\nextensions += ['rst.linker']\nlink_files = {\n    '../NEWS.rst': dict(\n        using=dict(GH='https://github.com'),\n        replace=[\n            dict(\n                pattern=r'(Issue #|\\B#)(?P<issue>\\d+)',\n                url='{package_url}/issues/{issue}',\n            ),\n            dict(\n                pattern=r'(?m:^((?P<scm_version>v?\\d+(\\.\\d+){1,2}))\\n[-=]+\\n)',\n                with_scm='{text}\\n{rev[timestamp]:%d %b %Y}\\n',\n            ),\n            dict(\n                pattern=r'PEP[- ](?P<pep_number>\\d+)',\n                url='https://peps.python.org/pep-{pep_number:0>4}/',\n            ),\n        ],\n    )\n}\n\n# Be strict about any broken references\nnitpicky = True\nnitpick_ignore: list[tuple[str, str]] = []\n\n# Include Python intersphinx mapping to prevent failures\n# jaraco/skeleton#51\nextensions += ['sphinx.ext.intersphinx']\nintersphinx_mapping = {\n    'python': ('https://docs.python.org/3', None),\n}\n\n# Preserve authored syntax for defaults\nautodoc_preserve_defaults = True\n\n# Add support for linking usernames, PyPI projects, Wikipedia pages\ngithub_url = 'https://github.com/'\nextlinks = {\n    'user': (f'{github_url}%s', '@%s'),\n    'pypi': ('https://pypi.org/project/%s', '%s'),\n    'wiki': ('https://wikipedia.org/wiki/%s', '%s'),\n}\nextensions += ['sphinx.ext.extlinks']\n\n# local\n\nextensions += ['jaraco.tidelift']\n"
  },
  {
    "path": "docs/history.rst",
    "content": ":tocdepth: 2\n\n.. _changes:\n\nHistory\n*******\n\n.. include:: ../NEWS (links).rst\n"
  },
  {
    "path": "docs/index.rst",
    "content": "Welcome to |project| documentation!\n===================================\n\n.. sidebar-links::\n   :home:\n   :pypi:\n\n.. toctree::\n   :maxdepth: 1\n\n   history\n\n.. tidelift-referral-banner::\n\n.. automodule:: inflect\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n"
  },
  {
    "path": "inflect/__init__.py",
    "content": "\"\"\"\ninflect: english language inflection\n - correctly generate plurals, ordinals, indefinite articles\n - convert numbers to words\n\nCopyright (C) 2010 Paul Dyson\n\nBased upon the Perl module\n`Lingua::EN::Inflect <https://metacpan.org/pod/Lingua::EN::Inflect>`_.\n\nmethods:\n    classical inflect\n    plural plural_noun plural_verb plural_adj singular_noun no num a an\n    compare compare_nouns compare_verbs compare_adjs\n    present_participle\n    ordinal\n    number_to_words\n    join\n    defnoun defverb defadj defa defan\n\nINFLECTIONS:\n    classical inflect\n    plural plural_noun plural_verb plural_adj singular_noun compare\n    no num a an present_participle\n\nPLURALS:\n    classical inflect\n    plural plural_noun plural_verb plural_adj singular_noun no num\n    compare compare_nouns compare_verbs compare_adjs\n\nCOMPARISONS:\n    classical\n    compare compare_nouns compare_verbs compare_adjs\n\nARTICLES:\n    classical inflect num a an\n\nNUMERICAL:\n    ordinal number_to_words\n\nUSER_DEFINED:\n    defnoun defverb defadj defa defan\n\nExceptions:\n UnknownClassicalModeError\n BadNumValueError\n BadChunkingOptionError\n NumOutOfRangeError\n BadUserDefinedPatternError\n BadRcFileError\n BadGenderError\n\n\"\"\"\n\nfrom __future__ import annotations\n\nimport ast\nimport collections\nimport contextlib\nimport functools\nimport itertools\nimport re\nfrom numbers import Number\nfrom typing import (\n    TYPE_CHECKING,\n    Any,\n    Callable,\n    Dict,\n    Iterable,\n    List,\n    Literal,\n    Match,\n    Optional,\n    Sequence,\n    Tuple,\n    Union,\n    cast,\n)\n\nfrom more_itertools import windowed_complete\nfrom typeguard import typechecked\n\nfrom .compat.py38 import Annotated\n\n\nclass UnknownClassicalModeError(Exception):\n    pass\n\n\nclass BadNumValueError(Exception):\n    pass\n\n\nclass BadChunkingOptionError(Exception):\n    pass\n\n\nclass NumOutOfRangeError(Exception):\n    pass\n\n\nclass BadUserDefinedPatternError(Exception):\n    pass\n\n\nclass BadRcFileError(Exception):\n    pass\n\n\nclass BadGenderError(Exception):\n    pass\n\n\ndef enclose(s: str) -> str:\n    return f\"(?:{s})\"\n\n\ndef joinstem(cutpoint: Optional[int] = 0, words: Optional[Iterable[str]] = None) -> str:\n    \"\"\"\n    Join stem of each word in words into a string for regex.\n\n    Each word is truncated at cutpoint.\n\n    Cutpoint is usually negative indicating the number of letters to remove\n    from the end of each word.\n\n    >>> joinstem(-2, [\"ephemeris\", \"iris\", \".*itis\"])\n    '(?:ephemer|ir|.*it)'\n\n    >>> joinstem(None, [\"ephemeris\"])\n    '(?:ephemeris)'\n\n    >>> joinstem(5, None)\n    '(?:)'\n    \"\"\"\n    return enclose(\"|\".join(w[:cutpoint] for w in words or []))\n\n\ndef bysize(words: Iterable[str]) -> Dict[int, set]:\n    \"\"\"\n    From a list of words, return a dict of sets sorted by word length.\n\n    >>> words = ['ant', 'cat', 'dog', 'pig', 'frog', 'goat', 'horse', 'elephant']\n    >>> ret = bysize(words)\n    >>> sorted(ret[3])\n    ['ant', 'cat', 'dog', 'pig']\n    >>> ret[5]\n    {'horse'}\n    \"\"\"\n    res: Dict[int, set] = collections.defaultdict(set)\n    for w in words:\n        res[len(w)].add(w)\n    return res\n\n\ndef make_pl_si_lists(\n    lst: Iterable[str],\n    plending: str,\n    siendingsize: Optional[int],\n    dojoinstem: bool = True,\n):\n    \"\"\"\n    given a list of singular words: lst\n\n    an ending to append to make the plural: plending\n\n    the number of characters to remove from the singular\n    before appending plending: siendingsize\n\n    a flag whether to create a joinstem: dojoinstem\n\n    return:\n    a list of pluralised words: si_list (called si because this is what you need to\n    look for to make the singular)\n\n    the pluralised words as a dict of sets sorted by word length: si_bysize\n    the singular words as a dict of sets sorted by word length: pl_bysize\n    if dojoinstem is True: a regular expression that matches any of the stems: stem\n    \"\"\"\n    if siendingsize is not None:\n        siendingsize = -siendingsize\n    si_list = [w[:siendingsize] + plending for w in lst]\n    pl_bysize = bysize(lst)\n    si_bysize = bysize(si_list)\n    if dojoinstem:\n        stem = joinstem(siendingsize, lst)\n        return si_list, si_bysize, pl_bysize, stem\n    else:\n        return si_list, si_bysize, pl_bysize\n\n\n# 1. PLURALS\n\npl_sb_irregular_s = {\n    \"corpus\": \"corpuses|corpora\",\n    \"opus\": \"opuses|opera\",\n    \"genus\": \"genera\",\n    \"mythos\": \"mythoi\",\n    \"penis\": \"penises|penes\",\n    \"testis\": \"testes\",\n    \"atlas\": \"atlases|atlantes\",\n    \"yes\": \"yeses\",\n}\n\npl_sb_irregular = {\n    \"child\": \"children\",\n    \"chili\": \"chilis|chilies\",\n    \"brother\": \"brothers|brethren\",\n    \"infinity\": \"infinities|infinity\",\n    \"loaf\": \"loaves\",\n    \"lore\": \"lores|lore\",\n    \"hoof\": \"hoofs|hooves\",\n    \"beef\": \"beefs|beeves\",\n    \"thief\": \"thiefs|thieves\",\n    \"money\": \"monies\",\n    \"mongoose\": \"mongooses\",\n    \"ox\": \"oxen\",\n    \"cow\": \"cows|kine\",\n    \"graffito\": \"graffiti\",\n    \"octopus\": \"octopuses|octopodes\",\n    \"genie\": \"genies|genii\",\n    \"ganglion\": \"ganglions|ganglia\",\n    \"trilby\": \"trilbys\",\n    \"turf\": \"turfs|turves\",\n    \"numen\": \"numina\",\n    \"atman\": \"atmas\",\n    \"occiput\": \"occiputs|occipita\",\n    \"sabretooth\": \"sabretooths\",\n    \"sabertooth\": \"sabertooths\",\n    \"lowlife\": \"lowlifes\",\n    \"flatfoot\": \"flatfoots\",\n    \"tenderfoot\": \"tenderfoots\",\n    \"romany\": \"romanies\",\n    \"jerry\": \"jerries\",\n    \"mary\": \"maries\",\n    \"talouse\": \"talouses\",\n    \"rom\": \"roma\",\n    \"carmen\": \"carmina\",\n}\n\npl_sb_irregular.update(pl_sb_irregular_s)\n# pl_sb_irregular_keys = enclose('|'.join(pl_sb_irregular.keys()))\n\npl_sb_irregular_caps = {\n    \"Romany\": \"Romanies\",\n    \"Jerry\": \"Jerrys\",\n    \"Mary\": \"Marys\",\n    \"Rom\": \"Roma\",\n}\n\npl_sb_irregular_compound = {\"prima donna\": \"prima donnas|prime donne\"}\n\nsi_sb_irregular = {v: k for (k, v) in pl_sb_irregular.items()}\nfor k in list(si_sb_irregular):\n    if \"|\" in k:\n        k1, k2 = k.split(\"|\")\n        si_sb_irregular[k1] = si_sb_irregular[k2] = si_sb_irregular[k]\n        del si_sb_irregular[k]\nsi_sb_irregular_caps = {v: k for (k, v) in pl_sb_irregular_caps.items()}\nsi_sb_irregular_compound = {v: k for (k, v) in pl_sb_irregular_compound.items()}\nfor k in list(si_sb_irregular_compound):\n    if \"|\" in k:\n        k1, k2 = k.split(\"|\")\n        si_sb_irregular_compound[k1] = si_sb_irregular_compound[k2] = (\n            si_sb_irregular_compound[k]\n        )\n        del si_sb_irregular_compound[k]\n\n# si_sb_irregular_keys = enclose('|'.join(si_sb_irregular.keys()))\n\n# Z's that don't double\n\npl_sb_z_zes_list = (\"quartz\", \"topaz\")\npl_sb_z_zes_bysize = bysize(pl_sb_z_zes_list)\n\npl_sb_ze_zes_list = (\"snooze\",)\npl_sb_ze_zes_bysize = bysize(pl_sb_ze_zes_list)\n\n\n# CLASSICAL \"..is\" -> \"..ides\"\n\npl_sb_C_is_ides_complete = [\n    # GENERAL WORDS...\n    \"ephemeris\",\n    \"iris\",\n    \"clitoris\",\n    \"chrysalis\",\n    \"epididymis\",\n]\n\npl_sb_C_is_ides_endings = [\n    # INFLAMATIONS...\n    \"itis\"\n]\n\npl_sb_C_is_ides = joinstem(\n    -2, pl_sb_C_is_ides_complete + [f\".*{w}\" for w in pl_sb_C_is_ides_endings]\n)\n\npl_sb_C_is_ides_list = pl_sb_C_is_ides_complete + pl_sb_C_is_ides_endings\n\n(\n    si_sb_C_is_ides_list,\n    si_sb_C_is_ides_bysize,\n    pl_sb_C_is_ides_bysize,\n) = make_pl_si_lists(pl_sb_C_is_ides_list, \"ides\", 2, dojoinstem=False)\n\n\n# CLASSICAL \"..a\" -> \"..ata\"\n\npl_sb_C_a_ata_list = (\n    \"anathema\",\n    \"bema\",\n    \"carcinoma\",\n    \"charisma\",\n    \"diploma\",\n    \"dogma\",\n    \"drama\",\n    \"edema\",\n    \"enema\",\n    \"enigma\",\n    \"lemma\",\n    \"lymphoma\",\n    \"magma\",\n    \"melisma\",\n    \"miasma\",\n    \"oedema\",\n    \"sarcoma\",\n    \"schema\",\n    \"soma\",\n    \"stigma\",\n    \"stoma\",\n    \"trauma\",\n    \"gumma\",\n    \"pragma\",\n)\n\n(\n    si_sb_C_a_ata_list,\n    si_sb_C_a_ata_bysize,\n    pl_sb_C_a_ata_bysize,\n    pl_sb_C_a_ata,\n) = make_pl_si_lists(pl_sb_C_a_ata_list, \"ata\", 1)\n\n# UNCONDITIONAL \"..a\" -> \"..ae\"\n\npl_sb_U_a_ae_list = (\n    \"alumna\",\n    \"alga\",\n    \"vertebra\",\n    \"persona\",\n    \"vita\",\n)\n(\n    si_sb_U_a_ae_list,\n    si_sb_U_a_ae_bysize,\n    pl_sb_U_a_ae_bysize,\n    pl_sb_U_a_ae,\n) = make_pl_si_lists(pl_sb_U_a_ae_list, \"e\", None)\n\n# CLASSICAL \"..a\" -> \"..ae\"\n\npl_sb_C_a_ae_list = (\n    \"amoeba\",\n    \"antenna\",\n    \"formula\",\n    \"hyperbola\",\n    \"medusa\",\n    \"nebula\",\n    \"parabola\",\n    \"abscissa\",\n    \"hydra\",\n    \"nova\",\n    \"lacuna\",\n    \"aurora\",\n    \"umbra\",\n    \"flora\",\n    \"fauna\",\n)\n(\n    si_sb_C_a_ae_list,\n    si_sb_C_a_ae_bysize,\n    pl_sb_C_a_ae_bysize,\n    pl_sb_C_a_ae,\n) = make_pl_si_lists(pl_sb_C_a_ae_list, \"e\", None)\n\n\n# CLASSICAL \"..en\" -> \"..ina\"\n\npl_sb_C_en_ina_list = (\"stamen\", \"foramen\", \"lumen\")\n\n(\n    si_sb_C_en_ina_list,\n    si_sb_C_en_ina_bysize,\n    pl_sb_C_en_ina_bysize,\n    pl_sb_C_en_ina,\n) = make_pl_si_lists(pl_sb_C_en_ina_list, \"ina\", 2)\n\n\n# UNCONDITIONAL \"..um\" -> \"..a\"\n\npl_sb_U_um_a_list = (\n    \"bacterium\",\n    \"agendum\",\n    \"desideratum\",\n    \"erratum\",\n    \"stratum\",\n    \"datum\",\n    \"ovum\",\n    \"extremum\",\n    \"candelabrum\",\n)\n(\n    si_sb_U_um_a_list,\n    si_sb_U_um_a_bysize,\n    pl_sb_U_um_a_bysize,\n    pl_sb_U_um_a,\n) = make_pl_si_lists(pl_sb_U_um_a_list, \"a\", 2)\n\n# CLASSICAL \"..um\" -> \"..a\"\n\npl_sb_C_um_a_list = (\n    \"maximum\",\n    \"minimum\",\n    \"momentum\",\n    \"optimum\",\n    \"quantum\",\n    \"cranium\",\n    \"curriculum\",\n    \"dictum\",\n    \"phylum\",\n    \"aquarium\",\n    \"compendium\",\n    \"emporium\",\n    \"encomium\",\n    \"gymnasium\",\n    \"honorarium\",\n    \"interregnum\",\n    \"lustrum\",\n    \"memorandum\",\n    \"millennium\",\n    \"rostrum\",\n    \"spectrum\",\n    \"speculum\",\n    \"stadium\",\n    \"trapezium\",\n    \"ultimatum\",\n    \"medium\",\n    \"vacuum\",\n    \"velum\",\n    \"consortium\",\n    \"arboretum\",\n)\n\n(\n    si_sb_C_um_a_list,\n    si_sb_C_um_a_bysize,\n    pl_sb_C_um_a_bysize,\n    pl_sb_C_um_a,\n) = make_pl_si_lists(pl_sb_C_um_a_list, \"a\", 2)\n\n\n# UNCONDITIONAL \"..us\" -> \"i\"\n\npl_sb_U_us_i_list = (\n    \"alumnus\",\n    \"alveolus\",\n    \"bacillus\",\n    \"bronchus\",\n    \"locus\",\n    \"nucleus\",\n    \"stimulus\",\n    \"meniscus\",\n    \"sarcophagus\",\n)\n(\n    si_sb_U_us_i_list,\n    si_sb_U_us_i_bysize,\n    pl_sb_U_us_i_bysize,\n    pl_sb_U_us_i,\n) = make_pl_si_lists(pl_sb_U_us_i_list, \"i\", 2)\n\n# CLASSICAL \"..us\" -> \"..i\"\n\npl_sb_C_us_i_list = (\n    \"focus\",\n    \"radius\",\n    \"genius\",\n    \"incubus\",\n    \"succubus\",\n    \"nimbus\",\n    \"fungus\",\n    \"nucleolus\",\n    \"stylus\",\n    \"torus\",\n    \"umbilicus\",\n    \"uterus\",\n    \"hippopotamus\",\n    \"cactus\",\n)\n\n(\n    si_sb_C_us_i_list,\n    si_sb_C_us_i_bysize,\n    pl_sb_C_us_i_bysize,\n    pl_sb_C_us_i,\n) = make_pl_si_lists(pl_sb_C_us_i_list, \"i\", 2)\n\n\n# CLASSICAL \"..us\" -> \"..us\"  (ASSIMILATED 4TH DECLENSION LATIN NOUNS)\n\npl_sb_C_us_us = (\n    \"status\",\n    \"apparatus\",\n    \"prospectus\",\n    \"sinus\",\n    \"hiatus\",\n    \"impetus\",\n    \"plexus\",\n)\npl_sb_C_us_us_bysize = bysize(pl_sb_C_us_us)\n\n# UNCONDITIONAL \"..on\" -> \"a\"\n\npl_sb_U_on_a_list = (\n    \"criterion\",\n    \"perihelion\",\n    \"aphelion\",\n    \"phenomenon\",\n    \"prolegomenon\",\n    \"noumenon\",\n    \"organon\",\n    \"asyndeton\",\n    \"hyperbaton\",\n)\n(\n    si_sb_U_on_a_list,\n    si_sb_U_on_a_bysize,\n    pl_sb_U_on_a_bysize,\n    pl_sb_U_on_a,\n) = make_pl_si_lists(pl_sb_U_on_a_list, \"a\", 2)\n\n# CLASSICAL \"..on\" -> \"..a\"\n\npl_sb_C_on_a_list = (\"oxymoron\",)\n\n(\n    si_sb_C_on_a_list,\n    si_sb_C_on_a_bysize,\n    pl_sb_C_on_a_bysize,\n    pl_sb_C_on_a,\n) = make_pl_si_lists(pl_sb_C_on_a_list, \"a\", 2)\n\n\n# CLASSICAL \"..o\" -> \"..i\"  (BUT NORMALLY -> \"..os\")\n\npl_sb_C_o_i = [\n    \"solo\",\n    \"soprano\",\n    \"basso\",\n    \"alto\",\n    \"contralto\",\n    \"tempo\",\n    \"piano\",\n    \"virtuoso\",\n]  # list not tuple so can concat for pl_sb_U_o_os\n\npl_sb_C_o_i_bysize = bysize(pl_sb_C_o_i)\nsi_sb_C_o_i_bysize = bysize([f\"{w[:-1]}i\" for w in pl_sb_C_o_i])\n\npl_sb_C_o_i_stems = joinstem(-1, pl_sb_C_o_i)\n\n# ALWAYS \"..o\" -> \"..os\"\n\npl_sb_U_o_os_complete = {\"ado\", \"ISO\", \"NATO\", \"NCO\", \"NGO\", \"oto\"}\nsi_sb_U_o_os_complete = {f\"{w}s\" for w in pl_sb_U_o_os_complete}\n\n\npl_sb_U_o_os_endings = [\n    \"aficionado\",\n    \"aggro\",\n    \"albino\",\n    \"allegro\",\n    \"ammo\",\n    \"Antananarivo\",\n    \"archipelago\",\n    \"armadillo\",\n    \"auto\",\n    \"avocado\",\n    \"Bamako\",\n    \"Barquisimeto\",\n    \"bimbo\",\n    \"bingo\",\n    \"Biro\",\n    \"bolero\",\n    \"Bolzano\",\n    \"bongo\",\n    \"Boto\",\n    \"burro\",\n    \"Cairo\",\n    \"canto\",\n    \"cappuccino\",\n    \"casino\",\n    \"cello\",\n    \"Chicago\",\n    \"Chimango\",\n    \"cilantro\",\n    \"cochito\",\n    \"coco\",\n    \"Colombo\",\n    \"Colorado\",\n    \"commando\",\n    \"concertino\",\n    \"contango\",\n    \"credo\",\n    \"crescendo\",\n    \"cyano\",\n    \"demo\",\n    \"ditto\",\n    \"Draco\",\n    \"dynamo\",\n    \"embryo\",\n    \"Esperanto\",\n    \"espresso\",\n    \"euro\",\n    \"falsetto\",\n    \"Faro\",\n    \"fiasco\",\n    \"Filipino\",\n    \"flamenco\",\n    \"furioso\",\n    \"generalissimo\",\n    \"Gestapo\",\n    \"ghetto\",\n    \"gigolo\",\n    \"gizmo\",\n    \"Greensboro\",\n    \"gringo\",\n    \"Guaiabero\",\n    \"guano\",\n    \"gumbo\",\n    \"gyro\",\n    \"hairdo\",\n    \"hippo\",\n    \"Idaho\",\n    \"impetigo\",\n    \"inferno\",\n    \"info\",\n    \"intermezzo\",\n    \"intertrigo\",\n    \"Iquico\",\n    \"jumbo\",\n    \"junto\",\n    \"Kakapo\",\n    \"kilo\",\n    \"Kinkimavo\",\n    \"Kokako\",\n    \"Kosovo\",\n    \"Lesotho\",\n    \"libero\",\n    \"libido\",\n    \"libretto\",\n    \"lido\",\n    \"Lilo\",\n    \"limbo\",\n    \"limo\",\n    \"lineno\",\n    \"lingo\",\n    \"lino\",\n    \"livedo\",\n    \"loco\",\n    \"logo\",\n    \"lumbago\",\n    \"macho\",\n    \"macro\",\n    \"mafioso\",\n    \"magneto\",\n    \"magnifico\",\n    \"Majuro\",\n    \"Malabo\",\n    \"manifesto\",\n    \"Maputo\",\n    \"Maracaibo\",\n    \"medico\",\n    \"memo\",\n    \"metro\",\n    \"Mexico\",\n    \"micro\",\n    \"Milano\",\n    \"Monaco\",\n    \"mono\",\n    \"Montenegro\",\n    \"Morocco\",\n    \"Muqdisho\",\n    \"myo\",\n    \"neutrino\",\n    \"Ningbo\",\n    \"octavo\",\n    \"oregano\",\n    \"Orinoco\",\n    \"Orlando\",\n    \"Oslo\",\n    \"panto\",\n    \"Paramaribo\",\n    \"Pardusco\",\n    \"pedalo\",\n    \"photo\",\n    \"pimento\",\n    \"pinto\",\n    \"pleco\",\n    \"Pluto\",\n    \"pogo\",\n    \"polo\",\n    \"poncho\",\n    \"Porto-Novo\",\n    \"Porto\",\n    \"pro\",\n    \"psycho\",\n    \"pueblo\",\n    \"quarto\",\n    \"Quito\",\n    \"repo\",\n    \"rhino\",\n    \"risotto\",\n    \"rococo\",\n    \"rondo\",\n    \"Sacramento\",\n    \"saddo\",\n    \"sago\",\n    \"salvo\",\n    \"Santiago\",\n    \"Sapporo\",\n    \"Sarajevo\",\n    \"scherzando\",\n    \"scherzo\",\n    \"silo\",\n    \"sirocco\",\n    \"sombrero\",\n    \"staccato\",\n    \"sterno\",\n    \"stucco\",\n    \"stylo\",\n    \"sumo\",\n    \"Taiko\",\n    \"techno\",\n    \"terrazzo\",\n    \"testudo\",\n    \"timpano\",\n    \"tiro\",\n    \"tobacco\",\n    \"Togo\",\n    \"Tokyo\",\n    \"torero\",\n    \"Torino\",\n    \"Toronto\",\n    \"torso\",\n    \"tremolo\",\n    \"typo\",\n    \"tyro\",\n    \"ufo\",\n    \"UNESCO\",\n    \"vaquero\",\n    \"vermicello\",\n    \"verso\",\n    \"vibrato\",\n    \"violoncello\",\n    \"Virgo\",\n    \"weirdo\",\n    \"WHO\",\n    \"WTO\",\n    \"Yamoussoukro\",\n    \"yo-yo\",\n    \"zero\",\n    \"Zibo\",\n] + pl_sb_C_o_i\n\npl_sb_U_o_os_bysize = bysize(pl_sb_U_o_os_endings)\nsi_sb_U_o_os_bysize = bysize([f\"{w}s\" for w in pl_sb_U_o_os_endings])\n\n\n# UNCONDITIONAL \"..ch\" -> \"..chs\"\n\npl_sb_U_ch_chs_list = (\"czech\", \"eunuch\", \"stomach\")\n\n(\n    si_sb_U_ch_chs_list,\n    si_sb_U_ch_chs_bysize,\n    pl_sb_U_ch_chs_bysize,\n    pl_sb_U_ch_chs,\n) = make_pl_si_lists(pl_sb_U_ch_chs_list, \"s\", None)\n\n\n# UNCONDITIONAL \"..[ei]x\" -> \"..ices\"\n\npl_sb_U_ex_ices_list = (\"codex\", \"murex\", \"silex\")\n(\n    si_sb_U_ex_ices_list,\n    si_sb_U_ex_ices_bysize,\n    pl_sb_U_ex_ices_bysize,\n    pl_sb_U_ex_ices,\n) = make_pl_si_lists(pl_sb_U_ex_ices_list, \"ices\", 2)\n\npl_sb_U_ix_ices_list = (\"radix\", \"helix\")\n(\n    si_sb_U_ix_ices_list,\n    si_sb_U_ix_ices_bysize,\n    pl_sb_U_ix_ices_bysize,\n    pl_sb_U_ix_ices,\n) = make_pl_si_lists(pl_sb_U_ix_ices_list, \"ices\", 2)\n\n# CLASSICAL \"..[ei]x\" -> \"..ices\"\n\npl_sb_C_ex_ices_list = (\n    \"vortex\",\n    \"vertex\",\n    \"cortex\",\n    \"latex\",\n    \"pontifex\",\n    \"apex\",\n    \"index\",\n    \"simplex\",\n)\n\n(\n    si_sb_C_ex_ices_list,\n    si_sb_C_ex_ices_bysize,\n    pl_sb_C_ex_ices_bysize,\n    pl_sb_C_ex_ices,\n) = make_pl_si_lists(pl_sb_C_ex_ices_list, \"ices\", 2)\n\n\npl_sb_C_ix_ices_list = (\"appendix\",)\n\n(\n    si_sb_C_ix_ices_list,\n    si_sb_C_ix_ices_bysize,\n    pl_sb_C_ix_ices_bysize,\n    pl_sb_C_ix_ices,\n) = make_pl_si_lists(pl_sb_C_ix_ices_list, \"ices\", 2)\n\n\n# ARABIC: \"..\" -> \"..i\"\n\npl_sb_C_i_list = (\"afrit\", \"afreet\", \"efreet\")\n\n(si_sb_C_i_list, si_sb_C_i_bysize, pl_sb_C_i_bysize, pl_sb_C_i) = make_pl_si_lists(\n    pl_sb_C_i_list, \"i\", None\n)\n\n\n# HEBREW: \"..\" -> \"..im\"\n\npl_sb_C_im_list = (\"goy\", \"seraph\", \"cherub\")\n\n(si_sb_C_im_list, si_sb_C_im_bysize, pl_sb_C_im_bysize, pl_sb_C_im) = make_pl_si_lists(\n    pl_sb_C_im_list, \"im\", None\n)\n\n\n# UNCONDITIONAL \"..man\" -> \"..mans\"\n\npl_sb_U_man_mans_list = \"\"\"\n    ataman caiman cayman ceriman\n    desman dolman farman harman hetman\n    human leman ottoman shaman talisman\n\"\"\".split()\npl_sb_U_man_mans_caps_list = \"\"\"\n    Alabaman Bahaman Burman German\n    Hiroshiman Liman Nakayaman Norman Oklahoman\n    Panaman Roman Selman Sonaman Tacoman Yakiman\n    Yokohaman Yuman\n\"\"\".split()\n\n(\n    si_sb_U_man_mans_list,\n    si_sb_U_man_mans_bysize,\n    pl_sb_U_man_mans_bysize,\n) = make_pl_si_lists(pl_sb_U_man_mans_list, \"s\", None, dojoinstem=False)\n(\n    si_sb_U_man_mans_caps_list,\n    si_sb_U_man_mans_caps_bysize,\n    pl_sb_U_man_mans_caps_bysize,\n) = make_pl_si_lists(pl_sb_U_man_mans_caps_list, \"s\", None, dojoinstem=False)\n\n# UNCONDITIONAL \"..louse\" -> \"..lice\"\npl_sb_U_louse_lice_list = (\"booklouse\", \"grapelouse\", \"louse\", \"woodlouse\")\n\n(\n    si_sb_U_louse_lice_list,\n    si_sb_U_louse_lice_bysize,\n    pl_sb_U_louse_lice_bysize,\n) = make_pl_si_lists(pl_sb_U_louse_lice_list, \"lice\", 5, dojoinstem=False)\n\npl_sb_uninflected_s_complete = [\n    # PAIRS OR GROUPS SUBSUMED TO A SINGULAR...\n    \"breeches\",\n    \"britches\",\n    \"pajamas\",\n    \"pyjamas\",\n    \"clippers\",\n    \"gallows\",\n    \"hijinks\",\n    \"headquarters\",\n    \"pliers\",\n    \"scissors\",\n    \"testes\",\n    \"herpes\",\n    \"pincers\",\n    \"shears\",\n    \"proceedings\",\n    \"trousers\",\n    # UNASSIMILATED LATIN 4th DECLENSION\n    \"cantus\",\n    \"coitus\",\n    \"nexus\",\n    # RECENT IMPORTS...\n    \"contretemps\",\n    \"corps\",\n    \"debris\",\n    \"siemens\",\n    # DISEASES\n    \"mumps\",\n    # MISCELLANEOUS OTHERS...\n    \"diabetes\",\n    \"jackanapes\",\n    \"series\",\n    \"species\",\n    \"subspecies\",\n    \"rabies\",\n    \"chassis\",\n    \"innings\",\n    \"news\",\n    \"mews\",\n    \"haggis\",\n]\n\npl_sb_uninflected_s_endings = [\n    # RECENT IMPORTS...\n    \"ois\",\n    # DISEASES\n    \"measles\",\n]\n\npl_sb_uninflected_s = pl_sb_uninflected_s_complete + [\n    f\".*{w}\" for w in pl_sb_uninflected_s_endings\n]\n\npl_sb_uninflected_herd = (\n    # DON'T INFLECT IN CLASSICAL MODE, OTHERWISE NORMAL INFLECTION\n    \"wildebeest\",\n    \"swine\",\n    \"eland\",\n    \"bison\",\n    \"buffalo\",\n    \"cattle\",\n    \"elk\",\n    \"rhinoceros\",\n    \"zucchini\",\n    \"caribou\",\n    \"dace\",\n    \"grouse\",\n    \"guinea fowl\",\n    \"guinea-fowl\",\n    \"haddock\",\n    \"hake\",\n    \"halibut\",\n    \"herring\",\n    \"mackerel\",\n    \"pickerel\",\n    \"pike\",\n    \"roe\",\n    \"seed\",\n    \"shad\",\n    \"snipe\",\n    \"teal\",\n    \"turbot\",\n    \"water fowl\",\n    \"water-fowl\",\n)\n\npl_sb_uninflected_complete = [\n    # SOME FISH AND HERD ANIMALS\n    \"tuna\",\n    \"salmon\",\n    \"mackerel\",\n    \"trout\",\n    \"bream\",\n    \"sea-bass\",\n    \"sea bass\",\n    \"carp\",\n    \"cod\",\n    \"flounder\",\n    \"whiting\",\n    \"moose\",\n    # OTHER ODDITIES\n    \"graffiti\",\n    \"djinn\",\n    \"samuri\",\n    \"offspring\",\n    \"pence\",\n    \"quid\",\n    \"hertz\",\n] + pl_sb_uninflected_s_complete\n# SOME WORDS ENDING IN ...s (OFTEN PAIRS TAKEN AS A WHOLE)\n\npl_sb_uninflected_caps = [\n    # ALL NATIONALS ENDING IN -ese\n    \"Portuguese\",\n    \"Amoyese\",\n    \"Borghese\",\n    \"Congoese\",\n    \"Faroese\",\n    \"Foochowese\",\n    \"Genevese\",\n    \"Genoese\",\n    \"Gilbertese\",\n    \"Hottentotese\",\n    \"Kiplingese\",\n    \"Kongoese\",\n    \"Lucchese\",\n    \"Maltese\",\n    \"Nankingese\",\n    \"Niasese\",\n    \"Pekingese\",\n    \"Piedmontese\",\n    \"Pistoiese\",\n    \"Sarawakese\",\n    \"Shavese\",\n    \"Vermontese\",\n    \"Wenchowese\",\n    \"Yengeese\",\n]\n\n\npl_sb_uninflected_endings = [\n    # UNCOUNTABLE NOUNS\n    \"butter\",\n    \"cash\",\n    \"furniture\",\n    \"information\",\n    # SOME FISH AND HERD ANIMALS\n    \"fish\",\n    \"deer\",\n    \"sheep\",\n    # ALL NATIONALS ENDING IN -ese\n    \"nese\",\n    \"rese\",\n    \"lese\",\n    \"mese\",\n    # DISEASES\n    \"pox\",\n    # OTHER ODDITIES\n    \"craft\",\n] + pl_sb_uninflected_s_endings\n# SOME WORDS ENDING IN ...s (OFTEN PAIRS TAKEN AS A WHOLE)\n\n\npl_sb_uninflected_bysize = bysize(pl_sb_uninflected_endings)\n\n\n# SINGULAR WORDS ENDING IN ...s (ALL INFLECT WITH ...es)\n\npl_sb_singular_s_complete = [\n    \"acropolis\",\n    \"aegis\",\n    \"alias\",\n    \"asbestos\",\n    \"bathos\",\n    \"bias\",\n    \"bronchitis\",\n    \"bursitis\",\n    \"caddis\",\n    \"cannabis\",\n    \"canvas\",\n    \"chaos\",\n    \"cosmos\",\n    \"dais\",\n    \"digitalis\",\n    \"epidermis\",\n    \"ethos\",\n    \"eyas\",\n    \"gas\",\n    \"glottis\",\n    \"hubris\",\n    \"ibis\",\n    \"lens\",\n    \"mantis\",\n    \"marquis\",\n    \"metropolis\",\n    \"pathos\",\n    \"pelvis\",\n    \"polis\",\n    \"rhinoceros\",\n    \"sassafras\",\n    \"trellis\",\n] + pl_sb_C_is_ides_complete\n\n\npl_sb_singular_s_endings = [\"ss\", \"us\"] + pl_sb_C_is_ides_endings\n\npl_sb_singular_s_bysize = bysize(pl_sb_singular_s_endings)\n\nsi_sb_singular_s_complete = [f\"{w}es\" for w in pl_sb_singular_s_complete]\nsi_sb_singular_s_endings = [f\"{w}es\" for w in pl_sb_singular_s_endings]\nsi_sb_singular_s_bysize = bysize(si_sb_singular_s_endings)\n\npl_sb_singular_s_es = [\"[A-Z].*es\"]\n\npl_sb_singular_s = enclose(\n    \"|\".join(\n        pl_sb_singular_s_complete\n        + [f\".*{w}\" for w in pl_sb_singular_s_endings]\n        + pl_sb_singular_s_es\n    )\n)\n\n\n# PLURALS ENDING IN uses -> use\n\n\nsi_sb_ois_oi_case = (\"Bolshois\", \"Hanois\")\n\nsi_sb_uses_use_case = (\"Betelgeuses\", \"Duses\", \"Meuses\", \"Syracuses\", \"Toulouses\")\n\nsi_sb_uses_use = (\n    \"abuses\",\n    \"applauses\",\n    \"blouses\",\n    \"carouses\",\n    \"causes\",\n    \"chartreuses\",\n    \"clauses\",\n    \"contuses\",\n    \"douses\",\n    \"excuses\",\n    \"fuses\",\n    \"grouses\",\n    \"hypotenuses\",\n    \"masseuses\",\n    \"menopauses\",\n    \"misuses\",\n    \"muses\",\n    \"overuses\",\n    \"pauses\",\n    \"peruses\",\n    \"profuses\",\n    \"recluses\",\n    \"reuses\",\n    \"ruses\",\n    \"souses\",\n    \"spouses\",\n    \"suffuses\",\n    \"transfuses\",\n    \"uses\",\n)\n\nsi_sb_ies_ie_case = (\n    \"Addies\",\n    \"Aggies\",\n    \"Allies\",\n    \"Amies\",\n    \"Angies\",\n    \"Annies\",\n    \"Annmaries\",\n    \"Archies\",\n    \"Arties\",\n    \"Aussies\",\n    \"Barbies\",\n    \"Barries\",\n    \"Basies\",\n    \"Bennies\",\n    \"Bernies\",\n    \"Berties\",\n    \"Bessies\",\n    \"Betties\",\n    \"Billies\",\n    \"Blondies\",\n    \"Bobbies\",\n    \"Bonnies\",\n    \"Bowies\",\n    \"Brandies\",\n    \"Bries\",\n    \"Brownies\",\n    \"Callies\",\n    \"Carnegies\",\n    \"Carries\",\n    \"Cassies\",\n    \"Charlies\",\n    \"Cheries\",\n    \"Christies\",\n    \"Connies\",\n    \"Curies\",\n    \"Dannies\",\n    \"Debbies\",\n    \"Dixies\",\n    \"Dollies\",\n    \"Donnies\",\n    \"Drambuies\",\n    \"Eddies\",\n    \"Effies\",\n    \"Ellies\",\n    \"Elsies\",\n    \"Eries\",\n    \"Ernies\",\n    \"Essies\",\n    \"Eugenies\",\n    \"Fannies\",\n    \"Flossies\",\n    \"Frankies\",\n    \"Freddies\",\n    \"Gillespies\",\n    \"Goldies\",\n    \"Gracies\",\n    \"Guthries\",\n    \"Hallies\",\n    \"Hatties\",\n    \"Hetties\",\n    \"Hollies\",\n    \"Jackies\",\n    \"Jamies\",\n    \"Janies\",\n    \"Jannies\",\n    \"Jeanies\",\n    \"Jeannies\",\n    \"Jennies\",\n    \"Jessies\",\n    \"Jimmies\",\n    \"Jodies\",\n    \"Johnies\",\n    \"Johnnies\",\n    \"Josies\",\n    \"Julies\",\n    \"Kalgoorlies\",\n    \"Kathies\",\n    \"Katies\",\n    \"Kellies\",\n    \"Kewpies\",\n    \"Kristies\",\n    \"Laramies\",\n    \"Lassies\",\n    \"Lauries\",\n    \"Leslies\",\n    \"Lessies\",\n    \"Lillies\",\n    \"Lizzies\",\n    \"Lonnies\",\n    \"Lories\",\n    \"Lorries\",\n    \"Lotties\",\n    \"Louies\",\n    \"Mackenzies\",\n    \"Maggies\",\n    \"Maisies\",\n    \"Mamies\",\n    \"Marcies\",\n    \"Margies\",\n    \"Maries\",\n    \"Marjories\",\n    \"Matties\",\n    \"McKenzies\",\n    \"Melanies\",\n    \"Mickies\",\n    \"Millies\",\n    \"Minnies\",\n    \"Mollies\",\n    \"Mounties\",\n    \"Nannies\",\n    \"Natalies\",\n    \"Nellies\",\n    \"Netties\",\n    \"Ollies\",\n    \"Ozzies\",\n    \"Pearlies\",\n    \"Pottawatomies\",\n    \"Reggies\",\n    \"Richies\",\n    \"Rickies\",\n    \"Robbies\",\n    \"Ronnies\",\n    \"Rosalies\",\n    \"Rosemaries\",\n    \"Rosies\",\n    \"Roxies\",\n    \"Rushdies\",\n    \"Ruthies\",\n    \"Sadies\",\n    \"Sallies\",\n    \"Sammies\",\n    \"Scotties\",\n    \"Selassies\",\n    \"Sherries\",\n    \"Sophies\",\n    \"Stacies\",\n    \"Stefanies\",\n    \"Stephanies\",\n    \"Stevies\",\n    \"Susies\",\n    \"Sylvies\",\n    \"Tammies\",\n    \"Terries\",\n    \"Tessies\",\n    \"Tommies\",\n    \"Tracies\",\n    \"Trekkies\",\n    \"Valaries\",\n    \"Valeries\",\n    \"Valkyries\",\n    \"Vickies\",\n    \"Virgies\",\n    \"Willies\",\n    \"Winnies\",\n    \"Wylies\",\n    \"Yorkies\",\n)\n\nsi_sb_ies_ie = (\n    \"aeries\",\n    \"baggies\",\n    \"belies\",\n    \"biggies\",\n    \"birdies\",\n    \"bogies\",\n    \"bonnies\",\n    \"boogies\",\n    \"bookies\",\n    \"bourgeoisies\",\n    \"brownies\",\n    \"budgies\",\n    \"caddies\",\n    \"calories\",\n    \"camaraderies\",\n    \"cockamamies\",\n    \"collies\",\n    \"cookies\",\n    \"coolies\",\n    \"cooties\",\n    \"coteries\",\n    \"crappies\",\n    \"curies\",\n    \"cutesies\",\n    \"dogies\",\n    \"eyries\",\n    \"floozies\",\n    \"footsies\",\n    \"freebies\",\n    \"genies\",\n    \"goalies\",\n    \"groupies\",\n    \"hies\",\n    \"jalousies\",\n    \"junkies\",\n    \"kiddies\",\n    \"laddies\",\n    \"lassies\",\n    \"lies\",\n    \"lingeries\",\n    \"magpies\",\n    \"menageries\",\n    \"mommies\",\n    \"movies\",\n    \"neckties\",\n    \"newbies\",\n    \"nighties\",\n    \"oldies\",\n    \"organdies\",\n    \"overlies\",\n    \"pies\",\n    \"pinkies\",\n    \"pixies\",\n    \"potpies\",\n    \"prairies\",\n    \"quickies\",\n    \"reveries\",\n    \"rookies\",\n    \"rotisseries\",\n    \"softies\",\n    \"sorties\",\n    \"species\",\n    \"stymies\",\n    \"sweeties\",\n    \"ties\",\n    \"underlies\",\n    \"unties\",\n    \"veggies\",\n    \"vies\",\n    \"yuppies\",\n    \"zombies\",\n)\n\n\nsi_sb_oes_oe_case = (\n    \"Chloes\",\n    \"Crusoes\",\n    \"Defoes\",\n    \"Faeroes\",\n    \"Ivanhoes\",\n    \"Joes\",\n    \"McEnroes\",\n    \"Moes\",\n    \"Monroes\",\n    \"Noes\",\n    \"Poes\",\n    \"Roscoes\",\n    \"Tahoes\",\n    \"Tippecanoes\",\n    \"Zoes\",\n)\n\nsi_sb_oes_oe = (\n    \"aloes\",\n    \"backhoes\",\n    \"canoes\",\n    \"does\",\n    \"floes\",\n    \"foes\",\n    \"hoes\",\n    \"mistletoes\",\n    \"oboes\",\n    \"pekoes\",\n    \"roes\",\n    \"sloes\",\n    \"throes\",\n    \"tiptoes\",\n    \"toes\",\n    \"woes\",\n)\n\nsi_sb_z_zes = (\"quartzes\", \"topazes\")\n\nsi_sb_zzes_zz = (\"buzzes\", \"fizzes\", \"frizzes\", \"razzes\")\n\nsi_sb_ches_che_case = (\n    \"Andromaches\",\n    \"Apaches\",\n    \"Blanches\",\n    \"Comanches\",\n    \"Nietzsches\",\n    \"Porsches\",\n    \"Roches\",\n)\n\nsi_sb_ches_che = (\n    \"aches\",\n    \"avalanches\",\n    \"backaches\",\n    \"bellyaches\",\n    \"caches\",\n    \"cloches\",\n    \"creches\",\n    \"douches\",\n    \"earaches\",\n    \"fiches\",\n    \"headaches\",\n    \"heartaches\",\n    \"microfiches\",\n    \"niches\",\n    \"pastiches\",\n    \"psyches\",\n    \"quiches\",\n    \"stomachaches\",\n    \"toothaches\",\n    \"tranches\",\n)\n\nsi_sb_xes_xe = (\"annexes\", \"axes\", \"deluxes\", \"pickaxes\")\n\nsi_sb_sses_sse_case = (\"Hesses\", \"Jesses\", \"Larousses\", \"Matisses\")\nsi_sb_sses_sse = (\n    \"bouillabaisses\",\n    \"crevasses\",\n    \"demitasses\",\n    \"impasses\",\n    \"mousses\",\n    \"posses\",\n)\n\nsi_sb_ves_ve_case = (\n    # *[nwl]ives -> [nwl]live\n    \"Clives\",\n    \"Palmolives\",\n)\nsi_sb_ves_ve = (\n    # *[^d]eaves -> eave\n    \"interweaves\",\n    \"weaves\",\n    # *[nwl]ives -> [nwl]live\n    \"olives\",\n    # *[eoa]lves -> [eoa]lve\n    \"bivalves\",\n    \"dissolves\",\n    \"resolves\",\n    \"salves\",\n    \"twelves\",\n    \"valves\",\n)\n\n\nplverb_special_s = enclose(\n    \"|\".join(\n        [pl_sb_singular_s]\n        + pl_sb_uninflected_s\n        + list(pl_sb_irregular_s)\n        + [\"(.*[csx])is\", \"(.*)ceps\", \"[A-Z].*s\"]\n    )\n)\n\n_pl_sb_postfix_adj_defn = (\n    (\"general\", enclose(r\"(?!major|lieutenant|brigadier|adjutant|.*star)\\S+\")),\n    (\"martial\", enclose(\"court\")),\n    (\"force\", enclose(\"pound\")),\n)\n\npl_sb_postfix_adj: Iterable[str] = (\n    enclose(val + f\"(?=(?:-|\\\\s+){key})\") for key, val in _pl_sb_postfix_adj_defn\n)\n\npl_sb_postfix_adj_stems = f\"({'|'.join(pl_sb_postfix_adj)})(.*)\"\n\n\n# PLURAL WORDS ENDING IS es GO TO SINGULAR is\n\nsi_sb_es_is = (\n    \"amanuenses\",\n    \"amniocenteses\",\n    \"analyses\",\n    \"antitheses\",\n    \"apotheoses\",\n    \"arterioscleroses\",\n    \"atheroscleroses\",\n    \"axes\",\n    # 'bases', # bases -> basis\n    \"catalyses\",\n    \"catharses\",\n    \"chasses\",\n    \"cirrhoses\",\n    \"cocces\",\n    \"crises\",\n    \"diagnoses\",\n    \"dialyses\",\n    \"diereses\",\n    \"electrolyses\",\n    \"emphases\",\n    \"exegeses\",\n    \"geneses\",\n    \"halitoses\",\n    \"hydrolyses\",\n    \"hypnoses\",\n    \"hypotheses\",\n    \"hystereses\",\n    \"metamorphoses\",\n    \"metastases\",\n    \"misdiagnoses\",\n    \"mitoses\",\n    \"mononucleoses\",\n    \"narcoses\",\n    \"necroses\",\n    \"nemeses\",\n    \"neuroses\",\n    \"oases\",\n    \"osmoses\",\n    \"osteoporoses\",\n    \"paralyses\",\n    \"parentheses\",\n    \"parthenogeneses\",\n    \"periphrases\",\n    \"photosyntheses\",\n    \"probosces\",\n    \"prognoses\",\n    \"prophylaxes\",\n    \"prostheses\",\n    \"preces\",\n    \"psoriases\",\n    \"psychoanalyses\",\n    \"psychokineses\",\n    \"psychoses\",\n    \"scleroses\",\n    \"scolioses\",\n    \"sepses\",\n    \"silicoses\",\n    \"symbioses\",\n    \"synopses\",\n    \"syntheses\",\n    \"taxes\",\n    \"telekineses\",\n    \"theses\",\n    \"thromboses\",\n    \"tuberculoses\",\n    \"urinalyses\",\n)\n\npl_prep_list = \"\"\"\n    about above across after among around at athwart before behind\n    below beneath beside besides between betwixt beyond but by\n    during except for from in into near of off on onto out over\n    since till to under until unto upon with\"\"\".split()\n\npl_prep_list_da = pl_prep_list + [\"de\", \"du\", \"da\"]\n\npl_prep_bysize = bysize(pl_prep_list_da)\n\npl_prep = enclose(\"|\".join(pl_prep_list_da))\n\npl_sb_prep_dual_compound = rf\"(.*?)((?:-|\\s+)(?:{pl_prep})(?:-|\\s+))a(?:-|\\s+)(.*)\"\n\n\nsingular_pronoun_genders = {\n    \"neuter\",\n    \"feminine\",\n    \"masculine\",\n    \"gender-neutral\",\n    \"feminine or masculine\",\n    \"masculine or feminine\",\n}\n\npl_pron_nom = {\n    # NOMINATIVE    REFLEXIVE\n    \"i\": \"we\",\n    \"myself\": \"ourselves\",\n    \"you\": \"you\",\n    \"yourself\": \"yourselves\",\n    \"she\": \"they\",\n    \"herself\": \"themselves\",\n    \"he\": \"they\",\n    \"himself\": \"themselves\",\n    \"it\": \"they\",\n    \"itself\": \"themselves\",\n    \"they\": \"they\",\n    \"themself\": \"themselves\",\n    #   POSSESSIVE\n    \"mine\": \"ours\",\n    \"yours\": \"yours\",\n    \"hers\": \"theirs\",\n    \"his\": \"theirs\",\n    \"its\": \"theirs\",\n    \"theirs\": \"theirs\",\n}\n\nsi_pron: Dict[str, Dict[str, Union[str, Dict[str, str]]]] = {\n    \"nom\": {v: k for (k, v) in pl_pron_nom.items()}\n}\nsi_pron[\"nom\"][\"we\"] = \"I\"\n\n\npl_pron_acc = {\n    # ACCUSATIVE    REFLEXIVE\n    \"me\": \"us\",\n    \"myself\": \"ourselves\",\n    \"you\": \"you\",\n    \"yourself\": \"yourselves\",\n    \"her\": \"them\",\n    \"herself\": \"themselves\",\n    \"him\": \"them\",\n    \"himself\": \"themselves\",\n    \"it\": \"them\",\n    \"itself\": \"themselves\",\n    \"them\": \"them\",\n    \"themself\": \"themselves\",\n}\n\npl_pron_acc_keys = enclose(\"|\".join(pl_pron_acc))\npl_pron_acc_keys_bysize = bysize(pl_pron_acc)\n\nsi_pron[\"acc\"] = {v: k for (k, v) in pl_pron_acc.items()}\n\nfor _thecase, _plur, _gend_sing in (\n    (\n        \"nom\",\n        \"they\",\n        {\n            \"neuter\": \"it\",\n            \"feminine\": \"she\",\n            \"masculine\": \"he\",\n            \"gender-neutral\": \"they\",\n            \"feminine or masculine\": \"she or he\",\n            \"masculine or feminine\": \"he or she\",\n        },\n    ),\n    (\n        \"nom\",\n        \"themselves\",\n        {\n            \"neuter\": \"itself\",\n            \"feminine\": \"herself\",\n            \"masculine\": \"himself\",\n            \"gender-neutral\": \"themself\",\n            \"feminine or masculine\": \"herself or himself\",\n            \"masculine or feminine\": \"himself or herself\",\n        },\n    ),\n    (\n        \"nom\",\n        \"theirs\",\n        {\n            \"neuter\": \"its\",\n            \"feminine\": \"hers\",\n            \"masculine\": \"his\",\n            \"gender-neutral\": \"theirs\",\n            \"feminine or masculine\": \"hers or his\",\n            \"masculine or feminine\": \"his or hers\",\n        },\n    ),\n    (\n        \"acc\",\n        \"them\",\n        {\n            \"neuter\": \"it\",\n            \"feminine\": \"her\",\n            \"masculine\": \"him\",\n            \"gender-neutral\": \"them\",\n            \"feminine or masculine\": \"her or him\",\n            \"masculine or feminine\": \"him or her\",\n        },\n    ),\n    (\n        \"acc\",\n        \"themselves\",\n        {\n            \"neuter\": \"itself\",\n            \"feminine\": \"herself\",\n            \"masculine\": \"himself\",\n            \"gender-neutral\": \"themself\",\n            \"feminine or masculine\": \"herself or himself\",\n            \"masculine or feminine\": \"himself or herself\",\n        },\n    ),\n):\n    si_pron[_thecase][_plur] = _gend_sing\n\n\nsi_pron_acc_keys = enclose(\"|\".join(si_pron[\"acc\"]))\nsi_pron_acc_keys_bysize = bysize(si_pron[\"acc\"])\n\n\ndef get_si_pron(thecase, word, gender) -> str:\n    try:\n        sing = si_pron[thecase][word]\n    except KeyError:\n        raise  # not a pronoun\n    try:\n        return sing[gender]  # has several types due to gender\n    except TypeError:\n        return cast(str, sing)  # answer independent of gender\n\n\n# These dictionaries group verbs by first, second and third person\n# conjugations.\n\nplverb_irregular_pres = {\n    \"am\": \"are\",\n    \"are\": \"are\",\n    \"is\": \"are\",\n    \"was\": \"were\",\n    \"were\": \"were\",\n    \"have\": \"have\",\n    \"has\": \"have\",\n    \"do\": \"do\",\n    \"does\": \"do\",\n}\n\nplverb_ambiguous_pres = {\n    \"act\": \"act\",\n    \"acts\": \"act\",\n    \"blame\": \"blame\",\n    \"blames\": \"blame\",\n    \"can\": \"can\",\n    \"must\": \"must\",\n    \"fly\": \"fly\",\n    \"flies\": \"fly\",\n    \"copy\": \"copy\",\n    \"copies\": \"copy\",\n    \"drink\": \"drink\",\n    \"drinks\": \"drink\",\n    \"fight\": \"fight\",\n    \"fights\": \"fight\",\n    \"fire\": \"fire\",\n    \"fires\": \"fire\",\n    \"like\": \"like\",\n    \"likes\": \"like\",\n    \"look\": \"look\",\n    \"looks\": \"look\",\n    \"make\": \"make\",\n    \"makes\": \"make\",\n    \"reach\": \"reach\",\n    \"reaches\": \"reach\",\n    \"run\": \"run\",\n    \"runs\": \"run\",\n    \"sink\": \"sink\",\n    \"sinks\": \"sink\",\n    \"sleep\": \"sleep\",\n    \"sleeps\": \"sleep\",\n    \"view\": \"view\",\n    \"views\": \"view\",\n}\n\nplverb_ambiguous_pres_keys = re.compile(\n    rf\"^({enclose('|'.join(plverb_ambiguous_pres))})((\\s.*)?)$\", re.IGNORECASE\n)\n\n\nplverb_irregular_non_pres = (\n    \"did\",\n    \"had\",\n    \"ate\",\n    \"made\",\n    \"put\",\n    \"spent\",\n    \"fought\",\n    \"sank\",\n    \"gave\",\n    \"sought\",\n    \"shall\",\n    \"could\",\n    \"ought\",\n    \"should\",\n)\n\nplverb_ambiguous_non_pres = re.compile(\n    r\"^((?:thought|saw|bent|will|might|cut))((\\s.*)?)$\", re.IGNORECASE\n)\n\n# \"..oes\" -> \"..oe\" (the rest are \"..oes\" -> \"o\")\n\npl_v_oes_oe = (\"canoes\", \"floes\", \"oboes\", \"roes\", \"throes\", \"woes\")\npl_v_oes_oe_endings_size4 = (\"hoes\", \"toes\")\npl_v_oes_oe_endings_size5 = (\"shoes\",)\n\n\npl_count_zero = (\"0\", \"no\", \"zero\", \"nil\")\n\n\npl_count_one = (\"1\", \"a\", \"an\", \"one\", \"each\", \"every\", \"this\", \"that\")\n\npl_adj_special = {\"a\": \"some\", \"an\": \"some\", \"this\": \"these\", \"that\": \"those\"}\n\npl_adj_special_keys = re.compile(\n    rf\"^({enclose('|'.join(pl_adj_special))})$\", re.IGNORECASE\n)\n\npl_adj_poss = {\n    \"my\": \"our\",\n    \"your\": \"your\",\n    \"its\": \"their\",\n    \"her\": \"their\",\n    \"his\": \"their\",\n    \"their\": \"their\",\n}\n\npl_adj_poss_keys = re.compile(rf\"^({enclose('|'.join(pl_adj_poss))})$\", re.IGNORECASE)\n\n\n# 2. INDEFINITE ARTICLES\n\n# THIS PATTERN MATCHES STRINGS OF CAPITALS STARTING WITH A \"VOWEL-SOUND\"\n# CONSONANT FOLLOWED BY ANOTHER CONSONANT, AND WHICH ARE NOT LIKELY\n# TO BE REAL WORDS (OH, ALL RIGHT THEN, IT'S JUST MAGIC!)\n\nA_abbrev = re.compile(\n    r\"\"\"\n^(?! FJO | [HLMNS]Y.  | RY[EO] | SQU\n  | ( F[LR]? | [HL] | MN? | N | RH? | S[CHKLMNPTVW]? | X(YL)?) [AEIOU])\n[FHLMNRSX][A-Z]\n\"\"\",\n    re.VERBOSE,\n)\n\n# THIS PATTERN CODES THE BEGINNINGS OF ALL ENGLISH WORDS BEGINING WITH A\n# 'y' FOLLOWED BY A CONSONANT. ANY OTHER Y-CONSONANT PREFIX THEREFORE\n# IMPLIES AN ABBREVIATION.\n\nA_y_cons = re.compile(r\"^(y(b[lor]|cl[ea]|fere|gg|p[ios]|rou|tt))\", re.IGNORECASE)\n\n# EXCEPTIONS TO EXCEPTIONS\n\nA_explicit_a = re.compile(r\"^((?:unabomber|unanimous|US))\", re.IGNORECASE)\n\nA_explicit_an = re.compile(\n    r\"^((?:euler|hour(?!i)|heir|honest|hono[ur]|mpeg))\", re.IGNORECASE\n)\n\nA_ordinal_an = re.compile(r\"^([aefhilmnorsx]-?th)\", re.IGNORECASE)\n\nA_ordinal_a = re.compile(r\"^([bcdgjkpqtuvwyz]-?th)\", re.IGNORECASE)\n\n\n# NUMERICAL INFLECTIONS\n\nnth = {\n    0: \"th\",\n    1: \"st\",\n    2: \"nd\",\n    3: \"rd\",\n    4: \"th\",\n    5: \"th\",\n    6: \"th\",\n    7: \"th\",\n    8: \"th\",\n    9: \"th\",\n    11: \"th\",\n    12: \"th\",\n    13: \"th\",\n}\nnth_suff = set(nth.values())\n\nordinal = dict(\n    ty=\"tieth\",\n    one=\"first\",\n    two=\"second\",\n    three=\"third\",\n    five=\"fifth\",\n    eight=\"eighth\",\n    nine=\"ninth\",\n    twelve=\"twelfth\",\n)\n\nordinal_suff = re.compile(rf\"({'|'.join(ordinal)})\\Z\")\n\n\n# NUMBERS\n\nunit = [\"\", \"one\", \"two\", \"three\", \"four\", \"five\", \"six\", \"seven\", \"eight\", \"nine\"]\nteen = [\n    \"ten\",\n    \"eleven\",\n    \"twelve\",\n    \"thirteen\",\n    \"fourteen\",\n    \"fifteen\",\n    \"sixteen\",\n    \"seventeen\",\n    \"eighteen\",\n    \"nineteen\",\n]\nten = [\n    \"\",\n    \"\",\n    \"twenty\",\n    \"thirty\",\n    \"forty\",\n    \"fifty\",\n    \"sixty\",\n    \"seventy\",\n    \"eighty\",\n    \"ninety\",\n]\nmill = [\n    \" \",\n    \" thousand\",\n    \" million\",\n    \" billion\",\n    \" trillion\",\n    \" quadrillion\",\n    \" quintillion\",\n    \" sextillion\",\n    \" septillion\",\n    \" octillion\",\n    \" nonillion\",\n    \" decillion\",\n]\n\n\n# SUPPORT CLASSICAL PLURALIZATIONS\n\ndef_classical = dict(\n    all=False, zero=False, herd=False, names=True, persons=False, ancient=False\n)\n\nall_classical = {k: True for k in def_classical}\nno_classical = {k: False for k in def_classical}\n\n\n# Maps strings to built-in constant types\nstring_to_constant = {\"True\": True, \"False\": False, \"None\": None}\n\n\n# Pre-compiled regular expression objects\nDOLLAR_DIGITS = re.compile(r\"\\$(\\d+)\")\nFUNCTION_CALL = re.compile(r\"((\\w+)\\([^)]*\\)*)\", re.IGNORECASE)\nPARTITION_WORD = re.compile(r\"\\A(\\s*)(.+?)(\\s*)\\Z\")\nPL_SB_POSTFIX_ADJ_STEMS_RE = re.compile(\n    rf\"^(?:{pl_sb_postfix_adj_stems})$\", re.IGNORECASE\n)\nPL_SB_PREP_DUAL_COMPOUND_RE = re.compile(\n    rf\"^(?:{pl_sb_prep_dual_compound})$\", re.IGNORECASE\n)\nDENOMINATOR = re.compile(r\"(?P<denominator>.+)( (per|a) .+)\")\nPLVERB_SPECIAL_S_RE = re.compile(rf\"^({plverb_special_s})$\")\nWHITESPACE = re.compile(r\"\\s\")\nENDS_WITH_S = re.compile(r\"^(.*[^s])s$\", re.IGNORECASE)\nENDS_WITH_APOSTROPHE_S = re.compile(r\"^(.+)'s?$\")\nINDEFINITE_ARTICLE_TEST = re.compile(r\"\\A(\\s*)(?:an?\\s+)?(.+?)(\\s*)\\Z\", re.IGNORECASE)\nSPECIAL_AN = re.compile(r\"^[aefhilmnorsx]$\", re.IGNORECASE)\nSPECIAL_A = re.compile(r\"^[bcdgjkpqtuvwyz]$\", re.IGNORECASE)\nSPECIAL_ABBREV_AN = re.compile(r\"^[aefhilmnorsx][.-]\", re.IGNORECASE)\nSPECIAL_ABBREV_A = re.compile(r\"^[a-z][.-]\", re.IGNORECASE)\nCONSONANTS = re.compile(r\"^[^aeiouy]\", re.IGNORECASE)\nARTICLE_SPECIAL_EU = re.compile(r\"^e[uw]\", re.IGNORECASE)\nARTICLE_SPECIAL_ONCE = re.compile(r\"^onc?e\\b\", re.IGNORECASE)\nARTICLE_SPECIAL_ONETIME = re.compile(r\"^onetime\\b\", re.IGNORECASE)\nARTICLE_SPECIAL_UNIT = re.compile(r\"^uni([^nmd]|mo)\", re.IGNORECASE)\nARTICLE_SPECIAL_UBA = re.compile(r\"^u[bcfghjkqrst][aeiou]\", re.IGNORECASE)\nARTICLE_SPECIAL_UKR = re.compile(r\"^ukr\", re.IGNORECASE)\nSPECIAL_CAPITALS = re.compile(r\"^U[NK][AIEO]?\")\nVOWELS = re.compile(r\"^[aeiou]\", re.IGNORECASE)\n\nDIGIT_GROUP = re.compile(r\"(\\d)\")\nTWO_DIGITS = re.compile(r\"(\\d)(\\d)\")\nTHREE_DIGITS = re.compile(r\"(\\d)(\\d)(\\d)\")\nTHREE_DIGITS_WORD = re.compile(r\"(\\d)(\\d)(\\d)(?=\\D*\\Z)\")\nTWO_DIGITS_WORD = re.compile(r\"(\\d)(\\d)(?=\\D*\\Z)\")\nONE_DIGIT_WORD = re.compile(r\"(\\d)(?=\\D*\\Z)\")\n\nFOUR_DIGIT_COMMA = re.compile(r\"(\\d)(\\d{3}(?:,|\\Z))\")\nNON_DIGIT = re.compile(r\"\\D\")\nWHITESPACES_COMMA = re.compile(r\"\\s+,\")\nCOMMA_WORD = re.compile(r\", (\\S+)\\s+\\Z\")\nWHITESPACES = re.compile(r\"\\s+\")\n\n\nPRESENT_PARTICIPLE_REPLACEMENTS = (\n    (re.compile(r\"ie$\"), r\"y\"),\n    (\n        re.compile(r\"ue$\"),\n        r\"u\",\n    ),  # TODO: isn't ue$ -> u encompassed in the following rule?\n    (re.compile(r\"([auy])e$\"), r\"\\g<1>\"),\n    (re.compile(r\"ski$\"), r\"ski\"),\n    (re.compile(r\"[^b]i$\"), r\"\"),\n    (re.compile(r\"^(are|were)$\"), r\"be\"),\n    (re.compile(r\"^(had)$\"), r\"hav\"),\n    (re.compile(r\"^(hoe)$\"), r\"\\g<1>\"),\n    (re.compile(r\"([^e])e$\"), r\"\\g<1>\"),\n    (re.compile(r\"er$\"), r\"er\"),\n    (re.compile(r\"([^aeiou][aeiouy]([bdgmnprst]))$\"), r\"\\g<1>\\g<2>\"),\n)\n\nDIGIT = re.compile(r\"\\d\")\n\n\nclass Words(str):\n    lowered: str\n    split_: List[str]\n    first: str\n    last: str\n\n    def __init__(self, orig) -> None:\n        self.lowered = self.lower()\n        self.split_ = self.split()\n        self.first = self.split_[0]\n        self.last = self.split_[-1]\n\n\nFalsish = Any  # ideally, falsish would only validate on bool(value) is False\n\n\n_STATIC_TYPE_CHECKING = TYPE_CHECKING\n# ^-- Workaround for typeguard AST manipulation:\n#     https://github.com/agronholm/typeguard/issues/353#issuecomment-1556306554\n\nif _STATIC_TYPE_CHECKING:  # pragma: no cover\n    Word = Annotated[str, \"String with at least 1 character\"]\nelse:\n\n    class _WordMeta(type):  # Too dynamic to be supported by mypy...\n        def __instancecheck__(self, instance: Any) -> bool:\n            return isinstance(instance, str) and len(instance) >= 1\n\n    class Word(metaclass=_WordMeta):  # type: ignore[no-redef]\n        \"\"\"String with at least 1 character\"\"\"\n\n\nclass engine:\n    def __init__(self) -> None:\n        self.classical_dict = def_classical.copy()\n        self.persistent_count: Optional[int] = None\n        self.mill_count = 0\n        self.pl_sb_user_defined: List[Optional[Word]] = []\n        self.pl_v_user_defined: List[Optional[Word]] = []\n        self.pl_adj_user_defined: List[Optional[Word]] = []\n        self.si_sb_user_defined: List[Optional[Word]] = []\n        self.A_a_user_defined: List[Optional[Word]] = []\n        self.thegender = \"neuter\"\n        self.__number_args: Optional[Dict[str, str]] = None\n\n    @property\n    def _number_args(self):\n        return cast(Dict[str, str], self.__number_args)\n\n    @_number_args.setter\n    def _number_args(self, val):\n        self.__number_args = val\n\n    @typechecked\n    def defnoun(self, singular: Optional[Word], plural: Optional[Word]) -> int:\n        \"\"\"\n        Set the noun plural of singular to plural.\n\n        \"\"\"\n        self.checkpat(singular)\n        self.checkpatplural(plural)\n        self.pl_sb_user_defined.extend((singular, plural))\n        self.si_sb_user_defined.extend((plural, singular))\n        return 1\n\n    @typechecked\n    def defverb(\n        self,\n        s1: Optional[Word],\n        p1: Optional[Word],\n        s2: Optional[Word],\n        p2: Optional[Word],\n        s3: Optional[Word],\n        p3: Optional[Word],\n    ) -> int:\n        \"\"\"\n        Set the verb plurals for s1, s2 and s3 to p1, p2 and p3 respectively.\n\n        Where 1, 2 and 3 represent the 1st, 2nd and 3rd person forms of the verb.\n\n        \"\"\"\n        self.checkpat(s1)\n        self.checkpat(s2)\n        self.checkpat(s3)\n        self.checkpatplural(p1)\n        self.checkpatplural(p2)\n        self.checkpatplural(p3)\n        self.pl_v_user_defined.extend((s1, p1, s2, p2, s3, p3))\n        return 1\n\n    @typechecked\n    def defadj(self, singular: Optional[Word], plural: Optional[Word]) -> int:\n        \"\"\"\n        Set the adjective plural of singular to plural.\n\n        \"\"\"\n        self.checkpat(singular)\n        self.checkpatplural(plural)\n        self.pl_adj_user_defined.extend((singular, plural))\n        return 1\n\n    @typechecked\n    def defa(self, pattern: Optional[Word]) -> int:\n        \"\"\"\n        Define the indefinite article as 'a' for words matching pattern.\n\n        \"\"\"\n        self.checkpat(pattern)\n        self.A_a_user_defined.extend((pattern, \"a\"))\n        return 1\n\n    @typechecked\n    def defan(self, pattern: Optional[Word]) -> int:\n        \"\"\"\n        Define the indefinite article as 'an' for words matching pattern.\n\n        \"\"\"\n        self.checkpat(pattern)\n        self.A_a_user_defined.extend((pattern, \"an\"))\n        return 1\n\n    def checkpat(self, pattern: Optional[Word]) -> None:\n        \"\"\"\n        check for errors in a regex pattern\n        \"\"\"\n        if pattern is None:\n            return\n        try:\n            re.match(pattern, \"\")\n        except re.error as err:\n            raise BadUserDefinedPatternError(pattern) from err\n\n    def checkpatplural(self, pattern: Optional[Word]) -> None:\n        \"\"\"\n        check for errors in a regex replace pattern\n        \"\"\"\n        return\n\n    @typechecked\n    def ud_match(self, word: Word, wordlist: Sequence[Optional[Word]]) -> Optional[str]:\n        for i in range(len(wordlist) - 2, -2, -2):  # backwards through even elements\n            mo = re.search(rf\"^{wordlist[i]}$\", word, re.IGNORECASE)\n            if mo:\n                if wordlist[i + 1] is None:\n                    return None\n                pl = DOLLAR_DIGITS.sub(\n                    r\"\\\\1\", cast(Word, wordlist[i + 1])\n                )  # change $n to \\n for expand\n                return mo.expand(pl)\n        return None\n\n    def classical(self, **kwargs) -> None:\n        \"\"\"\n        turn classical mode on and off for various categories\n\n        turn on all classical modes:\n        classical()\n        classical(all=True)\n\n        turn on or off specific claassical modes:\n        e.g.\n        classical(herd=True)\n        classical(names=False)\n\n        By default all classical modes are off except names.\n\n        unknown value in args or key in kwargs raises\n        exception: UnknownClasicalModeError\n\n        \"\"\"\n        if not kwargs:\n            self.classical_dict = all_classical.copy()\n            return\n        if \"all\" in kwargs:\n            if kwargs[\"all\"]:\n                self.classical_dict = all_classical.copy()\n            else:\n                self.classical_dict = no_classical.copy()\n\n        for k, v in kwargs.items():\n            if k in def_classical:\n                self.classical_dict[k] = v\n            else:\n                raise UnknownClassicalModeError\n\n    def num(\n        self, count: Optional[int] = None, show: Optional[int] = None\n    ) -> str:  # (;$count,$show)\n        \"\"\"\n        Set the number to be used in other method calls.\n\n        Returns count.\n\n        Set show to False to return '' instead.\n\n        \"\"\"\n        if count is not None:\n            try:\n                self.persistent_count = int(count)\n            except ValueError as err:\n                raise BadNumValueError from err\n            if (show is None) or show:\n                return str(count)\n        else:\n            self.persistent_count = None\n        return \"\"\n\n    def gender(self, gender: str) -> None:\n        \"\"\"\n        set the gender for the singular of plural pronouns\n\n        can be one of:\n        'neuter'                ('they' -> 'it')\n        'feminine'              ('they' -> 'she')\n        'masculine'             ('they' -> 'he')\n        'gender-neutral'        ('they' -> 'they')\n        'feminine or masculine' ('they' -> 'she or he')\n        'masculine or feminine' ('they' -> 'he or she')\n        \"\"\"\n        if gender in singular_pronoun_genders:\n            self.thegender = gender\n        else:\n            raise BadGenderError\n\n    def _get_value_from_ast(self, obj):\n        \"\"\"\n        Return the value of the ast object.\n        \"\"\"\n        if isinstance(obj, ast.Constant):\n            return obj.value\n        elif isinstance(obj, ast.List):\n            return [self._get_value_from_ast(e) for e in obj.elts]\n        elif isinstance(obj, ast.Tuple):\n            return tuple([self._get_value_from_ast(e) for e in obj.elts])\n\n        # Probably passed a variable name.\n        # Or passed a single word without wrapping it in quotes as an argument\n        # ex: p.inflect(\"I plural(see)\") instead of p.inflect(\"I plural('see')\")\n        raise NameError(f\"name '{obj.id}' is not defined\")\n\n    def _string_to_substitute(\n        self, mo: Match, methods_dict: Dict[str, Callable]\n    ) -> str:\n        \"\"\"\n        Return the string to be substituted for the match.\n        \"\"\"\n        matched_text, f_name = mo.groups()\n        # matched_text is the complete match string. e.g. plural_noun(cat)\n        # f_name is the function name. e.g. plural_noun\n\n        # Return matched_text if function name is not in methods_dict\n        if f_name not in methods_dict:\n            return matched_text\n\n        # Parse the matched text\n        a_tree = ast.parse(matched_text)\n\n        # get the args and kwargs from ast objects\n        args_list = [\n            self._get_value_from_ast(a)\n            for a in a_tree.body[0].value.args  # type: ignore[attr-defined]\n        ]\n        kwargs_list = {\n            kw.arg: self._get_value_from_ast(kw.value)\n            for kw in a_tree.body[0].value.keywords  # type: ignore[attr-defined]\n        }\n\n        # Call the corresponding function\n        return methods_dict[f_name](*args_list, **kwargs_list)\n\n    # 0. PERFORM GENERAL INFLECTIONS IN A STRING\n\n    @typechecked\n    def inflect(self, text: Word) -> str:\n        \"\"\"\n        Perform inflections in a string.\n\n        e.g. inflect('The plural of cat is plural(cat)') returns\n        'The plural of cat is cats'\n\n        can use plural, plural_noun, plural_verb, plural_adj,\n        singular_noun, a, an, no, ordinal, number_to_words,\n        and prespart\n\n        \"\"\"\n        save_persistent_count = self.persistent_count\n\n        # Dictionary of allowed methods\n        methods_dict: Dict[str, Callable] = {\n            \"plural\": self.plural,\n            \"plural_adj\": self.plural_adj,\n            \"plural_noun\": self.plural_noun,\n            \"plural_verb\": self.plural_verb,\n            \"singular_noun\": self.singular_noun,\n            \"a\": self.a,\n            \"an\": self.a,\n            \"no\": self.no,\n            \"ordinal\": self.ordinal,\n            \"number_to_words\": self.number_to_words,\n            \"present_participle\": self.present_participle,\n            \"num\": self.num,\n        }\n\n        # Regular expression to find Python's function call syntax\n        output = FUNCTION_CALL.sub(\n            lambda mo: self._string_to_substitute(mo, methods_dict), text\n        )\n        self.persistent_count = save_persistent_count\n        return output\n\n    # ## PLURAL SUBROUTINES\n\n    def postprocess(self, orig: str, inflected) -> str:\n        inflected = str(inflected)\n        if \"|\" in inflected:\n            word_options = inflected.split(\"|\")\n            # When two parts of a noun need to be pluralized\n            if len(word_options[0].split(\" \")) == len(word_options[1].split(\" \")):\n                result = inflected.split(\"|\")[self.classical_dict[\"all\"]].split(\" \")\n            # When only the last part of the noun needs to be pluralized\n            else:\n                result = inflected.split(\" \")\n                for index, word in enumerate(result):\n                    if \"|\" in word:\n                        result[index] = word.split(\"|\")[self.classical_dict[\"all\"]]\n        else:\n            result = inflected.split(\" \")\n\n        # Try to fix word wise capitalization\n        for index, word in enumerate(orig.split(\" \")):\n            if word == \"I\":\n                # Is this the only word for exceptions like this\n                # Where the original is fully capitalized\n                # without 'meaning' capitalization?\n                # Also this fails to handle a capitalizaion in context\n                continue\n            if word.capitalize() == word:\n                result[index] = result[index].capitalize()\n            if word == word.upper():\n                result[index] = result[index].upper()\n        return \" \".join(result)\n\n    def partition_word(self, text: str) -> Tuple[str, str, str]:\n        mo = PARTITION_WORD.search(text)\n        if mo:\n            return mo.group(1), mo.group(2), mo.group(3)\n        else:\n            return \"\", \"\", \"\"\n\n    @typechecked\n    def plural(self, text: Word, count: Optional[Union[str, int, Any]] = None) -> str:\n        \"\"\"\n        Return the plural of text.\n\n        If count supplied, then return text if count is one of:\n            1, a, an, one, each, every, this, that\n\n        otherwise return the plural.\n\n        Whitespace at the start and end is preserved.\n\n        \"\"\"\n        pre, word, post = self.partition_word(text)\n        if not word:\n            return text\n        plural = self.postprocess(\n            word,\n            self._pl_special_adjective(word, count)\n            or self._pl_special_verb(word, count)\n            or self._plnoun(word, count),\n        )\n        return f\"{pre}{plural}{post}\"\n\n    @typechecked\n    def plural_noun(\n        self, text: Word, count: Optional[Union[str, int, Any]] = None\n    ) -> str:\n        \"\"\"\n        Return the plural of text, where text is a noun.\n\n        If count supplied, then return text if count is one of:\n            1, a, an, one, each, every, this, that\n\n        otherwise return the plural.\n\n        Whitespace at the start and end is preserved.\n\n        \"\"\"\n        pre, word, post = self.partition_word(text)\n        if not word:\n            return text\n        plural = self.postprocess(word, self._plnoun(word, count))\n        return f\"{pre}{plural}{post}\"\n\n    @typechecked\n    def plural_verb(\n        self, text: Word, count: Optional[Union[str, int, Any]] = None\n    ) -> str:\n        \"\"\"\n        Return the plural of text, where text is a verb.\n\n        If count supplied, then return text if count is one of:\n            1, a, an, one, each, every, this, that\n\n        otherwise return the plural.\n\n        Whitespace at the start and end is preserved.\n\n        \"\"\"\n        pre, word, post = self.partition_word(text)\n        if not word:\n            return text\n        plural = self.postprocess(\n            word,\n            self._pl_special_verb(word, count) or self._pl_general_verb(word, count),\n        )\n        return f\"{pre}{plural}{post}\"\n\n    @typechecked\n    def plural_adj(\n        self, text: Word, count: Optional[Union[str, int, Any]] = None\n    ) -> str:\n        \"\"\"\n        Return the plural of text, where text is an adjective.\n\n        If count supplied, then return text if count is one of:\n            1, a, an, one, each, every, this, that\n\n        otherwise return the plural.\n\n        Whitespace at the start and end is preserved.\n\n        \"\"\"\n        pre, word, post = self.partition_word(text)\n        if not word:\n            return text\n        plural = self.postprocess(word, self._pl_special_adjective(word, count) or word)\n        return f\"{pre}{plural}{post}\"\n\n    @typechecked\n    def compare(self, word1: Word, word2: Word) -> Union[str, bool]:\n        \"\"\"\n        compare word1 and word2 for equality regardless of plurality\n\n        return values:\n        eq - the strings are equal\n        p:s - word1 is the plural of word2\n        s:p - word2 is the plural of word1\n        p:p - word1 and word2 are two different plural forms of the one word\n        False - otherwise\n\n        >>> compare = engine().compare\n        >>> compare(\"egg\", \"eggs\")\n        's:p'\n        >>> compare('egg', 'egg')\n        'eq'\n\n        Words should not be empty.\n\n        >>> compare('egg', '')\n        Traceback (most recent call last):\n        ...\n        typeguard.TypeCheckError:...is not an instance of inflect.Word\n        \"\"\"\n        norms = self.plural_noun, self.plural_verb, self.plural_adj\n        results = (self._plequal(word1, word2, norm) for norm in norms)\n        return next(filter(None, results), False)\n\n    @typechecked\n    def compare_nouns(self, word1: Word, word2: Word) -> Union[str, bool]:\n        \"\"\"\n        compare word1 and word2 for equality regardless of plurality\n        word1 and word2 are to be treated as nouns\n\n        return values:\n        eq - the strings are equal\n        p:s - word1 is the plural of word2\n        s:p - word2 is the plural of word1\n        p:p - word1 and word2 are two different plural forms of the one word\n        False - otherwise\n\n        \"\"\"\n        return self._plequal(word1, word2, self.plural_noun)\n\n    @typechecked\n    def compare_verbs(self, word1: Word, word2: Word) -> Union[str, bool]:\n        \"\"\"\n        compare word1 and word2 for equality regardless of plurality\n        word1 and word2 are to be treated as verbs\n\n        return values:\n        eq - the strings are equal\n        p:s - word1 is the plural of word2\n        s:p - word2 is the plural of word1\n        p:p - word1 and word2 are two different plural forms of the one word\n        False - otherwise\n\n        \"\"\"\n        return self._plequal(word1, word2, self.plural_verb)\n\n    @typechecked\n    def compare_adjs(self, word1: Word, word2: Word) -> Union[str, bool]:\n        \"\"\"\n        compare word1 and word2 for equality regardless of plurality\n        word1 and word2 are to be treated as adjectives\n\n        return values:\n        eq - the strings are equal\n        p:s - word1 is the plural of word2\n        s:p - word2 is the plural of word1\n        p:p - word1 and word2 are two different plural forms of the one word\n        False - otherwise\n\n        \"\"\"\n        return self._plequal(word1, word2, self.plural_adj)\n\n    @typechecked\n    def singular_noun(\n        self,\n        text: Word,\n        count: Optional[Union[int, str, Any]] = None,\n        gender: Optional[str] = None,\n    ) -> Union[str, Literal[False]]:\n        \"\"\"\n        Return the singular of text, where text is a plural noun.\n\n        If count supplied, then return the singular if count is one of:\n            1, a, an, one, each, every, this, that or if count is None\n\n        otherwise return text unchanged.\n\n        Whitespace at the start and end is preserved.\n\n        >>> p = engine()\n        >>> p.singular_noun('horses')\n        'horse'\n        >>> p.singular_noun('knights')\n        'knight'\n\n        Returns False when a singular noun is passed.\n\n        >>> p.singular_noun('horse')\n        False\n        >>> p.singular_noun('knight')\n        False\n        >>> p.singular_noun('soldier')\n        False\n\n        \"\"\"\n        pre, word, post = self.partition_word(text)\n        if not word:\n            return text\n        sing = self._sinoun(word, count=count, gender=gender)\n        if sing is not False:\n            plural = self.postprocess(word, sing)\n            return f\"{pre}{plural}{post}\"\n        return False\n\n    def _plequal(self, word1: str, word2: str, pl) -> Union[str, bool]:\n        classval = self.classical_dict.copy()\n        for dictionary in (all_classical, no_classical):\n            self.classical_dict = dictionary.copy()\n            if word1 == word2:\n                return \"eq\"\n            if word1 == pl(word2):\n                return \"p:s\"\n            if pl(word1) == word2:\n                return \"s:p\"\n        self.classical_dict = classval.copy()\n\n        if pl == self.plural or pl == self.plural_noun:\n            if self._pl_check_plurals_N(word1, word2):\n                return \"p:p\"\n            if self._pl_check_plurals_N(word2, word1):\n                return \"p:p\"\n        if pl == self.plural or pl == self.plural_adj:\n            if self._pl_check_plurals_adj(word1, word2):\n                return \"p:p\"\n        return False\n\n    def _pl_reg_plurals(self, pair: str, stems: str, end1: str, end2: str) -> bool:\n        pattern = rf\"({stems})({end1}\\|\\1{end2}|{end2}\\|\\1{end1})\"\n        return bool(re.search(pattern, pair))\n\n    def _pl_check_plurals_N(self, word1: str, word2: str) -> bool:\n        stem_endings = (\n            (pl_sb_C_a_ata, \"as\", \"ata\"),\n            (pl_sb_C_is_ides, \"is\", \"ides\"),\n            (pl_sb_C_a_ae, \"s\", \"e\"),\n            (pl_sb_C_en_ina, \"ens\", \"ina\"),\n            (pl_sb_C_um_a, \"ums\", \"a\"),\n            (pl_sb_C_us_i, \"uses\", \"i\"),\n            (pl_sb_C_on_a, \"ons\", \"a\"),\n            (pl_sb_C_o_i_stems, \"os\", \"i\"),\n            (pl_sb_C_ex_ices, \"exes\", \"ices\"),\n            (pl_sb_C_ix_ices, \"ixes\", \"ices\"),\n            (pl_sb_C_i, \"s\", \"i\"),\n            (pl_sb_C_im, \"s\", \"im\"),\n            (\".*eau\", \"s\", \"x\"),\n            (\".*ieu\", \"s\", \"x\"),\n            (\".*tri\", \"xes\", \"ces\"),\n            (\".{2,}[yia]n\", \"xes\", \"ges\"),\n        )\n\n        words = map(Words, (word1, word2))\n        pair = \"|\".join(word.last for word in words)\n\n        return (\n            pair in pl_sb_irregular_s.values()\n            or pair in pl_sb_irregular.values()\n            or pair in pl_sb_irregular_caps.values()\n            or any(\n                self._pl_reg_plurals(pair, stems, end1, end2)\n                for stems, end1, end2 in stem_endings\n            )\n        )\n\n    def _pl_check_plurals_adj(self, word1: str, word2: str) -> bool:\n        word1a = word1[: word1.rfind(\"'\")] if word1.endswith((\"'s\", \"'\")) else \"\"\n        word2a = word2[: word2.rfind(\"'\")] if word2.endswith((\"'s\", \"'\")) else \"\"\n\n        return (\n            bool(word1a)\n            and bool(word2a)\n            and (\n                self._pl_check_plurals_N(word1a, word2a)\n                or self._pl_check_plurals_N(word2a, word1a)\n            )\n        )\n\n    def get_count(self, count: Optional[Union[str, int]] = None) -> Union[str, int]:\n        if count is None and self.persistent_count is not None:\n            count = self.persistent_count\n\n        if count is not None:\n            count = (\n                1\n                if (\n                    (str(count) in pl_count_one)\n                    or (\n                        self.classical_dict[\"zero\"]\n                        and str(count).lower() in pl_count_zero\n                    )\n                )\n                else 2\n            )\n        else:\n            count = \"\"\n        return count\n\n    # @profile\n    def _plnoun(  # noqa: C901\n        self, word: str, count: Optional[Union[str, int]] = None\n    ) -> str:\n        count = self.get_count(count)\n\n        # DEFAULT TO PLURAL\n\n        if count == 1:\n            return word\n\n        # HANDLE USER-DEFINED NOUNS\n\n        value = self.ud_match(word, self.pl_sb_user_defined)\n        if value is not None:\n            return value\n\n        # HANDLE EMPTY WORD, SINGULAR COUNT AND UNINFLECTED PLURALS\n\n        if word == \"\":\n            return word\n\n        word = Words(word)\n\n        if word.last.lower() in pl_sb_uninflected_complete:\n            if len(word.split_) >= 3:\n                return self._handle_long_compounds(word, count=2) or word\n            return word\n\n        if word in pl_sb_uninflected_caps:\n            return word\n\n        for k, v in pl_sb_uninflected_bysize.items():\n            if word.lowered[-k:] in v:\n                return word\n\n        if self.classical_dict[\"herd\"] and word.last.lower() in pl_sb_uninflected_herd:\n            return word\n\n        # HANDLE COMPOUNDS (\"Governor General\", \"mother-in-law\", \"aide-de-camp\", ETC.)\n\n        mo = PL_SB_POSTFIX_ADJ_STEMS_RE.search(word)\n        if mo and mo.group(2) != \"\":\n            return f\"{self._plnoun(mo.group(1), 2)}{mo.group(2)}\"\n\n        if \" a \" in word.lowered or \"-a-\" in word.lowered:\n            mo = PL_SB_PREP_DUAL_COMPOUND_RE.search(word)\n            if mo and mo.group(2) != \"\" and mo.group(3) != \"\":\n                return (\n                    f\"{self._plnoun(mo.group(1), 2)}\"\n                    f\"{mo.group(2)}\"\n                    f\"{self._plnoun(mo.group(3))}\"\n                )\n\n        if len(word.split_) >= 3:\n            handled_words = self._handle_long_compounds(word, count=2)\n            if handled_words is not None:\n                return handled_words\n\n        # only pluralize denominators in units\n        mo = DENOMINATOR.search(word.lowered)\n        if mo:\n            index = len(mo.group(\"denominator\"))\n            return f\"{self._plnoun(word[:index])}{word[index:]}\"\n\n        # handle units given in degrees (only accept if\n        # there is no more than one word following)\n        # degree Celsius => degrees Celsius but degree\n        # fahrenheit hour => degree fahrenheit hours\n        if len(word.split_) >= 2 and word.split_[-2] == \"degree\":\n            return \" \".join([self._plnoun(word.first)] + word.split_[1:])\n\n        with contextlib.suppress(ValueError):\n            return self._handle_prepositional_phrase(\n                word.lowered,\n                functools.partial(self._plnoun, count=2),\n                '-',\n            )\n\n        # HANDLE PRONOUNS\n\n        for k, v in pl_pron_acc_keys_bysize.items():\n            if word.lowered[-k:] in v:  # ends with accusative pronoun\n                for pk, pv in pl_prep_bysize.items():\n                    if word.lowered[:pk] in pv:  # starts with a prep\n                        if word.lowered.split() == [\n                            word.lowered[:pk],\n                            word.lowered[-k:],\n                        ]:\n                            # only whitespace in between\n                            return word.lowered[:-k] + pl_pron_acc[word.lowered[-k:]]\n\n        try:\n            return pl_pron_nom[word.lowered]\n        except KeyError:\n            pass\n\n        try:\n            return pl_pron_acc[word.lowered]\n        except KeyError:\n            pass\n\n        # HANDLE ISOLATED IRREGULAR PLURALS\n\n        if word.last in pl_sb_irregular_caps:\n            llen = len(word.last)\n            return f\"{word[:-llen]}{pl_sb_irregular_caps[word.last]}\"\n\n        lowered_last = word.last.lower()\n        if lowered_last in pl_sb_irregular:\n            llen = len(lowered_last)\n            return f\"{word[:-llen]}{pl_sb_irregular[lowered_last]}\"\n\n        dash_split = word.lowered.split('-')\n        if (\" \".join(dash_split[-2:])).lower() in pl_sb_irregular_compound:\n            llen = len(\n                \" \".join(dash_split[-2:])\n            )  # TODO: what if 2 spaces between these words?\n            return (\n                f\"{word[:-llen]}\"\n                f\"{pl_sb_irregular_compound[(' '.join(dash_split[-2:])).lower()]}\"\n            )\n\n        if word.lowered[-3:] == \"quy\":\n            return f\"{word[:-1]}ies\"\n\n        if word.lowered[-6:] == \"person\":\n            if self.classical_dict[\"persons\"]:\n                return f\"{word}s\"\n            else:\n                return f\"{word[:-4]}ople\"\n\n        # HANDLE FAMILIES OF IRREGULAR PLURALS\n\n        if word.lowered[-3:] == \"man\":\n            for k, v in pl_sb_U_man_mans_bysize.items():\n                if word.lowered[-k:] in v:\n                    return f\"{word}s\"\n            for k, v in pl_sb_U_man_mans_caps_bysize.items():\n                if word[-k:] in v:\n                    return f\"{word}s\"\n            return f\"{word[:-3]}men\"\n        if word.lowered[-5:] == \"mouse\":\n            return f\"{word[:-5]}mice\"\n        if word.lowered[-5:] == \"louse\":\n            v = pl_sb_U_louse_lice_bysize.get(len(word))\n            if v and word.lowered in v:\n                return f\"{word[:-5]}lice\"\n            return f\"{word}s\"\n        if word.lowered[-5:] == \"goose\":\n            return f\"{word[:-5]}geese\"\n        if word.lowered[-5:] == \"tooth\":\n            return f\"{word[:-5]}teeth\"\n        if word.lowered[-4:] == \"foot\":\n            return f\"{word[:-4]}feet\"\n        if word.lowered[-4:] == \"taco\":\n            return f\"{word[:-5]}tacos\"\n\n        if word.lowered == \"die\":\n            return \"dice\"\n\n        # HANDLE UNASSIMILATED IMPORTS\n\n        if word.lowered[-4:] == \"ceps\":\n            return word\n        if word.lowered[-4:] == \"zoon\":\n            return f\"{word[:-2]}a\"\n        if word.lowered[-3:] in (\"cis\", \"sis\", \"xis\"):\n            return f\"{word[:-2]}es\"\n\n        for lastlet, d, numend, post in (\n            (\"h\", pl_sb_U_ch_chs_bysize, None, \"s\"),\n            (\"x\", pl_sb_U_ex_ices_bysize, -2, \"ices\"),\n            (\"x\", pl_sb_U_ix_ices_bysize, -2, \"ices\"),\n            (\"m\", pl_sb_U_um_a_bysize, -2, \"a\"),\n            (\"s\", pl_sb_U_us_i_bysize, -2, \"i\"),\n            (\"n\", pl_sb_U_on_a_bysize, -2, \"a\"),\n            (\"a\", pl_sb_U_a_ae_bysize, None, \"e\"),\n        ):\n            if word.lowered[-1] == lastlet:  # this test to add speed\n                for k, v in d.items():\n                    if word.lowered[-k:] in v:\n                        return word[:numend] + post\n\n        # HANDLE INCOMPLETELY ASSIMILATED IMPORTS\n\n        if self.classical_dict[\"ancient\"]:\n            if word.lowered[-4:] == \"trix\":\n                return f\"{word[:-1]}ces\"\n            if word.lowered[-3:] in (\"eau\", \"ieu\"):\n                return f\"{word}x\"\n            if word.lowered[-3:] in (\"ynx\", \"inx\", \"anx\") and len(word) > 4:\n                return f\"{word[:-1]}ges\"\n\n            for lastlet, d, numend, post in (\n                (\"n\", pl_sb_C_en_ina_bysize, -2, \"ina\"),\n                (\"x\", pl_sb_C_ex_ices_bysize, -2, \"ices\"),\n                (\"x\", pl_sb_C_ix_ices_bysize, -2, \"ices\"),\n                (\"m\", pl_sb_C_um_a_bysize, -2, \"a\"),\n                (\"s\", pl_sb_C_us_i_bysize, -2, \"i\"),\n                (\"s\", pl_sb_C_us_us_bysize, None, \"\"),\n                (\"a\", pl_sb_C_a_ae_bysize, None, \"e\"),\n                (\"a\", pl_sb_C_a_ata_bysize, None, \"ta\"),\n                (\"s\", pl_sb_C_is_ides_bysize, -1, \"des\"),\n                (\"o\", pl_sb_C_o_i_bysize, -1, \"i\"),\n                (\"n\", pl_sb_C_on_a_bysize, -2, \"a\"),\n            ):\n                if word.lowered[-1] == lastlet:  # this test to add speed\n                    for k, v in d.items():\n                        if word.lowered[-k:] in v:\n                            return word[:numend] + post\n\n            for d, numend, post in (\n                (pl_sb_C_i_bysize, None, \"i\"),\n                (pl_sb_C_im_bysize, None, \"im\"),\n            ):\n                for k, v in d.items():\n                    if word.lowered[-k:] in v:\n                        return word[:numend] + post\n\n        # HANDLE SINGULAR NOUNS ENDING IN ...s OR OTHER SILIBANTS\n\n        if lowered_last in pl_sb_singular_s_complete:\n            return f\"{word}es\"\n\n        for k, v in pl_sb_singular_s_bysize.items():\n            if word.lowered[-k:] in v:\n                return f\"{word}es\"\n\n        if word.lowered[-2:] == \"es\" and word[0] == word[0].upper():\n            return f\"{word}es\"\n\n        if word.lowered[-1] == \"z\":\n            for k, v in pl_sb_z_zes_bysize.items():\n                if word.lowered[-k:] in v:\n                    return f\"{word}es\"\n\n            if word.lowered[-2:-1] != \"z\":\n                return f\"{word}zes\"\n\n        if word.lowered[-2:] == \"ze\":\n            for k, v in pl_sb_ze_zes_bysize.items():\n                if word.lowered[-k:] in v:\n                    return f\"{word}s\"\n\n        if word.lowered[-2:] in (\"ch\", \"sh\", \"zz\", \"ss\") or word.lowered[-1] == \"x\":\n            return f\"{word}es\"\n\n        # HANDLE ...f -> ...ves\n\n        if word.lowered[-3:] in (\"elf\", \"alf\", \"olf\"):\n            return f\"{word[:-1]}ves\"\n        if word.lowered[-3:] == \"eaf\" and word.lowered[-4:-3] != \"d\":\n            return f\"{word[:-1]}ves\"\n        if word.lowered[-4:] in (\"nife\", \"life\", \"wife\"):\n            return f\"{word[:-2]}ves\"\n        if word.lowered[-3:] == \"arf\":\n            return f\"{word[:-1]}ves\"\n\n        # HANDLE ...y\n\n        if word.lowered[-1] == \"y\":\n            if word.lowered[-2:-1] in \"aeiou\" or len(word) == 1:\n                return f\"{word}s\"\n\n            if self.classical_dict[\"names\"]:\n                if word.lowered[-1] == \"y\" and word[0] == word[0].upper():\n                    return f\"{word}s\"\n\n            return f\"{word[:-1]}ies\"\n\n        # HANDLE ...o\n\n        if lowered_last in pl_sb_U_o_os_complete:\n            return f\"{word}s\"\n\n        for k, v in pl_sb_U_o_os_bysize.items():\n            if word.lowered[-k:] in v:\n                return f\"{word}s\"\n\n        if word.lowered[-2:] in (\"ao\", \"eo\", \"io\", \"oo\", \"uo\"):\n            return f\"{word}s\"\n\n        if word.lowered[-1] == \"o\":\n            return f\"{word}es\"\n\n        # OTHERWISE JUST ADD ...s\n\n        return f\"{word}s\"\n\n    @classmethod\n    def _handle_prepositional_phrase(cls, phrase, transform, sep):\n        \"\"\"\n        Given a word or phrase possibly separated by sep, parse out\n        the prepositional phrase and apply the transform to the word\n        preceding the prepositional phrase.\n\n        Raise ValueError if the pivot is not found or if at least two\n        separators are not found.\n\n        >>> engine._handle_prepositional_phrase(\"man-of-war\", str.upper, '-')\n        'MAN-of-war'\n        >>> engine._handle_prepositional_phrase(\"man of war\", str.upper, ' ')\n        'MAN of war'\n        \"\"\"\n        parts = phrase.split(sep)\n        if len(parts) < 3:\n            raise ValueError(\"Cannot handle words with fewer than two separators\")\n\n        pivot = cls._find_pivot(parts, pl_prep_list_da)\n\n        transformed = transform(parts[pivot - 1]) or parts[pivot - 1]\n        return \" \".join(\n            parts[: pivot - 1] + [sep.join([transformed, parts[pivot], ''])]\n        ) + \" \".join(parts[(pivot + 1) :])\n\n    def _handle_long_compounds(self, word: Words, count: int) -> Union[str, None]:\n        \"\"\"\n        Handles the plural and singular for compound `Words` that\n        have three or more words, based on the given count.\n\n        >>> engine()._handle_long_compounds(Words(\"pair of scissors\"), 2)\n        'pairs of scissors'\n        >>> engine()._handle_long_compounds(Words(\"men beyond hills\"), 1)\n        'man beyond hills'\n        \"\"\"\n        inflection = self._sinoun if count == 1 else self._plnoun\n        solutions = (\n            \" \".join(\n                itertools.chain(\n                    leader,\n                    [inflection(cand, count), prep],  # type: ignore[operator]\n                    trailer,\n                )\n            )\n            for leader, (cand, prep), trailer in windowed_complete(word.split_, 2)\n            if prep in pl_prep_list_da\n        )\n        return next(solutions, None)\n\n    @staticmethod\n    def _find_pivot(words, candidates):\n        pivots = (\n            index for index in range(1, len(words) - 1) if words[index] in candidates\n        )\n        try:\n            return next(pivots)\n        except StopIteration:\n            raise ValueError(\"No pivot found\") from None\n\n    def _pl_special_verb(  # noqa: C901\n        self, word: str, count: Optional[Union[str, int]] = None\n    ) -> Union[str, bool]:\n        if self.classical_dict[\"zero\"] and str(count).lower() in pl_count_zero:\n            return False\n        count = self.get_count(count)\n\n        if count == 1:\n            return word\n\n        # HANDLE USER-DEFINED VERBS\n\n        value = self.ud_match(word, self.pl_v_user_defined)\n        if value is not None:\n            return value\n\n        # HANDLE IRREGULAR PRESENT TENSE (SIMPLE AND COMPOUND)\n\n        try:\n            words = Words(word)\n        except IndexError:\n            return False  # word is ''\n\n        if words.first in plverb_irregular_pres:\n            return f\"{plverb_irregular_pres[words.first]}{words[len(words.first) :]}\"\n\n        # HANDLE IRREGULAR FUTURE, PRETERITE AND PERFECT TENSES\n\n        if words.first in plverb_irregular_non_pres:\n            return word\n\n        # HANDLE PRESENT NEGATIONS (SIMPLE AND COMPOUND)\n\n        if words.first.endswith(\"n't\") and words.first[:-3] in plverb_irregular_pres:\n            return (\n                f\"{plverb_irregular_pres[words.first[:-3]]}n't\"\n                f\"{words[len(words.first) :]}\"\n            )\n\n        if words.first.endswith(\"n't\"):\n            return word\n\n        # HANDLE SPECIAL CASES\n\n        mo = PLVERB_SPECIAL_S_RE.search(word)\n        if mo:\n            return False\n        if WHITESPACE.search(word):\n            return False\n\n        if words.lowered == \"quizzes\":\n            return \"quiz\"\n\n        # HANDLE STANDARD 3RD PERSON (CHOP THE ...(e)s OFF SINGLE WORDS)\n\n        if (\n            words.lowered[-4:] in (\"ches\", \"shes\", \"zzes\", \"sses\")\n            or words.lowered[-3:] == \"xes\"\n        ):\n            return words[:-2]\n\n        if words.lowered[-3:] == \"ies\" and len(words) > 3:\n            return words.lowered[:-3] + \"y\"\n\n        if (\n            words.last.lower() in pl_v_oes_oe\n            or words.lowered[-4:] in pl_v_oes_oe_endings_size4\n            or words.lowered[-5:] in pl_v_oes_oe_endings_size5\n        ):\n            return words[:-1]\n\n        if words.lowered.endswith(\"oes\") and len(words) > 3:\n            return words.lowered[:-2]\n\n        mo = ENDS_WITH_S.search(words)\n        if mo:\n            return mo.group(1)\n\n        # OTHERWISE, A REGULAR VERB (HANDLE ELSEWHERE)\n\n        return False\n\n    def _pl_general_verb(\n        self, word: str, count: Optional[Union[str, int]] = None\n    ) -> str:\n        count = self.get_count(count)\n\n        if count == 1:\n            return word\n\n        # HANDLE AMBIGUOUS PRESENT TENSES  (SIMPLE AND COMPOUND)\n\n        mo = plverb_ambiguous_pres_keys.search(word)\n        if mo:\n            return f\"{plverb_ambiguous_pres[mo.group(1).lower()]}{mo.group(2)}\"\n\n        # HANDLE AMBIGUOUS PRETERITE AND PERFECT TENSES\n\n        mo = plverb_ambiguous_non_pres.search(word)\n        if mo:\n            return word\n\n        # OTHERWISE, 1st OR 2ND PERSON IS UNINFLECTED\n\n        return word\n\n    def _pl_special_adjective(\n        self, word: str, count: Optional[Union[str, int]] = None\n    ) -> Union[str, bool]:\n        count = self.get_count(count)\n\n        if count == 1:\n            return word\n\n        # HANDLE USER-DEFINED ADJECTIVES\n\n        value = self.ud_match(word, self.pl_adj_user_defined)\n        if value is not None:\n            return value\n\n        # HANDLE KNOWN CASES\n\n        mo = pl_adj_special_keys.search(word)\n        if mo:\n            return pl_adj_special[mo.group(1).lower()]\n\n        # HANDLE POSSESSIVES\n\n        mo = pl_adj_poss_keys.search(word)\n        if mo:\n            return pl_adj_poss[mo.group(1).lower()]\n\n        mo = ENDS_WITH_APOSTROPHE_S.search(word)\n        if mo:\n            pl = self.plural_noun(mo.group(1))\n            trailing_s = \"\" if pl[-1] == \"s\" else \"s\"\n            return f\"{pl}'{trailing_s}\"\n\n        # OTHERWISE, NO IDEA\n\n        return False\n\n    # @profile\n    def _sinoun(  # noqa: C901\n        self,\n        word: str,\n        count: Optional[Union[str, int]] = None,\n        gender: Optional[str] = None,\n    ) -> Union[str, bool]:\n        count = self.get_count(count)\n\n        # DEFAULT TO PLURAL\n\n        if count == 2:\n            return word\n\n        # SET THE GENDER\n\n        try:\n            if gender is None:\n                gender = self.thegender\n            elif gender not in singular_pronoun_genders:\n                raise BadGenderError\n        except (TypeError, IndexError) as err:\n            raise BadGenderError from err\n\n        # HANDLE USER-DEFINED NOUNS\n\n        value = self.ud_match(word, self.si_sb_user_defined)\n        if value is not None:\n            return value\n\n        # HANDLE EMPTY WORD, SINGULAR COUNT AND UNINFLECTED PLURALS\n\n        if word == \"\":\n            return word\n\n        if word in si_sb_ois_oi_case:\n            return word[:-1]\n\n        words = Words(word)\n\n        if words.last.lower() in pl_sb_uninflected_complete:\n            if len(words.split_) >= 3:\n                return self._handle_long_compounds(words, count=1) or word\n            return word\n\n        if word in pl_sb_uninflected_caps:\n            return word\n\n        for k, v in pl_sb_uninflected_bysize.items():\n            if words.lowered[-k:] in v:\n                return word\n\n        if self.classical_dict[\"herd\"] and words.last.lower() in pl_sb_uninflected_herd:\n            return word\n\n        if words.last.lower() in pl_sb_C_us_us:\n            return word if self.classical_dict[\"ancient\"] else False\n\n        # HANDLE COMPOUNDS (\"Governor General\", \"mother-in-law\", \"aide-de-camp\", ETC.)\n\n        mo = PL_SB_POSTFIX_ADJ_STEMS_RE.search(word)\n        if mo and mo.group(2) != \"\":\n            return f\"{self._sinoun(mo.group(1), 1, gender=gender)}{mo.group(2)}\"\n\n        with contextlib.suppress(ValueError):\n            return self._handle_prepositional_phrase(\n                words.lowered,\n                functools.partial(self._sinoun, count=1, gender=gender),\n                ' ',\n            )\n\n        with contextlib.suppress(ValueError):\n            return self._handle_prepositional_phrase(\n                words.lowered,\n                functools.partial(self._sinoun, count=1, gender=gender),\n                '-',\n            )\n\n        # HANDLE PRONOUNS\n\n        for k, v in si_pron_acc_keys_bysize.items():\n            if words.lowered[-k:] in v:  # ends with accusative pronoun\n                for pk, pv in pl_prep_bysize.items():\n                    if words.lowered[:pk] in pv:  # starts with a prep\n                        if words.lowered.split() == [\n                            words.lowered[:pk],\n                            words.lowered[-k:],\n                        ]:\n                            # only whitespace in between\n                            return words.lowered[:-k] + get_si_pron(\n                                \"acc\", words.lowered[-k:], gender\n                            )\n\n        try:\n            return get_si_pron(\"nom\", words.lowered, gender)\n        except KeyError:\n            pass\n\n        try:\n            return get_si_pron(\"acc\", words.lowered, gender)\n        except KeyError:\n            pass\n\n        # HANDLE ISOLATED IRREGULAR PLURALS\n\n        if words.last in si_sb_irregular_caps:\n            llen = len(words.last)\n            return f\"{word[:-llen]}{si_sb_irregular_caps[words.last]}\"\n\n        if words.last.lower() in si_sb_irregular:\n            llen = len(words.last.lower())\n            return f\"{word[:-llen]}{si_sb_irregular[words.last.lower()]}\"\n\n        dash_split = words.lowered.split(\"-\")\n        if (\" \".join(dash_split[-2:])).lower() in si_sb_irregular_compound:\n            llen = len(\n                \" \".join(dash_split[-2:])\n            )  # TODO: what if 2 spaces between these words?\n            return \"{}{}\".format(\n                word[:-llen],\n                si_sb_irregular_compound[(\" \".join(dash_split[-2:])).lower()],\n            )\n\n        if words.lowered[-5:] == \"quies\":\n            return word[:-3] + \"y\"\n\n        if words.lowered[-7:] == \"persons\":\n            return word[:-1]\n        if words.lowered[-6:] == \"people\":\n            return word[:-4] + \"rson\"\n\n        # HANDLE FAMILIES OF IRREGULAR PLURALS\n\n        if words.lowered[-4:] == \"mans\":\n            for k, v in si_sb_U_man_mans_bysize.items():\n                if words.lowered[-k:] in v:\n                    return word[:-1]\n            for k, v in si_sb_U_man_mans_caps_bysize.items():\n                if word[-k:] in v:\n                    return word[:-1]\n        if words.lowered[-3:] == \"men\":\n            return word[:-3] + \"man\"\n        if words.lowered[-4:] == \"mice\":\n            return word[:-4] + \"mouse\"\n        if words.lowered[-4:] == \"lice\":\n            v = si_sb_U_louse_lice_bysize.get(len(word))\n            if v and words.lowered in v:\n                return word[:-4] + \"louse\"\n        if words.lowered[-5:] == \"geese\":\n            return word[:-5] + \"goose\"\n        if words.lowered[-5:] == \"teeth\":\n            return word[:-5] + \"tooth\"\n        if words.lowered[-4:] == \"feet\":\n            return word[:-4] + \"foot\"\n\n        if words.lowered == \"dice\":\n            return \"die\"\n\n        # HANDLE UNASSIMILATED IMPORTS\n\n        if words.lowered[-4:] == \"ceps\":\n            return word\n        if words.lowered[-3:] == \"zoa\":\n            return word[:-1] + \"on\"\n\n        for lastlet, d, unass_numend, post in (\n            (\"s\", si_sb_U_ch_chs_bysize, -1, \"\"),\n            (\"s\", si_sb_U_ex_ices_bysize, -4, \"ex\"),\n            (\"s\", si_sb_U_ix_ices_bysize, -4, \"ix\"),\n            (\"a\", si_sb_U_um_a_bysize, -1, \"um\"),\n            (\"i\", si_sb_U_us_i_bysize, -1, \"us\"),\n            (\"a\", si_sb_U_on_a_bysize, -1, \"on\"),\n            (\"e\", si_sb_U_a_ae_bysize, -1, \"\"),\n        ):\n            if words.lowered[-1] == lastlet:  # this test to add speed\n                for k, v in d.items():\n                    if words.lowered[-k:] in v:\n                        return word[:unass_numend] + post\n\n        # HANDLE INCOMPLETELY ASSIMILATED IMPORTS\n\n        if self.classical_dict[\"ancient\"]:\n            if words.lowered[-6:] == \"trices\":\n                return word[:-3] + \"x\"\n            if words.lowered[-4:] in (\"eaux\", \"ieux\"):\n                return word[:-1]\n            if words.lowered[-5:] in (\"ynges\", \"inges\", \"anges\") and len(word) > 6:\n                return word[:-3] + \"x\"\n\n            for lastlet, d, class_numend, post in (\n                (\"a\", si_sb_C_en_ina_bysize, -3, \"en\"),\n                (\"s\", si_sb_C_ex_ices_bysize, -4, \"ex\"),\n                (\"s\", si_sb_C_ix_ices_bysize, -4, \"ix\"),\n                (\"a\", si_sb_C_um_a_bysize, -1, \"um\"),\n                (\"i\", si_sb_C_us_i_bysize, -1, \"us\"),\n                (\"s\", pl_sb_C_us_us_bysize, None, \"\"),\n                (\"e\", si_sb_C_a_ae_bysize, -1, \"\"),\n                (\"a\", si_sb_C_a_ata_bysize, -2, \"\"),\n                (\"s\", si_sb_C_is_ides_bysize, -3, \"s\"),\n                (\"i\", si_sb_C_o_i_bysize, -1, \"o\"),\n                (\"a\", si_sb_C_on_a_bysize, -1, \"on\"),\n                (\"m\", si_sb_C_im_bysize, -2, \"\"),\n                (\"i\", si_sb_C_i_bysize, -1, \"\"),\n            ):\n                if words.lowered[-1] == lastlet:  # this test to add speed\n                    for k, v in d.items():\n                        if words.lowered[-k:] in v:\n                            return word[:class_numend] + post\n\n        # HANDLE PLURLS ENDING IN uses -> use\n\n        if (\n            words.lowered[-6:] == \"houses\"\n            or word in si_sb_uses_use_case\n            or words.last.lower() in si_sb_uses_use\n        ):\n            return word[:-1]\n\n        # HANDLE PLURLS ENDING IN ies -> ie\n\n        if word in si_sb_ies_ie_case or words.last.lower() in si_sb_ies_ie:\n            return word[:-1]\n\n        # HANDLE PLURLS ENDING IN oes -> oe\n\n        if (\n            words.lowered[-5:] == \"shoes\"\n            or word in si_sb_oes_oe_case\n            or words.last.lower() in si_sb_oes_oe\n        ):\n            return word[:-1]\n\n        # HANDLE SINGULAR NOUNS ENDING IN ...s OR OTHER SILIBANTS\n\n        if word in si_sb_sses_sse_case or words.last.lower() in si_sb_sses_sse:\n            return word[:-1]\n\n        if words.last.lower() in si_sb_singular_s_complete:\n            return word[:-2]\n\n        for k, v in si_sb_singular_s_bysize.items():\n            if words.lowered[-k:] in v:\n                return word[:-2]\n\n        if words.lowered[-4:] == \"eses\" and word[0] == word[0].upper():\n            return word[:-2]\n\n        if words.last.lower() in si_sb_z_zes:\n            return word[:-2]\n\n        if words.last.lower() in si_sb_zzes_zz:\n            return word[:-2]\n\n        if words.lowered[-4:] == \"zzes\":\n            return word[:-3]\n\n        if word in si_sb_ches_che_case or words.last.lower() in si_sb_ches_che:\n            return word[:-1]\n\n        if words.lowered[-4:] in (\"ches\", \"shes\"):\n            return word[:-2]\n\n        if words.last.lower() in si_sb_xes_xe:\n            return word[:-1]\n\n        if words.lowered[-3:] == \"xes\":\n            return word[:-2]\n\n        # HANDLE ...f -> ...ves\n\n        if word in si_sb_ves_ve_case or words.last.lower() in si_sb_ves_ve:\n            return word[:-1]\n\n        if words.lowered[-3:] == \"ves\":\n            if words.lowered[-5:-3] in (\"el\", \"al\", \"ol\"):\n                return word[:-3] + \"f\"\n            if words.lowered[-5:-3] == \"ea\" and word[-6:-5] != \"d\":\n                return word[:-3] + \"f\"\n            if words.lowered[-5:-3] in (\"ni\", \"li\", \"wi\"):\n                return word[:-3] + \"fe\"\n            if words.lowered[-5:-3] == \"ar\":\n                return word[:-3] + \"f\"\n\n        # HANDLE ...y\n\n        if words.lowered[-2:] == \"ys\":\n            if len(words.lowered) > 2 and words.lowered[-3] in \"aeiou\":\n                return word[:-1]\n\n            if self.classical_dict[\"names\"]:\n                if words.lowered[-2:] == \"ys\" and word[0] == word[0].upper():\n                    return word[:-1]\n\n        if words.lowered[-3:] == \"ies\":\n            return word[:-3] + \"y\"\n\n        # HANDLE ...o\n\n        if words.lowered[-2:] == \"os\":\n            if words.last.lower() in si_sb_U_o_os_complete:\n                return word[:-1]\n\n            for k, v in si_sb_U_o_os_bysize.items():\n                if words.lowered[-k:] in v:\n                    return word[:-1]\n\n            if words.lowered[-3:] in (\"aos\", \"eos\", \"ios\", \"oos\", \"uos\"):\n                return word[:-1]\n\n        if words.lowered[-3:] == \"oes\":\n            return word[:-2]\n\n        # UNASSIMILATED IMPORTS FINAL RULE\n\n        if word in si_sb_es_is:\n            return word[:-2] + \"is\"\n\n        # OTHERWISE JUST REMOVE ...s\n\n        if words.lowered[-1] == \"s\":\n            return word[:-1]\n\n        # COULD NOT FIND SINGULAR\n\n        return False\n\n    # ADJECTIVES\n\n    @typechecked\n    def a(self, text: Word, count: Optional[Union[int, str, Any]] = 1) -> str:\n        \"\"\"\n        Return the appropriate indefinite article followed by text.\n\n        The indefinite article is either 'a' or 'an'.\n\n        If count is not one, then return count followed by text\n        instead of 'a' or 'an'.\n\n        Whitespace at the start and end is preserved.\n\n        \"\"\"\n        mo = INDEFINITE_ARTICLE_TEST.search(text)\n        if mo:\n            word = mo.group(2)\n            if not word:\n                return text\n            pre = mo.group(1)\n            post = mo.group(3)\n            result = self._indef_article(word, count)\n            return f\"{pre}{result}{post}\"\n        return \"\"\n\n    an = a\n\n    _indef_article_cases = (\n        # HANDLE ORDINAL FORMS\n        (A_ordinal_a, \"a\"),\n        (A_ordinal_an, \"an\"),\n        # HANDLE SPECIAL CASES\n        (A_explicit_an, \"an\"),\n        (SPECIAL_AN, \"an\"),\n        (SPECIAL_A, \"a\"),\n        # HANDLE ABBREVIATIONS\n        (A_abbrev, \"an\"),\n        (SPECIAL_ABBREV_AN, \"an\"),\n        (SPECIAL_ABBREV_A, \"a\"),\n        # HANDLE CONSONANTS\n        (CONSONANTS, \"a\"),\n        # HANDLE SPECIAL VOWEL-FORMS\n        (ARTICLE_SPECIAL_EU, \"a\"),\n        (ARTICLE_SPECIAL_ONCE, \"a\"),\n        (ARTICLE_SPECIAL_ONETIME, \"a\"),\n        (ARTICLE_SPECIAL_UNIT, \"a\"),\n        (ARTICLE_SPECIAL_UBA, \"a\"),\n        (ARTICLE_SPECIAL_UKR, \"a\"),\n        (A_explicit_a, \"a\"),\n        # HANDLE SPECIAL CAPITALS\n        (SPECIAL_CAPITALS, \"a\"),\n        # HANDLE VOWELS\n        (VOWELS, \"an\"),\n        # HANDLE y...\n        # (BEFORE CERTAIN CONSONANTS IMPLIES (UNNATURALIZED) \"i..\" SOUND)\n        (A_y_cons, \"an\"),\n    )\n\n    def _indef_article(self, word: str, count: Union[int, str, Any]) -> str:\n        mycount = self.get_count(count)\n\n        if mycount != 1:\n            return f\"{count} {word}\"\n\n        # HANDLE USER-DEFINED VARIANTS\n\n        value = self.ud_match(word, self.A_a_user_defined)\n        if value is not None:\n            return f\"{value} {word}\"\n\n        matches = (\n            f'{article} {word}'\n            for regexen, article in self._indef_article_cases\n            if regexen.search(word)\n        )\n\n        # OTHERWISE, GUESS \"a\"\n        fallback = f'a {word}'\n        return next(matches, fallback)\n\n    # 2. TRANSLATE ZERO-QUANTIFIED $word TO \"no plural($word)\"\n\n    @typechecked\n    def no(self, text: Word, count: Optional[Union[int, str]] = None) -> str:\n        \"\"\"\n        If count is 0, no, zero or nil, return 'no' followed by the plural\n        of text.\n\n        If count is one of:\n            1, a, an, one, each, every, this, that\n            return count followed by text.\n\n        Otherwise return count follow by the plural of text.\n\n        In the return value count is always followed by a space.\n\n        Whitespace at the start and end is preserved.\n\n        \"\"\"\n        if count is None and self.persistent_count is not None:\n            count = self.persistent_count\n\n        if count is None:\n            count = 0\n        mo = PARTITION_WORD.search(text)\n        if mo:\n            pre = mo.group(1)\n            word = mo.group(2)\n            post = mo.group(3)\n        else:\n            pre = \"\"\n            word = \"\"\n            post = \"\"\n\n        if str(count).lower() in pl_count_zero:\n            count = 'no'\n        return f\"{pre}{count} {self.plural(word, count)}{post}\"\n\n    # PARTICIPLES\n\n    @typechecked\n    def present_participle(self, word: Word) -> str:\n        \"\"\"\n        Return the present participle for word.\n\n        word is the 3rd person singular verb.\n\n        \"\"\"\n        plv = self.plural_verb(word, 2)\n        ans = plv\n\n        for regexen, repl in PRESENT_PARTICIPLE_REPLACEMENTS:\n            ans, num = regexen.subn(repl, plv)\n            if num:\n                return f\"{ans}ing\"\n        return f\"{ans}ing\"\n\n    # NUMERICAL INFLECTIONS\n\n    @typechecked\n    def ordinal(self, num: Union[Number, Word]) -> str:\n        \"\"\"\n        Return the ordinal of num.\n\n        >>> ordinal = engine().ordinal\n        >>> ordinal(1)\n        '1st'\n        >>> ordinal('one')\n        'first'\n        \"\"\"\n        if DIGIT.match(str(num)):\n            if isinstance(num, (float, int)) and int(num) == num:\n                n = int(num)\n            else:\n                if \".\" in str(num):\n                    try:\n                        # numbers after decimal,\n                        # so only need last one for ordinal\n                        n = int(str(num)[-1])\n\n                    except ValueError:\n                        # ends with '.', so need to use whole string\n                        n = int(str(num)[:-1])\n                else:\n                    n = int(num)  # type: ignore[arg-type]\n            try:\n                post = nth[n % 100]\n            except KeyError:\n                post = nth[n % 10]\n            return f\"{num}{post}\"\n        else:\n            return self._sub_ord(num)\n\n    def millfn(self, ind: int = 0) -> str:\n        if ind > len(mill) - 1:\n            raise NumOutOfRangeError\n        return mill[ind]\n\n    def unitfn(self, units: int, mindex: int = 0) -> str:\n        return f\"{unit[units]}{self.millfn(mindex)}\"\n\n    def tenfn(self, tens, units, mindex=0) -> str:\n        if tens != 1:\n            tens_part = ten[tens]\n            if tens and units:\n                hyphen = \"-\"\n            else:\n                hyphen = \"\"\n            unit_part = unit[units]\n            mill_part = self.millfn(mindex)\n            return f\"{tens_part}{hyphen}{unit_part}{mill_part}\"\n        return f\"{teen[units]}{mill[mindex]}\"\n\n    def hundfn(self, hundreds: int, tens: int, units: int, mindex: int) -> str:\n        if hundreds:\n            andword = f\" {self._number_args['andword']} \" if tens or units else \"\"\n            # use unit not unitfn as simpler\n            return (\n                f\"{unit[hundreds]} hundred{andword}\"\n                f\"{self.tenfn(tens, units)}{self.millfn(mindex)}, \"\n            )\n        if tens or units:\n            return f\"{self.tenfn(tens, units)}{self.millfn(mindex)}, \"\n        return \"\"\n\n    def group1sub(self, mo: Match) -> str:\n        units = int(mo.group(1))\n        if units == 1:\n            return f\" {self._number_args['one']}, \"\n        elif units:\n            return f\"{unit[units]}, \"\n        else:\n            return f\" {self._number_args['zero']}, \"\n\n    def group1bsub(self, mo: Match) -> str:\n        units = int(mo.group(1))\n        if units:\n            return f\"{unit[units]}, \"\n        else:\n            return f\" {self._number_args['zero']}, \"\n\n    def group2sub(self, mo: Match) -> str:\n        tens = int(mo.group(1))\n        units = int(mo.group(2))\n        if tens:\n            return f\"{self.tenfn(tens, units)}, \"\n        if units:\n            return f\" {self._number_args['zero']} {unit[units]}, \"\n        return f\" {self._number_args['zero']} {self._number_args['zero']}, \"\n\n    def group3sub(self, mo: Match) -> str:\n        hundreds = int(mo.group(1))\n        tens = int(mo.group(2))\n        units = int(mo.group(3))\n        if hundreds == 1:\n            hunword = f\" {self._number_args['one']}\"\n        elif hundreds:\n            hunword = str(unit[hundreds])\n        else:\n            hunword = f\" {self._number_args['zero']}\"\n        if tens:\n            tenword = self.tenfn(tens, units)\n        elif units:\n            tenword = f\" {self._number_args['zero']} {unit[units]}\"\n        else:\n            tenword = f\" {self._number_args['zero']} {self._number_args['zero']}\"\n        return f\"{hunword} {tenword}, \"\n\n    def hundsub(self, mo: Match) -> str:\n        ret = self.hundfn(\n            int(mo.group(1)), int(mo.group(2)), int(mo.group(3)), self.mill_count\n        )\n        self.mill_count += 1\n        return ret\n\n    def tensub(self, mo: Match) -> str:\n        return f\"{self.tenfn(int(mo.group(1)), int(mo.group(2)), self.mill_count)}, \"\n\n    def unitsub(self, mo: Match) -> str:\n        return f\"{self.unitfn(int(mo.group(1)), self.mill_count)}, \"\n\n    def enword(self, num: str, group: int) -> str:\n        # import pdb\n        # pdb.set_trace()\n\n        if group == 1:\n            num = DIGIT_GROUP.sub(self.group1sub, num)\n        elif group == 2:\n            num = TWO_DIGITS.sub(self.group2sub, num)\n            num = DIGIT_GROUP.sub(self.group1bsub, num, 1)\n        elif group == 3:\n            num = THREE_DIGITS.sub(self.group3sub, num)\n            num = TWO_DIGITS.sub(self.group2sub, num, 1)\n            num = DIGIT_GROUP.sub(self.group1sub, num, 1)\n        elif int(num) == 0:\n            num = self._number_args[\"zero\"]\n        elif int(num) == 1:\n            num = self._number_args[\"one\"]\n        else:\n            num = num.lstrip().lstrip(\"0\")\n            self.mill_count = 0\n            # surely there's a better way to do the next bit\n            mo = THREE_DIGITS_WORD.search(num)\n            while mo:\n                num = THREE_DIGITS_WORD.sub(self.hundsub, num, 1)\n                mo = THREE_DIGITS_WORD.search(num)\n            num = TWO_DIGITS_WORD.sub(self.tensub, num, 1)\n            num = ONE_DIGIT_WORD.sub(self.unitsub, num, 1)\n        return num\n\n    @staticmethod\n    def _sub_ord(val):\n        new = ordinal_suff.sub(lambda match: ordinal[match.group(1)], val)\n        return new + \"th\" * (new == val)\n\n    @classmethod\n    def _chunk_num(cls, num, decimal, group):\n        if decimal:\n            max_split = -1 if group != 0 else 1\n            chunks = num.split(\".\", max_split)\n        else:\n            chunks = [num]\n        return cls._remove_last_blank(chunks)\n\n    @staticmethod\n    def _remove_last_blank(chunks):\n        \"\"\"\n        Remove the last item from chunks if it's a blank string.\n\n        Return the resultant chunks and whether the last item was removed.\n        \"\"\"\n        removed = chunks[-1] == \"\"\n        result = chunks[:-1] if removed else chunks\n        return result, removed\n\n    @staticmethod\n    def _get_sign(num):\n        return {'+': 'plus', '-': 'minus'}.get(num.lstrip()[0], '')\n\n    @typechecked\n    def number_to_words(  # noqa: C901\n        self,\n        num: Union[Number, Word],\n        wantlist: bool = False,\n        group: int = 0,\n        comma: Union[Falsish, str] = \",\",\n        andword: str = \"and\",\n        zero: str = \"zero\",\n        one: str = \"one\",\n        decimal: Union[Falsish, str] = \"point\",\n        threshold: Optional[int] = None,\n    ) -> Union[str, List[str]]:\n        \"\"\"\n        Return a number in words.\n\n        group = 1, 2 or 3 to group numbers before turning into words\n        comma: define comma\n\n        andword:\n            word for 'and'. Can be set to ''.\n            e.g. \"one hundred and one\" vs \"one hundred one\"\n\n        zero: word for '0'\n        one: word for '1'\n        decimal: word for decimal point\n        threshold: numbers above threshold not turned into words\n\n        parameters not remembered from last call. Departure from Perl version.\n        \"\"\"\n        self._number_args = {\"andword\": andword, \"zero\": zero, \"one\": one}\n        num = str(num)\n\n        # Handle \"stylistic\" conversions (up to a given threshold)...\n        if threshold is not None and float(num) > threshold:\n            spnum = num.split(\".\", 1)\n            while comma:\n                (spnum[0], n) = FOUR_DIGIT_COMMA.subn(r\"\\1,\\2\", spnum[0])\n                if n == 0:\n                    break\n            try:\n                return f\"{spnum[0]}.{spnum[1]}\"\n            except IndexError:\n                return str(spnum[0])\n\n        if group < 0 or group > 3:\n            raise BadChunkingOptionError\n\n        sign = self._get_sign(num)\n\n        if num in nth_suff:\n            num = zero\n\n        myord = num[-2:] in nth_suff\n        if myord:\n            num = num[:-2]\n\n        chunks, finalpoint = self._chunk_num(num, decimal, group)\n\n        loopstart = chunks[0] == \"\"\n        first: bool | None = not loopstart\n\n        def _handle_chunk(chunk):\n            nonlocal first\n\n            # remove all non numeric \\D\n            chunk = NON_DIGIT.sub(\"\", chunk)\n            if chunk == \"\":\n                chunk = \"0\"\n\n            if group == 0 and not first:\n                chunk = self.enword(chunk, 1)\n            else:\n                chunk = self.enword(chunk, group)\n\n            if chunk[-2:] == \", \":\n                chunk = chunk[:-2]\n            chunk = WHITESPACES_COMMA.sub(\",\", chunk)\n\n            if group == 0 and first:\n                chunk = COMMA_WORD.sub(f\" {andword} \\\\1\", chunk)\n            chunk = WHITESPACES.sub(\" \", chunk)\n            # chunk = re.sub(r\"(\\A\\s|\\s\\Z)\", self.blankfn, chunk)\n            chunk = chunk.strip()\n            if first:\n                first = None\n            return chunk\n\n        chunks[loopstart:] = map(_handle_chunk, chunks[loopstart:])\n\n        numchunks = []\n        if first != 0:\n            numchunks = chunks[0].split(f\"{comma} \")\n\n        if myord and numchunks:\n            numchunks[-1] = self._sub_ord(numchunks[-1])\n\n        for chunk in chunks[1:]:\n            numchunks.append(decimal)\n            numchunks.extend(chunk.split(f\"{comma} \"))\n\n        if finalpoint:\n            numchunks.append(decimal)\n\n        if wantlist:\n            return [sign] * bool(sign) + numchunks\n\n        signout = f\"{sign} \" if sign else \"\"\n        valout = (\n            ', '.join(numchunks)\n            if group\n            else ''.join(self._render(numchunks, decimal, comma))\n        )\n        return signout + valout\n\n    @staticmethod\n    def _render(chunks, decimal, comma):\n        first_item = chunks.pop(0)\n        yield first_item\n        first = decimal is None or not first_item.endswith(decimal)\n        for nc in chunks:\n            if nc == decimal:\n                first = False\n            elif first:\n                yield comma\n            yield f\" {nc}\"\n\n    @typechecked\n    def join(\n        self,\n        words: Optional[Sequence[Word]],\n        sep: Optional[str] = None,\n        sep_spaced: bool = True,\n        final_sep: Optional[str] = None,\n        conj: str = \"and\",\n        conj_spaced: bool = True,\n    ) -> str:\n        \"\"\"\n        Join words into a list.\n\n        e.g. join(['ant', 'bee', 'fly']) returns 'ant, bee, and fly'\n\n        options:\n        conj: replacement for 'and'\n        sep: separator. default ',', unless ',' is in the list then ';'\n        final_sep: final separator. default ',', unless ',' is in the list then ';'\n        conj_spaced: boolean. Should conj have spaces around it\n\n        \"\"\"\n        if not words:\n            return \"\"\n        if len(words) == 1:\n            return words[0]\n\n        if conj_spaced:\n            if conj == \"\":\n                conj = \" \"\n            else:\n                conj = f\" {conj} \"\n\n        if len(words) == 2:\n            return f\"{words[0]}{conj}{words[1]}\"\n\n        if sep is None:\n            if \",\" in \"\".join(words):\n                sep = \";\"\n            else:\n                sep = \",\"\n        if final_sep is None:\n            final_sep = sep\n\n        final_sep = f\"{final_sep}{conj}\"\n\n        if sep_spaced:\n            sep += \" \"\n\n        return f\"{sep.join(words[0:-1])}{final_sep}{words[-1]}\"\n"
  },
  {
    "path": "inflect/compat/__init__.py",
    "content": ""
  },
  {
    "path": "inflect/compat/py38.py",
    "content": "import sys\n\n\nif sys.version_info > (3, 9):\n    from typing import Annotated\nelse:  # pragma: no cover\n    from typing_extensions import Annotated  # noqa: F401\n"
  },
  {
    "path": "inflect/py.typed",
    "content": ""
  },
  {
    "path": "mypy.ini",
    "content": "[mypy]\n# Is the project well-typed?\nstrict = False\n\n# Early opt-in even when strict = False\nwarn_unused_ignores = True\nwarn_redundant_casts = True\nenable_error_code = ignore-without-code\n\n# Support namespace packages per https://github.com/python/mypy/issues/14057\nexplicit_package_bases = True\n\ndisable_error_code =\n\t# Disable due to many false positives\n\toverload-overlap,\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\n\t\"setuptools>=77\",\n\t\"setuptools_scm[toml]>=3.4.1\",\n\t# jaraco/skeleton#174\n\t\"coherent.licensed\",\n]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"inflect\"\nauthors = [\n\t{ name = \"Paul Dyson\", email = \"pwdyson@yahoo.com\" },\n]\nmaintainers = [\n\t{ name = \"Jason R. Coombs\", email = \"jaraco@jaraco.com\" },\n]\ndescription = \"Correctly generate plurals, singular nouns, ordinals, indefinite articles\" # convert numbers to words\nreadme = \"README.rst\"\nclassifiers = [\n\t\"Development Status :: 5 - Production/Stable\",\n\t\"Intended Audience :: Developers\",\n\t\"Programming Language :: Python :: 3\",\n\t\"Programming Language :: Python :: 3 :: Only\",\n\t\"Natural Language :: English\",\n\t\"Operating System :: OS Independent\",\n\t\"Topic :: Software Development :: Libraries :: Python Modules\",\n\t\"Topic :: Text Processing :: Linguistic\",\n]\nrequires-python = \">=3.10\"\nlicense = \"MIT\"\ndependencies = [\n\t\"more_itertools >= 8.5.0\",\n\t\"typeguard >= 4.0.1\",\n\t\"typing_extensions ; python_version<'3.9'\",\n]\nkeywords = [\n\t\"plural\",\n\t\"inflect\",\n\t\"participle\",\n]\ndynamic = [\"version\"]\n\n[project.urls]\nSource = \"https://github.com/jaraco/inflect\"\n\n[project.optional-dependencies]\ntest = [\n\t# upstream\n\t\"pytest >= 6, != 8.1.*\",\n\n\t# local\n\t\"pygments\",\n]\n\ndoc = [\n\t# upstream\n\t\"sphinx >= 3.5\",\n\t\"jaraco.packaging >= 9.3\",\n\t\"rst.linker >= 1.9\",\n\t\"furo\",\n\t\"sphinx-lint\",\n\n\t# tidelift\n\t\"jaraco.tidelift >= 1.4\",\n\n\t# local\n]\n\ncheck = [\n\t\"pytest-checkdocs >= 2.14\",\n\t\"pytest-ruff >= 0.2.1; sys_platform != 'cygwin'\",\n]\n\ncover = [\n\t\"pytest-cov\",\n]\n\nenabler = [\n\t\"pytest-enabler >= 3.4\",\n]\n\ntype = [\n\t# upstream\n\t\n    # Exclude PyPy from type checks (python/mypy#20454 jaraco/skeleton#187)\n\t\"pytest-mypy >= 1.0.1; platform_python_implementation != 'PyPy'\",\n\n\t# local\n]\n\n\n[tool.setuptools_scm]\n\n\n[tool.pytest-enabler.mypy]\n# Disabled due to jaraco/skeleton#143\n"
  },
  {
    "path": "pytest.ini",
    "content": "[pytest]\nnorecursedirs=dist build .tox .eggs\naddopts=\n\t--doctest-modules\n\t--import-mode importlib\nconsider_namespace_packages=true\nfilterwarnings=\n\t## upstream\n\n\t# Ensure ResourceWarnings are emitted\n\tdefault::ResourceWarning\n\n\t# realpython/pytest-mypy#152\n\tignore:'encoding' argument not specified::pytest_mypy\n\n\t# python/cpython#100750\n\tignore:'encoding' argument not specified::platform\n\n\t# pypa/build#615\n\tignore:'encoding' argument not specified::build.env\n\n\t# dateutil/dateutil#1284\n\tignore:datetime.datetime.utcfromtimestamp:DeprecationWarning:dateutil.tz.tz\n\n\t## end upstream\n"
  },
  {
    "path": "ruff.toml",
    "content": "[lint]\nextend-select = [\n\t# upstream\n\n\t\"C901\", # complex-structure\n\t\"I\", # isort\n\t\"PERF401\", # manual-list-comprehension\n\n\t# Ensure modern type annotation syntax and best practices\n\t# Not including those covered by type-checkers or exclusive to Python 3.11+\n\t\"FA\", # flake8-future-annotations\n\t\"F404\", # late-future-import\n\t\"PYI\", # flake8-pyi\n\t\"UP006\", # non-pep585-annotation\n\t\"UP007\", # non-pep604-annotation\n\t\"UP010\", # unnecessary-future-import\n\t\"UP035\", # deprecated-import\n\t\"UP037\", # quoted-annotation\n\t\"UP043\", # unnecessary-default-type-args\n\n\t# local\n]\nignore = [\n\t# upstream\n\n\t# Typeshed rejects complex or non-literal defaults for maintenance and testing reasons,\n\t# irrelevant to this project.\n\t\"PYI011\", # typed-argument-default-in-stub\n\t# https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules\n\t\"W191\",\n\t\"E111\",\n\t\"E114\",\n\t\"E117\",\n\t\"D206\",\n\t\"D300\",\n\t\"Q000\",\n\t\"Q001\",\n\t\"Q002\",\n\t\"Q003\",\n\t\"COM812\",\n\t\"COM819\",\n\n\t# local\n]\n\n[format]\n# Enable preview to get hugged parenthesis unwrapping and other nice surprises\n# See https://github.com/jaraco/skeleton/pull/133#issuecomment-2239538373\npreview = true\n# https://docs.astral.sh/ruff/settings/#format_quote-style\nquote-style = \"preserve\"\n"
  },
  {
    "path": "tea.yaml",
    "content": "# https://tea.xyz/what-is-this-file\n---\nversion: 1.0.0\ncodeOwners:\n  - '0x32392EaEA1FDE87733bEEc3b184C9006501c4A82'\nquorum: 1\n"
  },
  {
    "path": "tests/inflections.txt",
    "content": "                    a  ->  as                             # NOUN FORM\n      TODO:sing              a  ->  some                           # INDEFINITE ARTICLE\n      TODO:  A.C.R.O.N.Y.M.  ->  A.C.R.O.N.Y.M.s\n             abscissa  ->  abscissas|abscissae\n           accomplice  ->  accomplices\n             Achinese  ->  Achinese\n            acropolis  ->  acropolises\n                adieu  ->  adieus|adieux\n     adjutant general  ->  adjutant generals\n                aegis  ->  aegises\n             afflatus  ->  afflatuses\n               afreet  ->  afreets|afreeti\n                afrit  ->  afrits|afriti\n              agendum  ->  agenda\n         aide-de-camp  ->  aides-de-camp\n             Alabaman  ->  Alabamans\n               albino  ->  albinos\n                album  ->  albums\n             Alfurese  ->  Alfurese\n                 alga  ->  algae\n                alias  ->  aliases\n                 alto  ->  altos|alti\n               alumna  ->  alumnae\n              alumnus  ->  alumni\n             alveolus  ->  alveoli\n   TODO:siverb                am  ->  are\n   TODO:siverb          am going  ->  are going\n  ambassador-at-large  ->  ambassadors-at-large\n            Amboinese  ->  Amboinese\n          Americanese  ->  Americanese\n               amoeba  ->  amoebas|amoebae\n              Amoyese  ->  Amoyese\n   TODO:siadj                an  ->  some                           # INDEFINITE ARTICLE\n             analysis  ->  analyses\n             anathema  ->  anathemas|anathemata\n           Andamanese  ->  Andamanese\n             Angolese  ->  Angolese\n             Annamese  ->  Annamese\n              antenna  ->  antennas|antennae\n                 anus  ->  anuses\n                 apex  ->  apexes|apices\n   TODO:siadj            apex's  ->  apexes'|apices'                # POSSESSIVE FORM\n             aphelion  ->  aphelia\n            apparatus  ->  apparatuses|apparatus\n             appendix  ->  appendixes|appendices\n                apple  ->  apples\n             aquarium  ->  aquariums|aquaria\n            Aragonese  ->  Aragonese\n            Arakanese  ->  Arakanese\n          archipelago  ->  archipelagos\n   TODO:siverb               are  ->  are\n   TODO:siverb          are made  ->  are made\n            armadillo  ->  armadillos\n             arpeggio  ->  arpeggios\n            arthritis  ->  arthritises|arthritides\n             asbestos  ->  asbestoses\n            asparagus  ->  asparaguses\n                  ass  ->  asses\n             Assamese  ->  Assamese\n               asylum  ->  asylums\n            asyndeton  ->  asyndeta\n                at it  ->  at them                        # ACCUSATIVE\n               ataman  ->  atamans\n   TODO:siverb               ate  ->  ate\n                atlas  ->  atlases|atlantes\n                atman  ->  atmas\n  TODO:singular_noun   attorney general  ->  attorneys general\n   attorney of record  ->  attorneys of record\n               aurora  ->  auroras|aurorae\n                 auto  ->  autos\n           auto-da-fe  ->  autos-da-fe\n             aviatrix  ->  aviatrixes|aviatrices\n    TODO:siadj       aviatrix's  ->  aviatrixes'|aviatrices'\n           Avignonese  ->  Avignonese\n                  axe  ->  axes\n    TODO:singular_noun 2 anwers!            axis  ->  axes\n                axman  ->  axmen\n        Azerbaijanese  ->  Azerbaijanese\n             bacillus  ->  bacilli\n            bacterium  ->  bacteria\n              Bahaman  ->  Bahamans\n             Balinese  ->  Balinese\n               bamboo  ->  bamboos\n                banjo  ->  banjoes\n                 bass  ->  basses                         # INSTRUMENT, NOT FISH\n                basso  ->  bassos|bassi\n               bathos  ->  bathoses\n                 beau  ->  beaus|beaux\n                 beef  ->  beefs|beeves\n           beneath it  ->  beneath them                   # ACCUSATIVE\n            Bengalese  ->  Bengalese\n                 bent  ->  bent                           # VERB FORM\n                 bent  ->  bents                          # NOUN FORM\n              Bernese  ->  Bernese\n            Bhutanese  ->  Bhutanese\n                 bias  ->  biases\n               biceps  ->  biceps\n                bison  ->  bisons|bison\n          black olive  ->  black olives\n               blouse  ->  blouses\n            Bolognese  ->  Bolognese\n                bonus  ->  bonuses\n             Borghese  ->  Borghese\n                 boss  ->  bosses\n            Bostonese  ->  Bostonese\n                  box  ->  boxes\n                  boy  ->  boys\n                bravo  ->  bravoes\n                bream  ->  bream\n             breeches  ->  breeches\n          bride-to-be  ->  brides-to-be\n    Brigadier General  ->  Brigadier Generals\n             britches  ->  britches\n           bronchitis  ->  bronchitises|bronchitides\n             bronchus  ->  bronchi\n              brother  ->  brothers|brethren\n   TODO:         brother's  ->  brothers'|brethren's\n              buffalo  ->  buffaloes|buffalo\n             Buginese  ->  Buginese\n                 buoy  ->  buoys\n               bureau  ->  bureaus|bureaux\n               Burman  ->  Burmans\n              Burmese  ->  Burmese\n             bursitis  ->  bursitises|bursitides\n                  bus  ->  buses\n               butter  ->  butter\n                 buzz  ->  buzzes\n               buzzes  ->  buzz                           # VERB FORM\n                by it  ->  by them                        # ACCUSATIVE\n               caddis  ->  caddises\n               caiman  ->  caimans\n                 cake  ->  cakes\n            Calabrese  ->  Calabrese\n                 calf  ->  calves\n               callus  ->  calluses\n          Camaldolese  ->  Camaldolese\n                cameo  ->  cameos\n               campus  ->  campuses\n                  can  ->  cans                           # NOUN FORM\n                  can  ->  can                            # VERB FORM (all pers.)\n                can't  ->  can't                          # VERB FORM\n          candelabrum  ->  candelabra\n             cannabis  ->  cannabises\n      TODO:siverb         canoes  ->  canoe\n                canto  ->  cantos\n            Cantonese  ->  Cantonese\n               cantus  ->  cantus\n               canvas  ->  canvases\n              CAPITAL  ->  CAPITALS\n            carcinoma  ->  carcinomas|carcinomata\n                 care  ->  cares\n                cargo  ->  cargoes\n              caribou  ->  caribous|caribou\n            Carlylese  ->  Carlylese\n               carmen  ->  carmina\n                 carp  ->  carp\n                 cash  ->  cash\n            Cassinese  ->  Cassinese\n                  cat  ->  cats\n              catfish  ->  catfish\n\t\t\t   cattle  ->  cattles|cattle\n               cayman  ->  caymans\n             Celanese  ->  Celanese\n              ceriman  ->  cerimans\n               cervid  ->  cervids\n            Ceylonese  ->  Ceylonese\n             chairman  ->  chairmen\n              chamois  ->  chamois\n                chaos  ->  chaoses\n              chapeau  ->  chapeaus|chapeaux\n             charisma  ->  charismas|charismata\n    TODO:siverb           chases  ->  chase\n              chassis  ->  chassis\n              chateau  ->  chateaus|chateaux\n               cherub  ->  cherubs|cherubim\n           chickenpox  ->  chickenpox\n                chief  ->  chiefs\n                child  ->  children\n                chili  ->  chilis|chilies\n              Chinese  ->  Chinese\n       oatmeal cookie  ->  oatmeal cookies\n               chorus  ->  choruses\n            chrysalis  ->  chrysalises|chrysalides\n               church  ->  churches\n             cicatrix  ->  cicatrixes|cicatrices\n               circus  ->  circuses\n                class  ->  classes\n              classes  ->  class                          # VERB FORM\n             clippers  ->  clippers\n             clitoris  ->  clitorises|clitorides\n                  cod  ->  cod\n                codex  ->  codices\n               coitus  ->  coitus\n             commando  ->  commandos\n           compendium  ->  compendiums|compendia\n                coney  ->  coneys\n             Congoese  ->  Congoese\n            Congolese  ->  Congolese\n           conspectus  ->  conspectuses\n            contralto  ->  contraltos|contralti\n          contretemps  ->  contretemps\n            conundrum  ->  conundrums\n                corps  ->  corps\n               corpus  ->  corpuses|corpora\n               cortex  ->  cortexes|cortices\n               cosmos  ->  cosmoses\n     TODO:singular_noun   court martial  ->  courts martial\n                  cow  ->  cows|kine\n              cranium  ->  craniums|crania\n            crescendo  ->  crescendos\n            criterion  ->  criteria\n           curriculum  ->  curriculums|curricula\n                czech  ->  czechs\n                 dais  ->  daises\n           data point  ->  data points\n                datum  ->  data\n               debris  ->  debris\n              decorum  ->  decorums\n                 deer  ->  deer\n           delphinium  ->  delphiniums\n          desideratum  ->  desiderata\n               desman  ->  desmans\n             diabetes  ->  diabetes\n               dictum  ->  dictums|dicta\n    TODO:siverb              did  ->  did\n    TODO:siverb         did need  ->  did need\n            digitalis  ->  digitalises\n                dingo  ->  dingoes\n              diploma  ->  diplomas|diplomata\n               discus  ->  discuses\n                 dish  ->  dishes\n                ditto  ->  dittos\n                djinn  ->  djinn\n     TODO:siverb            does  ->  do\n     TODO:siverb         doesn't  ->  don't                          # VERB FORM\n                  dog  ->  dogs\n                dogma  ->  dogmas|dogmata\n               dolman  ->  dolmans\n           dominatrix  ->  dominatrixes|dominatrices\n               domino  ->  dominoes\n            Dongolese  ->  Dongolese\n             dormouse  ->  dormice\n                drama  ->  dramas|dramata\n                 drum  ->  drums\n                dwarf  ->  dwarves\n               dynamo  ->  dynamos\n                edema  ->  edemas|edemata\n                eland  ->  elands|eland\n                  elf  ->  elves\n                  elk  ->  elks|elk\n               embryo  ->  embryos\n             emporium  ->  emporiums|emporia\n         encephalitis  ->  encephalitises|encephalitides\n             encomium  ->  encomiums|encomia\n                enema  ->  enemas|enemata\n               enigma  ->  enigmas|enigmata\n            epidermis  ->  epidermises\n           epididymis  ->  epididymises|epididymides\n              erratum  ->  errata\n                ethos  ->  ethoses\n           eucalyptus  ->  eucalyptuses\n               eunuch  ->  eunuchs\n             extremum  ->  extrema\n                 eyas  ->  eyases\n             factotum  ->  factotums\n               farman  ->  farmans\n              Faroese  ->  Faroese\n                fauna  ->  faunas|faunae\n                  fax  ->  faxes\n            Ferrarese  ->  Ferrarese\n                ferry  ->  ferries\n                fetus  ->  fetuses\n               fiance  ->  fiances\n              fiancee  ->  fiancees\n               fiasco  ->  fiascos\n                 fish  ->  fish\n                 fizz  ->  fizzes\n             flamingo  ->  flamingoes\n         flittermouse  ->  flittermice\n     TODO:siverb           floes  ->  floe\n                flora  ->  floras|florae\n             flounder  ->  flounder\n                focus  ->  focuses|foci\n               foetus  ->  foetuses\n                folio  ->  folios\n           Foochowese  ->  Foochowese\n                 foot  ->  feet\n     TODO:siadj          foot's  ->  feet's                         # POSSESSIVE FORM\n              foramen  ->  foramens|foramina\n     TODO:siverb       foreshoes  ->  foreshoe\n              formula  ->  formulas|formulae\n                forum  ->  forums\n     TODO:siverb          fought  ->  fought\n                  fox  ->  foxes\n     TODO:singular_noun 2 different returns        from him  ->  from them\n              from it  ->  from them                      # ACCUSATIVE\n               fungus  ->  funguses|fungi\n             Gabunese  ->  Gabunese\n              gallows  ->  gallows\n             ganglion  ->  ganglions|ganglia\n                  gas  ->  gases\n               gateau  ->  gateaus|gateaux\n    TODO:siverb             gave  ->  gave\n              general  ->  generals\n        generalissimo  ->  generalissimos\n             Genevese  ->  Genevese\n                genie  ->  genies|genii\n    TODO:singular_noun 2 diff return values!           genius  ->  geniuses|genii\n              Genoese  ->  Genoese\n                genus  ->  genera\n               German  ->  Germans\n               ghetto  ->  ghettos\n           Gilbertese  ->  Gilbertese\n              glottis  ->  glottises\n              Goanese  ->  Goanese\n                 goat  ->  goats\n                goose  ->  geese\n    TODO:singular_noun Governor General  ->  Governors General\n                  goy  ->  goys|goyim\n             graffiti  ->  graffiti\n    TODO:singular_noun 2 diff ret values         graffito  ->  graffiti\n              grizzly  ->  grizzlies\n                guano  ->  guanos\n            guardsman  ->  guardsmen\n             Guianese  ->  Guianese\n                gumma  ->  gummas|gummata\n    TODO:siverb         gumshoes  ->  gumshoe\n               gunman  ->  gunmen\n            gymnasium  ->  gymnasiums|gymnasia\n    TODO:siverb              had  ->  had\n    TODO:siverb      had thought  ->  had thought\n            Hainanese  ->  Hainanese\n    TODO:siverb       hammertoes  ->  hammertoe\n         handkerchief  ->  handkerchiefs\n             Hararese  ->  Hararese\n            Harlemese  ->  Harlemese\n               harman  ->  harmans\n            harmonium  ->  harmoniums\n    TODO:siverb              has  ->  have\n    TODO:siverb       has become  ->  have become\n    TODO:siverb         has been  ->  have been\n    TODO:siverb         has-been  ->  has-beens\n               hasn't  ->  haven't                        # VERB FORM\n             Havanese  ->  Havanese\n    TODO:siverb             have  ->  have\n    TODO:siverb    have conceded  ->  have conceded\n    TODO:singular_noun 2 values               he  ->  they\n         headquarters  ->  headquarters\n            Heavenese  ->  Heavenese\n                helix  ->  helices\n            hepatitis  ->  hepatitises|hepatitides\n    TODO:singular_noun 2 values              her  ->  them                           # PRONOUN\n    TODO:singular_noun 2 values              her  ->  their\n         # POSSESSIVE ADJ\n                 hero  ->  heroes\n               herpes  ->  herpes\n    TODO:singular_noun 2 values             hers  ->  theirs\n         # POSSESSIVE NOUN\n    TODO:singular_noun 2 values          herself  ->  themselves\n               hetman  ->  hetmans\n               hiatus  ->  hiatuses|hiatus\n            highlight  ->  highlights\n              hijinks  ->  hijinks\n    TODO:singular_noun 2 values              him  ->  them\n    TODO:singular_noun 2 values          himself  ->  themselves\n         hippopotamus  ->  hippopotamuses|hippopotami\n           Hiroshiman  ->  Hiroshimans\n    TODO:singular_noun 2 values              his  ->  their\n         # POSSESSIVE ADJ\n    TODO:singular_noun 2 values              his  ->  theirs\n         # POSSESSIVE NOUN\n    TODO:siverb             hoes  ->  hoe\n           honorarium  ->  honorariums|honoraria\n                 hoof  ->  hoofs|hooves\n           Hoosierese  ->  Hoosierese\n    TODO:siverb       horseshoes  ->  horseshoe\n         Hottentotese  ->  Hottentotese\n                house  ->  houses\n            housewife  ->  housewives\n               hubris  ->  hubrises\n                human  ->  humans\n             Hunanese  ->  Hunanese\n                hydra  ->  hydras|hydrae\n           hyperbaton  ->  hyperbata\n            hyperbola  ->  hyperbolas|hyperbolae\n                    I  ->  we\n                 ibis  ->  ibises\n            ignoramus  ->  ignoramuses\n              impetus  ->  impetuses|impetus\n              incubus  ->  incubuses|incubi\n                index  ->  indexes|indices\n          Indochinese  ->  Indochinese\n              inferno  ->  infernos\n             infinity  ->  infinities|infinity\n          information  ->  information\n              innings  ->  innings\n   TODO:singular_noun Inspector General  ->  Inspectors General\n          interregnum  ->  interregnums|interregna\n                 iris  ->  irises|irides\n       TODO:siverb            is  ->  are\n       TODO:siverb      is eaten  ->  are eaten\n                isn't  ->  aren't                         # VERB FORM\n                   it  ->  they                           # NOMINATIVE\n       TODO:siadj           its  ->  their                          # POSSESSIVE FORM\n               itself  ->  themselves\n           jackanapes  ->  jackanapes\n             Japanese  ->  Japanese\n             Javanese  ->  Javanese\n                Jerry  ->  Jerrys\n                jerry  ->  jerries\n                 jinx  ->  jinxes\n               jinxes  ->  jinx                           # VERB FORM\n           Johnsonese  ->  Johnsonese\n                Jones  ->  Joneses\n                jumbo  ->  jumbos\n             Kanarese  ->  Kanarese\n           Kiplingese  ->  Kiplingese\n                knife  ->  knives                         # NOUN FORM\n                knife  ->  knife                          # VERB FORM (1st/2nd pers.)\n               knifes  ->  knife                          # VERB FORM (3rd pers.)\n             Kongoese  ->  Kongoese\n            Kongolese  ->  Kongolese\n               lacuna  ->  lacunas|lacunae\n      lady in waiting  ->  ladies in waiting\n            Lapponese  ->  Lapponese\n               larynx  ->  larynxes|larynges\n                latex  ->  latexes|latices\n               lawman  ->  lawmen\n               layman  ->  laymen\n                 leaf  ->  leaves                         # NOUN FORM\n                 leaf  ->  leaf                           # VERB FORM (1st/2nd pers.)\n                leafs  ->  leaf                           # VERB FORM (3rd pers.)\n             Lebanese  ->  Lebanese\n                leman  ->  lemans\n                lemma  ->  lemmas|lemmata\n                 lens  ->  lenses\n              Leonese  ->  Leonese\n      lick of the cat  ->  licks of the cat\n   Lieutenant General  ->  Lieutenant Generals\n                  lie  ->  lies\n                 life  ->  lives\n                Liman  ->  Limans\n                lingo  ->  lingos\n                 loaf  ->  loaves\n                locus  ->  loci\n            Londonese  ->  Londonese\n                 lore  ->  lores|lore\n           Lorrainese  ->  Lorrainese\n             lothario  ->  lotharios\n                louse  ->  lice\n             Lucchese  ->  Lucchese\n              lumbago  ->  lumbagos\n                lumen  ->  lumens|lumina\n               lummox  ->  lummoxes\n              lustrum  ->  lustrums|lustra\n               lyceum  ->  lyceums\n             lymphoma  ->  lymphomas|lymphomata\n                 lynx  ->  lynxes\n              Lyonese  ->  Lyonese\n   TODO:            M.I.A.  ->  M.I.A.s\n             Macanese  ->  Macanese\n          Macassarese  ->  Macassarese\n             mackerel  ->  mackerel\n                macro  ->  macros\n     TODO:siverb            made  ->  made\n               madman  ->  madmen\n             Madurese  ->  Madurese\n                magma  ->  magmas|magmata\n              magneto  ->  magnetos\n        Major General  ->  Major Generals\n           Malabarese  ->  Malabarese\n              Maltese  ->  Maltese\n                  man  ->  men\n             mandamus  ->  mandamuses\n            manifesto  ->  manifestos\n               mantis  ->  mantises\n              marquis  ->  marquises\n                 Mary  ->  Marys\n              maximum  ->  maximums|maxima\n              measles  ->  measles\n               medico  ->  medicos\n               medium  ->  mediums|media\n   TODO:siadj          medium's  ->  mediums'|media's\n               medusa  ->  medusas|medusae\n           memorandum  ->  memorandums|memoranda\n             meniscus  ->  menisci\n               merman  ->  mermen\n            Messinese  ->  Messinese\n        metamorphosis  ->  metamorphoses\n           metropolis  ->  metropolises\n                 mews  ->  mews\n               miasma  ->  miasmas|miasmata\n             Milanese  ->  Milanese\n               milieu  ->  milieus|milieux\n           millennium  ->  millenniums|millennia\n              minimum  ->  minimums|minima\n                 minx  ->  minxes\n                 miss  ->  miss                           # VERB FORM (1st/2nd pers.)\n                 miss  ->  misses                         # NOUN FORM\n               misses  ->  miss                           # VERB FORM (3rd pers.)\n    TODO:siverb       mistletoes  ->  mistletoe\n             mittamus  ->  mittamuses\n             Modenese  ->  Modenese\n             momentum  ->  momentums|momenta\n                money  ->  monies\n             mongoose  ->  mongooses\n                moose  ->  moose\n        mother-in-law  ->  mothers-in-law\n                mouse  ->  mice\n                mumps  ->  mumps\n             Muranese  ->  Muranese\n                murex  ->  murices\n               museum  ->  museums\n            mustachio  ->  mustachios\n   TODO:siadj                my  ->  our                            # POSSESSIVE FORM\n               myself  ->  ourselves\n               mythos  ->  mythoi\n            Nakayaman  ->  Nakayamans\n           Nankingese  ->  Nankingese\n           nasturtium  ->  nasturtiums\n            Navarrese  ->  Navarrese\n               nebula  ->  nebulas|nebulae\n             Nepalese  ->  Nepalese\n             neuritis  ->  neuritises|neuritides\n             neurosis  ->  neuroses\n                 news  ->  news\n                nexus  ->  nexus\n              Niasese  ->  Niasese\n           Nicobarese  ->  Nicobarese\n               nimbus  ->  nimbuses|nimbi\n            Nipponese  ->  Nipponese\n                   no  ->  noes\n               Norman  ->  Normans\n              nostrum  ->  nostrums\n             noumenon  ->  noumena\n                 nova  ->  novas|novae\n            nucleolus  ->  nucleoluses|nucleoli\n              nucleus  ->  nuclei\n                numen  ->  numina\n                  oaf  ->  oafs\n    TODO:siverb            oboes  ->  oboe\n              occiput  ->  occiputs|occipita\n               octavo  ->  octavos\n              octopus  ->  octopuses|octopodes\n               oedema  ->  oedemas|oedemata\n            Oklahoman  ->  Oklahomans\n              omnibus  ->  omnibuses\n                on it  ->  on them                        # ACCUSATIVE\n                 onus  ->  onuses\n                opera  ->  operas\n              optimum  ->  optimums|optima\n                 opus  ->  opuses|opera\n              organon  ->  organa\n              ottoman  ->  ottomans\n          ought to be  ->  ought to be                    # VERB (UNLIKE bride to be)\n     TODO:siverb       overshoes  ->  overshoe\n     TODO:siverb        overtoes  ->  overtoe\n                 ovum  ->  ova\n                   ox  ->  oxen\n     TODO:siadj            ox's  ->  oxen's                         # POSSESSIVE FORM\n                oxman  ->  oxmen\n             oxymoron  ->  oxymorons|oxymora\n              Panaman  ->  Panamans\n             parabola  ->  parabolas|parabolae\n              Parmese  ->  Parmese\n               pathos  ->  pathoses\n              pegasus  ->  pegasuses\n            Pekingese  ->  Pekingese\n               pelvis  ->  pelvises\n             pendulum  ->  pendulums\n                penis  ->  penises|penes\n             penumbra  ->  penumbras|penumbrae\n           perihelion  ->  perihelia\n               person  ->  people|persons\n              persona  ->  personae\n            petroleum  ->  petroleums\n              phalanx  ->  phalanxes|phalanges\n                  PhD  ->  PhDs\n           phenomenon  ->  phenomena\n             philtrum  ->  philtrums\n                photo  ->  photos\n               phylum  ->  phylums|phyla\n                piano  ->  pianos|piani\n          Piedmontese  ->  Piedmontese\n                 pika  ->  pikas\n   TODO:singular_noun ret mul value            pincer  ->  pincers\n              pincers  ->  pincers\n            Pistoiese  ->  Pistoiese\n              plateau  ->  plateaus|plateaux\n                 play  ->  plays\n               plexus  ->  plexuses|plexus\n               pliers  ->  pliers\n                plies  ->  ply                            # VERB FORM\n                polis  ->  polises\n             Polonese  ->  Polonese\n             pontifex  ->  pontifexes|pontifices\n          portmanteau  ->  portmanteaus|portmanteaux\n           Portuguese  ->  Portuguese\n               possum  ->  possums\n               potato  ->  potatoes\n                  pox  ->  pox\n               pragma  ->  pragmas|pragmata\n              premium  ->  premiums\n          prima donna  ->  prima donnas|prime donne\n                  pro  ->  pros\n          proceedings  ->  proceedings\n         prolegomenon  ->  prolegomena\n                proof  ->  proofs\n     proof of concept  ->  proofs of concept\n          prosecutrix  ->  prosecutrixes|prosecutrices\n           prospectus  ->  prospectuses|prospectus\n            protozoan  ->  protozoans\n            protozoon  ->  protozoa\n                 puma  ->  pumas\n     TODO:siverb             put  ->  put\n              quantum  ->  quantums|quanta\n TODO:singular_noun quartermaster general  ->  quartermasters general\n               quarto  ->  quartos\n                 quiz  ->  quizzes\n              quizzes  ->  quiz                           # VERB FORM\n               quorum  ->  quorums\n               rabies  ->  rabies\n               radius  ->  radiuses|radii\n                radix  ->  radices\n               ragman  ->  ragmen\n                rebus  ->  rebuses\n   TODO:siverb            rehoes  ->  rehoe\n             reindeer  ->  reindeer\n             repo      ->  repos\n   TODO:siverb           reshoes  ->  reshoe\n                rhino  ->  rhinos\n           rhinoceros  ->  rhinoceroses|rhinoceros\n   TODO:siverb              roes  ->  roe\n                  Rom  ->  Roma\n            Romagnese  ->  Romagnese\n                Roman  ->  Romans\n             Romanese  ->  Romanese\n               Romany  ->  Romanies\n                romeo  ->  romeos\n                 roof  ->  roofs\n              rostrum  ->  rostrums|rostra\n               ruckus  ->  ruckuses\n               salmon  ->  salmon\n            Sangirese  ->  Sangirese\n   TODO: siverb              sank  ->  sank\n           Sarawakese  ->  Sarawakese\n              sarcoma  ->  sarcomas|sarcomata\n            sassafras  ->  sassafrases\n                  saw  ->  saw                            # VERB FORM (1st/2nd pers.)\n                  saw  ->  saws                           # NOUN FORM\n                 saws  ->  saw                            # VERB FORM (3rd pers.)\n                scarf  ->  scarves\n               schema  ->  schemas|schemata\n             scissors  ->  scissors\n     pair of scissors  ->  pairs of scissors\n     pair of slippers  ->  pairs of slippers\n             Scotsman  ->  Scotsmen\n             sea-bass  ->  sea-bass\n               seaman  ->  seamen\n                 self  ->  selves\n               Selman  ->  Selmans\n           Senegalese  ->  Senegalese\n               seraph  ->  seraphs|seraphim\n               series  ->  series\n    TODO:siverb        shall eat  ->  shall eat\n               shaman  ->  shamans\n              Shavese  ->  Shavese\n            Shawanese  ->  Shawanese\n    TODO:singular_noun multivalue              she  ->  they\n                sheaf  ->  sheaves\n               shears  ->  shears\n                sheep  ->  sheep\n                shelf  ->  shelves\n   TODO:siverb             shoes  ->  shoe\n   TODO:siverb       should have  ->  should have\n              Siamese  ->  Siamese\n              siemens  ->  siemens\n              Sienese  ->  Sienese\n            Sikkimese  ->  Sikkimese\n                silex  ->  silices\n              simplex  ->  simplexes|simplices\n           Singhalese  ->  Singhalese\n            Sinhalese  ->  Sinhalese\n                sinus  ->  sinuses|sinus\n                 size  ->  sizes\n                sizes  ->  size                           #VERB FORM\n                slice  ->  slices\n             smallpox  ->  smallpox\n                Smith  ->  Smiths\n   TODO:siverb         snowshoes  ->  snowshoe\n           Sogdianese  ->  Sogdianese\n            soliloquy  ->  soliloquies\n                 solo  ->  solos|soli\n                 soma  ->  somas|somata\n   TODO:singular_noun tough    son of a bitch  ->  sons of bitches\n              Sonaman  ->  Sonamans\n              soprano  ->  sopranos|soprani\n   TODO:siverb            sought  ->  sought\n   TODO:siverb       spattlehoes  ->  spattlehoe\n              species  ->  species\n             spectrum  ->  spectrums|spectra\n             speculum  ->  speculums|specula\n   TODO:siverb             spent  ->  spent\n         spermatozoon  ->  spermatozoa\n               sphinx  ->  sphinxes|sphinges\n         spokesperson  ->  spokespeople|spokespersons\n              stadium  ->  stadiums|stadia\n               stamen  ->  stamens|stamina\n               status  ->  statuses|status\n               stereo  ->  stereos\n               stigma  ->  stigmas|stigmata\n             stimulus  ->  stimuli\n                stoma  ->  stomas|stomata\n              stomach  ->  stomachs\n               storey  ->  storeys\n                story  ->  stories\n              stratum  ->  strata\n               strife  ->  strifes\n                stylo  ->  stylos\n               stylus  ->  styluses|styli\n             succubus  ->  succubuses|succubi\n             Sudanese  ->  Sudanese\n               suffix  ->  suffixes\n            Sundanese  ->  Sundanese\n             superior  ->  superiors\n               supply  ->  supplies\n  TODO:singular_noun    Surgeon-General  ->  Surgeons-General\n              surplus  ->  surpluses\n            Swahilese  ->  Swahilese\n                swine  ->  swines|swine\n      TODO:singular_noun multiple return        syringe  ->  syringes\n               syrinx  ->  syrinxes|syringes\n              tableau  ->  tableaus|tableaux\n                 taco  ->  tacos\n              Tacoman  ->  Tacomans\n              talouse  ->  talouses\n               tattoo  ->  tattoos\n               taxman  ->  taxmen\n                tempo  ->  tempos|tempi\n           Tenggerese  ->  Tenggerese\n            testatrix  ->  testatrixes|testatrices\n               testes  ->  testes\n      TODO:singular_noun multiple return         testis  ->  testes\n      TODO:siadj           that  ->  those\n      TODO:siadj          their  ->  their\n           # POSSESSIVE FORM (GENDER-INCLUSIVE)\n      TODO:singular_noun multiple return       themself  ->  themselves\n           # ugly but gaining currency\n      TODO:singular_noun multiple return           they  ->  they\n           # for indeterminate gender\n                thief  ->  thiefs|thieves\n      TODO:siadj           this  ->  these\n              thought  ->  thoughts                       # NOUN FORM\n              thought  ->  thought                        # VERB FORM\n      TODO:siverb         throes  ->  throe\n      TODO:siverb   ticktacktoes  ->  ticktacktoe\n                Times  ->  Timeses\n             Timorese  ->  Timorese\n      TODO:siverb        tiptoes  ->  tiptoe\n             Tirolese  ->  Tirolese\n             titmouse  ->  titmice\n      TODO:singular_noun multivalue         to her  ->  to them\n      TODO:singular_noun multivalue     to herself  ->  to themselves\n      TODO:singular_noun multivalue         to him  ->  to them\n      TODO:singular_noun multivalue     to himself  ->  to themselves\n                to it  ->  to them\n                to it  ->  to them                        # ACCUSATIVE\n            to itself  ->  to themselves\n                to me  ->  to us\n            to myself  ->  to ourselves\n      TODO:singular_noun multivalue        to them  ->  to them\n           # for indeterminate gender\n      TODO:singular_noun multivalue    to themself  ->  to themselves\n            # ugly but gaining currency\n               to you  ->  to you\n          to yourself  ->  to yourselves\n            Tocharese  ->  Tocharese\n      TODO:siverb           toes  ->  toe\n               tomato  ->  tomatoes\n            Tonkinese  ->  Tonkinese\n          tonsillitis  ->  tonsillitises|tonsillitides\n                tooth  ->  teeth\n             Torinese  ->  Torinese\n                torus  ->  toruses|tori\n            trapezium  ->  trapeziums|trapezia\n               trauma  ->  traumas|traumata\n              travois  ->  travois\n              tranche  ->  tranches\n              trellis  ->  trellises\n     TODO:siverb           tries  ->  try\n               trilby  ->  trilbys\n             trousers  ->  trousers\n            trousseau  ->  trousseaus|trousseaux\n                trout  ->  trout\n      TODO:siverb            try  ->  tries\n                 tuna  ->  tuna\n                 turf  ->  turfs|turves\n             Tyrolese  ->  Tyrolese\n            ultimatum  ->  ultimatums|ultimata\n            umbilicus  ->  umbilicuses|umbilici\n                umbra  ->  umbras|umbrae\n      TODO:siverb     undershoes  ->  undershoe\n      TODO:siverb        unshoes  ->  unshoe\n               uterus  ->  uteruses|uteri\n               vacuum  ->  vacuums|vacua\n               vellum  ->  vellums\n                velum  ->  velums|vela\n           Vermontese  ->  Vermontese\n             Veronese  ->  Veronese\n             vertebra  ->  vertebrae\n               vertex  ->  vertexes|vertices\n             Viennese  ->  Viennese\n           Vietnamese  ->  Vietnamese\n             virtuoso  ->  virtuosos|virtuosi\n                virus  ->  viruses\n\t\t\t\t vita  ->  vitae\n                vixen  ->  vixens\n               vortex  ->  vortexes|vortices\n               walrus  ->  walruses\n   TODO:siverb               was  ->  were\n   TODO:siverb    was faced with  ->  were faced with\n   TODO:siverb        was hoping  ->  were hoping\n           Wenchowese  ->  Wenchowese\n   TODO:siverb              were  ->  were\n   TODO:siverb        were found  ->  were found\n                wharf  ->  wharves\n              whiting  ->  whiting\n           Whitmanese  ->  Whitmanese\n                 whiz  ->  whizzes\n   TODO:singular_noun multivalue             whizz  ->  whizzes\n               widget  ->  widgets\n                 wife  ->  wives\n           wildebeest  ->  wildebeests|wildebeest\n                 will  ->  will                           # VERB FORM\n                 will  ->  wills                          # NOUN FORM\n             will eat  ->  will eat                       # VERB FORM\n                wills  ->  will                           # VERB FORM\n                 wish  ->  wishes\n   TODO:singular_noun multivalue          with him  ->  with them\n              with it  ->  with them                      # ACCUSATIVE\n   TODO:siverb              woes  ->  woe\n                 wolf  ->  wolves\n                woman  ->  women\n   woman of substance  ->  women of substance\n    TODO:siadj          woman's  ->  women's                        # POSSESSIVE FORM\n                won't  ->  won't                          # VERB FORM\n            woodlouse  ->  woodlice\n              Yakiman  ->  Yakimans\n             Yengeese  ->  Yengeese\n               yeoman  ->  yeomen\n             yeowoman  ->  yeowomen\n                  yes  ->  yeses\n            Yokohaman  ->  Yokohamans\n                  you  ->  you\n   TODO:siadj              your  ->  your                           # POSSESSIVE FORM\n             yourself  ->  yourselves\n                Yuman  ->  Yumans\n            Yunnanese  ->  Yunnanese\n                 zero  ->  zeros\n                 zoon  ->  zoa\n"
  },
  {
    "path": "tests/test_an.py",
    "content": "import inflect\n\n\ndef test_an():\n    p = inflect.engine()\n\n    assert p.an(\"cat\") == \"a cat\"\n    assert p.an(\"ant\") == \"an ant\"\n    assert p.an(\"a\") == \"an a\"\n    assert p.an(\"b\") == \"a b\"\n    assert p.an(\"honest cat\") == \"an honest cat\"\n    assert p.an(\"dishonest cat\") == \"a dishonest cat\"\n    assert p.an(\"Honolulu sunset\") == \"a Honolulu sunset\"\n    assert p.an(\"mpeg\") == \"an mpeg\"\n    assert p.an(\"onetime holiday\") == \"a onetime holiday\"\n    assert p.an(\"Ugandan person\") == \"a Ugandan person\"\n    assert p.an(\"Ukrainian person\") == \"a Ukrainian person\"\n    assert p.an(\"Unabomber\") == \"a Unabomber\"\n    assert p.an(\"unanimous decision\") == \"a unanimous decision\"\n    assert p.an(\"US farmer\") == \"a US farmer\"\n    assert p.an(\"wild PIKACHU appeared\") == \"a wild PIKACHU appeared\"\n\n\ndef test_an_abbreviation():\n    p = inflect.engine()\n\n    assert p.an(\"YAML code block\") == \"a YAML code block\"\n    assert p.an(\"Core ML function\") == \"a Core ML function\"\n    assert p.an(\"JSON code block\") == \"a JSON code block\"\n"
  },
  {
    "path": "tests/test_classical_all.py",
    "content": "import inflect\n\n\nclass Test:\n    def test_classical(self):\n        p = inflect.engine()\n\n        # DEFAULT...\n\n        assert p.plural_noun(\"error\", 0) == \"errors\", \"classical 'zero' not active\"\n        assert p.plural_noun(\"wildebeest\") == \"wildebeests\", (\n            \"classical 'herd' not active\"\n        )\n        assert p.plural_noun(\"Sally\") == \"Sallys\", \"classical 'names' active\"\n        assert p.plural_noun(\"brother\") == \"brothers\", \"classical others not active\"\n        assert p.plural_noun(\"person\") == \"people\", \"classical 'persons' not active\"\n        assert p.plural_noun(\"formula\") == \"formulas\", \"classical 'ancient' not active\"\n\n        # CLASSICAL PLURALS ACTIVATED...\n\n        p.classical(all=True)\n        assert p.plural_noun(\"error\", 0) == \"error\", \"classical 'zero' active\"\n        assert p.plural_noun(\"wildebeest\") == \"wildebeest\", \"classical 'herd' active\"\n        assert p.plural_noun(\"Sally\") == \"Sallys\", \"classical 'names' active\"\n        assert p.plural_noun(\"brother\") == \"brethren\", \"classical others active\"\n        assert p.plural_noun(\"person\") == \"persons\", \"classical 'persons' active\"\n        assert p.plural_noun(\"formula\") == \"formulae\", \"classical 'ancient' active\"\n\n        # CLASSICAL PLURALS DEACTIVATED...\n\n        p.classical(all=False)\n        assert p.plural_noun(\"error\", 0) == \"errors\", \"classical 'zero' not active\"\n        assert p.plural_noun(\"wildebeest\") == \"wildebeests\", (\n            \"classical 'herd' not active\"\n        )\n        assert p.plural_noun(\"Sally\") == \"Sallies\", \"classical 'names' not active\"\n        assert p.plural_noun(\"brother\") == \"brothers\", \"classical others not active\"\n        assert p.plural_noun(\"person\") == \"people\", \"classical 'persons' not active\"\n        assert p.plural_noun(\"formula\") == \"formulas\", \"classical 'ancient' not active\"\n\n        # CLASSICAL PLURALS REREREACTIVATED...\n\n        p.classical()\n        assert p.plural_noun(\"error\", 0) == \"error\", \"classical 'zero' active\"\n        assert p.plural_noun(\"wildebeest\") == \"wildebeest\", \"classical 'herd' active\"\n        assert p.plural_noun(\"Sally\") == \"Sallys\", \"classical 'names' active\"\n        assert p.plural_noun(\"brother\") == \"brethren\", \"classical others active\"\n        assert p.plural_noun(\"person\") == \"persons\", \"classical 'persons' active\"\n        assert p.plural_noun(\"formula\") == \"formulae\", \"classical 'ancient' active\"\n"
  },
  {
    "path": "tests/test_classical_ancient.py",
    "content": "import inflect\n\n\ndef test_ancient_1():\n    p = inflect.engine()\n\n    # DEFAULT...\n\n    assert p.plural_noun(\"formula\") == \"formulas\"\n\n    # \"person\" PLURALS ACTIVATED...\n\n    p.classical(ancient=True)\n    assert p.plural_noun(\"formula\") == \"formulae\"\n\n    # OTHER CLASSICALS NOT ACTIVATED...\n\n    assert p.plural_noun(\"wildebeest\") == \"wildebeests\"\n    assert p.plural_noun(\"error\", 0) == \"errors\"\n    assert p.plural_noun(\"Sally\") == \"Sallys\"\n    assert p.plural_noun(\"brother\") == \"brothers\"\n    assert p.plural_noun(\"person\") == \"people\"\n"
  },
  {
    "path": "tests/test_classical_herd.py",
    "content": "import inflect\n\n\ndef test_ancient_1():\n    p = inflect.engine()\n\n    # DEFAULT...\n\n    assert p.plural_noun(\"wildebeest\") == \"wildebeests\"\n\n    # \"person\" PLURALS ACTIVATED...\n\n    p.classical(herd=True)\n    assert p.plural_noun(\"wildebeest\") == \"wildebeest\"\n\n    # OTHER CLASSICALS NOT ACTIVATED...\n\n    assert p.plural_noun(\"formula\") == \"formulas\"\n    assert p.plural_noun(\"error\", 0) == \"errors\"\n    assert p.plural_noun(\"Sally\") == \"Sallys\"\n    assert p.plural_noun(\"brother\") == \"brothers\"\n    assert p.plural_noun(\"person\") == \"people\"\n"
  },
  {
    "path": "tests/test_classical_names.py",
    "content": "import inflect\n\n\ndef test_ancient_1():\n    p = inflect.engine()\n\n    # DEFAULT...\n\n    assert p.plural_noun(\"Sally\") == \"Sallys\"\n    assert p.plural_noun(\"Jones\", 0) == \"Joneses\"\n\n    # \"person\" PLURALS ACTIVATED...\n\n    p.classical(names=True)\n    assert p.plural_noun(\"Sally\") == \"Sallys\"\n    assert p.plural_noun(\"Jones\", 0) == \"Joneses\"\n\n    # OTHER CLASSICALS NOT ACTIVATED...\n\n    assert p.plural_noun(\"wildebeest\") == \"wildebeests\"\n    assert p.plural_noun(\"formula\") == \"formulas\"\n    assert p.plural_noun(\"error\", 0) == \"errors\"\n    assert p.plural_noun(\"brother\") == \"brothers\"\n    assert p.plural_noun(\"person\") == \"people\"\n"
  },
  {
    "path": "tests/test_classical_person.py",
    "content": "import inflect\n\n\ndef test_ancient_1():\n    p = inflect.engine()\n\n    # DEFAULT...\n\n    assert p.plural_noun(\"person\") == \"people\"\n\n    # \"person\" PLURALS ACTIVATED...\n\n    p.classical(persons=True)\n    assert p.plural_noun(\"person\") == \"persons\"\n\n    # OTHER CLASSICALS NOT ACTIVATED...\n\n    assert p.plural_noun(\"wildebeest\") == \"wildebeests\"\n    assert p.plural_noun(\"formula\") == \"formulas\"\n    assert p.plural_noun(\"error\", 0) == \"errors\"\n    assert p.plural_noun(\"brother\") == \"brothers\"\n    assert p.plural_noun(\"Sally\") == \"Sallys\"\n    assert p.plural_noun(\"Jones\", 0) == \"Joneses\"\n"
  },
  {
    "path": "tests/test_classical_zero.py",
    "content": "import inflect\n\n\ndef test_ancient_1():\n    p = inflect.engine()\n\n    # DEFAULT...\n\n    assert p.plural_noun(\"error\", 0) == \"errors\"\n\n    # \"person\" PLURALS ACTIVATED...\n\n    p.classical(zero=True)\n    assert p.plural_noun(\"error\", 0) == \"error\"\n\n    # OTHER CLASSICALS NOT ACTIVATED...\n\n    assert p.plural_noun(\"wildebeest\") == \"wildebeests\"\n    assert p.plural_noun(\"formula\") == \"formulas\"\n    assert p.plural_noun(\"person\") == \"people\"\n    assert p.plural_noun(\"brother\") == \"brothers\"\n    assert p.plural_noun(\"Sally\") == \"Sallys\"\n"
  },
  {
    "path": "tests/test_compounds.py",
    "content": "import inflect\n\np = inflect.engine()\n\n\ndef test_compound_1():\n    assert p.singular_noun(\"hello-out-there\") == \"hello-out-there\"\n\n\ndef test_compound_2():\n    assert p.singular_noun(\"hello out there\") == \"hello out there\"\n\n\ndef test_compound_3():\n    assert p.singular_noun(\"continue-to-operate\") == \"continue-to-operate\"\n\n\ndef test_compound_4():\n    assert p.singular_noun(\"case of diapers\") == \"case of diapers\"\n\n\ndef test_unit_handling_degree():\n    test_cases = {\n        \"degree celsius\": \"degrees celsius\",\n        # 'degree Celsius': 'degrees Celsius',\n        \"degree fahrenheit\": \"degrees fahrenheit\",\n        \"degree rankine\": \"degrees rankine\",\n        \"degree fahrenheit second\": \"degree fahrenheit seconds\",\n    }\n    for singular, plural in test_cases.items():\n        assert p.plural(singular) == plural\n\n\ndef test_unit_handling_fractional():\n    test_cases = {\n        \"pound per square inch\": \"pounds per square inch\",\n        \"metre per second\": \"metres per second\",\n        \"kilometre per hour\": \"kilometres per hour\",\n        \"cubic metre per second\": \"cubic metres per second\",\n        \"dollar a year\": \"dollars a year\",\n        # Correct pluralization of denominator\n        \"foot per square second\": \"feet per square second\",\n        \"mother-in-law per lifetime\": \"mothers-in-law per lifetime\",\n        \"pound-force per square inch\": \"pounds-force per square inch\",\n    }\n    for singular, plural in test_cases.items():\n        assert p.plural(singular) == plural\n\n\ndef test_unit_handling_combined():\n    test_cases = {\n        # Heat transfer coefficient unit\n        \"watt per square meter degree celsius\": \"watts per square meter degree celsius\",\n        \"degree celsius per hour\": \"degrees celsius per hour\",\n        \"degree fahrenheit hour square foot per btuit inch\": (\n            \"degree fahrenheit hour square feet per btuit inch\"\n        ),\n        # 'degree Celsius per hour': 'degrees Celsius per hour',\n        # 'degree Fahrenheit hour square foot per BtuIT inch':\n        #   'degree Fahrenheit hour square feet per BtuIT inch'\n    }\n    for singular, plural in test_cases.items():\n        assert p.plural(singular) == plural\n\n\ndef test_unit_open_compound_nouns():\n    test_cases = {\n        \"high school\": \"high schools\",\n        \"master genie\": \"master genies\",\n        \"MASTER genie\": \"MASTER genies\",\n        \"Blood brother\": \"Blood brothers\",\n        \"prima donna\": \"prima donnas\",\n        \"prima DONNA\": \"prima DONNAS\",\n    }\n    for singular, plural in test_cases.items():\n        assert p.plural(singular) == plural\n\n\ndef test_unit_open_compound_nouns_classical():\n    p.classical(all=True)\n    test_cases = {\n        \"master genie\": \"master genii\",\n        \"MASTER genie\": \"MASTER genii\",\n        \"Blood brother\": \"Blood brethren\",\n        \"prima donna\": \"prime donne\",\n        \"prima DONNA\": \"prime DONNE\",\n    }\n    for singular, plural in test_cases.items():\n        assert p.plural(singular) == plural\n    p.classical(all=False)\n"
  },
  {
    "path": "tests/test_inflections.py",
    "content": "import os\n\nimport pytest\n\nimport inflect\n\n\ndef is_eq(p, a, b):\n    return (\n        p.compare(a, b)\n        or p.plnounequal(a, b)\n        or p.plverbequal(a, b)\n        or p.pladjequal(a, b)\n    )\n\n\ndef test_many():  # noqa: C901\n    p = inflect.engine()\n\n    data = get_data()\n\n    for line in data:\n        if \"TODO:\" in line:\n            continue\n        try:\n            singular, rest = line.split(\"->\", 1)\n        except ValueError:\n            continue\n        singular = singular.strip()\n        rest = rest.strip()\n        try:\n            plural, comment = rest.split(\"#\", 1)\n        except ValueError:\n            plural = rest.strip()\n            comment = \"\"\n        try:\n            mod_plural, class_plural = plural.split(\"|\", 1)\n            mod_plural = mod_plural.strip()\n            class_plural = class_plural.strip()\n        except ValueError:\n            mod_plural = class_plural = plural.strip()\n        if \"verb\" in comment.lower():\n            is_nv = \"_V\"\n        elif \"noun\" in comment.lower():\n            is_nv = \"_N\"\n        else:\n            is_nv = \"\"\n\n        p.classical(all=0, names=0)\n        mod_PL_V = p.plural_verb(singular)\n        mod_PL_N = p.plural_noun(singular)\n        mod_PL = p.plural(singular)\n        if is_nv == \"_V\":\n            mod_PL_val = mod_PL_V\n        elif is_nv == \"_N\":\n            mod_PL_val = mod_PL_N\n        else:\n            mod_PL_val = mod_PL\n\n        p.classical(all=1)\n        class_PL_V = p.plural_verb(singular)\n        class_PL_N = p.plural_noun(singular)\n        class_PL = p.plural(singular)\n        if is_nv == \"_V\":\n            class_PL_val = class_PL_V\n        elif is_nv == \"_N\":\n            class_PL_val = class_PL_N\n        else:\n            class_PL_val = class_PL\n\n        check_all(\n            p, is_nv, singular, mod_PL_val, class_PL_val, mod_plural, class_plural\n        )\n\n\ndef check_all(p, is_nv, singular, mod_PL_val, class_PL_val, mod_plural, class_plural):\n    assert mod_plural == mod_PL_val\n    assert class_plural == class_PL_val\n    assert is_eq(p, singular, mod_plural) in (\"s:p\", \"p:s\", \"eq\")\n    assert is_eq(p, mod_plural, singular) in (\"p:s\", \"s:p\", \"eq\")\n    assert is_eq(p, singular, class_plural) in (\"s:p\", \"p:s\", \"eq\")\n    assert is_eq(p, class_plural, singular) in (\"p:s\", \"s:p\", \"eq\")\n    assert singular != \"\"\n    expected = mod_PL_val if class_PL_val else f\"{mod_PL_val}|{class_PL_val}\"\n    assert mod_PL_val == expected\n\n    if is_nv != \"_V\":\n        assert p.singular_noun(mod_plural, 1) == singular\n\n        assert p.singular_noun(class_plural, 1) == singular\n\n\ndef test_def():\n    p = inflect.engine()\n\n    p.defnoun(\"kin\", \"kine\")\n    p.defnoun(\"(.*)x\", \"$1xen\")\n\n    p.defverb(\"foobar\", \"feebar\", \"foobar\", \"feebar\", \"foobars\", \"feebar\")\n\n    p.defadj(\"red\", \"red|gules\")\n\n    assert p.no(\"kin\", 0) == \"no kine\"\n    assert p.no(\"kin\", 1) == \"1 kin\"\n    assert p.no(\"kin\", 2) == \"2 kine\"\n\n    assert p.no(\"regex\", 0) == \"no regexen\"\n\n    assert p.plural(\"foobar\", 2) == \"feebar\"\n    assert p.plural(\"foobars\", 2) == \"feebar\"\n\n    assert p.plural(\"red\", 0) == \"red\"\n    assert p.plural(\"red\", 1) == \"red\"\n    assert p.plural(\"red\", 2) == \"red\"\n    p.classical(all=True)\n    assert p.plural(\"red\", 0) == \"red\"\n    assert p.plural(\"red\", 1) == \"red\"\n    assert p.plural(\"red\", 2) == \"gules\"\n\n\ndef test_ordinal():\n    p = inflect.engine()\n    assert p.ordinal(0) == \"0th\"\n    assert p.ordinal(1) == p.ordinal(\"1\") == \"1st\"\n    assert p.ordinal(2) == \"2nd\"\n    assert p.ordinal(3) == \"3rd\"\n    assert p.ordinal(4) == \"4th\"\n    assert p.ordinal(5) == \"5th\"\n    assert p.ordinal(6) == \"6th\"\n    assert p.ordinal(7) == \"7th\"\n    assert p.ordinal(8) == \"8th\"\n    assert p.ordinal(9) == \"9th\"\n    assert p.ordinal(10) == \"10th\"\n    assert p.ordinal(11) == \"11th\"\n    assert p.ordinal(12) == \"12th\"\n    assert p.ordinal(13) == \"13th\"\n    assert p.ordinal(14) == \"14th\"\n    assert p.ordinal(15) == \"15th\"\n    assert p.ordinal(16) == \"16th\"\n    assert p.ordinal(17) == \"17th\"\n    assert p.ordinal(18) == \"18th\"\n    assert p.ordinal(19) == \"19th\"\n    assert p.ordinal(20) == \"20th\"\n    assert p.ordinal(21) == \"21st\"\n    assert p.ordinal(22) == \"22nd\"\n    assert p.ordinal(23) == \"23rd\"\n    assert p.ordinal(24) == \"24th\"\n    assert p.ordinal(100) == \"100th\"\n    assert p.ordinal(101) == \"101st\"\n    assert p.ordinal(102) == \"102nd\"\n    assert p.ordinal(103) == \"103rd\"\n    assert p.ordinal(104) == \"104th\"\n\n    assert p.ordinal(1.1) == p.ordinal(\"1.1\") == \"1.1st\"\n    assert p.ordinal(1.2) == \"1.2nd\"\n    assert p.ordinal(5.502) == \"5.502nd\"\n\n    assert p.ordinal(\"zero\") == \"zeroth\"\n    assert p.ordinal(\"one\") == \"first\"\n    assert p.ordinal(\"two\") == \"second\"\n    assert p.ordinal(\"three\") == \"third\"\n    assert p.ordinal(\"four\") == \"fourth\"\n    assert p.ordinal(\"five\") == \"fifth\"\n    assert p.ordinal(\"six\") == \"sixth\"\n    assert p.ordinal(\"seven\") == \"seventh\"\n    assert p.ordinal(\"eight\") == \"eighth\"\n    assert p.ordinal(\"nine\") == \"ninth\"\n    assert p.ordinal(\"ten\") == \"tenth\"\n    assert p.ordinal(\"eleven\") == \"eleventh\"\n    assert p.ordinal(\"twelve\") == \"twelfth\"\n    assert p.ordinal(\"thirteen\") == \"thirteenth\"\n    assert p.ordinal(\"fourteen\") == \"fourteenth\"\n    assert p.ordinal(\"fifteen\") == \"fifteenth\"\n    assert p.ordinal(\"sixteen\") == \"sixteenth\"\n    assert p.ordinal(\"seventeen\") == \"seventeenth\"\n    assert p.ordinal(\"eighteen\") == \"eighteenth\"\n    assert p.ordinal(\"nineteen\") == \"nineteenth\"\n    assert p.ordinal(\"twenty\") == \"twentieth\"\n    assert p.ordinal(\"twenty-one\") == \"twenty-first\"\n    assert p.ordinal(\"twenty-two\") == \"twenty-second\"\n    assert p.ordinal(\"twenty-three\") == \"twenty-third\"\n    assert p.ordinal(\"twenty-four\") == \"twenty-fourth\"\n    assert p.ordinal(\"one hundred\") == \"one hundredth\"\n    assert p.ordinal(\"one hundred and one\") == \"one hundred and first\"\n    assert p.ordinal(\"one hundred and two\") == \"one hundred and second\"\n    assert p.ordinal(\"one hundred and three\") == \"one hundred and third\"\n    assert p.ordinal(\"one hundred and four\") == \"one hundred and fourth\"\n\n\ndef test_decimal_ordinals():\n    \"\"\"\n    Capture expectation around ordinals for decimals.\n\n    This expectation is held loosely. Another expectation may be\n    considered if appropriate.\n    \"\"\"\n\n    p = inflect.engine()\n    assert p.ordinal(\"1.23\") == \"1.23rd\"\n    assert p.ordinal(\"7.09\") == \"7.09th\"\n\n\ndef test_prespart():\n    p = inflect.engine()\n    assert p.present_participle(\"sees\") == \"seeing\"\n    assert p.present_participle(\"eats\") == \"eating\"\n    assert p.present_participle(\"bats\") == \"batting\"\n    assert p.present_participle(\"hates\") == \"hating\"\n    assert p.present_participle(\"spies\") == \"spying\"\n    assert p.present_participle(\"skis\") == \"skiing\"\n\n\ndef test_inflect_on_tuples():\n    p = inflect.engine()\n    assert p.inflect(\"plural('egg', ('a', 'b', 'c'))\") == \"eggs\"\n    assert p.inflect(\"plural('egg', ['a', 'b', 'c'])\") == \"eggs\"\n    assert p.inflect(\"plural_noun('egg', ('a', 'b', 'c'))\") == \"eggs\"\n    assert p.inflect(\"plural_adj('a', ('a', 'b', 'c'))\") == \"some\"\n    assert p.inflect(\"plural_verb('was', ('a', 'b', 'c'))\") == \"were\"\n    assert p.inflect(\"singular_noun('eggs', ('a', 'b', 'c'))\") == \"eggs\"\n    assert p.inflect(\"an('error', ('a', 'b', 'c'))\") == \"('a', 'b', 'c') error\"\n    assert p.inflect(\"This is not a function(name)\") == \"This is not a function(name)\"\n\n\ndef test_inflect_on_builtin_constants():\n    p = inflect.engine()\n    assert (\n        p.inflect(\"Plural of False is plural('False')\") == \"Plural of False is Falses\"\n    )\n    assert p.inflect(\"num(%d, False) plural('False')\" % 10) == \" Falses\"\n\n    assert p.inflect(\"plural('True')\") == \"Trues\"\n    assert p.inflect(\"num(%d, True) plural('False')\" % 10) == \"10 Falses\"\n    assert p.inflect(\"num(%d, %r) plural('False')\" % (10, True)) == \"10 Falses\"\n\n    assert p.inflect(\"plural('None')\") == \"Nones\"\n    assert p.inflect(\"num(%d, %r) plural('True')\" % (10, None)) == \"10 Trues\"\n\n\ndef test_inflect_keyword_args():\n    p = inflect.engine()\n    assert (\n        p.inflect(\"number_to_words(1234, andword='')\")\n        == \"one thousand, two hundred thirty-four\"\n    )\n\n    assert (\n        p.inflect(\"number_to_words(1234, andword='plus')\")\n        == \"one thousand, two hundred plus thirty-four\"\n    )\n\n    assert (\n        p.inflect(\"number_to_words('555_1202', group=1, zero='oh')\")\n        == \"five, five, five, one, two, oh, two\"\n    )\n\n\ndef test_NameError_in_strings():\n    with pytest.raises(NameError):\n        p = inflect.engine()\n        assert p.inflect(\"plural('two')\") == \"twoes\"\n        p.inflect(\"plural(two)\")\n\n\ndef get_data():\n    filename = os.path.join(os.path.dirname(__file__), \"inflections.txt\")\n    with open(filename, encoding='utf-8') as strm:\n        return list(map(str.strip, strm))\n"
  },
  {
    "path": "tests/test_join.py",
    "content": "import inflect\n\n\ndef test_join():\n    p = inflect.engine()\n\n    # Three words...\n    words = \"apple banana carrot\".split()\n\n    assert p.join(words), \"apple, banana == and carrot\"\n\n    assert p.join(words, final_sep=\"\") == \"apple, banana and carrot\"\n\n    assert p.join(words, final_sep=\"...\") == \"apple, banana... and carrot\"\n\n    assert p.join(words, final_sep=\"...\", conj=\"\") == \"apple, banana... carrot\"\n\n    assert p.join(words, conj=\"or\") == \"apple, banana, or carrot\"\n\n    # Three words with semicolons...\n    words = (\"apple,fuji\", \"banana\", \"carrot\")\n\n    assert p.join(words) == \"apple,fuji; banana; and carrot\"\n\n    assert p.join(words, final_sep=\"\") == \"apple,fuji; banana and carrot\"\n\n    assert p.join(words, final_sep=\"...\") == \"apple,fuji; banana... and carrot\"\n\n    assert p.join(words, final_sep=\"...\", conj=\"\") == \"apple,fuji; banana... carrot\"\n\n    assert p.join(words, conj=\"or\") == \"apple,fuji; banana; or carrot\"\n\n    # Two words...\n    words = (\"apple\", \"carrot\")\n\n    assert p.join(words) == \"apple and carrot\"\n\n    assert p.join(words, final_sep=\"\") == \"apple and carrot\"\n\n    assert p.join(words, final_sep=\"...\") == \"apple and carrot\"\n\n    assert p.join(words, final_sep=\"...\", conj=\"\") == \"apple carrot\"\n\n    assert p.join(words, final_sep=\"...\", conj=\"\", conj_spaced=False) == \"applecarrot\"\n\n    assert p.join(words, conj=\"or\") == \"apple or carrot\"\n\n    # One word...\n    words = [\"carrot\"]\n\n    assert p.join(words) == \"carrot\"\n\n    assert p.join(words, final_sep=\"\") == \"carrot\"\n\n    assert p.join(words, final_sep=\"...\") == \"carrot\"\n\n    assert p.join(words, final_sep=\"...\", conj=\"\") == \"carrot\"\n\n    assert p.join(words, conj=\"or\") == \"carrot\"\n"
  },
  {
    "path": "tests/test_numwords.py",
    "content": "import inflect\n\n\ndef test_loop():\n    p = inflect.engine()\n\n    for thresh in range(21):\n        for n in range(21):\n            threshed = p.number_to_words(n, threshold=thresh)\n            numwords = p.number_to_words(n)\n\n            if n <= thresh:\n                assert numwords == threshed\n            else:\n                # $threshed =~ s/\\D//gxms;\n                assert threshed == str(n)\n\n\ndef test_lines():\n    p = inflect.engine()\n    assert p.number_to_words(999, threshold=500) == \"999\"\n    assert p.number_to_words(1000, threshold=500) == \"1,000\"\n    assert p.number_to_words(10000, threshold=500) == \"10,000\"\n    assert p.number_to_words(100000, threshold=500) == \"100,000\"\n    assert p.number_to_words(1000000, threshold=500) == \"1,000,000\"\n\n    assert p.number_to_words(999.3, threshold=500) == \"999.3\"\n    assert p.number_to_words(1000.3, threshold=500) == \"1,000.3\"\n    assert p.number_to_words(10000.3, threshold=500) == \"10,000.3\"\n\n    assert p.number_to_words(100000.3, threshold=500) == \"100,000.3\"\n    assert p.number_to_words(1000000.3, threshold=500) == \"1,000,000.3\"\n\n    assert p.number_to_words(999, threshold=500, comma=0) == \"999\"\n    assert p.number_to_words(1000, threshold=500, comma=0) == \"1000\"\n    assert p.number_to_words(10000, threshold=500, comma=0) == \"10000\"\n    assert p.number_to_words(100000, threshold=500, comma=0) == \"100000\"\n    assert p.number_to_words(1000000, threshold=500, comma=0) == \"1000000\"\n\n    assert p.number_to_words(999.3, threshold=500, comma=0) == \"999.3\"\n    assert p.number_to_words(1000.3, threshold=500, comma=0) == \"1000.3\"\n    assert p.number_to_words(10000.3, threshold=500, comma=0) == \"10000.3\"\n    assert p.number_to_words(100000.3, threshold=500, comma=0) == \"100000.3\"\n    assert p.number_to_words(1000000.3, threshold=500, comma=0) == \"1000000.3\"\n\n\ndef test_array():\n    nw = [\n        [\"0\", \"zero\", \"zero\", \"zero\", \"zero\", \"zeroth\"],\n        [\"1\", \"one\", \"one\", \"one\", \"one\", \"first\"],\n        [\"2\", \"two\", \"two\", \"two\", \"two\", \"second\"],\n        [\"3\", \"three\", \"three\", \"three\", \"three\", \"third\"],\n        [\"4\", \"four\", \"four\", \"four\", \"four\", \"fourth\"],\n        [\"5\", \"five\", \"five\", \"five\", \"five\", \"fifth\"],\n        [\"6\", \"six\", \"six\", \"six\", \"six\", \"sixth\"],\n        [\"7\", \"seven\", \"seven\", \"seven\", \"seven\", \"seventh\"],\n        [\"8\", \"eight\", \"eight\", \"eight\", \"eight\", \"eighth\"],\n        [\"9\", \"nine\", \"nine\", \"nine\", \"nine\", \"ninth\"],\n        [\"10\", \"ten\", \"one, zero\", \"ten\", \"ten\", \"tenth\"],\n        [\"11\", \"eleven\", \"one, one\", \"eleven\", \"eleven\", \"eleventh\"],\n        [\"12\", \"twelve\", \"one, two\", \"twelve\", \"twelve\", \"twelfth\"],\n        [\"13\", \"thirteen\", \"one, three\", \"thirteen\", \"thirteen\", \"thirteenth\"],\n        [\"14\", \"fourteen\", \"one, four\", \"fourteen\", \"fourteen\", \"fourteenth\"],\n        [\"15\", \"fifteen\", \"one, five\", \"fifteen\", \"fifteen\", \"fifteenth\"],\n        [\"16\", \"sixteen\", \"one, six\", \"sixteen\", \"sixteen\", \"sixteenth\"],\n        [\"17\", \"seventeen\", \"one, seven\", \"seventeen\", \"seventeen\", \"seventeenth\"],\n        [\"18\", \"eighteen\", \"one, eight\", \"eighteen\", \"eighteen\", \"eighteenth\"],\n        [\"19\", \"nineteen\", \"one, nine\", \"nineteen\", \"nineteen\", \"nineteenth\"],\n        [\"20\", \"twenty\", \"two, zero\", \"twenty\", \"twenty\", \"twentieth\"],\n        [\"21\", \"twenty-one\", \"two, one\", \"twenty-one\", \"twenty-one\", \"twenty-first\"],\n        [\n            \"29\",\n            \"twenty-nine\",\n            \"two, nine\",\n            \"twenty-nine\",\n            \"twenty-nine\",\n            \"twenty-ninth\",\n        ],\n        [\n            \"99\",\n            \"ninety-nine\",\n            \"nine, nine\",\n            \"ninety-nine\",\n            \"ninety-nine\",\n            \"ninety-ninth\",\n        ],\n        [\n            \"100\",\n            \"one hundred\",\n            \"one, zero, zero\",\n            \"ten, zero\",\n            \"one zero zero\",\n            \"one hundredth\",\n        ],\n        [\n            \"101\",\n            \"one hundred and one\",\n            \"one, zero, one\",\n            \"ten, one\",\n            \"one zero one\",\n            \"one hundred and first\",\n        ],\n        [\n            \"110\",\n            \"one hundred and ten\",\n            \"one, one, zero\",\n            \"eleven, zero\",\n            \"one ten\",\n            \"one hundred and tenth\",\n        ],\n        [\n            \"111\",\n            \"one hundred and eleven\",\n            \"one, one, one\",\n            \"eleven, one\",\n            \"one eleven\",\n            \"one hundred and eleventh\",\n        ],\n        [\n            \"900\",\n            \"nine hundred\",\n            \"nine, zero, zero\",\n            \"ninety, zero\",\n            \"nine zero zero\",\n            \"nine hundredth\",\n        ],\n        [\n            \"999\",\n            \"nine hundred and ninety-nine\",\n            \"nine, nine, nine\",\n            \"ninety-nine, nine\",\n            \"nine ninety-nine\",\n            \"nine hundred and ninety-ninth\",\n        ],\n        [\n            \"1000\",\n            \"one thousand\",\n            \"one, zero, zero, zero\",\n            \"ten, zero zero\",\n            \"one zero zero, zero\",\n            \"one thousandth\",\n        ],\n        [\n            \"1001\",\n            \"one thousand and one\",\n            \"one, zero, zero, one\",\n            \"ten, zero one\",\n            \"one zero zero, one\",\n            \"one thousand and first\",\n        ],\n        [\n            \"1010\",\n            \"one thousand and ten\",\n            \"one, zero, one, zero\",\n            \"ten, ten\",\n            \"one zero one, zero\",\n            \"one thousand and tenth\",\n        ],\n        [\n            \"1100\",\n            \"one thousand, one hundred\",\n            \"one, one, zero, zero\",\n            \"eleven, zero zero\",\n            \"one ten, zero\",\n            \"one thousand, one hundredth\",\n        ],\n        [\n            \"2000\",\n            \"two thousand\",\n            \"two, zero, zero, zero\",\n            \"twenty, zero zero\",\n            \"two zero zero, zero\",\n            \"two thousandth\",\n        ],\n        [\n            \"10000\",\n            \"ten thousand\",\n            \"one, zero, zero, zero, zero\",\n            \"ten, zero zero, zero\",\n            \"one zero zero, zero zero\",\n            \"ten thousandth\",\n        ],\n        [\n            \"100000\",\n            \"one hundred thousand\",\n            \"one, zero, zero, zero, zero, zero\",\n            \"ten, zero zero, zero zero\",\n            \"one zero zero, zero zero zero\",\n            \"one hundred thousandth\",\n        ],\n        [\n            \"100001\",\n            \"one hundred thousand and one\",\n            \"one, zero, zero, zero, zero, one\",\n            \"ten, zero zero, zero one\",\n            \"one zero zero, zero zero one\",\n            \"one hundred thousand and first\",\n        ],\n        [\n            \"123456\",\n            \"one hundred and twenty-three thousand, four hundred and fifty-six\",\n            \"one, two, three, four, five, six\",\n            \"twelve, thirty-four, fifty-six\",\n            \"one twenty-three, four fifty-six\",\n            \"one hundred and twenty-three thousand, four hundred and fifty-sixth\",\n        ],\n        [\n            \"0123456\",\n            \"one hundred and twenty-three thousand, four hundred and fifty-six\",\n            \"zero, one, two, three, four, five, six\",\n            \"zero one, twenty-three, forty-five, six\",\n            \"zero twelve, three forty-five, six\",\n            \"one hundred and twenty-three thousand, four hundred and fifty-sixth\",\n        ],\n        [\n            \"1234567\",\n            \"one million, two hundred and thirty-four thousand, \"\n            \"five hundred and sixty-seven\",\n            \"one, two, three, four, five, six, seven\",\n            \"twelve, thirty-four, fifty-six, seven\",\n            \"one twenty-three, four fifty-six, seven\",\n            \"one million, two hundred and thirty-four thousand, \"\n            \"five hundred and sixty-seventh\",\n        ],\n        [\n            \"12345678\",\n            \"twelve million, three hundred and forty-five thousand, \"\n            \"six hundred and seventy-eight\",\n            \"one, two, three, four, five, six, seven, eight\",\n            \"twelve, thirty-four, fifty-six, seventy-eight\",\n            \"one twenty-three, four fifty-six, seventy-eight\",\n            \"twelve million, three hundred and forty-five thousand, \"\n            \"six hundred and seventy-eighth\",\n        ],\n        [\n            \"12_345_678\",\n            \"twelve million, three hundred and forty-five thousand, \"\n            \"six hundred and seventy-eight\",\n            \"one, two, three, four, five, six, seven, eight\",\n            \"twelve, thirty-four, fifty-six, seventy-eight\",\n            \"one twenty-three, four fifty-six, seventy-eight\",\n        ],\n        [\n            \"1234,5678\",\n            \"twelve million, three hundred and forty-five thousand, \"\n            \"six hundred and seventy-eight\",\n            \"one, two, three, four, five, six, seven, eight\",\n            \"twelve, thirty-four, fifty-six, seventy-eight\",\n            \"one twenty-three, four fifty-six, seventy-eight\",\n        ],\n        [\n            \"1234567890\",\n            \"one billion, two hundred and thirty-four million, five hundred \"\n            \"and sixty-seven thousand, eight hundred and ninety\",\n            \"one, two, three, four, five, six, seven, eight, nine, zero\",\n            \"twelve, thirty-four, fifty-six, seventy-eight, ninety\",\n            \"one twenty-three, four fifty-six, seven eighty-nine, zero\",\n            \"one billion, two hundred and thirty-four million, five hundred \"\n            \"and sixty-seven thousand, eight hundred and ninetieth\",\n        ],\n        [\n            \"123456789012345\",\n            \"one hundred and twenty-three trillion, four hundred and \"\n            \"fifty-six billion, seven hundred and eighty-nine million, twelve \"\n            \"thousand, three hundred and forty-five\",\n            \"one, two, three, four, five, six, seven, eight, nine, zero, one, \"\n            \"two, three, four, five\",\n            \"twelve, thirty-four, fifty-six, seventy-eight, ninety, twelve, \"\n            \"thirty-four, five\",\n            \"one twenty-three, four fifty-six, seven eighty-nine, \"\n            \"zero twelve, three forty-five\",\n            \"one hundred and twenty-three trillion, four hundred and \"\n            \"fifty-six billion, seven hundred and eighty-nine million, \"\n            \"twelve thousand, three hundred and forty-fifth\",\n        ],\n        [\n            \"12345678901234567890\",\n            \"twelve quintillion, three hundred and forty-five quadrillion, \"\n            \"six hundred and seventy-eight trillion, nine hundred and one \"\n            \"billion, two hundred and thirty-four million, five hundred and \"\n            \"sixty-seven thousand, eight hundred and ninety\",\n            \"one, two, three, four, five, six, seven, eight, nine, zero, one, \"\n            \"two, three, four, five, six, seven, eight, nine, zero\",\n            \"twelve, thirty-four, fifty-six, seventy-eight, ninety, twelve, \"\n            \"thirty-four, fifty-six, seventy-eight, ninety\",\n            \"one twenty-three, four fifty-six, seven eighty-nine, \"\n            \"zero twelve, three forty-five, six seventy-eight, ninety\",\n            \"twelve quintillion, three hundred and forty-five quadrillion, \"\n            \"six hundred and seventy-eight trillion, nine hundred and one \"\n            \"billion, two hundred and thirty-four million, five hundred and \"\n            \"sixty-seven thousand, eight hundred and ninetieth\",\n        ],\n        [\n            \"0.987654\",\n            \"zero point nine eight seven six five four\",\n            \"zero, point, nine, eight, seven, six, five, four\",\n            \"zero, point, ninety-eight, seventy-six, fifty-four\",\n            \"zero, point, nine eighty-seven, six fifty-four\",\n            \"zeroth point nine eight seven six five four\",\n            \"zero point nine eight seven six five fourth\",\n        ],\n        [\n            \".987654\",\n            \"point nine eight seven six five four\",\n            \"point, nine, eight, seven, six, five, four\",\n            \"point, ninety-eight, seventy-six, fifty-four\",\n            \"point, nine eighty-seven, six fifty-four\",\n            \"point nine eight seven six five four\",\n            \"point nine eight seven six five fourth\",\n        ],\n        [\n            \"9.87654\",\n            \"nine point eight seven six five four\",\n            \"nine, point, eight, seven, six, five, four\",\n            \"nine, point, eighty-seven, sixty-five, four\",\n            \"nine, point, eight seventy-six, fifty-four\",\n            \"ninth point eight seven six five four\",\n            \"nine point eight seven six five fourth\",\n        ],\n        [\n            \"98.7654\",\n            \"ninety-eight point seven six five four\",\n            \"nine, eight, point, seven, six, five, four\",\n            \"ninety-eight, point, seventy-six, fifty-four\",\n            \"ninety-eight, point, seven sixty-five, four\",\n            \"ninety-eighth point seven six five four\",\n            \"ninety-eight point seven six five fourth\",\n        ],\n        [\n            \"987.654\",\n            \"nine hundred and eighty-seven point six five four\",\n            \"nine, eight, seven, point, six, five, four\",\n            \"ninety-eight, seven, point, sixty-five, four\",\n            \"nine eighty-seven, point, six fifty-four\",\n            \"nine hundred and eighty-seventh point six five four\",\n            \"nine hundred and eighty-seven point six five fourth\",\n        ],\n        [\n            \"9876.54\",\n            \"nine thousand, eight hundred and seventy-six point five four\",\n            \"nine, eight, seven, six, point, five, four\",\n            \"ninety-eight, seventy-six, point, fifty-four\",\n            \"nine eighty-seven, six, point, fifty-four\",\n            \"nine thousand, eight hundred and seventy-sixth point five four\",\n            \"nine thousand, eight hundred and seventy-six point five fourth\",\n        ],\n        [\n            \"98765.4\",\n            \"ninety-eight thousand, seven hundred and sixty-five point four\",\n            \"nine, eight, seven, six, five, point, four\",\n            \"ninety-eight, seventy-six, five, point, four\",\n            \"nine eighty-seven, sixty-five, point, four\",\n            \"ninety-eight thousand, seven hundred and sixty-fifth point four\",\n            \"ninety-eight thousand, seven hundred and sixty-five point fourth\",\n        ],\n        [\n            \"101.202.303\",\n            \"one hundred and one point two zero two three zero three\",\n            \"one, zero, one, point, two, zero, two, point, three, zero, three\",\n            \"ten, one, point, twenty, two, point, thirty, three\",\n            \"one zero one, point, two zero two, point, three zero three\",\n        ],\n        [\n            \"98765.\",\n            \"ninety-eight thousand, seven hundred and sixty-five point\",\n            \"nine, eight, seven, six, five, point\",\n            \"ninety-eight, seventy-six, five, point\",\n            \"nine eighty-seven, sixty-five, point\",\n        ],\n    ]\n\n    p = inflect.engine()\n\n    for i in nw:\n        go(p, i)\n\n\ndef go(p, i):\n    assert p.number_to_words(i[0]) == i[1]\n    assert p.number_to_words(i[0], group=1) == i[2]\n    assert p.number_to_words(i[0], group=2) == i[3]\n    assert p.number_to_words(i[0], group=3) == i[4]\n    if len(i) > 5:\n        assert p.number_to_words(p.ordinal(i[0])) == i[5]\n    if len(i) > 6:\n        assert p.ordinal(p.number_to_words(i[0])) == i[6]\n    else:\n        if len(i) > 5:\n            assert p.ordinal(p.number_to_words(i[0])) == i[5]\n\n    # eq_ !eval { p.number_to_words(42, and=>); 1; };\n    # eq_ $@ =~ 'odd number of';\n\n\ndef test_issue_131():\n    p = inflect.engine()\n    for nth_word in inflect.nth_suff:\n        assert p.number_to_words(nth_word) == \"zero\"\n"
  },
  {
    "path": "tests/test_pl_si.py",
    "content": "import pytest\n\nimport inflect\n\n\n@pytest.fixture(params=[False, True], ids=['classical off', 'classical on'])\ndef classical(request):\n    return request.param\n\n\n@pytest.mark.parametrize(\"word\", ['Times', 'Jones'])\ndef test_pl_si(classical, word):\n    p = inflect.engine()\n    p.classical(all=classical)\n    assert p.singular_noun(p.plural_noun(word, 2), 1) == word\n"
  },
  {
    "path": "tests/test_pwd.py",
    "content": "import pytest\nfrom typeguard import TypeCheckError\n\nimport inflect\nfrom inflect import (\n    BadChunkingOptionError,\n    BadGenderError,\n    BadNumValueError,\n    NumOutOfRangeError,\n    UnknownClassicalModeError,\n)\n\nmissing = object()\n\n\nclass Test:\n    def test_enclose(self):\n        # def enclose\n        assert inflect.enclose(\"test\") == \"(?:test)\"\n\n    def test_joinstem(self):\n        # def joinstem\n        assert (\n            inflect.joinstem(-2, [\"ephemeris\", \"iris\", \".*itis\"])\n            == \"(?:ephemer|ir|.*it)\"\n        )\n\n    def test_classical(self):\n        # classical dicts\n        assert set(inflect.def_classical.keys()) == set(inflect.all_classical.keys())\n        assert set(inflect.def_classical.keys()) == set(inflect.no_classical.keys())\n\n        # def classical\n        p = inflect.engine()\n        assert p.classical_dict == inflect.def_classical\n\n        p.classical()\n        assert p.classical_dict == inflect.all_classical\n\n        with pytest.raises(TypeError):\n            p.classical(0)\n        with pytest.raises(TypeError):\n            p.classical(1)\n        with pytest.raises(TypeError):\n            p.classical(\"names\")\n        with pytest.raises(TypeError):\n            p.classical(\"names\", \"zero\")\n        with pytest.raises(TypeError):\n            p.classical(\"all\")\n\n        p.classical(all=False)\n        assert p.classical_dict == inflect.no_classical\n\n        p.classical(names=True, zero=True)\n        mydict = inflect.def_classical.copy()\n        mydict.update(dict(names=1, zero=1))\n        assert p.classical_dict == mydict\n\n        p.classical(all=True)\n        assert p.classical_dict == inflect.all_classical\n\n        p.classical(all=False)\n        p.classical(names=True, zero=True)\n        mydict = inflect.def_classical.copy()\n        mydict.update(dict(names=True, zero=True))\n        assert p.classical_dict == mydict\n\n        p.classical(all=False)\n        p.classical(names=True, zero=False)\n        mydict = inflect.def_classical.copy()\n        mydict.update(dict(names=True, zero=False))\n        assert p.classical_dict == mydict\n\n        with pytest.raises(UnknownClassicalModeError):\n            p.classical(bogus=True)\n\n    def test_num(self):\n        # def num\n        p = inflect.engine()\n        assert p.persistent_count is None\n\n        p.num()\n        assert p.persistent_count is None\n\n        ret = p.num(3)\n        assert p.persistent_count == 3\n        assert ret == \"3\"\n\n        p.num()\n        ret = p.num(\"3\")\n        assert p.persistent_count == 3\n        assert ret == \"3\"\n\n        p.num()\n        ret = p.num(count=3, show=1)\n        assert p.persistent_count == 3\n        assert ret == \"3\"\n\n        p.num()\n        ret = p.num(count=3, show=0)\n        assert p.persistent_count == 3\n        assert ret == \"\"\n\n        with pytest.raises(BadNumValueError):\n            p.num(\"text\")\n\n    def test_inflect(self):\n        p = inflect.engine()\n        for txt, ans in (\n            (\"num(1)\", \"1\"),\n            (\"num(1,0)\", \"\"),\n            (\"num(1,1)\", \"1\"),\n            (\"num(1)   \", \"1   \"),\n            (\"   num(1)   \", \"   1   \"),\n            (\"num(3) num(1)\", \"3 1\"),\n        ):\n            assert p.inflect(txt) == ans, f'p.inflect(\"{txt}\") != \"{ans}\"'\n\n        for txt, ans in (\n            (\"plural('rock')\", \"rocks\"),\n            (\"plural('rock')  plural('child')\", \"rocks  children\"),\n            (\"num(2) plural('rock')  plural('child')\", \"2 rocks  children\"),\n            (\n                \"plural('rock') plural_noun('rock') plural_verb('rocks') \"\n                \"plural_adj('big') a('ant')\",\n                \"rocks rocks rock big an ant\",\n            ),\n            (\n                \"an('rock') no('cat') ordinal(3) number_to_words(1234) \"\n                \"present_participle('runs')\",\n                \"a rock no cats 3rd one thousand, two hundred and thirty-four running\",\n            ),\n            (\"a('cat',0) a('cat',1) a('cat',2) a('cat', 2)\", \"0 cat a cat 2 cat 2 cat\"),\n        ):\n            assert p.inflect(txt) == ans, f'p.inflect(\"{txt}\") != \"{ans}\"'\n\n    def test_user_input_fns(self):\n        p = inflect.engine()\n\n        assert p.pl_sb_user_defined == []\n        p.defnoun(\"VAX\", \"VAXen\")\n        assert p.plural(\"VAX\") == \"VAXEN\"\n        assert p.pl_sb_user_defined == [\"VAX\", \"VAXen\"]\n\n        assert p.ud_match(\"word\", p.pl_sb_user_defined) is None\n        assert p.ud_match(\"VAX\", p.pl_sb_user_defined) == \"VAXen\"\n        assert p.ud_match(\"VVAX\", p.pl_sb_user_defined) is None\n\n        p.defnoun(\"cow\", \"cows|kine\")\n        assert p.plural(\"cow\") == \"cows\"\n        p.classical()\n        assert p.plural(\"cow\") == \"kine\"\n\n        assert p.ud_match(\"cow\", p.pl_sb_user_defined) == \"cows|kine\"\n\n        p.defnoun(\"(.+i)o\", r\"$1i\")\n        assert p.plural(\"studio\") == \"studii\"\n        assert p.ud_match(\"studio\", p.pl_sb_user_defined) == \"studii\"\n\n        p.defnoun(\"aviatrix\", \"aviatrices\")\n        assert p.plural(\"aviatrix\") == \"aviatrices\"\n        assert p.ud_match(\"aviatrix\", p.pl_sb_user_defined) == \"aviatrices\"\n        p.defnoun(\"aviatrix\", \"aviatrixes\")\n        assert p.plural(\"aviatrix\") == \"aviatrixes\"\n        assert p.ud_match(\"aviatrix\", p.pl_sb_user_defined) == \"aviatrixes\"\n        p.defnoun(\"aviatrix\", None)\n        assert p.plural(\"aviatrix\") == \"aviatrices\"\n        assert p.ud_match(\"aviatrix\", p.pl_sb_user_defined) is None\n\n        p.defnoun(\"(cat)\", r\"$1s\")\n        assert p.plural(\"cat\") == \"cats\"\n\n        with pytest.raises(inflect.BadUserDefinedPatternError):\n            p.defnoun(\"(??\", None)\n\n        p.defnoun(None, \"any\")  # check None doesn't crash it\n\n        # defadj\n        p.defadj(\"hir\", \"their\")\n        assert p.plural(\"hir\") == \"their\"\n        assert p.ud_match(\"hir\", p.pl_adj_user_defined) == \"their\"\n\n        # defa defan\n        p.defa(\"h\")\n        assert p.a(\"h\") == \"a h\"\n        assert p.ud_match(\"h\", p.A_a_user_defined) == \"a\"\n\n        p.defan(\"horrendous.*\")\n        assert p.a(\"horrendously\") == \"an horrendously\"\n        assert p.ud_match(\"horrendously\", p.A_a_user_defined) == \"an\"\n\n    def test_user_input_defverb(self):\n        p = inflect.engine()\n        p.defverb(\"will\", \"shall\", \"will\", \"will\", \"will\", \"will\")\n        assert p.ud_match(\"will\", p.pl_v_user_defined) == \"will\"\n        assert p.plural(\"will\") == \"will\"\n\n    @pytest.mark.xfail(reason=\"todo\")\n    def test_user_input_defverb_compare(self):\n        p = inflect.engine()\n        p.defverb(\"will\", \"shall\", \"will\", \"will\", \"will\", \"will\")\n        assert p.compare(\"will\", \"shall\") == \"s:p\"\n        assert p.compare_verbs(\"will\", \"shall\") == \"s:p\"\n\n    def test_postprocess(self):\n        p = inflect.engine()\n        for orig, infl, txt in (\n            (\"cow\", \"cows\", \"cows\"),\n            (\"I\", \"we\", \"we\"),\n            (\"COW\", \"cows\", \"COWS\"),\n            (\"Cow\", \"cows\", \"Cows\"),\n            (\"cow\", \"cows|kine\", \"cows\"),\n            (\"Entry\", \"entries\", \"Entries\"),\n            (\"can of Coke\", \"cans of coke\", \"cans of Coke\"),\n        ):\n            assert p.postprocess(orig, infl) == txt\n\n        p.classical()\n        assert p.postprocess(\"cow\", \"cows|kine\") == \"kine\"\n\n    def test_partition_word(self):\n        p = inflect.engine()\n        for txt, part in (\n            (\" cow \", (\" \", \"cow\", \" \")),\n            (\"cow\", (\"\", \"cow\", \"\")),\n            (\"   cow\", (\"   \", \"cow\", \"\")),\n            (\"cow   \", (\"\", \"cow\", \"   \")),\n            (\"  cow   \", (\"  \", \"cow\", \"   \")),\n            (\"\", (\"\", \"\", \"\")),\n            (\"bottle of beer\", (\"\", \"bottle of beer\", \"\")),\n            # spaces give weird results\n            # (' '),('', ' ', '')),\n            # ('  '),(' ', ' ', '')),\n            # ('   '),('  ', ' ', '')),\n        ):\n            assert p.partition_word(txt) == part\n\n    def test_pl(self):\n        p = inflect.engine()\n        for fn, sing, plur in (\n            (p.plural, \"cow\", \"cows\"),\n            (p.plural, \"thought\", \"thoughts\"),\n            (p.plural, \"mouse\", \"mice\"),\n            (p.plural, \"knife\", \"knives\"),\n            (p.plural, \"knifes\", \"knife\"),\n            (p.plural, \" cat  \", \" cats  \"),\n            (p.plural, \"court martial\", \"courts martial\"),\n            (p.plural, \"a\", \"some\"),\n            (p.plural, \"carmen\", \"carmina\"),\n            (p.plural, \"quartz\", \"quartzes\"),\n            (p.plural, \"care\", \"cares\"),\n            (p.plural_noun, \"cow\", \"cows\"),\n            (p.plural_noun, \"thought\", \"thoughts\"),\n            (p.plural_noun, \"snooze\", \"snoozes\"),\n            (p.plural_verb, \"runs\", \"run\"),\n            (p.plural_verb, \"thought\", \"thought\"),\n            (p.plural_verb, \"eyes\", \"eye\"),\n            (p.plural_adj, \"a\", \"some\"),\n            (p.plural_adj, \"this\", \"these\"),\n            (p.plural_adj, \"that\", \"those\"),\n            (p.plural_adj, \"my\", \"our\"),\n            (p.plural_adj, \"cat's\", \"cats'\"),\n            (p.plural_adj, \"child's\", \"children's\"),\n        ):\n            assert fn(sing) == plur, (\n                f'{fn.__name__}(\"{sing}\") == \"{fn(sing)}\" != \"{plur}\"'\n            )\n\n        for sing, num, plur in (\n            (\"cow\", 1, \"cow\"),\n            (\"cow\", 2, \"cows\"),\n            (\"cow\", \"one\", \"cow\"),\n            (\"cow\", \"each\", \"cow\"),\n            (\"cow\", \"two\", \"cows\"),\n            (\"cow\", 0, \"cows\"),\n            (\"cow\", \"zero\", \"cows\"),\n            (\"runs\", 0, \"run\"),\n            (\"runs\", 1, \"runs\"),\n            (\"am\", 0, \"are\"),\n        ):\n            assert p.plural(sing, num) == plur\n\n        p.classical(zero=True)\n        assert p.plural(\"cow\", 0) == \"cow\"\n        assert p.plural(\"cow\", \"zero\") == \"cow\"\n        assert p.plural(\"runs\", 0) == \"runs\"\n        assert p.plural(\"am\", 0) == \"am\"\n        assert p.plural_verb(\"runs\", 1) == \"runs\"\n\n        assert p.plural(\"die\") == \"dice\"\n        assert p.plural_noun(\"die\") == \"dice\"\n\n        with pytest.raises(TypeCheckError):\n            p.plural(\"\")\n        with pytest.raises(TypeCheckError):\n            p.plural_noun(\"\")\n        with pytest.raises(TypeCheckError):\n            p.plural_verb(\"\")\n        with pytest.raises(TypeCheckError):\n            p.plural_adj(\"\")\n\n    def test_sinoun(self):\n        p = inflect.engine()\n        for sing, plur in (\n            (\"cat\", \"cats\"),\n            (\"die\", \"dice\"),\n            (\"goose\", \"geese\"),\n        ):\n            assert p.singular_noun(plur) == sing\n            assert p.inflect(\"singular_noun('%s')\" % plur) == sing\n\n        assert p.singular_noun(\"cats\", count=2) == \"cats\"\n        assert p.singular_noun(\"open valves\", count=2) == \"open valves\"\n\n        assert p.singular_noun(\"zombies\") == \"zombie\"\n\n        assert p.singular_noun(\"shoes\") == \"shoe\"\n        assert p.singular_noun(\"dancing shoes\") == \"dancing shoe\"\n\n        assert p.singular_noun(\"Matisses\") == \"Matisse\"\n        assert p.singular_noun(\"bouillabaisses\") == \"bouillabaisse\"\n\n        assert p.singular_noun(\"quartzes\") == \"quartz\"\n\n        assert p.singular_noun(\"Nietzsches\") == \"Nietzsche\"\n        assert p.singular_noun(\"aches\") == \"ache\"\n\n        assert p.singular_noun(\"Clives\") == \"Clive\"\n        assert p.singular_noun(\"weaves\") == \"weave\"\n\n        assert p.singular_noun(\"status\") is False\n        assert p.singular_noun(\"hiatus\") is False\n\n    def test_gender(self):\n        p = inflect.engine()\n        p.gender(\"feminine\")\n        for sing, plur in (\n            (\"she\", \"they\"),\n            (\"herself\", \"themselves\"),\n            (\"hers\", \"theirs\"),\n            (\"to her\", \"to them\"),\n            (\"to herself\", \"to themselves\"),\n        ):\n            assert p.singular_noun(plur) == sing, (\n                f\"singular_noun({plur}) == {p.singular_noun(plur)} != {sing}\"\n            )\n            assert p.inflect(\"singular_noun('%s')\" % plur) == sing\n\n        p.gender(\"masculine\")\n        for sing, plur in (\n            (\"he\", \"they\"),\n            (\"himself\", \"themselves\"),\n            (\"his\", \"theirs\"),\n            (\"to him\", \"to them\"),\n            (\"to himself\", \"to themselves\"),\n        ):\n            assert p.singular_noun(plur) == sing, (\n                f\"singular_noun({plur}) == {p.singular_noun(plur)} != {sing}\"\n            )\n            assert p.inflect(\"singular_noun('%s')\" % plur) == sing\n\n        p.gender(\"gender-neutral\")\n        for sing, plur in (\n            (\"they\", \"they\"),\n            (\"themself\", \"themselves\"),\n            (\"theirs\", \"theirs\"),\n            (\"to them\", \"to them\"),\n            (\"to themself\", \"to themselves\"),\n        ):\n            assert p.singular_noun(plur) == sing, (\n                f\"singular_noun({plur}) == {p.singular_noun(plur)} != {sing}\"\n            )\n            assert p.inflect(\"singular_noun('%s')\" % plur) == sing\n\n        p.gender(\"neuter\")\n        for sing, plur in (\n            (\"it\", \"they\"),\n            (\"itself\", \"themselves\"),\n            (\"its\", \"theirs\"),\n            (\"to it\", \"to them\"),\n            (\"to itself\", \"to themselves\"),\n        ):\n            assert p.singular_noun(plur) == sing, (\n                f\"singular_noun({plur}) == {p.singular_noun(plur)} != {sing}\"\n            )\n            assert p.inflect(\"singular_noun('%s')\" % plur) == sing\n\n        with pytest.raises(BadGenderError):\n            p.gender(\"male\")\n\n        for sing, plur, gen in (\n            (\"it\", \"they\", \"neuter\"),\n            (\"she\", \"they\", \"feminine\"),\n            (\"he\", \"they\", \"masculine\"),\n            (\"they\", \"they\", \"gender-neutral\"),\n            (\"she or he\", \"they\", \"feminine or masculine\"),\n            (\"he or she\", \"they\", \"masculine or feminine\"),\n        ):\n            assert p.singular_noun(plur, gender=gen) == sing\n\n        with pytest.raises(BadGenderError):\n            p.singular_noun(\"cats\", gender=\"unknown gender\")\n\n    @pytest.mark.parametrize(\n        'sing,plur,res',\n        (\n            (\"index\", \"index\", \"eq\"),\n            (\"index\", \"indexes\", \"s:p\"),\n            (\"index\", \"indices\", \"s:p\"),\n            (\"indexes\", \"index\", \"p:s\"),\n            (\"indices\", \"index\", \"p:s\"),\n            (\"indices\", \"indexes\", \"p:p\"),\n            (\"indexes\", \"indices\", \"p:p\"),\n            (\"indices\", \"indices\", \"eq\"),\n            (\"inverted index\", \"inverted indices\", \"s:p\"),\n            (\"inverted indices\", \"inverted index\", \"p:s\"),\n            (\"inverted indexes\", \"inverted indices\", \"p:p\"),\n            (\"opuses\", \"opera\", \"p:p\"),\n            (\"opera\", \"opuses\", \"p:p\"),\n            (\"brothers\", \"brethren\", \"p:p\"),\n            (\"cats\", \"cats\", \"eq\"),\n            (\"base\", \"basis\", False),\n            (\"syrinx\", \"syringe\", False),\n            (\"she\", \"he\", False),\n            (\"opus\", \"operas\", False),\n            (\"taxi\", \"taxes\", False),\n            (\"time\", \"Times\", False),\n            (\"time\".lower(), \"Times\".lower(), \"s:p\"),\n            (\"courts martial\", \"court martial\", \"p:s\"),\n            (\"my\", \"my\", \"eq\"),\n            (\"my\", \"our\", \"s:p\"),\n            (\"our\", \"our\", \"eq\"),\n            pytest.param(\n                \"dresses's\", \"dresses'\", \"p:p\", marks=pytest.mark.xfail(reason=\"todo\")\n            ),\n            pytest.param(\n                \"dress's\", \"dress'\", \"s:s\", marks=pytest.mark.xfail(reason='todo')\n            ),\n            pytest.param(\n                \"Jess's\", \"Jess'\", \"s:s\", marks=pytest.mark.xfail(reason='todo')\n            ),\n        ),\n    )\n    def test_compare_simple(self, sing, plur, res):\n        assert inflect.engine().compare(sing, plur) == res\n\n    @pytest.mark.parametrize(\n        'sing,plur,res',\n        (\n            (\"index\", \"index\", \"eq\"),\n            (\"index\", \"indexes\", \"s:p\"),\n            (\"index\", \"indices\", \"s:p\"),\n            (\"indexes\", \"index\", \"p:s\"),\n            (\"indices\", \"index\", \"p:s\"),\n            (\"indices\", \"indexes\", \"p:p\"),\n            (\"indexes\", \"indices\", \"p:p\"),\n            (\"indices\", \"indices\", \"eq\"),\n            (\"inverted index\", \"inverted indices\", \"s:p\"),\n            (\"inverted indices\", \"inverted index\", \"p:s\"),\n            (\"inverted indexes\", \"inverted indices\", \"p:p\"),\n        ),\n    )\n    def test_compare_nouns(self, sing, plur, res):\n        assert inflect.engine().compare_nouns(sing, plur) == res\n\n    @pytest.mark.parametrize(\n        'sing,plur,res',\n        (\n            (\"runs\", \"runs\", \"eq\"),\n            (\"runs\", \"run\", \"s:p\"),\n            (\"run\", \"run\", \"eq\"),\n        ),\n    )\n    def test_compare_verbs(self, sing, plur, res):\n        assert inflect.engine().compare_verbs(sing, plur) == res\n\n    @pytest.mark.parametrize(\n        'sing,plur,res',\n        (\n            (\"my\", \"my\", \"eq\"),\n            (\"my\", \"our\", \"s:p\"),\n            (\"our\", \"our\", \"eq\"),\n            pytest.param(\n                \"dresses's\", \"dresses'\", \"p:p\", marks=pytest.mark.xfail(reason=\"todo\")\n            ),\n            pytest.param(\n                \"dress's\", \"dress'\", \"s:s\", marks=pytest.mark.xfail(reason='todo')\n            ),\n            pytest.param(\n                \"Jess's\", \"Jess'\", \"s:s\", marks=pytest.mark.xfail(reason='todo')\n            ),\n        ),\n    )\n    def test_compare_adjectives(self, sing, plur, res):\n        assert inflect.engine().compare_adjs(sing, plur) == res\n\n    @pytest.mark.xfail()\n    def test_compare_your_our(self):\n        # multiple adjective plurals not (yet) supported\n        p = inflect.engine()\n        assert p.compare(\"your\", \"our\") is False\n        p.defadj(\"my\", \"our|your\")  # what's ours is yours\n        assert p.compare(\"your\", \"our\") == \"p:p\"\n\n    def test__pl_reg_plurals(self):\n        p = inflect.engine()\n        for pair, stems, end1, end2, ans in (\n            (\"indexes|indices\", \"dummy|ind\", \"exes\", \"ices\", True),\n            (\"indexes|robots\", \"dummy|ind\", \"exes\", \"ices\", False),\n            (\"beaus|beaux\", \".*eau\", \"s\", \"x\", True),\n        ):\n            assert p._pl_reg_plurals(pair, stems, end1, end2) == ans\n\n    def test__pl_check_plurals_N(self):\n        p = inflect.engine()\n        assert p._pl_check_plurals_N(\"index\", \"indices\") is False\n        assert p._pl_check_plurals_N(\"indexes\", \"indices\") is True\n        assert p._pl_check_plurals_N(\"indices\", \"indexes\") is True\n        assert p._pl_check_plurals_N(\"stigmata\", \"stigmas\") is True\n        assert p._pl_check_plurals_N(\"phalanxes\", \"phalanges\") is True\n\n    def test__pl_check_plurals_adj(self):\n        p = inflect.engine()\n        assert p._pl_check_plurals_adj(\"indexes's\", \"indices's\") is True\n        assert p._pl_check_plurals_adj(\"indices's\", \"indexes's\") is True\n        assert p._pl_check_plurals_adj(\"indexes'\", \"indices's\") is True\n        assert p._pl_check_plurals_adj(\"indexes's\", \"indices'\") is True\n        assert p._pl_check_plurals_adj(\"indexes's\", \"indexes's\") is False\n        assert p._pl_check_plurals_adj(\"dogmas's\", \"dogmata's\") is True\n        assert p._pl_check_plurals_adj(\"dogmas'\", \"dogmata'\") is True\n        assert p._pl_check_plurals_adj(\"indexes'\", \"indices'\") is True\n\n    def test_count(self):\n        p = inflect.engine()\n        for txt, num in (\n            (1, 1),\n            (2, 2),\n            (0, 2),\n            (87, 2),\n            (-7, 2),\n            (\"1\", 1),\n            (\"2\", 2),\n            (\"0\", 2),\n            (\"no\", 2),\n            (\"zero\", 2),\n            (\"nil\", 2),\n            (\"a\", 1),\n            (\"an\", 1),\n            (\"one\", 1),\n            (\"each\", 1),\n            (\"every\", 1),\n            (\"this\", 1),\n            (\"that\", 1),\n            (\"dummy\", 2),\n        ):\n            assert p.get_count(txt) == num\n\n        assert p.get_count() == \"\"\n        p.num(3)\n        assert p.get_count() == 2\n\n    def test__plnoun(self):\n        p = inflect.engine()\n        for sing, plur in (\n            (\"tuna\", \"tuna\"),\n            (\"TUNA\", \"TUNA\"),\n            (\"swordfish\", \"swordfish\"),\n            (\"Governor General\", \"Governors General\"),\n            (\"Governor-General\", \"Governors-General\"),\n            (\"Major General\", \"Major Generals\"),\n            (\"Major-General\", \"Major-Generals\"),\n            (\"mother in law\", \"mothers in law\"),\n            (\"mother-in-law\", \"mothers-in-law\"),\n            (\"about me\", \"about us\"),\n            (\"to it\", \"to them\"),\n            (\"from it\", \"from them\"),\n            (\"with it\", \"with them\"),\n            (\"I\", \"we\"),\n            (\"you\", \"you\"),\n            (\"me\", \"us\"),\n            (\"mine\", \"ours\"),\n            (\"child\", \"children\"),\n            (\"brainchild\", \"brainchilds\"),\n            (\"human\", \"humans\"),\n            (\"soliloquy\", \"soliloquies\"),\n            (\"chairwoman\", \"chairwomen\"),\n            (\"goose\", \"geese\"),\n            (\"tooth\", \"teeth\"),\n            (\"foot\", \"feet\"),\n            (\"forceps\", \"forceps\"),\n            (\"protozoon\", \"protozoa\"),\n            (\"czech\", \"czechs\"),\n            (\"codex\", \"codices\"),\n            (\"radix\", \"radices\"),\n            (\"bacterium\", \"bacteria\"),\n            (\"alumnus\", \"alumni\"),\n            (\"criterion\", \"criteria\"),\n            (\"alumna\", \"alumnae\"),\n            (\"bias\", \"biases\"),\n            (\"quiz\", \"quizzes\"),\n            (\"fox\", \"foxes\"),\n            (\"shelf\", \"shelves\"),\n            (\"leaf\", \"leaves\"),\n            (\"midwife\", \"midwives\"),\n            (\"scarf\", \"scarves\"),\n            (\"key\", \"keys\"),\n            (\"Sally\", \"Sallys\"),\n            (\"sally\", \"sallies\"),\n            (\"ado\", \"ados\"),\n            (\"auto\", \"autos\"),\n            (\"alto\", \"altos\"),\n            (\"zoo\", \"zoos\"),\n            (\"tomato\", \"tomatoes\"),\n        ):\n            assert p._plnoun(sing) == plur, (\n                f'p._plnoun(\"{sing}\") == {p._plnoun(sing)} != \"{plur}\"'\n            )\n\n            assert p._sinoun(plur) == sing, f'p._sinoun(\"{plur}\") != \"{sing}\"'\n\n        # words where forming singular is ambiguous or not attempted\n        for sing, plur in (\n            (\"son of a gun\", \"sons of guns\"),\n            (\"son-of-a-gun\", \"sons-of-guns\"),\n            (\"basis\", \"bases\"),\n            (\"Jess\", \"Jesses\"),\n        ):\n            assert p._plnoun(sing) == plur, f'p._plnoun(\"{sing}\") != \"{plur}\"'\n\n        p.num(1)\n        assert p._plnoun(\"cat\") == \"cat\"\n        p.num(3)\n\n        p.classical(herd=True)\n        assert p._plnoun(\"swine\") == \"swine\"\n        p.classical(herd=False)\n        assert p._plnoun(\"swine\") == \"swines\"\n        p.classical(persons=True)\n        assert p._plnoun(\"chairperson\") == \"chairpersons\"\n        p.classical(persons=False)\n        assert p._plnoun(\"chairperson\") == \"chairpeople\"\n        p.classical(ancient=True)\n        assert p._plnoun(\"formula\") == \"formulae\"\n        p.classical(ancient=False)\n        assert p._plnoun(\"formula\") == \"formulas\"\n\n        p.classical()\n        for sing, plur in (\n            (\"matrix\", \"matrices\"),\n            (\"gateau\", \"gateaux\"),\n            (\"millieu\", \"millieux\"),\n            (\"syrinx\", \"syringes\"),\n            (\"stamen\", \"stamina\"),\n            (\"apex\", \"apices\"),\n            (\"appendix\", \"appendices\"),\n            (\"maximum\", \"maxima\"),\n            (\"focus\", \"foci\"),\n            (\"status\", \"status\"),\n            (\"aurora\", \"aurorae\"),\n            (\"soma\", \"somata\"),\n            (\"iris\", \"irides\"),\n            (\"solo\", \"soli\"),\n            (\"oxymoron\", \"oxymora\"),\n            (\"goy\", \"goyim\"),\n            (\"afrit\", \"afriti\"),\n        ):\n            assert p._plnoun(sing) == plur\n\n        # p.classical(0)\n        # p.classical('names')\n        # classical now back to the default mode\n\n    @pytest.mark.parametrize(\n        'sing, plur',\n        (\n            pytest.param(\n                'about ME',\n                'about US',\n                marks=pytest.mark.xfail(reason='does not keep case'),\n            ),\n            pytest.param(\n                'YOU',\n                'YOU',\n                marks=pytest.mark.xfail(reason='does not keep case'),\n            ),\n        ),\n    )\n    def test_plnoun_retains_case(self, sing, plur):\n        assert inflect.engine()._plnoun(sing) == plur\n\n    def test_classical_pl(self):\n        p = inflect.engine()\n        p.classical()\n        for sing, plur in ((\"brother\", \"brethren\"), (\"dogma\", \"dogmata\")):\n            assert p.plural(sing) == plur\n\n    def test__pl_special_verb(self):\n        p = inflect.engine()\n        with pytest.raises(TypeCheckError):\n            p._pl_special_verb(\"\")\n        assert p._pl_special_verb(\"am\") == \"are\"\n        assert p._pl_special_verb(\"am\", 0) == \"are\"\n        assert p._pl_special_verb(\"runs\", 0) == \"run\"\n        p.classical(zero=True)\n        assert p._pl_special_verb(\"am\", 0) is False\n        assert p._pl_special_verb(\"am\", 1) == \"am\"\n        assert p._pl_special_verb(\"am\", 2) == \"are\"\n        assert p._pl_special_verb(\"runs\", 0) is False\n        assert p._pl_special_verb(\"am going to\") == \"are going to\"\n        assert p._pl_special_verb(\"did\") == \"did\"\n        assert p._pl_special_verb(\"wasn't\") == \"weren't\"\n        assert p._pl_special_verb(\"shouldn't\") == \"shouldn't\"\n        assert p._pl_special_verb(\"bias\") is False\n        assert p._pl_special_verb(\"news\") is False\n        assert p._pl_special_verb(\"Jess\") is False\n        assert p._pl_special_verb(\" \") is False\n        assert p._pl_special_verb(\"brushes\") == \"brush\"\n        assert p._pl_special_verb(\"fixes\") == \"fix\"\n        assert p._pl_special_verb(\"quizzes\") == \"quiz\"\n        assert p._pl_special_verb(\"fizzes\") == \"fizz\"\n        assert p._pl_special_verb(\"dresses\") == \"dress\"\n        assert p._pl_special_verb(\"flies\") == \"fly\"\n        assert p._pl_special_verb(\"canoes\") == \"canoe\"\n        assert p._pl_special_verb(\"horseshoes\") == \"horseshoe\"\n        assert p._pl_special_verb(\"does\") == \"do\"\n        # TODO: what's a real word to test this case?\n        assert p._pl_special_verb(\"zzzoes\") == \"zzzo\"\n        assert p._pl_special_verb(\"runs\") == \"run\"\n\n    def test__pl_general_verb(self):\n        p = inflect.engine()\n        assert p._pl_general_verb(\"acts\") == \"act\"\n        assert p._pl_general_verb(\"act\") == \"act\"\n        assert p._pl_general_verb(\"saw\") == \"saw\"\n        assert p._pl_general_verb(\"runs\", 1) == \"runs\"\n\n    @pytest.mark.parametrize(\n        'adj,plur',\n        (\n            (\"a\", \"some\"),\n            (\"my\", \"our\"),\n            (\"John's\", \"Johns'\"),\n            (\"tuna's\", \"tuna's\"),\n            (\"TUNA's\", \"TUNA's\"),\n            (\"bad\", False),\n            (\"'\", False),\n            pytest.param(\n                \"JOHN's\",\n                \"JOHNS'\",\n                marks=pytest.mark.xfail(reason='should this be handled?'),\n            ),\n            pytest.param(\n                \"JOHN'S\",\n                \"JOHNS'\",\n                marks=pytest.mark.xfail(reason=\"can't handle capitals\"),\n            ),\n            pytest.param(\n                \"TUNA'S\",\n                \"TUNA'S\",\n                marks=pytest.mark.xfail(reason=\"can't handle capitals\"),\n            ),\n        ),\n    )\n    def test__pl_special_adjective(self, adj, plur):\n        p = inflect.engine()\n        assert p._pl_special_adjective(adj) == plur\n\n    @pytest.mark.parametrize(\n        'sing, plur',\n        (\n            (\"cat\", \"a cat\"),\n            (\"euphemism\", \"a euphemism\"),\n            (\"Euler number\", \"an Euler number\"),\n            (\"hour\", \"an hour\"),\n            (\"houri\", \"a houri\"),\n            (\"nth\", \"an nth\"),\n            (\"rth\", \"an rth\"),\n            (\"sth\", \"an sth\"),\n            (\"xth\", \"an xth\"),\n            (\"ant\", \"an ant\"),\n            (\"book\", \"a book\"),\n            (\"RSPCA\", \"an RSPCA\"),\n            (\"SONAR\", \"a SONAR\"),\n            (\"FJO\", \"a FJO\"),\n            (\"FJ\", \"an FJ\"),\n            (\"NASA\", \"a NASA\"),\n            (\"UN\", \"a UN\"),\n            (\"yak\", \"a yak\"),\n            (\"yttrium\", \"an yttrium\"),\n            (\"a elephant\", \"an elephant\"),\n            (\"a giraffe\", \"a giraffe\"),\n            (\"an ewe\", \"a ewe\"),\n            (\"a orangutan\", \"an orangutan\"),\n            (\"R.I.P.\", \"an R.I.P.\"),\n            (\"C.O.D.\", \"a C.O.D.\"),\n            (\"e-mail\", \"an e-mail\"),\n            (\"X-ray\", \"an X-ray\"),\n            (\"T-square\", \"a T-square\"),\n            (\"LCD\", \"an LCD\"),\n            (\"XML\", \"an XML\"),\n            (\"YWCA\", \"a YWCA\"),\n            (\"LED\", \"a LED\"),\n            (\"OPEC\", \"an OPEC\"),\n            (\"FAQ\", \"a FAQ\"),\n            (\"UNESCO\", \"a UNESCO\"),\n            (\"a\", \"an a\"),\n            (\"an\", \"an an\"),\n            (\"an ant\", \"an ant\"),\n            (\"a cat\", \"a cat\"),\n            (\"an cat\", \"a cat\"),\n            (\"a ant\", \"an ant\"),\n        ),\n    )\n    def test_a(self, sing, plur):\n        p = inflect.engine()\n        assert p.a(sing) == plur\n\n    def test_a_alt(self):\n        p = inflect.engine()\n        assert p.a(\"cat\", 1) == \"a cat\"\n        assert p.a(\"cat\", 2) == \"2 cat\"\n\n        with pytest.raises(TypeCheckError):\n            p.a(\"\")\n\n    def test_a_and_an_same_method(self):\n        assert inflect.engine.a == inflect.engine.an\n        p = inflect.engine()\n        assert p.a == p.an\n\n    def test_no(self):\n        p = inflect.engine()\n        assert p.no(\"cat\") == \"no cats\"\n        assert p.no(\"cat\", count=3) == \"3 cats\"\n        assert p.no(\"cat\", count=\"three\") == \"three cats\"\n        assert p.no(\"cat\", count=1) == \"1 cat\"\n        assert p.no(\"cat\", count=\"one\") == \"one cat\"\n        assert p.no(\"mouse\") == \"no mice\"\n        p.num(3)\n        assert p.no(\"cat\") == \"3 cats\"\n\n    @pytest.mark.parametrize(\n        'sing, plur',\n        (\n            (\"runs\", \"running\"),\n            (\"dies\", \"dying\"),\n            (\"glues\", \"gluing\"),\n            (\"eyes\", \"eying\"),\n            (\"skis\", \"skiing\"),\n            (\"names\", \"naming\"),\n            (\"sees\", \"seeing\"),\n            (\"hammers\", \"hammering\"),\n            (\"bats\", \"batting\"),\n            (\"eats\", \"eating\"),\n            (\"loves\", \"loving\"),\n            (\"spies\", \"spying\"),\n            (\"hoes\", \"hoeing\"),\n            (\"alibis\", \"alibiing\"),\n            (\"is\", \"being\"),\n            (\"are\", \"being\"),\n            (\"had\", \"having\"),\n            (\"has\", \"having\"),\n        ),\n    )\n    def test_prespart(self, sing, plur):\n        p = inflect.engine()\n        assert p.present_participle(sing) == plur\n\n    @pytest.mark.parametrize(\n        'num, ord',\n        (\n            (\"1\", \"1st\"),\n            (\"2\", \"2nd\"),\n            (\"3\", \"3rd\"),\n            (\"4\", \"4th\"),\n            (\"10\", \"10th\"),\n            (\"28\", \"28th\"),\n            (\"100\", \"100th\"),\n            (\"101\", \"101st\"),\n            (\"1000\", \"1000th\"),\n            (\"1001\", \"1001st\"),\n            (\"0\", \"0th\"),\n            (\"one\", \"first\"),\n            (\"two\", \"second\"),\n            (\"four\", \"fourth\"),\n            (\"twenty\", \"twentieth\"),\n            (\"one hundered\", \"one hunderedth\"),\n            (\"one hundered and one\", \"one hundered and first\"),\n            (\"zero\", \"zeroth\"),\n            (\"n\", \"nth\"),  # bonus!\n        ),\n    )\n    def test_ordinal(self, num, ord):\n        p = inflect.engine()\n        assert p.ordinal(num) == ord\n\n    def test_millfn(self):\n        p = inflect.engine()\n        millfn = p.millfn\n        assert millfn(1) == \" thousand\"\n        assert millfn(2) == \" million\"\n        assert millfn(3) == \" billion\"\n        assert millfn(0) == \" \"\n        assert millfn(11) == \" decillion\"\n        with pytest.raises(NumOutOfRangeError):\n            millfn(12)\n\n    def test_unitfn(self):\n        p = inflect.engine()\n        unitfn = p.unitfn\n        assert unitfn(1, 2) == \"one million\"\n        assert unitfn(1, 3) == \"one billion\"\n        assert unitfn(5, 3) == \"five billion\"\n        assert unitfn(5, 0) == \"five \"\n        assert unitfn(0, 0) == \" \"\n\n    def test_tenfn(self):\n        p = inflect.engine()\n        tenfn = p.tenfn\n        assert tenfn(3, 1, 2) == \"thirty-one million\"\n        assert tenfn(3, 0, 2) == \"thirty million\"\n        assert tenfn(0, 1, 2) == \"one million\"\n        assert tenfn(1, 1, 2) == \"eleven million\"\n        assert tenfn(1, 0, 2) == \"ten million\"\n        assert tenfn(1, 0, 0) == \"ten \"\n        assert tenfn(0, 0, 0) == \" \"\n\n    def test_hundfn(self):\n        p = inflect.engine()\n        hundfn = p.hundfn\n        p._number_args = dict(andword=\"and\")\n        assert hundfn(4, 3, 1, 2) == \"four hundred and thirty-one  million, \"\n        assert hundfn(4, 0, 0, 2) == \"four hundred  million, \"\n        assert hundfn(4, 0, 5, 2) == \"four hundred and five  million, \"\n        assert hundfn(0, 3, 1, 2) == \"thirty-one  million, \"\n        assert hundfn(0, 0, 7, 2) == \"seven  million, \"\n\n    def test_enword(self):\n        p = inflect.engine()\n        enword = p.enword\n        assert enword(\"5\", 1) == \"five, \"\n        p._number_args = dict(zero=\"zero\", one=\"one\", andword=\"and\")\n        assert enword(\"0\", 1) == \" zero, \"\n        assert enword(\"1\", 1) == \" one, \"\n        assert enword(\"347\", 1) == \"three, four, seven, \"\n\n        assert enword(\"34\", 2) == \"thirty-four , \"\n        assert enword(\"347\", 2) == \"thirty-four , seven, \"\n        assert enword(\"34768\", 2) == \"thirty-four , seventy-six , eight, \"\n        assert enword(\"1\", 2) == \"one, \"\n\n        assert enword(\"134\", 3) == \" one thirty-four , \"\n\n        assert enword(\"0\", -1) == \"zero\"\n        assert enword(\"1\", -1) == \"one\"\n\n        assert enword(\"3\", -1) == \"three , \"\n        assert enword(\"12\", -1) == \"twelve , \"\n        assert enword(\"123\", -1) == \"one hundred and twenty-three  , \"\n        assert enword(\"1234\", -1) == \"one thousand, two hundred and thirty-four  , \"\n        assert (\n            enword(\"12345\", -1) == \"twelve thousand, three hundred and forty-five  , \"\n        )\n        assert (\n            enword(\"123456\", -1)\n            == \"one hundred and twenty-three  thousand, four hundred and fifty-six  , \"\n        )\n        assert (\n            enword(\"1234567\", -1)\n            == \"one million, two hundred and thirty-four  thousand, \"\n            \"five hundred and sixty-seven  , \"\n        )\n\n    @pytest.mark.xfail(reason=\"doesn't use indicated word for 'one'\")\n    def test_enword_number_args_override(self):\n        p = inflect.engine()\n        p._number_args[\"one\"] = \"single\"\n        assert p.enword(\"1\", 2) == \"single, \"\n\n    def test_numwords(self):\n        p = inflect.engine()\n        numwords = p.number_to_words\n\n        for n, word in (\n            (\"1\", \"one\"),\n            (\"10\", \"ten\"),\n            (\"100\", \"one hundred\"),\n            (\"1000\", \"one thousand\"),\n            (\"10000\", \"ten thousand\"),\n            (\"100000\", \"one hundred thousand\"),\n            (\"1000000\", \"one million\"),\n            (\"10000000\", \"ten million\"),\n            (\"+10\", \"plus ten\"),\n            (\"-10\", \"minus ten\"),\n            (\"10.\", \"ten point\"),\n            (\".10\", \"point one zero\"),\n        ):\n            assert numwords(n) == word\n\n        for n, word, _wrongword in (\n            # TODO: should be one point two three\n            (\"1.23\", \"one point two three\", \"one point twenty-three\"),\n        ):\n            assert numwords(n) == word\n\n        for n, txt in (\n            (3, \"three bottles of beer on the wall\"),\n            (2, \"two bottles of beer on the wall\"),\n            (1, \"a solitary bottle of beer on the wall\"),\n            (0, \"no more bottles of beer on the wall\"),\n        ):\n            assert (\n                \"{}{}\".format(\n                    numwords(n, one=\"a solitary\", zero=\"no more\"),\n                    p.plural(\" bottle of beer on the wall\", n),\n                )\n                == txt\n            )\n\n        assert numwords(0, one=\"one\", zero=\"zero\") == \"zero\"\n\n        assert numwords(\"1234\") == \"one thousand, two hundred and thirty-four\"\n        assert numwords(\"1234\", wantlist=True) == [\n            \"one thousand\",\n            \"two hundred and thirty-four\",\n        ]\n        assert numwords(\"1234567\", wantlist=True) == [\n            \"one million\",\n            \"two hundred and thirty-four thousand\",\n            \"five hundred and sixty-seven\",\n        ]\n        assert numwords(\"+10\", wantlist=True) == [\"plus\", \"ten\"]\n        assert numwords(\"1234\", andword=\"\") == \"one thousand, two hundred thirty-four\"\n        assert (\n            numwords(\"1234\", andword=\"plus\")\n            == \"one thousand, two hundred plus thirty-four\"\n        )\n        assert numwords(p.ordinal(\"21\")) == \"twenty-first\"\n        assert numwords(\"9\", threshold=10) == \"nine\"\n        assert numwords(\"10\", threshold=10) == \"ten\"\n        assert numwords(\"11\", threshold=10) == \"11\"\n        assert numwords(\"1000\", threshold=10) == \"1,000\"\n        assert numwords(\"123\", threshold=10) == \"123\"\n        assert numwords(\"1234\", threshold=10) == \"1,234\"\n        assert numwords(\"1234.5678\", threshold=10) == \"1,234.5678\"\n        assert numwords(\"1\", decimal=None) == \"one\"\n        assert (\n            numwords(\"1234.5678\", decimal=None)\n            == \"twelve million, three hundred and forty-five \"\n            \"thousand, six hundred and seventy-eight\"\n        )\n\n    def test_numwords_group_chunking_error(self):\n        p = inflect.engine()\n        with pytest.raises(BadChunkingOptionError):\n            p.number_to_words(\"1234\", group=4)\n\n    @pytest.mark.parametrize(\n        'input,kwargs,expect',\n        (\n            (\"12345\", dict(group=2), \"twelve, thirty-four, five\"),\n            (\"123456\", dict(group=3), \"one twenty-three, four fifty-six\"),\n            (\"12345\", dict(group=1), \"one, two, three, four, five\"),\n            (\n                \"1234th\",\n                dict(group=0, andword=\"and\"),\n                \"one thousand, two hundred and thirty-fourth\",\n            ),\n            (\n                \"1234th\",\n                dict(group=0),\n                \"one thousand, two hundred and thirty-fourth\",\n            ),\n            (\"120\", dict(group=2), \"twelve, zero\"),\n            (\"120\", dict(group=2, zero=\"oh\", one=\"unity\"), \"twelve, oh\"),\n            (\n                \"555_1202\",\n                dict(group=1, zero=\"oh\"),\n                \"five, five, five, one, two, oh, two\",\n            ),\n            (\n                \"555_1202\",\n                dict(group=1, one=\"unity\"),\n                \"five, five, five, unity, two, zero, two\",\n            ),\n            (\n                \"123.456\",\n                dict(group=1, decimal=\"mark\", one=\"one\"),\n                \"one, two, three, mark, four, five, six\",\n            ),\n            pytest.param(\n                '12345',\n                dict(group=3),\n                'one hundred and twenty-three',\n                marks=pytest.mark.xfail(reason=\"'hundred and' missing\"),\n            ),\n            pytest.param(\n                '101',\n                dict(group=2, zero=\"oh\", one=\"unity\"),\n                \"ten, unity\",\n                marks=pytest.mark.xfail(reason=\"ignoring 'one' param with group=2\"),\n            ),\n        ),\n    )\n    def test_numwords_group(self, input, kwargs, expect):\n        p = inflect.engine()\n        assert p.number_to_words(input, **kwargs) == expect\n\n    def test_wordlist(self):\n        p = inflect.engine()\n        wordlist = p.join\n        assert wordlist([]) == \"\"\n        assert wordlist((\"apple\",)) == \"apple\"\n        assert wordlist((\"apple\", \"banana\")) == \"apple and banana\"\n        assert wordlist((\"apple\", \"banana\", \"carrot\")) == \"apple, banana, and carrot\"\n        assert wordlist((\"apple\", \"1,000\", \"carrot\")) == \"apple; 1,000; and carrot\"\n        assert (\n            wordlist((\"apple\", \"1,000\", \"carrot\"), sep=\",\")\n            == \"apple, 1,000, and carrot\"\n        )\n        assert (\n            wordlist((\"apple\", \"banana\", \"carrot\"), final_sep=\"\")\n            == \"apple, banana and carrot\"\n        )\n        assert (\n            wordlist((\"apple\", \"banana\", \"carrot\"), final_sep=\";\")\n            == \"apple, banana; and carrot\"\n        )\n        assert (\n            wordlist((\"apple\", \"banana\", \"carrot\"), conj=\"or\")\n            == \"apple, banana, or carrot\"\n        )\n\n        assert wordlist((\"apple\", \"banana\"), conj=\" or \") == \"apple  or  banana\"\n        assert wordlist((\"apple\", \"banana\"), conj=\"&\") == \"apple & banana\"\n        assert (\n            wordlist((\"apple\", \"banana\"), conj=\"&\", conj_spaced=False) == \"apple&banana\"\n        )\n        assert (\n            wordlist((\"apple\", \"banana\"), conj=\"& \", conj_spaced=False)\n            == \"apple& banana\"\n        )\n\n        assert (\n            wordlist((\"apple\", \"banana\", \"carrot\"), conj=\" or \")\n            == \"apple, banana,  or  carrot\"\n        )\n        assert (\n            wordlist((\"apple\", \"banana\", \"carrot\"), conj=\"+\")\n            == \"apple, banana, + carrot\"\n        )\n        assert (\n            wordlist((\"apple\", \"banana\", \"carrot\"), conj=\"&\")\n            == \"apple, banana, & carrot\"\n        )\n        assert (\n            wordlist((\"apple\", \"banana\", \"carrot\"), conj=\"&\", conj_spaced=False)\n            == \"apple, banana,&carrot\"\n        )\n        assert (\n            wordlist((\"apple\", \"banana\", \"carrot\"), conj=\" &\", conj_spaced=False)\n            == \"apple, banana, &carrot\"\n        )\n\n    def test_doc_examples(self):\n        p = inflect.engine()\n        assert p.plural_noun(\"I\") == \"we\"\n        assert p.plural_verb(\"saw\") == \"saw\"\n        assert p.plural_adj(\"my\") == \"our\"\n        assert p.plural_noun(\"saw\") == \"saws\"\n        assert p.plural(\"was\") == \"were\"\n        assert p.plural(\"was\", 1) == \"was\"\n        assert p.plural_verb(\"was\", 2) == \"were\"\n        assert p.plural_verb(\"was\") == \"were\"\n        assert p.plural_verb(\"was\", 1) == \"was\"\n\n        for errors, txt in (\n            (0, \"There were no errors\"),\n            (1, \"There was 1 error\"),\n            (2, \"There were 2 errors\"),\n        ):\n            assert (\n                \"There {}{}\".format(\n                    p.plural_verb(\"was\", errors), p.no(\" error\", errors)\n                )\n                == txt\n            )\n\n            assert (\n                p.inflect(\n                    \"There plural_verb('was',%d) no('error',%d)\" % (errors, errors)\n                )\n                == txt\n            )\n\n        for num1, num2, txt in ((1, 2, \"I saw 2 saws\"), (2, 1, \"we saw 1 saw\")):\n            assert (\n                \"{}{}{} {}{}\".format(\n                    p.num(num1, \"\"),\n                    p.plural(\"I\"),\n                    p.plural_verb(\" saw\"),\n                    p.num(num2),\n                    p.plural_noun(\" saw\"),\n                )\n                == txt\n            )\n\n            assert (\n                p.inflect(\n                    \"num(%d, False)plural('I') plural_verb('saw') \"\n                    \"num(%d) plural_noun('saw')\" % (num1, num2)\n                )\n                == txt\n            )\n\n        assert p.a(\"a cat\") == \"a cat\"\n\n        for word, txt in (\n            (\"cat\", \"a cat\"),\n            (\"aardvark\", \"an aardvark\"),\n            (\"ewe\", \"a ewe\"),\n            (\"hour\", \"an hour\"),\n        ):\n            assert p.a(\"{} {}\".format(p.number_to_words(1, one=\"a\"), word)) == txt\n\n        p.num(2)\n\n    def test_unknown_method(self):\n        p = inflect.engine()\n        with pytest.raises(AttributeError):\n            p.unknown_method  # noqa: B018\n"
  },
  {
    "path": "tests/test_unicode.py",
    "content": "import inflect\n\n\nclass TestUnicode:\n    \"\"\"Unicode compatibility test cases\"\"\"\n\n    def test_unicode_plural(self):\n        \"\"\"Unicode compatibility test cases for plural\"\"\"\n        engine = inflect.engine()\n        unicode_test_cases = {\"cliché\": \"clichés\", \"ångström\": \"ångströms\"}\n        for singular, plural in unicode_test_cases.items():\n            assert plural == engine.plural(singular)\n"
  },
  {
    "path": "towncrier.toml",
    "content": "[tool.towncrier]\ntitle_format = \"{version}\"\ndirectory = \"newsfragments\"  # jaraco/skeleton#184\n"
  },
  {
    "path": "tox.ini",
    "content": "[testenv]\ndescription = perform primary checks (tests, style, types, coverage)\ndeps =\nsetenv =\n\tPYTHONWARNDEFAULTENCODING = 1\ncommands =\n\tpytest {posargs}\nusedevelop = True\nextras =\n\ttest\n\tcheck\n\tcover\n\tenabler\n\ttype\n\n[testenv:diffcov]\ndescription = run tests and check that diff from main is covered\ndeps =\n\t{[testenv]deps}\n\tdiff-cover\ncommands =\n\tpytest {posargs} --cov-report xml\n\tdiff-cover coverage.xml --compare-branch=origin/main --format html:diffcov.html\n\tdiff-cover coverage.xml --compare-branch=origin/main --fail-under=100\n\n[testenv:docs]\ndescription = build the documentation\nextras =\n\tdoc\n\ttest\nchangedir = docs\ncommands =\n\tpython -m sphinx -W --keep-going . {toxinidir}/build/html\n\tpython -m sphinxlint\n\n[testenv:finalize]\ndescription = assemble changelog and tag a release\nskip_install = True\ndeps =\n\ttowncrier\n\tjaraco.develop >= 7.23\npass_env = *\ncommands =\n\tpython -m jaraco.develop.finalize\n\n\n[testenv:release]\ndescription = publish the package to PyPI and GitHub\nskip_install = True\ndeps =\n\tbuild\n\ttwine>=3\n\tjaraco.develop>=7.1\npass_env =\n\tTWINE_PASSWORD\n\tGITHUB_TOKEN\nsetenv =\n\tTWINE_USERNAME = {env:TWINE_USERNAME:__token__}\ncommands =\n\tpython -c \"import shutil; shutil.rmtree('dist', ignore_errors=True)\"\n\tpython -m build\n\tpython -m twine upload dist/*\n\tpython -m jaraco.develop.create-github-release\n"
  }
]