Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-docstring-to-markdown for 
openSUSE:Factory checked in at 2026-04-01 19:52:13
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-docstring-to-markdown (Old)
 and      /work/SRC/openSUSE:Factory/.python-docstring-to-markdown.new.21863 
(New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-docstring-to-markdown"

Wed Apr  1 19:52:13 2026 rev:7 rq:1344057 version:0.17

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-docstring-to-markdown/python-docstring-to-markdown.changes
        2024-04-07 22:13:58.308107118 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-docstring-to-markdown.new.21863/python-docstring-to-markdown.changes
     2026-04-01 19:53:43.388842706 +0200
@@ -1,0 +2,12 @@
+Wed Apr  1 07:47:58 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.17:
+  * Fix test failures with `importlib-metadata` 8.7.0
+  * Fix reStructuredText edge cases to better support `polars`
+  * Use trusted publishing for package uploads
+- update to 0.16:
+  * Implement entry points to allow extension/customization
+  * Drop Python 3.7 from testing, add 3.12 and 3.13, update
+    actions
+
+-------------------------------------------------------------------

Old:
----
  docstring-to-markdown-0.15-gh.tar.gz

New:
----
  docstring-to-markdown-0.17-gh.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-docstring-to-markdown.spec ++++++
--- /var/tmp/diff_new_pack.3SxiNt/_old  2026-04-01 19:53:45.236919468 +0200
+++ /var/tmp/diff_new_pack.3SxiNt/_new  2026-04-01 19:53:45.248919967 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-docstring-to-markdown
 #
-# Copyright (c) 2024 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -17,28 +17,31 @@
 
 
 %{?sle15_python_module_pythons}
-
 Name:           python-docstring-to-markdown
-Version:        0.15
+Version:        0.17
 Release:        0
 Summary:        On the fly conversion of Python docstrings to markdown
-License:        LGPL-2.1-only
+License:        LGPL-2.1-or-later
 URL:            https://github.com/python-lsp/docstring-to-markdown
 Source:         
https://github.com/python-lsp/docstring-to-markdown/archive/refs/tags/v%{version}.tar.gz#/docstring-to-markdown-%{version}-gh.tar.gz
-BuildRequires:  %{python_module base >= 3.6}
+BuildRequires:  %{python_module base >= 3.7}
+BuildRequires:  %{python_module importlib_metadata >= 3.6}
 BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module setuptools}
+BuildRequires:  %{python_module typing_extensions >= 4.6}
 BuildRequires:  %{python_module wheel}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
+Requires:       python-importlib_metadata >= 3.6
+Requires:       python-typing_extensions >= 4.6
 Provides:       python-docstring_to_markdown = %{version}-%{release}
 BuildArch:      noarch
 %python_subpackages
 
 %description
 On the fly conversion of Python docstrings to markdown
-  - Python 3.6+
+  - Python 3.7+
   - currently can recognise reStructuredText and convert
     multiple of its features to Markdown
   - in the future will be able to convert Google docstrings too

++++++ docstring-to-markdown-0.15-gh.tar.gz -> 
docstring-to-markdown-0.17-gh.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/docstring-to-markdown-0.15/.github/workflows/publish.yml 
new/docstring-to-markdown-0.17/.github/workflows/publish.yml
--- old/docstring-to-markdown-0.15/.github/workflows/publish.yml        
2024-02-21 14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/.github/workflows/publish.yml        
2025-05-02 17:07:43.000000000 +0200
@@ -5,23 +5,39 @@
     types: [created]
 
 jobs:
-  deploy:
+  build:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v2
-    - name: Set up Python 3.8
-      uses: actions/setup-python@v2
+    - uses: actions/checkout@v4
+    - name: Set up Python 3.9
+      uses: actions/setup-python@v5
       with:
-        python-version: 3.8
+        python-version: 3.9
     - name: Install build dependencies
       run: |
-        python -m pip install --upgrade pip wheel
+        python -m pip install --upgrade pip wheel build
     - name: Build package
       run: |
-        python setup.py sdist bdist_wheel
-    - name: Publish a Python distribution to PyPI
-      uses: pypa/[email protected]
+        python -m build
+    - name: Upload Artifact
+      uses: actions/upload-artifact@v4
       with:
