Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2@file 

3@brief Helpers to convert docstring to various format. 

4""" 

5import os 

6import sys 

7from collections import deque 

8import warnings 

9import pickle 

10import platform 

11from html import escape as htmlescape 

12from io import StringIO 

13from docutils.parsers.rst import roles 

14from docutils.languages import en as docutils_en 

15from docutils import nodes 

16from docutils.utils import Reporter 

17from sphinx.application import Sphinx 

18from sphinx.environment import BuildEnvironment 

19from sphinx.errors import ExtensionError 

20from sphinx.ext.extlinks import setup_link_roles 

21from sphinx.transforms import SphinxTransformer 

22from sphinx.util.docutils import is_html5_writer_available 

23from sphinx.writers.html import HTMLWriter 

24from sphinx.util.build_phase import BuildPhase 

25from sphinx.util.logging import prefixed_warnings 

26from sphinx.project import Project 

27from sphinx.errors import ApplicationError 

28from sphinx.util.logging import getLogger 

29from ..sphinxext.sphinx_doctree_builder import ( 

30 DocTreeBuilder, DocTreeWriter, DocTreeTranslator) 

31from ..sphinxext.sphinx_md_builder import MdBuilder, MdWriter, MdTranslator 

32from ..sphinxext.sphinx_latex_builder import ( 

33 EnhancedLaTeXBuilder, EnhancedLaTeXWriter, EnhancedLaTeXTranslator) 

34from ..sphinxext.sphinx_rst_builder import RstBuilder, RstWriter, RstTranslator 

35from ._single_file_html_builder import CustomSingleFileHTMLBuilder 

36 

37 

38def _get_LaTeXTranslator(): 

39 try: 

40 from sphinx.writers.latex import LaTeXTranslator 

41 except ImportError: # pragma: no cover 

42 # Since sphinx 1.7.3 (circular reference). 

43 import sphinx.builders.latex.transforms 

44 from sphinx.writers.latex import LaTeXTranslator 

45 return LaTeXTranslator 

46 

47 

48if is_html5_writer_available(): 

49 from sphinx.writers.html5 import HTML5Translator as HTMLTranslator 

50else: 

51 from sphinx.writers.html import HTMLTranslator # pragma: no cover 

52 

53 

54def update_docutils_languages(values=None): 

55 """ 

56 Updates ``docutils/languages/en.py`` with missing labels. 

57 It Does it for languages *en*. 

58 

59 @param values consider values in this dictionaries first 

60 """ 

61 if values is None: 

62 values = dict() 

63 lab = docutils_en.labels 

64 if 'versionmodified' not in lab: 

65 lab['versionmodified'] = values.get( 

66 'versionmodified', 'modified version') 

67 if 'desc' not in lab: 

68 lab['desc'] = values.get('desc', 'description') 

69 

70 

71class _AdditionalVisitDepart: 

72 """ 

73 Additional visitors and departors. 

74 """ 

75 

76 def __init__(self, output_format): 

77 self.output_format = output_format 

78 

79 def is_html(self): 

80 """ 

81 Tells if the translator is :epkg:`html` format. 

82 """ 

83 return self.base_class is HTMLTranslator 

84 

85 def is_rst(self): 

86 """ 

87 Tells if the translator is :epkg:`rst` format. 

88 """ 

89 return self.base_class is RstTranslator 

90 

91 def is_latex(self): 

92 """ 

93 Tells if the translator is :epkg:`latex` format. 

94 """ 

95 return self.base_class is _get_LaTeXTranslator() 

96 

97 def is_md(self): 

98 """ 

99 Tells if the translator is :epkg:`markdown` format. 

100 """ 

101 return self.base_class is _get_LaTeXTranslator() 

102 

103 def is_doctree(self): 

104 """ 

105 Tells if the translator is doctree format. 

106 """ 

107 return self.base_class is _get_LaTeXTranslator() 

108 

109 def add_secnumber(self, node): 

110 """ 

111 Overwrites this method to catch errors due when 

112 it is a single document being processed. 

113 """ 

114 if node.get('secnumber'): 

115 self.base_class.add_secnumber(self, node) 

116 elif len(node.parent['ids']) > 0: 

117 self.base_class.add_secnumber(self, node) 

118 else: 

119 n = len(self.builder.secnumbers) 

120 node.parent['ids'].append("custom_label_%d" % n) 

121 self.base_class.add_secnumber(self, node) 

122 

123 def eval_expr(self, expr): 

124 rst = self.output_format == 'rst' 

125 latex = self.output_format in ('latex', 'elatex') 

126 texinfo = [('index', 'A_AdditionalVisitDepart', 'B_AdditionalVisitDepart', # pylint: disable=W0612 

127 'C_AdditionalVisitDepart', 'D_AdditionalVisitDepart', 

128 'E_AdditionalVisitDepart', 'Miscellaneous')] 

129 html = self.output_format == 'html' 

130 md = self.output_format == 'md' 

131 doctree = self.output_format in ('doctree', 'doctree.txt') 

132 if not(rst or html or latex or md or doctree): 

133 raise ValueError( # pragma: no cover 

134 "Unknown output format '{0}'.".format(self.output_format)) 

135 try: 

136 ev = eval(expr) 

137 except Exception: # pragma: no cover 

138 raise ValueError( 

139 "Unable to interpret expression '{0}'".format(expr)) 

140 return ev 

141 

142 def visit_only(self, node): 

143 ev = self.eval_expr(node.attributes['expr']) 

144 if ev: 

145 pass 

146 else: 

147 raise nodes.SkipNode 

148 

149 def depart_only(self, node): 

150 ev = self.eval_expr(node.attributes['expr']) 

151 if ev: 

152 pass 

153 else: 

154 # The program should not necessarily be here. 

155 pass 

156 

157 def visit_viewcode_anchor(self, node): 

158 # Removed in sphinx 3.5 

159 pass 

160 

161 def depart_viewcode_anchor(self, node): 

162 # Removed in sphinx 3.5 

163 pass 

164 

165 def unknown_visit(self, node): # pragma: no cover 

166 raise NotImplementedError( 

167 "[_AdditionalVisitDepart] Unknown node: '{0}' in '{1}'".format( 

168 node.__class__.__name__, self.__class__.__name__)) 

169 

170 

171class HTMLTranslatorWithCustomDirectives(_AdditionalVisitDepart, HTMLTranslator): 

172 """ 

173 See @see cl HTMLWriterWithCustomDirectives. 

174 """ 

175 

176 def __init__(self, document, builder, *args, **kwds): 

177 HTMLTranslator.__init__(self, document, builder, *args, **kwds) 

178 _AdditionalVisitDepart.__init__(self, 'html') 

179 nodes_list = getattr(builder, '_function_node', None) 

180 if nodes_list is not None: 

181 for name, f1, f2 in nodes_list: 

182 setattr(self.__class__, "visit_" + name, f1) 

183 setattr(self.__class__, "depart_" + name, f2) 

184 self.base_class = HTMLTranslator 

185 

186 def visit_field(self, node): 

187 if not hasattr(self, '_fieldlist_row_index'): 

188 # needed when a docstring starts with :param: 

189 self._fieldlist_row_index = 0 

190 return HTMLTranslator.visit_field(self, node) 

191 

192 def visit_pending_xref(self, node): 

193 self.visit_Text(node) 

194 raise nodes.SkipNode 

195 

196 def unknown_visit(self, node): # pragma: no cover 

197 raise NotImplementedError("[HTMLTranslatorWithCustomDirectives] Unknown node: '{0}' in '{1}'".format( 

198 node.__class__.__name__, self.__class__.__name__)) 

199 

200 

201class RSTTranslatorWithCustomDirectives(_AdditionalVisitDepart, RstTranslator): 

202 """ 

203 See @see cl HTMLWriterWithCustomDirectives. 

204 """ 

205 

206 def __init__(self, document, builder, *args, **kwds): 

207 """ 

208 constructor 

209 """ 

210 RstTranslator.__init__(self, document, builder, *args, **kwds) 

211 _AdditionalVisitDepart.__init__(self, 'rst') 

212 for name, f1, f2 in builder._function_node: 

213 setattr(self.__class__, "visit_" + name, f1) 

214 setattr(self.__class__, "depart_" + name, f2) 

215 self.base_class = RstTranslator 

216 

217 

218class MDTranslatorWithCustomDirectives(_AdditionalVisitDepart, MdTranslator): 

219 """ 

