From ad1fc1229e939499685bc0cbad003f621e786e06 Mon Sep 17 00:00:00 2001 From: tristanlatr <19967168+tristanlatr@users.noreply.github.com> Date: Thu, 21 Nov 2024 13:19:23 -0500 Subject: [PATCH 01/11] Use pytest-xdist to speed-up tests locally. (#838) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since the multiprocessing doesn’t play well with code coverage, use another specific environment for that --- .github/pull_request_template.md | 5 ----- .github/workflows/unit.yaml | 4 ++-- setup.cfg | 1 + tox.ini | 8 ++++++++ 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e087c871a..75d8c2300 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,11 +1,6 @@ diff --git a/.github/workflows/unit.yaml b/.github/workflows/unit.yaml index 7e42c0f9c..8060e9d35 100644 --- a/.github/workflows/unit.yaml +++ b/.github/workflows/unit.yaml @@ -42,9 +42,9 @@ jobs: python -c "print('\nENVIRONMENT VARIABLES\n=====================\n')" python -c "import os; [print(f'{k}={v}') for k, v in os.environ.items()]" - - name: Run unit tests + - name: Run unit tests and coverage reports run: | - tox -e test + tox -e test-cov - name: Run unit tests with latest Twisted version run: | diff --git a/setup.cfg b/setup.cfg index ae434c0f8..16b5a3abe 100644 --- a/setup.cfg +++ b/setup.cfg @@ -65,6 +65,7 @@ test = docutils>=0.18.1 coverage pytest + pytest-xdist hypothesis cython-test-exception-raiser bs4 diff --git a/tox.ini b/tox.ini index 76a58483f..ec3c4accf 100644 --- a/tox.ini +++ b/tox.ini @@ -19,11 +19,19 @@ allowlist_externals = passenv = * [testenv:test] +description = Run tests with multiprocessing without coverage report, made for local development. +extras = + test +commands = + pytest -vv -n auto {posargs: pydoctor} + +[testenv:test-cov] description = Run tests and coverage report extras = test commands = coverage erase + # The multiprocessing doesn't play well with the coverage :/ coverage run -m pytest -vv {posargs: pydoctor} coverage report -m # The XML version is generatred to be uploaded to Codecov. From 26cbc91e824d2f2a8a5361c6764250d7f6953b0d Mon Sep 17 00:00:00 2001 From: tristanlatr <19967168+tristanlatr@users.noreply.github.com> Date: Mon, 25 Nov 2024 20:43:22 -0500 Subject: [PATCH 02/11] Search bar enhancements and Help page (#823) - Implement one idea described in #822, more specifically: add a leading wildcard to the query when the term contains a dot ".". - Fixes #819 - Move the NotFoundLinker to the linker.py module because t's now used by the help page renderer. Simplify the readthedocs theme and fix an issue that prevented to use the search bar from the module index page and other summary pages. --- .github/workflows/unit.yaml | 2 +- README.rst | 4 + pydoctor/linker.py | 13 ++ pydoctor/templatewriter/summary.py | 133 +++++++++++++++++- pydoctor/test/__init__.py | 26 +--- pydoctor/test/test_commandline.py | 13 +- .../test/testcustomtemplates/allok/nav.html | 2 +- pydoctor/themes/base/apidocs-help.html | 39 +++++ pydoctor/themes/base/apidocs.css | 11 +- pydoctor/themes/base/nav.html | 41 +----- pydoctor/themes/base/search.js | 35 ++--- pydoctor/themes/base/searchlib.js | 74 ++++++---- pydoctor/themes/readthedocs/common.html | 64 --------- pydoctor/themes/readthedocs/nav.html | 42 ------ .../themes/readthedocs/readthedocstheme.css | 4 +- 15 files changed, 267 insertions(+), 236 deletions(-) create mode 100644 pydoctor/themes/base/apidocs-help.html delete mode 100644 pydoctor/themes/readthedocs/nav.html diff --git a/.github/workflows/unit.yaml b/.github/workflows/unit.yaml index 8060e9d35..4b25a8531 100644 --- a/.github/workflows/unit.yaml +++ b/.github/workflows/unit.yaml @@ -20,7 +20,7 @@ jobs: strategy: matrix: python-version: ['pypy-3.8', 'pypy-3.9', 'pypy-3.10', - '3.8', '3.9', '3.10', '3.11', '3.12', '3.13.0-rc.2'] + '3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] os: [ubuntu-latest, windows-latest, macos-latest] steps: diff --git a/README.rst b/README.rst index 680679920..85a0b56e0 100644 --- a/README.rst +++ b/README.rst @@ -91,6 +91,10 @@ in development from the ``pydoctor_url_path`` config option now includes a project name which defaults to 'main' (instead of putting None), use mapping instead of a list to define your own project name. * Improve the themes so the adds injected by ReadTheDocs are rendered with the correct width and do not overlap too much with the main content. +* Fix an issue in the readthedocs theme that prevented to use the search bar from the summary pages (like the class hierarchy). +* The generated documentation now includes a help page under the path ``/apidocs-help.html``. + This page is accessible by clicking on the information icon in the navbar (``ℹ``). +* Improve the javascript searching code to better understand terms that contains a dot (``.``). pydoctor 24.3.3 ^^^^^^^^^^^^^^^ diff --git a/pydoctor/linker.py b/pydoctor/linker.py index a36949339..a569107b2 100644 --- a/pydoctor/linker.py +++ b/pydoctor/linker.py @@ -284,3 +284,16 @@ def link_xref(self, target: str, label: "Flattenable", lineno: int) -> Tag: def switch_context(self, ob:Optional['model.Documentable']) -> Iterator[None]: with self._scope_linker.switch_context(ob): yield + +class NotFoundLinker(DocstringLinker): + """A DocstringLinker implementation that cannot find any links.""" + + def link_to(self, target: str, label: "Flattenable") -> Tag: + return tags.transparent(label) + + def link_xref(self, target: str, label: "Flattenable", lineno: int) -> Tag: + return tags.code(label) + + @contextlib.contextmanager + def switch_context(self, ob: Optional[model.Documentable]) -> Iterator[None]: + yield diff --git a/pydoctor/templatewriter/summary.py b/pydoctor/templatewriter/summary.py index 36bd5adea..d24992905 100644 --- a/pydoctor/templatewriter/summary.py +++ b/pydoctor/templatewriter/summary.py @@ -2,6 +2,8 @@ from __future__ import annotations from collections import defaultdict +from string import Template +from textwrap import dedent from typing import ( TYPE_CHECKING, DefaultDict, Dict, Iterable, List, Mapping, MutableSet, Sequence, Tuple, Type, Union, cast @@ -358,12 +360,141 @@ def stuff(self, request: object, tag: Tag) -> Tag: )) return tag +# TODO: The help page should dynamically include notes about the (source) code links. +class HelpPage(Page): + + filename = 'apidocs-help.html' + + RST_SOURCE_TEMPLATE = Template(''' + Navigation + ---------- + + There is one page per class, module and package. + Each page present summary table(s) which feature the members of the object. + + Package or Module page + ~~~~~~~~~~~~~~~~~~~~~~~ + + Each of these pages has two main sections consisting of: + + - summary tables submodules and subpackages and the members of the module or in the ``__init__.py`` file. + - detailed descriptions of function and attribute members. + + Class page + ~~~~~~~~~~ + + Each class has its own separate page. + Each of these pages has three main sections consisting of: + + - declaration, constructors, know subclasses and description + - summary tables of members, including inherited + - detailed descriptions of method and attribute members + + Entries in each of these sections are omitted if they are empty or not applicable. + + Module Index + ~~~~~~~~~~~~ + + Provides a high level overview of the packages and modules structure. + + Class Hierarchy + ~~~~~~~~~~~~~~~ + + Provides a list of classes organized by inheritance structure. Note that ``object`` is ommited. + + Index Of Names + ~~~~~~~~~~~~~~ + + The Index contains an alphabetic index of all objects in the documentation. + + + Search + ------ + + You can search for definitions of modules, packages, classes, functions, methods and attributes. + + These items can be searched using part or all of the name and/or from their docstrings if "search in docstrings" is enabled. + Multiple search terms can be provided separated by whitespace. + + The search is powered by `lunrjs `_. + + Indexing + ~~~~~~~~ + + By default the search only matches on the name of the object. + Enable the full text search in the docstrings with the checkbox option. + + You can instruct the search to look only in specific fields by passing the field name in the search like ``docstring:term``. + + **Possible fields are**: + + - ``name``, the name of the object (example: "MyClassAdapter" or "my_fmin_opti"). + - ``qname``, the fully qualified name of the object (example: "lib.classses.MyClassAdapter"). + - ``names``, the name splitted on camel case or snake case (example: "My Class Adapter" or "my fmin opti") + - ``docstring``, the docstring of the object (example: "This is an adapter for HTTP json requests that logs into a file...") + - ``kind``, can be one of: $kind_names + + Last two fields are only applicable if "search in docstrings" is enabled. + + Other search features + ~~~~~~~~~~~~~~~~~~~~~ + + Term presence. + The default behaviour is to give a better ranking to object matching multiple terms of your query, + but still show entries that matches only one of the two terms. + To change this behavour, you can use the sign ``+``. + + - To indicate a term must exactly match use the plus sing: ``+``. + - To indicate a term must not match use the minus sing: ``-``. + + + Wildcards + A trailling wildcard is automatically added to each term of your query if they don't contain an explicit term presence (``+`` or ``-``). + Searching for ``foo`` is the same as searching for ``foo*``. + + If the query include a dot (``.``), a leading wildcard will to also added, + searching for ``model.`` is the same as ``*model.*`` and ``.model`` is the same as ``*.model*``. + + In addition to this automatic feature, you can manually add a wildcard anywhere else in the query. + + + Query examples + ~~~~~~~~~~~~~~ + + - "doc" matches "pydoctor.model.Documentable" and "pydoctor.model.DocLocation". + - "+doc" matches "pydoctor.model.DocLocation" but won't match "pydoctor.model.Documentable". + - "ensure doc" matches "pydoctor.epydoc2stan.ensure_parsed_docstring" and other object whose matches either "doc" or "ensure". + - "inp str" matches "java.io.InputStream" and other object whose matches either "in" or "str". + - "model." matches everything in the pydoctor.model module. + - ".web.*tag" matches "twisted.web.teplate.Tag" and related. + - "docstring:ansi" matches object whose docstring matches "ansi". + ''') + + def title(self) -> str: + return 'Help' + + @renderer + def heading(self, request: object, tag: Tag) -> Tag: + return tag.clear()("Help") + + @renderer + def helpcontent(self, request: object, tag: Tag) -> Tag: + from pydoctor.epydoc.markup import restructuredtext, ParseError + from pydoctor.linker import NotFoundLinker + errs: list[ParseError] = [] + parsed = restructuredtext.parse_docstring(dedent(self.RST_SOURCE_TEMPLATE.substitute( + kind_names=', '.join(f'"{k.name}"' for k in model.DocumentableKind) + )), errs) + assert not errs + return parsed.to_stan(NotFoundLinker()) + def summaryPages(system: model.System) -> Iterable[Type[Page]]: - pages = [ + pages: list[type[Page]] = [ ModuleIndexPage, ClassIndexPage, NameIndexPage, UndocumentedSummaryPage, + HelpPage, ] if len(system.root_names) > 1: pages.append(IndexPage) diff --git a/pydoctor/test/__init__.py b/pydoctor/test/__init__.py index 45e8cf406..d6a07e9f9 100644 --- a/pydoctor/test/__init__.py +++ b/pydoctor/test/__init__.py @@ -1,24 +1,18 @@ """PyDoctor's test suite.""" -import contextlib from logging import LogRecord -from typing import Iterable, TYPE_CHECKING, Iterator, Optional, Sequence +from typing import Iterable, TYPE_CHECKING, Sequence import sys import pytest from pathlib import Path -from twisted.web.template import Tag, tags - from pydoctor import epydoc2stan, model from pydoctor.templatewriter import IWriter, TemplateLookup -from pydoctor.epydoc.markup import DocstringLinker - -if TYPE_CHECKING: - from twisted.web.template import Flattenable +from pydoctor.linker import NotFoundLinker posonlyargs = pytest.mark.skipif(sys.version_info < (3, 8), reason="requires python 3.8") typecomment = pytest.mark.skipif(sys.version_info < (3, 8), reason="requires python 3.8") - +NotFoundLinker = NotFoundLinker # Because pytest 6.1 does not yet export types for fixtures, we define # approximations that are good enough for our test cases: @@ -87,18 +81,4 @@ def _writeDocsFor(self, ob: model.Documentable) -> None: for o in ob.contents.values(): self._writeDocsFor(o) - - -class NotFoundLinker(DocstringLinker): - """A DocstringLinker implementation that cannot find any links.""" - - def link_to(self, target: str, label: "Flattenable") -> Tag: - return tags.transparent(label) - - def link_xref(self, target: str, label: "Flattenable", lineno: int) -> Tag: - return tags.code(label) - - @contextlib.contextmanager - def switch_context(self, ob: Optional[model.Documentable]) -> Iterator[None]: - yield \ No newline at end of file diff --git a/pydoctor/test/test_commandline.py b/pydoctor/test/test_commandline.py index 01e06c97e..63c39c90a 100644 --- a/pydoctor/test/test_commandline.py +++ b/pydoctor/test/test_commandline.py @@ -304,6 +304,16 @@ def test_index_hardlink(tmp_path: Path) -> None: assert not (tmp_path / 'basic.html').is_symlink() assert (tmp_path / 'basic.html').is_file() + +def test_apidocs_help(tmp_path: Path) -> None: + """ + Checks that the help page is well generated. + """ + exit_code = driver.main(args=['--html-output', str(tmp_path), 'pydoctor/test/testpackages/basic/']) + assert exit_code == 0 + help_page = (tmp_path / 'apidocs-help.html').read_text() + assert '>Search' in help_page + def test_htmlbaseurl_option_all_pages(tmp_path: Path) -> None: """ Check that the canonical link is included in all html pages, including summary pages. @@ -319,4 +329,5 @@ def test_htmlbaseurl_option_all_pages(tmp_path: Path) -> None: if t.stem == 'basic': filename = 'index.html' # since we have only one module it's linked as index.html assert f' - +
diff --git a/pydoctor/themes/base/apidocs-help.html b/pydoctor/themes/base/apidocs-help.html new file mode 100644 index 000000000..10ed5f71c --- /dev/null +++ b/pydoctor/themes/base/apidocs-help.html @@ -0,0 +1,39 @@ + + + + + + Head + + + +
+ + + +
+ + + +
+ +
+ +

+ Report a bug or suggest an enhancement +

+ +
+
+ + + + + + diff --git a/pydoctor/themes/base/apidocs.css b/pydoctor/themes/base/apidocs.css index 98d734d3c..b073bd9aa 100644 --- a/pydoctor/themes/base/apidocs.css +++ b/pydoctor/themes/base/apidocs.css @@ -60,7 +60,6 @@ nav.navbar .navbar-header { margin-bottom: 3px; border-bottom: 0; box-shadow: 0 0 8px 8px #fff; - z-index: 99; } .navbar-brand { @@ -1064,19 +1063,15 @@ input[type="search"]::-webkit-search-results-decoration { display: none; } padding: 5px 0 0 8px; } -.search-help-hidden #search-help-box{ - display: none!important; -} - -#search-help-button{ +#apidocs-help-button{ background-color: #e6e6e6; } -.search-help-hidden #search-help-button{ +.search-help-hidden #apidocs-help-button{ background-color: rgb(255, 255, 255); } -.search-help-hidden #search-help-button:hover { +.search-help-hidden #apidocs-help-button:hover { background-color: #e6e6e6; } diff --git a/pydoctor/themes/base/nav.html b/pydoctor/themes/base/nav.html index 2e4a5c3de..c68c9226d 100644 --- a/pydoctor/themes/base/nav.html +++ b/pydoctor/themes/base/nav.html @@ -1,6 +1,6 @@ - +
@@ -29,7 +29,7 @@ - Help + Help
@@ -53,39 +53,6 @@

Cannot search: JavaScript is not supported/enabled in your browser.

-
-

- - Search bar offers the following options: -

    -
  • - Term presence. The below example searches for documents that - must contain “foo”, might contain “bar” and must not contain “baz”: +foo bar -baz -
  • - -
  • - Wildcards. The below example searches for documents with words beginning with “foo”: foo* -
  • - -
  • - Search in specific fields. The following search matches all objects - in "twisted.mail" that matches “search”: +qname:twisted.mail.* +search - -

    - Possible fields: 'name', 'qname' (fully qualified name), 'docstring', and 'kind'. - Last two fields are only applicable if "search in docstrings" is enabled. -

    -
  • - -
  • - Fuzzy matches. The following search matches all documents - that have a word within 1 edit distance of “foo”: foo~1 -
  • -
- -

-
-