Post-process docstring output

This commit is contained in:
Griatch 2020-07-10 14:14:34 +02:00
parent 09c602dd69
commit 29fc31bb01
5 changed files with 140 additions and 97 deletions

View file

@ -258,7 +258,17 @@ li > p:first-child {
li > p { li > p {
margin-top: 0px; margin-top: 0px;
margin-bottom: 0px; margin-bottom: 0px;
} }
/* The indents of kwarg - lists in api docs */
dl.field-list.simple > dd.field-odd > ul.simple > li > ul {
margin-left: 24px;
}
dd.field-odd > ul.simple > li > ul > li > dl.simple {
margin-bottom: -8px;
}
.admonition.important { .admonition.important {
background-color: #fbf7c3; background-color: #fbf7c3;

View file

@ -6,6 +6,7 @@
import os import os
import sys import sys
import re
import sphinx_theme import sphinx_theme
from recommonmark.transform import AutoStructify from recommonmark.transform import AutoStructify
from sphinx.util.osutil import cd from sphinx.util.osutil import cd
@ -117,13 +118,13 @@ _github_issue_choose = "https://github.com/evennia/evennia/issues/new/choose"
def url_resolver(url): def url_resolver(url):
""" """
Convert urls by catching special markers. Convert urls by catching special markers.
""" """
githubstart = "github:" githubstart = "github:"
apistart = "api:" apistart = "api:"
choose_issue = "github:issue" choose_issue = "github:issue"
sourcestart = "src:" sourcestart = "src:"
if url.endswith(choose_issue): if url.endswith(choose_issue):
return _github_issue_choose return _github_issue_choose
elif githubstart in url: elif githubstart in url:
@ -233,11 +234,39 @@ def autodoc_skip_member(app, what, name, obj, skip, options):
return False return False
def autodoc_clean_docstring(app, what, name, obj, options, lines): def autodoc_post_process_docstring(app, what, name, obj, options, lines):
"""Clean docstring of ansi. Must modify lines list in-place""" """
if ansi_clean: Post-process docstring in various ways. Must modify lines-list in-place.
for il, line in enumerate(lines): """
lines[il] = ansi_clean(line) try:
# clean out ANSI colors
if ansi_clean:
for il, line in enumerate(lines):
lines[il] = ansi_clean(line)
# post-parse docstrings to convert any remaining
# markdown -> reST since napoleon doesn't know Markdown
def _sub_codeblock(match):
code = match.group(1)
return "::\n\n {}".format(
"\n ".join(lne for lne in code.split("\n")))
doc = "\n".join(lines)
doc = re.sub(r"```python\s*\n+(.*?)```", _sub_codeblock, doc,
flags=re.MULTILINE + re.DOTALL)
doc = re.sub(r"```", "", doc, flags=re.MULTILINE)
doc = re.sub(r"`{1}", "**", doc, flags=re.MULTILINE)
newlines = doc.split("\n")
# we must modify lines in-place
lines[:] = newlines[:]
except Exception as err:
# if we don't print here we won't see what the error actually is
print(f"Post-process docstring exception: {err}")
raise
# Napoleon Google-style docstring parser for autodocs # Napoleon Google-style docstring parser for autodocs
@ -247,7 +276,7 @@ napoleon_numpy_docstring = False
napoleon_include_init_with_doc = False napoleon_include_init_with_doc = False
napoleon_include_private_with_doc = False napoleon_include_private_with_doc = False
napoleon_include_special_with_doc = False napoleon_include_special_with_doc = False
napoleon_use_admonition_for_examples = True napoleon_use_admonition_for_examples = False
napoleon_use_admonition_for_notes = False napoleon_use_admonition_for_notes = False
napoleon_use_admonition_for_references = False napoleon_use_admonition_for_references = False
napoleon_use_ivar = False napoleon_use_ivar = False
@ -262,7 +291,7 @@ napoleon_use_rtype = False
def setup(app): def setup(app):
app.connect("autodoc-skip-member", autodoc_skip_member) app.connect("autodoc-skip-member", autodoc_skip_member)
app.connect("autodoc-process-docstring", autodoc_clean_docstring) app.connect("autodoc-process-docstring", autodoc_post_process_docstring)
app.add_transform(AutoStructify) app.add_transform(AutoStructify)
# build toctree file # build toctree file

View file

@ -76,6 +76,7 @@ class PortalSessionHandler(SessionHandler):
Returns: Returns:
sessid sessid
""" """
self.latest_sessid += 1 self.latest_sessid += 1
if self.latest_sessid in self: if self.latest_sessid in self:
@ -247,7 +248,7 @@ class PortalSessionHandler(SessionHandler):
for the protocol used, eg for the protocol used, eg
'evennia.server.portal.irc.IRCClientFactory' 'evennia.server.portal.irc.IRCClientFactory'
config (dict): Dictionary of configuration options, fed as config (dict): Dictionary of configuration options, fed as
**kwarg to protocol class' __init__ method. `**kwargs` to protocol class' __init__ method.
Raises: Raises:
RuntimeError: If The correct factory class is not found. RuntimeError: If The correct factory class is not found.

View file

@ -415,9 +415,7 @@ class EvMenu(object):
by default in all nodes of the menu. This will print out the current state of by default in all nodes of the menu. This will print out the current state of
the menu. Deactivate for production use! When the debug flag is active, the the menu. Deactivate for production use! When the debug flag is active, the
`persistent` flag is deactivated. `persistent` flag is deactivated.
**kwargs: All kwargs will become initialization variables on `caller.ndb._menutree`,
Kwargs:
any (any): All kwargs will become initialization variables on `caller.ndb._menutree`,
to be available at run. to be available at run.
Raises: Raises:
@ -789,8 +787,7 @@ class EvMenu(object):
raw_string (str): The raw default string entered on the raw_string (str): The raw default string entered on the
previous node (only used if the node accepts it as an previous node (only used if the node accepts it as an
argument) argument)
Kwargs: **kwargs: Extra arguments to goto callables.
any: Extra arguments to goto callables.
""" """

View file

@ -356,16 +356,16 @@ def list_to_string(inlist, endsep="and", addquote=False):
Returns: Returns:
liststr (str): The list represented as a string. liststr (str): The list represented as a string.
Examples: Example:
```python ```python
# no endsep: # no endsep:
[1,2,3] -> '1, 2, 3' [1,2,3] -> '1, 2, 3'
# with endsep=='and': # with endsep=='and':
[1,2,3] -> '1, 2 and 3' [1,2,3] -> '1, 2 and 3'
# with addquote and endsep # with addquote and endsep
[1,2,3] -> '"1", "2" and "3"' [1,2,3] -> '"1", "2" and "3"'
``` ```
""" """
if not endsep: if not endsep:
@ -839,7 +839,7 @@ def to_bytes(text, session=None):
the text with "?" in place of problematic characters. If the specified encoding cannot the text with "?" in place of problematic characters. If the specified encoding cannot
be found, the protocol flag is reset to utf-8. In any case, returns bytes. be found, the protocol flag is reset to utf-8. In any case, returns bytes.
Note: Notes:
If `text` is already bytes, return it as is. If `text` is already bytes, return it as is.
""" """
@ -879,7 +879,7 @@ def to_str(text, session=None):
Returns: Returns:
decoded_text (str): The decoded text. decoded_text (str): The decoded text.
Note: Notes:
If `text` is already str, return it as is. If `text` is already str, return it as is.
""" """
if isinstance(text, str): if isinstance(text, str):
@ -977,18 +977,17 @@ def inherits_from(obj, parent):
distance from parent. distance from parent.
Args: Args:
obj (any): Object to analyze. This may be either an instance obj (any): Object to analyze. This may be either an instance or
or a class. a class.
parent (any): Can be either instance, class or python path to class. parent (any): Can be either an instance, a class or the python
path to the class.
Returns: Returns:
inherits_from (bool): If `parent` is a parent to `obj` or not. inherits_from (bool): If `parent` is a parent to `obj` or not.
Notes: Notes:
What differs this function from e.g. `isinstance()` is that `obj` What differentiates this function from Python's `isinstance()` is the
may be both an instance and a class, and parent may be an flexibility in the types allowed for the object and parent being compared.
instance, a class, or the python path to a class (counting from
the evennia root directory).
""" """
@ -1036,8 +1035,7 @@ def uses_database(name="sqlite3"):
shortcut to having to use the full backend name. shortcut to having to use the full backend name.
Args: Args:
name (str): One of 'sqlite3', 'mysql', 'postgresql' name (str): One of 'sqlite3', 'mysql', 'postgresql' or 'oracle'.
or 'oracle'.
Returns: Returns:
uses (bool): If the given database is used or not. uses (bool): If the given database is used or not.
@ -1061,20 +1059,19 @@ def delay(timedelay, callback, *args, **kwargs):
timedelay (int or float): The delay in seconds timedelay (int or float): The delay in seconds
callback (callable): Will be called as `callback(*args, **kwargs)` callback (callable): Will be called as `callback(*args, **kwargs)`
after `timedelay` seconds. after `timedelay` seconds.
args (any, optional): Will be used as arguments to callback *args: Will be used as arguments to callback
Kwargs: Keyword args:
persistent (bool, optional): should make the delay persistent persistent (bool): Make the delay persistent over a reboot or reload.
over a reboot or reload any: Any other keywords will be use as keyword arguments to callback.
any (any): Will be used as keyword arguments to callback.
Returns: Returns:
deferred (deferred): Will fire with callback after deferred: Will fire with callback after `timedelay` seconds. Note that
`timedelay` seconds. Note that if `timedelay()` is used in the if `timedelay()` is used in the
commandhandler callback chain, the callback chain can be commandhandler callback chain, the callback chain can be
defined directly in the command body and don't need to be defined directly in the command body and don't need to be
specified here. specified here.
Note: Notes:
The task handler (`evennia.scripts.taskhandler.TASK_HANDLER`) will The task handler (`evennia.scripts.taskhandler.TASK_HANDLER`) will
be called for persistent or non-persistent tasks. be called for persistent or non-persistent tasks.
If persistent is set to True, the callback, its arguments If persistent is set to True, the callback, its arguments
@ -1102,20 +1099,19 @@ def run_async(to_execute, *args, **kwargs):
Args: Args:
to_execute (callable): If this is a callable, it will be to_execute (callable): If this is a callable, it will be
executed with *args and non-reserved *kwargs as arguments. executed with `*args` and non-reserved `**kwargs` as arguments.
The callable will be executed using ProcPool, or in a thread The callable will be executed using ProcPool, or in a thread
if ProcPool is not available. if ProcPool is not available.
Keyword args:
Kwargs:
at_return (callable): Should point to a callable with one at_return (callable): Should point to a callable with one
argument. It will be called with the return value from argument. It will be called with the return value from
to_execute. to_execute.
at_return_kwargs (dict): This dictionary will be used as at_return_kwargs (dict): This dictionary will be used as
keyword arguments to the at_return callback. keyword arguments to the at_return callback.
at_err (callable): This will be called with a Failure instance at_err (callable): This will be called with a Failure instance
if there is an error in to_execute. if there is an error in to_execute.
at_err_kwargs (dict): This dictionary will be used as keyword at_err_kwargs (dict): This dictionary will be used as keyword
arguments to the at_err errback. arguments to the at_err errback.
Notes: Notes:
All other `*args` and `**kwargs` will be passed on to All other `*args` and `**kwargs` will be passed on to
@ -1528,8 +1524,8 @@ def init_new_account(account):
def string_similarity(string1, string2): def string_similarity(string1, string2):
""" """
This implements a "cosine-similarity" algorithm as described for example in This implements a "cosine-similarity" algorithm as described for example in
*Proceedings of the 22nd International Conference on Computation *Proceedings of the 22nd International Conference on Computation
Linguistics* (Coling 2008), pages 593-600, Manchester, August 2008. Linguistics* (Coling 2008), pages 593-600, Manchester, August 2008.
The measure-vectors used is simply a "bag of words" type histogram The measure-vectors used is simply a "bag of words" type histogram
(but for letters). (but for letters).
@ -1569,8 +1565,8 @@ def string_suggestions(string, vocabulary, cutoff=0.6, maxnum=3):
Returns: Returns:
suggestions (list): Suggestions from `vocabulary` with a suggestions (list): Suggestions from `vocabulary` with a
similarity-rating that higher than or equal to `cutoff`. similarity-rating that higher than or equal to `cutoff`.
Could be empty if there are no matches. Could be empty if there are no matches.
""" """
return [ return [
@ -1638,11 +1634,9 @@ def string_partial_matching(alternatives, inp, ret_index=True):
def format_table(table, extra_space=1): def format_table(table, extra_space=1):
""" """
Note: `evennia.utils.evtable` is more powerful than this, but this Format a 2D array of strings into a multi-column table.
function can be useful when the number of columns and rows are
unknown and must be calculated on the fly.
Args. Args:
table (list): A list of lists to represent columns in the table (list): A list of lists to represent columns in the
table: `[[val,val,val,...], [val,val,val,...], ...]`, where table: `[[val,val,val,...], [val,val,val,...], ...]`, where
each val will be placed on a separate row in the each val will be placed on a separate row in the
@ -1652,16 +1646,19 @@ def format_table(table, extra_space=1):
padding (in characters) should be left between columns. padding (in characters) should be left between columns.
Returns: Returns:
table (list): A list of lists representing the rows to print list: A list of lists representing the rows to print out one by one.
out one by one.
Notes: Notes:
The function formats the columns to be as wide as the widest member The function formats the columns to be as wide as the widest member
of each column. of each column.
Examples: `evennia.utils.evtable` is more powerful than this, but this
function can be useful when the number of columns and rows are
unknown and must be calculated on the fly.
```python Example:
```python
ftable = format_table([[...], [...], ...]) ftable = format_table([[...], [...], ...])
for ir, row in enumarate(ftable): for ir, row in enumarate(ftable):
if ir == 0: if ir == 0:
@ -1671,7 +1668,9 @@ def format_table(table, extra_space=1):
string += "\n" + "".join(row) string += "\n" + "".join(row)
print string print string
``` ```
""" """
if not table: if not table:
return [[]] return [[]]
@ -1703,12 +1702,10 @@ def percent(value, minval, maxval, formatting="{:3.1f}%"):
current value as a percentage. If None, the current value as a percentage. If None, the
raw float will be returned instead. raw float will be returned instead.
Returns: Returns:
str or float: The formatted value or the raw percentage str or float: The formatted value or the raw percentage as a float.
as a float.
Notes: Notes:
We try to handle a weird interval gracefully. We try to handle a weird interval gracefully.
- If either maxval or minval is None (open interval), - If either maxval or minval is None (open interval), we (aribtrarily) assume 100%.
we (aribtrarily) assume 100%.
- If minval > maxval, we return 0%. - If minval > maxval, we return 0%.
- If minval == maxval == value we are looking at a single value match - If minval == maxval == value we are looking at a single value match
and return 100%. and return 100%.
@ -1759,7 +1756,9 @@ def percentile(iterable, percent, key=lambda x: x):
percent (float): A value from 0.0 to 1.0. percent (float): A value from 0.0 to 1.0.
key (callable, optional). Function to compute value from each element of N. key (callable, optional). Function to compute value from each element of N.
@return - the percentile of the values Returns:
float: The percentile of the values
""" """
if not iterable: if not iterable:
return None return None
@ -1792,9 +1791,9 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None):
decorations in the grid, such as horizontal bars. decorations in the grid, such as horizontal bars.
Returns: Returns:
gridstr (list): The grid as a list of ready-formatted rows. We return it gridstr: The grid as a list of ready-formatted rows. We return it
like this to make it easier to insert decorations between rows, such like this to make it easier to insert decorations between rows, such
as horizontal bars. as horizontal bars.
""" """
if not verbatim_elements: if not verbatim_elements:
verbatim_elements = [] verbatim_elements = []
@ -1883,13 +1882,13 @@ def get_evennia_pids():
Examples: Examples:
This can be used to determine if we are in a subprocess by This can be used to determine if we are in a subprocess by
something like:
```python ```python
self_pid = os.getpid() self_pid = os.getpid()
server_pid, portal_pid = get_evennia_pids() server_pid, portal_pid = get_evennia_pids()
is_subprocess = self_pid not in (server_pid, portal_pid) is_subprocess = self_pid not in (server_pid, portal_pid)
``` ```
""" """
server_pidfile = os.path.join(settings.GAME_DIR, "server.pid") server_pidfile = os.path.join(settings.GAME_DIR, "server.pid")
portal_pidfile = os.path.join(settings.GAME_DIR, "portal.pid") portal_pidfile = os.path.join(settings.GAME_DIR, "portal.pid")
@ -2078,16 +2077,15 @@ def at_search_result(matches, caller, query="", quiet=False, **kwargs):
query (str, optional): The search query used to produce `matches`. query (str, optional): The search query used to produce `matches`.
quiet (bool, optional): If `True`, no messages will be echoed to caller quiet (bool, optional): If `True`, no messages will be echoed to caller
on errors. on errors.
Keyword args:
Kwargs:
nofound_string (str): Replacement string to echo on a notfound error. nofound_string (str): Replacement string to echo on a notfound error.
multimatch_string (str): Replacement string to echo on a multimatch error. multimatch_string (str): Replacement string to echo on a multimatch error.
Returns: Returns:
processed_result (Object or None): This is always a single result processed_result (Object or None): This is always a single result
or `None`. If `None`, any error reporting/handling should or `None`. If `None`, any error reporting/handling should
already have happened. The returned object is of the type we are already have happened. The returned object is of the type we are
checking multimatches for (e.g. Objects or Commands) checking multimatches for (e.g. Objects or Commands)
""" """
@ -2138,12 +2136,12 @@ class LimitedSizeOrderedDict(OrderedDict):
""" """
Limited-size ordered dict. Limited-size ordered dict.
Kwargs: Keyword args:
size_limit (int): Use this to limit the number of elements size_limit (int): Use this to limit the number of elements
alloweds to be in this list. By default the overshooting elements alloweds to be in this list. By default the overshooting elements
will be removed in FIFO order. will be removed in FIFO order.
fifo (bool, optional): Defaults to `True`. Remove overshooting elements fifo (bool, optional): Defaults to `True`. Remove overshooting elements
in FIFO order. If `False`, remove in FILO order. in FIFO order. If `False`, remove in FILO order.
""" """
super().__init__() super().__init__()
@ -2210,10 +2208,10 @@ def get_all_typeclasses(parent=None):
from this parent. from this parent.
Returns: Returns:
typeclasses (dict): On the form {"typeclass.path": typeclass, ...} dict: On the form `{"typeclass.path": typeclass, ...}`
Notes: Notes:
This will dynamicall retrieve all abstract django models inheriting at any distance This will dynamically retrieve all abstract django models inheriting at any distance
from the TypedObject base (aka a Typeclass) so it will work fine with any custom from the TypedObject base (aka a Typeclass) so it will work fine with any custom
classes being added. classes being added.
@ -2236,26 +2234,34 @@ def get_all_typeclasses(parent=None):
def interactive(func): def interactive(func):
""" """
Decorator to make a method pausable with yield(seconds) Decorator to make a method pausable with `yield(seconds)`
and able to ask for user-input with response=yield(question). and able to ask for user-input with `response=yield(question)`.
For the question-asking to work, 'caller' must the name For the question-asking to work, one of the args or kwargs to the
of an argument or kwarg to the decorated function. decorated function must be named 'caller'.
Note that this turns the method into a generator. Raises:
ValueError: If asking an interactive question but the decorated
function has no arg or kwarg named 'caller'.
ValueError: If passing non int/float to yield using for pausing.
Example usage: Example:
@interactive ```python
def myfunc(caller): @interactive
caller.msg("This is a test") def myfunc(caller):
# wait five seconds caller.msg("This is a test")
yield(5) # wait five seconds
# ask user (caller) a question
response = yield("Do you want to continue waiting?")
if response == "yes":
yield(5) yield(5)
else: # ask user (caller) a question
# ... response = yield("Do you want to continue waiting?")
if response == "yes":
yield(5)
else:
# ...
```
Notes:
This turns the decorated function or method into a generator.
""" """
from evennia.utils.evmenu import get_input from evennia.utils.evmenu import get_input