-        user: __token__
-        password: ${{ secrets.PYPI_UPLOAD_API_TOKEN }}
-
+        name: docstring-to-markdown dist ${{ github.run_number }}
+        path: ./dist
+  pypi-publish:
+    name: Upload release to PyPI
+    runs-on: ubuntu-latest
+    needs: [build]
+    environment:
+      name: pypi
+      url: https://pypi.org/p/docstring-to-markdown
+    permissions:
+      id-token: write
+    steps:
+    - name: Download artifacts
+      uses: actions/download-artifact@v4
+      with:
+        name: docstring-to-markdown dist ${{ github.run_number }}
+        path: ./dist
+    - name: Publish package distributions to PyPI
+      uses: pypa/gh-action-pypi-publish@release/v1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/docstring-to-markdown-0.15/.github/workflows/tests.yml 
new/docstring-to-markdown-0.17/.github/workflows/tests.yml
--- old/docstring-to-markdown-0.15/.github/workflows/tests.yml  2024-02-21 
14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/.github/workflows/tests.yml  2025-05-02 
17:07:43.000000000 +0200
@@ -11,17 +11,17 @@
     strategy:
       matrix:
         os: [ubuntu-latest]
-        python-version: [3.7, 3.8, 3.9, '3.10', '3.11']
+        python-version: [3.8, 3.9, '3.10', '3.11', '3.12', '3.13']
     runs-on: ${{ matrix.os }}
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v4
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v5
       with:
         python-version: ${{ matrix.python-version }}
     - name: Install test dependencies
       run: |
-        python -m pip install --upgrade pip wheel
+        python -m pip install --upgrade pip wheel build
         python -m pip install -r requirements-dev.txt
     - name: Temporary installation
       run: python -m pip install -e .
@@ -33,8 +33,8 @@
         mypy docstring_to_markdown
     - name: Build package
       run: |
-        python setup.py sdist bdist_wheel
+        python -m build
     - name: Install package
-      run: python -m pip install --find-links=dist --no-index 
--ignore-installed docstring_to_markdown
+      run: python -m pip install --find-links=dist --ignore-installed 
docstring_to_markdown
     - name: Pip check
       run: python -m pip check
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/docstring-to-markdown-0.15/README.md 
new/docstring-to-markdown-0.17/README.md
--- old/docstring-to-markdown-0.15/README.md    2024-02-21 14:50:11.000000000 
+0100
+++ new/docstring-to-markdown-0.17/README.md    2025-05-02 17:07:43.000000000 
+0200
@@ -6,7 +6,7 @@
 
 On the fly conversion of Python docstrings to markdown
 
-- Python 3.6+ (tested on 3.7 up to 3.11)
+- Python 3.7+ (tested on 3.8 up to 3.13)
 - can recognise reStructuredText and convert multiple of its features to 
Markdown
 - since v0.13 includes initial support for Google-formatted docstrings
 
@@ -35,6 +35,11 @@
 docstring_to_markdown.UnknownFormatError
 ```
 
+### Extensibility
+
+`docstring_to_markdown` entry point group allows to add custom converters 
which follow the `Converter` protocol.
+The built-in converters can be customized by providing entry point with 
matching name.
+
 ### Development
 
 ```bash
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/docstring-to-markdown-0.15/docstring_to_markdown/__init__.py 
new/docstring-to-markdown-0.17/docstring_to_markdown/__init__.py
--- old/docstring-to-markdown-0.15/docstring_to_markdown/__init__.py    
2024-02-21 14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/docstring_to_markdown/__init__.py    
2025-05-02 17:07:43.000000000 +0200
@@ -1,27 +1,59 @@
-from .cpython import cpython_to_markdown
-from .google import google_to_markdown, looks_like_google
-from .plain import looks_like_plain_text, plain_text_to_markdown
-from .rst import looks_like_rst, rst_to_markdown
+from importlib_metadata import entry_points
+from typing import List, TYPE_CHECKING
 
-__version__ = "0.15"
+from .types import Converter
+
+if TYPE_CHECKING:
+    from importlib_metadata import EntryPoint
+
+__version__ = "0.17"
 
 
 class UnknownFormatError(Exception):
     pass
 
 
-def convert(docstring: str) -> str:
-    if looks_like_rst(docstring):
-        return rst_to_markdown(docstring)
+def _entry_points_sort_key(entry_point: 'EntryPoint'):
+    if entry_point.dist is None:
+        return 1
+    if entry_point.dist.name == "docstring-to-markdown":
+        return 0
+    return 1
+
+
+def _load_converters() -> List[Converter]:
+    converter_entry_points = entry_points(
+        group="docstring_to_markdown"
+    )
+    # sort so that the default ones can be overridden
+    sorted_entry_points = sorted(
+        converter_entry_points,
+        key=_entry_points_sort_key
+    )
+    # de-duplicate
+    unique_entry_points = {}
+    for entry_point in sorted_entry_points:
+        unique_entry_points[entry_point.name] = entry_point
+
+    converters = []
+    for entry_point in unique_entry_points.values():
+        converter_class = entry_point.load()
+        converters.append(converter_class())
+
+    converters.sort(key=lambda converter: -converter.priority)
 
