【问题标题】:How do I make my sphinx directive add assets to _static folder?如何让我的 sphinx 指令将资产添加到 _static 文件夹?
【发布时间】:2020-09-03 16:46:41
【问题描述】:

我正在构建一个自定义 sphinx 扩展和指令,以在 sphinx 和 ReadTheDocs 上呈现交互式图表。图表的实际数据位于.json 文件中。

.rst 文档中,包括一个如下所示的图表:

.. chart:: charts/test.json
    :width: 400px
    :height: 250px

    This is the caption of the chart, yay...

我采取的步骤是:

  1. 使用与图表相同大小的占位符呈现文档(加载微调器)

  2. 使用 jQuery(在添加的 javascript 文件中)获取 json 文件(在本例中为 charts/test.json)的 URI(我将其作为属性添加到我的指令中的 dom 节点)

  3. 使用 jQuery 获取文件并解析 JSON

  4. 成功获取数据后,使用 plotly 库将其呈现为图表并使用 jQuery 删除占位符


指令看起来像:


class PlotlyChartDirective(Directive):
    """ Top-level plotly chart directive """

    has_content = True

    def px_value(argument):
        # This is not callable as self.align.  We cannot make it a
        # staticmethod because we're saving an unbound method in
        # option_spec below.
        return directives.length_or_percentage_or_unitless(argument, 'px')

    required_arguments = 1
    optional_arguments = 0

    option_spec = {
        # TODO allow static images for PDF renders   'altimage': directives.unchanged,
        'height': px_value,
        'width': px_value,
    }

    def run(self):
        """ Parse a plotly chart directive """

        self.assert_has_content()
        env = self.state.document.settings.env
        # Ensure the current chart ID is initialised in the environment
        if 'next_plotly_chart_id' not in env.temp_data:
            env.temp_data['next_plotly_chart_id'] = 0

        id = env.temp_data['next_plotly_chart_id']

        # Handle the URI of the *.json asset
        uri = directives.uri(self.arguments[0])

        # Create the main node container and store the URI of the file which will be collected later
        node = nodes.container()
        node['classes'] = ['sphinx-plotly']

        # Increment the ID counter ready for the next chart
        env.temp_data['next_plotly_chart_id'] += 1

        # Only if its a supported builder do we proceed (otherwise return an empty node)
        if env.app.builder.name in get_compatible_builders(env.app):

            chart_node = nodes.container()
            chart_node['classes'] = ['sphinx-plotly-chart', f"sphinx-plotly-chart-id-{id}", f"sphinx-plotly-chart-uri-{uri}"]

            placeholder_node = nodes.container()
            placeholder_node['classes'] = ['sphinx-plotly-placeholder', f"sphinx-plotly-placeholder-{id}"]
            placeholder_node += nodes.caption('', 'Loading...')

            node += chart_node
            node += placeholder_node

            # Add optional chart caption and legend (inspired by Figure directive)
            if self.content:
                caption_node = nodes.Element()  # Anonymous container for parsing
                self.state.nested_parse(self.content, self.content_offset, caption_node)
                first_node = caption_node[0]
                if isinstance(first_node, nodes.paragraph):
                    caption = nodes.caption(first_node.rawsource, '', *first_node.children)
                    caption.source = first_node.source
                    caption.line = first_node.line
                    node += caption
                elif not (isinstance(first_node, nodes.comment) and len(first_node) == 0):
                    error = self.state_machine.reporter.error(
                        'Chart caption must be a paragraph or empty comment.',
                        nodes.literal_block(self.block_text, self.block_text),
                        line=self.lineno)
                    return [node, error]
                if len(caption_node) > 1:
                    node += nodes.legend('', *caption_node[1:])

        return [node]

无论我在哪里查看 FigureImage 指令的源代码(我以此为基础),我都无法弄清楚如何将实际图像从其输入位置复制到构建目录中的静态文件夹。

如果不将参数中指定的*.json 文件复制到我的指令中,我总是得到一个找不到文件!

我努力寻找支持方法(我假设 Sphinx app 实例将具有 add_static_file() 方法,就像它具有 add_css_file() 方法一样)。

我还尝试添加要复制到 app 实例的文件列表,然后在构建结束时复制资产(但由于无法向 Sphinx 类添加属性而受阻)

简单的问题

