Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTML documentation: Show preparsed doctests using inline tabs #37083

Merged
merged 15 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .ci/merge-fixes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ for REPO in ${SAGE_CI_FIXES_FROM_REPOSITORIES:-sagemath/sage}; do
# Considered alternative: Use https://github.com/$REPO/pull/$a.diff,
# which squashes everything into one diff without commit metadata.
PULL_URL="https://github.com/$REPO/pull/$a"
PULL_SHORT="$REPO#$a"
PULL_FILE="$REPO_FILE-$a"
PATH=build/bin:$PATH build/bin/sage-download-file --quiet "$PULL_URL.patch" $PULL_FILE.patch
date -u +"%Y-%m-%dT%H:%M:%SZ" > $PULL_FILE.date # Record the date, for future reference
Expand All @@ -67,7 +68,7 @@ for REPO in ${SAGE_CI_FIXES_FROM_REPOSITORIES:-sagemath/sage}; do
git am --signoff --show-current-patch=diff
echo "--------------------------------------------------------------------8<-----------------------------"
echo "::endgroup::"
echo "Failure applying $PULL_URL as a patch, resetting"
echo "Failure applying $PULL_SHORT as a patch, resetting"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the full URL since it easy to open the corresponding PR. Either way, these changes are unrelated to the actual goal of the PR and thus should be moved to somewhere else.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The full URL appears just above. The improvement is that the repeated one is shortened so that there is not a second, duplicate link.

I'll not open a separate PR for this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not? This is clearly not connected to main theme of this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've just finished the review of this one line change, no?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, and there are also more changes here that seem unrelated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You commented, I responded to your comment. What's missing for the review of this one line change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rules of review, spelled out for you:

  • Provide the necessary information.
  • Do not use vagueness as a tool.
  • Do not ask for information that has already been provided.

git am --signoff --abort
fi
done
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ jobs:
uses: actions/checkout@v4
- name: Merge CI fixes from sagemath/sage
run: |
.ci/merge-fixes.sh
mkdir -p upstream
.ci/merge-fixes.sh 2>&1 | tee upstream/ci_fixes.log
env:
GH_TOKEN: ${{ github.token }}
SAGE_CI_FIXES_FROM_REPOSITORIES: ${{ vars.SAGE_CI_FIXES_FROM_REPOSITORIES }}
- name: Store CI fixes in upstream artifact
run: |
mkdir -p upstream
if git format-patch --stdout test_base > ci_fixes.patch; then
cp ci_fixes.patch upstream/
fi
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/doc-build-pdf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ jobs:
uses: actions/checkout@v4
- name: Merge CI fixes from sagemath/sage
run: |
.ci/merge-fixes.sh
mkdir -p upstream
.ci/merge-fixes.sh 2>&1 | tee upstream/ci_fixes.log
env:
GH_TOKEN: ${{ github.token }}
SAGE_CI_FIXES_FROM_REPOSITORIES: ${{ vars.SAGE_CI_FIXES_FROM_REPOSITORIES }}
- name: Store CI fixes in upstream artifact
run: |
mkdir -p upstream
if git format-patch --stdout test_base > ci_fixes.patch; then
cp ci_fixes.patch upstream/
fi
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/doc-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ jobs:
uses: actions/checkout@v4
- name: Merge CI fixes from sagemath/sage
run: |
.ci/merge-fixes.sh
mkdir -p upstream
.ci/merge-fixes.sh 2>&1 | tee upstream/ci_fixes.log
env:
GH_TOKEN: ${{ github.token }}
SAGE_CI_FIXES_FROM_REPOSITORIES: ${{ vars.SAGE_CI_FIXES_FROM_REPOSITORIES }}
- name: Store CI fixes in upstream artifact
run: |
mkdir -p upstream
if git format-patch --stdout test_base > ci_fixes.patch; then
cp ci_fixes.patch upstream/
fi
Expand Down
13 changes: 13 additions & 0 deletions src/doc/common/static/jupyter-sphinx-furo.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,16 @@ thebelab.on("status", function (evt, data) {
kernel.requestExecute({code: "%display latex"});
}
});

// Activate Thebe when "Sage (live)" tab is clicked
document.querySelectorAll('input[class="tab-input"]').forEach((elem) => {
elem.addEventListener("click", function(event) {
if (elem.nextElementSibling) {
if (elem.nextElementSibling.nextElementSibling) {
if (elem.nextElementSibling.nextElementSibling.querySelector('div[class="thebelab-code"]')) {
initThebelab();
}
}
}
});
});
18 changes: 13 additions & 5 deletions src/doc/en/installation/source.rst
Original file line number Diff line number Diff line change
Expand Up @@ -940,11 +940,19 @@ Environment variables controlling the documentation build

