Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/doc/common/multidocs.py
8815 views
1
# -*- coding: utf-8 -*-
2
"""
3
multi documentation in Sphinx
4
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6
This is a slightly hacked-up version of the Sphinx-multidoc plugin
7
8
The goal of this extension is to manage a multi documentation in Sphinx.
9
To be able to compile Sage's huge documentation in parallel, the
10
documentation is cut into a bunch of independent documentations called
11
"subdocs", which are compiled separately. There is a master document which
12
points to all the subdocs. The intersphinx extension ensures that the
13
cross-link between the subdocs are correctly resolved. However some work
14
is needed to build a global index. This is the goal of multidocs.
15
16
More precisely this extension ensures the correct merging of
17
- the todo list if this extension is activated;
18
- the python indexes;
19
- the list of python modules;
20
- the javascript index;
21
- the citations.
22
"""
23
import cPickle, os, sys, shutil, re, tempfile
24
import sphinx
25
from sphinx.util.console import bold
26
27
28
CITE_FILENAME = 'citations.pickle'
29
30
31
def merge_environment(app, env):
32
"""
33
Merges the following attributes of the sub-docs environment into the main
34
environment:
35
- todo_all_todos # ToDo's
36
- indexentries # global python index
37
- all_docs # needed by the js index
38
- citations # citations
39
40
- domaindata['py']['modules'] # list of python modules
41
"""
42
app.info(bold('Merging environment/index files...'))
43
for curdoc in app.env.config.multidocs_subdoc_list:
44
app.info(" %s:"%curdoc, nonl=1)
45
docenv = get_env(app, curdoc)
46
if docenv is not None:
47
fixpath = lambda path: os.path.join(curdoc, path)
48
app.info(" %s todos, %s index, %s citations"%(
49
len(docenv.todo_all_todos),
50
len(docenv.indexentries),
51
len(docenv.citations)
52
), nonl=1)
53
54
# merge the todo links
55
for dct in docenv.todo_all_todos:
56
dct['docname']=fixpath(dct['docname'])
57
env.todo_all_todos += docenv.todo_all_todos
58
# merge the html index links
59
newindex = {}
60
for ind in docenv.indexentries:
61
if ind.startswith('sage/'):
62
newind = fixpath(ind)
63
newindex[newind] = docenv.indexentries[ind]
64
else:
65
newindex[ind] = docenv.indexentries[ind]
66
env.indexentries.update(newindex)
67
# merge the all_docs links, needed by the js index
68
newalldoc = {}
69
for ind in docenv.all_docs:
70
newalldoc[fixpath(ind)]=docenv.all_docs[ind]
71
env.all_docs.update(newalldoc)
72
# needed by env.check_consistency (sphinx.environement, line 1734)
73
for ind in newalldoc:
74
# treat subdocument source as orphaned file and don't complain
75
md = env.metadata.get(ind, set())
76
md.add('orphan')
77
env.metadata[ind] = md
78
# merge the citations
79
newcite = {}
80
for ind, (path, tag) in docenv.citations.iteritems():
81
# TODO: Warn on conflicts
82
newcite[ind]=(fixpath(path), tag)
83
env.citations.update(newcite)
84
# merge the py:module indexes
85
newmodules = {}
86
for ind,(modpath,v1,v2,v3) in (
87
docenv.domaindata['py']['modules'].iteritems()):
88
newmodules[ind] = (fixpath(modpath),v1,v2,v3)
89
env.domaindata['py']['modules'].update(newmodules)
90
app.info(", %s modules"%(len(newmodules)))
91
app.info('... done (%s todos, %s index, %s citations, %s modules)'%(
92
len(env.todo_all_todos),
93
len(env.indexentries),
94
len(env.citations),
95
len(env.domaindata['py']['modules'])))
96
write_citations(app, env.citations)
97
98
def get_env(app, curdoc):
99
"""
100
Get the environment of a sub-doc from the pickle
101
"""
102
from sphinx.application import ENV_PICKLE_FILENAME
103
filename = os.path.join(
104
app.env.doctreedir, curdoc, ENV_PICKLE_FILENAME)
105
try:
106
f = open(filename, 'rb')
107
except IOError:
108
app.info("")
109
app.warn("Unable to fetch %s "%filename)
110
return None
111
docenv = cPickle.load(f)
112
f.close()
113
return docenv
114
115
def merge_js_index(app):
116
"""
117
Merge the JS indexes of the sub-docs into the main JS index
118
"""
119
app.info('')
120
app.info(bold('Merging js index files...'))
121
mapping = app.builder.indexer._mapping
122
for curdoc in app.env.config.multidocs_subdoc_list:
123
app.info(" %s:"%curdoc, nonl=1)
124
fixpath = lambda path: os.path.join(curdoc, path)
125
index = get_js_index(app, curdoc)
126
if index is not None:
127
# merge the mappings
128
app.info(" %s js index entries"%(len(index._mapping)))
129
for (ref, locs) in index._mapping.iteritems():
130
newmapping = set(map(fixpath, locs))
131
if ref in mapping:
132
newmapping = mapping[ref] | newmapping
133
mapping[unicode(ref)] = newmapping
134
# merge the titles
135
titles = app.builder.indexer._titles
136
for (res, title) in index._titles.iteritems():
137
titles[fixpath(res)] = title
138
# TODO: merge indexer._objtypes, indexer._objnames as well
139
140
# Setup source symbolic links
141
dest = os.path.join(app.outdir, "_sources", curdoc)
142
if not os.path.exists(dest):
143
os.symlink(os.path.join("..", curdoc, "_sources"), dest)
144
app.info('... done (%s js index entries)'%(len(mapping)))
145
app.info(bold('Writing js search indexes...'), nonl=1)
146
return [] # no extra page to setup
147
148
def get_js_index(app, curdoc):
149
"""
150
Get the JS index of a sub-doc from the file
151
"""
152
from sphinx.search import IndexBuilder, languages
153
# FIXME: find the correct lang
154
indexer = IndexBuilder(app.env, 'en',
155
app.config.html_search_options)
156
indexfile = os.path.join(app.outdir, curdoc, 'searchindex.js')
157
try:
158
f = open(indexfile, 'rb')
159
except IOError:
160
app.info("")
161
app.warn("Unable to fetch %s "%indexfile)
162
return None
163
indexer.load(f, sphinx.search.js_index)
164
f.close()
165
return indexer
166
167
168
mustbefixed = ['search', 'genindex', 'genindex-all'
169
'py-modindex', 'searchindex.js']
170
def fix_path_html(app, pagename, templatename, ctx, event_arg):
171
"""
172
Fixes the context so that the files
173
- search.html
174
- genindex.html
175
- py-modindex.html
176
point to the right place, that is in
177
reference/
178
instead of
179
reference/subdocument
180
"""
181
# sphinx/builder/html.py line 702
182
# def pathto(otheruri, resource=False,
183
# baseuri=self.get_target_uri(pagename)):
184
old_pathto = ctx['pathto']
185
def sage_pathto(otheruri, *args, **opts):
186
if otheruri in mustbefixed:
187
otheruri = os.path.join("..", otheruri)
188
return old_pathto(otheruri, *args, **opts)
189
ctx['pathto'] = sage_pathto
190
191
192
def citation_dir(app):
193
citedir = re.sub('/doc/output/[^/]*/', '/doc/output/inventory/', app.outdir)
194
if not os.path.isdir(citedir):
195
os.makedirs(os.path.abspath(citedir))
196
return citedir
197
198
def write_citations(app, citations):
199
"""
200
Pickle the citation in a file.
201
202
Atomic except on Windows, where you need to upgrade to a real filesystem.
203
"""
204
import cPickle
205
outdir = citation_dir(app)
206
fd, tmp = tempfile.mkstemp(dir=outdir)
207
os.close(fd)
208
with open(tmp, 'wb') as file:
209
cPickle.dump(citations, file)
210
citation = os.path.join(outdir, CITE_FILENAME)
211
try:
212
os.rename(tmp, citation)
213
except OSError: # your OS sucks and cannot do atomic file replacement
214
os.unlink(citation)
215
os.rename(tmp, citation)
216
app.info("Saved pickle file: %s"%CITE_FILENAME)
217
218
219
def fetch_citation(app, env):
220
"""
221
Fetch the global citation index from the refman to allow for cross
222
references.
223
"""
224
app.builder.info(bold('loading cross citations... '), nonl=1)
225
filename = os.path.join(citation_dir(app), '..', CITE_FILENAME)
226
if not os.path.exists(filename):
227
return
228
import cPickle
229
file = open(filename, 'rb')
230
cache = cPickle.load(file)
231
file.close()
232
app.builder.info("done (%s citations)."%len(cache))
233
cite = env.citations
234
for ind, (path, tag) in cache.iteritems():
235
if ind not in cite: # don't override local citation
236
cite[ind]=(os.path.join("..", path), tag)
237
238
def init_subdoc(app):
239
"""
240
Init the merger depending on if we are compiling a subdoc or the master
241
doc itself.
242
"""
243
if app.config.multidocs_is_master:
244
app.info(bold("Compiling the master document"))
245
app.connect('env-updated', merge_environment)
246
app.connect('html-collect-pages', merge_js_index)
247
if app.config.multidocs_subdoc_list:
248
# Master file with indexes computed by merging indexes:
249
# Monkey patch index fetching to silence warning about broken index
250
def load_indexer(docnames):
251
app.builder.info(bold('Skipping loading of indexes'), nonl=1)
252
app.builder.load_indexer = load_indexer
253
254
else:
255
app.info(bold("Compiling a sub-document"))
256
app.connect('env-updated', fetch_citation)
257
app.connect('html-page-context', fix_path_html)
258
259
# Monkey patch copy_static_files to make a symlink to "../"
260
def link_static_files():
261
"""
262
Instead of copying static files, make a link to the master static file.
263
See sphinx/builder/html.py line 536::
264
265
class StandaloneHTMLBuilder(Builder):
266
[...]
267
def copy_static_files(self):
268
[...]
269
"""
270
app.builder.info(bold('linking _static directory.'))
271
static_dir = os.path.join(app.builder.outdir, '_static')
272
master_static_dir = os.path.join('..', '_static')
273
if os.path.exists(static_dir):
274
if os.path.isdir(static_dir) and not os.path.islink(static_dir):
275
shutil.rmtree(static_dir)
276
else:
277
os.unlink(static_dir)
278
os.symlink(master_static_dir, static_dir)
279
280
app.builder.copy_static_files = link_static_files
281
282
if app.config.multidoc_first_pass == 1:
283
app.config.intersphinx_mapping = {}
284
285
286
def setup(app):
287
app.add_config_value('multidocs_is_master', True, True)
288
app.add_config_value('multidocs_subdoc_list', [], True)
289
app.add_config_value('multidoc_first_pass', 0, False) # 1 = deactivate the loading of the inventory
290
app.connect('builder-inited', init_subdoc)
291
292