Created
December 23, 2015 22:15
-
-
Save skizzerz/68711a48d2c25f75d487 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
import string | |
""" Custom formatter mimicing str.format() with additional capabilities. | |
A format string can now additionally take the form: | |
{literal|:format_spec} | |
This form will treat the text "literal" as the text to be formatted | |
instead of attempting to get the value from the passed-in arguments. | |
Furthermore, the built-in format_spec mini language has been expanded. | |
The following may now appear as the "type": | |
plural[(int)] - converts the value to plural form | |
num(int) - converts the value to plural form and prefixes with int | |
list[(sep=", ")] - converts value to list separated by sep | |
is - returns "is" if value is 1 or "are" otherwise (value must be an int) | |
was - returns "was" if value is 1 or "were" otherwise | |
an - prefixes value with "a" or "an" | |
If the # specifier is used with num or an, it will cause the prefix | |
to be bolded. If it is used with list, it will omit the "and" prior to | |
the last list item and will not special-case the two-item form. | |
""" | |
# Note: {0:is} is simply shorthand for {is|:plural({0})}, same with {0:was} | |
# https://docs.python.org/3/library/string.html#format-specification-mini-language | |
# (enhanced with the above additional types) | |
FORMAT = re.compile( | |
r"^(?:(?P<fill>.?)(?P<align>[<>^=]))?(?P<sign>[+\- ]?)(?P<alt>#?)(?P<zero>0?)" | |
r"(?P<width>[1-9]+|0+|0[oO][1-7]+|0[xX][0-9a-fA-F]+|0[bB][01]+)?(?P<comma>,?)" | |
r"(?P<precision>\.(?:[1-9]+|0+|0[oO][1-7]+|0[xX][0-9a-fA-F]+|0[bB][01]+))?" | |
r"(?P<type>[bcdeEfFgGnosxX%]|plural|num|list|is|was|an)(?:\((?P<arg>.*)\))?$" | |
) | |
class CustomFormatter(string.Formatter): | |
def get_value(self, key, args, kwargs): | |
if not isinstance(key, int) and key[-1] == "|": | |
return key[:-1] | |
return super(CustomFormatter, self).get_value(key, args, kwargs) | |
def format_field(self, value, format_spec): | |
if format_spec: | |
m = FORMAT.match(format_spec) | |
if m: | |
fill = m.group("fill") or "" | |
align = m.group("align") or "" | |
sign = m.group("sign") or "" | |
alt = m.group("alt") == "#" # we don't pass alt through | |
zero = m.group("zero") or "" | |
width = m.group("width") or "" | |
comma = m.group("comma") or "" | |
precision = m.group("precision") or "" | |
ty = m.group("type") | |
arg = m.group("arg") # may be None | |
if ty in ("plural", "num", "list", "is", "was", "an"): | |
format_spec = fill + align + sign + zero + width + comma + precision + "s" | |
if ty == "plural": | |
arg = int(arg, 0) if arg else 2 | |
value = self.plural(value, arg) | |
elif ty == "num": | |
if arg is None: | |
raise ValueError("num() format requires an argument") | |
arg = int(arg, 0) | |
prefix = str(arg) if arg > 0 else "no" | |
if alt: | |
prefix = "\u0002" + prefix + "\u0002" | |
value = prefix + " " + self.plural(value, arg) | |
elif ty == "an": | |
prefix = "an" if value[0] in ("a", "e", "i", "o", "u") else "a" | |
if alt: | |
prefix = "\u0002" + prefix + "\u0002" | |
value = prefix + " " + value | |
elif ty == "is": | |
if not isinstance(value, int): | |
value = int(value, 0) | |
value = "is" if value == 1 else "are" | |
elif ty == "was": | |
if not isinstance(value, int): | |
value = int(value, 0) | |
value = "was" if value == 1 else "were" | |
elif ty == "list": | |
sep = arg or ", " | |
lv = len(value) | |
if alt: | |
value = sep.join(value) | |
elif lv == 0: | |
value = "" | |
elif lv == 1: | |
value = value[0] | |
elif lv == 2: | |
value = value[0] + " and " + value[1] | |
else: | |
value = sep.join(value[:-1]) + sep + "and " + value[-1] | |
return super(CustomFormatter, self).format_field(value, format_spec) | |
def plural(self, value, count=2): | |
if count == 1: | |
return value | |
bits = value.split() | |
if bits[-1][-2:] == "'s": | |
bits[-1] = self.plural(bits[-1][:-2], count) | |
bits[-1] += "'" if bits[-1][-1] == "s" else "'s" | |
else: | |
bits[-1] = {"person": "people", | |
"wolf": "wolves", | |
"has": "have", | |
"is": "are", | |
"was": "were", | |
"succubus": "succubi"}.get(bits[-1], bits[-1] + "s") | |
return " ".join(bits) | |
# vim: set expandtab:sw=4:ts=4: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment