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# -*- coding: utf-8 -*- 

2""" 

3@file 

4@brief Defines runpython directives. 

5See `Tutorial: Writing a simple extension 

6<https://www.sphinx-doc.org/en/master/development/tutorials/helloworld.html>`_ 

7""" 

8import sys 

9import os 

10from contextlib import redirect_stdout, redirect_stderr 

11import traceback 

12import warnings 

13from io import StringIO 

14import sphinx 

15from docutils import nodes, core 

16from docutils.parsers.rst import Directive, directives 

17from docutils.statemachine import StringList 

18from sphinx.util.nodes import nested_parse_with_titles 

19from ..loghelper.flog import run_cmd 

20from ..texthelper.texts_language import TITLES 

21from ..pycode.code_helper import remove_extra_spaces_and_pep8 

22from .sphinx_collapse_extension import collapse_node 

23 

24 

25class RunPythonCompileError(Exception): 

26 """ 

27 exception raised when a piece of code 

28 included in the documentation does not compile 

29 """ 

30 pass 

31 

32 

33class RunPythonExecutionError(Exception): 

34 """ 

35 Exception raised when a piece of code 

36 included in the documentation raises an exception. 

37 """ 

38 pass 

39 

40 

41def run_python_script(script, params=None, comment=None, setsysvar=None, process=False, 

42 exception=False, warningout=None, chdir=None, context=None, 

43 store_in_file=None): 

44 """ 

45 Executes a script :epkg:`python` as a string. 

46 

47 @param script python script 

48 @param params params to add before the execution 

49 @param comment message to add in a exception when the script fails 

50 @param setsysvar if not None, add a member to module *sys*, 

51 set up this variable to True, 

52 if is remove after the execution 

53 @param process run the script in a separate process 

54 @param exception expects an exception to be raised, 

55 fails if it is not, the function returns no output and the 

56 error message 

57 @param warningout warning to disable (name of warnings) 

58 @param chdir change directory before running this script (if not None) 

59 @param context if not None, added to the local context 

60 @param store_in_file stores the script into this file 

61 and calls tells python the source can be found here, 

62 that is useful is the script is using module 

63 ``inspect`` to retrieve the source which are not 

64 stored in memory 

65 @return stdout, stderr, context 

66 

67 If the execution throws an exception such as 

68 ``NameError: name 'math' is not defined`` after importing 

69 the module ``math``. It comes from the fact 

70 the domain name used by the function 

71 `exec <https://docs.python.org/3/library/functions.html#exec>`_ 

72 contains the declared objects. Example: 

73 

74 :: 

75 

76 import math 

77 def coordonnees_polaires(x,y): 

78 rho = math.sqrt(x*x+y*y) 

79 theta = math.atan2 (y,x) 

80 return rho, theta 

81 coordonnees_polaires(1, 1) 

82 

83 The code can be modified into: 

84 

85 :: 

86 

87 def fake_function(): 

88 import math 

89 def coordonnees_polaires(x,y): 

90 rho = math.sqrt(x*x+y*y) 

91 theta = math.atan2 (y,x) 

92 return rho, theta 

93 coordonnees_polaires(1, 1) 

94 fake_function() 

95 

96 Section :ref:`l-image-rst-runpython` explains 

97 how to display an image with this directive. 

98 

99 .. versionchanged:: 1.9 

100 Parameter *store_in_file* was added. 

101 """ 

102 def warning_filter(warningout): 

103 if warningout in (None, ''): 

104 warnings.simplefilter("always") 

105 elif isinstance(warningout, str): 

106 li = [_.strip() for _ in warningout.split()] 

107 warning_filter(li) 

108 elif isinstance(warningout, list): 

109 def interpret(s): 

110 return eval(s) if isinstance(s, str) else s 

111 warns = [interpret(w) for w in warningout] 

112 for w in warns: 

113 warnings.simplefilter("ignore", w) 

114 else: 

115 raise ValueError( 

116 "Unexpected value for warningout: {0}".format(warningout)) 

117 

118 if params is None: 

119 params = {} 

120 

121 if process: 

122 if context is not None and len(context) != 0: 

123 raise RunPythonExecutionError( 

124 "context cannot be used if the script runs in a separate process.") 

125 

126 cmd = sys.executable 

127 header = ["# coding: utf-8", "import sys"] 

