from __future__ import annotations from docutils import nodes from sphinx.application import Sphinx from sphinx.util.docutils import SphinxDirective, SphinxRole from sphinx.util.typing import ExtensionMetadata from sphinx.directives.code import LiteralInclude, container_wrapper import requests from requests.exceptions import RequestException from docutils.parsers.rst import directives class ExtInclude(LiteralInclude): """A directive to include code that comes from an url Sample use:: .. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/firefox/00-proxy.yml :linenos: :language: yaml :caption: this is a interesting code - parameter required - linenos, language and caption are optionnal. :default language: yaml :default caption: extinclude parameter (url) """ def run(self) -> list[nodes.Node]: url = self.arguments[0] try: headers = { 'accept': 'application/text', 'Content-Type': 'application/text', } response = requests.get(url, headers=headers) response.raise_for_status() # This will raise an exception for 4xx/5xx status codes except requests.exceptions.HTTPError as e: if response.status_code == 404: error_msg = f"extinclude: URL not found (404): {url}" else: error_msg = f"extinclude: HTTP error {response.status_code}: {url}" # Create an error node that will be displayed in the documentation error_node = nodes.error() para = nodes.paragraph() para += nodes.Text(error_msg) error_node += para self.state.document.reporter.warning(error_msg, line=self.lineno) return [error_node] except requests.exceptions.RequestException as e: error_msg = f"extinclude: Failed to fetch URL {url}: {str(e)}" # Create an error node that will be displayed in the documentation error_node = nodes.error() para = nodes.paragraph() para += nodes.Text(error_msg) error_node += para self.state.document.reporter.warning(error_msg, line=self.lineno) return [error_node] code = response.text literal = nodes.literal_block(code, code) if 'language' in self.options: literal['language'] = self.options['language'] else: literal['language'] = 'yaml' literal['linenos'] = 'linenos' in self.options if 'caption' in self.options: caption = self.options.get('caption') else: caption = url literal['caption'] = caption if 'name' in self.options: literal['name'] = self.options.get('name') literal = container_wrapper(self, literal, caption) self.add_name(literal) return [literal] def setup(app: Sphinx) -> ExtensionMetadata: app.add_directive('extinclude', ExtInclude) return { 'version': '0.1', 'parallel_read_safe': True, 'parallel_write_safe': True, }