-    if looks_like_google(docstring):
-        return google_to_markdown(docstring)
+    return converters
 
-    if looks_like_plain_text(docstring):
-        return plain_text_to_markdown(docstring)
 
-    cpython = cpython_to_markdown(docstring)
-    if cpython:
-        return cpython
+_CONVERTERS = None
+
+
+def convert(docstring: str) -> str:
+    global _CONVERTERS
+    if _CONVERTERS is None:
+        _CONVERTERS = _load_converters()
+    for converter in _CONVERTERS:
+        if converter.can_convert(docstring):
+            return converter.convert(docstring)
 
     raise UnknownFormatError()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/docstring-to-markdown-0.15/docstring_to_markdown/cpython.py 
new/docstring-to-markdown-0.17/docstring_to_markdown/cpython.py
--- old/docstring-to-markdown-0.15/docstring_to_markdown/cpython.py     
2024-02-21 14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/docstring_to_markdown/cpython.py     
2025-05-02 17:07:43.000000000 +0200
@@ -1,8 +1,10 @@
 from typing import Union, List
 from re import fullmatch
 
+from .types import Converter
 from ._utils import escape_markdown
 
+
 def _is_cpython_signature_line(line: str) -> bool:
     """CPython uses signature lines in the following format:
 
@@ -30,8 +32,29 @@
         escape_markdown('\n'.join(other_lines))
     ])
 
+
 def looks_like_cpython(text: str) -> bool:
     return cpython_to_markdown(text) is not None
 
 
-__all__ = ['looks_like_cpython', 'cpython_to_markdown']
+class CPythonConverter(Converter):
+
+    priority = 10
+
+    def __init__(self) -> None:
+        self._last_docstring: Union[str, None] = None
+        self._converted: Union[str, None] = None
+
+    def can_convert(self, docstring):
+        self._last_docstring = docstring
+        self._converted = cpython_to_markdown(docstring)
+        return self._converted is not None
+
+    def convert(self, docstring):
+        if docstring != self._last_docstring:
+            self._last_docstring = docstring
+            self._converted = cpython_to_markdown(docstring)
+        return self._converted
+
+
+__all__ = ['looks_like_cpython', 'cpython_to_markdown', 'CPythonConverter']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/docstring-to-markdown-0.15/docstring_to_markdown/google.py 
new/docstring-to-markdown-0.17/docstring_to_markdown/google.py
--- old/docstring-to-markdown-0.15/docstring_to_markdown/google.py      
2024-02-21 14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/docstring_to_markdown/google.py      
2025-05-02 17:07:43.000000000 +0200
@@ -2,6 +2,9 @@
 from textwrap import dedent
 from typing import List
 
+from .types import Converter
+
+
 # All possible sections in Google style docstrings
 SECTION_HEADERS: List[str] = [
     "Args",
@@ -169,3 +172,14 @@
     docstring = GoogleDocstring(text)
 
     return docstring.as_markdown()
+
+
+class GoogleConverter(Converter):
+
+    priority = 75
+
+    def can_convert(self, docstring):
+        return looks_like_google(docstring)
+
+    def convert(self, docstring):
+        return google_to_markdown(docstring)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/docstring-to-markdown-0.15/docstring_to_markdown/plain.py 
new/docstring-to-markdown-0.17/docstring_to_markdown/plain.py
--- old/docstring-to-markdown-0.15/docstring_to_markdown/plain.py       
2024-02-21 14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/docstring_to_markdown/plain.py       
2025-05-02 17:07:43.000000000 +0200
@@ -1,4 +1,5 @@
 from re import fullmatch
+from .types import Converter
 from ._utils import escape_markdown
 
 
@@ -24,4 +25,16 @@
 def plain_text_to_markdown(text: str) -> str:
     return escape_markdown(text)
 
-__all__ = ['looks_like_plain_text', 'plain_text_to_markdown']
+
+class PlainTextConverter(Converter):
+
+    priority = 50
+
+    def can_convert(self, docstring):
+        return looks_like_plain_text(docstring)
+
+    def convert(self, docstring):
+        return plain_text_to_markdown(docstring)
+
+
+__all__ = ['looks_like_plain_text', 'plain_text_to_markdown', 
'PlainTextConverter']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/docstring-to-markdown-0.15/docstring_to_markdown/rst.py 
new/docstring-to-markdown-0.17/docstring_to_markdown/rst.py
--- old/docstring-to-markdown-0.15/docstring_to_markdown/rst.py 2024-02-21 
14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/docstring_to_markdown/rst.py 2025-05-02 
17:07:43.000000000 +0200
@@ -1,9 +1,12 @@
 from abc import ABC, abstractmethod
 from enum import IntEnum, auto
+from textwrap import dedent
 from types import SimpleNamespace
 from typing import Callable, Match, Union, List, Dict
 import re
 
+from .types import Converter
+
 
 class Directive:
     def __init__(
@@ -297,8 +300,8 @@
 SECTION_DIRECTIVES: Dict[str, List[Directive]] = {
     'Parameters': [
         Directive(
-            pattern=r'^(?P<other_args>\*\*kwargs|\*args)$',
-            replacement=r'- `\g<other_args>`'
+            pattern=r'^(?P<other_args>(\w[\w\d_\.]*)|\*\*kwargs|\*args)$',
+            replacement=r'- `\g<other_args>`:'
         ),
         Directive(
             pattern=r'^(?P<arg1>[^:\s]+\d), (?P<arg2>[^:\s]+\d), \.\.\. : 
(?P<type>.+)$',
@@ -334,6 +337,7 @@
 
 
 def looks_like_rst(value: str) -> bool:
+    value = dedent(value)
     # check if any of the characteristic sections (and the properly formatted 
underline) is there
     for section in _RST_SECTIONS:
         if (section + '\n' + '-' * len(section) + '\n') in value:
@@ -532,7 +536,7 @@
                 self._rows.append(self._split(line))
             self._expecting_row_content = not self._expecting_row_content
         else:
-            self._state += 1
+            self._state += 1    # pragma: no cover
 
 
 class BlockParser(IParser):
@@ -540,10 +544,20 @@
     follower: Union['IParser', None] = None
     _buffer: List[str]
     _block_started: bool
+    _indent: Union[int, None]
+    should_measure_indent = True
 
     def __init__(self):
         self._buffer = []
         self._block_started = False
+        self._indent = None
+
+    def measure_indent(self, line: str):
+        line_indent = len(line) - len(line.lstrip())
+        if self._indent is None:
+            self._indent = line_indent
+        else:
+            self._indent = min(line_indent, self._indent)
 
     @abstractmethod
     def can_parse(self, line: str) -> bool:
@@ -556,6 +570,8 @@
     def consume(self, line: str):
         if not self._block_started:
             raise ValueError('Block has not started')   # pragma: no cover
+        if self.should_measure_indent:
+            self.measure_indent(line)
         self._buffer.append(line)
 
     def finish_consumption(self, final: bool) -> str:
@@ -563,17 +579,24 @@
         if self._buffer[len(self._buffer) - 1].strip() == '':
             self._buffer.pop()
         self._buffer.append(self.enclosure + '\n')
-        result = '\n'.join(self._buffer)
+        indent = " " * (self._indent or 0)
+        intermediate = '\n'.join(self._buffer)
+        result = '\n'.join([
+            (indent + line) if line else line
+            for line in intermediate.splitlines()
+        ]) if indent else intermediate
         if not final:
             result += '\n'
         self._buffer = []
         self._block_started = False
+        self._indent = None
         return result
 
 
 class IndentedBlockParser(BlockParser, ABC):
     _is_block_beginning: bool
     _block_indent_size: Union[int, None]
+    should_measure_indent = False
 
     def __init__(self):
         super(IndentedBlockParser, self).__init__()
@@ -597,6 +620,7 @@
                 return
         if self._block_indent_size is None:
             self._block_indent_size = len(line) - len(line.lstrip())
+        self.measure_indent(line)
         super().consume(line[self._block_indent_size:])
 
     def finish_consumption(self, final: bool) -> str:
@@ -651,11 +675,13 @@
         if line.strip() == '.. autosummary::':
             language = ''
             line = ''
+            suffix = ''
         else:
             line = re.sub(r'::$', '', line)
+            suffix = '\n\n'
 
         self._start_block(language)
-        return IBlockBeginning(remainder=line.rstrip() + '\n\n')
+        return IBlockBeginning(remainder=line.rstrip() + suffix)
 
 
 class MathBlockParser(IndentedBlockParser):
@@ -680,6 +706,7 @@
         return line.strip() in self.directives
 
     def initiate_parsing(self, line: str, current_language: str):
+        self.measure_indent(line)
         admonition = self.directives[line.strip()]
         self._start_block(f'\n{admonition.block_markdown}\n')
         return IBlockBeginning(remainder='')
@@ -690,6 +717,7 @@
         return re.match(CODE_BLOCK_PATTERN, line) is not None
 
     def initiate_parsing(self, line: str, current_language: str) -> 
IBlockBeginning:
+        self.measure_indent(line)
         match = re.match(CODE_BLOCK_PATTERN, line)
         # already checked in can_parse
         assert match
@@ -749,6 +777,8 @@
     most_recent_section: Union[str, None] = None
     is_first_line = True
 
+    text = dedent(text)
+
     def flush_buffer():
         nonlocal lines_buffer
         lines = '\n'.join(lines_buffer)
@@ -762,7 +792,8 @@
         lines_buffer = []
         return lines
 
-    for line in text.split('\n'):
+    lines = text.split('\n')
+    for i, line in enumerate(lines):
         if is_first_line:
             if extract_signature:
                 signature_match = 
re.match(r'^(?P<name>\S+)\((?P<params>.*)\)$', line)
@@ -805,7 +836,9 @@
             else:
                 if most_recent_section in SECTION_DIRECTIVES:
                     for section_directive in 
SECTION_DIRECTIVES[most_recent_section]:
-                        if re.match(section_directive.pattern, trimmed_line):
+                        next_line = lines[i + 1] if i + 1 < len(lines) else ""
+                        is_next_line_section = set(next_line.strip()) == {"-"}
+                        if re.match(section_directive.pattern, line) and not 
is_next_line_section:
                             line = re.sub(section_directive.pattern, 
section_directive.replacement, trimmed_line)
                             break
                 if trimmed_line.rstrip() in RST_SECTIONS:
@@ -825,3 +858,14 @@
     if active_parser:
         markdown += active_parser.finish_consumption(True)
     return markdown
+
+
+class ReStructuredTextConverter(Converter):
+
+    priority = 100
+
+    def can_convert(self, docstring):
+        return looks_like_rst(docstring)
+
+    def convert(self, docstring):
+        return rst_to_markdown(docstring)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/docstring-to-markdown-0.15/docstring_to_markdown/types.py 
new/docstring-to-markdown-0.17/docstring_to_markdown/types.py
--- old/docstring-to-markdown-0.15/docstring_to_markdown/types.py       
1970-01-01 01:00:00.000000000 +0100
+++ new/docstring-to-markdown-0.17/docstring_to_markdown/types.py       
2025-05-02 17:07:43.000000000 +0200
@@ -0,0 +1,14 @@
+from typing_extensions import Protocol
+
+
+class Converter(Protocol):
+
+    def convert(self, docstring: str) -> str:
+        """Convert given docstring to markdown."""
+
+    def can_convert(self, docstring: str) -> bool:
+        """Check if conversion to markdown can be performed."""
+
+    # The higher the priority, the sooner the conversion
+    # with this converter will be attempted.
+    priority: int
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/docstring-to-markdown-0.15/pyproject.toml 
new/docstring-to-markdown-0.17/pyproject.toml
--- old/docstring-to-markdown-0.15/pyproject.toml       1970-01-01 
01:00:00.000000000 +0100
+++ new/docstring-to-markdown-0.17/pyproject.toml       2025-05-02 
17:07:43.000000000 +0200
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/docstring-to-markdown-0.15/requirements-dev.txt 
new/docstring-to-markdown-0.17/requirements-dev.txt
--- old/docstring-to-markdown-0.15/requirements-dev.txt 2024-02-21 
14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/requirements-dev.txt 2025-05-02 
17:07:43.000000000 +0200
@@ -1,5 +1,5 @@
 pytest
 pytest-cov
 pytest-flake8
-flake8<5
+flake8
 mypy
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/docstring-to-markdown-0.15/setup.cfg 
new/docstring-to-markdown-0.17/setup.cfg
--- old/docstring-to-markdown-0.15/setup.cfg    2024-02-21 14:50:11.000000000 
+0100
+++ new/docstring-to-markdown-0.17/setup.cfg    2025-05-02 17:07:43.000000000 
+0200
@@ -28,21 +28,36 @@
 
 [options]
 packages = find:
-python_requires = >=3.6
+python_requires = >=3.7
+install_requires =
+    importlib-metadata>=3.6
+    typing_extensions>=4.6
 
 [options.package_data]
 docstring-to-markdown = py.typed
 
+[options.entry_points]
+docstring_to_markdown =
+  rst = docstring_to_markdown.rst:ReStructuredTextConverter
+  google = docstring_to_markdown.google:GoogleConverter
+  plain = docstring_to_markdown.plain:PlainTextConverter
+  cpython = docstring_to_markdown.cpython:CPythonConverter
+
 [tool:pytest]
 addopts =
     --pyargs tests
     --cov docstring_to_markdown
-    --cov-fail-under=99
+    --cov-fail-under=100
     --cov-report term-missing:skip-covered
     -p no:warnings
     --flake8
     -vv
 
+[coverage:report]
+exclude_lines =
+    pragma: no cover
+    if TYPE_CHECKING:
+
 [flake8]
 max-line-length = 120
 max-complexity = 15
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/docstring-to-markdown-0.15/tests/test_convert.py 
new/docstring-to-markdown-0.17/tests/test_convert.py
--- old/docstring-to-markdown-0.15/tests/test_convert.py        2024-02-21 
14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/tests/test_convert.py        2025-05-02 
17:07:43.000000000 +0200
@@ -1,4 +1,10 @@
+from contextlib import contextmanager
 from docstring_to_markdown import convert, UnknownFormatError
+from docstring_to_markdown.types import Converter
+from docstring_to_markdown.cpython import CPythonConverter
+from importlib_metadata import EntryPoint, entry_points, distribution
+from unittest.mock import patch
+import docstring_to_markdown
 import pytest
 
 CPYTHON = """\