128 if setsysvar: 

129 header.append("sys.{0} = True".format(setsysvar)) 

130 add = 0 

131 for path in sys.path: 

132 if path.endswith("source") or path.endswith("source/") or path.endswith("source\\"): 

133 header.append("sys.path.append('{0}')".format( 

134 path.replace("\\", "\\\\"))) 

135 add += 1 

136 if add == 0: 

137 for path in sys.path: 

138 if path.endswith("src") or path.endswith("src/") or path.endswith("src\\"): 

139 header.append("sys.path.append('{0}')".format( 

140 path.replace("\\", "\\\\"))) 

141 add += 1 

142 if add == 0: 

143 # It did not find any path linked to the copy of 

144 # the current module in the documentation 

145 # it assumes the first path of `sys.path` is part 

146 # of the unit test. 

147 path = sys.path[0] 

148 path = os.path.join(path, "..", "..", "src") 

149 if os.path.exists(path): 

150 header.append("sys.path.append('{0}')".format( 

151 path.replace("\\", "\\\\"))) 

152 add += 1 

153 else: 

154 path = sys.path[0] 

155 path = os.path.join(path, "src") 

156 if os.path.exists(path): 

157 header.append("sys.path.append('{0}')".format( 

158 path.replace("\\", "\\\\"))) 

159 add += 1 

160 

161 if add == 0: 

162 # We do nothing unless the execution failed. 

163 exc_path = RunPythonExecutionError( 

164 "Unable to find a path to add:\n{0}".format("\n".join(sys.path))) 

165 else: 

166 exc_path = None 

167 header.append('') 

168 script = "\n".join(header) + script 

169 

170 if store_in_file is not None: 

171 with open(store_in_file, "w", encoding="utf-8") as f: 

172 f.write(script) 

173 script_arg = None 

174 cmd += ' ' + store_in_file 

175 else: 

176 script_arg = script 

177 

178 try: 

179 out, err = run_cmd(cmd, script_arg, wait=True, change_path=chdir) 

180 return out, err, None 

181 except Exception as ee: 

182 if not exception: 

183 message = ("--SCRIPT--\n{0}\n--PARAMS--\n{1}\n--COMMENT--\n" 

184 "{2}\n--ERR--\n{3}\n--OUT--\n{4}\n--EXC--\n{5}" 

185 "").format(script, params, comment, "", 

186 str(ee), ee) 

187 if exc_path: 

188 message += "\n---EXC--\n{0}".format(exc_path) 

189 raise RunPythonExecutionError(message) from ee 

190 return str(ee), str(ee), None 

191 else: 

192 if store_in_file: 

193 raise NotImplementedError( 

194 "store_in_file is only implemented if process is True.") 

195 try: 

196 obj = compile(script, "", "exec") 

197 except Exception as ec: # pragma: no cover 

198 if comment is None: 

199 comment = "" 

200 if not exception: 

201 message = "SCRIPT:\n{0}\nPARAMS\n{1}\nCOMMENT\n{2}".format( 

202 script, params, comment) 

203 raise RunPythonCompileError(message) from ec 

204 return "", "Cannot compile the do to {0}".format(ec), None 

205 

206 globs = globals().copy() 

207 loc = locals() 

208 for k, v in params.items(): 

209 loc[k] = v 

210 loc["__dict__"] = params 

211 if context is not None: 

212 for k, v in context.items(): 

213 globs["__runpython__" + k] = v 

214 globs['__runpython__script__'] = script 

215 

216 if setsysvar is not None: 

217 sys.__dict__[setsysvar] = True 

218 

219 sout = StringIO() 

220 serr = StringIO() 

221 with redirect_stdout(sout): 

222 with redirect_stderr(sout): 

223 

224 with warnings.catch_warnings(): 

225 warning_filter(warningout) 

226 

227 if chdir is not None: 

228 current = os.getcwd() 

229 os.chdir(chdir) 

230 

231 try: 

232 exec(obj, globs, loc) 

233 except Exception as ee: 

234 if chdir is not None: 

235 os.chdir(current) 

236 if setsysvar is not None: 

237 del sys.__dict__[setsysvar] 

238 if comment is None: 

239 comment = "" 

240 gout = sout.getvalue() 

241 gerr = serr.getvalue() 

242 

243 excs = traceback.format_exc() 

244 lines = excs.split("\n") 