220 See @see cl HTMLWriterWithCustomDirectives. 

221 """ 

222 

223 def __init__(self, document, builder, *args, **kwds): 

224 """ 

225 constructor 

226 """ 

227 MdTranslator.__init__(self, document, builder, *args, **kwds) 

228 _AdditionalVisitDepart.__init__(self, 'md') 

229 for name, f1, f2 in builder._function_node: 

230 setattr(self.__class__, "visit_" + name, f1) 

231 setattr(self.__class__, "depart_" + name, f2) 

232 self.base_class = MdTranslator 

233 

234 

235class DocTreeTranslatorWithCustomDirectives(DocTreeTranslator): 

236 """ 

237 See @see cl HTMLWriterWithCustomDirectives. 

238 """ 

239 

240 def __init__(self, document, builder, *args, **kwds): 

241 """ 

242 constructor 

243 """ 

244 DocTreeTranslator.__init__(self, document, builder, *args, **kwds) 

245 self.base_class = DocTreeTranslator 

246 

247 

248class LatexTranslatorWithCustomDirectives(_AdditionalVisitDepart, EnhancedLaTeXTranslator): 

249 """ 

250 See @see cl LatexWriterWithCustomDirectives. 

251 """ 

252 

253 def __init__(self, document, builder, *args, **kwds): 

254 """ 

255 constructor 

256 """ 

257 if not hasattr(builder, "config"): 

258 builder, document = document, builder 

259 if not hasattr(builder, "config"): 

260 raise TypeError( # pragma: no cover 

261 "Builder has no config: {} - {}".format(type(builder), type(document))) 

262 EnhancedLaTeXTranslator.__init__( 

263 self, document, builder, *args, **kwds) 

264 _AdditionalVisitDepart.__init__(self, 'md') 

265 for name, f1, f2 in builder._function_node: 

266 setattr(self.__class__, "visit_" + name, f1) 

267 setattr(self.__class__, "depart_" + name, f2) 

268 self.base_class = EnhancedLaTeXTranslator 

269 

270 

271class _WriterWithCustomDirectives: 

272 """ 

273 Common class to @see cl HTMLWriterWithCustomDirectives and @see cl RSTWriterWithCustomDirectives. 

274 """ 

275 

276 def _init(self, base_class, translator_class, app=None): 

277 """ 

278 @param base_class base class 

279 @param app Sphinx application 

280 """ 

281 if app is None: 

282 self.app = _CustomSphinx(srcdir=None, confdir=None, outdir=None, doctreedir=None, 

283 buildername='memoryhtml') 

284 else: 

285 self.app = app 

286 builder = self.app.builder 

287 builder.fignumbers = {} 

288 base_class.__init__(self, builder) 

289 self.translator_class = translator_class 

290 self.builder.secnumbers = {} 

291 self.builder._function_node = [] 

292 self.builder.current_docname = None 

293 self.base_class = base_class 

294 

295 def connect_directive_node(self, name, f_visit, f_depart): 

296 """ 

297 Adds custom node to the translator. 

298 

299 @param name name of the directive 

300 @param f_visit visit function 

301 @param f_depart depart function 

302 """ 

303 if self.builder.format != "doctree": 

304 self.builder._function_node.append((name, f_visit, f_depart)) 

305 

306 def add_configuration_options(self, new_options): 

307 """ 

308 Add new options. 

309 

310 @param new_options new options 

311 """ 

312 for k, v in new_options.items(): 

313 self.builder.config.values[k] = v 

314 

315 def write(self, document, destination): 

316 """ 

317 Processes a document into its final form. 

318 Translates `document` (a Docutils document tree) into the Writer's 

319 native format, and write it out to its `destination` (a 

320 `docutils.io.Output` subclass object). 

321 

322 Normally not overridden or extended in subclasses. 

323 """ 

324 self.base_class.write(self, document, destination) 

325 

326 

327class HTMLWriterWithCustomDirectives(_WriterWithCustomDirectives, HTMLWriter): 

328 """ 

329 This :epkg:`docutils` writer extends the HTML writer with 

330 custom directives implemented in this module, 

331 @see cl RunPythonDirective, @see cl BlogPostDirective. 

332 

333 See `Write your own ReStructuredText-Writer <http://www.arnebrodowski.de/blog/write-your-own-restructuredtext-writer.html>`_. 

334 

335 This class needs to tell :epkg:`docutils` to call the added function 

336 when directives *runpython* or *blogpost* are met. 

337 """ 

338 

339 def __init__(self, builder=None, app=None): # pylint: disable=W0231 

340 """ 

341 @param builder builder 

342 @param app Sphinx application 

343 """ 

344 _WriterWithCustomDirectives._init( 

345 self, HTMLWriter, HTMLTranslatorWithCustomDirectives, app) 

346 

347 def translate(self): 

348 self.visitor = visitor = self.translator_class( 

349 self.document, self.builder) 

350 self.document.walkabout(visitor) 

351 self.output = visitor.astext() 

352 for attr in ('head_prefix', 'stylesheet', 'head', 'body_prefix', 

353 'body_pre_docinfo', 'docinfo', 'body', 'fragment', 

354 'body_suffix', 'meta', 'title', 'subtitle', 'header', 

355 'footer', 'html_prolog', 'html_head', 'html_title', 

356 'html_subtitle', 'html_body', ): 

357 setattr(self, attr, getattr(visitor, attr, None)) 

358 self.clean_meta = ''.join(visitor.meta[2:]) 

359 

360 

361class RSTWriterWithCustomDirectives(_WriterWithCustomDirectives, RstWriter): 

362 """ 

363 This :epkg:`docutils` writer extends the :epkg:`RST` writer with 

364 custom directives implemented in this module. 

365 """ 

366 

367 def __init__(self, builder=None, app=None): # pylint: disable=W0231 

368 """ 

369 @param builder builder 

370 @param app Sphinx application 

371 """ 

372 _WriterWithCustomDirectives._init( 

373 self, RstWriter, RSTTranslatorWithCustomDirectives, app) 

374 

375 def translate(self): 

376 visitor = self.translator_class(self.document, self.builder) 

377 self.document.walkabout(visitor) 

378 self.output = visitor.body 

379 

380 

381class MDWriterWithCustomDirectives(_WriterWithCustomDirectives, MdWriter): 

382 """ 

383 This :epkg:`docutils` writer extends the :epkg:`MD` writer with 

384 custom directives implemented in this module. 

385 """ 

386 

387 def __init__(self, builder=None, app=None): # pylint: disable=W0231 

388 """ 

389 @param builder builder 

390 @param app Sphinx application 

391 """ 

392 _WriterWithCustomDirectives._init( 

393 self, MdWriter, MDTranslatorWithCustomDirectives, app) 

394 

395 def translate(self): 

396 visitor = self.translator_class(self.document, self.builder) 

397 self.document.walkabout(visitor) 

398 self.output = visitor.body 

399 

400 

401class DocTreeWriterWithCustomDirectives(_WriterWithCustomDirectives, DocTreeWriter): 

402 """ 

403 This :epkg:`docutils` writer creates a doctree writer with 

404 custom directives implemented in this module. 

405 """ 

406 

407 def __init__(self, builder=None, app=None): # pylint: disable=W0231 

408 """ 

409 @param builder builder 

410 @param app Sphinx application 

411 """ 

412 _WriterWithCustomDirectives._init( 

413 self, DocTreeWriter, DocTreeTranslatorWithCustomDirectives, app) 

414 

415 def translate(self): 

416 visitor = self.translator_class(self.document, self.builder) 

417 self.document.walkabout(visitor) 

418 self.output = visitor.body 

419 

420 

421class LatexWriterWithCustomDirectives(_WriterWithCustomDirectives, EnhancedLaTeXWriter): 

422 """ 

423 This :epkg:`docutils` writer extends the :epkg:`Latex` writer with 

424 custom directives implemented in this module. 

