diff --git a/swh/web/api/apidoc.py b/swh/web/api/apidoc.py --- a/swh/web/api/apidoc.py +++ b/swh/web/api/apidoc.py @@ -63,6 +63,7 @@ self.reqheaders_set = set() self.resheaders_set = set() self.field_list_visited = False + self.current_json_obj = None def process_paragraph(self, par): """ @@ -82,8 +83,12 @@ # remove parsed document markups par = re.sub('<[^<]+?>', '', par) # api urls cleanup to generate valid links afterwards - par = re.sub(r'\(\w+\)', '', par) - par = re.sub(r'\[.*\]', '', par) + subs_made = 1 + while subs_made: + (par, subs_made) = re.subn(r'(:http:.*)(\(\w+\))', r'\1', par) + subs_made = 1 + while subs_made: + (par, subs_made) = re.subn(r'(:http:.*)(\[.*\])', r'\1', par) par = par.replace('//', '/') # transform references to api endpoints into valid rst links par = re.sub(':http:get:`([^,]*)`', r'`<\1>`_', par) @@ -135,6 +140,7 @@ 'type': field_data[1], 'doc': text}) self.inputs_set.add(field_data[2]) + self.current_json_obj = self.data['inputs'][-1] # Response type if (field_data[0] in self.response_json_array_roles or field_data[0] in self.response_json_object_roles): @@ -150,6 +156,7 @@ 'type': field_data[1], 'doc': text}) self.returns_set.add(field_data[2]) + self.current_json_obj = self.data['returns'][-1] # Status Codes if field_data[0] in self.status_code_roles: if field_data[1] not in self.status_codes_set: @@ -218,6 +225,14 @@ if isinstance(child, docutils.nodes.paragraph): line_text = self.process_paragraph(str(child)) self.data['description'] += '\t* %s\n' % line_text + elif self.current_json_obj: + self.current_json_obj['doc'] += '\n\n' + for child in node.traverse(): + # process list item + if isinstance(child, docutils.nodes.paragraph): + line_text = self.process_paragraph(str(child)) + self.current_json_obj['doc'] += '\t\t* %s\n' % line_text + self.current_json_obj = None def visit_warning(self, node): text = self.process_paragraph(str(node)) diff --git a/swh/web/assets/src/bundles/webapp/webapp.css b/swh/web/assets/src/bundles/webapp/webapp.css --- a/swh/web/assets/src/bundles/webapp/webapp.css +++ b/swh/web/assets/src/bundles/webapp/webapp.css @@ -507,6 +507,12 @@ margin: 10px; } +.swh-apidoc .swh-rst blockquote { + border: 0; + margin: 0; + padding: 0; +} + a.toggle-col { text-decoration: none; } diff --git a/swh/web/common/middlewares.py b/swh/web/common/middlewares.py --- a/swh/web/common/middlewares.py +++ b/swh/web/common/middlewares.py @@ -3,10 +3,11 @@ # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information -from bs4 import BeautifulSoup from htmlmin import minify import sentry_sdk +from swh.web.common.utils import prettify_html + class HtmlPrettifyMiddleware(object): """ @@ -22,11 +23,10 @@ if 'text/html' in response.get('Content-Type', ''): if hasattr(response, 'content'): content = response.content - response.content = BeautifulSoup(content, 'lxml').prettify() + response.content = prettify_html(content) elif hasattr(response, 'streaming_content'): content = b''.join(response.streaming_content) - response.streaming_content = \ - BeautifulSoup(content, 'lxml').prettify() + response.streaming_content = prettify_html(content) return response diff --git a/swh/web/common/swh_templatetags.py b/swh/web/common/swh_templatetags.py --- a/swh/web/common/swh_templatetags.py +++ b/swh/web/common/swh_templatetags.py @@ -6,8 +6,6 @@ import json import re -from inspect import cleandoc - from django import template from django.core.serializers.json import DjangoJSONEncoder from django.utils.safestring import mark_safe @@ -21,12 +19,12 @@ @register.filter -def safe_docstring_display(docstring): +def docstring_display(docstring): """ Utility function to htmlize reST-formatted documentation in browsable api. """ - return rst_to_html(cleandoc(docstring)) + return rst_to_html(docstring) @register.filter diff --git a/swh/web/common/utils.py b/swh/web/common/utils.py --- a/swh/web/common/utils.py +++ b/swh/web/common/utils.py @@ -14,6 +14,8 @@ import docutils.parsers.rst import docutils.utils +from bs4 import BeautifulSoup + from docutils.core import publish_parts from docutils.writers.html5_polyglot import Writer, HTMLTranslator @@ -477,3 +479,16 @@ pp = publish_parts(rst, writer=_HTML_WRITER, settings_overrides=settings) return f'
{pp["html_body"]}
' + + +def prettify_html(html: str) -> str: + """ + Prettify an HTML document. + + Args: + html: Input HTML document + + Returns: + The prettified HTML document + """ + return BeautifulSoup(html, 'lxml').prettify() diff --git a/swh/web/templates/api/apidoc.html b/swh/web/templates/api/apidoc.html --- a/swh/web/templates/api/apidoc.html +++ b/swh/web/templates/api/apidoc.html @@ -35,7 +35,7 @@ {% if description %}