245 excs = "\n".join( 

246 _ for _ in lines if "sphinx_runpython_extension.py" not in _) 

247 

248 if not exception: 

249 message = ("--SCRIPT--\n{0}\n--PARAMS--\n{1}\n--COMMENT--" 

250 "\n{2}\n--ERR--\n{3}\n--OUT--\n{4}\n--EXC--" 

251 "\n{5}\n--TRACEBACK--\n{6}").format( 

252 script, params, comment, gout, gerr, 

253 ee, excs) 

254 raise RunPythonExecutionError(message) from ee 

255 return (gout + "\n" + gerr), (gerr + "\n" + excs), None 

256 

257 if chdir is not None: 

258 os.chdir(current) 

259 

260 if setsysvar is not None: 

261 del sys.__dict__[setsysvar] 

262 

263 gout = sout.getvalue() 

264 gerr = serr.getvalue() 

265 avoid = {"__runpython____WD__", 

266 "__runpython____k__", "__runpython____w__"} 

267 context = {k[13:]: v for k, v in globs.items() if k.startswith( 

268 "__runpython__") and k not in avoid} 

269 return gout, gerr, context 

270 

271 

272class runpython_node(nodes.Structural, nodes.Element): 

273 

274 """ 

275 Defines *runpython* node. 

276 """ 

277 pass 

278 

279 

280class RunPythonDirective(Directive): 

281 

282 """ 

283 Extracts script to run described by ``.. runpython::`` 

284 and modifies the documentation. 

285 

286 .. exref:: 

287 :title: A python script which generates documentation 

288 

289 The following code prints the version of Python 

290 on the standard output. It is added to the documentation:: 

291 

292 .. runpython:: 

293 :showcode: 

294 

295 import sys 

296 print("sys.version_info=", str(sys.version_info)) 

297 

298 If give the following results: 

299 

300 .. runpython:: 

301 

302 import sys 

303 print("sys.version_info=", str(sys.version_info)) 

304 

305 Options *showcode* can be used to display the code. 

306 The option *rst* will assume the output is in RST format and must be 

307 interpreted. *showout* will complement the RST output with the raw format. 

308 

309 The directive has a couple of options: 

310 

311 * ``:assert:`` condition to validate at the end of the execution 

312 to check it went right 

313 * ``:current:`` runs the script in the source file directory 

314 * ``:exception:`` the code throws an exception but it is expected. The error is displayed. 

315 * ``:indent:<int>`` to indent the output 

316 * ``:language:``: changes ``::`` into ``.. code-block:: language`` 

317 * ``:linenos:`` to show line numbers 

318 * ``:nopep8:`` if present, leaves the code as it is and does not apply pep8 by default, 

319 see @see fn remove_extra_spaces_and_pep8. 

320 * ``:numpy_precision: <precision>``, run ``numpy.set_printoptions(precision=...)``, 

321 precision is 3 by default 

322 * ``:process:`` run the script in an another process 

323 * ``:restore:`` restore the local context stored in :epkg:`sphinx` application 

324 by the previous call to *runpython* 

325 * ``:rst:`` to interpret the output, otherwise, it is considered as raw text 

326 * ``:setsysvar:`` adds a member to *sys* module, the module can act differently based on that information, 

327 if the value is left empty, *sys.enable_disabled_documented_pieces_of_code* will be be set up to *True*. 

328 * ``:showcode:`` to show the code before its output 

329 * ``:showout`` if *:rst:* is set up, this flag adds the raw rst output to check what is happening 

330 * ``:sin:<text_for_in>`` which text to display before the code (by default *In*) 

331 * ``:sout:<text_for_in>`` which text to display before the output (by default *Out*) 

332 * ``:sphinx:`` by default, function `nested_parse_with_titles 

333 <https://www.sphinx-doc.org/en/master/extdev/markupapi.html?highlight=nested_parse#parsing-directive-content-as-rest>`_ is 

334 used to parse the output of the script, if this option is set to false, 

335 `public_doctree <http://code.nabla.net/doc/docutils/api/docutils/core/docutils.core.publish_doctree.html>`_. 

336 * ``:store:`` stores the local context in :epkg:`sphinx` application to restore it later 

337 by another call to *runpython* 

338 * ``:toggle:`` add a button to hide or show the code, it takes the values 

339 ``code`` or ``out`` or ``both``. The direction then hides the given section 

340 but adds a button to show it. 

341 * ``:warningout:`` name of warnings to disable (ex: ``ImportWarning``), 

342 separated by spaces 

343 * ``:store_in_file:`` the directive store the script in a file, 

344 then executes this file (only if ``:process:`` is enabled), 

345 this trick is needed when the script to executes relies on 

346 function such :epkg:`*py:inspect:getsource` which requires 

347 the script to be stored somewhere in order to retrieve it. 

348 

349 Option *rst* can be used the following way:: 

350 

351 .. runpython:: 

352 :rst: 

353 

354 for l in range(0,10): 

355 print("**line**", "*" +str(l)+"*") 

356 print('') 

357 

358 Which displays interpreted :epkg:`RST`: 

359 

360 .. runpython:: 

361 :rst: 

362 

363 for l in range(0,10): 

364 print("**line**", "*" +str(l)+"*") 

365 print('') 

366 

367 If the directive produces RST text to be included later in the documentation, 

368 it is able to interpret 

369 `docutils directives <http://docutils.sourceforge.net/docs/ref/rst/directives.html>`_ 

370 and `Sphinx directives 

371 <https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html>`_ 

372 with function `nested_parse_with_titles <http://sphinx-doc.org/extdev/ 

373 markupapi.html?highlight=nested_parse>`_. However, if this text contains 

374 titles, it is better to use option ``:sphinx: false``. 

375 Unless *process* option is enabled, global variables cannot be used. 

376 `sphinx-autorun <https://pypi.org/project/sphinx-autorun/>`_ offers a similar 

377 service except it cannot produce compile :epkg:`RST` content, 

378 hide the source and a couple of other options. 

379 Option *toggle* can hide or unhide the piece of code 

380 or/and its output. 

381 The directive also adds local variables such as 

382 ``__WD__`` which contains the path to the documentation 

383 which contains the directive. It is useful to load additional 

384 files ``os.path.join(__WD__, ...)``. 

385 

386 .. runpython:: 

387 :toggle: out 

388 :showcode: 

389 

390 print("Hide or unhide this output.") 

391 

392 .. versionchanged:: 1.9 

393 Options *store_in_file* was added. 

394 """ 