在自定义指令中,如何将资产(其路径由指令的参数指定)复制到构建的 _static 目录?

【问题讨论】:

    标签: python python-sphinx directive read-the-docs docutils


    【解决方案1】:

    我意识到直接在指令的 run() 方法中使用 sphinx 的 copyfile 实用程序(仅在文件更改时复制,所以很快)很简单。

    查看我如何获取src_uribuild_uri 并在此更新后的Directive 中直接复制文件:

    class PlotlyChartDirective(Directive):
        """ Top-level plotly chart directive """
    
        has_content = True
    
        def px_value(argument):
            # This is not callable as self.align.  We cannot make it a
            # staticmethod because we're saving an unbound method in
            # option_spec below.
            return directives.length_or_percentage_or_unitless(argument, 'px')
    
        required_arguments = 1
        optional_arguments = 0
    
        option_spec = {
            # TODO allow static images for PDF renders   'altimage': directives.unchanged,
            'height': px_value,
            'width': px_value,
        }
    
        def run(self):
            """ Parse a plotly chart directive """
            self.assert_has_content()
            env = self.state.document.settings.env
    
            # Ensure the current chart ID is initialised in the environment
            if 'next_plotly_chart_id' not in env.temp_data:
                env.temp_data['next_plotly_chart_id'] = 0
    
            # Get the ID of this chart
            id = env.temp_data['next_plotly_chart_id']
    
            # Handle the src and destination URI of the *.json asset
            uri = directives.uri(self.arguments[0])
            src_uri = os.path.join(env.app.builder.srcdir, uri)
            build_uri = os.path.join(env.app.builder.outdir, '_static', uri)
    
            # Create the main node container and store the URI of the file which will be collected later
            node = nodes.container()
            node['classes'] = ['sphinx-plotly']
    
            # Increment the ID counter ready for the next chart
            env.temp_data['next_plotly_chart_id'] += 1
    
            # Only if its a supported builder do we proceed (otherwise return an empty node)
            if env.app.builder.name in get_compatible_builders(env.app):
    
                # Make the directories and copy file (if file has changed)
                destdir = os.path.dirname(build_uri)
                if not os.path.exists(destdir):
                    os.makedirs(destdir)
    
                copyfile(src_uri, build_uri)
    
                width = self.options.pop('width', DEFAULT_WIDTH)
                height = self.options.pop('height', DEFAULT_HEIGHT)
    
                chart_node = nodes.container()
                chart_node['classes'] = ['sphinx-plotly-chart', f"sphinx-plotly-chart-id-{id}", f"sphinx-plotly-chart-uri-{uri}"]
    
                placeholder_node = nodes.container()
                placeholder_node['classes'] = ['sphinx-plotly-placeholder', f"sphinx-plotly-placeholder-{id}"]
                placeholder_node += nodes.caption('', 'Loading...')
    
                node += chart_node
                node += placeholder_node
    
                # Add optional chart caption and legend (as per figure directive)
                if self.content:
                    caption_node = nodes.Element()  # Anonymous container for parsing
                    self.state.nested_parse(self.content, self.content_offset, caption_node)
                    first_node = caption_node[0]
                    if isinstance(first_node, nodes.paragraph):
                        caption = nodes.caption(first_node.rawsource, '', *first_node.children)
                        caption.source = first_node.source
                        caption.line = first_node.line
                        node += caption
                    elif not (isinstance(first_node, nodes.comment) and len(first_node) == 0):
                        error = self.state_machine.reporter.error(
                            'Chart caption must be a paragraph or empty comment.',
                            nodes.literal_block(self.block_text, self.block_text),
                            line=self.lineno)
                        return [node, error]
                    if len(caption_node) > 1:
                        node += nodes.legend('', *caption_node[1:])
    
            return [node]
    
    

    【讨论】:

    • 我正在使用内置的 .. raw:: html 并且我试图引用 index.rst 同一目录中的图像 foo.jpg,但是 ReadTheDocs 上的输出不包含我的图像 foo.jpg。我是否必须使用自定义指令来满足这种可能的常见需求?
    猜你喜欢
    • 2016-12-29
    • 2023-03-07
    • 1970-01-01
    • 2021-05-23
    • 1970-01-01
    • 2013-09-28
    • 2012-03-10
    • 1970-01-01
    • 2014-09-25
    相关资源
    最近更新 更多