425 """ 

426 

427 def __init__(self, builder=None, app=None): # pylint: disable=W0231 

428 """ 

429 @param builder builder 

430 @param app Sphinx application 

431 """ 

432 _WriterWithCustomDirectives._init( 

433 self, EnhancedLaTeXWriter, LatexTranslatorWithCustomDirectives, app) 

434 if not hasattr(self.builder, "config"): 

435 raise TypeError( # pragma: no cover 

436 "Builder has no config: {}".format(type(self.builder))) 

437 

438 def translate(self): 

439 if not hasattr(self.builder, "config"): 

440 raise TypeError( # pragma: no cover 

441 "Builder has no config: {}".format(type(self.builder))) 

442 # The instruction 

443 # visitor = self.builder.create_translator(self.document, self.builder) 

444 # automatically adds methods visit_ and depart_ for translator 

445 # based on the list of registered extensions. Might be worth using it. 

446 visitor = self.translator_class(self.document, self.builder) 

447 self.document.walkabout(visitor) 

448 self.output = visitor.body 

449 

450 

451class _MemoryBuilder: 

452 """ 

453 Builds :epkg:`HTML` output in memory. 

454 The API is defined by the page 

455 :epkg:`builderapi`. 

456 """ 

457 

458 def _init(self, base_class, app): 

459 """ 

460 Constructs the builder. 

461 Most of the parameter are static members of the class and cannot 

462 be overwritten (yet). 

463 

464 :param base_class: base builder class 

465 :param app: :epkg:`Sphinx application` 

466 """ 

467 if "IMPOSSIBLE:TOFIND" in app.srcdir: 

468 import sphinx.util.osutil 

469 from .conf_path_tools import custom_ensuredir 

470 sphinx.util.osutil.ensuredir = custom_ensuredir 

471 sphinx.builders.ensuredir = custom_ensuredir 

472 

473 base_class.__init__(self, app=app) 

474 self.built_pages = {} 

475 self.base_class = base_class 

476 

477 def iter_pages(self): 

478 """ 

479 Enumerate created pages. 

480 

481 @return iterator on tuple(name, content) 

482 """ 

483 for k, v in self.built_pages.items(): 

484 yield k, v.getvalue() 

485 

486 def create_translator(self, *args): 

487 """ 

488 Returns an instance of translator. 

489 This method returns an instance of ``default_translator_class`` by default. 

490 Users can replace the translator class with ``app.set_translator()`` API. 

491 """ 

492 translator_class = self.translator_class 

493 return translator_class(*args) 

494 

495 def _write_serial(self, docnames): 

496 """ 

497 Overwrites *_write_serial* to avoid writing on disk. 

498 """ 

499 from sphinx.util.logging import pending_warnings 

500 from sphinx.util import status_iterator 

501 with pending_warnings(): 

502 for docname in status_iterator(docnames, 'writing output... ', "darkgreen", 

503 len(docnames), self.app.verbosity): 

504 doctree = self.env.get_and_resolve_doctree(docname, self) 

505 self.write_doc_serialized(docname, doctree) 

506 self.write_doc(docname, doctree) 

507 

508 def _write_parallel(self, docnames, nproc): 

509 """ 

510 Not supported. 

511 """ 

512 raise NotImplementedError( 

513 "Use parallel=0 when creating the sphinx application.") 

514 

515 def assemble_doctree(self, *args, **kwargs): 

516 """ 

517 Overwrites *assemble_doctree* to control the doctree. 

518 """ 

519 from sphinx.util.nodes import inline_all_toctrees 

520 from sphinx.util.console import darkgreen 

521 master = self.config.master_doc 

522 if hasattr(self, "doctree_"): 

523 tree = self.doctree_ 

524 else: 

525 raise AttributeError( 

526 "Attribute 'doctree_' is not present. Call method finalize().") 

527 tree = inline_all_toctrees( 

528 self, set(), master, tree, darkgreen, [master]) 

529 tree['docname'] = master 

530 self.env.resolve_references(tree, master, self) 

531 self.fix_refuris(tree) 

532 return tree 

533 

534 def fix_refuris(self, tree): 

535 """ 

536 Overwrites *fix_refuris* to control the reference names. 

537 """ 

538 fname = "__" + self.config.master_doc + "__" 

539 for refnode in tree.traverse(nodes.reference): 

540 if 'refuri' not in refnode: 

541 continue 

542 refuri = refnode['refuri'] 

543 hashindex = refuri.find('#') 

544 if hashindex < 0: 

545 continue 

546 hashindex = refuri.find('#', hashindex + 1) 

547 if hashindex >= 0: 

548 refnode['refuri'] = fname + refuri[hashindex:] 

549 

550 def get_target_uri(self, docname, typ=None): 

551 """ 

552 Overwrites *get_target_uri* to control the page name. 

553 """ 

554 if docname in self.env.all_docs: 

555 # all references are on the same page... 

556 return self.config.master_doc + '#document-' + docname 

557 elif docname in ("genindex", "search"): 

558 return self.config.master_doc + '-#' + docname 

559 else: 

560 docs = ", ".join(sorted("'{0}'".format(_) 

561 for _ in self.env.all_docs)) 

562 raise ValueError( 

563 "docname='{0}' should be in 'self.env.all_docs' which contains:\n{1}".format(docname, docs)) 

564 

565 def get_outfilename(self, pagename): 

566 """ 

567 Overwrites *get_target_uri* to control file names. 

568 """ 

569 return "{0}/{1}.m.html".format(self.outdir, pagename).replace("\\", "/") 

570 

571 def handle_page(self, pagename, addctx, templatename='page.html', 

572 outfilename=None, event_arg=None): 

573 """ 

574 Overrides *handle_page* to write into stream instead of files. 

575 """ 

576 from sphinx.util.osutil import relative_uri 

577 ctx = self.globalcontext.copy() 

578 if hasattr(self, "warning"): 

579 ctx['warn'] = self.warning 

580 elif hasattr(self, "warn"): 

581 ctx['warn'] = self.warn 

582 # current_page_name is backwards compatibility 

583 ctx['pagename'] = ctx['current_page_name'] = pagename 

584 ctx['encoding'] = self.config.html_output_encoding 

585 default_baseuri = self.get_target_uri(pagename) 

586 # in the singlehtml builder, default_baseuri still contains an #anchor 

587 # part, which relative_uri doesn't really like... 

588 default_baseuri = default_baseuri.rsplit('#', 1)[0] 

589 

590 def pathto(otheruri, resource=False, baseuri=default_baseuri): 

591 if resource and '://' in otheruri: 

592 # allow non-local resources given by scheme 

593 return otheruri 

594 elif not resource: 

595 otheruri = self.get_target_uri(otheruri) 

596 uri = relative_uri(baseuri, otheruri) or '#' 

597 if uri == '#' and not self.allow_sharp_as_current_path: 

598 uri = baseuri 

599 return uri 

600 ctx['pathto'] = pathto 

601 

602 def css_tag(css): 

603 attrs = [] 

604 for key in sorted(css.attributes): 

605 value = css.attributes[key] 

606 if value is not None: 

607 attrs.append('%s="%s"' % (key, htmlescape( # pylint: disable=W1505 

608 value, True))) # pylint: disable=W1505 

609 attrs.append('href="%s"' % pathto(css.filename, resource=True)) 

610 return '<link %s />' % ' '.join(attrs) 

611 ctx['css_tag'] = css_tag 

612 

613 def hasdoc(name): 

614 if name in self.env.all_docs: 

615 return True 

616 elif name == 'search' and self.search: 

617 return True 

618 elif name == 'genindex' and self.get_builder_config('use_index', 'html'): 

619 return True 

620 return False 

621 ctx['hasdoc'] = hasdoc 

622 

623 ctx['toctree'] = lambda **kw: self._get_local_toctree(pagename, **kw) 

624 self.add_sidebars(pagename, ctx) 

625 ctx.update(addctx) 

626 

627 self.update_page_context(pagename, templatename, ctx, event_arg) 

628 newtmpl = self.app.emit_firstresult('html-page-context', pagename, 

629 templatename, ctx, event_arg) 

630 if newtmpl: 

631 templatename = newtmpl 

632 

633 try: 

634 output = self.templates.render(templatename, ctx) 

635 except UnicodeError: # pragma: no cover 

636 logger = getLogger("MockSphinxApp") 

637 logger.warning("[_CustomSphinx] A unicode error occurred when rendering the page %s. " 

638 "Please make sure all config values that contain " 

639 "non-ASCII content are Unicode strings.", pagename) 

640 return 

641 

642 if not outfilename: 

643 outfilename = self.get_outfilename(pagename) 

644 # outfilename's path is in general different from self.outdir 

645 # ensuredir(path.dirname(outfilename)) 

646 if outfilename not in self.built_pages: 

647 self.built_pages[outfilename] = StringIO() 

648 self.built_pages[outfilename].write(output) 

649 

650 

651class MemoryHTMLBuilder(_MemoryBuilder, CustomSingleFileHTMLBuilder): 

652 """ 

