Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
* origin/master:
  refactor: move encoding/decoding functions to encodor class (#111)
  Update AUTHORS.rst file with new author(s)
  • Loading branch information
weibullguy committed Aug 21, 2022
2 parents 6da318b + 7259cd5 commit e171a2e
Show file tree
Hide file tree
Showing 5 changed files with 362 additions and 161 deletions.
4 changes: 1 addition & 3 deletions AUTHORS.rst
@@ -1,7 +1,6 @@
.. This file is automatically generated/updated by a github actions workflow.
.. Every manual change will be overwritten on push to main.
.. You can find it here: ``.github/workflows/update-authors.yaml``
.. For more information see `https://github.com/rstcheck/rstcheck/graphs/contributors`
.. You can find it here: ``.github/workflows/do-update-authors.yaml``
Author
------
Expand Down Expand Up @@ -30,4 +29,3 @@ Additional contributions by (sorted by name)
- Swen Kooij <swenkooij@gmail.com>
- happlebao <c.x.bao@student.ucc.ie>
- serhiy-yevtushenko <syevtushenko@gmail.com>

218 changes: 148 additions & 70 deletions docformatter.py
Expand Up @@ -70,23 +70,6 @@
CR = "\r"
LF = "\n"
CRLF = "\r\n"
STR_QUOTE_TYPES = (
'"""',
"'''",
)
RAW_QUOTE_TYPES = (
'r"""',
'R"""',
"r'''",
"R'''",
)
UCODE_QUOTE_TYPES = (
'u"""',
'U"""',
"u'''",
"U'''",
)
QUOTE_TYPES = STR_QUOTE_TYPES + RAW_QUOTE_TYPES + UCODE_QUOTE_TYPES

_PYTHON_LIBS = set(sysconfig.get_paths().values())

Expand Down Expand Up @@ -180,8 +163,7 @@ def do_parse_arguments(self) -> None:
type=int,
metavar="length",
help="wrap long summary lines at this length; "
"set to 0 to disable wrapping "
"(default: %(default)s)",
"set to 0 to disable wrapping (default: 79)",
)
self.parser.add_argument(
"--wrap-descriptions",
Expand All @@ -190,14 +172,14 @@ def do_parse_arguments(self) -> None:
metavar="length",
help="wrap descriptions at this length; "
"set to 0 to disable wrapping "
"(default: %(default)s)",
"(default: 72)",
)
self.parser.add_argument(
"--force-wrap",
action="store_true",
default=bool(self.flargs_dct.get("force-wrap", False)),
help="force descriptions to be wrapped even if it may "
"result in a mess (default: %(default)s)",
"result in a mess (default: False)",
)
self.parser.add_argument(
"--tab-width",
Expand All @@ -206,28 +188,28 @@ def do_parse_arguments(self) -> None:
metavar="width",
default=int(self.flargs_dct.get("tab-width", 1)),
help="tabs in indentation are this many characters when "
"wrapping lines (default: %(default)s)",
"wrapping lines (default: 1)",
)
self.parser.add_argument(
"--blank",
dest="post_description_blank",
action="store_true",
default=bool(self.flargs_dct.get("blank", False)),
help="add blank line after description (default: %(default)s)",
help="add blank line after description (default: False)",
)
self.parser.add_argument(
"--pre-summary-newline",
action="store_true",
default=bool(self.flargs_dct.get("pre-summary-newline", False)),
help="add a newline before the summary of a multi-line docstring "
"(default: %(default)s)",
"(default: False)",
)
self.parser.add_argument(
"--pre-summary-space",
action="store_true",
default=bool(self.flargs_dct.get("pre-summary-space", False)),
help="add a space after the opening triple quotes "
"(default: %(default)s)",
"(default: False)",
)
self.parser.add_argument(
"--make-summary-multi-line",
Expand All @@ -236,7 +218,7 @@ def do_parse_arguments(self) -> None:
self.flargs_dct.get("make-summary-multi-line", False)
),
help="add a newline before and after the summary of a one-line "
"docstring (default: %(default)s)",
"docstring (default: False)",
)
self.parser.add_argument(
"--close-quotes-on-newline",
Expand All @@ -246,7 +228,7 @@ def do_parse_arguments(self) -> None:
),
help="place closing triple quotes on a new-line when a "
"one-line docstring wraps to two or more lines "
"(default: %(default)s)",
"(default: False)",
)
self.parser.add_argument(
"--range",
Expand All @@ -256,7 +238,7 @@ def do_parse_arguments(self) -> None:
type=int,
nargs=2,
help="apply docformatter to docstrings between these "
"lines; line numbers are indexed at 1 (default: %(default)s)",
"lines; line numbers are indexed at 1 (default: None)",
)
self.parser.add_argument(
"--docstring-length",
Expand All @@ -266,7 +248,7 @@ def do_parse_arguments(self) -> None:
type=int,
nargs=2,
help="apply docformatter to docstrings of given length range "
"(default: %(default)s)",
"(default: None)",
)
self.parser.add_argument(
"--non-strict",
Expand Down Expand Up @@ -353,6 +335,24 @@ def _do_read_parser_configuration(self) -> None:
class Formator:
"""Format docstrings."""

STR_QUOTE_TYPES = (
'"""',
"'''",
)
RAW_QUOTE_TYPES = (
'r"""',
'R"""',
"r'''",
"R'''",
)
UCODE_QUOTE_TYPES = (
'u"""',
'U"""',
"u'''",
"U'''",
)
QUOTE_TYPES = STR_QUOTE_TYPES + RAW_QUOTE_TYPES + UCODE_QUOTE_TYPES

parser = None
"""Parser object."""

Expand Down Expand Up @@ -558,7 +558,7 @@ def _format_code(
) in tokenize.generate_tokens(sio.readline):
if (
token_type == tokenize.STRING
and token_string.startswith(QUOTE_TYPES)
and token_string.startswith(self.QUOTE_TYPES)
and (
previous_token_type == tokenize.INDENT
or only_comments_so_far
Expand Down Expand Up @@ -625,13 +625,13 @@ def _do_format_docstring(
docstring_formatted: str
The docstring formatted according the various options.
"""
contents, open_quote = strip_docstring(docstring)
contents, open_quote = self._do_strip_docstring(docstring)
open_quote = (
f"{open_quote} " if self.args.pre_summary_space else open_quote
)

# Skip if there are nested triple double quotes
if contents.count(QUOTE_TYPES[0]):
if contents.count(self.QUOTE_TYPES[0]):
return docstring

# Do not modify things that start with doctests.
Expand Down Expand Up @@ -717,6 +717,122 @@ def _do_format_docstring(
).strip()
return f"{beginning}{summary_wrapped}{ending}"

def _do_strip_docstring(self, docstring: str) -> Tuple[str, str]:
"""Return contents of docstring and opening quote type.
Strips the docstring of its triple quotes, trailing white space,
and line returns. Determines type of docstring quote (either string,
raw, or unicode) and returns the opening quotes, including the type
identifier, with single quotes replaced by double quotes.
Parameters
----------
docstring: str
The docstring, including the opening and closing triple quotes.
Returns
-------
(docstring, open_quote) : tuple
The docstring with the triple quotes removed.
The opening quote type with single quotes replaced by double
quotes.
"""
docstring = docstring.strip()

for quote in self.QUOTE_TYPES:
if quote in self.RAW_QUOTE_TYPES + self.UCODE_QUOTE_TYPES and (
docstring.startswith(quote) and docstring.endswith(quote[1:])
):
return docstring.split(quote, 1)[1].rsplit(quote[1:], 1)[
0
].strip(), quote.replace("'", '"')
elif docstring.startswith(quote) and docstring.endswith(quote):
return docstring.split(quote, 1)[1].rsplit(quote, 1)[
0
].strip(), quote.replace("'", '"')

raise ValueError(
"docformatter only handles triple-quoted (single or double) "
"strings"
)


class Encodor:
"""Encoding and decoding of files."""

CR = "\r"
LF = "\n"
CRLF = "\r\n"

def __init__(self):
"""Initialize an Encodor instance."""
self.encoding = "latin-1"
self.system_encoding = (
locale.getpreferredencoding() or sys.getdefaultencoding()
)

def do_detect_encoding(self, filename: str) -> None:
"""Return the detected file encoding.
Parameters
----------
filename : str
The full path name of the file whose encoding is to be detected.
"""
try:
self.encoding = from_path(filename).best().encoding

# Check for correctness of encoding.
with self.do_open_with_encoding(filename) as check_file:
check_file.read()
except (SyntaxError, LookupError, UnicodeDecodeError):
self.encoding = "latin-1"

def do_find_newline(self, source: str) -> Dict[int, int]:
"""Return type of newline used in source.
Paramaters
----------
source : list
A list of lines.
Returns
-------
counter : dict
A dict with the count of new line types found.
"""
assert not isinstance(source, unicode)

counter = collections.defaultdict(int)
for line in source:
if line.endswith(self.CRLF):
counter[self.CRLF] += 1
elif line.endswith(self.CR):
counter[self.CR] += 1
elif line.endswith(self.LF):
counter[self.LF] += 1

return (sorted(counter, key=counter.get, reverse=True) or [self.LF])[0]

def do_open_with_encoding(self, filename: str, mode: str = "r"):
"""Return opened file with a specific encoding.
Parameters
----------
filename : str
The full path name of the file to open.
mode : str
The mode to open the file in. Defaults to read-only.
Returns
-------
contents : TextIO
The contents of the file.
"""
return io.open(
filename, mode=mode, encoding=self.encoding, newline=""
) # Preserve line endings


class Encodor:
"""Encoding and decoding of files."""
Expand Down Expand Up @@ -994,44 +1110,6 @@ def normalize_line_endings(lines, newline):
return "".join([normalize_line(line, newline) for line in lines])


def strip_docstring(docstring: str) -> Tuple[str, str]:
"""Return contents of docstring and opening quote type.
Strips the docstring of its triple quotes, trailing white space,
and line returns. Determines type of docstring quote (either string,
raw, or unicode) and returns the opening quotes, including the type
identifier, with single quotes replaced by double quotes.
Parameters
----------
docstring: str
The docstring, including the opening and closing triple quotes.
Returns
-------
(docstring, open_quote) : tuple
The docstring with the triple quotes removed.
The opening quote type with single quotes replaced by double quotes.
"""
docstring = docstring.strip()

for quote in QUOTE_TYPES:
if quote in RAW_QUOTE_TYPES + UCODE_QUOTE_TYPES and (
docstring.startswith(quote) and docstring.endswith(quote[1:])
):
return docstring.split(quote, 1)[1].rsplit(quote[1:], 1)[
0
].strip(), quote.replace("'", '"')
elif docstring.startswith(quote) and docstring.endswith(quote):
return docstring.split(quote, 1)[1].rsplit(quote, 1)[
0
].strip(), quote.replace("'", '"')

raise ValueError(
"docformatter only handles triple-quoted (single or double) strings"
)


def unwrap_summary(summary):
"""Return summary with newlines removed in preparation for wrapping."""
return re.sub(r"\s*\n\s*", " ", summary)
Expand Down
33 changes: 29 additions & 4 deletions docs/source/configuration.rst
Expand Up @@ -15,8 +15,8 @@ If no configuration file is explicitly passed, ``docformatter`` will search
the current directory for the supported files and use the first one found.
The order of precedence is ``pyproject.toml``, ``setup.cfg``, then ``tox.ini``.

In any of the configuration files, add a section ``[tool.docformatter]`` with
options listed using the same name as command line options. For example:
In ``pyproject.toml`` or ``tox.ini``, add a section ``[tool.docformatter]`` with
options listed using the same name as command line argument. For example:

.. code-block:: yaml
Expand All @@ -25,5 +25,30 @@ options listed using the same name as command line options. For example:
wrap-summaries = 82
blank = true
The ``setup.cfg`` and ``tox.ini`` files will also support the
``[tool:docformatter]`` syntax.
In ``setup.cfg``, add a ``[docformatter]`` section.

.. code-block:: yaml
[docformatter]
recursive = true
wrap-summaries = 82
blank = true
Command line arguments will take precedence over configuration file settings.
For example, if the following is in your ``pyproject.toml``

.. code-block:: yaml
[tool.docformatter]
recursive = true
wrap-summaries = 82
wrap-descriptions = 81
blank = true
And you invoke docformatter as follows:

.. code-block:: console
$ docformatter --config ~/.secret/path/to/pyproject.toml --wrap-summaries 68
Summaries will be wrapped at 68, not 82.

0 comments on commit e171a2e

Please sign in to comment.