77
88import sphinx .builders .html
99from sphinx .errors import ExtensionError
10+ from sphinx .environment .adapters .toctree import TocTree
11+ from sphinx import addnodes
12+ from docutils import nodes
1013
1114from .bootstrap_html_translator import BootstrapHTML5Translator
1215import docutils
1316
1417__version__ = "0.1.1"
1518
1619
20+ class MyTocTree (TocTree ):
21+ def get_toctree_for_subpage (
22+ self , pagename , builder , collapse = True , maxdepth = - 1 , ** kwargs
23+ ):
24+ """Return the global TOC nodetree."""
25+ doctree = self .env .get_doctree (pagename )
26+ toctrees = [] # type: List[Element]
27+ if "includehidden" not in kwargs :
28+ kwargs ["includehidden" ] = True
29+ if "maxdepth" not in kwargs :
30+ kwargs ["maxdepth" ] = 0
31+ kwargs ["collapse" ] = collapse
32+ for toctreenode in doctree .traverse (addnodes .toctree ):
33+ toctree = self .resolve (pagename , builder , toctreenode , prune = True , ** kwargs )
34+ if toctree :
35+ toctrees .append (toctree )
36+ if not toctrees :
37+ return None
38+ result = toctrees [0 ]
39+ for toctree in toctrees [1 :]:
40+ result .extend (toctree .children )
41+ return result
42+
43+
1744def add_toctree_functions (app , pagename , templatename , context , doctree ):
1845 """Add functions so Jinja templates can add toctree objects.
1946
2047 This converts the docutils nodes into a nested dictionary that Jinja can
2148 use in our templating.
2249 """
23- from sphinx .environment .adapters .toctree import TocTree
2450
25- def get_nav_object (maxdepth = None , collapse = True , ** kwargs ):
51+ def get_nav_object (maxdepth = None , collapse = True , subpage_caption = False , ** kwargs ):
2652 """Return a list of nav links that can be accessed from Jinja.
2753
2854 Parameters
@@ -38,26 +64,46 @@ def get_nav_object(maxdepth=None, collapse=True, **kwargs):
3864 # The TocTree will contain the full site TocTree including sub-pages.
3965 # "collapse=True" collapses sub-pages of non-active TOC pages.
4066 # maxdepth controls how many TOC levels are returned
41- toctree = TocTree (app .env ).get_toctree_for (
67+ toc = MyTocTree (app .env )
68+ toctree = toc .get_toctree_for (
4269 pagename , app .builder , collapse = collapse , maxdepth = maxdepth , ** kwargs
4370 )
71+
4472 # If no toctree is defined (AKA a single-page site), skip this
4573 if toctree is None :
4674 return []
4775
76+ if subpage_caption :
77+ # We only wish to show a single page's descendants, so we'll keep their captions
78+ subpage_toctree = toc .get_toctree_for_subpage (
79+ pagename , app .builder , collapse = collapse , maxdepth = maxdepth , ** kwargs
80+ )
81+ if subpage_toctree is not None :
82+ # Find the current page in the top-level children
83+ for item in toctree .children :
84+ if isinstance (item , nodes .bullet_list ) and item .attributes .get ("iscurrent" , []):
85+ # Append that pages' toctree so we get captions
86+ subpage_list = item .children [0 ]
87+ subpage_list .children = [subpage_list .children [0 ]] + subpage_toctree .children
88+
89+
4890 # toctree has this structure
4991 # <caption>
5092 # <bullet_list>
5193 # <list_item classes="toctree-l1">
5294 # <list_item classes="toctree-l1">
5395 # `list_item`s are the actual TOC links and are the only thing we want
54- toc_items = [item for child in toctree .children for item in child
55- if isinstance (item , docutils .nodes .list_item )]
96+ toc_items = []
97+ for child in toctree .children :
98+ if isinstance (child , docutils .nodes .caption ):
99+ toc_items .append (child )
100+ elif isinstance (child , docutils .nodes .bullet_list ):
101+ for list_entry in child :
102+ if isinstance (list_entry , docutils .nodes .list_item ):
103+ toc_items .append (list_entry )
56104
57105 # Now convert our docutils nodes into dicts that Jinja can use
58- nav = [docutils_node_to_jinja (child , only_pages = True )
59- for child in toc_items ]
60-
106+ nav = [docutils_node_to_jinja (child , only_pages = True ) for child in toc_items ]
61107 return nav
62108
63109 def get_page_toc_object ():
@@ -73,6 +119,7 @@ def get_page_toc_object():
73119 context ["get_nav_object" ] = get_nav_object
74120 context ["get_page_toc_object" ] = get_page_toc_object
75121
122+
76123def docutils_node_to_jinja (list_item , only_pages = False ):
77124 """Convert a docutils node to a structure that can be read by Jinja.
78125
@@ -91,6 +138,12 @@ def docutils_node_to_jinja(list_item, only_pages=False):
91138 The TocTree, converted into a dictionary with key/values that work
92139 within Jinja.
93140 """
141+ # If a caption, pass it through
142+ if isinstance (list_item , docutils .nodes .caption ):
143+ nav = {"text" : list_item .astext (), "type" : "caption" }
144+ return nav
145+
146+ # Else, we assume it's a list item and need to parse the item content
94147 if not list_item .children :
95148 return None
96149
@@ -100,18 +153,20 @@ def docutils_node_to_jinja(list_item, only_pages=False):
100153 # <reference> <-- the thing we want
101154 reference = list_item .children [0 ].children [0 ]
102155 title = reference .astext ()
103- url = reference .attributes ["refuri" ]
156+
157+ url = reference .attributes .get ("refuri" , "" )
104158 active = "current" in list_item .attributes ["classes" ]
105159
106160 # If we've got an anchor link, skip it if we wish
107- if only_pages and '#' in url :
161+ if only_pages and "#" in url :
108162 return None
109163
110164 # Converting the docutils attributes into jinja-friendly objects
111165 nav = {}
112166 nav ["title" ] = title
113167 nav ["url" ] = url
114168 nav ["active" ] = active
169+ nav ["type" ] = "ref"
115170
116171 # Recursively convert children as well
117172 # If there are sub-pages for this list_item, there should be two children:
@@ -129,19 +184,23 @@ def docutils_node_to_jinja(list_item, only_pages=False):
129184
130185# -----------------------------------------------------------------------------
131186
187+
132188def setup_edit_url (app , pagename , templatename , context , doctree ):
133189 """Add a function that jinja can access for returning the edit URL of a page."""
190+
134191 def get_edit_url ():
135192 """Return a URL for an "edit this page" link."""
136193 required_values = ["github_user" , "github_repo" , "github_version" ]
137194 for val in required_values :
138195 if not context .get (val ):
139- raise ExtensionError ("Missing required value for `edit this page` button. "
140- "Add %s to your `html_context` configuration" % val )
141-
142- github_user = context ['github_user' ]
143- github_repo = context ['github_repo' ]
144- github_version = context ['github_version' ]
196+ raise ExtensionError (
197+ "Missing required value for `edit this page` button. "
198+ "Add %s to your `html_context` configuration" % val
199+ )
200+
201+ github_user = context ["github_user" ]
202+ github_repo = context ["github_repo" ]
203+ github_version = context ["github_version" ]
145204 file_name = f"{ pagename } { context ['page_source_suffix' ]} "
146205
147206 # Make sure that doc_path has a path separator only if it exists (to avoid //)
@@ -153,7 +212,7 @@ def get_edit_url():
153212 url_edit = f"https://github.com/{ github_user } /{ github_repo } /edit/{ github_version } /{ doc_path } { file_name } "
154213 return url_edit
155214
156- context [' get_edit_url' ] = get_edit_url
215+ context [" get_edit_url" ] = get_edit_url
157216
158217
159218# -----------------------------------------------------------------------------
@@ -174,6 +233,6 @@ def setup(app):
174233 # uses a special "dirhtml" builder so we need to replace these both with
175234 # our custom HTML builder
176235 app .set_translator ("readthedocs" , BootstrapHTML5Translator , override = True )
177- app .set_translator (' readthedocsdirhtml' , BootstrapHTML5Translator , override = True )
236+ app .set_translator (" readthedocsdirhtml" , BootstrapHTML5Translator , override = True )
178237 app .connect ("html-page-context" , setup_edit_url )
179238 app .connect ("html-page-context" , add_toctree_functions )
0 commit comments