653 Builds :epkg:`HTML` output in memory. 

654 The API is defined by the page 

655 :epkg:`builderapi`. 

656 """ 

657 name = 'memoryhtml' 

658 format = 'html' 

659 out_suffix = None # ".memory.html" 

660 supported_image_types = ['application/pdf', 'image/png', 'image/jpeg'] 

661 default_translator_class = HTMLTranslatorWithCustomDirectives 

662 translator_class = HTMLTranslatorWithCustomDirectives 

663 _writer_class = HTMLWriterWithCustomDirectives 

664 supported_remote_images = True 

665 supported_data_uri_images = True 

666 html_scaled_image_link = True 

667 

668 def __init__(self, app): # pylint: disable=W0231 

669 """ 

670 Construct the builder. 

671 Most of the parameter are static members of the class and cannot 

672 be overwritten (yet). 

673 

674 :param app: :epkg:`Sphinx application` 

675 """ 

676 _MemoryBuilder._init(self, CustomSingleFileHTMLBuilder, app) 

677 

678 

679class MemoryRSTBuilder(_MemoryBuilder, RstBuilder): 

680 

681 """ 

682 Builds :epkg:`RST` output in memory. 

683 The API is defined by the page 

684 :epkg:`builderapi`. 

685 The writer simplifies the :epkg:`RST` syntax by replacing 

686 custom roles into true :epkg:`RST` syntax. 

687 """ 

688 

689 name = 'memoryrst' 

690 format = 'rst' 

691 out_suffix = None # ".memory.rst" 

692 supported_image_types = ['application/pdf', 'image/png', 'image/jpeg'] 

693 default_translator_class = RSTTranslatorWithCustomDirectives 

694 translator_class = RSTTranslatorWithCustomDirectives 

695 _writer_class = RSTWriterWithCustomDirectives 

696 supported_remote_images = True 

697 supported_data_uri_images = True 

698 html_scaled_image_link = True 

699 

700 def __init__(self, app): # pylint: disable=W0231 

701 """ 

702 Construct the builder. 

703 Most of the parameter are static members of the class and cannot 

704 be overwritten (yet). 

705 

706 :param app: :epkg:`Sphinx application` 

707 """ 

708 _MemoryBuilder._init(self, RstBuilder, app) 

709 

710 def handle_page(self, pagename, addctx, templatename=None, 

711 outfilename=None, event_arg=None): 

712 """ 

713 Override *handle_page* to write into stream instead of files. 

714 """ 

715 if templatename is not None: 

716 raise NotImplementedError( 

717 "templatename must be None.") # pragma: no cover 

718 if not outfilename: 

719 outfilename = self.get_outfilename(pagename) 

720 if outfilename not in self.built_pages: 

721 self.built_pages[outfilename] = StringIO() 

722 self.built_pages[outfilename].write(self.writer.output) 

723 

724 

725class MemoryMDBuilder(_MemoryBuilder, MdBuilder): 

726 """ 

727 Builds :epkg:`MD` output in memory. 

728 The API is defined by the page 

729 :epkg:`builderapi`. 

730 """ 

731 name = 'memorymd' 

732 format = 'md' 

733 out_suffix = None # ".memory.rst" 

734 supported_image_types = ['application/pdf', 'image/png', 'image/jpeg'] 

735 default_translator_class = MDTranslatorWithCustomDirectives 

736 translator_class = MDTranslatorWithCustomDirectives 

737 _writer_class = MDWriterWithCustomDirectives 

738 supported_remote_images = True 

739 supported_data_uri_images = True 

740 html_scaled_image_link = True 

741 

742 def __init__(self, app): # pylint: disable=W0231 

743 """ 

744 Construct the builder. 

745 Most of the parameter are static members of the class and cannot 

746 be overwritten (yet). 

747 

748 :param app: :epkg:`Sphinx application` 

749 """ 

750 _MemoryBuilder._init(self, MdBuilder, app) 

751 

752 def handle_page(self, pagename, addctx, templatename=None, 

753 outfilename=None, event_arg=None): 

754 """ 

755 Override *handle_page* to write into stream instead of files. 

756 """ 

757 if templatename is not None: 

758 raise NotImplementedError( 

759 "templatename must be None.") # pragma: no cover 

760 if not outfilename: 

761 outfilename = self.get_outfilename(pagename) 

762 if outfilename not in self.built_pages: 

763 self.built_pages[outfilename] = StringIO() 

764 self.built_pages[outfilename].write(self.writer.output) 

765 

766 

767class MemoryDocTreeBuilder(_MemoryBuilder, DocTreeBuilder): 

768 """ 

769 Builds doctree output in memory. 

770 The API is defined by the page 

771 :epkg:`builderapi`. 

772 """ 

773 name = 'memorydoctree' 

774 format = 'doctree' 

775 out_suffix = None # ".memory.rst" 

776 default_translator_class = DocTreeTranslatorWithCustomDirectives 

777 translator_class = DocTreeTranslatorWithCustomDirectives 

778 _writer_class = DocTreeWriterWithCustomDirectives 

779 supported_remote_images = True 

780 supported_data_uri_images = True 

781 html_scaled_image_link = True 

782 

783 def __init__(self, app): # pylint: disable=W0231 

784 """ 

785 Constructs the builder. 

786 Most of the parameter are static members of the class and cannot 

787 be overwritten (yet). 

788 

789 :param app: :epkg:`Sphinx application` 

790 """ 

791 _MemoryBuilder._init(self, DocTreeBuilder, app) 

792 

793 def handle_page(self, pagename, addctx, templatename=None, 

794 outfilename=None, event_arg=None): 

795 """ 

796 Override *handle_page* to write into stream instead of files. 

797 """ 

798 if templatename is not None: 

799 raise NotImplementedError( 

800 "templatename must be None.") # pragma: no cover 

801 if not outfilename: 

802 outfilename = self.get_outfilename(pagename) 

803 if outfilename not in self.built_pages: 

804 self.built_pages[outfilename] = StringIO() 

805 self.built_pages[outfilename].write(self.writer.output) 

806 

807 

808class MemoryLatexBuilder(_MemoryBuilder, EnhancedLaTeXBuilder): 

809 """ 

810 Builds :epkg:`Latex` output in memory. 

811 The API is defined by the page 

812 :epkg:`builderapi`. 

813 """ 

814 name = 'memorylatex' 

815 format = 'tex' 

816 out_suffix = None # ".memory.tex" 

817 supported_image_types = ['image/png', 'image/jpeg', 'image/gif'] 

818 default_translator_class = LatexTranslatorWithCustomDirectives 

819 translator_class = LatexTranslatorWithCustomDirectives 

820 _writer_class = LatexWriterWithCustomDirectives 

821 supported_remote_images = True 

822 supported_data_uri_images = True 

823 html_scaled_image_link = True 

824 

825 def __init__(self, app): # pylint: disable=W0231 

826 """ 

827 Constructs the builder. 

828 Most of the parameter are static members of the class and cannot 

829 be overwritten (yet). 

830 

831 :param app: :epkg:`Sphinx application` 