Description

- {{ description | safe_docstring_display | safe }} + {{ description | docstring_display | safe }}
{% endif %} {% if response_data is not None %} @@ -71,7 +71,7 @@ {% for url in urls %} - {{ url.rule | safe_docstring_display | safe }} + {{ url.rule | docstring_display | safe }} {{ url.methods | dictsort:0 | join:', ' }} {% endfor %} @@ -86,7 +86,7 @@ {% for arg in args %}
{{ arg.name }} ({{ arg.type }})
-
{{ arg.doc | safe_docstring_display | safe }}
+
{{ arg.doc | docstring_display | safe }}
{% endfor %} @@ -98,7 +98,7 @@ {% for param in params %}
{{ param.name }} ({{ param.type }})
-
{{ param.doc | safe_docstring_display | safe }}
+
{{ param.doc | docstring_display | safe }}
{% endfor %} @@ -110,7 +110,7 @@ {% for header in reqheaders %}
{{ header.name }}
-
{{ header.doc | safe_docstring_display | safe }}
+
{{ header.doc | docstring_display | safe }}
{% endfor %} @@ -133,7 +133,7 @@ an object containing the following keys: {% endif %} {% if inputs_list != '' %} - {{ inputs_list | safe_docstring_display | safe }} + {{ inputs_list | docstring_display | safe }} {% endif %}

@@ -147,7 +147,7 @@ {% for header in resheaders %}
{{ header.name }}
-
{{ header.doc | safe_docstring_display | safe }}
+
{{ header.doc | docstring_display | safe }}
{% endfor %} @@ -170,7 +170,7 @@ an object containing the following keys: {% endif %} {% if returns_list != '' %} - {{ returns_list | safe_docstring_display | safe }} + {{ returns_list | docstring_display | safe }} {% endif %}

