You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
175 lines
5.3 KiB
Python
175 lines
5.3 KiB
Python
2 years ago
|
import inspect
|
||
|
import re
|
||
|
import types
|
||
|
|
||
|
|
||
|
def describe(article, value, name=None, verbose=False, capital=False):
|
||
|
"""Return string that describes a value
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
article : str or None
|
||
|
A definite or indefinite article. If the article is
|
||
|
indefinite (i.e. "a" or "an") the appropriate one
|
||
|
will be infered. Thus, the arguments of ``describe``
|
||
|
can themselves represent what the resulting string
|
||
|
will actually look like. If None, then no article
|
||
|
will be prepended to the result. For non-articled
|
||
|
description, values that are instances are treated
|
||
|
definitely, while classes are handled indefinitely.
|
||
|
value : any
|
||
|
The value which will be named.
|
||
|
name : str or None (default: None)
|
||
|
Only applies when ``article`` is "the" - this
|
||
|
``name`` is a definite reference to the value.
|
||
|
By default one will be infered from the value's
|
||
|
type and repr methods.
|
||
|
verbose : bool (default: False)
|
||
|
Whether the name should be concise or verbose. When
|
||
|
possible, verbose names include the module, and/or
|
||
|
class name where an object was defined.
|
||
|
capital : bool (default: False)
|
||
|
Whether the first letter of the article should
|
||
|
be capitalized or not. By default it is not.
|
||
|
|
||
|
Examples
|
||
|
--------
|
||
|
Indefinite description:
|
||
|
|
||
|
>>> describe("a", object())
|
||
|
'an object'
|
||
|
>>> describe("a", object)
|
||
|
'an object'
|
||
|
>>> describe("a", type(object))
|
||
|
'a type'
|
||
|
|
||
|
Definite description:
|
||
|
|
||
|
>>> describe("the", object())
|
||
|
"the object at '...'"
|
||
|
>>> describe("the", object)
|
||
|
'the object object'
|
||
|
>>> describe("the", type(object))
|
||
|
'the type type'
|
||
|
|
||
|
Definitely named description:
|
||
|
|
||
|
>>> describe("the", object(), "I made")
|
||
|
'the object I made'
|
||
|
>>> describe("the", object, "I will use")
|
||
|
'the object I will use'
|
||
|
"""
|
||
|
if isinstance(article, str):
|
||
|
article = article.lower()
|
||
|
|
||
|
if not inspect.isclass(value):
|
||
|
typename = type(value).__name__
|
||
|
else:
|
||
|
typename = value.__name__
|
||
|
if verbose:
|
||
|
typename = _prefix(value) + typename
|
||
|
|
||
|
if article == "the" or (article is None and not inspect.isclass(value)):
|
||
|
if name is not None:
|
||
|
result = f"{typename} {name}"
|
||
|
if article is not None:
|
||
|
return add_article(result, True, capital)
|
||
|
else:
|
||
|
return result
|
||
|
else:
|
||
|
tick_wrap = False
|
||
|
if inspect.isclass(value):
|
||
|
name = value.__name__
|
||
|
elif isinstance(value, types.FunctionType):
|
||
|
name = value.__name__
|
||
|
tick_wrap = True
|
||
|
elif isinstance(value, types.MethodType):
|
||
|
name = value.__func__.__name__
|
||
|
tick_wrap = True
|
||
|
elif type(value).__repr__ in (
|
||
|
object.__repr__,
|
||
|
type.__repr__,
|
||
|
): # type:ignore[comparison-overlap]
|
||
|
name = "at '%s'" % hex(id(value))
|
||
|
verbose = False
|
||
|
else:
|
||
|
name = repr(value)
|
||
|
verbose = False
|
||
|
if verbose:
|
||
|
name = _prefix(value) + name
|
||
|
if tick_wrap:
|
||
|
name = name.join("''")
|
||
|
return describe(article, value, name=name, verbose=verbose, capital=capital)
|
||
|
elif article in ("a", "an") or article is None:
|
||
|
if article is None:
|
||
|
return typename
|
||
|
return add_article(typename, False, capital)
|
||
|
else:
|
||
|
raise ValueError(
|
||
|
"The 'article' argument should be 'the', 'a', 'an', or None not %r" % article
|
||
|
)
|
||
|
|
||
|
|
||
|
def _prefix(value):
|
||
|
if isinstance(value, types.MethodType):
|
||
|
name = describe(None, value.__self__, verbose=True) + "."
|
||
|
else:
|
||
|
module = inspect.getmodule(value)
|
||
|
if module is not None and module.__name__ != "builtins":
|
||
|
name = module.__name__ + "."
|
||
|
else:
|
||
|
name = ""
|
||
|
return name
|
||
|
|
||
|
|
||
|
def class_of(value):
|
||
|
"""Returns a string of the value's type with an indefinite article.
|
||
|
|
||
|
For example 'an Image' or 'a PlotValue'.
|
||
|
"""
|
||
|
if inspect.isclass(value):
|
||
|
return add_article(value.__name__)
|
||
|
else:
|
||
|
return class_of(type(value))
|
||
|
|
||
|
|
||
|
def add_article(name, definite=False, capital=False):
|
||
|
"""Returns the string with a prepended article.
|
||
|
|
||
|
The input does not need to begin with a charater.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
name : str
|
||
|
Name to which to prepend an article
|
||
|
definite : bool (default: False)
|
||
|
Whether the article is definite or not.
|
||
|
Indefinite articles being 'a' and 'an',
|
||
|
while 'the' is definite.
|
||
|
capital : bool (default: False)
|
||
|
Whether the added article should have
|
||
|
its first letter capitalized or not.
|
||
|
"""
|
||
|
if definite:
|
||
|
result = "the " + name
|
||
|
else:
|
||
|
first_letters = re.compile(r"[\W_]+").sub("", name)
|
||
|
if first_letters[:1].lower() in "aeiou":
|
||
|
result = "an " + name
|
||
|
else:
|
||
|
result = "a " + name
|
||
|
if capital:
|
||
|
return result[0].upper() + result[1:]
|
||
|
else:
|
||
|
return result
|
||
|
|
||
|
|
||
|
def repr_type(obj):
|
||
|
"""Return a string representation of a value and its type for readable
|
||
|
|
||
|
error messages.
|
||
|
"""
|
||
|
the_type = type(obj)
|
||
|
msg = f"{obj!r} {the_type!r}"
|
||
|
return msg
|