395 required_arguments = 0 

396 optional_arguments = 0 

397 final_argument_whitespace = True 

398 option_spec = { 

399 'indent': directives.unchanged, 

400 'showcode': directives.unchanged, 

401 'showout': directives.unchanged, 

402 'rst': directives.unchanged, 

403 'sin': directives.unchanged, 

404 'sout': directives.unchanged, 

405 'sphinx': directives.unchanged, 

406 'sout2': directives.unchanged, 

407 'setsysvar': directives.unchanged, 

408 'process': directives.unchanged, 

409 'exception': directives.unchanged, 

410 'nopep8': directives.unchanged, 

411 'warningout': directives.unchanged, 

412 'toggle': directives.unchanged, 

413 'current': directives.unchanged, 

414 'assert': directives.unchanged, 

415 'language': directives.unchanged, 

416 'store': directives.unchanged, 

417 'restore': directives.unchanged, 

418 'numpy_precision': directives.unchanged, 

419 'store_in_file': directives.unchanged, 

420 'linenos': directives.unchanged, 

421 } 

422 has_content = True 

423 runpython_class = runpython_node 

424 

425 def run(self): 

426 """ 

427 Extracts the information in a dictionary, 

428 runs the script. 

429 

430 @return a list of nodes 

431 """ 

432 # settings 

433 sett = self.state.document.settings 

434 language_code = sett.language_code 

435 lineno = self.lineno 

436 

437 # add the instance to the global settings 

438 if hasattr(sett, "out_runpythonlist"): 

439 sett.out_runpythonlist.append(self) 

440 

441 # env 

442 if hasattr(self.state.document.settings, "env"): 

443 env = self.state.document.settings.env 

444 else: 

445 env = None 

446 

447 if env is None: 

448 docname = "___unknown_docname___" 

449 else: 

450 docname = env.docname 

451 

452 # post 

453 bool_set = (True, 1, "True", "1", "true") 

454 bool_set_ = (True, 1, "True", "1", "true", '') 

