jbms / sphinx-immaterial

Adaptation of the popular mkdocs-material material design theme to the sphinx documentation system
https://jbms.github.io/sphinx-immaterial/
Other
195 stars 31 forks source link

Error while following sphinx tutorial about creating a todo ext #300

Closed mboll closed 7 months ago

mboll commented 11 months ago

I'm not sure if this is the correct title, or frankly what the problem is exactly, but it seems the theme doesn't support custom docutils nodes.

This should be reproduceable by following the sphinx tutorial for developing a custom extension. When I do that, then insert the new .. todo:: directive in a page and build the docs, I get the error below.

The todo_node is generated in the sphinx tutorial. The NotImplementedError is issued by docutils, though the custom directive works as expected when not using the immaterial theme.

I received this error with sphinx versions 6.2.1 and 7.2.6. Using sphinx-immaterial==0.11.7.

...
copying extra files... done
done
C:\Users\matt.bolliger\projects\pcf\src\pcf\traces\traces.py:docstring of pcf.traces.traces.make_sphere:16: WARNING: unknown node type: <todo_node: <title...><paragraph...>>

Exception occurred:
  File "C:\Users\matt.bolliger\AppData\Local\Programs\Python\Python310\lib\site-packages\docutils\nodes.py", line 2068, in unknown_departure
    raise NotImplementedError(
NotImplementedError: <class 'sphinx_immaterial.html_translator_mixin.get_html_translator.<locals>.CustomHTMLTranslator'> departing unknown node type: todo_node
The full traceback has been saved in C:\Users\MATT~1.BOL\AppData\Local\Temp\sphinx-err-yvik85hy.log, if you want to report the issue to the developers.
Please also report this if it was a user error, so that a better error message can be provided next time.
A bug report can be filed in the tracker at <https://github.com/sphinx-doc/sphinx/issues>. Thanks!
2bndy5 commented 11 months ago

The todo admonition is overridden by this theme in its custom admonitions ext. You can disable this behavior in conf.py.

The above advice should not be needed if you're following the tutorial correctly. The error shows that the node visitor cannot find a todo_node docutils node and that the todo_node isn't registered with app.add_node(). In fact, looking at the tutorial, the name of the node (class inheriting the docutils admonition) is todo (not todo_node). So, I think you have an error in your extension's source.

2bndy5 commented 11 months ago

The NotImplementedError is issued by docutils, though the custom directive works as expected when not using the immaterial theme.

I didn't see this at first. I need to see the source that you're using because the error is very specific to your undisclosed source code. Also, it would help to have a full traceback (obtained by using sphinx-build -v).

2bndy5 commented 11 months ago

I was able to reproduce this with a direct copy from the tutorial. I was also able to fix this by renaming the todo class to todo_node (as it is in the actual sphinx.ext.todo src). It would seem that the tutorial needs updating.

2bndy5 commented 11 months ago

disabling the overridden todo admonition with

sphinx_immaterial_override_builtin_admonitions = False

seems to build without error. So, the custom_admonitions.py cannot account for third-party variants of the todo extension. As this was a foreseen problem (why disabling the overrides is allowed), I don't think it needs to be addressed as it is specific to the todo admonition and not specific to custom docutils nodes.

mboll commented 11 months ago

Thanks Brendan. I'm glad you were able to reproduce; I had just been compiling an example.

It sounds like this isn't an issue specifically related to the immaterial theme? I'll create an item for the sphinx issue tracker.

Out of curiosity, is the reason that this works without the immaterial theme related to the overwritten admonitions?

mboll commented 11 months ago

Agh, sorry. Miss-clicked the close button.

2bndy5 commented 11 months ago

we posted at the same time 🤣 See my previous comment.

It would be beneficial (for this theme) to update the sphinx tutotial, but I don't think there is anything else that should be done on their end.

2bndy5 commented 11 months ago
Altered tutorial code (just for completeness) ```python from docutils import nodes from docutils.parsers.rst import Directive from sphinx.locale import _ from sphinx.util.docutils import SphinxDirective class todo_node(nodes.Admonition, nodes.Element): pass class todolist(nodes.General, nodes.Element): pass def visit_todo_node(self, node): self.visit_admonition(node) def depart_todo_node(self, node): self.depart_admonition(node) class TodolistDirective(Directive): def run(self): return [todolist("")] class TodoDirective(SphinxDirective): # this enables content in the directive has_content = True def run(self): targetid = "todo-%d" % self.env.new_serialno("todo") targetnode = nodes.target("", "", ids=[targetid]) todo_node_instance = todo_node("\n".join(self.content)) todo_node_instance += nodes.title(_("Todo"), _("Todo")) self.state.nested_parse(self.content, self.content_offset, todo_node_instance) if not hasattr(self.env, "todo_all_todos"): self.env.todo_all_todos = [] self.env.todo_all_todos.append( { "docname": self.env.docname, "lineno": self.lineno, "todo": todo_node_instance.deepcopy(), "target": targetnode, } ) return [targetnode, todo_node_instance] def purge_todos(app, env, docname): if not hasattr(env, "todo_all_todos"): return env.todo_all_todos = [ todo for todo in env.todo_all_todos if todo["docname"] != docname ] def merge_todos(app, env, docnames, other): if not hasattr(env, "todo_all_todos"): env.todo_all_todos = [] if hasattr(other, "todo_all_todos"): env.todo_all_todos.extend(other.todo_all_todos) def process_todo_nodes(app, doctree, fromdocname): if not app.config.todo_include_todos: for node in doctree.findall(todo_node): node.parent.remove(node) # Replace all todolist nodes with a list of the collected todos. # Augment each todo with a backlink to the original location. env = app.builder.env if not hasattr(env, "todo_all_todos"): env.todo_all_todos = [] for node in doctree.findall(todolist): if not app.config.todo_include_todos: node.replace_self([]) continue content = [] for todo_info in env.todo_all_todos: para = nodes.paragraph() filename = env.doc2path(todo_info["docname"], base=None) description = _( "(The original entry is located in %s, line %d and can be found " ) % (filename, todo_info["lineno"]) para += nodes.Text(description) # Create a reference newnode = nodes.reference("", "") innernode = nodes.emphasis(_("here"), _("here")) newnode["refdocname"] = todo_info["docname"] newnode["refuri"] = app.builder.get_relative_uri( fromdocname, todo_info["docname"] ) newnode["refuri"] += "#" + todo_info["target"]["refid"] newnode.append(innernode) para += newnode para += nodes.Text(".)") # Insert into the todolist content.append(todo_info["todo"]) content.append(para) node.replace_self(content) def setup(app): app.add_config_value("todo_include_todos", False, "html") app.add_node(todolist) app.add_node( todo_node, html=(visit_todo_node, depart_todo_node), latex=(visit_todo_node, depart_todo_node), text=(visit_todo_node, depart_todo_node), ) app.add_directive("todo", TodoDirective) app.add_directive("todolist", TodolistDirective) app.connect("doctree-resolved", process_todo_nodes) app.connect("env-purge-doc", purge_todos) app.connect("env-merge-info", merge_todos) return { "version": "0.1", "parallel_read_safe": True, "parallel_write_safe": True, } ```

Note I also had to rename a local variable in the TodoDirective.run() to avoid naming conflicts.