File: __init__.py

package info (click to toggle)
sphinx-press-theme 0.8.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 500 kB
  • sloc: javascript: 298; python: 185; makefile: 22
file content (170 lines) | stat: -rw-r--r-- 6,138 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
from os import path

from docutils import nodes
from sphinx.environment.collectors import EnvironmentCollector
from sphinx import addnodes
from sphinx.util.osutil import relative_uri

__version__ = (0, 8, 0)


class SimpleTocTreeCollector(EnvironmentCollector):
    """A TocTree collector that saves toctrees in a simple dict.

    sphinx.environment.collectors.toctree.TocTreeCollector saves
    TocTree as docutils.nodes which are hard to work with...

    Executed once per document/page, at sphinx's "read" phase.

    Saved data example:
    >>> {
    >>>  'sections': [{'title': 'Demo', 'href': '#demo'}],
    >>>  'toctrees': [<toctree: >]
    >>> }
    """
    def enable(self, app):
        super().enable(app)
        # env is populated from cache, if not cache create/initalize attibute
        if not hasattr(app.env, 'toc_dict'):
            app.env.toc_dict = {}

    def clear_doc(self, app, env, docname):
        env.toc_dict.pop(docname, None)

    def merge_other(self, app, env, docnames, other):
        for docname in docnames:
            env.toc_dict[docname] = other.toc_dict[docname]


    def process_doc(self, app, doctree):
        docname = app.env.docname # sphinx mutates this, ouch!!!

        # print(f"================ Collector\n{docname}\n============\n")
        # get 1 level document toc (sections)
        section_nodes = [s for s in doctree if isinstance(s, nodes.section)]
        # if first level is a single section,
        # ignore it and use second level of sections
        if len(section_nodes) == 1:
            section2_nodes = [s for s in section_nodes[0]
                              if isinstance(s, nodes.section)]
            if section2_nodes: # do not replace with level-2 sections if None
                section_nodes = section2_nodes

        sections = []
        for node in section_nodes:
            sections.append({
                'title': node[0].astext(),
                'href': '#{}'.format(node['ids'][0]),
            })

        app.env.toc_dict[docname] = {
            'sections': sections,
            'toctrees': doctree.traverse(addnodes.toctree)
        }



def add_toctree_data(app, pagename, templatename, context, doctree):
    """Create toctree_data, used to build sidebar navigation

    :param pagename: The name of the page
    :type pagename: str
    :param templatename: The name of the templatename
    :type templatename: str
    :param context: The context
    :type context: dict
    :param doctree: A doctree
    :type doctree: docutils.nodes.document

    Add to `toctree_data` to `context` that will be available on templates.
    Although data is "global", it is called once per page because current
    page is "highlighted", and some part of TOC might be collapsed.

    :return: None
    """
    # print(f"---------- Context\n{pagename}\n-------------\n")

    # start from master_doc
    master = app.env.get_doctree(app.env.config.master_doc)

    # each toctree will create navigation section
    res = [] # list of top level toctrees in master_doc
    for tree in master.traverse(addnodes.toctree):

        # special case for toctree that includes a single item
        # that contains a nested toctree.
        # In this case, just use the referenced toctree directly
        if len(tree['entries']) == 1:
            entry_docname = tree['entries'][0][1]
            toctrees = list(app.env.toc_dict[entry_docname]['toctrees'])

            if toctrees:
                # FIXME
                assert len(toctrees) == 1, "Press: Not supported more then one toctree on nested toctree"
                tree = toctrees[0]

        current0 = False # same page might have multiple tocs

        # add toc tree items, expand one more level if toctree is current page
        entries = []
        # entries contain a pair (title, name|external url)
        # if name of document is given and not title, title is taken from document
        for title, name in tree['entries']:
            if not title:
                title = app.env.titles[name].astext()

            # check if entry is an external resource
            ext_resource = '://' in name

            current1 = (pagename == name)
            children = []
            if current1:
                current0 = True
                # if current, add another level
                children = app.env.toc_dict[name]['sections']
            # add page_toc for current page
            entries.append({
                'name': name,
                'title': title,
                'current': current1,
                'children': children,
                'ext_resource': ext_resource,
            })

        toc_docname = tree['parent'] # docname where this toc appears
        title = tree['caption']

        # Anchor element is the section containing the toc,
        # as the toc itself does not contain ID.
        anchor_id = ''

        # tree.parent is the parent docutils node.
        # First parent is "compound" node toctree-wrapper,
        # second parent is the section containing the toctree
        toc_section = tree.parent.parent
        if toc_section['ids']: # no id means toc actually not in a section
            # TODO: should we be strict about toc being inside a section
            anchor_id = toc_section['ids'][0]
            if not title:
                title = toc_section['names'][0]

        # sphinx `pathto` does not play nice with anchors when
        # `allow_sharp_as_current_path` is True
        baseuri = app.builder.get_target_uri(pagename).rsplit('#', 1)[0]
        toc_uri = app.builder.get_target_uri(toc_docname).rsplit('#', 1)[0]
        toc_href = '{}#{}'.format(relative_uri(baseuri, toc_uri), anchor_id)
        res.append({
            'docname': toc_docname,
            'href': toc_href,
            'title': title,
            'current': current0,
            'entries': entries,
        })
    context['toctree_data'] = res



def setup(app):
    app.add_env_collector(SimpleTocTreeCollector)
    app.connect('html-page-context', add_toctree_data)
    app.add_html_theme('press', path.abspath(path.dirname(__file__)))