Spaces:
Runtime error
Runtime error
# -*- coding: utf-8 -*- | |
""" | |
inflection | |
~~~~~~~~~~~~ | |
A port of Ruby on Rails' inflector to Python. | |
:copyright: (c) 2012-2020 by Janne Vanhala | |
:license: MIT, see LICENSE for more details. | |
""" | |
import re | |
import unicodedata | |
__version__ = '0.5.1' | |
PLURALS = [ | |
(r"(?i)(quiz)$", r'\1zes'), | |
(r"(?i)^(oxen)$", r'\1'), | |
(r"(?i)^(ox)$", r'\1en'), | |
(r"(?i)(m|l)ice$", r'\1ice'), | |
(r"(?i)(m|l)ouse$", r'\1ice'), | |
(r"(?i)(passer)s?by$", r'\1sby'), | |
(r"(?i)(matr|vert|ind)(?:ix|ex)$", r'\1ices'), | |
(r"(?i)(x|ch|ss|sh)$", r'\1es'), | |
(r"(?i)([^aeiouy]|qu)y$", r'\1ies'), | |
(r"(?i)(hive)$", r'\1s'), | |
(r"(?i)([lr])f$", r'\1ves'), | |
(r"(?i)([^f])fe$", r'\1ves'), | |
(r"(?i)sis$", 'ses'), | |
(r"(?i)([ti])a$", r'\1a'), | |
(r"(?i)([ti])um$", r'\1a'), | |
(r"(?i)(buffal|potat|tomat)o$", r'\1oes'), | |
(r"(?i)(bu)s$", r'\1ses'), | |
(r"(?i)(alias|status)$", r'\1es'), | |
(r"(?i)(octop|vir)i$", r'\1i'), | |
(r"(?i)(octop|vir)us$", r'\1i'), | |
(r"(?i)^(ax|test)is$", r'\1es'), | |
(r"(?i)s$", 's'), | |
(r"$", 's'), | |
] | |
SINGULARS = [ | |
(r"(?i)(database)s$", r'\1'), | |
(r"(?i)(quiz)zes$", r'\1'), | |
(r"(?i)(matr)ices$", r'\1ix'), | |
(r"(?i)(vert|ind)ices$", r'\1ex'), | |
(r"(?i)(passer)sby$", r'\1by'), | |
(r"(?i)^(ox)en", r'\1'), | |
(r"(?i)(alias|status)(es)?$", r'\1'), | |
(r"(?i)(octop|vir)(us|i)$", r'\1us'), | |
(r"(?i)^(a)x[ie]s$", r'\1xis'), | |
(r"(?i)(cris|test)(is|es)$", r'\1is'), | |
(r"(?i)(shoe)s$", r'\1'), | |
(r"(?i)(o)es$", r'\1'), | |
(r"(?i)(bus)(es)?$", r'\1'), | |
(r"(?i)(m|l)ice$", r'\1ouse'), | |
(r"(?i)(x|ch|ss|sh)es$", r'\1'), | |
(r"(?i)(m)ovies$", r'\1ovie'), | |
(r"(?i)(s)eries$", r'\1eries'), | |
(r"(?i)([^aeiouy]|qu)ies$", r'\1y'), | |
(r"(?i)([lr])ves$", r'\1f'), | |
(r"(?i)(tive)s$", r'\1'), | |
(r"(?i)(hive)s$", r'\1'), | |
(r"(?i)([^f])ves$", r'\1fe'), | |
(r"(?i)(t)he(sis|ses)$", r"\1hesis"), | |
(r"(?i)(s)ynop(sis|ses)$", r"\1ynopsis"), | |
(r"(?i)(p)rogno(sis|ses)$", r"\1rognosis"), | |
(r"(?i)(p)arenthe(sis|ses)$", r"\1arenthesis"), | |
(r"(?i)(d)iagno(sis|ses)$", r"\1iagnosis"), | |
(r"(?i)(b)a(sis|ses)$", r"\1asis"), | |
(r"(?i)(a)naly(sis|ses)$", r"\1nalysis"), | |
(r"(?i)([ti])a$", r'\1um'), | |
(r"(?i)(n)ews$", r'\1ews'), | |
(r"(?i)(ss)$", r'\1'), | |
(r"(?i)s$", ''), | |
] | |
UNCOUNTABLES = { | |
'equipment', | |
'fish', | |
'information', | |
'jeans', | |
'money', | |
'rice', | |
'series', | |
'sheep', | |
'species'} | |
def _irregular(singular: str, plural: str) -> None: | |
""" | |
A convenience function to add appropriate rules to plurals and singular | |
for irregular words. | |
:param singular: irregular word in singular form | |
:param plural: irregular word in plural form | |
""" | |
def caseinsensitive(string: str) -> str: | |
return ''.join('[' + char + char.upper() + ']' for char in string) | |
if singular[0].upper() == plural[0].upper(): | |
PLURALS.insert(0, ( | |
r"(?i)({}){}$".format(singular[0], singular[1:]), | |
r'\1' + plural[1:] | |
)) | |
PLURALS.insert(0, ( | |
r"(?i)({}){}$".format(plural[0], plural[1:]), | |
r'\1' + plural[1:] | |
)) | |
SINGULARS.insert(0, ( | |
r"(?i)({}){}$".format(plural[0], plural[1:]), | |
r'\1' + singular[1:] | |
)) | |
else: | |
PLURALS.insert(0, ( | |
r"{}{}$".format(singular[0].upper(), | |
caseinsensitive(singular[1:])), | |
plural[0].upper() + plural[1:] | |
)) | |
PLURALS.insert(0, ( | |
r"{}{}$".format(singular[0].lower(), | |
caseinsensitive(singular[1:])), | |
plural[0].lower() + plural[1:] | |
)) | |
PLURALS.insert(0, ( | |
r"{}{}$".format(plural[0].upper(), caseinsensitive(plural[1:])), | |
plural[0].upper() + plural[1:] | |
)) | |
PLURALS.insert(0, ( | |
r"{}{}$".format(plural[0].lower(), caseinsensitive(plural[1:])), | |
plural[0].lower() + plural[1:] | |
)) | |
SINGULARS.insert(0, ( | |
r"{}{}$".format(plural[0].upper(), caseinsensitive(plural[1:])), | |
singular[0].upper() + singular[1:] | |
)) | |
SINGULARS.insert(0, ( | |
r"{}{}$".format(plural[0].lower(), caseinsensitive(plural[1:])), | |
singular[0].lower() + singular[1:] | |
)) | |
def camelize(string: str, uppercase_first_letter: bool = True) -> str: | |
""" | |
Convert strings to CamelCase. | |
Examples:: | |
>>> camelize("device_type") | |
'DeviceType' | |
>>> camelize("device_type", False) | |
'deviceType' | |
:func:`camelize` can be thought of as a inverse of :func:`underscore`, | |
although there are some cases where that does not hold:: | |
>>> camelize(underscore("IOError")) | |
'IoError' | |
:param uppercase_first_letter: if set to `True` :func:`camelize` converts | |
strings to UpperCamelCase. If set to `False` :func:`camelize` produces | |
lowerCamelCase. Defaults to `True`. | |
""" | |
if uppercase_first_letter: | |
return re.sub(r"(?:^|_)(.)", lambda m: m.group(1).upper(), string) | |
else: | |
return string[0].lower() + camelize(string)[1:] | |
def dasherize(word: str) -> str: | |
"""Replace underscores with dashes in the string. | |
Example:: | |
>>> dasherize("puni_puni") | |
'puni-puni' | |
""" | |
return word.replace('_', '-') | |
def humanize(word: str) -> str: | |
""" | |
Capitalize the first word and turn underscores into spaces and strip a | |
trailing ``"_id"``, if any. Like :func:`titleize`, this is meant for | |
creating pretty output. | |
Examples:: | |
>>> humanize("employee_salary") | |
'Employee salary' | |
>>> humanize("author_id") | |
'Author' | |
""" | |
word = re.sub(r"_id$", "", word) | |
word = word.replace('_', ' ') | |
word = re.sub(r"(?i)([a-z\d]*)", lambda m: m.group(1).lower(), word) | |
word = re.sub(r"^\w", lambda m: m.group(0).upper(), word) | |
return word | |
def ordinal(number: int) -> str: | |
""" | |
Return the suffix that should be added to a number to denote the position | |
in an ordered sequence such as 1st, 2nd, 3rd, 4th. | |
Examples:: | |
>>> ordinal(1) | |
'st' | |
>>> ordinal(2) | |
'nd' | |
>>> ordinal(1002) | |
'nd' | |
>>> ordinal(1003) | |
'rd' | |
>>> ordinal(-11) | |
'th' | |
>>> ordinal(-1021) | |
'st' | |
""" | |
number = abs(int(number)) | |
if number % 100 in (11, 12, 13): | |
return "th" | |
else: | |
return { | |
1: "st", | |
2: "nd", | |
3: "rd", | |
}.get(number % 10, "th") | |
def ordinalize(number: int) -> str: | |
""" | |
Turn a number into an ordinal string used to denote the position in an | |
ordered sequence such as 1st, 2nd, 3rd, 4th. | |
Examples:: | |
>>> ordinalize(1) | |
'1st' | |
>>> ordinalize(2) | |
'2nd' | |
>>> ordinalize(1002) | |
'1002nd' | |
>>> ordinalize(1003) | |
'1003rd' | |
>>> ordinalize(-11) | |
'-11th' | |
>>> ordinalize(-1021) | |
'-1021st' | |
""" | |
return "{}{}".format(number, ordinal(number)) | |
def parameterize(string: str, separator: str = '-') -> str: | |
""" | |
Replace special characters in a string so that it may be used as part of a | |
'pretty' URL. | |
Example:: | |
>>> parameterize(u"Donald E. Knuth") | |
'donald-e-knuth' | |
""" | |
string = transliterate(string) | |
# Turn unwanted chars into the separator | |
string = re.sub(r"(?i)[^a-z0-9\-_]+", separator, string) | |
if separator: | |
re_sep = re.escape(separator) | |
# No more than one of the separator in a row. | |
string = re.sub(r'%s{2,}' % re_sep, separator, string) | |
# Remove leading/trailing separator. | |
string = re.sub(r"(?i)^{sep}|{sep}$".format(sep=re_sep), '', string) | |
return string.lower() | |
def pluralize(word: str) -> str: | |
""" | |
Return the plural form of a word. | |
Examples:: | |
>>> pluralize("posts") | |
'posts' | |
>>> pluralize("octopus") | |
'octopi' | |
>>> pluralize("sheep") | |
'sheep' | |
>>> pluralize("CamelOctopus") | |
'CamelOctopi' | |
""" | |
if not word or word.lower() in UNCOUNTABLES: | |
return word | |
else: | |
for rule, replacement in PLURALS: | |
if re.search(rule, word): | |
return re.sub(rule, replacement, word) | |
return word | |
def singularize(word: str) -> str: | |
""" | |
Return the singular form of a word, the reverse of :func:`pluralize`. | |
Examples:: | |
>>> singularize("posts") | |
'post' | |
>>> singularize("octopi") | |
'octopus' | |
>>> singularize("sheep") | |
'sheep' | |
>>> singularize("word") | |
'word' | |
>>> singularize("CamelOctopi") | |
'CamelOctopus' | |
""" | |
for inflection in UNCOUNTABLES: | |
if re.search(r'(?i)\b(%s)\Z' % inflection, word): | |
return word | |
for rule, replacement in SINGULARS: | |
if re.search(rule, word): | |
return re.sub(rule, replacement, word) | |
return word | |
def tableize(word: str) -> str: | |
""" | |
Create the name of a table like Rails does for models to table names. This | |
method uses the :func:`pluralize` method on the last word in the string. | |
Examples:: | |
>>> tableize('RawScaledScorer') | |
'raw_scaled_scorers' | |
>>> tableize('egg_and_ham') | |
'egg_and_hams' | |
>>> tableize('fancyCategory') | |
'fancy_categories' | |
""" | |
return pluralize(underscore(word)) | |
def titleize(word: str) -> str: | |
""" | |
Capitalize all the words and replace some characters in the string to | |
create a nicer looking title. :func:`titleize` is meant for creating pretty | |
output. | |
Examples:: | |
>>> titleize("man from the boondocks") | |
'Man From The Boondocks' | |
>>> titleize("x-men: the last stand") | |
'X Men: The Last Stand' | |
>>> titleize("TheManWithoutAPast") | |
'The Man Without A Past' | |
>>> titleize("raiders_of_the_lost_ark") | |
'Raiders Of The Lost Ark' | |
""" | |
return re.sub( | |
r"\b('?\w)", | |
lambda match: match.group(1).capitalize(), | |
humanize(underscore(word)).title() | |
) | |
def transliterate(string: str) -> str: | |
""" | |
Replace non-ASCII characters with an ASCII approximation. If no | |
approximation exists, the non-ASCII character is ignored. The string must | |
be ``unicode``. | |
Examples:: | |
>>> transliterate('älämölö') | |
'alamolo' | |
>>> transliterate('Ærøskøbing') | |
'rskbing' | |
""" | |
normalized = unicodedata.normalize('NFKD', string) | |
return normalized.encode('ascii', 'ignore').decode('ascii') | |
def underscore(word: str) -> str: | |
""" | |
Make an underscored, lowercase form from the expression in the string. | |
Example:: | |
>>> underscore("DeviceType") | |
'device_type' | |
As a rule of thumb you can think of :func:`underscore` as the inverse of | |
:func:`camelize`, though there are cases where that does not hold:: | |
>>> camelize(underscore("IOError")) | |
'IoError' | |
""" | |
word = re.sub(r"([A-Z]+)([A-Z][a-z])", r'\1_\2', word) | |
word = re.sub(r"([a-z\d])([A-Z])", r'\1_\2', word) | |
word = word.replace("-", "_") | |
return word.lower() | |
_irregular('person', 'people') | |
_irregular('man', 'men') | |
_irregular('human', 'humans') | |
_irregular('child', 'children') | |
_irregular('sex', 'sexes') | |
_irregular('move', 'moves') | |
_irregular('cow', 'kine') | |
_irregular('zombie', 'zombies') | |