The value of this variable is passed as an
argument to ``sage --docbuild all html`` or ``sage --docbuild all pdf`` when
you run ``make``, ``make doc``, or ``make doc-pdf``. For example, you can
add ``--no-plot`` to this variable to avoid building the graphics coming from
the ``.. PLOT`` directive within the documentation, or you can add
``--include-tests-blocks`` to include all "TESTS" blocks in the reference
manual. Run ``sage --docbuild help`` to see the full list of options.
you run ``make``, ``make doc``, or ``make doc-pdf``. For example:

- add ``--no-plot`` to this variable to avoid building the graphics coming from
the ``.. PLOT`` directive within the documentation,

- add ``--no-preparsed-examples`` to only show the original Sage code of
"EXAMPLES" blocks, suppressing the tab with the preparsed, plain Python
version, or

- add ``--include-tests-blocks`` to include all "TESTS" blocks in the reference
manual.

Run ``sage --docbuild help`` to see the full list of options.

.. envvar:: SAGE_SPKG_INSTALL_DOCS

Expand Down
5 changes: 5 additions & 0 deletions src/sage_docbuild/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,9 @@ def setup_parser():
standard.add_argument("--no-plot", dest="no_plot",
action="store_true",
help="do not include graphics auto-generated using the '.. plot' markup")
standard.add_argument("--no-preparsed-examples", dest="no_preparsed_examples",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the use case for this option?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's faster and some people may not want to see the tabs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Speedwise it doesn't seem to make a change (by looking at the github workflow execution time). Thus, to reduce complexity I prefer to just remove the option here and always build the docs with the parsed python tab.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted that this is your preference. I won't remove it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you want to make this feature configurable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More rules for review

  • Check the facts about an unfamiliar domain before making claims about it

Specifically here: Any developer who has worked with the Sage docbuild system knows that we have long had a range of options that configure global aspects of it. There's SAGE_SKIP_PLOT_DIRECTIVE (which you see even in the diff of the present PR...), SAGE_DOC_UNDERSCORE, SAGE_USE_CDNS, and we used to have SAGE_DOC_MATHJAX, SAGE_DOC_JSMATH until recently.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In each of these cases there is a good reason for why the switch exists beyond "some people may not want to" have xyz.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a moment ago you denied that the switches even exist.
Now you say they exist but they have a better reason to exist.

This is called "moving the goalposts", a well-known standard technique.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goalpost stayed the same: we should not add config options to toggle default documentation features off just because "some people may not want to see [xyz]".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're repeating yourself.

Just because you have this opinion does not mean that this PR needs work.

action="store_true",
help="do not show preparsed versions of EXAMPLES blocks")
standard.add_argument("--include-tests-blocks", dest="skip_tests", default=True,
action="store_false",
help="include TESTS blocks in the reference manual")
Expand Down Expand Up @@ -478,6 +481,8 @@ def excepthook(*exc_info):
build_options.ALLSPHINXOPTS += "-n "
if args.no_plot:
os.environ['SAGE_SKIP_PLOT_DIRECTIVE'] = 'yes'
if args.no_preparsed_examples:
os.environ['SAGE_PREPARSED_DOC'] = 'no'
if args.live_doc:
os.environ['SAGE_LIVE_DOC'] = 'yes'
if args.skip_tests:
Expand Down
117 changes: 88 additions & 29 deletions src/sage_docbuild/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
# General configuration
# ---------------------

SAGE_LIVE_DOC = os.environ.get('SAGE_LIVE_DOC', 'no')
SAGE_PREPARSED_DOC = os.environ.get('SAGE_PREPARSED_DOC', 'yes')

# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
Expand All @@ -57,7 +60,7 @@

jupyter_execute_default_kernel = 'sagemath'

if os.environ.get('SAGE_LIVE_DOC', 'no') == 'yes':
if SAGE_LIVE_DOC == 'yes':
SAGE_JUPYTER_SERVER = os.environ.get('SAGE_JUPYTER_SERVER', 'binder')
if SAGE_JUPYTER_SERVER.startswith('binder'):
# format: "binder" or
Expand Down Expand Up @@ -230,7 +233,7 @@ def sphinx_plot(graphics, **kwds):
# console lexers. 'ipycon' is the IPython console, which is what we want
# for most code blocks: anything with "sage:" prompts. For other IPython,
# like blocks which might appear in a notebook cell, use 'ipython'.
highlighting.lexers['ipycon'] = IPythonConsoleLexer(in1_regex=r'sage: ', in2_regex=r'[.][.][.][.]: ')
highlighting.lexers['ipycon'] = IPythonConsoleLexer(in1_regex=r'(sage:|>>>)', in2_regex=r'([.][.][.][.]:|[.][.][.])')
highlighting.lexers['ipython'] = IPyLexer()
highlight_language = 'ipycon'

Expand Down Expand Up @@ -305,7 +308,7 @@ def set_intersphinx_mappings(app, config):
multidocs_is_master = True