832 """ 

833 _MemoryBuilder._init(self, EnhancedLaTeXBuilder, app) 

834 

835 def write_stylesheet(self): 

836 from sphinx.highlighting import PygmentsBridge 

837 highlighter = PygmentsBridge('latex', self.config.pygments_style) 

838 rows = [] 

839 rows.append('\\NeedsTeXFormat{LaTeX2e}[1995/12/01]\n') 

840 rows.append('\\ProvidesPackage{sphinxhighlight}') 

841 rows.append( 

842 '[2016/05/29 stylesheet for highlighting with pygments]\n\n') 

843 rows.append(highlighter.get_stylesheet()) 

844 self.built_pages['sphinxhighlight.sty'] = StringIO() 

845 self.built_pages['sphinxhighlight.sty'].write("".join(rows)) 

846 

847 class EnhancedStringIO(StringIO): 

848 def write(self, content): 

849 if isinstance(content, str): 

850 StringIO.write(self, content) 

851 else: 

852 for line in content: 

853 StringIO.write(self, line) 

854 

855 def _get_filename(self, targetname, encoding='utf-8', overwrite_if_changed=True): 

856 if not isinstance(targetname, str): 

857 raise TypeError( # pragma: no cover 

858 "targetname must be a string: {0}".format(targetname)) 

859 destination = MemoryLatexBuilder.EnhancedStringIO() 

860 self.built_pages[targetname] = destination 

861 return destination 

862 

863 

864class _CustomBuildEnvironment(BuildEnvironment): 

865 """ 

866 Overrides some functionalities of 

867 `BuildEnvironment <https://www.sphinx-doc.org/en/master/extdev/envapi.html>`_. 

868 """ 

869 

870 def __init__(self, app): 

871 """ 

872 """ 

873 BuildEnvironment.__init__(self, app) 

874 self.doctree_ = {} 

875 

876 def get_doctree(self, docname): 

877 """Read the doctree for a file from the pickle and return it.""" 

878 if hasattr(self, "doctree_") and docname in self.doctree_: 

879 from sphinx.util.docutils import WarningStream 

880 doctree = self.doctree_[docname] 

881 doctree.settings.env = self 

882 doctree.reporter = Reporter(self.doc2path( 

883 docname), 2, 5, stream=WarningStream()) 

884 return doctree 

885 

886 if hasattr(self, "doctree_"): 

887 available = list(sorted(self.doctree_)) 

888 if len(available) > 10: 

889 available = available[10:] 

890 raise KeyError( 

891 "Unable to find entry '{}' (has doctree: {})\nFirst documents:\n{}" 

892 "".format( 

893 docname, hasattr(self, "doctree_"), 

894 "\n".join(available))) 

895 

896 raise KeyError( # pragma: no cover 

897 "Doctree empty or not found for '{}' (has doctree: {})" 

898 "".format( 

899 docname, hasattr(self, "doctree_"))) 

900 # return BuildEnvironment.get_doctree(self, docname) 

901 

902 def apply_post_transforms(self, doctree, docname): 

903 """Apply all post-transforms.""" 

904 # set env.docname during applying post-transforms 

905 self.temp_data['docname'] = docname 

906 

907 transformer = SphinxTransformer(doctree) 

908 transformer.set_environment(self) 

909 transformer.add_transforms(self.app.post_transforms) 

910 transformer.apply_transforms() 

911 self.temp_data.clear() 

912 

913 

914class _CustomSphinx(Sphinx): 

915 """ 

916 Custom :epkg:`Sphinx` application to avoid using disk. 

