Skip to content

Commit

Permalink
feat: Add members and filters options
Browse files Browse the repository at this point in the history
  • Loading branch information
pawamoy committed Apr 28, 2022
1 parent 8f4c853 commit 24a6136
Show file tree
Hide file tree
Showing 8 changed files with 400 additions and 303 deletions.
13 changes: 12 additions & 1 deletion src/mkdocstrings_handlers/python/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import os
import posixpath
import re
import sys
from collections import ChainMap
from contextlib import suppress
Expand Down Expand Up @@ -75,6 +76,8 @@ class PythonHandler(BaseHandler):
"heading_level": 2,
"members_order": rendering.Order.alphabetical.value,
"docstring_section_style": "table",
"members": None,
"filters": ["!^_[^_]"],
}
"""
Attributes: Default rendering options:
Expand All @@ -97,6 +100,9 @@ class PythonHandler(BaseHandler):
heading_level (int): The initial heading level to use. Default: `2`.
members_order (str): The members ordering to use. Options: `alphabetical` - order by the members names, `source` - order members as they appear in the source file. Default: `alphabetical`.
docstring_section_style (str): The style used to render docstring sections. Options: `table`, `list`, `spacy`. Default: `table`.
members (list[str] | False | None): An explicit list of members to render. Default: `None`.
filters (list[str] | None): A list of filters applied to filter objects based on their name.
A filter starting with `!` will exclude matching objects instead of including them. Default: `["!^_[^_]"]`.
""" # noqa: E501

