##// END OF EJS Templates
Merge pull request #11544 from BoboTiG/fix-invalid-seq-warnings...
Matthias Bussonnier -
r24899:4417fe97 merge
parent child Browse files
Show More
@@ -1,1248 +1,1248 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 Sphinx directive to support embedded IPython code.
4 4
5 5 IPython provides an extension for `Sphinx <http://www.sphinx-doc.org/>`_ to
6 6 highlight and run code.
7 7
8 8 This directive allows pasting of entire interactive IPython sessions, prompts
9 9 and all, and their code will actually get re-executed at doc build time, with
10 10 all prompts renumbered sequentially. It also allows you to input code as a pure
11 11 python input by giving the argument python to the directive. The output looks
12 12 like an interactive ipython section.
13 13
14 14 Here is an example of how the IPython directive can
15 15 **run** python code, at build time.
16 16
17 17 .. ipython::
18 18
19 19 In [1]: 1+1
20 20
21 21 In [1]: import datetime
22 22 ...: datetime.datetime.now()
23 23
24 24 It supports IPython construct that plain
25 25 Python does not understand (like magics):
26 26
27 27 .. ipython::
28 28
29 29 In [0]: import time
30 30
31 31 In [0]: %timeit time.sleep(0.05)
32 32
33 33 This will also support top-level async when using IPython 7.0+
34 34
35 35 .. ipython::
36 36
37 37 In [2]: import asyncio
38 38 ...: print('before')
39 39 ...: await asyncio.sleep(1)
40 40 ...: print('after')
41 41
42 42
43 43 The namespace will persist across multiple code chucks, Let's define a variable:
44 44
45 45 .. ipython::
46 46
47 47 In [0]: who = "World"
48 48
49 49 And now say hello:
50 50
51 51 .. ipython::
52 52
53 53 In [0]: print('Hello,', who)
54 54
55 55 If the current section raises an exception, you can add the ``:okexcept:`` flag
56 56 to the current block, otherwise the build will fail.
57 57
58 58 .. ipython::
59 59 :okexcept:
60 60
61 61 In [1]: 1/0
62 62
63 63 IPython Sphinx directive module
64 64 ===============================
65 65
66 66 To enable this directive, simply list it in your Sphinx ``conf.py`` file
67 67 (making sure the directory where you placed it is visible to sphinx, as is
68 68 needed for all Sphinx directives). For example, to enable syntax highlighting
69 69 and the IPython directive::
70 70
71 71 extensions = ['IPython.sphinxext.ipython_console_highlighting',
72 72 'IPython.sphinxext.ipython_directive']
73 73
74 74 The IPython directive outputs code-blocks with the language 'ipython'. So
75 75 if you do not have the syntax highlighting extension enabled as well, then
76 76 all rendered code-blocks will be uncolored. By default this directive assumes
77 77 that your prompts are unchanged IPython ones, but this can be customized.
78 78 The configurable options that can be placed in conf.py are:
79 79
80 80 ipython_savefig_dir:
81 81 The directory in which to save the figures. This is relative to the
82 82 Sphinx source directory. The default is `html_static_path`.
83 83 ipython_rgxin:
84 84 The compiled regular expression to denote the start of IPython input
85 lines. The default is ``re.compile('In \[(\d+)\]:\s?(.*)\s*')``. You
85 lines. The default is ``re.compile('In \\[(\\d+)\\]:\\s?(.*)\\s*')``. You
86 86 shouldn't need to change this.
87 87 ipython_warning_is_error: [default to True]
88 88 Fail the build if something unexpected happen, for example if a block raise
89 89 an exception but does not have the `:okexcept:` flag. The exact behavior of
90 90 what is considered strict, may change between the sphinx directive version.
91 91 ipython_rgxout:
92 92 The compiled regular expression to denote the start of IPython output
93 lines. The default is ``re.compile('Out\[(\d+)\]:\s?(.*)\s*')``. You
93 lines. The default is ``re.compile('Out\\[(\\d+)\\]:\\s?(.*)\\s*')``. You
94 94 shouldn't need to change this.
95 95 ipython_promptin:
96 96 The string to represent the IPython input prompt in the generated ReST.
97 97 The default is ``'In [%d]:'``. This expects that the line numbers are used
98 98 in the prompt.
99 99 ipython_promptout:
100 100 The string to represent the IPython prompt in the generated ReST. The
101 101 default is ``'Out [%d]:'``. This expects that the line numbers are used
102 102 in the prompt.
103 103 ipython_mplbackend:
104 104 The string which specifies if the embedded Sphinx shell should import
105 105 Matplotlib and set the backend. The value specifies a backend that is
106 106 passed to `matplotlib.use()` before any lines in `ipython_execlines` are
107 107 executed. If not specified in conf.py, then the default value of 'agg' is
108 108 used. To use the IPython directive without matplotlib as a dependency, set
109 109 the value to `None`. It may end up that matplotlib is still imported
110 110 if the user specifies so in `ipython_execlines` or makes use of the
111 111 @savefig pseudo decorator.
112 112 ipython_execlines:
113 113 A list of strings to be exec'd in the embedded Sphinx shell. Typical
114 114 usage is to make certain packages always available. Set this to an empty
115 115 list if you wish to have no imports always available. If specified in
116 116 ``conf.py`` as `None`, then it has the effect of making no imports available.
117 117 If omitted from conf.py altogether, then the default value of
118 118 ['import numpy as np', 'import matplotlib.pyplot as plt'] is used.
119 119 ipython_holdcount
120 120 When the @suppress pseudo-decorator is used, the execution count can be
121 121 incremented or not. The default behavior is to hold the execution count,
122 122 corresponding to a value of `True`. Set this to `False` to increment
123 123 the execution count after each suppressed command.
124 124
125 125 As an example, to use the IPython directive when `matplotlib` is not available,
126 126 one sets the backend to `None`::
127 127
128 128 ipython_mplbackend = None
129 129
130 130 An example usage of the directive is:
131 131
132 132 .. code-block:: rst
133 133
134 134 .. ipython::
135 135
136 136 In [1]: x = 1
137 137
138 138 In [2]: y = x**2
139 139
140 140 In [3]: print(y)
141 141
142 142 See http://matplotlib.org/sampledoc/ipython_directive.html for additional
143 143 documentation.
144 144
145 145 Pseudo-Decorators
146 146 =================
147 147
148 148 Note: Only one decorator is supported per input. If more than one decorator
149 149 is specified, then only the last one is used.
150 150
151 151 In addition to the Pseudo-Decorators/options described at the above link,
152 152 several enhancements have been made. The directive will emit a message to the
153 153 console at build-time if code-execution resulted in an exception or warning.
154 154 You can suppress these on a per-block basis by specifying the :okexcept:
155 155 or :okwarning: options:
156 156
157 157 .. code-block:: rst
158 158
159 159 .. ipython::
160 160 :okexcept:
161 161 :okwarning:
162 162
163 163 In [1]: 1/0
164 164 In [2]: # raise warning.
165 165
166 166 To Do
167 167 =====
168 168
169 169 - Turn the ad-hoc test() function into a real test suite.
170 170 - Break up ipython-specific functionality from matplotlib stuff into better
171 171 separated code.
172 172
173 173 """
174 174
175 175 # Authors
176 176 # =======
177 177 #
178 178 # - John D Hunter: original author.
179 179 # - Fernando Perez: refactoring, documentation, cleanups, port to 0.11.
180 180 # - VΓ‘clavΕ milauer <eudoxos-AT-arcig.cz>: Prompt generalizations.
181 181 # - Skipper Seabold, refactoring, cleanups, pure python addition
182 182
183 183 #-----------------------------------------------------------------------------
184 184 # Imports
185 185 #-----------------------------------------------------------------------------
186 186
187 187 # Stdlib
188 188 import atexit
189 189 import errno
190 190 import os
191 191 import re
192 192 import sys
193 193 import tempfile
194 194 import ast
195 195 import warnings
196 196 import shutil
197 197 from io import StringIO
198 198
199 199 # Third-party
200 200 from docutils.parsers.rst import directives
201 201 from docutils.parsers.rst import Directive
202 202
203 203 # Our own
204 204 from traitlets.config import Config
205 205 from IPython import InteractiveShell
206 206 from IPython.core.profiledir import ProfileDir
207 207
208 208 use_matpltolib = False
209 209 try:
210 210 import matplotlib
211 211 use_matpltolib = True
212 212 except Exception:
213 213 pass
214 214
215 215 #-----------------------------------------------------------------------------
216 216 # Globals
217 217 #-----------------------------------------------------------------------------
218 218 # for tokenizing blocks
219 219 COMMENT, INPUT, OUTPUT = range(3)
220 220
221 221 #-----------------------------------------------------------------------------
222 222 # Functions and class declarations
223 223 #-----------------------------------------------------------------------------
224 224
225 225 def block_parser(part, rgxin, rgxout, fmtin, fmtout):
226 226 """
227 227 part is a string of ipython text, comprised of at most one
228 228 input, one output, comments, and blank lines. The block parser
229 229 parses the text into a list of::
230 230
231 231 blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...]
232 232
233 233 where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and
234 234 data is, depending on the type of token::
235 235
236 236 COMMENT : the comment string
237 237
238 238 INPUT: the (DECORATOR, INPUT_LINE, REST) where
239 239 DECORATOR: the input decorator (or None)
240 240 INPUT_LINE: the input as string (possibly multi-line)
241 241 REST : any stdout generated by the input line (not OUTPUT)
242 242
243 243 OUTPUT: the output string, possibly multi-line
244 244
245 245 """
246 246 block = []
247 247 lines = part.split('\n')
248 248 N = len(lines)
249 249 i = 0
250 250 decorator = None
251 251 while 1:
252 252
253 253 if i==N:
254 254 # nothing left to parse -- the last line
255 255 break
256 256
257 257 line = lines[i]
258 258 i += 1
259 259 line_stripped = line.strip()
260 260 if line_stripped.startswith('#'):
261 261 block.append((COMMENT, line))
262 262 continue
263 263
264 264 if line_stripped.startswith('@'):
265 265 # Here is where we assume there is, at most, one decorator.
266 266 # Might need to rethink this.
267 267 decorator = line_stripped
268 268 continue
269 269
270 270 # does this look like an input line?
271 271 matchin = rgxin.match(line)
272 272 if matchin:
273 273 lineno, inputline = int(matchin.group(1)), matchin.group(2)
274 274
275 275 # the ....: continuation string
276 276 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
277 277 Nc = len(continuation)
278 278 # input lines can continue on for more than one line, if
279 279 # we have a '\' line continuation char or a function call
280 280 # echo line 'print'. The input line can only be
281 281 # terminated by the end of the block or an output line, so
282 282 # we parse out the rest of the input line if it is
283 283 # multiline as well as any echo text
284 284
285 285 rest = []
286 286 while i<N:
287 287
288 288 # look ahead; if the next line is blank, or a comment, or
289 289 # an output line, we're done
290 290
291 291 nextline = lines[i]
292 292 matchout = rgxout.match(nextline)
293 293 #print "nextline=%s, continuation=%s, starts=%s"%(nextline, continuation, nextline.startswith(continuation))
294 294 if matchout or nextline.startswith('#'):
295 295 break
296 296 elif nextline.startswith(continuation):
297 297 # The default ipython_rgx* treat the space following the colon as optional.
298 298 # However, If the space is there we must consume it or code
299 299 # employing the cython_magic extension will fail to execute.
300 300 #
301 301 # This works with the default ipython_rgx* patterns,
302 302 # If you modify them, YMMV.
303 303 nextline = nextline[Nc:]
304 304 if nextline and nextline[0] == ' ':
305 305 nextline = nextline[1:]
306 306
307 307 inputline += '\n' + nextline
308 308 else:
309 309 rest.append(nextline)
310 310 i+= 1
311 311
312 312 block.append((INPUT, (decorator, inputline, '\n'.join(rest))))
313 313 continue
314 314
315 315 # if it looks like an output line grab all the text to the end
316 316 # of the block
317 317 matchout = rgxout.match(line)
318 318 if matchout:
319 319 lineno, output = int(matchout.group(1)), matchout.group(2)
320 320 if i<N-1:
321 321 output = '\n'.join([output] + lines[i:])
322 322
323 323 block.append((OUTPUT, output))
324 324 break
325 325
326 326 return block
327 327
328 328
329 329 class EmbeddedSphinxShell(object):
330 330 """An embedded IPython instance to run inside Sphinx"""
331 331
332 332 def __init__(self, exec_lines=None):
333 333
334 334 self.cout = StringIO()
335 335
336 336 if exec_lines is None:
337 337 exec_lines = []
338 338
339 339 # Create config object for IPython
340 340 config = Config()
341 341 config.HistoryManager.hist_file = ':memory:'
342 342 config.InteractiveShell.autocall = False
343 343 config.InteractiveShell.autoindent = False
344 344 config.InteractiveShell.colors = 'NoColor'
345 345
346 346 # create a profile so instance history isn't saved
347 347 tmp_profile_dir = tempfile.mkdtemp(prefix='profile_')
348 348 profname = 'auto_profile_sphinx_build'
349 349 pdir = os.path.join(tmp_profile_dir,profname)
350 350 profile = ProfileDir.create_profile_dir(pdir)
351 351
352 352 # Create and initialize global ipython, but don't start its mainloop.
353 353 # This will persist across different EmbeddedSphinxShell instances.
354 354 IP = InteractiveShell.instance(config=config, profile_dir=profile)
355 355 atexit.register(self.cleanup)
356 356
357 357 # Store a few parts of IPython we'll need.
358 358 self.IP = IP
359 359 self.user_ns = self.IP.user_ns
360 360 self.user_global_ns = self.IP.user_global_ns
361 361
362 362 self.input = ''
363 363 self.output = ''
364 364 self.tmp_profile_dir = tmp_profile_dir
365 365
366 366 self.is_verbatim = False
367 367 self.is_doctest = False
368 368 self.is_suppress = False
369 369
370 370 # Optionally, provide more detailed information to shell.
371 371 # this is assigned by the SetUp method of IPythonDirective
372 372 # to point at itself.
373 373 #
374 374 # So, you can access handy things at self.directive.state
375 375 self.directive = None
376 376
377 377 # on the first call to the savefig decorator, we'll import
378 378 # pyplot as plt so we can make a call to the plt.gcf().savefig
379 379 self._pyplot_imported = False
380 380
381 381 # Prepopulate the namespace.
382 382 for line in exec_lines:
383 383 self.process_input_line(line, store_history=False)
384 384
385 385 def cleanup(self):
386 386 shutil.rmtree(self.tmp_profile_dir, ignore_errors=True)
387 387
388 388 def clear_cout(self):
389 389 self.cout.seek(0)
390 390 self.cout.truncate(0)
391 391
392 392 def process_input_line(self, line, store_history):
393 393 return self.process_input_lines([line], store_history=store_history)
394 394
395 395 def process_input_lines(self, lines, store_history=True):
396 396 """process the input, capturing stdout"""
397 397 stdout = sys.stdout
398 398 source_raw = '\n'.join(lines)
399 399 try:
400 400 sys.stdout = self.cout
401 401 self.IP.run_cell(source_raw, store_history=store_history)
402 402 finally:
403 403 sys.stdout = stdout
404 404
405 405 def process_image(self, decorator):
406 406 """
407 407 # build out an image directive like
408 408 # .. image:: somefile.png
409 409 # :width 4in
410 410 #
411 411 # from an input like
412 412 # savefig somefile.png width=4in
413 413 """
414 414 savefig_dir = self.savefig_dir
415 415 source_dir = self.source_dir
416 416 saveargs = decorator.split(' ')
417 417 filename = saveargs[1]
418 418 # insert relative path to image file in source (as absolute path for Sphinx)
419 419 outfile = '/' + os.path.relpath(os.path.join(savefig_dir,filename),
420 420 source_dir)
421 421
422 422 imagerows = ['.. image:: %s'%outfile]
423 423
424 424 for kwarg in saveargs[2:]:
425 425 arg, val = kwarg.split('=')
426 426 arg = arg.strip()
427 427 val = val.strip()
428 428 imagerows.append(' :%s: %s'%(arg, val))
429 429
430 430 image_file = os.path.basename(outfile) # only return file name
431 431 image_directive = '\n'.join(imagerows)
432 432 return image_file, image_directive
433 433
434 434 # Callbacks for each type of token
435 435 def process_input(self, data, input_prompt, lineno):
436 436 """
437 437 Process data block for INPUT token.
438 438
439 439 """
440 440 decorator, input, rest = data
441 441 image_file = None
442 442 image_directive = None
443 443
444 444 is_verbatim = decorator=='@verbatim' or self.is_verbatim
445 445 is_doctest = (decorator is not None and \
446 446 decorator.startswith('@doctest')) or self.is_doctest
447 447 is_suppress = decorator=='@suppress' or self.is_suppress
448 448 is_okexcept = decorator=='@okexcept' or self.is_okexcept
449 449 is_okwarning = decorator=='@okwarning' or self.is_okwarning
450 450 is_savefig = decorator is not None and \
451 451 decorator.startswith('@savefig')
452 452
453 453 input_lines = input.split('\n')
454 454 if len(input_lines) > 1:
455 455 if input_lines[-1] != "":
456 456 input_lines.append('') # make sure there's a blank line
457 457 # so splitter buffer gets reset
458 458
459 459 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
460 460
461 461 if is_savefig:
462 462 image_file, image_directive = self.process_image(decorator)
463 463
464 464 ret = []
465 465 is_semicolon = False
466 466
467 467 # Hold the execution count, if requested to do so.
468 468 if is_suppress and self.hold_count:
469 469 store_history = False
470 470 else:
471 471 store_history = True
472 472
473 473 # Note: catch_warnings is not thread safe
474 474 with warnings.catch_warnings(record=True) as ws:
475 475 if input_lines[0].endswith(';'):
476 476 is_semicolon = True
477 477 #for i, line in enumerate(input_lines):
478 478
479 479 # process the first input line
480 480 if is_verbatim:
481 481 self.process_input_lines([''])
482 482 self.IP.execution_count += 1 # increment it anyway
483 483 else:
484 484 # only submit the line in non-verbatim mode
485 485 self.process_input_lines(input_lines, store_history=store_history)
486 486
487 487 if not is_suppress:
488 488 for i, line in enumerate(input_lines):
489 489 if i == 0:
490 490 formatted_line = '%s %s'%(input_prompt, line)
491 491 else:
492 492 formatted_line = '%s %s'%(continuation, line)
493 493 ret.append(formatted_line)
494 494
495 495 if not is_suppress and len(rest.strip()) and is_verbatim:
496 496 # The "rest" is the standard output of the input. This needs to be
497 497 # added when in verbatim mode. If there is no "rest", then we don't
498 498 # add it, as the new line will be added by the processed output.
499 499 ret.append(rest)
500 500
501 501 # Fetch the processed output. (This is not the submitted output.)
502 502 self.cout.seek(0)
503 503 processed_output = self.cout.read()
504 504 if not is_suppress and not is_semicolon:
505 505 #
506 506 # In IPythonDirective.run, the elements of `ret` are eventually
507 507 # combined such that '' entries correspond to newlines. So if
508 508 # `processed_output` is equal to '', then the adding it to `ret`
509 509 # ensures that there is a blank line between consecutive inputs
510 510 # that have no outputs, as in:
511 511 #
512 512 # In [1]: x = 4
513 513 #
514 514 # In [2]: x = 5
515 515 #
516 516 # When there is processed output, it has a '\n' at the tail end. So
517 517 # adding the output to `ret` will provide the necessary spacing
518 518 # between consecutive input/output blocks, as in:
519 519 #
520 520 # In [1]: x
521 521 # Out[1]: 5
522 522 #
523 523 # In [2]: x
524 524 # Out[2]: 5
525 525 #
526 526 # When there is stdout from the input, it also has a '\n' at the
527 527 # tail end, and so this ensures proper spacing as well. E.g.:
528 528 #
529 529 # In [1]: print x
530 530 # 5
531 531 #
532 532 # In [2]: x = 5
533 533 #
534 534 # When in verbatim mode, `processed_output` is empty (because
535 535 # nothing was passed to IP. Sometimes the submitted code block has
536 536 # an Out[] portion and sometimes it does not. When it does not, we
537 537 # need to ensure proper spacing, so we have to add '' to `ret`.
538 538 # However, if there is an Out[] in the submitted code, then we do
539 539 # not want to add a newline as `process_output` has stuff to add.
540 540 # The difficulty is that `process_input` doesn't know if
541 541 # `process_output` will be called---so it doesn't know if there is
542 542 # Out[] in the code block. The requires that we include a hack in
543 543 # `process_block`. See the comments there.
544 544 #
545 545 ret.append(processed_output)
546 546 elif is_semicolon:
547 547 # Make sure there is a newline after the semicolon.
548 548 ret.append('')
549 549
550 550 # context information
551 551 filename = "Unknown"
552 552 lineno = 0
553 553 if self.directive.state:
554 554 filename = self.directive.state.document.current_source
555 555 lineno = self.directive.state.document.current_line
556 556
557 557 # output any exceptions raised during execution to stdout
558 558 # unless :okexcept: has been specified.
559 559 if not is_okexcept and (("Traceback" in processed_output) or ("SyntaxError" in processed_output)):
560 560 s = "\nException in %s at block ending on line %s\n" % (filename, lineno)
561 561 s += "Specify :okexcept: as an option in the ipython:: block to suppress this message\n"
562 562 sys.stdout.write('\n\n>>>' + ('-' * 73))
563 563 sys.stdout.write(s)
564 564 sys.stdout.write(processed_output)
565 565 sys.stdout.write('<<<' + ('-' * 73) + '\n\n')
566 566 if self.warning_is_error:
567 567 raise RuntimeError('Non Expected exception in `{}` line {}'.format(filename, lineno))
568 568
569 569 # output any warning raised during execution to stdout
570 570 # unless :okwarning: has been specified.
571 571 if not is_okwarning:
572 572 for w in ws:
573 573 s = "\nWarning in %s at block ending on line %s\n" % (filename, lineno)
574 574 s += "Specify :okwarning: as an option in the ipython:: block to suppress this message\n"
575 575 sys.stdout.write('\n\n>>>' + ('-' * 73))
576 576 sys.stdout.write(s)
577 577 sys.stdout.write(('-' * 76) + '\n')
578 578 s=warnings.formatwarning(w.message, w.category,
579 579 w.filename, w.lineno, w.line)
580 580 sys.stdout.write(s)
581 581 sys.stdout.write('<<<' + ('-' * 73) + '\n')
582 582 if self.warning_is_error:
583 583 raise RuntimeError('Non Expected warning in `{}` line {}'.format(filename, lineno))
584 584
585 585 self.cout.truncate(0)
586 586 return (ret, input_lines, processed_output,
587 587 is_doctest, decorator, image_file, image_directive)
588 588
589 589
590 590 def process_output(self, data, output_prompt, input_lines, output,
591 591 is_doctest, decorator, image_file):
592 592 """
593 593 Process data block for OUTPUT token.
594 594
595 595 """
596 596 # Recall: `data` is the submitted output, and `output` is the processed
597 597 # output from `input_lines`.
598 598
599 599 TAB = ' ' * 4
600 600
601 601 if is_doctest and output is not None:
602 602
603 603 found = output # This is the processed output
604 604 found = found.strip()
605 605 submitted = data.strip()
606 606
607 607 if self.directive is None:
608 608 source = 'Unavailable'
609 609 content = 'Unavailable'
610 610 else:
611 611 source = self.directive.state.document.current_source
612 612 content = self.directive.content
613 613 # Add tabs and join into a single string.
614 614 content = '\n'.join([TAB + line for line in content])
615 615
616 616 # Make sure the output contains the output prompt.
617 617 ind = found.find(output_prompt)
618 618 if ind < 0:
619 619 e = ('output does not contain output prompt\n\n'
620 620 'Document source: {0}\n\n'
621 621 'Raw content: \n{1}\n\n'
622 622 'Input line(s):\n{TAB}{2}\n\n'
623 623 'Output line(s):\n{TAB}{3}\n\n')
624 624 e = e.format(source, content, '\n'.join(input_lines),
625 625 repr(found), TAB=TAB)
626 626 raise RuntimeError(e)
627 627 found = found[len(output_prompt):].strip()
628 628
629 629 # Handle the actual doctest comparison.
630 630 if decorator.strip() == '@doctest':
631 631 # Standard doctest
632 632 if found != submitted:
633 633 e = ('doctest failure\n\n'
634 634 'Document source: {0}\n\n'
635 635 'Raw content: \n{1}\n\n'
636 636 'On input line(s):\n{TAB}{2}\n\n'
637 637 'we found output:\n{TAB}{3}\n\n'
638 638 'instead of the expected:\n{TAB}{4}\n\n')
639 639 e = e.format(source, content, '\n'.join(input_lines),
640 640 repr(found), repr(submitted), TAB=TAB)
641 641 raise RuntimeError(e)
642 642 else:
643 643 self.custom_doctest(decorator, input_lines, found, submitted)
644 644
645 645 # When in verbatim mode, this holds additional submitted output
646 646 # to be written in the final Sphinx output.
647 647 # https://github.com/ipython/ipython/issues/5776
648 648 out_data = []
649 649
650 650 is_verbatim = decorator=='@verbatim' or self.is_verbatim
651 651 if is_verbatim and data.strip():
652 652 # Note that `ret` in `process_block` has '' as its last element if
653 653 # the code block was in verbatim mode. So if there is no submitted
654 654 # output, then we will have proper spacing only if we do not add
655 655 # an additional '' to `out_data`. This is why we condition on
656 656 # `and data.strip()`.
657 657
658 658 # The submitted output has no output prompt. If we want the
659 659 # prompt and the code to appear, we need to join them now
660 660 # instead of adding them separately---as this would create an
661 661 # undesired newline. How we do this ultimately depends on the
662 662 # format of the output regex. I'll do what works for the default
663 663 # prompt for now, and we might have to adjust if it doesn't work
664 664 # in other cases. Finally, the submitted output does not have
665 665 # a trailing newline, so we must add it manually.
666 666 out_data.append("{0} {1}\n".format(output_prompt, data))
667 667
668 668 return out_data
669 669
670 670 def process_comment(self, data):
671 671 """Process data fPblock for COMMENT token."""
672 672 if not self.is_suppress:
673 673 return [data]
674 674
675 675 def save_image(self, image_file):
676 676 """
677 677 Saves the image file to disk.
678 678 """
679 679 self.ensure_pyplot()
680 680 command = 'plt.gcf().savefig("%s")'%image_file
681 681 #print 'SAVEFIG', command # dbg
682 682 self.process_input_line('bookmark ipy_thisdir', store_history=False)
683 683 self.process_input_line('cd -b ipy_savedir', store_history=False)
684 684 self.process_input_line(command, store_history=False)
685 685 self.process_input_line('cd -b ipy_thisdir', store_history=False)
686 686 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
687 687 self.clear_cout()
688 688
689 689 def process_block(self, block):
690 690 """
691 691 process block from the block_parser and return a list of processed lines
692 692 """
693 693 ret = []
694 694 output = None
695 695 input_lines = None
696 696 lineno = self.IP.execution_count
697 697
698 698 input_prompt = self.promptin % lineno
699 699 output_prompt = self.promptout % lineno
700 700 image_file = None
701 701 image_directive = None
702 702
703 703 found_input = False
704 704 for token, data in block:
705 705 if token == COMMENT:
706 706 out_data = self.process_comment(data)
707 707 elif token == INPUT:
708 708 found_input = True
709 709 (out_data, input_lines, output, is_doctest,
710 710 decorator, image_file, image_directive) = \
711 711 self.process_input(data, input_prompt, lineno)
712 712 elif token == OUTPUT:
713 713 if not found_input:
714 714
715 715 TAB = ' ' * 4
716 716 linenumber = 0
717 717 source = 'Unavailable'
718 718 content = 'Unavailable'
719 719 if self.directive:
720 720 linenumber = self.directive.state.document.current_line
721 721 source = self.directive.state.document.current_source
722 722 content = self.directive.content
723 723 # Add tabs and join into a single string.
724 724 content = '\n'.join([TAB + line for line in content])
725 725
726 726 e = ('\n\nInvalid block: Block contains an output prompt '
727 727 'without an input prompt.\n\n'
728 728 'Document source: {0}\n\n'
729 729 'Content begins at line {1}: \n\n{2}\n\n'
730 730 'Problematic block within content: \n\n{TAB}{3}\n\n')
731 731 e = e.format(source, linenumber, content, block, TAB=TAB)
732 732
733 733 # Write, rather than include in exception, since Sphinx
734 734 # will truncate tracebacks.
735 735 sys.stdout.write(e)
736 736 raise RuntimeError('An invalid block was detected.')
737 737 out_data = \
738 738 self.process_output(data, output_prompt, input_lines,
739 739 output, is_doctest, decorator,
740 740 image_file)
741 741 if out_data:
742 742 # Then there was user submitted output in verbatim mode.
743 743 # We need to remove the last element of `ret` that was
744 744 # added in `process_input`, as it is '' and would introduce
745 745 # an undesirable newline.
746 746 assert(ret[-1] == '')
747 747 del ret[-1]
748 748
749 749 if out_data:
750 750 ret.extend(out_data)
751 751
752 752 # save the image files
753 753 if image_file is not None:
754 754 self.save_image(image_file)
755 755
756 756 return ret, image_directive
757 757
758 758 def ensure_pyplot(self):
759 759 """
760 760 Ensures that pyplot has been imported into the embedded IPython shell.
761 761
762 762 Also, makes sure to set the backend appropriately if not set already.
763 763
764 764 """
765 765 # We are here if the @figure pseudo decorator was used. Thus, it's
766 766 # possible that we could be here even if python_mplbackend were set to
767 767 # `None`. That's also strange and perhaps worthy of raising an
768 768 # exception, but for now, we just set the backend to 'agg'.
769 769
770 770 if not self._pyplot_imported:
771 771 if 'matplotlib.backends' not in sys.modules:
772 772 # Then ipython_matplotlib was set to None but there was a
773 773 # call to the @figure decorator (and ipython_execlines did
774 774 # not set a backend).
775 775 #raise Exception("No backend was set, but @figure was used!")
776 776 import matplotlib
777 777 matplotlib.use('agg')
778 778
779 779 # Always import pyplot into embedded shell.
780 780 self.process_input_line('import matplotlib.pyplot as plt',
781 781 store_history=False)
782 782 self._pyplot_imported = True
783 783
784 784 def process_pure_python(self, content):
785 785 """
786 786 content is a list of strings. it is unedited directive content
787 787
788 788 This runs it line by line in the InteractiveShell, prepends
789 789 prompts as needed capturing stderr and stdout, then returns
790 790 the content as a list as if it were ipython code
791 791 """
792 792 output = []
793 793 savefig = False # keep up with this to clear figure
794 794 multiline = False # to handle line continuation
795 795 multiline_start = None
796 796 fmtin = self.promptin
797 797
798 798 ct = 0
799 799
800 800 for lineno, line in enumerate(content):
801 801
802 802 line_stripped = line.strip()
803 803 if not len(line):
804 804 output.append(line)
805 805 continue
806 806
807 807 # handle decorators
808 808 if line_stripped.startswith('@'):
809 809 output.extend([line])
810 810 if 'savefig' in line:
811 811 savefig = True # and need to clear figure
812 812 continue
813 813
814 814 # handle comments
815 815 if line_stripped.startswith('#'):
816 816 output.extend([line])
817 817 continue
818 818
819 819 # deal with lines checking for multiline
820 820 continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2))
821 821 if not multiline:
822 822 modified = u"%s %s" % (fmtin % ct, line_stripped)
823 823 output.append(modified)
824 824 ct += 1
825 825 try:
826 826 ast.parse(line_stripped)
827 827 output.append(u'')
828 828 except Exception: # on a multiline
829 829 multiline = True
830 830 multiline_start = lineno
831 831 else: # still on a multiline
832 832 modified = u'%s %s' % (continuation, line)
833 833 output.append(modified)
834 834
835 835 # if the next line is indented, it should be part of multiline
836 836 if len(content) > lineno + 1:
837 837 nextline = content[lineno + 1]
838 838 if len(nextline) - len(nextline.lstrip()) > 3:
839 839 continue
840 840 try:
841 841 mod = ast.parse(
842 842 '\n'.join(content[multiline_start:lineno+1]))
843 843 if isinstance(mod.body[0], ast.FunctionDef):
844 844 # check to see if we have the whole function
845 845 for element in mod.body[0].body:
846 846 if isinstance(element, ast.Return):
847 847 multiline = False
848 848 else:
849 849 output.append(u'')
850 850 multiline = False
851 851 except Exception:
852 852 pass
853 853
854 854 if savefig: # clear figure if plotted
855 855 self.ensure_pyplot()
856 856 self.process_input_line('plt.clf()', store_history=False)
857 857 self.clear_cout()
858 858 savefig = False
859 859
860 860 return output
861 861
862 862 def custom_doctest(self, decorator, input_lines, found, submitted):
863 863 """
864 864 Perform a specialized doctest.
865 865
866 866 """
867 867 from .custom_doctests import doctests
868 868
869 869 args = decorator.split()
870 870 doctest_type = args[1]
871 871 if doctest_type in doctests:
872 872 doctests[doctest_type](self, args, input_lines, found, submitted)
873 873 else:
874 874 e = "Invalid option to @doctest: {0}".format(doctest_type)
875 875 raise Exception(e)
876 876
877 877
878 878 class IPythonDirective(Directive):
879 879
880 880 has_content = True
881 881 required_arguments = 0
882 882 optional_arguments = 4 # python, suppress, verbatim, doctest
883 883 final_argumuent_whitespace = True
884 884 option_spec = { 'python': directives.unchanged,
885 885 'suppress' : directives.flag,
886 886 'verbatim' : directives.flag,
887 887 'doctest' : directives.flag,
888 888 'okexcept': directives.flag,
889 889 'okwarning': directives.flag
890 890 }
891 891
892 892 shell = None
893 893
894 894 seen_docs = set()
895 895
896 896 def get_config_options(self):
897 897 # contains sphinx configuration variables
898 898 config = self.state.document.settings.env.config
899 899
900 900 # get config variables to set figure output directory
901 901 savefig_dir = config.ipython_savefig_dir
902 902 source_dir = self.state.document.settings.env.srcdir
903 903 savefig_dir = os.path.join(source_dir, savefig_dir)
904 904
905 905 # get regex and prompt stuff
906 906 rgxin = config.ipython_rgxin
907 907 rgxout = config.ipython_rgxout
908 908 warning_is_error= config.ipython_warning_is_error
909 909 promptin = config.ipython_promptin
910 910 promptout = config.ipython_promptout
911 911 mplbackend = config.ipython_mplbackend
912 912 exec_lines = config.ipython_execlines
913 913 hold_count = config.ipython_holdcount
914 914
915 915 return (savefig_dir, source_dir, rgxin, rgxout,
916 916 promptin, promptout, mplbackend, exec_lines, hold_count, warning_is_error)
917 917
918 918 def setup(self):
919 919 # Get configuration values.
920 920 (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout,
921 921 mplbackend, exec_lines, hold_count, warning_is_error) = self.get_config_options()
922 922
923 923 try:
924 924 os.makedirs(savefig_dir)
925 925 except OSError as e:
926 926 if e.errno != errno.EEXIST:
927 927 raise
928 928
929 929 if self.shell is None:
930 930 # We will be here many times. However, when the
931 931 # EmbeddedSphinxShell is created, its interactive shell member
932 932 # is the same for each instance.
933 933
934 934 if mplbackend and 'matplotlib.backends' not in sys.modules and use_matpltolib:
935 935 import matplotlib
936 936 matplotlib.use(mplbackend)
937 937
938 938 # Must be called after (potentially) importing matplotlib and
939 939 # setting its backend since exec_lines might import pylab.
940 940 self.shell = EmbeddedSphinxShell(exec_lines)
941 941
942 942 # Store IPython directive to enable better error messages
943 943 self.shell.directive = self
944 944
945 945 # reset the execution count if we haven't processed this doc
946 946 #NOTE: this may be borked if there are multiple seen_doc tmp files
947 947 #check time stamp?
948 948 if not self.state.document.current_source in self.seen_docs:
949 949 self.shell.IP.history_manager.reset()
950 950 self.shell.IP.execution_count = 1
951 951 self.seen_docs.add(self.state.document.current_source)
952 952
953 953 # and attach to shell so we don't have to pass them around
954 954 self.shell.rgxin = rgxin
955 955 self.shell.rgxout = rgxout
956 956 self.shell.promptin = promptin
957 957 self.shell.promptout = promptout
958 958 self.shell.savefig_dir = savefig_dir
959 959 self.shell.source_dir = source_dir
960 960 self.shell.hold_count = hold_count
961 961 self.shell.warning_is_error = warning_is_error
962 962
963 963 # setup bookmark for saving figures directory
964 964 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
965 965 store_history=False)
966 966 self.shell.clear_cout()
967 967
968 968 return rgxin, rgxout, promptin, promptout
969 969
970 970 def teardown(self):
971 971 # delete last bookmark
972 972 self.shell.process_input_line('bookmark -d ipy_savedir',
973 973 store_history=False)
974 974 self.shell.clear_cout()
975 975
976 976 def run(self):
977 977 debug = False
978 978
979 979 #TODO, any reason block_parser can't be a method of embeddable shell
980 980 # then we wouldn't have to carry these around
981 981 rgxin, rgxout, promptin, promptout = self.setup()
982 982
983 983 options = self.options
984 984 self.shell.is_suppress = 'suppress' in options
985 985 self.shell.is_doctest = 'doctest' in options
986 986 self.shell.is_verbatim = 'verbatim' in options
987 987 self.shell.is_okexcept = 'okexcept' in options
988 988 self.shell.is_okwarning = 'okwarning' in options
989 989
990 990 # handle pure python code
991 991 if 'python' in self.arguments:
992 992 content = self.content
993 993 self.content = self.shell.process_pure_python(content)
994 994
995 995 # parts consists of all text within the ipython-block.
996 996 # Each part is an input/output block.
997 997 parts = '\n'.join(self.content).split('\n\n')
998 998
999 999 lines = ['.. code-block:: ipython', '']
1000 1000 figures = []
1001 1001
1002 1002 for part in parts:
1003 1003 block = block_parser(part, rgxin, rgxout, promptin, promptout)
1004 1004 if len(block):
1005 1005 rows, figure = self.shell.process_block(block)
1006 1006 for row in rows:
1007 1007 lines.extend([' {0}'.format(line)
1008 1008 for line in row.split('\n')])
1009 1009
1010 1010 if figure is not None:
1011 1011 figures.append(figure)
1012 1012 else:
1013 1013 message = 'Code input with no code at {}, line {}'\
1014 1014 .format(
1015 1015 self.state.document.current_source,
1016 1016 self.state.document.current_line)
1017 1017 if self.shell.warning_is_error:
1018 1018 raise RuntimeError(message)
1019 1019 else:
1020 1020 warnings.warn(message)
1021 1021
1022 1022 for figure in figures:
1023 1023 lines.append('')
1024 1024 lines.extend(figure.split('\n'))
1025 1025 lines.append('')
1026 1026
1027 1027 if len(lines) > 2:
1028 1028 if debug:
1029 1029 print('\n'.join(lines))
1030 1030 else:
1031 1031 # This has to do with input, not output. But if we comment
1032 1032 # these lines out, then no IPython code will appear in the
1033 1033 # final output.
1034 1034 self.state_machine.insert_input(
1035 1035 lines, self.state_machine.input_lines.source(0))
1036 1036
1037 1037 # cleanup
1038 1038 self.teardown()
1039 1039
1040 1040 return []
1041 1041
1042 1042 # Enable as a proper Sphinx directive
1043 1043 def setup(app):
1044 1044 setup.app = app
1045 1045
1046 1046 app.add_directive('ipython', IPythonDirective)
1047 1047 app.add_config_value('ipython_savefig_dir', 'savefig', 'env')
1048 1048 app.add_config_value('ipython_warning_is_error', True, 'env')
1049 1049 app.add_config_value('ipython_rgxin',
1050 re.compile('In \[(\d+)\]:\s?(.*)\s*'), 'env')
1050 re.compile(r'In \[(\d+)\]:\s?(.*)\s*'), 'env')
1051 1051 app.add_config_value('ipython_rgxout',
1052 re.compile('Out\[(\d+)\]:\s?(.*)\s*'), 'env')
1052 re.compile(r'Out\[(\d+)\]:\s?(.*)\s*'), 'env')
1053 1053 app.add_config_value('ipython_promptin', 'In [%d]:', 'env')
1054 1054 app.add_config_value('ipython_promptout', 'Out[%d]:', 'env')
1055 1055
1056 1056 # We could just let matplotlib pick whatever is specified as the default
1057 1057 # backend in the matplotlibrc file, but this would cause issues if the
1058 1058 # backend didn't work in headless environments. For this reason, 'agg'
1059 1059 # is a good default backend choice.
1060 1060 app.add_config_value('ipython_mplbackend', 'agg', 'env')
1061 1061
1062 1062 # If the user sets this config value to `None`, then EmbeddedSphinxShell's
1063 1063 # __init__ method will treat it as [].
1064 1064 execlines = ['import numpy as np']
1065 1065 if use_matpltolib:
1066 1066 execlines.append('import matplotlib.pyplot as plt')
1067 1067 app.add_config_value('ipython_execlines', execlines, 'env')
1068 1068
1069 1069 app.add_config_value('ipython_holdcount', True, 'env')
1070 1070
1071 1071 metadata = {'parallel_read_safe': True, 'parallel_write_safe': True}
1072 1072 return metadata
1073 1073
1074 1074 # Simple smoke test, needs to be converted to a proper automatic test.
1075 1075 def test():
1076 1076
1077 1077 examples = [
1078 1078 r"""
1079 1079 In [9]: pwd
1080 1080 Out[9]: '/home/jdhunter/py4science/book'
1081 1081
1082 1082 In [10]: cd bookdata/
1083 1083 /home/jdhunter/py4science/book/bookdata
1084 1084
1085 1085 In [2]: from pylab import *
1086 1086
1087 1087 In [2]: ion()
1088 1088
1089 1089 In [3]: im = imread('stinkbug.png')
1090 1090
1091 1091 @savefig mystinkbug.png width=4in
1092 1092 In [4]: imshow(im)
1093 1093 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
1094 1094
1095 1095 """,
1096 1096 r"""
1097 1097
1098 1098 In [1]: x = 'hello world'
1099 1099
1100 1100 # string methods can be
1101 1101 # used to alter the string
1102 1102 @doctest
1103 1103 In [2]: x.upper()
1104 1104 Out[2]: 'HELLO WORLD'
1105 1105
1106 1106 @verbatim
1107 1107 In [3]: x.st<TAB>
1108 1108 x.startswith x.strip
1109 1109 """,
1110 1110 r"""
1111 1111
1112 1112 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
1113 1113 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
1114 1114
1115 1115 In [131]: print url.split('&')
1116 1116 ['http://ichart.finance.yahoo.com/table.csv?s=CROX', 'd=9', 'e=22', 'f=2009', 'g=d', 'a=1', 'b=8', 'c=2006', 'ignore=.csv']
1117 1117
1118 1118 In [60]: import urllib
1119 1119
1120 1120 """,
1121 1121 r"""\
1122 1122
1123 1123 In [133]: import numpy.random
1124 1124
1125 1125 @suppress
1126 1126 In [134]: numpy.random.seed(2358)
1127 1127
1128 1128 @doctest
1129 1129 In [135]: numpy.random.rand(10,2)
1130 1130 Out[135]:
1131 1131 array([[ 0.64524308, 0.59943846],
1132 1132 [ 0.47102322, 0.8715456 ],
1133 1133 [ 0.29370834, 0.74776844],
1134 1134 [ 0.99539577, 0.1313423 ],
1135 1135 [ 0.16250302, 0.21103583],
1136 1136 [ 0.81626524, 0.1312433 ],
1137 1137 [ 0.67338089, 0.72302393],
1138 1138 [ 0.7566368 , 0.07033696],
1139 1139 [ 0.22591016, 0.77731835],
1140 1140 [ 0.0072729 , 0.34273127]])
1141 1141
1142 1142 """,
1143 1143
1144 1144 r"""
1145 1145 In [106]: print x
1146 1146 jdh
1147 1147
1148 1148 In [109]: for i in range(10):
1149 1149 .....: print i
1150 1150 .....:
1151 1151 .....:
1152 1152 0
1153 1153 1
1154 1154 2
1155 1155 3
1156 1156 4
1157 1157 5
1158 1158 6
1159 1159 7
1160 1160 8
1161 1161 9
1162 1162 """,
1163 1163
1164 1164 r"""
1165 1165
1166 1166 In [144]: from pylab import *
1167 1167
1168 1168 In [145]: ion()
1169 1169
1170 1170 # use a semicolon to suppress the output
1171 1171 @savefig test_hist.png width=4in
1172 1172 In [151]: hist(np.random.randn(10000), 100);
1173 1173
1174 1174
1175 1175 @savefig test_plot.png width=4in
1176 1176 In [151]: plot(np.random.randn(10000), 'o');
1177 1177 """,
1178 1178
1179 1179 r"""
1180 1180 # use a semicolon to suppress the output
1181 1181 In [151]: plt.clf()
1182 1182
1183 1183 @savefig plot_simple.png width=4in
1184 1184 In [151]: plot([1,2,3])
1185 1185
1186 1186 @savefig hist_simple.png width=4in
1187 1187 In [151]: hist(np.random.randn(10000), 100);
1188 1188
1189 1189 """,
1190 1190 r"""
1191 1191 # update the current fig
1192 1192 In [151]: ylabel('number')
1193 1193
1194 1194 In [152]: title('normal distribution')
1195 1195
1196 1196
1197 1197 @savefig hist_with_text.png
1198 1198 In [153]: grid(True)
1199 1199
1200 1200 @doctest float
1201 1201 In [154]: 0.1 + 0.2
1202 1202 Out[154]: 0.3
1203 1203
1204 1204 @doctest float
1205 1205 In [155]: np.arange(16).reshape(4,4)
1206 1206 Out[155]:
1207 1207 array([[ 0, 1, 2, 3],
1208 1208 [ 4, 5, 6, 7],
1209 1209 [ 8, 9, 10, 11],
1210 1210 [12, 13, 14, 15]])
1211 1211
1212 1212 In [1]: x = np.arange(16, dtype=float).reshape(4,4)
1213 1213
1214 1214 In [2]: x[0,0] = np.inf
1215 1215
1216 1216 In [3]: x[0,1] = np.nan
1217 1217
1218 1218 @doctest float
1219 1219 In [4]: x
1220 1220 Out[4]:
1221 1221 array([[ inf, nan, 2., 3.],
1222 1222 [ 4., 5., 6., 7.],
1223 1223 [ 8., 9., 10., 11.],
1224 1224 [ 12., 13., 14., 15.]])
1225 1225
1226 1226
1227 1227 """,
1228 1228 ]
1229 1229 # skip local-file depending first example:
1230 1230 examples = examples[1:]
1231 1231
1232 1232 #ipython_directive.DEBUG = True # dbg
1233 1233 #options = dict(suppress=True) # dbg
1234 1234 options = {}
1235 1235 for example in examples:
1236 1236 content = example.split('\n')
1237 1237 IPythonDirective('debug', arguments=None, options=options,
1238 1238 content=content, lineno=0,
1239 1239 content_offset=None, block_text=None,
1240 1240 state=None, state_machine=None,
1241 1241 )
1242 1242
1243 1243 # Run test suite as a script
1244 1244 if __name__=='__main__':
1245 1245 if not os.path.isdir('_static'):
1246 1246 os.mkdir('_static')
1247 1247 test()
1248 1248 print('All OK? Check figures in _static/')
@@ -1,74 +1,74 b''
1 1 """Test embedding of IPython"""
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2013 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 14 import os
15 15 import sys
16 16 from IPython.testing.decorators import skip_win32
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Tests
20 20 #-----------------------------------------------------------------------------
21 21
22 22 @skip_win32
23 23 def test_debug_magic_passes_through_generators():
24 24 """
25 25 This test that we can correctly pass through frames of a generator post-mortem.
26 26 """
27 27 import pexpect
28 28 import re
29 in_prompt = re.compile(b'In ?\[\\d+\]:')
29 in_prompt = re.compile(br'In ?\[\d+\]:')
30 30 ipdb_prompt = 'ipdb>'
31 31 env = os.environ.copy()
32 32 child = pexpect.spawn(sys.executable, ['-m', 'IPython', '--colors=nocolor', '--simple-prompt'],
33 33 env=env)
34 34 child.timeout = 2
35 35
36 36 child.expect(in_prompt)
37 37 child.sendline("def f(x):")
38 38 child.sendline(" raise Exception")
39 39 child.sendline("")
40 40
41 41 child.expect(in_prompt)
42 42 child.sendline("gen = (f(x) for x in [0])")
43 43 child.sendline("")
44 44
45 45 child.expect(in_prompt)
46 46 child.sendline("for x in gen:")
47 47 child.sendline(" pass")
48 48 child.sendline("")
49 49
50 50 child.expect('Exception:')
51 51
52 52 child.expect(in_prompt)
53 53 child.sendline(r'%debug')
54 54 child.expect('----> 2 raise Exception')
55 55
56 56 child.expect(ipdb_prompt)
57 57 child.sendline('u')
58 58 child.expect_exact(r'----> 1 gen = (f(x) for x in [0])')
59 59
60 60 child.expect(ipdb_prompt)
61 61 child.sendline('u')
62 62 child.expect_exact('----> 1 for x in gen:')
63 63
64 64 child.expect(ipdb_prompt)
65 65 child.sendline('u')
66 66 child.expect_exact('*** Oldest frame')
67 67
68 68 child.expect(ipdb_prompt)
69 69 child.sendline('exit')
70 70
71 71 child.expect(in_prompt)
72 72 child.sendline('exit')
73 73
74 74 child.close()
@@ -1,453 +1,453 b''
1 1 """Attempt to generate templates for module reference with Sphinx
2 2
3 3 XXX - we exclude extension modules
4 4
5 5 To include extension modules, first identify them as valid in the
6 6 ``_uri2path`` method, then handle them in the ``_parse_module`` script.
7 7
8 8 We get functions and classes by parsing the text of .py files.
9 9 Alternatively we could import the modules for discovery, and we'd have
10 10 to do that for extension modules. This would involve changing the
11 11 ``_parse_module`` method to work via import and introspection, and
12 12 might involve changing ``discover_modules`` (which determines which
13 13 files are modules, and therefore which module URIs will be passed to
14 14 ``_parse_module``).
15 15
16 16 NOTE: this is a modified version of a script originally shipped with the
17 17 PyMVPA project, which we've adapted for NIPY use. PyMVPA is an MIT-licensed
18 18 project."""
19 19
20 20
21 21 # Stdlib imports
22 22 import ast
23 23 import inspect
24 24 import os
25 25 import re
26 26 from importlib import import_module
27 27
28 28
29 29 class Obj(object):
30 30 '''Namespace to hold arbitrary information.'''
31 31 def __init__(self, **kwargs):
32 32 for k, v in kwargs.items():
33 33 setattr(self, k, v)
34 34
35 35 class FuncClsScanner(ast.NodeVisitor):
36 36 """Scan a module for top-level functions and classes.
37 37
38 38 Skips objects with an @undoc decorator, or a name starting with '_'.
39 39 """
40 40 def __init__(self):
41 41 ast.NodeVisitor.__init__(self)
42 42 self.classes = []
43 43 self.classes_seen = set()
44 44 self.functions = []
45 45
46 46 @staticmethod
47 47 def has_undoc_decorator(node):
48 48 return any(isinstance(d, ast.Name) and d.id == 'undoc' \
49 49 for d in node.decorator_list)
50 50
51 51 def visit_If(self, node):
52 52 if isinstance(node.test, ast.Compare) \
53 53 and isinstance(node.test.left, ast.Name) \
54 54 and node.test.left.id == '__name__':
55 55 return # Ignore classes defined in "if __name__ == '__main__':"
56 56
57 57 self.generic_visit(node)
58 58
59 59 def visit_FunctionDef(self, node):
60 60 if not (node.name.startswith('_') or self.has_undoc_decorator(node)) \
61 61 and node.name not in self.functions:
62 62 self.functions.append(node.name)
63 63
64 64 def visit_ClassDef(self, node):
65 65 if not (node.name.startswith('_') or self.has_undoc_decorator(node)) \
66 66 and node.name not in self.classes_seen:
67 67 cls = Obj(name=node.name)
68 68 cls.has_init = any(isinstance(n, ast.FunctionDef) and \
69 69 n.name=='__init__' for n in node.body)
70 70 self.classes.append(cls)
71 71 self.classes_seen.add(node.name)
72 72
73 73 def scan(self, mod):
74 74 self.visit(mod)
75 75 return self.functions, self.classes
76 76
77 77 # Functions and classes
78 78 class ApiDocWriter(object):
79 79 ''' Class for automatic detection and parsing of API docs
80 80 to Sphinx-parsable reST format'''
81 81
82 82 # only separating first two levels
83 83 rst_section_levels = ['*', '=', '-', '~', '^']
84 84
85 85 def __init__(self,
86 86 package_name,
87 87 rst_extension='.rst',
88 88 package_skip_patterns=None,
89 89 module_skip_patterns=None,
90 90 names_from__all__=None,
91 91 ):
92 92 ''' Initialize package for parsing
93 93
94 94 Parameters
95 95 ----------
96 96 package_name : string
97 97 Name of the top-level package. *package_name* must be the
98 98 name of an importable package
99 99 rst_extension : string, optional
100 100 Extension for reST files, default '.rst'
101 101 package_skip_patterns : None or sequence of {strings, regexps}
102 102 Sequence of strings giving URIs of packages to be excluded
103 103 Operates on the package path, starting at (including) the
104 104 first dot in the package path, after *package_name* - so,
105 105 if *package_name* is ``sphinx``, then ``sphinx.util`` will
106 106 result in ``.util`` being passed for earching by these
107 107 regexps. If is None, gives default. Default is:
108 ['\.tests$']
108 ['\\.tests$']
109 109 module_skip_patterns : None or sequence
110 110 Sequence of strings giving URIs of modules to be excluded
111 111 Operates on the module name including preceding URI path,
112 112 back to the first dot after *package_name*. For example
113 113 ``sphinx.util.console`` results in the string to search of
114 114 ``.util.console``
115 115 If is None, gives default. Default is:
116 ['\.setup$', '\._']
116 ['\\.setup$', '\\._']
117 117 names_from__all__ : set, optional
118 118 Modules listed in here will be scanned by doing ``from mod import *``,
119 119 rather than finding function and class definitions by scanning the
120 120 AST. This is intended for API modules which expose things defined in
121 121 other files. Modules listed here must define ``__all__`` to avoid
122 122 exposing everything they import.
123 123 '''
124 124 if package_skip_patterns is None:
125 125 package_skip_patterns = ['\\.tests$']
126 126 if module_skip_patterns is None:
127 127 module_skip_patterns = ['\\.setup$', '\\._']
128 128 self.package_name = package_name
129 129 self.rst_extension = rst_extension
130 130 self.package_skip_patterns = package_skip_patterns
131 131 self.module_skip_patterns = module_skip_patterns
132 132 self.names_from__all__ = names_from__all__ or set()
133 133
134 134 def get_package_name(self):
135 135 return self._package_name
136 136
137 137 def set_package_name(self, package_name):
138 138 ''' Set package_name
139 139
140 140 >>> docwriter = ApiDocWriter('sphinx')
141 141 >>> import sphinx
142 142 >>> docwriter.root_path == sphinx.__path__[0]
143 143 True
144 144 >>> docwriter.package_name = 'docutils'
145 145 >>> import docutils
146 146 >>> docwriter.root_path == docutils.__path__[0]
147 147 True
148 148 '''
149 149 # It's also possible to imagine caching the module parsing here
150 150 self._package_name = package_name
151 151 self.root_module = import_module(package_name)
152 152 self.root_path = self.root_module.__path__[0]
153 153 self.written_modules = None
154 154
155 155 package_name = property(get_package_name, set_package_name, None,
156 156 'get/set package_name')
157 157
158 158 def _uri2path(self, uri):
159 159 ''' Convert uri to absolute filepath
160 160
161 161 Parameters
162 162 ----------
163 163 uri : string
164 164 URI of python module to return path for
165 165
166 166 Returns
167 167 -------
168 168 path : None or string
169 169 Returns None if there is no valid path for this URI
170 170 Otherwise returns absolute file system path for URI
171 171
172 172 Examples
173 173 --------
174 174 >>> docwriter = ApiDocWriter('sphinx')
175 175 >>> import sphinx
176 176 >>> modpath = sphinx.__path__[0]
177 177 >>> res = docwriter._uri2path('sphinx.builder')
178 178 >>> res == os.path.join(modpath, 'builder.py')
179 179 True
180 180 >>> res = docwriter._uri2path('sphinx')
181 181 >>> res == os.path.join(modpath, '__init__.py')
182 182 True
183 183 >>> docwriter._uri2path('sphinx.does_not_exist')
184 184
185 185 '''
186 186 if uri == self.package_name:
187 187 return os.path.join(self.root_path, '__init__.py')
188 188 path = uri.replace('.', os.path.sep)
189 189 path = path.replace(self.package_name + os.path.sep, '')
190 190 path = os.path.join(self.root_path, path)
191 191 # XXX maybe check for extensions as well?
192 192 if os.path.exists(path + '.py'): # file
193 193 path += '.py'
194 194 elif os.path.exists(os.path.join(path, '__init__.py')):
195 195 path = os.path.join(path, '__init__.py')
196 196 else:
197 197 return None
198 198 return path
199 199
200 200 def _path2uri(self, dirpath):
201 201 ''' Convert directory path to uri '''
202 202 relpath = dirpath.replace(self.root_path, self.package_name)
203 203 if relpath.startswith(os.path.sep):
204 204 relpath = relpath[1:]
205 205 return relpath.replace(os.path.sep, '.')
206 206
207 207 def _parse_module(self, uri):
208 208 ''' Parse module defined in *uri* '''
209 209 filename = self._uri2path(uri)
210 210 if filename is None:
211 211 # nothing that we could handle here.
212 212 return ([],[])
213 213 with open(filename, 'rb') as f:
214 214 mod = ast.parse(f.read())
215 215 return FuncClsScanner().scan(mod)
216 216
217 217 def _import_funcs_classes(self, uri):
218 218 """Import * from uri, and separate out functions and classes."""
219 219 ns = {}
220 220 exec('from %s import *' % uri, ns)
221 221 funcs, classes = [], []
222 222 for name, obj in ns.items():
223 223 if inspect.isclass(obj):
224 224 cls = Obj(name=name, has_init='__init__' in obj.__dict__)
225 225 classes.append(cls)
226 226 elif inspect.isfunction(obj):
227 227 funcs.append(name)
228 228
229 229 return sorted(funcs), sorted(classes, key=lambda x: x.name)
230 230
231 231 def find_funcs_classes(self, uri):
232 232 """Find the functions and classes defined in the module ``uri``"""
233 233 if uri in self.names_from__all__:
234 234 # For API modules which expose things defined elsewhere, import them
235 235 return self._import_funcs_classes(uri)
236 236 else:
237 237 # For other modules, scan their AST to see what they define
238 238 return self._parse_module(uri)
239 239
240 240 def generate_api_doc(self, uri):
241 241 '''Make autodoc documentation template string for a module
242 242
243 243 Parameters
244 244 ----------
245 245 uri : string
246 246 python location of module - e.g 'sphinx.builder'
247 247
248 248 Returns
249 249 -------
250 250 S : string
251 251 Contents of API doc
252 252 '''
253 253 # get the names of all classes and functions
254 254 functions, classes = self.find_funcs_classes(uri)
255 255 if not len(functions) and not len(classes):
256 256 #print ('WARNING: Empty -', uri) # dbg
257 257 return ''
258 258
259 259 # Make a shorter version of the uri that omits the package name for
260 260 # titles
261 261 uri_short = re.sub(r'^%s\.' % self.package_name,'',uri)
262 262
263 263 ad = '.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n'
264 264
265 265 # Set the chapter title to read 'Module:' for all modules except for the
266 266 # main packages
267 267 if '.' in uri:
268 268 chap_title = 'Module: :mod:`' + uri_short + '`'
269 269 else:
270 270 chap_title = ':mod:`' + uri_short + '`'
271 271 ad += chap_title + '\n' + self.rst_section_levels[1] * len(chap_title)
272 272
273 273 ad += '\n.. automodule:: ' + uri + '\n'
274 274 ad += '\n.. currentmodule:: ' + uri + '\n'
275 275
276 276 if classes:
277 277 subhead = str(len(classes)) + (' Classes' if len(classes) > 1 else ' Class')
278 278 ad += '\n'+ subhead + '\n' + \
279 279 self.rst_section_levels[2] * len(subhead) + '\n'
280 280
281 281 for c in classes:
282 282 ad += '\n.. autoclass:: ' + c.name + '\n'
283 283 # must NOT exclude from index to keep cross-refs working
284 284 ad += ' :members:\n' \
285 285 ' :show-inheritance:\n'
286 286 if c.has_init:
287 287 ad += '\n .. automethod:: __init__\n'
288 288
289 289 if functions:
290 290 subhead = str(len(functions)) + (' Functions' if len(functions) > 1 else ' Function')
291 291 ad += '\n'+ subhead + '\n' + \
292 292 self.rst_section_levels[2] * len(subhead) + '\n'
293 293 for f in functions:
294 294 # must NOT exclude from index to keep cross-refs working
295 295 ad += '\n.. autofunction:: ' + uri + '.' + f + '\n\n'
296 296 return ad
297 297
298 298 def _survives_exclude(self, matchstr, match_type):
299 299 ''' Returns True if *matchstr* does not match patterns
300 300
301 301 ``self.package_name`` removed from front of string if present
302 302
303 303 Examples
304 304 --------
305 305 >>> dw = ApiDocWriter('sphinx')
306 306 >>> dw._survives_exclude('sphinx.okpkg', 'package')
307 307 True
308 308 >>> dw.package_skip_patterns.append('^\\.badpkg$')
309 309 >>> dw._survives_exclude('sphinx.badpkg', 'package')
310 310 False
311 311 >>> dw._survives_exclude('sphinx.badpkg', 'module')
312 312 True
313 313 >>> dw._survives_exclude('sphinx.badmod', 'module')
314 314 True
315 315 >>> dw.module_skip_patterns.append('^\\.badmod$')
316 316 >>> dw._survives_exclude('sphinx.badmod', 'module')
317 317 False
318 318 '''
319 319 if match_type == 'module':
320 320 patterns = self.module_skip_patterns
321 321 elif match_type == 'package':
322 322 patterns = self.package_skip_patterns
323 323 else:
324 324 raise ValueError('Cannot interpret match type "%s"'
325 325 % match_type)
326 326 # Match to URI without package name
327 327 L = len(self.package_name)
328 328 if matchstr[:L] == self.package_name:
329 329 matchstr = matchstr[L:]
330 330 for pat in patterns:
331 331 try:
332 332 pat.search
333 333 except AttributeError:
334 334 pat = re.compile(pat)
335 335 if pat.search(matchstr):
336 336 return False
337 337 return True
338 338
339 339 def discover_modules(self):
340 340 ''' Return module sequence discovered from ``self.package_name``
341 341
342 342
343 343 Parameters
344 344 ----------
345 345 None
346 346
347 347 Returns
348 348 -------
349 349 mods : sequence
350 350 Sequence of module names within ``self.package_name``
351 351
352 352 Examples
353 353 --------
354 354 >>> dw = ApiDocWriter('sphinx')
355 355 >>> mods = dw.discover_modules()
356 356 >>> 'sphinx.util' in mods
357 357 True
358 >>> dw.package_skip_patterns.append('\.util$')
358 >>> dw.package_skip_patterns.append('\\.util$')
359 359 >>> 'sphinx.util' in dw.discover_modules()
360 360 False
361 361 >>>
362 362 '''
363 363 modules = [self.package_name]
364 364 # raw directory parsing
365 365 for dirpath, dirnames, filenames in os.walk(self.root_path):
366 366 # Check directory names for packages
367 367 root_uri = self._path2uri(os.path.join(self.root_path,
368 368 dirpath))
369 369 for dirname in dirnames[:]: # copy list - we modify inplace
370 370 package_uri = '.'.join((root_uri, dirname))
371 371 if (self._uri2path(package_uri) and
372 372 self._survives_exclude(package_uri, 'package')):
373 373 modules.append(package_uri)
374 374 else:
375 375 dirnames.remove(dirname)
376 376 # Check filenames for modules
377 377 for filename in filenames:
378 378 module_name = filename[:-3]
379 379 module_uri = '.'.join((root_uri, module_name))
380 380 if (self._uri2path(module_uri) and
381 381 self._survives_exclude(module_uri, 'module')):
382 382 modules.append(module_uri)
383 383 return sorted(modules)
384 384
385 385 def write_modules_api(self, modules,outdir):
386 386 # write the list
387 387 written_modules = []
388 388 for m in modules:
389 389 api_str = self.generate_api_doc(m)
390 390 if not api_str:
391 391 continue
392 392 # write out to file
393 393 outfile = os.path.join(outdir,
394 394 m + self.rst_extension)
395 395 with open(outfile, 'wt') as fileobj:
396 396 fileobj.write(api_str)
397 397 written_modules.append(m)
398 398 self.written_modules = written_modules
399 399
400 400 def write_api_docs(self, outdir):
401 401 """Generate API reST files.
402 402
403 403 Parameters
404 404 ----------
405 405 outdir : string
406 406 Directory name in which to store files
407 407 We create automatic filenames for each module
408 408
409 409 Returns
410 410 -------
411 411 None
412 412
413 413 Notes
414 414 -----
415 415 Sets self.written_modules to list of written modules
416 416 """
417 417 if not os.path.exists(outdir):
418 418 os.mkdir(outdir)
419 419 # compose list of modules
420 420 modules = self.discover_modules()
421 421 self.write_modules_api(modules,outdir)
422 422
423 423 def write_index(self, outdir, path='gen.rst', relative_to=None):
424 424 """Make a reST API index file from written files
425 425
426 426 Parameters
427 427 ----------
428 428 outdir : string
429 429 Directory to which to write generated index file
430 430 path : string
431 431 Filename to write index to
432 432 relative_to : string
433 433 path to which written filenames are relative. This
434 434 component of the written file path will be removed from
435 435 outdir, in the generated index. Default is None, meaning,
436 436 leave path as it is.
437 437 """
438 438 if self.written_modules is None:
439 439 raise ValueError('No modules written')
440 440 # Get full filename path
441 441 path = os.path.join(outdir, path)
442 442 # Path written into index is relative to rootpath
443 443 if relative_to is not None:
444 444 relpath = outdir.replace(relative_to + os.path.sep, '')
445 445 else:
446 446 relpath = outdir
447 447 with open(path,'wt') as idx:
448 448 w = idx.write
449 449 w('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n')
450 450 w('.. autosummary::\n'
451 451 ' :toctree: %s\n\n' % relpath)
452 452 for mod in self.written_modules:
453 453 w(' %s\n' % mod)
@@ -1,403 +1,403 b''
1 1 # encoding: utf-8
2 2 """
3 3 This module defines the things that are used in setup.py for building IPython
4 4
5 5 This includes:
6 6
7 7 * The basic arguments to setup
8 8 * Functions for finding things like packages, package data, etc.
9 9 * A function for checking dependencies.
10 10 """
11 11
12 12 # Copyright (c) IPython Development Team.
13 13 # Distributed under the terms of the Modified BSD License.
14 14
15 15
16 16 import re
17 17 import os
18 18 import sys
19 19
20 20 from distutils import log
21 21 from distutils.command.build_py import build_py
22 22 from distutils.command.build_scripts import build_scripts
23 23 from distutils.command.install import install
24 24 from distutils.command.install_scripts import install_scripts
25 25 from distutils.cmd import Command
26 26 from glob import glob
27 27
28 28 from setupext import install_data_ext
29 29
30 30 #-------------------------------------------------------------------------------
31 31 # Useful globals and utility functions
32 32 #-------------------------------------------------------------------------------
33 33
34 34 # A few handy globals
35 35 isfile = os.path.isfile
36 36 pjoin = os.path.join
37 37 repo_root = os.path.dirname(os.path.abspath(__file__))
38 38
39 39 def execfile(fname, globs, locs=None):
40 40 locs = locs or globs
41 41 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
42 42
43 43 # A little utility we'll need below, since glob() does NOT allow you to do
44 44 # exclusion on multiple endings!
45 45 def file_doesnt_endwith(test,endings):
46 46 """Return true if test is a file and its name does NOT end with any
47 47 of the strings listed in endings."""
48 48 if not isfile(test):
49 49 return False
50 50 for e in endings:
51 51 if test.endswith(e):
52 52 return False
53 53 return True
54 54
55 55 #---------------------------------------------------------------------------
56 56 # Basic project information
57 57 #---------------------------------------------------------------------------
58 58
59 59 # release.py contains version, authors, license, url, keywords, etc.
60 60 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
61 61
62 62 # Create a dict with the basic information
63 63 # This dict is eventually passed to setup after additional keys are added.
64 64 setup_args = dict(
65 65 name = name,
66 66 version = version,
67 67 description = description,
68 68 long_description = long_description,
69 69 author = author,
70 70 author_email = author_email,
71 71 url = url,
72 72 license = license,
73 73 platforms = platforms,
74 74 keywords = keywords,
75 75 classifiers = classifiers,
76 76 cmdclass = {'install_data': install_data_ext},
77 77 project_urls={
78 78 'Documentation': 'https://ipython.readthedocs.io/',
79 79 'Funding' : 'https://numfocus.org/',
80 80 'Source' : 'https://github.com/ipython/ipython',
81 81 'Tracker' : 'https://github.com/ipython/ipython/issues',
82 82 }
83 83 )
84 84
85 85
86 86 #---------------------------------------------------------------------------
87 87 # Find packages
88 88 #---------------------------------------------------------------------------
89 89
90 90 def find_packages():
91 91 """
92 92 Find all of IPython's packages.
93 93 """
94 94 excludes = ['deathrow', 'quarantine']
95 95 packages = []
96 96 for dir,subdirs,files in os.walk('IPython'):
97 97 package = dir.replace(os.path.sep, '.')
98 98 if any(package.startswith('IPython.'+exc) for exc in excludes):
99 99 # package is to be excluded (e.g. deathrow)
100 100 continue
101 101 if '__init__.py' not in files:
102 102 # not a package
103 103 continue
104 104 packages.append(package)
105 105 return packages
106 106
107 107 #---------------------------------------------------------------------------
108 108 # Find package data
109 109 #---------------------------------------------------------------------------
110 110
111 111 def find_package_data():
112 112 """
113 113 Find IPython's package_data.
114 114 """
115 115 # This is not enough for these things to appear in an sdist.
116 116 # We need to muck with the MANIFEST to get this to work
117 117
118 118 package_data = {
119 119 'IPython.core' : ['profile/README*'],
120 120 'IPython.core.tests' : ['*.png', '*.jpg', 'daft_extension/*.py'],
121 121 'IPython.lib.tests' : ['*.wav'],
122 122 'IPython.testing.plugin' : ['*.txt'],
123 123 }
124 124
125 125 return package_data
126 126
127 127
128 128 def check_package_data(package_data):
129 129 """verify that package_data globs make sense"""
130 130 print("checking package data")
131 131 for pkg, data in package_data.items():
132 132 pkg_root = pjoin(*pkg.split('.'))
133 133 for d in data:
134 134 path = pjoin(pkg_root, d)
135 135 if '*' in path:
136 136 assert len(glob(path)) > 0, "No files match pattern %s" % path
137 137 else:
138 138 assert os.path.exists(path), "Missing package data: %s" % path
139 139
140 140
141 141 def check_package_data_first(command):
142 142 """decorator for checking package_data before running a given command
143 143
144 144 Probably only needs to wrap build_py
145 145 """
146 146 class DecoratedCommand(command):
147 147 def run(self):
148 148 check_package_data(self.package_data)
149 149 command.run(self)
150 150 return DecoratedCommand
151 151
152 152
153 153 #---------------------------------------------------------------------------
154 154 # Find data files
155 155 #---------------------------------------------------------------------------
156 156
157 157 def find_data_files():
158 158 """
159 159 Find IPython's data_files.
160 160
161 161 Just man pages at this point.
162 162 """
163 163
164 164 manpagebase = pjoin('share', 'man', 'man1')
165 165
166 166 # Simple file lists can be made by hand
167 167 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
168 168 if not manpages:
169 169 # When running from a source tree, the manpages aren't gzipped
170 170 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
171 171
172 172 # And assemble the entire output list
173 173 data_files = [ (manpagebase, manpages) ]
174 174
175 175 return data_files
176 176
177 177
178 178 # The two functions below are copied from IPython.utils.path, so we don't need
179 179 # to import IPython during setup, which fails on Python 3.
180 180
181 181 def target_outdated(target,deps):
182 182 """Determine whether a target is out of date.
183 183
184 184 target_outdated(target,deps) -> 1/0
185 185
186 186 deps: list of filenames which MUST exist.
187 187 target: single filename which may or may not exist.
188 188
189 189 If target doesn't exist or is older than any file listed in deps, return
190 190 true, otherwise return false.
191 191 """
192 192 try:
193 193 target_time = os.path.getmtime(target)
194 194 except os.error:
195 195 return 1
196 196 for dep in deps:
197 197 dep_time = os.path.getmtime(dep)
198 198 if dep_time > target_time:
199 199 #print "For target",target,"Dep failed:",dep # dbg
200 200 #print "times (dep,tar):",dep_time,target_time # dbg
201 201 return 1
202 202 return 0
203 203
204 204
205 205 def target_update(target,deps,cmd):
206 206 """Update a target with a given command given a list of dependencies.
207 207
208 208 target_update(target,deps,cmd) -> runs cmd if target is outdated.
209 209
210 210 This is just a wrapper around target_outdated() which calls the given
211 211 command if target is outdated."""
212 212
213 213 if target_outdated(target,deps):
214 214 os.system(cmd)
215 215
216 216 #---------------------------------------------------------------------------
217 217 # Find scripts
218 218 #---------------------------------------------------------------------------
219 219
220 220 def find_entry_points():
221 221 """Defines the command line entry points for IPython
222 222
223 223 This always uses setuptools-style entry points. When setuptools is not in
224 224 use, our own build_scripts_entrypt class below parses these and builds
225 225 command line scripts.
226 226
227 227 Each of our entry points gets both a plain name, e.g. ipython, and one
228 228 suffixed with the Python major version number, e.g. ipython3.
229 229 """
230 230 ep = [
231 231 'ipython%s = IPython:start_ipython',
232 232 'iptest%s = IPython.testing.iptestcontroller:main',
233 233 ]
234 234 suffix = str(sys.version_info[0])
235 235 return [e % '' for e in ep] + [e % suffix for e in ep]
236 236
237 237 script_src = """#!{executable}
238 238 # This script was automatically generated by setup.py
239 239 if __name__ == '__main__':
240 240 from {mod} import {func}
241 241 {func}()
242 242 """
243 243
244 244 class build_scripts_entrypt(build_scripts):
245 245 """Build the command line scripts
246 246
247 247 Parse setuptools style entry points and write simple scripts to run the
248 248 target functions.
249 249
250 250 On Windows, this also creates .cmd wrappers for the scripts so that you can
251 251 easily launch them from a command line.
252 252 """
253 253 def run(self):
254 254 self.mkpath(self.build_dir)
255 255 outfiles = []
256 256 for script in find_entry_points():
257 257 name, entrypt = script.split('=')
258 258 name = name.strip()
259 259 entrypt = entrypt.strip()
260 260 outfile = os.path.join(self.build_dir, name)
261 261 outfiles.append(outfile)
262 262 print('Writing script to', outfile)
263 263
264 264 mod, func = entrypt.split(':')
265 265 with open(outfile, 'w') as f:
266 266 f.write(script_src.format(executable=sys.executable,
267 267 mod=mod, func=func))
268 268
269 269 if sys.platform == 'win32':
270 270 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
271 271 # command line
272 272 cmd_file = os.path.join(self.build_dir, name + '.cmd')
273 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
273 cmd = r'@"{python}" "%~dp0\{script}" %*\r\n'.format(
274 274 python=sys.executable, script=name)
275 275 log.info("Writing %s wrapper script" % cmd_file)
276 276 with open(cmd_file, 'w') as f:
277 277 f.write(cmd)
278 278
279 279 return outfiles, outfiles
280 280
281 281 class install_lib_symlink(Command):
282 282 user_options = [
283 283 ('install-dir=', 'd', "directory to install to"),
284 284 ]
285 285
286 286 def initialize_options(self):
287 287 self.install_dir = None
288 288
289 289 def finalize_options(self):
290 290 self.set_undefined_options('symlink',
291 291 ('install_lib', 'install_dir'),
292 292 )
293 293
294 294 def run(self):
295 295 if sys.platform == 'win32':
296 296 raise Exception("This doesn't work on Windows.")
297 297 pkg = os.path.join(os.getcwd(), 'IPython')
298 298 dest = os.path.join(self.install_dir, 'IPython')
299 299 if os.path.islink(dest):
300 300 print('removing existing symlink at %s' % dest)
301 301 os.unlink(dest)
302 302 print('symlinking %s -> %s' % (pkg, dest))
303 303 os.symlink(pkg, dest)
304 304
305 305 class unsymlink(install):
306 306 def run(self):
307 307 dest = os.path.join(self.install_lib, 'IPython')
308 308 if os.path.islink(dest):
309 309 print('removing symlink at %s' % dest)
310 310 os.unlink(dest)
311 311 else:
312 312 print('No symlink exists at %s' % dest)
313 313
314 314 class install_symlinked(install):
315 315 def run(self):
316 316 if sys.platform == 'win32':
317 317 raise Exception("This doesn't work on Windows.")
318 318
319 319 # Run all sub-commands (at least those that need to be run)
320 320 for cmd_name in self.get_sub_commands():
321 321 self.run_command(cmd_name)
322 322
323 323 # 'sub_commands': a list of commands this command might have to run to
324 324 # get its work done. See cmd.py for more info.
325 325 sub_commands = [('install_lib_symlink', lambda self:True),
326 326 ('install_scripts_sym', lambda self:True),
327 327 ]
328 328
329 329 class install_scripts_for_symlink(install_scripts):
330 330 """Redefined to get options from 'symlink' instead of 'install'.
331 331
332 332 I love distutils almost as much as I love setuptools.
333 333 """
334 334 def finalize_options(self):
335 335 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
336 336 self.set_undefined_options('symlink',
337 337 ('install_scripts', 'install_dir'),
338 338 ('force', 'force'),
339 339 ('skip_build', 'skip_build'),
340 340 )
341 341
342 342
343 343 #---------------------------------------------------------------------------
344 344 # VCS related
345 345 #---------------------------------------------------------------------------
346 346
347 347
348 348 def git_prebuild(pkg_dir, build_cmd=build_py):
349 349 """Return extended build or sdist command class for recording commit
350 350
351 351 records git commit in IPython.utils._sysinfo.commit
352 352
353 353 for use in IPython.utils.sysinfo.sys_info() calls after installation.
354 354 """
355 355
356 356 class MyBuildPy(build_cmd):
357 357 ''' Subclass to write commit data into installation tree '''
358 358 def run(self):
359 359 # loose as `.dev` is suppose to be invalid
360 360 print("check version number")
361 loose_pep440re = re.compile('^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
361 loose_pep440re = re.compile(r'^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
362 362 if not loose_pep440re.match(version):
363 363 raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % version)
364 364
365 365
366 366 build_cmd.run(self)
367 367 # this one will only fire for build commands
368 368 if hasattr(self, 'build_lib'):
369 369 self._record_commit(self.build_lib)
370 370
371 371 def make_release_tree(self, base_dir, files):
372 372 # this one will fire for sdist
373 373 build_cmd.make_release_tree(self, base_dir, files)
374 374 self._record_commit(base_dir)
375 375
376 376 def _record_commit(self, base_dir):
377 377 import subprocess
378 378 proc = subprocess.Popen('git rev-parse --short HEAD',
379 379 stdout=subprocess.PIPE,
380 380 stderr=subprocess.PIPE,
381 381 shell=True)
382 382 repo_commit, _ = proc.communicate()
383 383 repo_commit = repo_commit.strip().decode("ascii")
384 384
385 385 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
386 386 if os.path.isfile(out_pth) and not repo_commit:
387 387 # nothing to write, don't clobber
388 388 return
389 389
390 390 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
391 391
392 392 # remove to avoid overwriting original via hard link
393 393 try:
394 394 os.remove(out_pth)
395 395 except (IOError, OSError):
396 396 pass
397 397 with open(out_pth, 'w') as out_file:
398 398 out_file.writelines([
399 399 '# GENERATED BY setup.py\n',
400 400 'commit = u"%s"\n' % repo_commit,
401 401 ])
402 402 return MyBuildPy
403 403
General Comments 0
You need to be logged in to leave comments. Login now