Skip to content
25 changes: 21 additions & 4 deletions pydoctor/node2stan.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from docutils.writers import html4css1
from docutils import nodes, frontend, __version_info__ as docutils_version_info

from twisted.web.template import Tag
from twisted.web.template import Tag, tags
if TYPE_CHECKING:
from twisted.web.template import Flattenable
from pydoctor.epydoc.markup import DocstringLinker
Expand Down Expand Up @@ -226,10 +226,27 @@ def starttag(self, node: nodes.Element, tagname: str, suffix: str = '\n', *args:

def visit_doctest_block(self, node: nodes.doctest_block) -> None:
pysrc = node[0].astext()
if node.get('codeblock'):
self.body.append(flatten(colorize_codeblock(pysrc)))
if is_code_block:=node.get('codeblock'):
pre_tag = colorize_codeblock(pysrc)
else:
self.body.append(flatten(colorize_doctest(pysrc)))
pre_tag = colorize_doctest(pysrc)

# If it's not a code block, then it must be a doctest block
if not is_code_block:
# Wrap doctest blocks with a container and toggle button
container = tags.div(
tags.button(
">>>",
class_='doctest-toggle',
type='button',
title='Doctest toggle'
),
pre_tag,
class_='doctest-output'
)
self.body.append(flatten(container))
else:
self.body.append(flatten(pre_tag))
raise nodes.SkipNode()


Expand Down
4 changes: 2 additions & 2 deletions pydoctor/test/epydoc/test_epytext2html.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,9 @@ def test_epytext_complex_list() -> None:
<ul class="rst-simple"><li>This is a sublist.</li><li>The sublist contains two items.
<ul><li>The second item of the sublist has its own sublist.</li></ul></li></ul></li>
<li><p class="rst-first">This list item contains two paragraphs and a doctest block.</p>
<pre class="py-doctest"><span class="py-prompt">&gt;&gt;&gt; </span>
<div class="doctest-output"><button class="doctest-toggle" title="Doctest toggle" type="button">&gt;&gt;&gt;</button><pre class="py-doctest"><span class="py-prompt">&gt;&gt;&gt; </span>
<span class="py-builtin">len</span>(<span class="py-string">'This is a doctest block'</span>)
<span class="py-output">23</span></pre><p>This is the second paragraph.</p></li></ol>
<span class="py-output">23</span></pre></div><p>This is the second paragraph.</p></li></ol>
'''
assert epytext2html(doc) == squash(expected)

Expand Down
45 changes: 43 additions & 2 deletions pydoctor/test/test_node2stan.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

from pydoctor.epydoc.docutils import get_lineno
from pydoctor.test import CapSys
from pydoctor.test.epydoc.test_epytext2html import epytext2node
from pydoctor.test.epydoc.test_restructuredtext import rst2node, parse_rst
from pydoctor.test.epydoc.test_epytext2html import epytext2node, epytext2html
from pydoctor.test.epydoc.test_restructuredtext import rst2node, parse_rst, rst2html

from pydoctor.node2stan import gettext
from docutils import nodes
Expand Down Expand Up @@ -154,3 +154,44 @@ def test_docutils_get_lineno_title_reference(capsys:CapSys) -> None:
parsed_doc.fields[0].body().to_node().walk(TitleReferenceDump(doc))
assert capsys.readouterr().out == r'''||title_reference line: None, get_lineno: 28, rawsource: `link <notfound>`
'''



def test_epytext_doctest_contains_toggle() -> None:
doc = '''
A short paragraph.

>>> 2 + 3
5
'''
html = epytext2html(doc)
assert '<div class="doctest-output"' in html
assert 'class="doctest-toggle"' in html
assert 'py-prompt' in html and 'py-output' in html


def test_rst_doctest_contains_toggle() -> None:
doc = '''
Some text.

>>> sum([1, 2, 3])
6
'''
html = rst2html(doc)
assert '<div class="doctest-output"' in html
assert 'class="doctest-toggle"' in html
assert 'py-prompt' in html and 'py-output' in html


def test_rst_codeblock_has_no_toggle() -> None:
doc = '''
.. code:: python

>>> 1 + 1
2
'''
html = rst2html(doc)
# Code blocks should be colorized but must not include the toggle button
assert '<div class="doctest-output"' not in html
assert 'class="doctest-toggle"' not in html
assert 'py-doctest' in html
35 changes: 35 additions & 0 deletions pydoctor/themes/base/apidocs.css
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,41 @@ pre.py-doctest {
color: #c7254e;
}

/* Doctest output toggle */

.doctest-output {
position: relative;
}

.doctest-toggle {
position: absolute;
top: 0.5em;
right: 0.5em;
background-color: #f0f0f0;
border: 1px solid #ccc;
border-radius: 3px;
padding: 0.25em 0.5em;
color: #666;
font-family: monospace;
line-height: 1;
}

.doctest-toggle:hover {
background-color: #e8e8e8;
border-color: #999;
}

.doctest-output.hide-output .py-prompt,
.doctest-output.hide-output .py-more,
.doctest-output.hide-output .py-output,
.doctest-output.hide-output .py-except {
display: none;
}

.doctest-output.hide-output .doctest-toggle {
text-decoration: line-through;
}

/* Admonitions */

div.rst-admonition p.rst-admonition-title:after {
Expand Down
17 changes: 17 additions & 0 deletions pydoctor/themes/base/pydoctor.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,20 @@ function updatePrivate() {
}

initPrivate();

// Toggle doctest output visibility

function initDoctest() {
// Add click handlers to all doctest toggle buttons
var buttons = document.querySelectorAll('button.doctest-toggle');
buttons.forEach(function(button) {
button.addEventListener('click', function() {
var container = button.closest('div.doctest-output');
if (container) {
container.classList.toggle('hide-output');
}
});
});
}

initDoctest()
Loading