def __init__(
Expand Down Expand Up @@ -222,6 +228,11 @@ def render(self, data: CollectorItem, config: dict) -> str: # noqa: D102 (ignor
choices = "', '".join(item.value for item in rendering.Order)
raise PluginError(f"Unknown members_order '{final_config['members_order']}', choose between '{choices}'.")

if final_config["filters"]:
final_config["filters"] = [
(re.compile(filtr.lstrip("!")), filtr.startswith("!")) for filtr in final_config["filters"]
]

return template.render(
**{"config": final_config, data.kind.value: data, "heading_level": heading_level, "root": True},
)
Expand All @@ -236,7 +247,7 @@ def update_env(self, md: Markdown, config: dict) -> None: # noqa: D102 (ignore
self.env.filters["order_members"] = rendering.do_order_members
self.env.filters["format_code"] = rendering.do_format_code
self.env.filters["format_signature"] = rendering.do_format_signature
self.env.filters["filter_docstrings"] = rendering.do_filter_docstrings
self.env.filters["filter_objects"] = rendering.do_filter_objects

def get_anchors(self, data: CollectorItem) -> list[str]: # noqa: D102 (ignore missing docstring)
try:
Expand Down
59 changes: 51 additions & 8 deletions src/mkdocstrings_handlers/python/rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import re
import sys
from functools import lru_cache
from typing import Any, Sequence
from typing import Any, Pattern, Sequence

from griffe.dataclasses import Alias, Object
from markupsafe import Markup
Expand Down Expand Up @@ -77,16 +77,28 @@ def do_format_signature(signature: str, line_length: int) -> str:
return formatted[4:-5].strip()[:-1]


def do_order_members(members: Sequence[Object | Alias], order: Order) -> Sequence[Object | Alias]:
def do_order_members(
members: Sequence[Object | Alias],
order: Order,
members_list: list[str] | None,
) -> Sequence[Object | Alias]:
"""Order members given an ordering method.
Parameters:
members: The members to order.
order: The ordering method.
members_list: An optional member list (manual ordering).
Returns:
The same members, ordered.
"""
if members_list:
sorted_members = []
members_dict = {member.name: member for member in members}
for name in members_list:
if name in members_dict:
sorted_members.append(members_dict[name])
return sorted_members
return sorted(members, key=order_map[order])


Expand Down Expand Up @@ -133,22 +145,53 @@ def repl(match): # noqa: WPS430
return Markup(text).format(**variables)


def do_filter_docstrings(
def _keep_object(name, filters):
keep = None
rules = set()
for regex, exclude in filters:
rules.add(exclude)
if regex.search(name):
keep = not exclude
if keep is None:
if rules == {False}: # noqa: WPS531
# only included stuff, no match = reject
return False
# only excluded stuff, or included and excluded stuff, no match = keep
return True
return keep


def do_filter_objects(
objects_dictionary: dict[str, Object | Alias],
keep_empty: bool = True,
filters: list[tuple[bool, Pattern]] | None = None,
members_list: list[str] | None = None,
keep_no_docstrings: bool = True,
) -> list[Object | Alias]:
"""Filter a dictionary of objects based on their docstrings.
Parameters:
objects_dictionary: The dictionary of objects.
keep_empty: Whether to keep objects with no/empty docstrings (recursive check).
filters: Filters to apply, based on members' names.
Each element is a tuple: a pattern, and a boolean indicating whether
to reject the object if the pattern matches.
members_list: An optional, explicit list of members to keep.
When given and empty, return an empty list.
When given and not empty, ignore filters and docstrings presence/absence.
keep_no_docstrings: Whether to keep objects with no/empty docstrings (recursive check).
Returns:
A list of objects.
"""
if keep_empty:
return list(objects_dictionary.values())
return [obj for obj in objects_dictionary.values() if obj.has_docstrings]
if members_list is not None:
if not members_list:
return []
return [obj for obj in objects_dictionary.values() if obj.name in set(members_list)]
objects = list(objects_dictionary.values())
if filters:
objects = [obj for obj in objects if _keep_object(obj.name, filters)]
if keep_no_docstrings:
return objects
return [obj for obj in objects if obj.has_docstrings]


@lru_cache(maxsize=1)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,72 +1,69 @@
{{ log.debug("Rendering " + attribute.path) }}
{% if config.show_if_no_docstring or attribute.has_docstrings %}

<div class="doc doc-object doc-attribute">
{% with html_id = attribute.path %}
<div class="doc doc-object doc-attribute">
{% with html_id = attribute.path %}

{% if not root or config.show_root_heading %}
{% if root %}
{% set show_full_path = config.show_root_full_path %}
{% set root_members = True %}
{% elif root_members %}
{% set show_full_path = config.show_root_members_full_path or config.show_object_full_path %}
{% set root_members = False %}
{% else %}
{% set show_full_path = config.show_object_full_path %}
{% endif %}

{% if root %}
{% set show_full_path = config.show_root_full_path %}
{% set root_members = True %}
{% elif root_members %}
{% set show_full_path = config.show_root_members_full_path or config.show_object_full_path %}
{% set root_members = False %}
{% else %}
{% set show_full_path = config.show_object_full_path %}
{% endif %}

{% filter heading(heading_level,
role="data" if attribute.parent.kind.value == "module" else "attr",
id=html_id,
class="doc doc-heading",
toc_label=attribute.name) %}
{% if not root or config.show_root_heading %}

{% if config.separate_signature %}
{% if show_full_path %}{{ attribute.path }}{% else %}{{ attribute.name }}{% endif %}
{% else %}
{% filter highlight(language="python", inline=True) %}
{% if show_full_path %}{{ attribute.path }}{% else %}{{ attribute.name }}{% endif %}
{% if attribute.annotation %}: {{ attribute.annotation }}{% endif %}
{% if attribute.value %} = {{ attribute.value }}{% endif %}
{% endfilter %}
{% endif %}

{% with labels = attribute.labels %}
{% include "labels.html" with context %}
{% endwith %}

{% endfilter %}
{% filter heading(heading_level,
role="data" if attribute.parent.kind.value == "module" else "attr",
id=html_id,
class="doc doc-heading",
toc_label=attribute.name) %}

{% if config.separate_signature %}
{% filter highlight(language="python", inline=False) %}
{% filter format_code(config.line_length) %}
{% if show_full_path %}{{ attribute.path }}{% else %}{{ attribute.name }}{% endif %}
{% if attribute.annotation %}: {{ attribute.annotation|safe }}{% endif %}
{% if attribute.value %} = {{ attribute.value|safe }}{% endif %}
{% endfilter %}
{% if show_full_path %}{{ attribute.path }}{% else %}{{ attribute.name }}{% endif %}
{% else %}
{% filter highlight(language="python", inline=True) %}
{% if show_full_path %}{{ attribute.path }}{% else %}{{ attribute.name }}{% endif %}
{% if attribute.annotation %}: {{ attribute.annotation }}{% endif %}
{% if attribute.value %} = {{ attribute.value }}{% endif %}
{% endfilter %}
{% endif %}

{% else %}
{% if config.show_root_toc_entry %}
{% filter heading(heading_level,
role="data" if attribute.parent.kind.value == "module" else "attr",
id=html_id,
toc_label=attribute.path if config.show_root_full_path else attribute.name,
hidden=True) %}
{% with labels = attribute.labels %}
{% include "labels.html" with context %}
{% endwith %}

{% endfilter %}

{% if config.separate_signature %}
{% filter highlight(language="python", inline=False) %}
{% filter format_code(config.line_length) %}
{% if show_full_path %}{{ attribute.path }}{% else %}{{ attribute.name }}{% endif %}
{% if attribute.annotation %}: {{ attribute.annotation|safe }}{% endif %}
{% if attribute.value %} = {{ attribute.value|safe }}{% endif %}
{% endfilter %}
{% endif %}
{% set heading_level = heading_level - 1 %}
{% endfilter %}
{% endif %}

<div class="doc doc-contents {% if root %}first{% endif %}">
{% with docstring_sections = attribute.docstring.parsed %}
{% include "docstring.html" with context %}
{% endwith %}
</div>
{% else %}
{% if config.show_root_toc_entry %}
{% filter heading(heading_level,
role="data" if attribute.parent.kind.value == "module" else "attr",
id=html_id,
toc_label=attribute.path if config.show_root_full_path else attribute.name,
hidden=True) %}
{% endfilter %}
{% endif %}
{% set heading_level = heading_level - 1 %}
{% endif %}

{% endwith %}
<div class="doc doc-contents {% if root %}first{% endif %}">
{% with docstring_sections = attribute.docstring.parsed %}
{% include "docstring.html" with context %}
{% endwith %}
</div>

{% endif %}
{% endwith %}
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@

<div class="doc doc-children">

{% if root_members %}
{% set members_list = config.members %}
{% else %}
{% set members_list = none %}
{% endif %}

{% if config.group_by_category %}

{% with %}
Expand All @@ -13,59 +19,77 @@
{% set extra_level = 0 %}
{% endif %}

{% if config.show_category_heading and obj.attributes|filter_docstrings(config.show_if_no_docstring) %}
{% filter heading(heading_level, id=html_id ~ "-attributes") %}Attributes{% endfilter %}
{% endif %}
{% with heading_level = heading_level + extra_level %}
{% for attribute in obj.attributes.values()|order_members(config.members_order) %}
{% if not attribute.is_alias or attribute.is_explicitely_exported %}
{% include "attribute.html" with context %}
{% with attributes = obj.attributes|filter_objects(config.filters, members_list, config.show_if_no_docstring) %}
{% if attributes %}
{% if config.show_category_heading %}
{% filter heading(heading_level, id=html_id ~ "-attributes") %}Attributes{% endfilter %}
{% endif %}
{% endfor %}
{% with heading_level = heading_level + extra_level %}
{% for attribute in attributes|order_members(config.members_order, members_list) %}
{% if not attribute.is_alias or attribute.is_explicitely_exported %}
{% include "attribute.html" with context %}
{% endif %}
{% endfor %}
{% endwith %}
{% endif %}
{% endwith %}

{% if config.show_category_heading and obj.classes|filter_docstrings(config.show_if_no_docstring) %}
{% filter heading(heading_level, id=html_id ~ "-classes") %}Classes{% endfilter %}
{% endif %}
{% with heading_level = heading_level + extra_level %}
{% for class in obj.classes.values()|order_members(config.members_order) %}
{% if not class.is_alias or class.is_explicitely_exported %}
{% include "class.html" with context %}
{% with classes = obj.classes|filter_objects(config.filters, members_list, config.show_if_no_docstring) %}
{% if classes %}
{% if config.show_category_heading %}
{% filter heading(heading_level, id=html_id ~ "-classes") %}Classes{% endfilter %}
{% endif %}
{% endfor %}
{% with heading_level = heading_level + extra_level %}
{% for class in classes|order_members(config.members_order, members_list) %}
{% if not class.is_alias or class.is_explicitely_exported %}
{% include "class.html" with context %}
{% endif %}
{% endfor %}
{% endwith %}
{% endif %}
{% endwith %}

{% if config.show_category_heading and obj.functions|filter_docstrings(config.show_if_no_docstring) %}
{% filter heading(heading_level, id=html_id ~ "-functions") %}Functions{% endfilter %}
{% endif %}
{% with heading_level = heading_level + extra_level %}
{% for function in obj.functions.values()|order_members(config.members_order) %}
{% if not (obj.kind.value == "class" and function.name == "__init__" and config.merge_init_into_class) %}
{% if not function.is_alias or function.is_explicitely_exported %}
{% include "function.html" with context %}
{% endif %}
{% with functions = obj.functions|filter_objects(config.filters, members_list, config.show_if_no_docstring) %}
{% if functions %}
{% if config.show_category_heading %}
{% filter heading(heading_level, id=html_id ~ "-functions") %}Functions{% endfilter %}
{% endif %}
{% endfor %}
{% with heading_level = heading_level + extra_level %}
{% for function in functions|order_members(config.members_order, members_list) %}
{% if not (obj.kind.value == "class" and function.name == "__init__" and config.merge_init_into_class) %}
{% if not function.is_alias or function.is_explicitely_exported %}
{% include "function.html" with context %}
{% endif %}
{% endif %}
{% endfor %}
{% endwith %}
{% endif %}
{% endwith %}

{% if config.show_submodules %}
{% if config.show_category_heading and obj.modules|filter_docstrings(config.show_if_no_docstring) %}
{% filter heading(heading_level, id=html_id ~ "-modules") %}Modules{% endfilter %}
{% endif %}
{% with heading_level = heading_level + extra_level %}
{% for module in obj.modules.values()|order_members(config.members_order) %}
{% if not module.is_alias or module.is_explicitely_exported %}
{% include "module.html" with context %}
{% with modules = obj.modules|filter_objects(config.filters, members_list, config.show_if_no_docstring) %}
{% if modules %}
{% if config.show_category_heading %}
{% filter heading(heading_level, id=html_id ~ "-modules") %}Modules{% endfilter %}
{% endif %}
{% endfor %}
{% with heading_level = heading_level + extra_level %}
{% for module in modules|order_members(config.members_order, members_list) %}
{% if not module.is_alias or module.is_explicitely_exported %}
{% include "module.html" with context %}
{% endif %}
{% endfor %}
{% endwith %}
{% endif %}
{% endwith %}
{% endif %}

{% endwith %}

{% else %}

{% for child in obj.members.values()|order_members(config.members_order) %}
{% for child in obj.members|
filter_objects(config.filters, members_list, config.show_if_no_docstring)|
order_members(config.members_order, members_list) %}

{% if not (obj.kind.value == "class" and child.name == "__init__" and config.merge_init_into_class) %}

Expand Down
Loading

0 comments on commit 24a6136

Please sign in to comment.