@@ -55,3 +61,72 @@
 def test_unknown_format():
     with pytest.raises(UnknownFormatError):
         convert('ARGS [arg1, arg2] RETURNS: str OR None')
+
+
+class HighPriorityConverter(Converter):
+    priority = 120
+
+    def convert(self, docstring):
+        return "HighPriority"
+
+    def can_convert(self, docstring):
+        return True
+
+
+class MockEntryPoint(EntryPoint):
+    def load(self):
+        return globals()[self.attr]
+
+    dist = None
+
+
+class DistMockEntryPoint(MockEntryPoint):
+    # Pretend it is contributed by `pytest`.
+    # It could be anything else, but `pytest`
+    # is guaranteed to be installed during tests.
+    dist = distribution('pytest')
+
+
+class CustomCPythonConverter(CPythonConverter):
+    priority = 10
+
+    def convert(self, docstring):
+        return 'CustomCPython'
+
+    def can_convert(self, docstring):
+        return True
+
+
+@contextmanager
+def custom_entry_points(entry_points):
+    old = docstring_to_markdown._CONVERTERS
+    docstring_to_markdown._CONVERTERS = None
+    with patch.object(docstring_to_markdown, 'entry_points', 
return_value=entry_points):
+        yield
+    docstring_to_markdown._CONVERTERS = old
+
+
+def test_adding_entry_point():
+    original_entry_points = entry_points(group="docstring_to_markdown")
+    mock_entry_point = MockEntryPoint(
+        name='high-priority-converter',
+        group='docstring_to_markdown',
+        value="here:HighPriorityConverter",
+    )
+    with custom_entry_points([*original_entry_points, mock_entry_point]):
+        assert convert('test') == 'HighPriority'
+
+
+def test_replacing_entry_point():
+    assert convert(CPYTHON) == CPYTHON_MD
+    original_entry_points = entry_points(group="docstring_to_markdown")
+    mock_entry_point = DistMockEntryPoint(
+        name='cpython',
+        group='docstring_to_markdown',
+        value="here:CustomCPythonConverter",
+    )
+    with custom_entry_points([*original_entry_points, mock_entry_point]):
+        assert convert('test') == 'test'
+        assert convert(GOOGLE) == GOOGLE_MD
+        assert convert(RST) == RST_MD
+        assert convert(CPYTHON) == 'CustomCPython'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/docstring-to-markdown-0.15/tests/test_cpython.py 
new/docstring-to-markdown-0.17/tests/test_cpython.py
--- old/docstring-to-markdown-0.15/tests/test_cpython.py        2024-02-21 
14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/tests/test_cpython.py        2025-05-02 
17:07:43.000000000 +0200
@@ -1,5 +1,5 @@
 import pytest
