# -*- coding: utf-8 -*-"""AutoTocTree Sphinx Directive ImplementationThis module implements a custom Sphinx directive ``.. autotoctree::`` that automaticallygenerates a ``toctree`` based on your documentation folder structure. It eliminates the needto manually update ``toctree`` entries when you add or modify documentation sections."""frompathlibimportPathimportsphinx.utilfromsphinx.directives.otherimportTocTreefromdocutilsimportnodesfromdocutils.parsers.rstimportDirective,directivesfromdocutils.statemachineimportStringListfrom..autotoctreeimportIndexFileNotFoundError,PageFolder
[docs]classAutoTocTree(Directive):""" Custom Sphinx directive that automatically includes subdirectory index files in a ``toctree``. This directive works by: 1. Determining the current document's location 2. Finding all subdirectories containing index files 3. Extracting titles from those index files 4. Generating a properly formatted toctree directive The directive supports all standard `toctree <https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-toctree>`_ options (inherited from TocTree) plus additional custom options: :param append_ahead: Flag to append manual entries before the auto-detected entries :param index_file: Base name of index files to look for (default: "index") Example:: .. autotoctree:: :maxdepth: 1 :index_file: index """# Define custom option names as class constants for clarity and maintainability_opt_append_ahead="append_ahead"_opt_index_file="index_file"_opt_index_file_default="index"# Directive configurationhas_content=True# override default behavior of TocTree classoption_spec=TocTree.option_spec.copy()option_spec[_opt_append_ahead]=directives.flagoption_spec[_opt_index_file]=str
[docs]defrun(self):""" Execute the directive to generate the ``toctree``. This method is called by Sphinx when processing the directive. It creates a docutils node tree representing the ``toctree``. :return: List of nodes to be inserted into the document """# Create an empty element node to hold our generated contentnode=nodes.Element()node.document=self.state.document# print(f"[DEBUG] {node.document = }") # for debug only# Get the path of the current file containing this directivecurrent_file=self.state.document.current_sourceprint(f"[DEBUG] {current_file= }")# for debug only# Generate the RST content for the toctreeoutput_rst=self.derive_toctree_rst(current_file)print(f"[DEBUG] {output_rst= }")# for debug only# Convert the RST string into a list of lines with source informationview_list=StringList(output_rst.splitlines(),source="")# Parse our generated RST content into docutils nodessphinx.util.nested_parse_with_titles(self.state,view_list,node)# Return the children of our node (the parsed toctree nodes)returnnode.children
[docs]defderive_toctree_rst(self,current_file:str):""" Generate the RST content for the ``toctree`` directive. This method creates a string containing a complete ``toctree`` directive with entries for all subdirectories with index files. :param current_file: Path to the file containing this directive :return: String containing RST ``toctree`` directive Generate the rst content:: .. toctree:: args ... example.rst ... :param current_file: :return: """TAB=" "*4# Standard indentationlines=list()# Create the toctree directive headerlines.append(".. toctree::")# Add all options from the original directive (like maxdepth, etc.)foroptinTocTree.option_spec:value=self.options.get(opt)ifvalueisnotNone:line="{indent}:{option}: {value}".format(indent=TAB,option=opt,value=value,).rstrip()lines.append(line)# Add a blank line after options (required by RST syntax)lines.append("")# If append_ahead option is set, add manual entries firstifself._opt_append_aheadinself.options:forlineinlist(self.content):lines.append(TAB+line)# Get the index file name from options or use defaultindex_file=self.options.get(self._opt_index_file,self._opt_index_file_default,)print(f"[DEBUG] {index_file= }")# for debug only# Create ArticleFolder to scan the directory structuretry:page_folder=PageFolder.new(dir=Path(current_file).parent,index_filename=index_file,)exceptIndexFileNotFoundErrorase:print(f"You set index_file = {index_file} in an `.. autotoctree::` directive"f"in {current_file}, but cannot locate the right index_file in the current directory!")raisee# Add each subdirectory with index file to the toctreeforchild_page_folderinpage_folder.child_page_folders:line=f"{TAB}{child_page_folder.title} <{child_page_folder.path_str}>"lines.append(line)# If append_ahead option is not set, add manual entries after auto entriesifself._opt_append_aheadnotinself.options:forlineinlist(self.content):lines.append(TAB+line)# Add final blank linelines.append("")# Join all lines into a single stringtoctree="\n".join(lines)returntoctree