455 p = { 

456 'showcode': 'showcode' in self.options, 

457 'linenos': 'linenos' in self.options, 

458 'showout': 'showout' in self.options, 

459 'rst': 'rst' in self.options, 

460 'sin': self.options.get('sin', TITLES[language_code]["In"]), 

461 'sout': self.options.get('sout', TITLES[language_code]["Out"]), 

462 'sout2': self.options.get('sout2', TITLES[language_code]["Out2"]), 

463 'sphinx': 'sphinx' not in self.options or self.options['sphinx'] in bool_set, 

464 'setsysvar': self.options.get('setsysvar', None), 

465 'process': 'process' in self.options and self.options['process'] in bool_set_, 

466 'exception': 'exception' in self.options and self.options['exception'] in bool_set_, 

467 'nopep8': 'nopep8' in self.options and self.options['nopep8'] in bool_set_, 

468 'warningout': self.options.get('warningout', '').strip(), 

469 'toggle': self.options.get('toggle', '').strip(), 

470 'current': 'current' in self.options and self.options['current'] in bool_set_, 

471 'assert': self.options.get('assert', '').strip(), 

472 'language': self.options.get('language', '').strip(), 

473 'store_in_file': self.options.get('store_in_file', None), 

474 'numpy_precision': self.options.get('numpy_precision', '3').strip(), 

475 'store': 'store' in self.options and self.options['store'] in bool_set_, 

476 'restore': 'restore' in self.options and self.options['restore'] in bool_set_, 

477 } 

478 

479 if p['setsysvar'] is not None and len(p['setsysvar']) == 0: 

480 p['setsysvar'] = 'enable_disabled_documented_pieces_of_code' 

481 dind = 0 if p['rst'] else 4 

482 p['indent'] = int(self.options.get("indent", dind)) 

483 

484 # run the script 

485 name = "run_python_script_{0}".format(id(p)) 

486 if p['process']: 

487 content = ["if True:"] 

488 else: 

489 content = ["def {0}():".format(name)] 

490 

491 if "numpy" in "\n".join(self.content) and p['numpy_precision'] not in (None, 'None', '-', ''): 

492 try: 

493 import numpy # pylint: disable=W0611 

494 prec = int(p['numpy_precision']) 

495 content.append(" import numpy") 

496 content.append(" numpy.set_printoptions(%d)" % prec) 

497 except (ImportError, ValueError): 

498 pass 

499 

500 content.append(' ## __WD__ ##') 

501 

502 if p["restore"]: 

503 context = getattr(env, "runpython_context", None) 

504 for k in sorted(context): 

505 content.append( 

506 " {0} = globals()['__runpython__{0}']".format(k)) 

507 else: 

508 context = None 

509 

510 modified_content = self.modify_script_before_running( 

511 "\n".join(self.content)) 

512 

513 if p['assert']: 

514 footer = [] 

515 assert_condition = p['assert'].split('\n') 

516 for cond in assert_condition: 

517 footer.append("if not({0}):".format(cond)) 

518 footer.append( 

519 " raise AssertionError('''Condition '{0}' failed.''')".format(cond)) 

520 modified_content += "\n\n" + "\n".join(footer) 

521 

522 for line in modified_content.split("\n"): 

523 content.append(" " + line) 

524 

525 if p["store"]: 

526 content.append(' for __k__, __v__ in locals().copy().items():') 

527 content.append( 

528 " globals()['__runpython__' + __k__] = __v__") 

529 

530 if not p['process']: 

531 content.append("{0}()".format(name)) 

532 

533 script = "\n".join(content) 

534 script_disp = "\n".join(self.content) 

535 if not p["nopep8"]: 

536 try: 

537 script_disp = remove_extra_spaces_and_pep8( 

538 script_disp, is_string=True) 

539 except Exception as e: # pragma: no cover 

540 if '.' in docname: 

541 comment = ' File "{0}", line {1}'.format(docname, lineno) 

542 else: 

543 comment = ' File "{0}.rst", line {1}\n File "{0}.py", line {1}\n'.format( 

544 docname, lineno) 

545 raise ValueError( 

546 "Pep8 issue with\n'{0}'\n---SCRIPT---\n{1}".format(docname, script)) from e 

547 

548 # if an exception is raised, the documentation should report a warning 

549 # return [document.reporter.warning('messagr', line=self.lineno)] 

550 current_source = self.state.document.current_source 