# https://sphinx-copybutton.readthedocs.io/en/latest/use.html
copybutton_prompt_text = r"sage: |[.][.][.][.]: |\$ "
copybutton_prompt_text = r"sage: |[.][.][.][.]: |>>> |[.][.][.] |\$ "
copybutton_prompt_is_regexp = True
copybutton_exclude = '.linenos, .c1' # exclude single comments (in particular, # optional!)
copybutton_only_copy_prompt_lines = True
Expand Down Expand Up @@ -789,8 +792,6 @@ class will be properly documented inside its surrounding class.
return skip


from jupyter_sphinx.ast import JupyterCellNode, CellInputNode

class SagecodeTransform(SphinxTransform):
"""
Transform a code block to a live code block enabled by jupyter-sphinx.
Expand Down Expand Up @@ -828,29 +829,87 @@ def apply(self):
if self.app.builder.tags.has('html') or self.app.builder.tags.has('inventory'):
for node in self.document.traverse(nodes.literal_block):
if node.get('language') is None and node.astext().startswith('sage:'):
source = node.rawsource
lines = []
for line in source.splitlines():
newline = line.lstrip()
if newline.startswith('sage: ') or newline.startswith('....: '):
lines.append(newline[6:])
cell_node = JupyterCellNode(
execute=False,
hide_code=True,
hide_output=True,
emphasize_lines=[],
raises=False,
stderr=True,
code_below=False,
classes=["jupyter_cell"])
cell_input = CellInputNode(classes=['cell_input','live-doc'])
cell_input += nodes.literal_block(
text='\n'.join(lines),
linenos=False,
linenostart=1)
cell_node += cell_input

node.parent.insert(node.parent.index(node) + 1, cell_node)
from docutils.nodes import container as Container, label as Label, literal_block as LiteralBlock, Text
from sphinx_inline_tabs._impl import TabContainer
parent = node.parent
index = parent.index(node)
if isinstance(node.previous_sibling(), TabContainer):
# Make sure not to merge inline tabs for adjacent literal blocks
parent.insert(index, Text(''))
index += 1
parent.remove(node)
# Tab for Sage code
container = TabContainer("", type="tab", new_set=False)
textnodes = [Text('Sage')]
label = Label("", "", *textnodes)
container += label
content = Container("", is_div=True, classes=["tab-content"])
content += node
container += content
parent.insert(index, container)
if SAGE_PREPARSED_DOC == 'yes':
# Tab for preparsed version
from sage.repl.preparse import preparse
container = TabContainer("", type="tab", new_set=False)
textnodes = [Text('Python')]
label = Label("", "", *textnodes)
container += label
content = Container("", is_div=True, classes=["tab-content"])
example_lines = []
preparsed_lines = ['>>> from sage.all import *']
for line in node.rawsource.splitlines() + ['']: # one extra to process last example
newline = line.lstrip()
if newline.startswith('....: '):
example_lines.append(newline[6:])
else:
if example_lines:
preparsed_example = preparse('\n'.join(example_lines))
prompt = '>>> '
for preparsed_line in preparsed_example.splitlines():
preparsed_lines.append(prompt + preparsed_line)
prompt = '... '
example_lines = []
if newline.startswith('sage: '):
example_lines.append(newline[6:])
else:
preparsed_lines.append(line)
preparsed = '\n'.join(preparsed_lines)
preparsed_node = LiteralBlock(preparsed, preparsed, language='ipycon')
content += preparsed_node
container += content
parent.insert(index + 1, container)
if SAGE_LIVE_DOC == 'yes':
# Tab for Jupyter-sphinx cell
from jupyter_sphinx.ast import JupyterCellNode, CellInputNode
source = node.rawsource
lines = []
for line in source.splitlines():
newline = line.lstrip()
if newline.startswith('sage: ') or newline.startswith('....: '):
lines.append(newline[6:])
cell_node = JupyterCellNode(
execute=False,
hide_code=False,
hide_output=True,
emphasize_lines=[],
raises=False,
stderr=True,
code_below=False,
classes=["jupyter_cell"])
cell_input = CellInputNode(classes=['cell_input','live-doc'])
cell_input += nodes.literal_block(
text='\n'.join(lines),
linenos=False,
linenostart=1)
cell_node += cell_input
container = TabContainer("", type="tab", new_set=False)
textnodes = [Text('Sage (live)')]
label = Label("", "", *textnodes)
container += label
content = Container("", is_div=True, classes=["tab-content"])
content += cell_node
container += content
parent.insert(index + 1, container)


# This replaces the setup() in sage.misc.sagedoc_conf
Expand All @@ -864,7 +923,7 @@ def setup(app):
app.connect('autodoc-process-docstring', skip_TESTS_block)
app.connect('autodoc-skip-member', skip_member)
app.add_transform(SagemathTransform)
if os.environ.get('SAGE_LIVE_DOC', 'no') == 'yes':
if SAGE_LIVE_DOC == 'yes' or SAGE_PREPARSED_DOC == 'yes':
app.add_transform(SagecodeTransform)

# When building the standard docs, app.srcdir is set to SAGE_DOC_SRC +
Expand Down
Loading