-from docstring_to_markdown.cpython import looks_like_cpython, 
cpython_to_markdown
+from docstring_to_markdown.cpython import looks_like_cpython, 
cpython_to_markdown, CPythonConverter
 
 BOOL = """\
 bool(x) -> bool
@@ -101,3 +101,10 @@
 
 def test_conversion_str():
     assert cpython_to_markdown(STR) == STR_MD
+
+
+def test_convert():
+    converter = CPythonConverter()
+    assert converter.can_convert(BOOL)
+    assert not converter.can_convert('this is plain text')
+    assert converter.convert(BOOL) == BOOL_MD
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/docstring-to-markdown-0.15/tests/test_google.py 
new/docstring-to-markdown-0.17/tests/test_google.py
--- old/docstring-to-markdown-0.15/tests/test_google.py 2024-02-21 
14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/tests/test_google.py 2025-05-02 
17:07:43.000000000 +0200
@@ -1,6 +1,6 @@
 import pytest
 
-from docstring_to_markdown.google import google_to_markdown, looks_like_google
+from docstring_to_markdown.google import google_to_markdown, 
looks_like_google, GoogleConverter
 
 BASIC_EXAMPLE = """Do **something**.
 
@@ -133,3 +133,10 @@
 )
 def test_google_to_markdown(google, markdown):
     assert google_to_markdown(google) == markdown