551 docstring = ":docstring of " in current_source 

552 if docstring: 

553 current_source = current_source.split(":docstring of ")[0] 

554 if os.path.exists(current_source): 

555 comment = ' File "{0}", line {1}'.format(current_source, lineno) 

556 if docstring: 

557 new_name = os.path.split(current_source)[0] + ".py" 

558 comment += '\n File "{0}", line {1}'.format(new_name, lineno) 

559 cs_source = current_source 

560 else: 

561 if '.' in docname: 

562 comment = ' File "{0}", line {1}'.format(docname, lineno) 

563 else: 

564 comment = ' File "{0}.rst", line {1}\n File "{0}.py", line {1}\n'.format( 

565 docname, lineno) 

566 cs_source = docname 

567 

568 # Add __WD__. 

569 cs_source_dir = os.path.dirname(cs_source).replace("\\", "/") 

570 script = script.replace( 

571 '## __WD__ ##', "__WD__ = '{0}'".format(cs_source_dir)) 

572 

573 out, err, context = run_python_script(script, comment=comment, setsysvar=p['setsysvar'], 

574 process=p["process"], exception=p['exception'], 

575 warningout=p['warningout'], 

576 chdir=cs_source_dir if p['current'] else None, 

577 context=context, store_in_file=p['store_in_file']) 

578 

579 if p['store']: 

580 # Stores modified local context. 

581 setattr(env, "runpython_context", context) 

582 else: 

583 context = {} 

584 setattr(env, "runpython_context", context) 

585 

586 if out is not None: 

587 out = out.rstrip(" \n\r\t") 

588 if err is not None: 

589 err = err.rstrip(" \n\r\t") 

590 content = out 

591 if len(err) > 0: 

592 content += "\n[runpythonerror]\n" + err 

593 

594 # add member 

595 self.exe_class = p.copy() 

596 self.exe_class.update(dict(out=out, err=err, script=script)) 

597 

598 # add indent 

599 def add_indent(content, nbind): 

600 "local function" 

601 lines = content.split("\n") 

602 if nbind > 0: 

603 lines = [(" " * nbind + _) for _ in lines] 

604 content = "\n".join(lines) 

605 return content 

606 

607 content = add_indent(content, p['indent']) 

608 

609 # build node 

610 node = self.__class__.runpython_class(rawsource=content, indent=p["indent"], 

611 showcode=p["showcode"], rst=p["rst"], 

612 sin=p["sin"], sout=p["sout"]) 

613 

614 if p["showcode"]: 

615 if 'code' in p['toggle'] or 'both' in p['toggle']: 

616 hide = TITLES[language_code]['hide'] + \ 

617 ' ' + TITLES[language_code]['code'] 

618 unhide = TITLES[language_code]['unhide'] + \ 

619 ' ' + TITLES[language_code]['code'] 

620 secin = collapse_node(hide=hide, unhide=unhide, show=False) 

621 node += secin 

622 else: 

623 secin = node 

624 pin = nodes.paragraph(text=p["sin"]) 

625 if p['language'] in (None, ''): 

626 p['language'] = 'python' 

627 if p['language']: 

628 pcode = nodes.literal_block( 

629 script_disp, script_disp, language=p['language'], 

630 linenos=p['linenos']) 

631 else: 

632 pcode = nodes.literal_block( 

633 script_disp, script_disp, linenos=p['linenos']) 

634 secin += pin 

635 secin += pcode 

636 

637 elif len(self.options.get('sout', '')) == 0: 

638 p["sout"] = '' 

639 p["sout2"] = '' 

640 

641 # RST output. 

642 if p["rst"]: 

643 settings_overrides = {} 

644 try: 

645 sett.output_encoding 

646 except KeyError: 

647 settings_overrides["output_encoding"] = "unicode" 

648 # try: 

649 # sett.doctitle_xform 

650 # except KeyError: 

651 # settings_overrides["doctitle_xform"] = True 

652 try: 

653 sett.warning_stream 

654 except KeyError: # pragma: no cover 

655 settings_overrides["warning_stream"] = StringIO() 

656 # 'initial_header_level': 2, 

657 

658 secout = node 

659 if 'out' in p['toggle'] or 'both' in p['toggle']: 

660 hide = TITLES[language_code]['hide'] + \ 

661 ' ' + TITLES[language_code]['outl'] 

