File: viewer_directive.py

package info (click to toggle)
python-pyvista 0.46.4-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 176,968 kB
  • sloc: python: 94,346; sh: 216; makefile: 70
file content (107 lines) | stat: -rw-r--r-- 4,324 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
"""Viewer directive module."""

from __future__ import annotations

import os
from pathlib import Path
import shutil

from docutils import nodes
from docutils.parsers.rst import Directive
from docutils.utils import relative_path  # pragma: no cover
from sphinx.util import logging
from trame_vtk.tools.vtksz2html import HTML_VIEWER_PATH

logger = logging.getLogger(__name__)


def is_path_relative_to(path, other):
    """Path.is_relative_to was introduced in Python 3.9 [1].

    Provide a replacement that works for all supported versions

    [1] https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_relative_to.
    """
    return path.is_relative_to(other)


class OfflineViewerDirective(Directive):
    required_arguments = 1
    optional_arguments = 0
    final_argument_whitespace = True
    has_content = True

    def run(self):  # pragma: no cover
        source_dir = Path(self.state.document.settings.env.app.srcdir)
        output_dir = Path(self.state.document.settings.env.app.outdir)
        # _build directory
        build_dir = Path(self.state.document.settings.env.app.outdir).parent

        # this is the path passed to 'offlineviewer:: <path>` directive
        source_file = str(Path(self.state.document.current_source).parent / self.arguments[0])
        source_file = Path(source_file).absolute().resolve()
        if not Path(source_file).is_file():
            logger.warning(f'Source file {source_file} does not exist.')
            return []

        # copy viewer HTML to _static
        static_path = Path(output_dir) / '_static'
        static_path.mkdir(exist_ok=True)
        if not Path(static_path, Path(HTML_VIEWER_PATH).name).exists():
            shutil.copy(HTML_VIEWER_PATH, static_path)

        # calculate the scene asset path relative to the build directory and
        # recreate the directory structure under output_dir/_images. This
        # avoids overriding files with the same name e.g. index-x_yy_zz.vtksz will
        # be generated by any index.rst file and we have a number of them.
        # Example:
        # source_file ${HOME}/pyvista/pyvista/doc/_build/plot_directive/getting-started/index-2_00_00.vtksz # noqa:E501
        # dest_partial_path: plot_directive/getting-started
        # dest_path: ${HOME}/pyvista/pyvista/doc/_build/html/_images/plot_directive/getting-started/index-2_00_00.vtksz  # noqa: E501

        if is_path_relative_to(source_file, build_dir):
            dest_partial_path = Path(source_file.parent).relative_to(build_dir)
        elif is_path_relative_to(source_file, source_dir):
            dest_partial_path = Path(source_file.parent).relative_to(source_dir)
        else:
            logger.warning(
                f'Source file {source_file} is not a subpath of either the build directory of the '
                f'source directory. Cannot extract base path',
            )
            return []

        dest_path = Path(output_dir).joinpath('_images').joinpath(dest_partial_path)
        dest_path.mkdir(parents=True, exist_ok=True)
        dest_file = dest_path.joinpath(source_file.name).resolve()
        if source_file != dest_file:
            try:
                shutil.copy(source_file, dest_file)
            except Exception as e:  # noqa: BLE001
                logger.warning(f'Failed to copy file from {source_file} to {dest_file}: {e}')

        # Compute the relative path of the current source to the source directory,
        # which is the same as the relative path of the '_static' directory to the
        # generated HTML file.
        relpath_to_source_root = relative_path(self.state.document.current_source, source_dir)
        rel_viewer_path = (
            Path() / relpath_to_source_root / '_static' / Path(HTML_VIEWER_PATH).name
        ).as_posix()
        rel_asset_path = Path(os.path.relpath(dest_file, static_path)).as_posix()
        html = (
            f"<iframe src='{rel_viewer_path}?fileURL={rel_asset_path}' "
            "width='100%%' height='400px' frameborder='0'></iframe>"
        )

        raw_node = nodes.raw('', html, format='html')

        return [raw_node]


def setup(app):
    app.add_directive('offlineviewer', OfflineViewerDirective)

    return {
        'version': '0.1',
        'parallel_read_safe': True,
        'parallel_write_safe': True,
    }