917 """ 

918 

919 def __init__(self, srcdir, confdir, outdir, doctreedir, buildername="memoryhtml", # pylint: disable=W0231 

920 confoverrides=None, status=None, warning=None, 

921 freshenv=False, warningiserror=False, 

922 tags=None, verbosity=0, parallel=0, keep_going=False, 

923 new_extensions=None): 

924 ''' 

925 Same constructor as :epkg:`Sphinx application`. 

926 Additional parameters: 

927 

928 @param new_extensions extensions to add to the application 

929 

930 Some insights about domains: 

931 

932 :: 

933 

934 {'cpp': sphinx.domains.cpp.CPPDomain, 

935 'hpp': sphinx.domains.cpp.CPPDomain, 

936 'h': sphinx.domains.cpp.CPPDomain, 

937 'js': sphinx.domains.javascript.JavaScriptDomain, 

938 'std': sphinx.domains.std.StandardDomain, 

939 'py': sphinx.domains.python.PythonDomain, 

940 'rst': sphinx.domains.rst.ReSTDomain, 

941 'c': sphinx.domains.c.CDomain} 

942 

943 And builders: 

944 

945 :: 

946 

947 {'epub': ('epub', 'EpubBuilder'), 

948 'singlehtml': ('html', 'SingleFileHTMLBuilder'), 

949 'qthelp': ('qthelp', 'QtHelpBuilder'), 

950 'epub3': ('epub3', 'Epub3Builder'), 

951 'man': ('manpage', 'ManualPageBuilder'), 

952 'dummy': ('dummy', 'DummyBuilder'), 

953 'json': ('html', 'JSONHTMLBuilder'), 

954 'html': ('html', 'StandaloneHTMLBuilder'), 

955 'xml': ('xml', 'XMLBuilder'), 

956 'texinfo': ('texinfo', 'TexinfoBuilder'), 

957 'devhelp': ('devhelp', 'DevhelpBuilder'), 

958 'web': ('html', 'PickleHTMLBuilder'), 

959 'pickle': ('html', 'PickleHTMLBuilder'), 

960 'htmlhelp': ('htmlhelp', 'HTMLHelpBuilder'), 

961 'applehelp': ('applehelp', 'AppleHelpBuilder'), 

962 'linkcheck': ('linkcheck', 'CheckExternalLinksBuilder'), 

963 'dirhtml': ('html', 'DirectoryHTMLBuilder'), 

964 'latex': ('latex', 'LaTeXBuilder'), 

965 'elatex': ('latex', 'EnchancedLaTeXBuilder'), 

966 'text': ('text', 'TextBuilder'), 

967 'changes': ('changes', 'ChangesBuilder'), 

968 'websupport': ('websupport', 'WebSupportBuilder'), 

969 'gettext': ('gettext', 'MessageCatalogBuilder'), 

970 'pseudoxml': ('xml', 'PseudoXMLBuilder')} 

971 'rst': ('rst', 'RstBuilder')} 

972 'md': ('md', 'MdBuilder'), 

973 'doctree': ('doctree', 'DocTreeBuilder')} 

974 ''' 

975 # own purpose (to monitor) 

976 self._logger = getLogger("_CustomSphinx") 

977 self._added_objects = [] 

978 self._added_collectors = [] 

979 

980 # from sphinx.domains.cpp import CPPDomain 

981 # from sphinx.domains.javascript import JavaScriptDomain 

982 # from sphinx.domains.python import PythonDomain 

983 # from sphinx.domains.std import StandardDomain 

984 # from sphinx.domains.rst import ReSTDomain 

985 # from sphinx.domains.c import CDomain 

986 

987 from sphinx.registry import SphinxComponentRegistry 

988 self.phase = BuildPhase.INITIALIZATION 

989 self.verbosity = verbosity 

990 self.extensions = {} 

991 self.builder = None 

992 self.env = None 

993 self.project = None 

994 self.registry = SphinxComponentRegistry() 

995 self.post_transforms = [] 

996 

997 if doctreedir is None: 

998 doctreedir = "IMPOSSIBLE:TOFIND" 

999 if srcdir is None: 

1000 srcdir = "IMPOSSIBLE:TOFIND" 

1001 update_docutils_languages() 

1002 

1003 self.srcdir = os.path.abspath(srcdir) 

1004 self.confdir = os.path.abspath( 

1005 confdir) if confdir is not None else None 

1006 self.outdir = os.path.abspath(outdir) if confdir is not None else None 

1007 self.doctreedir = os.path.abspath(doctreedir) 

1008 self.parallel = parallel 

1009 

1010 if self.srcdir == self.outdir: 

1011 raise ApplicationError('Source directory and destination ' # pragma: no cover 

1012 'directory cannot be identical') 

1013 

1014 if status is None: 

1015 self._status = StringIO() 

1016 self.quiet = True 

1017 else: 

1018 self._status = status 

1019 self.quiet = False 

1020 

1021 from sphinx.events import EventManager 

1022 # logging.setup(self, self._status, self._warning) 

1023 self.events = EventManager(self) 

1024 

1025 # keep last few messages for traceback 

1026 # This will be filled by sphinx.util.logging.LastMessagesWriter 

1027 self.messagelog = deque(maxlen=10) 

1028 

1029 # say hello to the world 

1030 from sphinx import __display_version__ 

1031 self.info('Running Sphinx v%s' % 

1032 __display_version__) # pragma: no cover 

1033 

1034 # notice for parallel build on macOS and py38+ 

1035 if sys.version_info > (3, 8) and platform.system() == 'Darwin' and parallel > 1: 

1036 self._logger.info( # pragma: no cover 

1037 "For security reason, parallel mode is disabled on macOS and " 

1038 "python3.8 and above. For more details, please read " 

1039 "https://github.com/sphinx-doc/sphinx/issues/6803") 

1040 

1041 # status code for command-line application 

1042 self.statuscode = 0 

1043 

1044 # delayed import to speed up time 

1045 from sphinx.application import builtin_extensions 

1046 try: 

1047 from sphinx.application import CONFIG_FILENAME, Config, Tags 

1048 sphinx_version = 2 # pragma: no cover 

1049 except ImportError: 

1050 # Sphinx 3.0.0 

1051 from sphinx.config import CONFIG_FILENAME, Config, Tags 

1052 sphinx_version = 3 

1053 

1054 # read config 

1055 self.tags = Tags(tags) 

1056 with warnings.catch_warnings(): 

1057 warnings.simplefilter( 

1058 "ignore", (DeprecationWarning, PendingDeprecationWarning)) 

1059 if self.confdir is None: 

1060 self.config = Config({}, confoverrides or {}) 

1061 else: # pragma: no cover 

1062 try: 

1063 self.config = Config.read( 

1064 self.confdir, confoverrides or {}, self.tags) 

1065 except AttributeError: 

1066 try: 

1067 self.config = Config( # pylint: disable=E1121 

1068 confdir, confoverrides or {}, self.tags) 

1069 except TypeError: 

1070 try: 

1071 self.config = Config(confdir, CONFIG_FILENAME, # pylint: disable=E1121 

1072 confoverrides or {}, self.tags) 

1073 except TypeError: 

1074 # Sphinx==3.0.0 

1075 self.config = Config({}, confoverrides or {}) 

1076 self.sphinx__display_version__ = __display_version__ 

1077 

1078 # create the environment 

1079 if sphinx_version == 2: # pragma: no cover 

1080 with warnings.catch_warnings(): 

1081 warnings.simplefilter( 

1082 "ignore", (DeprecationWarning, PendingDeprecationWarning, ImportWarning)) 

1083 self.config.check_unicode() 

1084 self.config.pre_init_values() 

1085 

1086 # set up translation infrastructure 

1087 self._init_i18n() 

1088 

1089 # check the Sphinx version if requested 

1090 if (self.config.needs_sphinx and self.config.needs_sphinx > 

1091 __display_version__): # pragma: no cover 

1092 from sphinx.locale import _ 

1093 from sphinx.application import VersionRequirementError 

1094 raise VersionRequirementError( 

1095 _('This project needs at least Sphinx v%s and therefore cannot ' 

1096 'be built with this version.') % self.config.needs_sphinx) 

1097 

1098 # set confdir to srcdir if -C given (!= no confdir); a few pieces 

1099 # of code expect a confdir to be set 

1100 if self.confdir is None: 

1101 self.confdir = self.srcdir 

1102 

1103 # load all built-in extension modules 

1104 for extension in builtin_extensions: 

1105 try: 

1106 with warnings.catch_warnings(): 

1107 warnings.filterwarnings( 

1108 "ignore", category=DeprecationWarning) 

1109 self.setup_extension(extension) 

1110 except Exception as e: # pragma: no cover 

1111 if 'sphinx.builders.applehelp' not in str(e): # pragma: no cover 

1112 mes = "Unable to run setup_extension '{0}'\nWHOLE LIST\n{1}".format( 

1113 extension, "\n".join(builtin_extensions)) 

1114 raise ExtensionError(mes) from e 

1115 

1116 # load all user-given extension modules 

1117 for extension in self.config.extensions: 

1118 self.setup_extension(extension) 

1119 

1120 # /1 addition to the original code 

1121 # additional extensions 

1122 if new_extensions: 

1123 for extension in new_extensions: 

1124 if isinstance(extension, str): 

1125 self.setup_extension(extension) 

1126 else: 

1127 # We assume it is a module. 

1128 dirname = os.path.dirname(extension.__file__) 

1129 sys.path.insert(0, dirname) 

1130 self.setup_extension(extension.__name__) 

1131 del sys.path[0] 

1132 

1133 # add default HTML builders 

1134 self.add_builder(MemoryHTMLBuilder) 

1135 self.add_builder(MemoryRSTBuilder) 

1136 self.add_builder(MemoryMDBuilder) 

1137 self.add_builder(MemoryLatexBuilder) 

1138 self.add_builder(MemoryDocTreeBuilder) 

1139 

1140 if isinstance(buildername, tuple): 

1141 if len(buildername) != 2: 

1142 raise ValueError( 

1143 "The builder can be custom but it must be specifed as a 2-uple=(builder_name, builder_class).") 

1144 self.add_builder(buildername[1]) 

1145 buildername = buildername[0] 

1146 

1147 # /1 end of addition 

1148 

1149 # preload builder module (before init config values) 

1150 self.preload_builder(buildername) 

1151 

1152 # the config file itself can be an extension 

1153 if self.config.setup: 

1154 prefix = 'while setting up extension %s:' % "conf.py" 

1155 if prefixed_warnings is not None: 

1156 with prefixed_warnings(prefix): 

1157 if callable(self.config.setup): 

1158 self.config.setup(self) 

1159 else: # pragma: no cover 

1160 from sphinx.locale import _ 

1161 from sphinx.application import ConfigError 

1162 raise ConfigError( 

1163 _("'setup' as currently defined in conf.py isn't a Python callable. " 

1164 "Please modify its definition to make it a callable function. This is " 

1165 "needed for conf.py to behave as a Sphinx extension.") 

1166 ) 

1167 elif callable(self.config.setup): 

1168 self.config.setup(self) 

1169 

1170 # now that we know all config values, collect them from conf.py 

1171 noallowed = [] 

1172 rem = [] 

1173 for k in confoverrides: 

1174 if k in {'initial_header_level', 'doctitle_xform', 'input_encoding', 

1175 'outdir', 'warnings_log', 'extensions'}: 

1176 continue 

1177 if k == 'override_image_directive': 

1178 self.config.images_config["override_image_directive"] = True 

1179 rem.append(k) 

1180 continue 

1181 if k not in self.config.values: 

1182 noallowed.append(k) 

1183 for k in rem: 

1184 del confoverrides[k] 

1185 if len(noallowed) > 0: 

1186 raise ValueError( 

1187 "The following configuration values are declared in any extension.\n--???--\n" 

1188 "{0}\n--DECLARED--\n{1}".format( 

1189 "\n".join(sorted(noallowed)), 

1190 "\n".join(sorted(self.config.values)))) 

1191 

1192 # now that we know all config values, collect them from conf.py 

1193 self.config.init_values() 

1194 self.events.emit('config-inited', self.config) 

1195 

1196 # /2 addition to the original code 

1197 # check extension versions if requested 

1198 # self.config.needs_extensions = self.config.extensions 

1199 if not hasattr(self.config, 'items'): 

1200 

1201 def _citems(): 

1202 for k, v in self.config.values.items(): 

1203 yield k, v 

1204 

1205 self.config.items = _citems 

1206 

1207 # /2 end of addition 

1208 

1209 # create the project 

1210 self.project = Project(self.srcdir, self.config.source_suffix) 

1211 # create the builder, initializes _MemoryBuilder 

1212 self.builder = self.create_builder(buildername) 

1213 # set up the build environment 

1214 self._init_env(freshenv) 

1215 # set up the builder 

1216 self._init_builder() 

1217 

1218 if not isinstance(self.env, _CustomBuildEnvironment): 

1219 raise TypeError( # pragma: no cover 

1220 "self.env is not _CustomBuildEnvironment: '{0}' buildername='{1}'".format(type(self.env), buildername)) 

1221 

1222 # addition 

1223 self._extended_init_() 

1224 

1225 # verification 

1226 self._check_init_() 

1227 

1228 def _check_init_(self): 

1229 pass 

1230 

1231 def _init_env(self, freshenv): 

1232 ENV_PICKLE_FILENAME = 'environment.pickle' 

1233 filename = os.path.join(self.doctreedir, ENV_PICKLE_FILENAME) 

1234 if freshenv or not os.path.exists(filename): 

1235 self.env = _CustomBuildEnvironment(self) 

1236 self.env.setup(self) 

1237 if self.srcdir is not None and self.srcdir != "IMPOSSIBLE:TOFIND": 

1238 self.env.find_files(self.config, self.builder) 

1239 elif "IMPOSSIBLE:TOFIND" not in self.doctreedir: # pragma: no cover 

1240 from sphinx.application import ENV_PICKLE_FILENAME 

1241 filename = os.path.join(self.doctreedir, ENV_PICKLE_FILENAME) 

1242 try: 

1243 self.info('loading pickled environment... ', nonl=True) 

1244 with open(filename, 'rb') as f: 

1245 self.env = pickle.load(f) 

1246 self.env.setup(self) 

1247 self.info('done') 

1248 except Exception as err: 

1249 self.info('failed: %s' % err) 

1250 self._init_env(freshenv=True) 

1251 elif self.env is None: 

1252 self.env = _CustomBuildEnvironment(self) 

1253 if hasattr(self.env, 'setup'): 

1254 self.env.setup(self) 

1255 if not hasattr(self.env, 'project') or self.env.project is None: 

1256 raise AttributeError( # pragma: no cover 

1257 "self.env.project is not initialized.") 

1258 

1259 def create_builder(self, name): 

1260 """ 

