Skip to content

Commit

Permalink
feat: support epytext style (#211)
Browse files Browse the repository at this point in the history
* feat: add wrapping support for Epytex

* docs: update documentation for Epytext style

* test: add tests for epytext style
  • Loading branch information
weibullguy committed May 18, 2023
1 parent f45a8e3 commit 1cef4e9
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 128 deletions.
7 changes: 7 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ Features
whitespace. Such trailing whitespace is visually indistinguishable
and some editors (or more recently, reindent.py) will trim them.

``docformatter`` formats docstrings compatible with ``black`` when passed the
``--black`` option.

``docformatter`` formats field lists that use Epytext or Sphinx styles.

See the the full documentation at `read-the-docs`_, especially the
`requirements`_ section for a more detailed discussion of PEP 257 and other
requirements.
Expand All @@ -82,6 +87,8 @@ Python < 3.11::

$ pip install --upgrade docformatter[tomli]

With Python >=3.11, ``tomllib`` from the standard library is used.

Or, if you want to use a release candidate (or any other tag)::

$ pip install git+https://github.com/PyCQA/docformatter.git@<RC_TAG>
Expand Down
13 changes: 6 additions & 7 deletions docs/source/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,9 @@ and
``""""This" summary does get a space."""`` becomes ``""" "This" summary does get a space."""``

The ``--style`` argument takes a string which is the name of the parameter
list style you are using. Currently, only ``sphinx`` is recognized, but
``epydoc``, ``numpy``, and ``google`` are future styles. For the selected
style, each line in the parameter lists will be wrapped at the
``--wrap-descriptions`` length as well as any portion of the elaborate
description preceding the parameter list. Parameter lists that don't follow the
passed style will cause the entire elaborate description to be ignored and
remain unwrapped.
list style you are using. Currently, only ``sphinx`` and ``epytext`` are recognized,
but ``numpy`` and ``google`` are future styles. For the selected style, each line in
the parameter lists will be wrapped at the ``--wrap-descriptions`` length as well as
any portion of the elaborate description preceding the parameter list. Parameter lists
that don't follow the passed style will cause the entire elaborate description to be
ignored and remain unwrapped.
9 changes: 6 additions & 3 deletions docs/source/requirements.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,14 @@ Docstring Style
---------------

There are at least four "flavors" of docstrings in common use today;
Epydoc, Sphinx, NumPy, and Google. Each of these docstring flavors follow the
Epytext, Sphinx, NumPy, and Google. Each of these docstring flavors follow the
PEP 257 *convention* requirements. What differs between the three docstring
flavors is the reST syntax used in the parameter description of the multi-line
docstring.

For example, here is how each syntax documents function arguments.

Epydoc syntax:
Epytext syntax:

.. code-block::
Expand Down Expand Up @@ -221,6 +221,9 @@ the requirement falls in, the type of requirement, and whether
' docformatter_10.5.2', ' Should wrap descriptions at 88 characters by default in black mode.', ' Style', ' Should', ' Yes'
' docformatter_10.5.3', ' Should insert a space before the first word in the summary if that word is quoted when in black mode.', ' Style', ' Should', ' Yes'
' docformatter_10.5.4', ' Default black mode options should be over-rideable by passing arguments or using configuration files.', ' Style', ' Should', ' Yes'
' docformatter_10.6', ' Should format docstrings using Epytext style.', ' Style', ' Should', ' Yes'
' docformatter_10.6.1', ' Shall ignore docstrings in other styles when using Epytext style.', ' Shall', ' Yes'
' docformatter_10.6.2', ' Shall wrap Epytext-style parameter descriptions that exceed wrap length when using Epytext style.', ' Shall', ' Yes'
' docformatter_11', '**Program Control**'
' docformatter_11.1', ' Should check formatting and report incorrectly documented docstrings.', ' Stakeholder', ' Should', ' Yes [*PR #32*]'
' docformatter_11.2', ' Should fix formatting and save changes to file.', ' Stakeholder', ' Should', ' Yes'
Expand Down Expand Up @@ -344,4 +347,4 @@ version bump (i.e., 1.5.0 -> 1.6.0). One or more release candidates will be
provided for each minor or major version bump. These will be indicated by
appending `-rcX` to the version number, where the X is the release candidate
number beginning with 1. Release candidates will not be uploaded to PyPi,
but will be made available via GitHub Releases.
but will be made available via GitHub Releases.
2 changes: 1 addition & 1 deletion docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ help output provides a summary of these options:
-s style, --style style
the docstring style to use when formatting parameter
lists (default: sphinx)
lists. One of epytext, sphinx. (default: sphinx)
--black
make formatting compatible with standard black options
(default: False)
Expand Down
11 changes: 11 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,17 @@ use_parentheses = true
ensure_newline_before_comments = true
line_length = 88

[tool.rstcheck]
report = "warning"
ignore_directives = [
"automodule",
"tabularcolumns",
"toctree",
]
ignore_roles = [
"numref",
]

[tool.tox]
legacy_tox_ini = """
[tox]
Expand Down
9 changes: 2 additions & 7 deletions src/docformatter/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,7 @@
"""Formats docstrings to follow PEP 257."""


from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from __future__ import absolute_import, division, print_function, unicode_literals

# Standard Library Imports
import contextlib
Expand Down Expand Up @@ -73,7 +68,7 @@ def _help():
-s style, --style style
the docstring style to use when formatting parameter
lists (default: sphinx)
lists. One of epytext, sphinx. (default: sphinx)
--black make formatting compatible with standard black options
(default: False)
--wrap-summaries length
Expand Down
219 changes: 133 additions & 86 deletions src/docformatter/syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,33 @@
import textwrap
from typing import Iterable, List, Tuple, Union

SPHINX_REGEX = r":[a-zA-Z0-9_\- ]*:"
"""Regular expression to use for finding Sphinx-style field lists."""
BULLET_REGEX = r"\s*[*\-+] [\S ]+"
"""Regular expression to use for finding bullet lists."""

ENUM_REGEX = r"\s*\d\."
"""Regular expression to use for finding enumerated lists."""

EPYTEXT_REGEX = r"@[a-zA-Z0-9_\-\s]+:"
"""Regular expression to use for finding Epytext-style field lists."""

GOOGLE_REGEX = r"^ *[a-zA-Z0-9_\- ]*:$"
"""Regular expression to use for finding Google-style field lists."""

LITERAL_REGEX = r"[\S ]*::"
"""Regular expression to use for finding literal blocks."""

NUMPY_REGEX = r"^\s[a-zA-Z0-9_\- ]+ ?: [\S ]+"
"""Regular expression to use for finding Numpy-style field lists."""

OPTION_REGEX = r"^-{1,2}[\S ]+ {2}\S+"
"""Regular expression to use for finding option lists."""

REST_REGEX = r"(\.{2}|``) ?[\w-]+(:{1,2}|``)?"
"""Regular expression to use for finding reST directives."""

SPHINX_REGEX = r":[a-zA-Z0-9_\- ]*:"
"""Regular expression to use for finding Sphinx-style field lists."""

URL_PATTERNS = (
"afp|"
"apt|"
Expand Down Expand Up @@ -238,22 +259,36 @@ def do_find_directives(text: str) -> bool:
return bool([(_rest.start(0), _rest.end(0)) for _rest in _rest_iter])


def do_find_sphinx_field_lists(text: str) -> List[Tuple[int, int]]:
def do_find_field_lists(text: str, style: str):
r"""Determine if docstring contains any field lists.
Parameters
----------
text : str
The docstring description to check for field list patterns.
style : str
The field list style used.
Returns
-------
field_index : list
_field_idx, _wrap_parameters : tuple
A list of tuples with each tuple containing the starting and ending
position of each field list found in the passed description.
A boolean indicating whether long field list lines should be wrapped.
"""
_field_iter = re.finditer(SPHINX_REGEX, text)
return [(_field.start(0), _field.end(0)) for _field in _field_iter]
_field_idx = []
_wrap_parameters = False

if style == "epytext":
_field_iter = re.finditer(EPYTEXT_REGEX, text)
_field_idx = [(_field.start(0), _field.end(0)) for _field in _field_iter]
_wrap_parameters = True
elif style == "sphinx":
_field_iter = re.finditer(SPHINX_REGEX, text)
_field_idx = [(_field.start(0), _field.end(0)) for _field in _field_iter]
_wrap_parameters = True

return _field_idx, _wrap_parameters


def do_find_links(text: str) -> List[Tuple[int, int]]:
Expand Down Expand Up @@ -333,12 +368,9 @@ def do_split_description(

# Check if the description contains any URLs.
_url_idx = do_find_links(text)
if style == "sphinx":
_parameter_idx = do_find_sphinx_field_lists(text)
_wrap_parameters = True
else:
_parameter_idx = []
_wrap_parameters = False

# Check if the description contains any field lists.
_parameter_idx, _wrap_parameters = do_find_field_lists(text, style)

if not _url_idx and not (_parameter_idx and _wrap_parameters):
return description_to_list(
Expand Down Expand Up @@ -511,6 +543,47 @@ def do_wrap_urls(
return _lines, text_idx


def is_some_sort_of_field_list(
text: str,
style: str,
) -> bool:
"""Determine if docstring contains field lists.
Parameters
----------
text : str
The docstring text.
style : str
The field list style to use.
Returns
-------
is_field_list : bool
Whether the field list pattern for style was found in the docstring.
"""
split_lines = text.rstrip().splitlines()

if style == "epytext":
return any(
(
# "@param x:" <-- Epytext style
# "@type x:" <-- Epytext style
re.match(EPYTEXT_REGEX, line)
)
for line in split_lines
)
elif style == "sphinx":
return any(
(
# ":parameter: description" <-- Sphinx style
re.match(SPHINX_REGEX, line)
)
for line in split_lines
)

return False


# pylint: disable=line-too-long
def is_some_sort_of_list(
text: str,
Expand Down Expand Up @@ -545,81 +618,55 @@ def is_some_sort_of_list(
) and not strict:
return True

if style == "sphinx":
return any(
(
# "* parameter" <-- Bullet list
# "- parameter" <-- Bullet list
# "+ parameter" <-- Bullet list
re.match(r"\s*[*\-+] [\S ]+", line)
or
# "1. item" <-- Enumerated list
re.match(r"\s*\d\.", line)
or
# "-a description" <-- Option list
# "--long description" <-- Option list
re.match(r"^-{1,2}[\S ]+ {2}\S+", line)
or
# "@parameter" <-- Epydoc style
re.match(r"\s*@\S*", line)
or
# "parameter : description" <-- Numpy style
# "parameter: description" <-- Numpy style
re.match(r"^\s*(?!:)\S+ ?: \S+", line)
or
# "word\n----" <-- Numpy headings
re.match(r"^\s*-+", line)
or
# "parameter - description"
re.match(r"[\S ]+ - \S+", line)
or
# "parameter -- description"
re.match(r"\s*\S+\s+--\s+", line)
or
# Literal block
re.match(r"[\S ]*::", line)
)
for line in split_lines
)
else:
return any(
(
# "* parameter" <-- Bullet list
# "- parameter" <-- Bullet list
# "+ parameter" <-- Bullet list
re.match(r"\s*[*\-+] [\S ]+", line)
or
# "1. item" <-- Enumerated list
re.match(r"\s*\d\.", line)
or
# "-a description" <-- Option list
# "--long description" <-- Option list
re.match(r"^-{1,2}[\S ]+ {2}\S+", line)
or
# "@parameter" <-- Epydoc style
re.match(r"\s*@\S*", line)
or
# ":parameter: description" <-- Sphinx style
re.match(SPHINX_REGEX, line)
or
# "parameter : description" <-- Numpy style
# "parameter: description" <-- Numpy style
re.match(r"^\s[\S ]+ ?: [\S ]+", line)
or
# "word\n----" <-- Numpy headings
re.match(r"^\s*-+", line)
or
# "parameter - description"
re.match(r"[\S ]+ - \S+", line)
or
# "parameter -- description"
re.match(r"\s*\S+\s+--\s+", line)
or
# Literal block
re.match(r"[\S ]*::", line)
)
for line in split_lines
if is_some_sort_of_field_list(text, style):
return False

return any(
(
# "* parameter" <-- Bullet list
# "- parameter" <-- Bullet list
# "+ parameter" <-- Bullet list
re.match(BULLET_REGEX, line)
or
# "1. item" <-- Enumerated list
re.match(ENUM_REGEX, line)
or
# "-a description" <-- Option list
# "--long description" <-- Option list
re.match(OPTION_REGEX, line)
or
# "@param x:" <-- Epytext style
# "@type x:" <-- Epytext style
re.match(EPYTEXT_REGEX, line)
or
# ":parameter: description" <-- Sphinx style
re.match(SPHINX_REGEX, line)
or
# "parameter : description" <-- Numpy style
# "parameter: description" <-- Numpy style
re.match(NUMPY_REGEX, line)
or
# "word\n----" <-- Numpy headings
re.match(r"^\s*-+", line)
or
# "Args:" <-- Google style
# "parameter:" <-- Google style
re.match(GOOGLE_REGEX, line)
or
# "parameter - description"
re.match(r"[\S ]+ - \S+", line)
or
# "parameter -- description"
re.match(r"\s*\S+\s+--\s+", line)
or
# Literal block
re.match(LITERAL_REGEX, line)
or
# "@parameter"
re.match(r"^ *@[a-zA-Z0-9_\- ]*(?:(?!:).)*$", line)
)
for line in split_lines
)


def is_some_sort_of_code(text: str) -> bool:
Expand Down
Loading

0 comments on commit 1cef4e9

Please sign in to comment.