@@ -184,7 +184,7 @@ {% for status in status_codes %}
{{ status.code }}
-
{{ status.doc | safe_docstring_display | safe }}
+
{{ status.doc | docstring_display | safe }}
{% endfor %} diff --git a/swh/web/templates/api/endpoints.html b/swh/web/templates/api/endpoints.html --- a/swh/web/templates/api/endpoints.html +++ b/swh/web/templates/api/endpoints.html @@ -59,7 +59,7 @@ {% endif %} - {{ doc.doc_intro | safe_docstring_display | safe }} + {{ doc.doc_intro | docstring_display | safe }} diff --git a/swh/web/tests/api/test_apidoc.py b/swh/web/tests/api/test_apidoc.py --- a/swh/web/tests/api/test_apidoc.py +++ b/swh/web/tests/api/test_apidoc.py @@ -12,8 +12,8 @@ from swh.web.api.apidoc import api_doc, _parse_httpdomain_doc from swh.web.api.apiurls import api_route from swh.web.common.exc import BadInputExc, ForbiddenExc, NotFoundExc -from swh.web.common.utils import reverse -from swh.web.tests.django_asserts import assert_template_used, assert_contains +from swh.web.common.utils import reverse, prettify_html +from swh.web.tests.django_asserts import assert_template_used _httpdomain_doc = """ @@ -229,7 +229,7 @@ expected_reqheaders = [{ 'doc': ('the requested response content type, either ' - '``application/json`` or ``application/yaml``'), + '``application/json`` (default) or ``application/yaml``'), 'name': 'Accept' }] @@ -378,9 +378,10 @@ :jsonarr string type: swh object type - :>jsonarr string sha1_git: swh object sha1_git - :>jsonarr boolean found: whether the object was found or not + :>json object : an object whose keys are input persistent + identifiers and values objects with the following keys: + + * **known (bool)**: whether the object was found """ pass @@ -393,39 +394,59 @@ assert_template_used(rv, 'api/apidoc.html') input_html_doc = ( - '
\n' - '
array
\n' - '
\n' - '

\n' - ' \n' - ' Input array of pids\n' - ' \n' - ' \n' - '

\n' - '
\n' - '
\n' + '
\n' + '
\n' + ' array\n' + '
\n' + '
\n' + '

\n' + ' Input array of pids\n' + '

\n' + '
\n' + '
\n' ) output_html_doc = ( - '
\n' - '
array
\n' - '
\n' - '

\n' - ' \n' - ' an array of objects containing the following keys:\n' - ' \n' - ' \n' - '

    \n' - '
  • type (string): swh object type

  • \n' - '
  • sha1_git (string): swh object sha1_git

  • \n' # noqa - '
  • found (boolean): whether the object was found or not

  • \n' # noqa - '
\n' - '
\n' - ' \n' - '

\n' - '
\n' - '
' + '
\n' + '
\n' + ' object\n' + '
\n' + '
\n' + '

\n' + ' an object containing the following keys:\n' + '

\n' + '
\n' + '
\n' + '
    \n' + '
  • \n' + '

    \n' + ' \n' + ' <swh_pid> (object)\n' + ' \n' + ' : an object whose keys are input persistent identifiers' + ' and values objects with the following keys:\n' + '

    \n' + '
    \n' + '
      \n' + '
    • \n' + '

      \n' + ' \n' + ' known (bool)\n' + ' \n' + ' : whether the object was found\n' + '

      \n' + '
    • \n' + '
    \n' + '
    \n' + '
  • \n' + '
\n' + '
\n' + '
\n' + '
\n' + '
\n' ) - assert_contains(rv, input_html_doc) - assert_contains(rv, output_html_doc) + html = prettify_html(rv.content) + + assert input_html_doc in html + assert output_html_doc in html diff --git a/swh/web/tests/common/test_templatetags.py b/swh/web/tests/common/test_templatetags.py --- a/swh/web/tests/common/test_templatetags.py +++ b/swh/web/tests/common/test_templatetags.py @@ -4,7 +4,7 @@ # See top-level LICENSE file for more information from swh.web.common.swh_templatetags import ( - urlize_links_and_mails, urlize_header_links, safe_docstring_display + urlize_links_and_mails, urlize_header_links, docstring_display ) @@ -36,7 +36,7 @@ assert urlize_header_links(content) == expected_content -def test_safe_docstring_display(): +def test_docstring_display(): # update api link with html links content with links docstring = ( 'This is my list header:\n\n' @@ -49,13 +49,15 @@ expected_docstring = ( '
' '

This is my list header:

\n' + '
\n' '
    \n' '
  • Here is item 1, with a continuation\n' 'line right here

  • \n' '
  • Here is item 2

  • \n' '
\n' '

Here is something that is not part of the list

\n' + '
\n' '
' ) - assert safe_docstring_display(docstring) == expected_docstring + assert docstring_display(docstring) == expected_docstring