executablebooks / sphinx-external-toc

A sphinx extension that allows the site-map to be defined in a single YAML file
https://sphinx-external-toc.readthedocs.io
MIT License
32 stars 18 forks source link

Invalid index.html generated for redirect to root_doc when dirhtml builder is used. #93

Closed jdsalaro closed 8 months ago

jdsalaro commented 1 year ago

Describe the bug

CONTEXT

Using jupyter-book build, sphinx-build or sphinx-autobuild on a book given:

  1. the root_doc is other than index.md or index.rst
  2. the dirhtml builder is used

Leads to the error [etoc] missing index.html written as redirect tof'{root_doc}.html'and the generation of an invalid redirectionroot_dir/index.htmlfile containingf''`

Regardless of whether the user manually browses to file://root_dir/ or uses sphinx-autobuild, the browser can't find the root_doc as it's bogus.

image

EXPECTATION

  1. in the case of sphinx-build -b html . _build/html, the built root_doc will be root_dir/root_doc.html and thus the redirection file index.html should be, as is the case nowadays, as follows:
<meta http-equiv="Refresh" content="0; url=root_doc.html" />
  1. However, in the case of sphinx-build -b dirhtml . _build/dirhtml, the built root_doc will be root_dir/root_doc/index.html and thus the redirection file index.html should be as follows:
<meta http-equiv="Refresh" content="0; url=root_doc/index.html" />

BUG

For the second case, what happens instead is the following:

<meta http-equiv="Refresh" content="0; url=root_doc.html" />

Which is due to sphinx-external-toc/events.py#318

def ensure_index_file(app: Sphinx, exception: Optional[Exception]) -> None:
    """Ensure that an index.html exists for HTML builds.

    This is required when navigating to the site, without specifying a page,
    which will then route to index.html by default.
    """
    index_path = Path(app.outdir).joinpath("index.html")
    if (
        exception is not None
        or "html" not in app.builder.format
        or app.config.master_doc == "index"
        # TODO rewrite the redirect if master_doc has changed since last build
        or index_path.exists()
    ):
        return
    root_name = remove_suffix(app.config.master_doc, app.config.source_suffix)
    # TODO the other way to do this would be to
    # simply copy the contents of the root file? (this method was taken from jupyter-book)
    redirect_text = f'<meta http-equiv="Refresh" content="0; url={root_name}.html" />\n'
    index_path.write_text(redirect_text, encoding="utf8")
    logger.info("[etoc] missing index.html written as redirect to '%s.html'", root_name)

PROBLEM

I use sphinx-external-toc together with sphinx and sphinx-autobuild to manage my website at https://jdsalaro.com, but I'm certainly not the only one as pointed out in https://github.com/executablebooks/jupyter-book/issues/1414 and https://github.com/alan-turing-institute/the-turing-way/issues/2887

FIX

The fix is rather simple as shown by the following commit in my fork: https://github.com/jdsalaro/sphinx-external-toc/commit/03e976bf422e880ff968a2c3fc5b828bec9a0f4e . The complexity of this issue revolved mostly around debugging it and finding the source of the redirection index.html file:

diff --git a/sphinx_external_toc/events.py b/sphinx_external_toc/events.py
index 522b7b8..4ee1b7d 100644
--- a/sphinx_external_toc/events.py
+++ b/sphinx_external_toc/events.py
@@ -330,9 +330,14 @@ def ensure_index_file(app: Sphinx, exception: Optional[Exception]) -> None:
         or index_path.exists()
     ):
         return
+    
     root_name = remove_suffix(app.config.master_doc, app.config.source_suffix)
-    # TODO the other way to do this would be to
-    # simply copy the contents of the root file? (this method was taken from jupyter-book)
-    redirect_text = f'<meta http-equiv="Refresh" content="0; url={root_name}.html" />\n'
+    
+    if app.builder.name == 'html':
+        redirect_url = f"{root_name}.html" 
+    elif app.builder.name == 'dirhtml':
+        redirect_url = f"{root_name}/index.html" 
+        
+    redirect_text = f'<meta http-equiv="Refresh" content="0; url={redirect_url}" />\n'
     index_path.write_text(redirect_text, encoding="utf8")
     logger.info("[etoc] missing index.html written as redirect to '%s.html'", root_name)

The above properly handles the two possible cases according to which builder was used: html or dirhtml

cc @chrisjsewell @mmcky

Reproduce the bug

jdsalaro$ jupyter-book create demo

===============================================================================

Your book template can be found at

    demo/

===============================================================================
jdsalaro$ jupyter-book build  --builder dirhtml demo 

Running Jupyter-Book v0.15.1
Source Folder: /tmp/jupyter-book-1414/demo
Config Path: /tmp/jupyter-book-1414/demo/_config.yml
Output Path: /tmp/jupyter-book-1414/demo/_build/dirhtml
[sphinxcontrib-bibtex] Beware that docutils versions 0.18 and 0.19 (you are running 0.18.1) are known to generate invalid html for citations. If this issue affects you, please use docutils<0.18 (or >=0.20 once released) instead. For more details, see https://sourceforge.net/p/docutils/patches/195/
Running Sphinx v5.0.2
making output directory... done

___________________________________________
| [etoc] Changing master_doc to 'intro'   |
------------------------------------------------------------

checking bibtex cache... out of date
parsing bibtex file /tmp/jupyter-book-1414/demo/references.bib... parsed 5 entries
myst v0.18.1: MdParserConfig(commonmark_only=False, gfm_only=False, enable_extensions=['colon_fence', 'dollarmath', 'linkify', 'substitution', 'tasklist'], disable_syntax=[], all_links_external=False, url_schemes=['mailto', 'http', 'https'], ref_domains=None, highlight_code_blocks=True, number_code_blocks=[], title_to_header=False, heading_anchors=None, heading_slug_func=None, footnote_transition=True, words_per_minute=200, sub_delimiters=('{', '}'), linkify_fuzzy_links=True, dmath_allow_labels=True, dmath_allow_space=True, dmath_allow_digits=True, dmath_double_inline=False, update_mathjax=True, mathjax_classes='tex2jax_process|mathjax_process|math|output_area')
myst-nb v0.17.2: NbParserConfig(custom_formats={}, metadata_key='mystnb', cell_metadata_key='mystnb', kernel_rgx_aliases={}, execution_mode='force', execution_cache_path='', execution_excludepatterns=[], execution_timeout=30, execution_in_temp=False, execution_allow_errors=False, execution_raise_on_error=False, execution_show_tb=False, merge_streams=False, render_plugin='default', remove_code_source=False, remove_code_outputs=False, code_prompt_show='Show code cell {type}', code_prompt_hide='Hide code cell {type}', number_source_lines=False, output_stderr='show', render_text_lexer='myst-ansi', render_error_lexer='ipythontb', render_image_options={}, render_figure_options={}, render_markdown_format='commonmark', output_folder='build', append_css=True, metadata_to_fm=False)
Using jupyter-cache at: /tmp/jupyter-book-1414/demo/_build/.jupyter_cache
building [mo]: targets for 0 po files that are out of date
building [dirhtml]: targets for 4 source files that are out of date
updating environment: [new config] 4 added, 0 changed, 0 removed
/tmp/jupyter-book-1414/demo/markdown-notebooks.md: Executing notebook using local CWD [mystnb]                                                                                                            
/tmp/jupyter-book-1414/demo/markdown-notebooks.md: Executed notebook in 1.00 seconds [mystnb]
/tmp/jupyter-book-1414/demo/notebooks.ipynb: Executing notebook using local CWD [mystnb]                                                                                                                  

/tmp/jupyter-book-1414/demo/notebooks.ipynb: WARNING: Executing notebook failed: CellExecutionError [mystnb.exec]
/tmp/jupyter-book-1414/demo/notebooks.ipynb: WARNING: Notebook exception traceback saved in: /tmp/jupyter-book-1414/demo/_build/dirhtml/reports/notebooks.err.log [mystnb.exec]
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
writing output... [100%] notebooks                                                                                                                                                                        
generating indices... genindex done
writing additional pages... search done
copying static files... done
copying extra files... done
dumping search index in English (code: en)... done
dumping object inventory... done
[etoc] missing index.html written as redirect to 'intro.html'
build succeeded, 2 warnings.

The HTML pages are in demo/_build/dirhtml.
jdsalaro$ cat demo/_build/dirhtml/index.html 
<meta http-equiv="Refresh" content="0; url=intro.html" />

List your environment

$ jupyter-book --version
Jupyter Book      : 0.15.1
External ToC      : 0.3.1
MyST-Parser       : 0.18.1
MyST-NB           : 0.17.2
Sphinx Book Theme : 1.0.1
Jupyter-Cache     : 0.6.1
NbClient          : 0.7.4
welcome[bot] commented 1 year ago

Thanks for opening your first issue here! Engagement like this is essential for open source projects! :hugs:
If you haven't done so already, check out EBP's Code of Conduct. Also, please try to follow the issue template as it helps other community members to contribute more effectively.
If your issue is a feature request, others may react to it, to raise its prominence (see Feature Voting).
Welcome to the EBP community! :tada:

agoose77 commented 8 months ago

Closed by #94!