diff --git a/lib/docs/filters/pytest/clean_html.rb b/lib/docs/filters/pytest/clean_html.rb new file mode 100644 index 0000000000..70411e9cb3 --- /dev/null +++ b/lib/docs/filters/pytest/clean_html.rb @@ -0,0 +1,33 @@ +module Docs + class Pytest + class CleanHtmlFilter < Filter + def call + # Removed here rather than by sphinx/clean_html because the entries + # filter reads the headings' text content + css('.headerlink').remove + + # "[source]" links pointing to the highlighted sources, which aren't scraped + css('.viewcode-link').each do |node| + node.parent.remove + end + + # Tab sets (e.g. the configuration file formats) are made of hidden radio + # inputs and labels, which don't work without the theme's stylesheet. The + # tabs are turned into consecutive sections instead. + css('.tab-input').remove + + css('.tab-label').each do |node| + node.name = 'h4' + node.remove_attribute('class') + node.remove_attribute('for') + end + + css('.tab-content', '.tab-set').each do |node| + node.before(node.children).remove + end + + doc + end + end + end +end diff --git a/lib/docs/filters/pytest/entries.rb b/lib/docs/filters/pytest/entries.rb new file mode 100644 index 0000000000..d9637c7f63 --- /dev/null +++ b/lib/docs/filters/pytest/entries.rb @@ -0,0 +1,71 @@ +module Docs + class Pytest + class EntriesFilter < Docs::EntriesFilter + # The documentation follows the Diátaxis framework, which its URLs mirror + TYPE_BY_DIRECTORY = { + 'how-to' => 'How-to guides', + 'reference' => 'Reference', + 'explanation' => 'Explanation', + 'example' => 'Examples' + } + + # The only page documenting objects; its second-level sections (Functions, + # Marks, Fixtures, Hooks, ...) are used as the types of those objects + API_REFERENCE_SLUG = 'reference/reference' + + # Objects are documented under the module implementing them, which is part + # of neither pytest's public API nor of the names shown in the signatures + PRIVATE_MODULE = /\Apytest\.(?:capture|doctest|fixtures|hookspec|junitxml|logging|monkeypatch|nodes|python|recwarn|tmpdir)\./ + + CALLABLE = /\b(?:function|method|classmethod|staticmethod)\b/ + + def get_name + at_css('h1').content.strip + end + + def get_type + TYPE_BY_DIRECTORY[slug.split('/').first] || 'Get Started' + end + + def additional_entries + return [] unless slug == API_REFERENCE_SLUG + + entries = [] + + css('> section').each do |section| + next unless heading = section.at_css('> h2') + type = heading.content.strip + + section.css('dl.py > dt[id], dl.std > dt[id]').each do |node| + entries << [object_name(node), node['id'], type] + end + + # Constants and a few other objects (e.g. custom marks) are documented + # as plain sections rather than as Sphinx objects + section.css('> section').each do |subsection| + next if subsection.at_css('dl.py > dt[id], dl.std > dt[id]') + next unless subheading = subsection.at_css('> h3') + entries << [subheading.content.strip, subsection['id'], type] + end + end + + entries + end + + private + + def object_name(node) + classes = node.parent['class'] + + # Configuration options, command-line flags, environment variables and + # global variables have prefixed ids (e.g. "confval-addopts"), the + # signature is the only place where they appear as one writes them + return node.at_css('.descname').content.strip if classes.include?('std') + + name = node['id'].sub(PRIVATE_MODULE, '') + name << '()' if classes =~ CALLABLE + name + end + end + end +end diff --git a/lib/docs/scrapers/pytest.rb b/lib/docs/scrapers/pytest.rb new file mode 100644 index 0000000000..3ca8cb2c46 --- /dev/null +++ b/lib/docs/scrapers/pytest.rb @@ -0,0 +1,43 @@ +module Docs + class Pytest < UrlScraper + self.name = 'pytest' + self.type = 'sphinx' + self.release = '9.1.1' + self.base_url = 'https://docs.pytest.org/en/stable/' + self.root_path = 'index.html' + self.links = { + home: 'https://pytest.org/', + code: 'https://github.com/pytest-dev/pytest' + } + + # The navigation lives in the Furo sidebar, i.e. outside of the scraped + # container, and the toctrees on the home page are hidden ones. The section + # indexes, whose toctrees are part of the content, are used as entry points + # instead. + self.initial_paths = %w( + getting-started.html + how-to/index.html + reference/index.html + explanation/index.html + example/index.html) + + html_filters.push 'pytest/clean_html', 'pytest/entries', 'sphinx/clean_html' + + options[:container] = 'article[role="main"] > section' + + # Restrict the scraper to the documentation itself, leaving out the project's + # meta pages (changelog, contributing, sponsors, announcements, ...) and the + # highlighted sources under _modules/. + options[:only] = %w(index.html getting-started.html) + options[:only_patterns] = [/\Ahow-to\//, /\Areference\//, /\Aexplanation\//, /\Aexample\//] + + options[:attribution] = <<-HTML + © 2015–2026 Holger Krekel and pytest-dev team
+ Licensed under the MIT License. + HTML + + def get_latest_version(opts) + get_latest_github_release('pytest-dev', 'pytest', opts) + end + end +end diff --git a/public/icons/docs/pytest/16.png b/public/icons/docs/pytest/16.png new file mode 100644 index 0000000000..71fb26b884 Binary files /dev/null and b/public/icons/docs/pytest/16.png differ diff --git a/public/icons/docs/pytest/16@2x.png b/public/icons/docs/pytest/16@2x.png new file mode 100644 index 0000000000..461da14ca7 Binary files /dev/null and b/public/icons/docs/pytest/16@2x.png differ diff --git a/public/icons/docs/pytest/SOURCE b/public/icons/docs/pytest/SOURCE new file mode 100644 index 0000000000..4db6b8149f --- /dev/null +++ b/public/icons/docs/pytest/SOURCE @@ -0,0 +1 @@ +https://github.com/pytest-dev/pytest/blob/main/doc/en/_static/pytest1.png