1261 Creates a builder, raises an exception if name is None. 

1262 """ 

1263 if name is None: 

1264 raise ValueError( # pragma: no cover 

1265 "Builder name cannot be None") 

1266 

1267 return self.registry.create_builder(self, name) 

1268 

1269 def _extended_init_(self): 

1270 """ 

1271 Additional initialization steps. 

1272 """ 

1273 if not hasattr(self, "domains"): 

1274 self.domains = {} 

1275 if not hasattr(self, "_events"): 

1276 self._events = {} 

1277 

1278 # Otherwise, role issue is missing. 

1279 setup_link_roles(self) 

1280 

1281 def _lookup_doctree(self, doctree, node_type): 

1282 for node in doctree.traverse(node_type): 

1283 yield node 

1284 

1285 def _add_missing_ids(self, doctree): 

1286 for i, node in enumerate(self._lookup_doctree(doctree, None)): 

1287 stype = str(type(node)) 

1288 if ('section' not in stype and 'title' not in stype and 

1289 'reference' not in stype): 

1290 continue 

1291 try: 

1292 node['ids'][0] 

1293 except IndexError: 

1294 node['ids'] = ['missing%d' % i] 

1295 except TypeError: # pragma: no cover 

1296 pass 

1297 

1298 def finalize(self, doctree, external_docnames=None): 

1299 """ 

1300 Finalizes the documentation after it was parsed. 

1301 

1302 @param doctree doctree (or pub.document), available after publication 

1303 @param external_docnames other docnames the doctree references 