662 unhide = TITLES[language_code]['unhide'] + \ 

663 ' ' + TITLES[language_code]['outl'] 

664 secout = collapse_node(hide=hide, unhide=unhide, show=False) 

665 node += secout 

666 elif len(p["sout"]) > 0: 

667 secout += nodes.paragraph(text=p["sout"]) 

668 

669 try: 

670 if p['sphinx']: 

671 st = StringList(content.replace("\r", "").split("\n")) 

672 nested_parse_with_titles(self.state, st, secout) 

673 dt = None 

674 else: 

675 dt = core.publish_doctree( 

676 content, settings=sett, 

677 settings_overrides=settings_overrides) 

678 except Exception as e: # pragma: no cover 

679 tab = content 

680 content = ["::"] 

681 st = StringIO() 

682 traceback.print_exc(file=st) 

683 content.append("") 

684 trace = st.getvalue() 

685 trace += "\n----------------------OPT\n" + str(p) 

686 trace += "\n----------------------EXC\n" + str(e) 

687 trace += "\n----------------------SETT\n" + str(sett) 

688 trace += "\n----------------------ENV\n" + str(env) 

689 trace += "\n----------------------DOCNAME\n" + str(docname) 

690 trace += "\n----------------------CODE\n" 

691 content.extend(" " + _ for _ in trace.split("\n")) 

692 content.append("") 

693 content.append("") 

694 content.extend(" " + _ for _ in tab.split("\n")) 

695 content = "\n".join(content) 

696 pout = nodes.literal_block(content, content) 

697 secout += pout 

698 dt = None 

699 

700 if dt is not None: 

701 for ch in dt.children: 

702 node += ch 

703 

704 # Regular output. 

705 if not p["rst"] or p["showout"]: 

706 text = p["sout2"] if p["rst"] else p["sout"] 

707 secout = node 

708 if 'out' in p['toggle'] or 'both' in p['toggle']: 

709 hide = TITLES[language_code]['hide'] + \ 

710 ' ' + TITLES[language_code]['outl'] 

711 unhide = TITLES[language_code]['unhide'] + \ 

712 ' ' + TITLES[language_code]['outl'] 

713 secout = collapse_node(hide=hide, unhide=unhide, show=False) 

714 node += secout 

715 elif len(text) > 0: 

716 pout2 = nodes.paragraph(text=text) 

717 node += pout2 

718 pout = nodes.literal_block(content, content) 

719 secout += pout 

720 

721 p['runpython'] = node 

722 

723 # classes 

724 node['classes'] += ["runpython"] 

725 ns = [node] 

726 return ns 

727 

728 def modify_script_before_running(self, script): 

729 """ 

730 Takes the script as a string 

731 and returns another string before it is run. 

732 It does not modify what is displayed. 

733 The function can be overwritten by any class 

734 based on this one. 

735 """ 

736 return script 

737 

738 

739def visit_runpython_node(self, node): 

740 """ 

741 What to do when visiting a node @see cl runpython_node 

742 the function should have different behaviour, 

743 depending on the format, or the setup should 

744 specify a different function for each. 

745 """ 

746 pass 

747 

748 

749def depart_runpython_node(self, node): 

750 """ 

751 What to do when leaving a node @see cl runpython_node 

752 the function should have different behaviour, 

753 depending on the format, or the setup should 

754 specify a different function for each. 

755 """ 

756 pass 

757 

758 

759def setup(app): 

760 """ 

761 setup for ``runpython`` (sphinx) 

762 """ 

763 app.add_config_value('out_runpythonlist', [], 'env') 

764 if hasattr(app, "add_mapping"): 

765 app.add_mapping('runpython', runpython_node) 

766 

767 app.add_node(runpython_node, 

768 html=(visit_runpython_node, depart_runpython_node), 

769 epub=(visit_runpython_node, depart_runpython_node), 

770 elatex=(visit_runpython_node, depart_runpython_node), 

771 latex=(visit_runpython_node, depart_runpython_node), 

772 rst=(visit_runpython_node, depart_runpython_node), 

773 md=(visit_runpython_node, depart_runpython_node), 

774 text=(visit_runpython_node, depart_runpython_node)) 

775 

776 app.add_directive('runpython', RunPythonDirective) 

777 return {'version': sphinx.__display_version__, 'parallel_read_safe': True}