|
68 | 68 | 'text/plain',
|
69 | 69 | )
|
70 | 70 |
|
71 |
| -MIME_TYPE_SUFFIXES = { |
| 71 | +THUMBNAIL_MIME_TYPES = { |
72 | 72 | 'image/svg+xml': '.svg',
|
73 | 73 | 'image/png': '.png',
|
74 | 74 | 'image/jpeg': '.jpg',
|
@@ -410,106 +410,106 @@ def from_notebook_node(self, nb, resources=None, **kw):
|
410 | 410 | resources['nbsphinx_widgets'] = True
|
411 | 411 |
|
412 | 412 | thumbnail = {}
|
413 |
| - thumbnail_filename = None |
414 | 413 |
|
415 | 414 | def warning(msg, *args):
|
416 | 415 | logger.warning(
|
417 |
| - '"nbsphinx-thumbnail": ' + msg, *args, |
| 416 | + '"nbsphinx-thumbnail" in cell %s: ' + msg, cell_index, *args, |
418 | 417 | location=resources.get('nbsphinx_docname'),
|
419 | 418 | type='nbsphinx', subtype='thumbnail')
|
420 | 419 | thumbnail['filename'] = _BROKEN_THUMBNAIL
|
421 | 420 |
|
422 | 421 | for cell_index, cell in enumerate(nb.cells):
|
423 |
| - # figure out if this cell is explicitly tagged |
424 |
| - # but if it's not, we'll default to the last figure in the notebook |
425 |
| - # if one exists |
426 |
| - metadata_cell = 'nbsphinx-thumbnail' in cell.metadata |
427 |
| - tagged_cell = 'nbsphinx_thubnail' in cell.metadata.get('tags',[]) |
428 |
| - thumbnail_cell = metadata_cell or tagged_cell |
429 |
| - |
430 |
| - if metadata_cell: |
| 422 | + if 'nbsphinx-thumbnail' in cell.metadata: |
431 | 423 | data = cell.metadata['nbsphinx-thumbnail'].copy()
|
432 |
| - output_index = data.pop('output-index', -1) |
| 424 | + output_index = data.pop('output-index', None) |
433 | 425 | tooltip = data.pop('tooltip', '')
|
434 |
| - |
435 | 426 | if data:
|
436 | 427 | warning('Invalid key(s): %s', set(data))
|
437 | 428 | break
|
438 |
| - else: |
439 |
| - output_index = -1 |
| 429 | + elif 'nbsphinx-thumbnail' in cell.metadata.get('tags', []): |
| 430 | + output_index = None |
440 | 431 | tooltip = ''
|
441 |
| - |
442 |
| - if cell.cell_type != 'code': |
443 |
| - if thumbnail_cell: |
444 |
| - warning('Only allowed in code cells; cell %s has type "%s"', |
445 |
| - cell_index, cell.cell_type) |
446 |
| - break |
447 |
| - |
| 432 | + else: |
448 | 433 | continue
|
449 | 434 |
|
450 |
| - if thumbnail and thumbnail_cell: |
451 |
| - warning('Only allowed onced per notebook') |
| 435 | + if cell.cell_type != 'code': |
| 436 | + warning( |
| 437 | + 'Only allowed in code cells; wrong cell type: "%s"', |
| 438 | + cell.cell_type) |
452 | 439 | break
|
453 | 440 |
|
454 |
| - if not cell.outputs: |
455 |
| - if thumbnail_cell: |
456 |
| - warning('No outputs in cell %s', cell_index) |
457 |
| - break |
458 |
| - |
459 |
| - continue |
| 441 | + if thumbnail: |
| 442 | + warning('Only allowed once per notebook') |
| 443 | + break |
460 | 444 |
|
461 |
| - if output_index == -1: |
| 445 | + if output_index is None: |
462 | 446 | output_index = len(cell.outputs) - 1
|
463 |
| - elif output_index >= len(cell.outputs): |
464 |
| - warning('Invalid "output-index" in cell %s: %s', |
465 |
| - cell_index, output_index) |
| 447 | + try: |
| 448 | + suffix = _extract_thumbnail(cell, output_index) |
| 449 | + except _ExtractThumbnailException as e: |
| 450 | + warning(*e.args) |
466 | 451 | break
|
467 | 452 |
|
468 |
| - out = cell.outputs[output_index] |
469 |
| - |
470 |
| - if out.output_type not in {'display_data', 'execute_result'}: |
471 |
| - if thumbnail_cell: |
472 |
| - warning('Unsupported output type in cell %s/output %s: "%s"', |
473 |
| - cell_index, output_index, out.output_type) |
474 |
| - break |
475 |
| - |
476 |
| - continue |
477 |
| - |
478 |
| - for mime_type in DISPLAY_DATA_PRIORITY_HTML: |
479 |
| - if mime_type not in out.data or mime_type not in MIME_TYPE_SUFFIXES: |
480 |
| - continue |
481 |
| - |
482 |
| - thumbnail_filename = '{}_{}_{}{}'.format( |
483 |
| - resources['unique_key'], |
484 |
| - cell_index, |
485 |
| - output_index, |
486 |
| - MIME_TYPE_SUFFIXES[mime_type], |
487 |
| - ) |
488 |
| - break |
489 |
| - else: |
490 |
| - if thumbnail_cell: |
491 |
| - warning('Unsupported MIME type(s) in cell %s/output %s: %s', |
492 |
| - cell_index, output_index, set(out.data)) |
| 453 | + thumbnail['filename'] = '{}_{}_{}{}'.format( |
| 454 | + resources['unique_key'], |
| 455 | + cell_index, |
| 456 | + output_index, |
| 457 | + suffix, |
| 458 | + ) |
| 459 | + if tooltip: |
| 460 | + thumbnail['tooltip'] = tooltip |
| 461 | + |
| 462 | + if not thumbnail: |
| 463 | + # No explicit thumbnails were specified in the notebook. |
| 464 | + # Now we are looking for the last output image in the notebook. |
| 465 | + for cell_index, cell in reversed(list(enumerate(nb.cells))): |
| 466 | + if cell.cell_type == 'code': |
| 467 | + for output_index in reversed(range(len(cell.outputs))): |
| 468 | + try: |
| 469 | + suffix = _extract_thumbnail(cell, output_index) |
| 470 | + except _ExtractThumbnailException: |
| 471 | + continue |
| 472 | + thumbnail['filename'] = '{}_{}_{}{}'.format( |
| 473 | + resources['unique_key'], |
| 474 | + cell_index, |
| 475 | + output_index, |
| 476 | + suffix, |
| 477 | + ) |
| 478 | + # NB: we use this as marker for implicit thumbnail: |
| 479 | + thumbnail['tooltip'] = None |
| 480 | + break |
| 481 | + else: |
| 482 | + continue |
493 | 483 | break
|
494 | 484 |
|
495 |
| - continue |
| 485 | + if thumbnail: |
| 486 | + resources['nbsphinx_thumbnail'] = thumbnail |
| 487 | + return rststr, resources |
496 | 488 |
|
497 |
| - if thumbnail_cell: |
498 |
| - thumbnail['filename'] = thumbnail_filename |
499 |
| - thumbnail['implicit'] = False |
500 |
| - if tooltip: |
501 |
| - thumbnail['tooltip'] = tooltip |
502 | 489 |
|
503 |
| - break |
| 490 | +class _ExtractThumbnailException(Exception): |
| 491 | + """Internal exception thrown by _extract_thumbnail().""" |
504 | 492 |
|
505 |
| - else: |
506 |
| - # default to the last figure in the notebook, if it's a valid thumbnail |
507 |
| - if thumbnail_filename: |
508 |
| - thumbnail['filename'] = thumbnail_filename |
509 |
| - thumbnail['implicit'] = True |
510 | 493 |
|
511 |
| - resources['nbsphinx_thumbnail'] = thumbnail |
512 |
| - return rststr, resources |
| 494 | +def _extract_thumbnail(cell, output_index): |
| 495 | + if not cell.outputs: |
| 496 | + raise _ExtractThumbnailException('No outputs') |
| 497 | + if output_index not in range(len(cell.outputs)): |
| 498 | + raise _ExtractThumbnailException( |
| 499 | + 'Invalid "output-index": %s', output_index) |
| 500 | + out = cell.outputs[output_index] |
| 501 | + if out.output_type not in {'display_data', 'execute_result'}: |
| 502 | + raise _ExtractThumbnailException( |
| 503 | + 'Unsupported output type in output %s: "%s"', |
| 504 | + output_index, out.output_type) |
| 505 | + for mime_type in DISPLAY_DATA_PRIORITY_HTML: |
| 506 | + if mime_type not in out.data or mime_type not in THUMBNAIL_MIME_TYPES: |
| 507 | + continue |
| 508 | + return THUMBNAIL_MIME_TYPES[mime_type] |
| 509 | + else: |
| 510 | + raise _ExtractThumbnailException( |
| 511 | + 'Unsupported MIME type(s) in output %s: %s', |
| 512 | + output_index, set(out.data)) |
513 | 513 |
|
514 | 514 |
|
515 | 515 | class NotebookParser(rst.Parser):
|
@@ -1735,24 +1735,26 @@ def has_wildcard(pattern):
|
1735 | 1735 | conf_py_thumbnail = candidate
|
1736 | 1736 |
|
1737 | 1737 | thumbnail = app.env.nbsphinx_thumbnails.get(doc, {})
|
| 1738 | + # NB: "None" is used as marker for implicit thumbnail: |
1738 | 1739 | tooltip = thumbnail.get('tooltip', '')
|
1739 | 1740 | filename = thumbnail.get('filename', '')
|
1740 |
| - was_implicit_thumbnail = thumbnail.get('implicit', True) |
1741 | 1741 |
|
1742 |
| - # thumbnail priority: broken, explicit in notebook, from conf.py |
1743 |
| - # implicit in notebook, default |
| 1742 | + # thumbnail priority: broken, explicit in notebook, |
| 1743 | + # from conf.py, implicit in notebook, default |
1744 | 1744 | if filename is _BROKEN_THUMBNAIL:
|
1745 | 1745 | filename = os.path.join(
|
1746 | 1746 | base, '_static', 'nbsphinx-broken-thumbnail.svg')
|
1747 |
| - elif filename and not was_implicit_thumbnail: |
| 1747 | + elif filename and tooltip is not None: |
1748 | 1748 | # thumbnail from tagged cell or metadata
|
1749 | 1749 | filename = os.path.join(
|
1750 | 1750 | base, app.builder.imagedir, filename)
|
1751 | 1751 | elif conf_py_thumbnail:
|
1752 | 1752 | # NB: Settings from conf.py can be overwritten in notebook
|
1753 | 1753 | filename = os.path.join(base, conf_py_thumbnail)
|
1754 | 1754 | elif filename:
|
1755 |
| - # implicit thumbnail from an image in the notebook |
| 1755 | + # implicit thumbnail from the last image in the notebook |
| 1756 | + assert tooltip is None |
| 1757 | + tooltip = '' |
1756 | 1758 | filename = os.path.join(
|
1757 | 1759 | base, app.builder.imagedir, filename)
|
1758 | 1760 | else:
|
|
0 commit comments