From 00e6c8f9ad0449bf36bf86d7d8f1d814b1354918 Mon Sep 17 00:00:00 2001 From: "Lex Darlog (DRL)" <3897975+Lex-DRL@users.noreply.github.com> Date: Mon, 14 Jul 2025 23:51:12 +0500 Subject: [PATCH] + change from callable class to functions --- docstring_formatter.py | 196 ++++++++++++++++++++--------------------- 1 file changed, 95 insertions(+), 101 deletions(-) diff --git a/docstring_formatter.py b/docstring_formatter.py index 00cd05d..899fd43 100644 --- a/docstring_formatter.py +++ b/docstring_formatter.py @@ -7,135 +7,129 @@ import typing as _t from inspect import getdoc as _getdoc import re as _re -from ._dataclass import dataclass_with_slots_if_possible as _dataclass_with_slots_if_possible - _re_indent_match = _re.compile("(\t*)( +)(\t*)(.*?)$").match _re_tab_indent_match = _re.compile("(\t+)(.*?)$").match -@_dataclass_with_slots_if_possible -class DocStringFormatter: - """ - Function-like (callable) class. Turn a pre-cleaned-up docstring (with tabs as spaces and newlines mid-sentence) - into an actually printable output. +def _recover_tab_indents(line: str, tab_size: int): + """Turn indenting spaces back to tabs using regexp. Half-tab indents are rounded.""" + assert bool(line) and isinstance(line, str) - Remember to call the class instance - i.e., ``DocStringFormatter(inspect.cleandoc(__doc__))()`` - """ - doc: str - tab_size: int = 8 - - def __recover_tab_indents(self, line: str): - """Turn indenting spaces back to tabs using regexp. Half-tab indents are rounded.""" - assert bool(line) and isinstance(line, str) - - tab_size = self.tab_size - n_tabs = 0.0 + n_tabs = 0.0 + match = _re_indent_match(line) + while match: + pre_tabs, spaces, post_tabs, line = match.groups() + n_tabs_from_spaces = float(len(spaces)) / tab_size + 0.00001 + n_post_tabs = len(post_tabs) + if n_post_tabs > 0: + # There are tabs after spaces. Don't preserve the fractional spaces-indent, truncate it: + n_tabs_from_spaces = int(n_tabs_from_spaces) + n_tabs += len(pre_tabs) + n_tabs_from_spaces + n_post_tabs match = _re_indent_match(line) - while match: - pre_tabs, spaces, post_tabs, line = match.groups() - n_tabs_from_spaces = float(len(spaces)) / tab_size + 0.00001 - n_post_tabs = len(post_tabs) - if n_post_tabs > 0: - # There are tabs after spaces. Don't preserve the fractional spaces-indent, truncate it: - n_tabs_from_spaces = int(n_tabs_from_spaces) - n_tabs += len(pre_tabs) + n_tabs_from_spaces + n_post_tabs - match = _re_indent_match(line) - if n_tabs < 0.5: - return line + if n_tabs < 0.5: + return line - tabs_prefix = '\t' * int(n_tabs + 0.50001) - return f"{tabs_prefix}{line}" + tabs_prefix = '\t' * int(n_tabs + 0.50001) + return f"{tabs_prefix}{line}" - def __join_paragraph_and_format_tabs(self, paragraph: _t.List[str]): - """ - Given "continuous" paragraph (i.e., with no empty newlines between chunks), recover tabs for each chunk - and join them together into a single actual line. - Works as a generator to account for blocks with different indents - to make each its own line. - """ - _recover_tab_indents = self.__recover_tab_indents - pending_indent = 0 - pending_chunks: _t.List[str] = list() +def _join_paragraph_and_format_tabs(paragraph: _t.List[str], tab_size: int): + """ + Given "continuous" paragraph (i.e., with no empty newlines between chunks), recover tabs for each chunk + and join them together into a single actual line. + Works as a generator to account for blocks with different indents - to make each its own line. + """ + pending_indent = 0 + pending_chunks: _t.List[str] = list() - def join_pending_chunks() -> str: - return "{}{}".format('\t' * pending_indent, ' '.join(pending_chunks)) + def join_pending_chunks() -> str: + return "{}{}".format('\t' * pending_indent, ' '.join(pending_chunks)) - for chunk in paragraph: - chunk = _recover_tab_indents(chunk) + for chunk in paragraph: + chunk = _recover_tab_indents(chunk, tab_size) - cur_indent = 0 - match = _re_tab_indent_match(chunk) - if match: - tab_indent, chunk = match.groups() # We've detected indent. Now, get rid of it. - cur_indent = len(tab_indent) + cur_indent = 0 + match = _re_tab_indent_match(chunk) + if match: + tab_indent, chunk = match.groups() # We've detected indent. Now, get rid of it. + cur_indent = len(tab_indent) - if cur_indent == pending_indent: - pending_chunks.append(chunk) - continue - - # Indent mismatch - we're either ended one block or entered another. Either way, the previous block ends. - if pending_chunks: - yield join_pending_chunks() - pending_chunks = list() - - assert not pending_chunks + if cur_indent == pending_indent: pending_chunks.append(chunk) - pending_indent = cur_indent + continue + # Indent mismatch - we're either ended one block or entered another. Either way, the previous block ends. if pending_chunks: yield join_pending_chunks() + pending_chunks = list() - def __formatted_paragraphs_gen(self): - """ - Generator, which splits docstring into lines and transforms them into an actual printable output: - - From each bulk of empty lines, the first one is skipped... - - ... thus, non-empty lines are joined into continuous paragraphs. - - Recover tabs in the beginning oh lines (``inspect.cleandoc()`` converts them into spaces). - """ - doc = self.doc - if not doc: - return - doc = str(doc) - if not doc.strip(): - return + assert not pending_chunks + pending_chunks.append(chunk) + pending_indent = cur_indent - self.tab_size = max(int(self.tab_size), 1) + if pending_chunks: + yield join_pending_chunks() - _join_paragraph_and_format_tabs = self.__join_paragraph_and_format_tabs - cur_paragraph: _t.List[str] = list() - for line in doc.splitlines(): - line: str = line.rstrip() - if line: - cur_paragraph.append(line) - continue +def _formatted_paragraphs_gen(doc: str, tab_size: int): + """ + Generator, which splits docstring into lines and transforms them into an actual printable output: + - From each bulk of empty lines, the first one is skipped... + - ... thus, non-empty lines are joined into continuous paragraphs. + - Recover tabs in the beginning oh lines (``inspect.cleandoc()`` converts them into spaces). + """ + if not doc: + return + doc = str(doc) + if not doc.strip(): + return - assert not line - if cur_paragraph: - for block in _join_paragraph_and_format_tabs(cur_paragraph): - yield block - cur_paragraph = list() - # Just skip the current empty line entirely - do nothing with it. - continue + tab_size = max(int(tab_size), 1) - # We're in a chain of empty lines, and we've already skipped the first one. Preserve the remaining ones: - yield '' + cur_paragraph: _t.List[str] = list() - # Return the last paragraph post-loop: + for line in doc.splitlines(): + line: str = line.rstrip() + if line: + cur_paragraph.append(line) + continue + + assert not line if cur_paragraph: - for block in _join_paragraph_and_format_tabs(cur_paragraph): + for block in _join_paragraph_and_format_tabs(cur_paragraph, tab_size): yield block + cur_paragraph = list() + # Just skip the current empty line entirely - do nothing with it. + continue - def __call__(self): - return '\n'.join(self.__formatted_paragraphs_gen()) + # We're in a chain of empty lines, and we've already skipped the first one. Preserve the remaining ones: + yield '' - @classmethod - def from_object(cls, _obj, tab_size: int = 8): - doc = _getdoc(_obj) - if not doc: - return '' - # noinspection PyArgumentList - return cls(doc, tab_size=tab_size)() + # Return the last paragraph post-loop: + if cur_paragraph: + for block in _join_paragraph_and_format_tabs(cur_paragraph, tab_size): + yield block + + +def format_docstring(doc: str, tab_size: int = 8) -> str: + """ + Turn a pre-cleaned-up docstring (with tabs as spaces and newlines mid-sentence) + into an actually printable output: + - mid-paragraph new lines are replaced with spaces... + - ... while still keeping indented blocks separate. + + Remember to pass a pre-cleaned-up docstring - i.e.: ``format_docstring(inspect.cleandoc(__doc__))`` + """ + return '\n'.join(_formatted_paragraphs_gen(doc, tab_size)) + + +def format_object_docstring(_obj, tab_size: int = 8) -> str: + """Find the object's docstring and format it with ``format_docstring()``""" + doc = _getdoc(_obj) + if not doc: + return '' + # noinspection PyArgumentList + return format_docstring(doc, tab_size=tab_size)