├── inflection
├── py.typed
└── __init__.py
├── requirements.txt
├── MANIFEST.in
├── .gitignore
├── setup.py
├── pyproject.toml
├── .editorconfig
├── .travis.yml
├── tox.ini
├── README.rst
├── LICENSE
├── docs
├── index.rst
├── Makefile
└── conf.py
├── setup.cfg
└── test_inflection.py
/inflection/py.typed:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | flake8
2 | isort
3 | pytest
4 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.rst LICENSE test_inflection.py
2 | graft docs
3 | prune docs/_build
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.egg-info/
2 | *.pyc
3 | .Python
4 | .tox/
5 | __pycache__/
6 | dist/
7 | docs/_build/
8 | .mypy_cache/
9 | .venv/
10 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from setuptools import setup
5 |
6 | if __name__ == "__main__":
7 | setup()
8 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools>=44", "wheel", "setuptools_scm[toml]>=3.4.3"]
3 | build-backend = "setuptools.build_meta"
4 |
5 | [tool.setuptools_scm]
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 4
7 | insert_final_newline = true
8 | end_of_line = lf
9 |
10 | [*.{yml,yaml}]
11 | indent_size = 2
12 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - 3.5
4 | - 3.6
5 | - 3.7
6 | - 3.8
7 | - pypy3
8 | script:
9 | - flake8 .
10 | - isort --recursive --diff . && isort --recursive --check-only .
11 | - pytest
12 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py35, py36, py37, py38, pypy3
3 |
4 | [testenv]
5 | deps = -r{toxinidir}/requirements.txt
6 | pytest-mypy
7 | commands = pytest --mypy {posargs}
8 |
9 | [pytest]
10 | addopts = --doctest-modules
11 |
12 | [testenv:pypy3]
13 | deps = -r{toxinidir}/requirements.txt
14 | commands = pytest {posargs}
15 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Inflection
2 | ==========
3 |
4 | |build status|_
5 |
6 | .. |build status| image:: https://travis-ci.org/jpvanhal/inflection.svg?branch=master
7 | :alt: Build Status
8 | .. _build status: http://travis-ci.org/jpvanhal/inflection
9 |
10 | Inflection is a string transformation library. It singularizes and pluralizes
11 | English words, and transforms strings from CamelCase to underscored string.
12 | Inflection is a port of `Ruby on Rails`_' `inflector`_ to Python.
13 |
14 | .. _Ruby on Rails: http://rubyonrails.org
15 | .. _inflector: http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html
16 |
17 | Resources
18 | ---------
19 |
20 | - `Documentation `_
21 | - `Issue Tracker `_
22 | - `Code `_
23 | - `Development Version
24 | `_
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2012-2020 Janne Vanhala
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | Inflection
2 | ==========
3 |
4 | Inflection is a string transformation library. It singularizes and pluralizes
5 | English words, and transforms strings from CamelCase to underscored_string.
6 | Inflection is a port of `Ruby on Rails`_' `inflector`_ to Python.
7 |
8 | .. _Ruby on Rails: http://rubyonrails.org
9 | .. _inflector: http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html
10 |
11 |
12 | Installation
13 | ------------
14 |
15 | Use pip to install from PyPI::
16 |
17 | pip install inflection
18 |
19 |
20 | Contributing
21 | ------------
22 |
23 | To contribute to Inflector `create a fork`_ on GitHub. Clone your fork, make
24 | some changes, and submit a pull request.
25 |
26 | .. _create a fork: https://github.com/jpvanhal/inflection/fork_select
27 |
28 |
29 | API Documentation
30 | -----------------
31 |
32 | .. module:: inflection
33 |
34 | .. autofunction:: camelize
35 | .. autofunction:: dasherize
36 | .. autofunction:: humanize
37 | .. autofunction:: ordinal
38 | .. autofunction:: ordinalize
39 | .. autofunction:: parameterize
40 | .. autofunction:: pluralize
41 | .. autofunction:: singularize
42 | .. autofunction:: tableize
43 | .. autofunction:: titleize
44 | .. autofunction:: transliterate
45 | .. autofunction:: underscore
46 |
47 |
48 | Changelog
49 | ---------
50 |
51 | You you can see the full list of changes between each Inflection release in
52 | `GitHub releases page`_.
53 |
54 | .. _GitHub releases page: https://github.com/jpvanhal/inflection/releases
55 |
56 |
57 | License
58 | -------
59 |
60 | .. include:: ../LICENSE
61 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | license_file = LICENSE
3 | name = inflection
4 | author = Janne Vanhala
5 | author_email = janne.vanhala@gmail.com
6 | license = MIT
7 | description = A port of Ruby on Rails inflector to Python
8 | url = https://github.com/jpvanhal/inflection
9 | long_description = file: README.rst
10 | classifiers =
11 | Development Status :: 4 - Beta
12 | Intended Audience :: Developers
13 | Natural Language :: English
14 | License :: OSI Approved :: MIT License
15 | Programming Language :: Python
16 | Programming Language :: Python :: 3
17 | Programming Language :: Python :: 3.5
18 | Programming Language :: Python :: 3.6
19 | Programming Language :: Python :: 3.7
20 | Programming Language :: Python :: 3.8
21 | Programming Language :: Python :: Implementation :: CPython
22 | Programming Language :: Python :: Implementation :: PyPy
23 |
24 | [options]
25 | py_modules = inflection
26 | zip_safe = False
27 | python_requires = >=3.5
28 |
29 | [options.package_data]
30 | inflection = py.typed
31 |
32 | [flake8]
33 | exclude = docs/*
34 |
35 | [isort]
36 | skip = .tox,docs
37 |
38 | [mypy]
39 | disallow_any_decorated = True
40 | disallow_any_explicit = True
41 | warn_unreachable = True
42 | # options from __strict
43 | warn_unused_configs = True
44 | disallow_any_generics = True
45 | disallow_subclassing_any = True
46 | disallow_untyped_calls = True
47 | disallow_untyped_defs = True
48 | disallow_incomplete_defs = True
49 | check_untyped_defs = True
50 | disallow_untyped_decorators = True
51 | no_implicit_optional = True
52 | warn_redundant_casts = True
53 | warn_unused_ignores = True
54 | warn_return_any = True
55 | no_implicit_reexport = True
56 | strict_equality = True
57 |
58 | [mypy-setuptools]
59 | ignore_missing_imports = True
60 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
16 |
17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18 |
19 | help:
20 | @echo "Please use \`make ' where is one of"
21 | @echo " html to make standalone HTML files"
22 | @echo " dirhtml to make HTML files named index.html in directories"
23 | @echo " singlehtml to make a single large HTML file"
24 | @echo " pickle to make pickle files"
25 | @echo " json to make JSON files"
26 | @echo " htmlhelp to make HTML files and a HTML help project"
27 | @echo " qthelp to make HTML files and a qthelp project"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 | @echo " text to make text files"
33 | @echo " man to make manual pages"
34 | @echo " texinfo to make Texinfo files"
35 | @echo " info to make Texinfo files and run them through makeinfo"
36 | @echo " gettext to make PO message catalogs"
37 | @echo " changes to make an overview of all changed/added/deprecated items"
38 | @echo " linkcheck to check all external links for integrity"
39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40 |
41 | clean:
42 | -rm -rf $(BUILDDIR)/*
43 |
44 | html:
45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48 |
49 | dirhtml:
50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 | @echo
52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53 |
54 | singlehtml:
55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 | @echo
57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58 |
59 | pickle:
60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 | @echo
62 | @echo "Build finished; now you can process the pickle files."
63 |
64 | json:
65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 | @echo
67 | @echo "Build finished; now you can process the JSON files."
68 |
69 | htmlhelp:
70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 | @echo
72 | @echo "Build finished; now you can run HTML Help Workshop with the" \
73 | ".hhp project file in $(BUILDDIR)/htmlhelp."
74 |
75 | qthelp:
76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 | @echo
78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/inflection.qhcp"
81 | @echo "To view the help file:"
82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/inflection.qhc"
83 |
84 | devhelp:
85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 | @echo
87 | @echo "Build finished."
88 | @echo "To view the help file:"
89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/inflection"
90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/inflection"
91 | @echo "# devhelp"
92 |
93 | epub:
94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 | @echo
96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97 |
98 | latex:
99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 | @echo
101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 | "(use \`make latexpdf' here to do that automatically)."
104 |
105 | latexpdf:
106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 | @echo "Running LaTeX files through pdflatex..."
108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110 |
111 | text:
112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 | @echo
114 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
115 |
116 | man:
117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 | @echo
119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120 |
121 | texinfo:
122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 | @echo
124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 | @echo "Run \`make' in that directory to run these through makeinfo" \
126 | "(use \`make info' here to do that automatically)."
127 |
128 | info:
129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 | @echo "Running Texinfo files through makeinfo..."
131 | make -C $(BUILDDIR)/texinfo info
132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133 |
134 | gettext:
135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 | @echo
137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138 |
139 | changes:
140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 | @echo
142 | @echo "The overview file is in $(BUILDDIR)/changes."
143 |
144 | linkcheck:
145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 | @echo
147 | @echo "Link check complete; look for any errors in the above output " \
148 | "or in $(BUILDDIR)/linkcheck/output.txt."
149 |
150 | doctest:
151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 | @echo "Testing of doctests in the sources finished, look at the " \
153 | "results in $(BUILDDIR)/doctest/output.txt."
154 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # inflection documentation build configuration file, created by
4 | # sphinx-quickstart on Wed Feb 22 22:51:13 2012.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 |
14 | import sys, os
15 |
16 | from typing import Dict
17 |
18 | # If extensions (or modules to document with autodoc) are in another directory,
19 | # add these directories to sys.path here. If the directory is relative to the
20 | # documentation root, use os.path.abspath to make it absolute, like shown here.
21 | sys.path.insert(0, os.path.abspath('..'))
22 | from inflection import __version__
23 |
24 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
25 |
26 |
27 | # -- General configuration -----------------------------------------------------
28 |
29 | # If your documentation needs a minimal Sphinx version, state it here.
30 | #needs_sphinx = '1.0'
31 |
32 | # Add any Sphinx extension module names here, as strings. They can be extensions
33 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
34 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.coverage', 'sphinx.ext.viewcode']
35 |
36 | # Add any paths that contain templates here, relative to this directory.
37 | templates_path = ['_templates']
38 |
39 | # The suffix of source filenames.
40 | source_suffix = '.rst'
41 |
42 | # The encoding of source files.
43 | #source_encoding = 'utf-8-sig'
44 |
45 | # The master toctree document.
46 | master_doc = 'index'
47 |
48 | # General information about the project.
49 | project = u'inflection'
50 | copyright = u'2012-2020, Janne Vanhala'
51 |
52 | # The version info for the project you're documenting, acts as replacement for
53 | # |version| and |release|, also used in various other places throughout the
54 | # built documents.
55 | #
56 | # The short X.Y version.
57 | version = __version__
58 | # The full version, including alpha/beta/rc tags.
59 | release = version
60 |
61 | # The language for content autogenerated by Sphinx. Refer to documentation
62 | # for a list of supported languages.
63 | #language = None
64 |
65 | # There are two options for replacing |today|: either, you set today to some
66 | # non-false value, then it is used:
67 | #today = ''
68 | # Else, today_fmt is used as the format for a strftime call.
69 | #today_fmt = '%B %d, %Y'
70 |
71 | # List of patterns, relative to source directory, that match files and
72 | # directories to ignore when looking for source files.
73 | exclude_patterns = ['_build']
74 |
75 | # The reST default role (used for this markup: `text`) to use for all documents.
76 | #default_role = None
77 |
78 | # If true, '()' will be appended to :func: etc. cross-reference text.
79 | #add_function_parentheses = True
80 |
81 | # If true, the current module name will be prepended to all description
82 | # unit titles (such as .. function::).
83 | #add_module_names = True
84 |
85 | # If true, sectionauthor and moduleauthor directives will be shown in the
86 | # output. They are ignored by default.
87 | #show_authors = False
88 |
89 | # The name of the Pygments (syntax highlighting) style to use.
90 | pygments_style = 'sphinx'
91 |
92 | # A list of ignored prefixes for module index sorting.
93 | #modindex_common_prefix = []
94 |
95 |
96 | # -- Options for HTML output ---------------------------------------------------
97 |
98 | # The theme to use for HTML and HTML Help pages. See the documentation for
99 | # a list of builtin themes.
100 | html_theme = 'default' if on_rtd else 'nature'
101 |
102 | # Theme options are theme-specific and customize the look and feel of a theme
103 | # further. For a list of options available for each theme, see the
104 | # documentation.
105 | #html_theme_options = {}
106 |
107 | # Add any paths that contain custom themes here, relative to this directory.
108 | #html_theme_path = []
109 |
110 | # The name for this set of Sphinx documents. If None, it defaults to
111 | # " v documentation".
112 | #html_title = None
113 |
114 | # A shorter title for the navigation bar. Default is the same as html_title.
115 | #html_short_title = None
116 |
117 | # The name of an image file (relative to this directory) to place at the top
118 | # of the sidebar.
119 | #html_logo = None
120 |
121 | # The name of an image file (within the static path) to use as favicon of the
122 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
123 | # pixels large.
124 | #html_favicon = None
125 |
126 | # Add any paths that contain custom static files (such as style sheets) here,
127 | # relative to this directory. They are copied after the builtin static files,
128 | # so a file named "default.css" will overwrite the builtin "default.css".
129 | #html_static_path = ['_static']
130 |
131 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
132 | # using the given strftime format.
133 | #html_last_updated_fmt = '%b %d, %Y'
134 |
135 | # If true, SmartyPants will be used to convert quotes and dashes to
136 | # typographically correct entities.
137 | #html_use_smartypants = True
138 |
139 | # Custom sidebar templates, maps document names to template names.
140 | #html_sidebars = {}
141 |
142 | # Additional templates that should be rendered to pages, maps page names to
143 | # template names.
144 | #html_additional_pages = {}
145 |
146 | # If false, no module index is generated.
147 | #html_domain_indices = True
148 |
149 | # If false, no index is generated.
150 | #html_use_index = True
151 |
152 | # If true, the index is split into individual pages for each letter.
153 | #html_split_index = False
154 |
155 | # If true, links to the reST sources are added to the pages.
156 | #html_show_sourcelink = True
157 |
158 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
159 | #html_show_sphinx = True
160 |
161 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
162 | #html_show_copyright = True
163 |
164 | # If true, an OpenSearch description file will be output, and all pages will
165 | # contain a tag referring to it. The value of this option must be the
166 | # base URL from which the finished HTML is served.
167 | #html_use_opensearch = ''
168 |
169 | # This is the file name suffix for HTML files (e.g. ".xhtml").
170 | #html_file_suffix = None
171 |
172 | # Output file base name for HTML help builder.
173 | htmlhelp_basename = 'inflectiondoc'
174 |
175 |
176 | # -- Options for LaTeX output --------------------------------------------------
177 |
178 | latex_elements: Dict[str, str] = {
179 | # The paper size ('letterpaper' or 'a4paper').
180 | #'papersize': 'letterpaper',
181 |
182 | # The font size ('10pt', '11pt' or '12pt').
183 | #'pointsize': '10pt',
184 |
185 | # Additional stuff for the LaTeX preamble.
186 | #'preamble': '',
187 | }
188 |
189 | # Grouping the document tree into LaTeX files. List of tuples
190 | # (source start file, target name, title, author, documentclass [howto/manual]).
191 | latex_documents = [
192 | ('index', 'inflection.tex', u'inflection Documentation',
193 | u'Janne Vanhala', 'manual'),
194 | ]
195 |
196 | # The name of an image file (relative to this directory) to place at the top of
197 | # the title page.
198 | #latex_logo = None
199 |
200 | # For "manual" documents, if this is true, then toplevel headings are parts,
201 | # not chapters.
202 | #latex_use_parts = False
203 |
204 | # If true, show page references after internal links.
205 | #latex_show_pagerefs = False
206 |
207 | # If true, show URL addresses after external links.
208 | #latex_show_urls = False
209 |
210 | # Documents to append as an appendix to all manuals.
211 | #latex_appendices = []
212 |
213 | # If false, no module index is generated.
214 | #latex_domain_indices = True
215 |
216 |
217 | # -- Options for manual page output --------------------------------------------
218 |
219 | # One entry per manual page. List of tuples
220 | # (source start file, name, description, authors, manual section).
221 | man_pages = [
222 | ('index', 'inflection', u'inflection Documentation',
223 | [u'Janne Vanhala'], 1)
224 | ]
225 |
226 | # If true, show URL addresses after external links.
227 | #man_show_urls = False
228 |
229 |
230 | # -- Options for Texinfo output ------------------------------------------------
231 |
232 | # Grouping the document tree into Texinfo files. List of tuples
233 | # (source start file, target name, title, author,
234 | # dir menu entry, description, category)
235 | texinfo_documents = [
236 | ('index', 'inflection', u'inflection Documentation',
237 | u'Janne Vanhala', 'inflection', 'One line description of project.',
238 | 'Miscellaneous'),
239 | ]
240 |
241 | # Documents to append as an appendix to all manuals.
242 | #texinfo_appendices = []
243 |
244 | # If false, no module index is generated.
245 | #texinfo_domain_indices = True
246 |
247 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
248 | #texinfo_show_urls = 'footnote'
249 |
--------------------------------------------------------------------------------
/inflection/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | inflection
4 | ~~~~~~~~~~~~
5 |
6 | A port of Ruby on Rails' inflector to Python.
7 |
8 | :copyright: (c) 2012-2020 by Janne Vanhala
9 |
10 | :license: MIT, see LICENSE for more details.
11 | """
12 | import re
13 | import typing
14 | import unicodedata
15 |
16 | __version__ = '0.5.1'
17 |
18 | RegexReplaceList = typing.List[typing.Tuple[str, str]]
19 |
20 | PLURALS: RegexReplaceList = [
21 | (r"(?i)(quiz)$", r'\1zes'),
22 | (r"(?i)^(oxen)$", r'\1'),
23 | (r"(?i)^(ox)$", r'\1en'),
24 | (r"(?i)(m|l)ice$", r'\1ice'),
25 | (r"(?i)(m|l)ouse$", r'\1ice'),
26 | (r"(?i)(passer)s?by$", r'\1sby'),
27 | (r"(?i)(matr|vert|ind)(?:ix|ex)$", r'\1ices'),
28 | (r"(?i)(x|ch|ss|sh)$", r'\1es'),
29 | (r"(?i)([^aeiouy]|qu)y$", r'\1ies'),
30 | (r"(?i)(hive)$", r'\1s'),
31 | (r"(?i)([lr])f$", r'\1ves'),
32 | (r"(?i)([^f])fe$", r'\1ves'),
33 | (r"(?i)sis$", 'ses'),
34 | (r"(?i)([ti])a$", r'\1a'),
35 | (r"(?i)([ti])um$", r'\1a'),
36 | (r"(?i)(buffal|potat|tomat)o$", r'\1oes'),
37 | (r"(?i)(bu)s$", r'\1ses'),
38 | (r"(?i)(alias|status)$", r'\1es'),
39 | (r"(?i)(octop|vir)i$", r'\1i'),
40 | (r"(?i)(octop|vir)us$", r'\1i'),
41 | (r"(?i)^(ax|test)is$", r'\1es'),
42 | (r"(?i)s$", 's'),
43 | (r"$", 's'),
44 | ]
45 |
46 | SINGULARS: RegexReplaceList = [
47 | (r"(?i)(database)s$", r'\1'),
48 | (r"(?i)(quiz)zes$", r'\1'),
49 | (r"(?i)(matr)ices$", r'\1ix'),
50 | (r"(?i)(vert|ind)ices$", r'\1ex'),
51 | (r"(?i)(passer)sby$", r'\1by'),
52 | (r"(?i)^(ox)en", r'\1'),
53 | (r"(?i)(alias|status)(es)?$", r'\1'),
54 | (r"(?i)(octop|vir)(us|i)$", r'\1us'),
55 | (r"(?i)^(a)x[ie]s$", r'\1xis'),
56 | (r"(?i)(cris|test)(is|es)$", r'\1is'),
57 | (r"(?i)(shoe)s$", r'\1'),
58 | (r"(?i)(o)es$", r'\1'),
59 | (r"(?i)(bus)(es)?$", r'\1'),
60 | (r"(?i)(m|l)ice$", r'\1ouse'),
61 | (r"(?i)(x|ch|ss|sh)es$", r'\1'),
62 | (r"(?i)(m)ovies$", r'\1ovie'),
63 | (r"(?i)(s)eries$", r'\1eries'),
64 | (r"(?i)([^aeiouy]|qu)ies$", r'\1y'),
65 | (r"(?i)([lr])ves$", r'\1f'),
66 | (r"(?i)(tive)s$", r'\1'),
67 | (r"(?i)(hive)s$", r'\1'),
68 | (r"(?i)([^f])ves$", r'\1fe'),
69 | (r"(?i)(t)he(sis|ses)$", r"\1hesis"),
70 | (r"(?i)(s)ynop(sis|ses)$", r"\1ynopsis"),
71 | (r"(?i)(p)rogno(sis|ses)$", r"\1rognosis"),
72 | (r"(?i)(p)arenthe(sis|ses)$", r"\1arenthesis"),
73 | (r"(?i)(d)iagno(sis|ses)$", r"\1iagnosis"),
74 | (r"(?i)(b)a(sis|ses)$", r"\1asis"),
75 | (r"(?i)(a)naly(sis|ses)$", r"\1nalysis"),
76 | (r"(?i)([ti])a$", r'\1um'),
77 | (r"(?i)(n)ews$", r'\1ews'),
78 | (r"(?i)(ss)$", r'\1'),
79 | (r"(?i)s$", ''),
80 | ]
81 |
82 | UNCOUNTABLES: typing.Set[str] = {
83 | 'equipment',
84 | 'fish',
85 | 'information',
86 | 'jeans',
87 | 'money',
88 | 'rice',
89 | 'series',
90 | 'sheep',
91 | 'species'}
92 |
93 |
94 | def _irregular(singular: str, plural: str) -> None:
95 | """
96 | A convenience function to add appropriate rules to plurals and singular
97 | for irregular words.
98 |
99 | :param singular: irregular word in singular form
100 | :param plural: irregular word in plural form
101 | """
102 | def caseinsensitive(string: str) -> str:
103 | return ''.join('[' + char + char.upper() + ']' for char in string)
104 |
105 | if singular[0].upper() == plural[0].upper():
106 | PLURALS.insert(0, (
107 | r"(?i)({}){}$".format(singular[0], singular[1:]),
108 | r'\1' + plural[1:]
109 | ))
110 | PLURALS.insert(0, (
111 | r"(?i)({}){}$".format(plural[0], plural[1:]),
112 | r'\1' + plural[1:]
113 | ))
114 | SINGULARS.insert(0, (
115 | r"(?i)({}){}$".format(plural[0], plural[1:]),
116 | r'\1' + singular[1:]
117 | ))
118 | else:
119 | PLURALS.insert(0, (
120 | r"{}{}$".format(singular[0].upper(),
121 | caseinsensitive(singular[1:])),
122 | plural[0].upper() + plural[1:]
123 | ))
124 | PLURALS.insert(0, (
125 | r"{}{}$".format(singular[0].lower(),
126 | caseinsensitive(singular[1:])),
127 | plural[0].lower() + plural[1:]
128 | ))
129 | PLURALS.insert(0, (
130 | r"{}{}$".format(plural[0].upper(), caseinsensitive(plural[1:])),
131 | plural[0].upper() + plural[1:]
132 | ))
133 | PLURALS.insert(0, (
134 | r"{}{}$".format(plural[0].lower(), caseinsensitive(plural[1:])),
135 | plural[0].lower() + plural[1:]
136 | ))
137 | SINGULARS.insert(0, (
138 | r"{}{}$".format(plural[0].upper(), caseinsensitive(plural[1:])),
139 | singular[0].upper() + singular[1:]
140 | ))
141 | SINGULARS.insert(0, (
142 | r"{}{}$".format(plural[0].lower(), caseinsensitive(plural[1:])),
143 | singular[0].lower() + singular[1:]
144 | ))
145 |
146 |
147 | def camelize(string: str, uppercase_first_letter: bool = True) -> str:
148 | """
149 | Convert strings to CamelCase.
150 |
151 | Examples::
152 |
153 | >>> camelize("device_type")
154 | 'DeviceType'
155 | >>> camelize("device_type", False)
156 | 'deviceType'
157 |
158 | :func:`camelize` can be thought of as a inverse of :func:`underscore`,
159 | although there are some cases where that does not hold::
160 |
161 | >>> camelize(underscore("IOError"))
162 | 'IoError'
163 |
164 | :param uppercase_first_letter: if set to `True` :func:`camelize` converts
165 | strings to UpperCamelCase. If set to `False` :func:`camelize` produces
166 | lowerCamelCase. Defaults to `True`.
167 | """
168 | if uppercase_first_letter:
169 | return re.sub(r"(?:^|_)(.)", lambda m: m.group(1).upper(), string)
170 | else:
171 | return string[0].lower() + camelize(string)[1:]
172 |
173 |
174 | def dasherize(word: str) -> str:
175 | """Replace underscores with dashes in the string.
176 |
177 | Example::
178 |
179 | >>> dasherize("puni_puni")
180 | 'puni-puni'
181 |
182 | """
183 | return word.replace('_', '-')
184 |
185 |
186 | def humanize(word: str) -> str:
187 | """
188 | Capitalize the first word and turn underscores into spaces and strip a
189 | trailing ``"_id"``, if any. Like :func:`titleize`, this is meant for
190 | creating pretty output.
191 |
192 | Examples::
193 |
194 | >>> humanize("employee_salary")
195 | 'Employee salary'
196 | >>> humanize("author_id")
197 | 'Author'
198 |
199 | """
200 | word = re.sub(r"_id$", "", word)
201 | word = word.replace('_', ' ')
202 | word = re.sub(r"(?i)([a-z\d]*)", lambda m: m.group(1).lower(), word)
203 | word = re.sub(r"^\w", lambda m: m.group(0).upper(), word)
204 | return word
205 |
206 |
207 | def ordinal(number: int) -> str:
208 | """
209 | Return the suffix that should be added to a number to denote the position
210 | in an ordered sequence such as 1st, 2nd, 3rd, 4th.
211 |
212 | Examples::
213 |
214 | >>> ordinal(1)
215 | 'st'
216 | >>> ordinal(2)
217 | 'nd'
218 | >>> ordinal(1002)
219 | 'nd'
220 | >>> ordinal(1003)
221 | 'rd'
222 | >>> ordinal(-11)
223 | 'th'
224 | >>> ordinal(-1021)
225 | 'st'
226 |
227 | """
228 | number = abs(int(number))
229 | if number % 100 in (11, 12, 13):
230 | return "th"
231 | else:
232 | return {
233 | 1: "st",
234 | 2: "nd",
235 | 3: "rd",
236 | }.get(number % 10, "th")
237 |
238 |
239 | def ordinalize(number: int) -> str:
240 | """
241 | Turn a number into an ordinal string used to denote the position in an
242 | ordered sequence such as 1st, 2nd, 3rd, 4th.
243 |
244 | Examples::
245 |
246 | >>> ordinalize(1)
247 | '1st'
248 | >>> ordinalize(2)
249 | '2nd'
250 | >>> ordinalize(1002)
251 | '1002nd'
252 | >>> ordinalize(1003)
253 | '1003rd'
254 | >>> ordinalize(-11)
255 | '-11th'
256 | >>> ordinalize(-1021)
257 | '-1021st'
258 |
259 | """
260 | return "{}{}".format(number, ordinal(number))
261 |
262 |
263 | def parameterize(string: str, separator: str = '-') -> str:
264 | """
265 | Replace special characters in a string so that it may be used as part of a
266 | 'pretty' URL.
267 |
268 | Example::
269 |
270 | >>> parameterize(u"Donald E. Knuth")
271 | 'donald-e-knuth'
272 |
273 | """
274 | string = transliterate(string)
275 | # Turn unwanted chars into the separator
276 | string = re.sub(r"(?i)[^a-z0-9\-_]+", separator, string)
277 | if separator:
278 | re_sep = re.escape(separator)
279 | # No more than one of the separator in a row.
280 | string = re.sub(r'%s{2,}' % re_sep, separator, string)
281 | # Remove leading/trailing separator.
282 | string = re.sub(r"(?i)^{sep}|{sep}$".format(sep=re_sep), '', string)
283 |
284 | return string.lower()
285 |
286 |
287 | def pluralize(word: str) -> str:
288 | """
289 | Return the plural form of a word.
290 |
291 | Examples::
292 |
293 | >>> pluralize("posts")
294 | 'posts'
295 | >>> pluralize("octopus")
296 | 'octopi'
297 | >>> pluralize("sheep")
298 | 'sheep'
299 | >>> pluralize("CamelOctopus")
300 | 'CamelOctopi'
301 |
302 | """
303 | if not word or word.lower() in UNCOUNTABLES:
304 | return word
305 | else:
306 | for rule, replacement in PLURALS:
307 | if re.search(rule, word):
308 | return re.sub(rule, replacement, word)
309 | return word
310 |
311 |
312 | def singularize(word: str) -> str:
313 | """
314 | Return the singular form of a word, the reverse of :func:`pluralize`.
315 |
316 | Examples::
317 |
318 | >>> singularize("posts")
319 | 'post'
320 | >>> singularize("octopi")
321 | 'octopus'
322 | >>> singularize("sheep")
323 | 'sheep'
324 | >>> singularize("word")
325 | 'word'
326 | >>> singularize("CamelOctopi")
327 | 'CamelOctopus'
328 |
329 | """
330 | for inflection in UNCOUNTABLES:
331 | if re.search(r'(?i)\b(%s)\Z' % inflection, word):
332 | return word
333 |
334 | for rule, replacement in SINGULARS:
335 | if re.search(rule, word):
336 | return re.sub(rule, replacement, word)
337 | return word
338 |
339 |
340 | def tableize(word: str) -> str:
341 | """
342 | Create the name of a table like Rails does for models to table names. This
343 | method uses the :func:`pluralize` method on the last word in the string.
344 |
345 | Examples::
346 |
347 | >>> tableize('RawScaledScorer')
348 | 'raw_scaled_scorers'
349 | >>> tableize('egg_and_ham')
350 | 'egg_and_hams'
351 | >>> tableize('fancyCategory')
352 | 'fancy_categories'
353 | """
354 | return pluralize(underscore(word))
355 |
356 |
357 | def titleize(word: str) -> str:
358 | """
359 | Capitalize all the words and replace some characters in the string to
360 | create a nicer looking title. :func:`titleize` is meant for creating pretty
361 | output.
362 |
363 | Examples::
364 |
365 | >>> titleize("man from the boondocks")
366 | 'Man From The Boondocks'
367 | >>> titleize("x-men: the last stand")
368 | 'X Men: The Last Stand'
369 | >>> titleize("TheManWithoutAPast")
370 | 'The Man Without A Past'
371 | >>> titleize("raiders_of_the_lost_ark")
372 | 'Raiders Of The Lost Ark'
373 |
374 | """
375 | return re.sub(
376 | r"\b('?\w)",
377 | lambda match: match.group(1).capitalize(),
378 | humanize(underscore(word)).title()
379 | )
380 |
381 |
382 | def transliterate(string: str) -> str:
383 | """
384 | Replace non-ASCII characters with an ASCII approximation. If no
385 | approximation exists, the non-ASCII character is ignored. The string must
386 | be ``unicode``.
387 |
388 | Examples::
389 |
390 | >>> transliterate('älämölö')
391 | 'alamolo'
392 | >>> transliterate('Ærøskøbing')
393 | 'rskbing'
394 |
395 | """
396 | normalized = unicodedata.normalize('NFKD', string)
397 | return normalized.encode('ascii', 'ignore').decode('ascii')
398 |
399 |
400 | def underscore(word: str) -> str:
401 | """
402 | Make an underscored, lowercase form from the expression in the string.
403 |
404 | Example::
405 |
406 | >>> underscore("DeviceType")
407 | 'device_type'
408 |
409 | As a rule of thumb you can think of :func:`underscore` as the inverse of
410 | :func:`camelize`, though there are cases where that does not hold::
411 |
412 | >>> camelize(underscore("IOError"))
413 | 'IoError'
414 |
415 | """
416 | word = re.sub(r"([A-Z]+)([A-Z][a-z])", r'\1_\2', word)
417 | word = re.sub(r"([a-z\d])([A-Z])", r'\1_\2', word)
418 | word = word.replace("-", "_")
419 | return word.lower()
420 |
421 |
422 | _irregular('person', 'people')
423 | _irregular('man', 'men')
424 | _irregular('human', 'humans')
425 | _irregular('child', 'children')
426 | _irregular('sex', 'sexes')
427 | _irregular('move', 'moves')
428 | _irregular('cow', 'kine')
429 | _irregular('zombie', 'zombies')
430 |
--------------------------------------------------------------------------------
/test_inflection.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import typing
3 |
4 | import pytest
5 |
6 | import inflection
7 |
8 | TestParameters = typing.Tuple[typing.Tuple[str, str], ...]
9 |
10 | SINGULAR_TO_PLURAL: TestParameters = (
11 | ("search", "searches"),
12 | ("switch", "switches"),
13 | ("fix", "fixes"),
14 | ("box", "boxes"),
15 | ("process", "processes"),
16 | ("address", "addresses"),
17 | ("case", "cases"),
18 | ("stack", "stacks"),
19 | ("wish", "wishes"),
20 | ("fish", "fish"),
21 | ("jeans", "jeans"),
22 | ("funky jeans", "funky jeans"),
23 |
24 | ("category", "categories"),
25 | ("query", "queries"),
26 | ("ability", "abilities"),
27 | ("agency", "agencies"),
28 | ("movie", "movies"),
29 |
30 | ("archive", "archives"),
31 |
32 | ("index", "indices"),
33 |
34 | ("wife", "wives"),
35 | ("safe", "saves"),
36 | ("half", "halves"),
37 |
38 | ("move", "moves"),
39 |
40 | ("salesperson", "salespeople"),
41 | ("person", "people"),
42 |
43 | ("spokesman", "spokesmen"),
44 | ("man", "men"),
45 | ("woman", "women"),
46 |
47 | ("basis", "bases"),
48 | ("diagnosis", "diagnoses"),
49 | ("diagnosis_a", "diagnosis_as"),
50 |
51 | ("datum", "data"),
52 | ("medium", "media"),
53 | ("stadium", "stadia"),
54 | ("analysis", "analyses"),
55 |
56 | ("node_child", "node_children"),
57 | ("child", "children"),
58 |
59 | ("experience", "experiences"),
60 | ("day", "days"),
61 |
62 | ("comment", "comments"),
63 | ("foobar", "foobars"),
64 | ("newsletter", "newsletters"),
65 |
66 | ("old_news", "old_news"),
67 | ("news", "news"),
68 |
69 | ("series", "series"),
70 | ("species", "species"),
71 |
72 | ("quiz", "quizzes"),
73 |
74 | ("perspective", "perspectives"),
75 |
76 | ("ox", "oxen"),
77 | ("passerby", "passersby"),
78 | ("photo", "photos"),
79 | ("buffalo", "buffaloes"),
80 | ("tomato", "tomatoes"),
81 | ("potato", "potatoes"),
82 | ("dwarf", "dwarves"),
83 | ("elf", "elves"),
84 | ("information", "information"),
85 | ("equipment", "equipment"),
86 | ("bus", "buses"),
87 | ("status", "statuses"),
88 | ("status_code", "status_codes"),
89 | ("mouse", "mice"),
90 |
91 | ("louse", "lice"),
92 | ("house", "houses"),
93 | ("octopus", "octopi"),
94 | ("virus", "viri"),
95 | ("alias", "aliases"),
96 | ("portfolio", "portfolios"),
97 |
98 | ("vertex", "vertices"),
99 | ("matrix", "matrices"),
100 | ("matrix_fu", "matrix_fus"),
101 |
102 | ("axis", "axes"),
103 | ("testis", "testes"),
104 | ("crisis", "crises"),
105 |
106 | ("rice", "rice"),
107 | ("shoe", "shoes"),
108 |
109 | ("horse", "horses"),
110 | ("prize", "prizes"),
111 | ("edge", "edges"),
112 |
113 | ("cow", "kine"),
114 | ("database", "databases"),
115 | ("human", "humans")
116 | )
117 |
118 | CAMEL_TO_UNDERSCORE: TestParameters = (
119 | ("Product", "product"),
120 | ("SpecialGuest", "special_guest"),
121 | ("ApplicationController", "application_controller"),
122 | ("Area51Controller", "area51_controller"),
123 | )
124 |
125 | CAMEL_TO_UNDERSCORE_WITHOUT_REVERSE: TestParameters = (
126 | ("HTMLTidy", "html_tidy"),
127 | ("HTMLTidyGenerator", "html_tidy_generator"),
128 | ("FreeBSD", "free_bsd"),
129 | ("HTML", "html"),
130 | )
131 |
132 | STRING_TO_PARAMETERIZED: TestParameters = (
133 | (u"Donald E. Knuth", "donald-e-knuth"),
134 | (
135 | u"Random text with *(bad)* characters",
136 | "random-text-with-bad-characters"
137 | ),
138 | (u"Allow_Under_Scores", "allow_under_scores"),
139 | (u"Trailing bad characters!@#", "trailing-bad-characters"),
140 | (u"!@#Leading bad characters", "leading-bad-characters"),
141 | (u"Squeeze separators", "squeeze-separators"),
142 | (u"Test with + sign", "test-with-sign"),
143 | (u"Test with malformed utf8 \251", "test-with-malformed-utf8"),
144 | )
145 |
146 | STRING_TO_PARAMETERIZE_WITH_NO_SEPARATOR: TestParameters = (
147 | (u"Donald E. Knuth", "donaldeknuth"),
148 | (u"With-some-dashes", "with-some-dashes"),
149 | (u"Random text with *(bad)* characters", "randomtextwithbadcharacters"),
150 | (u"Trailing bad characters!@#", "trailingbadcharacters"),
151 | (u"!@#Leading bad characters", "leadingbadcharacters"),
152 | (u"Squeeze separators", "squeezeseparators"),
153 | (u"Test with + sign", "testwithsign"),
154 | (u"Test with malformed utf8 \251", "testwithmalformedutf8"),
155 | )
156 |
157 | STRING_TO_PARAMETERIZE_WITH_UNDERSCORE: TestParameters = (
158 | (u"Donald E. Knuth", "donald_e_knuth"),
159 | (
160 | u"Random text with *(bad)* characters",
161 | "random_text_with_bad_characters"
162 | ),
163 | (u"With-some-dashes", "with-some-dashes"),
164 | (u"Retain_underscore", "retain_underscore"),
165 | (u"Trailing bad characters!@#", "trailing_bad_characters"),
166 | (u"!@#Leading bad characters", "leading_bad_characters"),
167 | (u"Squeeze separators", "squeeze_separators"),
168 | (u"Test with + sign", "test_with_sign"),
169 | (u"Test with malformed utf8 \251", "test_with_malformed_utf8"),
170 | )
171 |
172 | STRING_TO_PARAMETERIZED_AND_NORMALIZED: TestParameters = (
173 | (u"Malmö", "malmo"),
174 | (u"Garçons", "garcons"),
175 | (u"Ops\331", "opsu"),
176 | (u"Ærøskøbing", "rskbing"),
177 | (u"Aßlar", "alar"),
178 | (u"Japanese: 日本語", "japanese"),
179 | )
180 |
181 | UNDERSCORE_TO_HUMAN: TestParameters = (
182 | ("employee_salary", "Employee salary"),
183 | ("employee_id", "Employee"),
184 | ("underground", "Underground"),
185 | )
186 |
187 | MIXTURE_TO_TITLEIZED: TestParameters = (
188 | ('active_record', 'Active Record'),
189 | ('ActiveRecord', 'Active Record'),
190 | ('action web service', 'Action Web Service'),
191 | ('Action Web Service', 'Action Web Service'),
192 | ('Action web service', 'Action Web Service'),
193 | ('actionwebservice', 'Actionwebservice'),
194 | ('Actionwebservice', 'Actionwebservice'),
195 | ("david's code", "David's Code"),
196 | ("David's code", "David's Code"),
197 | ("david's Code", "David's Code"),
198 | ("ana índia", "Ana Índia"),
199 | ("Ana Índia", "Ana Índia"),
200 | )
201 |
202 |
203 | ORDINAL_NUMBERS: TestParameters = (
204 | ("-1", "-1st"),
205 | ("-2", "-2nd"),
206 | ("-3", "-3rd"),
207 | ("-4", "-4th"),
208 | ("-5", "-5th"),
209 | ("-6", "-6th"),
210 | ("-7", "-7th"),
211 | ("-8", "-8th"),
212 | ("-9", "-9th"),
213 | ("-10", "-10th"),
214 | ("-11", "-11th"),
215 | ("-12", "-12th"),
216 | ("-13", "-13th"),
217 | ("-14", "-14th"),
218 | ("-20", "-20th"),
219 | ("-21", "-21st"),
220 | ("-22", "-22nd"),
221 | ("-23", "-23rd"),
222 | ("-24", "-24th"),
223 | ("-100", "-100th"),
224 | ("-101", "-101st"),
225 | ("-102", "-102nd"),
226 | ("-103", "-103rd"),
227 | ("-104", "-104th"),
228 | ("-110", "-110th"),
229 | ("-111", "-111th"),
230 | ("-112", "-112th"),
231 | ("-113", "-113th"),
232 | ("-1000", "-1000th"),
233 | ("-1001", "-1001st"),
234 | ("0", "0th"),
235 | ("1", "1st"),
236 | ("2", "2nd"),
237 | ("3", "3rd"),
238 | ("4", "4th"),
239 | ("5", "5th"),
240 | ("6", "6th"),
241 | ("7", "7th"),
242 | ("8", "8th"),
243 | ("9", "9th"),
244 | ("10", "10th"),
245 | ("11", "11th"),
246 | ("12", "12th"),
247 | ("13", "13th"),
248 | ("14", "14th"),
249 | ("20", "20th"),
250 | ("21", "21st"),
251 | ("22", "22nd"),
252 | ("23", "23rd"),
253 | ("24", "24th"),
254 | ("100", "100th"),
255 | ("101", "101st"),
256 | ("102", "102nd"),
257 | ("103", "103rd"),
258 | ("104", "104th"),
259 | ("110", "110th"),
260 | ("111", "111th"),
261 | ("112", "112th"),
262 | ("113", "113th"),
263 | ("1000", "1000th"),
264 | ("1001", "1001st"),
265 | )
266 |
267 | UNDERSCORES_TO_DASHES: TestParameters = (
268 | ("street", "street"),
269 | ("street_address", "street-address"),
270 | ("person_street_address", "person-street-address"),
271 | )
272 |
273 | STRING_TO_TABLEIZE: TestParameters = (
274 | ("person", "people"),
275 | ("Country", "countries"),
276 | ("ChildToy", "child_toys"),
277 | ("_RecipeIngredient", "_recipe_ingredients"),
278 | )
279 |
280 |
281 | def test_pluralize_plurals() -> None:
282 | assert "plurals" == inflection.pluralize("plurals")
283 | assert "Plurals" == inflection.pluralize("Plurals")
284 |
285 |
286 | def test_pluralize_empty_string() -> None:
287 | assert "" == inflection.pluralize("")
288 |
289 |
290 | @pytest.mark.parametrize(
291 | ("word", ),
292 | [(word,) for word in inflection.UNCOUNTABLES]
293 | )
294 | def test_uncountability(word: str) -> None:
295 | assert word == inflection.singularize(word)
296 | assert word == inflection.pluralize(word)
297 | assert inflection.pluralize(word) == inflection.singularize(word)
298 |
299 |
300 | def test_uncountable_word_is_not_greedy() -> None:
301 | uncountable_word = "ors"
302 | countable_word = "sponsor"
303 |
304 | inflection.UNCOUNTABLES.add(uncountable_word)
305 | try:
306 | assert uncountable_word == inflection.singularize(uncountable_word)
307 | assert uncountable_word == inflection.pluralize(uncountable_word)
308 | assert(
309 | inflection.pluralize(uncountable_word) ==
310 | inflection.singularize(uncountable_word)
311 | )
312 |
313 | assert "sponsor" == inflection.singularize(countable_word)
314 | assert "sponsors" == inflection.pluralize(countable_word)
315 | assert (
316 | "sponsor" ==
317 | inflection.singularize(inflection.pluralize(countable_word))
318 | )
319 | finally:
320 | inflection.UNCOUNTABLES.remove(uncountable_word)
321 |
322 |
323 | @pytest.mark.parametrize(("singular", "plural"), SINGULAR_TO_PLURAL)
324 | def test_pluralize_singular(singular: str, plural: str) -> None:
325 | assert plural == inflection.pluralize(singular)
326 | assert plural.capitalize() == inflection.pluralize(singular.capitalize())
327 |
328 |
329 | @pytest.mark.parametrize(("singular", "plural"), SINGULAR_TO_PLURAL)
330 | def test_singularize_plural(singular: str, plural: str) -> None:
331 | assert singular == inflection.singularize(plural)
332 | assert singular.capitalize() == inflection.singularize(plural.capitalize())
333 |
334 |
335 | @pytest.mark.parametrize(("singular", "plural"), SINGULAR_TO_PLURAL)
336 | def test_pluralize_plural(singular: str, plural: str) -> None:
337 | assert plural == inflection.pluralize(plural)
338 | assert plural.capitalize() == inflection.pluralize(plural.capitalize())
339 |
340 |
341 | @pytest.mark.parametrize(("before", "titleized"), MIXTURE_TO_TITLEIZED)
342 | def test_titleize(before: str, titleized: str) -> None:
343 | assert titleized == inflection.titleize(before)
344 |
345 |
346 | @pytest.mark.parametrize(("camel", "underscore"), CAMEL_TO_UNDERSCORE)
347 | def test_camelize(camel: str, underscore: str) -> None:
348 | assert camel == inflection.camelize(underscore)
349 |
350 |
351 | def test_camelize_with_lower_downcases_the_first_letter() -> None:
352 | assert 'capital' == inflection.camelize('Capital', False)
353 |
354 |
355 | def test_camelize_with_underscores() -> None:
356 | assert "CamelCase" == inflection.camelize('Camel_Case')
357 |
358 |
359 | @pytest.mark.parametrize(
360 | ("camel", "underscore"),
361 | CAMEL_TO_UNDERSCORE + CAMEL_TO_UNDERSCORE_WITHOUT_REVERSE
362 | )
363 | def test_underscore(camel: str, underscore: str) -> None:
364 | assert underscore == inflection.underscore(camel)
365 |
366 |
367 | @pytest.mark.parametrize(
368 | ("some_string", "parameterized_string"),
369 | STRING_TO_PARAMETERIZED
370 | )
371 | def test_parameterize(some_string: str, parameterized_string: str) -> None:
372 | assert parameterized_string == inflection.parameterize(some_string)
373 |
374 |
375 | @pytest.mark.parametrize(
376 | ("some_string", "parameterized_string"),
377 | STRING_TO_PARAMETERIZED_AND_NORMALIZED
378 | )
379 | def test_parameterize_and_normalize(some_string: str, parameterized_string: str) -> None:
380 | assert parameterized_string == inflection.parameterize(some_string)
381 |
382 |
383 | @pytest.mark.parametrize(
384 | ("some_string", "parameterized_string"),
385 | STRING_TO_PARAMETERIZE_WITH_UNDERSCORE
386 | )
387 | def test_parameterize_with_custom_separator(some_string: str, parameterized_string: str) -> None:
388 | assert parameterized_string == inflection.parameterize(some_string, '_')
389 |
390 |
391 | @pytest.mark.parametrize(
392 | ("some_string", "parameterized_string"),
393 | STRING_TO_PARAMETERIZED
394 | )
395 | def test_parameterize_with_multi_character_separator(
396 | some_string: str,
397 | parameterized_string: str
398 | ) -> None:
399 | assert (
400 | parameterized_string.replace('-', '__sep__') ==
401 | inflection.parameterize(some_string, '__sep__')
402 | )
403 |
404 |
405 | @pytest.mark.parametrize(
406 | ("some_string", "parameterized_string"),
407 | STRING_TO_PARAMETERIZE_WITH_NO_SEPARATOR
408 | )
409 | def test_parameterize_with_no_separator(some_string: str, parameterized_string: str) -> None:
410 | assert parameterized_string == inflection.parameterize(some_string, '')
411 |
412 |
413 | @pytest.mark.parametrize(("underscore", "human"), UNDERSCORE_TO_HUMAN)
414 | def test_humanize(underscore: str, human: str) -> None:
415 | assert human == inflection.humanize(underscore)
416 |
417 |
418 | @pytest.mark.parametrize(("number", "ordinalized"), ORDINAL_NUMBERS)
419 | def test_ordinal(number: str, ordinalized: str) -> None:
420 | assert ordinalized == number + inflection.ordinal(int(number))
421 |
422 |
423 | @pytest.mark.parametrize(("number", "ordinalized"), ORDINAL_NUMBERS)
424 | def test_ordinalize(number: str, ordinalized: str) -> None:
425 | assert ordinalized == inflection.ordinalize(int(number))
426 |
427 |
428 | @pytest.mark.parametrize(("input", "expected"), UNDERSCORES_TO_DASHES)
429 | def test_dasherize(input: str, expected: str) -> None:
430 | assert inflection.dasherize(input) == expected
431 |
432 |
433 | @pytest.mark.parametrize(("string", "tableized"), STRING_TO_TABLEIZE)
434 | def test_tableize(string: str, tableized: str) -> None:
435 | assert inflection.tableize(string) == tableized
436 |
--------------------------------------------------------------------------------