+
+
+def test_converter():
+    converter = GoogleConverter()
+    assert converter.can_convert(BASIC_EXAMPLE)
+    assert not converter.can_convert("This is plain text")
+    assert converter.convert(BASIC_EXAMPLE) == BASIC_EXAMPLE_MD
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/docstring-to-markdown-0.15/tests/test_plain.py 
new/docstring-to-markdown-0.17/tests/test_plain.py
--- old/docstring-to-markdown-0.15/tests/test_plain.py  2024-02-21 
14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/tests/test_plain.py  2025-05-02 
17:07:43.000000000 +0200
@@ -1,5 +1,5 @@
 import pytest
-from docstring_to_markdown.plain import looks_like_plain_text, 
plain_text_to_markdown
+from docstring_to_markdown.plain import looks_like_plain_text, 
plain_text_to_markdown, PlainTextConverter
 
 
 @pytest.mark.parametrize("text", [
@@ -40,3 +40,10 @@
 
 def test_conversion():
     assert plain_text_to_markdown("test") == "test"
+
+
+def test_convert():
+    converter = PlainTextConverter()
+    assert not converter.can_convert('def test():')
+    assert converter.can_convert('this is plain text')
+    assert converter.convert('test') == 'test'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/docstring-to-markdown-0.15/tests/test_rst.py 
new/docstring-to-markdown-0.17/tests/test_rst.py
--- old/docstring-to-markdown-0.15/tests/test_rst.py    2024-02-21 
14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/tests/test_rst.py    2025-05-02 
17:07:43.000000000 +0200
@@ -1,6 +1,6 @@
 import pytest
 
-from docstring_to_markdown.rst import looks_like_rst, rst_to_markdown
+from docstring_to_markdown.rst import looks_like_rst, rst_to_markdown, 
ReStructuredTextConverter
 
 
 SEE_ALSO = """
@@ -119,6 +119,26 @@
 A function definition is an executable statement.
 """
 
+RST_AUTOSUMMARY_BLOCK = """
+Summary
+
+.. autosummary::
+
+   environment.BuildEnvironment
+   util.relative_uri
+"""
+
+
+RST_AUTOSUMMARY_BLOCK_MARKDOWN = """
+Summary
+
+```
+environment.BuildEnvironment
+util.relative_uri
+```
+"""
+
+
 RST_COLON_CODE_BLOCK = """
 For example, the following code ::
 
@@ -317,7 +337,7 @@
 
 - `x`: array_like
     Input array.
-- `**kwargs`
+- `**kwargs`:
     For other keyword-only arguments, see the ufunc docs.
 """
 
@@ -598,6 +618,139 @@
 """
 
 
+BROKEN_GRID_TABLE = """
++------------+-----------+------------+-----------------+---+---------+
+|param_kernel|param_gamma|param_degree|split0_test_score|...|rank_t...|
++============+===========+============+=================+===+=========+
+|  'poly'    |     --    |      2     |       0.80      |...|    2    |
++------------+-----------+------------+-----------------+---+---------+
+|  'poly'    |     --    |      3     |       0.70      |...|    4    |
+someone forgot to close the row above.
+"""
+
+
+BROKEN_GRID_TABLE_MARKDOWN = """
+| param_kernel | param_gamma | param_degree | split0_test_score | ... | 
rank_t... |
+| ------------ | ----------- | ------------ | ----------------- | --- | 
--------- |
+| 'poly'       | --          | 2            | 0.80              | ... | 2      
   |
+| 'poly'       | --          | 3            | 0.70              | ... | 4      
   |
+someone forgot to close the row above.
+"""
+
+
+# this format is often used by polars
+PARAMETERS_WITHOUT_TYPE = """
+Parameters
+----------
+source
+    Path(s) to a file or directory
+    When needing to authenticate for scanning cloud locations, see the
+    `storage_options` parameter.
+columns
+    Columns to select. Accepts a list of column indices (starting at zero) or 
a list
+    of column names.
+n_rows
+    Stop reading from parquet file after reading `n_rows`.
+    Only valid when `use_pyarrow=False`.
+
+Returns
+-------
+DataFrame
+"""
+
+PARAMETERS_WITHOUT_TYPE_MARKDOWN = """
+#### Parameters
+
+- `source`:
+    Path(s) to a file or directory
+    When needing to authenticate for scanning cloud locations, see the
+    `storage_options` parameter.
+- `columns`:
+    Columns to select. Accepts a list of column indices (starting at zero) or 
a list
+    of column names.
+- `n_rows`:
+    Stop reading from parquet file after reading `n_rows`.
+    Only valid when `use_pyarrow=False`.
+
+#### Returns
+
+DataFrame
+"""
+
+INDENTED_DOCSTRING = """
+    Parameters
+    ----------
+    glob
+        Expand path given via globbing rules.
+"""
+
+INDENTED_DOCSTRING_MARKDOWN = """
+#### Parameters
+
+- `glob`:
+    Expand path given via globbing rules.
+"""
+
+
+WARNINGS_IN_PARAMETERS = """
+Parameters
+----------
+glob
+    Expand path given via globbing rules.
+schema
+    Specify the datatypes of the columns. The datatypes must match the
+    datatypes in the file(s). If there are extra columns that are not in the
+    file(s), consider also enabling `allow_missing_columns`.
+
+    .. warning::
+        This functionality is considered **unstable**. It may be changed
+        at any point without it being considered a breaking change.
+hive_schema
+    The column names and data types of the columns by which the data is 
partitioned.
+    If set to `None` (default), the schema of the Hive partitions is inferred.
+
+    .. warning::
+        This functionality is considered **unstable**. It may be changed
+        at any point without it being considered a breaking change.
+try_parse_hive_dates
+    Whether to try parsing hive values as date/datetime types.
+"""
+
+
+WARNINGS_IN_PARAMETERS_MARKDOWN = """
+#### Parameters
+
+- `glob`:
+    Expand path given via globbing rules.
+- `schema`:
+    Specify the datatypes of the columns. The datatypes must match the
+    datatypes in the file(s). If there are extra columns that are not in the
+    file(s), consider also enabling `allow_missing_columns`.
+
+
+    ---
+    ⚠️  **Warning**
+
+    This functionality is considered **unstable**. It may be changed
+    at any point without it being considered a breaking change.
+
+    ---
+- `hive_schema`:
+    The column names and data types of the columns by which the data is 
partitioned.
+    If set to `None` (default), the schema of the Hive partitions is inferred.
+
+
+    ---
+    ⚠️  **Warning**
+
+    This functionality is considered **unstable**. It may be changed
+    at any point without it being considered a breaking change.
+
+    ---
+- `try_parse_hive_dates`:
+    Whether to try parsing hive values as date/datetime types.
+"""
+
 NESTED_PARAMETERS = """
 Parameters
 ----------
@@ -733,6 +886,10 @@
         'rst': NUMPY_EXAMPLE,
         'md': NUMPY_EXAMPLE_MARKDOWN
     },
+    'converts autosummary block': {
+        'rst': RST_AUTOSUMMARY_BLOCK,
+        'md': RST_AUTOSUMMARY_BLOCK_MARKDOWN
+    },
     'converts version changed': {
         'rst': '.. versionchanged:: 0.23.0',
         'md': '*Changed in 0.23.0*'
@@ -835,10 +992,26 @@
         'rst': GRID_TABLE_IN_SKLEARN,
         'md': GRID_TABLE_IN_SKLEARN_MARKDOWN
     },
+    'converts broken grid table': {
+        'rst': BROKEN_GRID_TABLE,
+        'md': BROKEN_GRID_TABLE_MARKDOWN
+    },
     'converts nested parameter lists': {
         'rst': NESTED_PARAMETERS,
         'md': NESTED_PARAMETERS_MARKDOWN
     },
+    'converts parameter without type': {
+        'rst': PARAMETERS_WITHOUT_TYPE,
+        'md': PARAMETERS_WITHOUT_TYPE_MARKDOWN
+    },
+    'converts indented parameters lists': {
+        'rst': INDENTED_DOCSTRING,
+        'md': INDENTED_DOCSTRING_MARKDOWN
+    },
+    'converts warnings in parameters lists': {
+        'rst': WARNINGS_IN_PARAMETERS,
+        'md': WARNINGS_IN_PARAMETERS_MARKDOWN
+    },
     'converts sphinx signatures': {
         'rst': SPHINX_SIGNATURE,
         'md': SPHINX_SIGNATURE_MARKDOWN
@@ -888,3 +1061,10 @@
     converted = rst_to_markdown(rst)
     print(converted)
     assert converted == markdown
+
+
+def test_converter():
+    converter = ReStructuredTextConverter()
+    assert converter.can_convert('.. versionadded:: 0.1')
+    assert not converter.can_convert('this is plain text')
+    assert converter.convert(PEP_287_CODE_BLOCK) == PEP_287_CODE_BLOCK_MARKDOWN

Reply via email to