1304 """ 

1305 imgs = list(self._lookup_doctree(doctree, nodes.image)) 

1306 for img in imgs: 

1307 img['save_uri'] = img['uri'] 

1308 

1309 if not isinstance(self.env, _CustomBuildEnvironment): 

1310 raise TypeError( # pragma: no cover 

1311 "self.env is not _CustomBuildEnvironment: '{0}'".format(type(self.env))) 

1312 if not isinstance(self.builder.env, _CustomBuildEnvironment): 

1313 raise TypeError( # pragma: no cover 

1314 "self.builder.env is not _CustomBuildEnvironment: '{0}'".format( 

1315 type(self.builder.env))) 

1316 self.doctree_ = doctree 

1317 self.builder.doctree_ = doctree 

1318 self.env.doctree_[self.config.master_doc] = doctree 

1319 self.env.all_docs = {self.config.master_doc: self.config.master_doc} 

1320 

1321 if external_docnames: 

1322 for doc in external_docnames: 

1323 self.env.all_docs[doc] = doc 

1324 

1325 # This steps goes through many function including one 

1326 # modifying paths in image node. 

1327 # Look for node['candidates'] = candidates in Sphinx code. 

1328 # If a path startswith('/'), it is removed. 

1329 from sphinx.environment.collectors.asset import logger as logger_asset 

1330 logger_asset.setLevel(40) # only errors 

1331 self._add_missing_ids(doctree) 

1332 self.events.emit('doctree-read', doctree) 

1333 logger_asset.setLevel(30) # back to warnings 

1334 

1335 for img in imgs: 

1336 img['uri'] = img['save_uri'] 

1337 

1338 self.events.emit('doctree-resolved', doctree, 

1339 self.config.master_doc) 

1340 self.builder.write(None, None, 'all') 

1341 

1342 def debug(self, message, *args, **kwargs): 

1343 self._logger.debug(message, *args, **kwargs) 

1344 

1345 def info(self, message='', nonl=False): 

1346 self._logger.info(message, nonl=nonl) 

1347 

1348 def warning(self, message='', nonl=False, name=None, type=None, subtype=None): 

1349 if "is already registered" not in message: 

1350 self._logger.warning( 

1351 "[_CustomSphinx] {0} -- {1}".format(message, name), nonl=nonl, type=type, subtype=subtype) 

1352 

1353 def add_builder(self, builder, override=False): 

1354 self._added_objects.append(('builder', builder)) 

1355 if builder.name not in self.registry.builders: 

1356 self.debug('[_CustomSphinx] adding builder: %r', builder) 

1357 try: 

1358 # Sphinx >= 1.8 

1359 self.registry.add_builder(builder, override=override) 

1360 except TypeError: # pragma: no cover 

1361 # Sphinx < 1.8 

1362 self.registry.add_builder(builder) 

1363 else: 

1364 self.debug('[_CustomSphinx] already added builder: %r', builder) 

1365 

1366 def setup_extension(self, extname): 

1367 self._added_objects.append(('extension', extname)) 

1368 

1369 logger = getLogger('sphinx.application') 

1370 disa = logger.logger.disabled 

1371 logger.logger.disabled = True 

1372 

1373 # delayed import to speed up time 

1374 try: 

1375 with warnings.catch_warnings(): 

1376 warnings.filterwarnings( 

1377 "ignore", category=DeprecationWarning) 

1378 self.registry.load_extension(self, extname) 

1379 except Exception as e: 

1380 raise ExtensionError( 

1381 "Unable to setup extension '{0}'".format(extname)) from e 

1382 finally: 

1383 logger.logger = disa 

1384 

1385 def add_directive(self, name, obj, content=None, arguments=None, # pylint: disable=W0221 

1386 override=True, **options): 

1387 self._added_objects.append(('directive', name)) 

1388 if name == 'plot' and obj.__name__ == 'PlotDirective': 

1389 

1390 old_run = obj.run 

1391 

1392 def run(self): 

1393 """Run the plot directive.""" 

1394 logger = getLogger("MockSphinxApp") 

1395 logger.info( 

1396 '[MockSphinxApp] PlotDirective: {}'.format(self.content)) 

1397 try: 

1398 res = old_run(self) 

1399 logger.info( 

1400 '[MockSphinxApp] PlotDirective ok') 

1401 return res 

1402 except OSError as e: # pragma: no cover 

1403 logger = getLogger("MockSphinxApp") 

1404 logger.info( 

1405 '[MockSphinxApp] PlotDirective failed: {}'.format(e)) 

1406 return [] 

1407 

1408 obj.run = run 

1409 

1410 try: 

1411 # Sphinx >= 1.8 

1412 Sphinx.add_directive(self, name, obj, content=content, # pylint: disable=E1123 

1413 arguments=arguments, 

1414 override=override, **options) 

1415 except TypeError: 

1416 # Sphinx >= 3.0.0 

1417 Sphinx.add_directive(self, name, obj, override=override, **options) 

1418 except ExtensionError: # pragma: no cover 

1419 # Sphinx < 1.8 

1420 Sphinx.add_directive(self, name, obj, content=content, # pylint: disable=E1123 

1421 arguments=arguments, **options) 

1422 

1423 def add_domain(self, domain, override=True): 

1424 self._added_objects.append(('domain', domain)) 

1425 try: 

1426 # Sphinx >= 1.8 

1427 Sphinx.add_domain(self, domain, override=override) 

1428 except TypeError: # pragma: no cover 

1429 # Sphinx < 1.8 

1430 Sphinx.add_domain(self, domain) 

1431 # For some reason, the directives are missing from the main catalog 

1432 # in docutils. 

1433 for k, v in domain.directives.items(): 

1434 self.add_directive("{0}:{1}".format(domain.name, k), v) 

1435 if domain.name in ('py', 'std', 'rst'): 

1436 # We add the directive without the domain name as a prefix. 

1437 self.add_directive(k, v) 

1438 for k, v in domain.roles.items(): 

1439 self.add_role("{0}:{1}".format(domain.name, k), v) 

1440 if domain.name in ('py', 'std', 'rst'): 

1441 # We add the role without the domain name as a prefix. 

1442 self.add_role(k, v) 

1443 

1444 def override_domain(self, domain): 

1445 self._added_objects.append(('domain-over', domain)) 

1446 try: 

1447 Sphinx.override_domain(self, domain) 

1448 except AttributeError: 

1449 # Sphinx==3.0.0 

1450 raise AttributeError( 

1451 "override_domain not available in sphinx==3.0.0") 

1452 

1453 def add_role(self, name, role, override=True): 

1454 self._added_objects.append(('role', name)) 

1455 self.debug('[_CustomSphinx] adding role: %r', (name, role)) 

1456 roles.register_local_role(name, role) 

1457 

1458 def add_generic_role(self, name, nodeclass, override=True): 

1459 self._added_objects.append(('generic_role', name)) 

1460 self.debug("[_CustomSphinx] adding generic role: '%r'", 

1461 (name, nodeclass)) 

1462 role = roles.GenericRole(name, nodeclass) 

1463 roles.register_local_role(name, role) 

1464 

1465 def add_node(self, node, override=True, **kwds): 

1466 self._added_objects.append(('node', node)) 

1467 self.debug('[_CustomSphinx] adding node: %r', (node, kwds)) 

1468 nodes._add_node_class_names([node.__name__]) 

1469 for key, val in kwds.items(): 

1470 try: 

1471 visit, depart = val 

1472 except ValueError: # pragma: no cover 

1473 raise ExtensionError(("Value for key '%r' must be a " 

1474 "(visit, depart) function tuple") % key) 

1475 translator = self.registry.translators.get(key) 

1476 translators = [] 

1477 if translator is not None: 

1478 translators.append(translator) 

1479 elif key == 'html': 

1480 from sphinx.writers.html import HTMLTranslator 

1481 translators.append(HTMLTranslator) 

1482 if is_html5_writer_available(): 

1483 from sphinx.writers.html5 import HTML5Translator 

1484 translators.append(HTML5Translator) 

1485 elif key == 'latex': 

1486 translators.append(_get_LaTeXTranslator()) 

1487 elif key == 'elatex': 

1488 translators.append(EnhancedLaTeXBuilder) 

1489 elif key == 'text': 

1490 from sphinx.writers.text import TextTranslator 

1491 translators.append(TextTranslator) 

1492 elif key == 'man': 

1493 from sphinx.writers.manpage import ManualPageTranslator 

1494 translators.append(ManualPageTranslator) 

1495 elif key == 'texinfo': 

1496 from sphinx.writers.texinfo import TexinfoTranslator 

1497 translators.append(TexinfoTranslator) 

1498 

1499 for translator in translators: 

1500 setattr(translator, 'visit_' + node.__name__, visit) 

1501 if depart: 

1502 setattr(translator, 'depart_' + node.__name__, depart) 

1503 

1504 def add_event(self, name): 

1505 self._added_objects.append(('event', name)) 

1506 Sphinx.add_event(self, name) 

1507 

1508 def add_config_value(self, name, default, rebuild, types_=()): # pylint: disable=W0221,W0237 

1509 self._added_objects.append(('config_value', name)) 

1510 Sphinx.add_config_value(self, name, default, rebuild, types_) 

1511 

1512 def add_directive_to_domain(self, domain, name, obj, has_content=None, # pylint: disable=W0221 

1513 argument_spec=None, override=False, **option_spec): 

1514 self._added_objects.append(('directive_to_domain', domain, name)) 

1515 try: 

1516 Sphinx.add_directive_to_domain(self, domain, name, obj, # pylint: disable=E1123 

1517 has_content=has_content, argument_spec=argument_spec, 

1518 override=override, **option_spec) 

1519 except TypeError: # pragma: no cover 

1520 # Sphinx==3.0.0 

1521 Sphinx.add_directive_to_domain(self, domain, name, obj, 

1522 override=override, **option_spec) 

1523 

1524 def add_role_to_domain(self, domain, name, role, override=False): 

1525 self._added_objects.append(('roles_to_domain', domain, name)) 

1526 Sphinx.add_role_to_domain(self, domain, name, role, override=override) 

1527 

1528 def add_transform(self, transform): 

1529 self._added_objects.append(('transform', transform)) 

1530 Sphinx.add_transform(self, transform) 

1531 

1532 def add_post_transform(self, transform): 

1533 self._added_objects.append(('post_transform', transform)) 

1534 Sphinx.add_post_transform(self, transform) 

1535 

1536 def add_js_file(self, filename, priority=500, **kwargs): 

1537 self._added_objects.append(('js', filename)) 

1538 Sphinx.add_js_file(self, filename, priority=priority, **kwargs) 

1539 

1540 def add_css_file(self, filename, priority=500, **kwargs): 

1541 self._added_objects.append(('css', filename)) 

1542 Sphinx.add_css_file(self, filename, priority=priority, **kwargs) 

1543 

1544 def add_latex_package(self, packagename, options=None, after_hyperref=False): 

1545 self._added_objects.append(('latex', packagename)) 

1546 Sphinx.add_latex_package( 

1547 self, packagename=packagename, options=options, 

1548 after_hyperref=after_hyperref) 

1549 

1550 def add_object_type(self, directivename, rolename, indextemplate='', 

1551 parse_node=None, ref_nodeclass=None, objname='', 

1552 doc_field_types=None, override=False): 

1553 if doc_field_types is None: 

1554 doc_field_types = [] 

1555 self._added_objects.append(('object', directivename, rolename)) 

1556 Sphinx.add_object_type(self, directivename, rolename, indextemplate=indextemplate, 

1557 parse_node=parse_node, ref_nodeclass=ref_nodeclass, 

1558 objname=objname, doc_field_types=doc_field_types, 

1559 override=override) 

1560 

1561 def add_env_collector(self, collector): 

1562 """ 

1563 See :epkg:`class Sphinx`. 

1564 """ 

1565 self.debug( 

1566 '[_CustomSphinx] adding environment collector: %r', collector) 

1567 coll = collector() 

1568 coll.enable(self) 

1569 self._added_collectors.append(coll) 

1570 

1571 def disconnect_env_collector(self, clname, exc=True): 

1572 """ 

1573 Disables a collector given its class name. 

1574 

1575 @param cl name 

1576 @param exc raises an exception if not found 

1577 @return found collector 

1578 """ 

1579 found = None 

1580 foundi = None 

1581 for i, co in enumerate(self._added_collectors): 

1582 if clname == co.__class__.__name__: 

1583 found = co 

1584 foundi = i 

1585 break 

1586 if found is not None and not exc: 

1587 return None 

1588 if found is None: 

1589 raise ValueError( # pragma: no cover 

1590 "Unable to find a collector '{0}' in \n{1}".format( 

1591 clname, "\n".join( 

1592 map(lambda x: x.__class__.__name__, 

1593 self._added_collectors)))) 

1594 for v in found.listener_ids.values(): 

1595 self.disconnect(v) 

1596 del self._added_collectors[foundi] 

1597 return found