From 8c44ec8d68afb4414f37eb128dc831ab84c5ae93 Mon Sep 17 00:00:00 2001 From: Kacper Bojakowski Date: Thu, 21 May 2026 08:45:05 +0200 Subject: [PATCH 1/2] Add dynamic lists to PoC --- .gitignore | 3 + conf.py | 48 ++- plugins/ros_related_articles.py | 224 ++++++++++ plugins/ros_related_packages.py | 218 ++++++++++ source/How-To-Guides.rst | 1 + .../Migrating-Interfaces.rst | 4 + ...ingle-Package-Define-And-Use-Interface.rst | 259 ++++++++++++ .../Creating-A-Workspace.rst | 4 + .../Creating-Your-First-ROS2-Package.rst | 9 + source/_static/related_packages.js | 393 ++++++++++++++++++ source/_static/vendor/js-yaml.min.js | 2 + source/_static/vendor/pako.min.js | 2 + tools/rosdistro_cache_proxy.py | 195 +++++++++ tools/serve_docs_with_proxy.py | 198 +++++++++ 14 files changed, 1559 insertions(+), 1 deletion(-) create mode 100644 plugins/ros_related_articles.py create mode 100644 plugins/ros_related_packages.py create mode 100644 source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst create mode 100644 source/_static/related_packages.js create mode 100644 source/_static/vendor/js-yaml.min.js create mode 100644 source/_static/vendor/pako.min.js create mode 100644 tools/rosdistro_cache_proxy.py create mode 100644 tools/serve_docs_with_proxy.py diff --git a/.gitignore b/.gitignore index 24c42db21a7..8ae912a4fb1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ __pycache__ ros2doc/ .DS_Store .env + +# Downloaded at HTML build time for browser-side package lists (large). +source/_static/rosdistro_cache/*.yaml.gz diff --git a/conf.py b/conf.py index 2a9def973fd..f4987d8e922 100644 --- a/conf.py +++ b/conf.py @@ -89,6 +89,8 @@ 'sphinx_adopters', 'sphinxcontrib.googleanalytics', 'sphinxcontrib.mermaid', + 'ros_related_packages', + 'ros_related_articles', ] # Intersphinx mapping @@ -182,7 +184,51 @@ # Relative to html_static_path html_css_files = ['custom.css', 'adopters.css'] -html_js_files = ['adopters.js'] +html_js_files = [ + ('vendor/pako.min.js', {'defer': ''}), + ('vendor/js-yaml.min.js', {'defer': ''}), + 'adopters.js', + 'related_packages.js', +] + +# Runtime proxy endpoint for freshest rosdistro cache data (same-origin). +# Default matches production: /api/rosdistro-cache/{distro}-cache.yaml.gz +# Override with ROS_RELATED_PACKAGES_PROXY_URL; set to empty string to disable +# proxy and use bundled _static fallback only. +# Local testing: python tools/serve_docs_with_proxy.py (serves build/html + /api/). +def _normalize_ros_related_packages_proxy_url(raw: str) -> str: + """Return a browser-safe proxy template. + + On Windows, GNU make / MSYS (common even when the terminal is PowerShell) can + rewrite ``/api/...`` into ``C:/Program Files/Git/api/...``. Recover the + intended same-origin path when that happens. + """ + value = (raw or '').strip() + if not value: + return '' + + normalized = value.replace('\\', '/') + marker = '/api/rosdistro-cache/' + idx = normalized.find(marker) + if idx != -1: + return normalized[idx:] + + if normalized.startswith('api/rosdistro-cache/'): + return '/' + normalized + + return value + + +_DEFAULT_ROS_RELATED_PACKAGES_PROXY_URL = ( + '/api/rosdistro-cache/{distro}-cache.yaml.gz' +) + +ros_related_packages_proxy_url = _normalize_ros_related_packages_proxy_url( + os.environ.get( + 'ROS_RELATED_PACKAGES_PROXY_URL', + _DEFAULT_ROS_RELATED_PACKAGES_PROXY_URL, + ) +) # -- Options for HTMLHelp output ------------------------------------------ diff --git a/plugins/ros_related_articles.py b/plugins/ros_related_articles.py new file mode 100644 index 00000000000..be55a54a6e0 --- /dev/null +++ b/plugins/ros_related_articles.py @@ -0,0 +1,224 @@ +# Copyright 2026 Open Robotics and contributors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Sphinx directive for build-time related article lists.""" + +from __future__ import annotations + +from typing import List, TypedDict + +from docutils import nodes +from docutils.parsers.rst import directives +from sphinx.util.docutils import SphinxDirective + + +def _normalize_field_name(raw: str) -> str: + """Normalize a metadata key for comparison (e.g. ``Experience`` -> ``experience``).""" + name = raw.strip().lower().rstrip(':') + return name.replace(' ', '-') + + +def _field_value_from_doctree(document: nodes.document, wanted: str) -> str | None: + """Return the body of the first matching docinfo/rST field in the document.""" + wanted_norm = _normalize_field_name(wanted) + for field in document.traverse(nodes.field): + children = getattr(field, 'children', ()) or () + if len(children) < 2: + continue + label = children[0].astext() + if _normalize_field_name(label) != wanted_norm: + continue + return children[1].astext().strip() + return None + + +def _meta_get(metadata: dict, *names: str) -> str | None: + """Look up metadata using several possible keys (Sphinx/docutils variants).""" + for name in names: + for key, val in metadata.items(): + if not val: + continue + if _normalize_field_name(str(key)) == _normalize_field_name(name): + return str(val).strip() + return None + + +def _meta_content_from_docutils(document: nodes.document, meta_name: str) -> str | None: + """Read ``docutils.nodes.meta`` emitted by ``.. meta::``.""" + for node in document.traverse(nodes.meta): + if node.get('name') != meta_name: + continue + raw = node.get('content') + if raw: + return str(raw).strip() + return None + + +def _positive_int_option(argument: str) -> int: + """Parse a positive integer option for the directive.""" + if argument is None: + raise ValueError('option requires a number') + value = int(argument) + if value < 1: + raise ValueError('must be positive') + return value + + +class RelatedArticle(TypedDict): + docname: str + title: str + area: str + experience: str + + +def _normalized_value(raw: str) -> str: + """Normalize metadata value for stable matching.""" + return ' '.join(raw.strip().lower().split()) + + +class RosRelatedArticlesNode(nodes.General, nodes.Element): + """Placeholder node replaced during ``doctree-resolved``.""" + + +class RosRelatedArticlesDirective(SphinxDirective): + """Emit a placeholder for static related-article links. + + Uses page metadata values from ``.. meta::``: + + .. code-block:: rst + + .. meta:: + :area: Tutorials + :experience: Beginner + """ + + has_content = False + required_arguments = 0 + optional_arguments = 0 + option_spec = {'max': _positive_int_option} + + def run(self) -> List[nodes.Node]: + meta = self.env.metadata.get(self.env.docname, {}) + area = ( + _meta_content_from_docutils(self.state.document, 'area') + or _meta_get(meta, 'area') + or _field_value_from_doctree(self.state.document, 'area') + or '' + ) + experience = ( + _meta_content_from_docutils(self.state.document, 'experience') + or _meta_get(meta, 'experience') + or _field_value_from_doctree(self.state.document, 'experience') + or '' + ) + + if not area or not experience: + raise self.error( + 'ros-related-articles: define both `area` and `experience` ' + 'with `.. meta::` (recommended), or field list metadata.' + ) + + node = RosRelatedArticlesNode() + node['area'] = area + node['experience'] = experience + node['max'] = self.options.get('max', 10) + return [node] + + +def _collect_article_index(env) -> List[RelatedArticle]: + """Build an index of docs that declare both ``area`` and ``experience`` metadata.""" + records: List[RelatedArticle] = [] + for docname in sorted(env.found_docs): + doctree = env.get_doctree(docname) + meta = env.metadata.get(docname, {}) + area = ( + _meta_content_from_docutils(doctree, 'area') + or _meta_get(meta, 'area') + or _field_value_from_doctree(doctree, 'area') + or '' + ) + experience = ( + _meta_content_from_docutils(doctree, 'experience') + or _meta_get(meta, 'experience') + or _field_value_from_doctree(doctree, 'experience') + or '' + ) + if not area or not experience: + continue + title_node = env.titles.get(docname) + title = title_node.astext().strip() if title_node else docname + records.append({ + 'docname': docname, + 'title': title, + 'area': _normalized_value(area), + 'experience': _normalized_value(experience), + }) + return records + + +def build_related_articles_index(app, env) -> None: + """Build metadata map once after Sphinx has read all source documents.""" + env.ros_related_articles_index = _collect_article_index(env) + + +def resolve_related_articles(app, doctree, fromdocname) -> None: + """Replace placeholders with static paragraph + list markup.""" + index: List[RelatedArticle] = getattr(app.env, 'ros_related_articles_index', []) + for node in list(doctree.traverse(RosRelatedArticlesNode)): + area = _normalized_value(str(node.get('area', ''))) + experience = _normalized_value(str(node.get('experience', ''))) + max_items = int(node.get('max', 10)) + + matches = [ + item for item in index + if item['docname'] != fromdocname + and item['area'] == area + and item['experience'] == experience + ] + matches.sort(key=lambda item: item['title'].lower()) + matches = matches[:max_items] + + if not matches: + node.replace_self([]) + continue + + container = nodes.container(classes=['related-articles']) + intro = nodes.paragraph() + intro += nodes.Text('Related articles:') + container += intro + + bullets = nodes.bullet_list() + for item in matches: + refuri = app.builder.get_relative_uri(fromdocname, item['docname']) + link = nodes.reference('', item['title'], refuri=refuri) + entry = nodes.list_item() + para = nodes.paragraph() + para += link + entry += para + bullets += entry + container += bullets + + node.replace_self(container) + + +def setup(app): + app.add_directive('ros-related-articles', RosRelatedArticlesDirective) + app.add_node(RosRelatedArticlesNode) + app.connect('env-updated', build_related_articles_index) + app.connect('doctree-resolved', resolve_related_articles) + return { + 'parallel_read_safe': True, + 'parallel_write_safe': True, + 'version': '1.0.0', + } diff --git a/plugins/ros_related_packages.py b/plugins/ros_related_packages.py new file mode 100644 index 00000000000..8321f18397c --- /dev/null +++ b/plugins/ros_related_packages.py @@ -0,0 +1,218 @@ +# Copyright 2026 Open Robotics and contributors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Sphinx directive for runtime ROS distro package lists (filtered in the browser).""" + +from __future__ import annotations + +import html +import os +import urllib.error +import urllib.request +from typing import List + +from docutils import nodes +from docutils.parsers.rst import directives +from sphinx.util import logging as sphinx_logging +from sphinx.util.docutils import SphinxDirective + +LOGGER = sphinx_logging.getLogger(__name__) + +ROSDISTRO_CACHE_TEMPLATE = ( + 'https://repo.ros2.org/rosdistro_cache/{distro}-cache.yaml.gz' +) + + +def _normalize_field_name(raw: str) -> str: + """Normalize a docinfo field label for comparison (e.g. ``Build-type`` → ``build-type``).""" + name = raw.strip().lower().rstrip(':') + return name.replace(' ', '-') + + +def _field_value_from_doctree(document: nodes.document, wanted: str) -> str | None: + """Return the body of the first matching docinfo/rst field in the document.""" + wanted_norm = _normalize_field_name(wanted) + for field in document.traverse(nodes.field): + children = getattr(field, 'children', ()) or () + if len(children) < 2: + continue + label = children[0].astext() + if _normalize_field_name(label) != wanted_norm: + continue + return children[1].astext().strip() + return None + + +def _meta_get(metadata: dict, *names: str) -> str | None: + """Look up document metadata using several possible keys (Sphinx/docutils variants).""" + for name in names: + for key, val in metadata.items(): + if not val: + continue + if _normalize_field_name(str(key)) == _normalize_field_name(name): + return str(val).strip() + return None + + +def _meta_content_from_docutils(document: nodes.document, meta_name: str) -> str | None: + """Read ``docutils.nodes.meta`` emitted by ``.. meta::`` (typically ```` HTML meta tags). + + Hyphenated names work in rST as ``.. meta::`` fields, e.g. ``:build-type: ament_cmake``. + """ + for node in document.traverse(nodes.meta): + if node.get('name') != meta_name: + continue + raw = node.get('content') + if raw: + return str(raw).strip() + return None + + +def _bundled_cache_href(docname: str, distro: str) -> str: + """Relative URL from this page's HTML file to the downloaded gzip in ``_static/``. + + Sphinx emits sibling paths like ``_static/`` under the HTML root (including per-version + directories for multiversion builds). Depth follows ``docname`` segments (slashes). + """ + depth = docname.count('/') + return ('../' * depth) + f'_static/rosdistro_cache/{distro}-cache.yaml.gz' + + +def _proxy_cache_href(proxy_template: str, distro: str) -> str: + """Build runtime proxy URL from template, replacing ``{distro}``.""" + if not proxy_template: + return '' + return proxy_template.replace('{distro}', distro) + + +def _positive_int_option(argument: str) -> int: + """Parse a positive integer option for the directive.""" + if argument is None: + raise ValueError('option requires a number') + value = int(argument) + if value < 1: + raise ValueError('must be positive') + return value + + +class RosRelatedPackagesDirective(SphinxDirective): + """Emit a placeholder ``div`` filled at runtime by ``related_packages.js``. + + Filter criteria (currently ``build-type``) should be supplied as **HTML meta tags** + via Docutils ``.. meta::`` so values appear in ```` and not in the page body:: + + .. meta:: + :build-type: ament_cmake + + Fallbacks: Sphinx ``env.metadata`` / a visible rST field list ``:build-type:``. + Optional ``:build-type:`` on this directive overrides document metadata. + """ + + has_content = False + required_arguments = 0 + optional_arguments = 0 + option_spec = { + 'build-type': directives.unchanged, + 'max': _positive_int_option, + } + + def run(self) -> List[nodes.Node]: + build_type_opt = self.options.get('build-type') + if build_type_opt: + build_type = build_type_opt.strip() + else: + meta = self.env.metadata.get(self.env.docname, {}) + build_type = ( + _meta_content_from_docutils(self.state.document, 'build-type') + or _meta_get(meta, 'build-type', 'build_type') + or _field_value_from_doctree(self.state.document, 'build-type') + or '' + ) + + if not build_type: + raise self.error( + 'ros-related-packages: define build type with `.. meta::` and ' + '`:build-type: ament_cmake` (recommended), or a `:build-type:` field list, ' + 'or pass `:build-type:` on this directive.' + ) + + max_pkgs = self.options.get('max', 10) + + macros = getattr(self.env.config, 'macros', {}) or {} + distro = macros.get('DISTRO', 'rolling') + + escaped_type = html.escape(build_type, quote=True) + escaped_distro = html.escape(distro, quote=True) + bundled_href = _bundled_cache_href(self.env.docname, distro) + escaped_bundled = html.escape(bundled_href, quote=True) + proxy_template = getattr(self.env.config, 'ros_related_packages_proxy_url', '') + proxy_href = _proxy_cache_href(proxy_template, distro) + escaped_proxy = html.escape(proxy_href, quote=True) + + html_body = ( + '' + ) + return [nodes.raw('', html_body, format='html')] + + +def download_rosdistro_cache(app) -> None: + """Fetch the gzipped rosdistro cache into ``source/_static`` for same-origin loads. + + Sphinx 8+ passes only ``app`` to ``builder-inited``; the builder is ``app.builder``. + """ + builder = app.builder + if builder is None or builder.format != 'html': + return + + macros = getattr(app.config, 'macros', {}) or {} + distro = macros.get('DISTRO', 'rolling') + + dest_dir = os.path.join(app.confdir, 'source', '_static', 'rosdistro_cache') + os.makedirs(dest_dir, exist_ok=True) + dest_path = os.path.join(dest_dir, f'{distro}-cache.yaml.gz') + url = ROSDISTRO_CACHE_TEMPLATE.format(distro=distro) + + request = urllib.request.Request(url, headers={'User-Agent': 'ros2-documentation-build/1.0'}) + try: + with urllib.request.urlopen(request, timeout=120) as response: + data = response.read() + with open(dest_path, 'wb') as handle: + handle.write(data) + except (urllib.error.URLError, OSError, TimeoutError) as exc: + LOGGER.warning( + 'Could not download rosdistro cache from %s (%s). ' + 'Related package lists may not work until the file exists at %s', + url, + exc, + dest_path, + ) + + +def setup(app): + app.add_config_value('ros_related_packages_proxy_url', '', 'html') + app.add_directive('ros-related-packages', RosRelatedPackagesDirective) + app.connect('builder-inited', download_rosdistro_cache) + return { + 'parallel_read_safe': True, + 'parallel_write_safe': True, + 'version': '1.0.0', + } diff --git a/source/How-To-Guides.rst b/source/How-To-Guides.rst index c8f3cb4627c..15707a74d71 100644 --- a/source/How-To-Guides.rst +++ b/source/How-To-Guides.rst @@ -22,6 +22,7 @@ If you are new and looking to learn the ropes, start with the :doc:`Tutorials `__ + +While predefined interface definitions are useful at the beginning, you soon realize that they can't meet all your needs. +That's why the ability to create custom interfaces is essential. + +Creating custom interfaces involves preparing a package, specifying interface definitions, and registering the interfaces in ``package.xml`` and ``CMakeLists.txt``. +Using custom interfaces involves configuring a node to include the interfaces in its source, and configuring the node to build with the interfaces in ``CMakeLists.txt``. + +.. tip:: + + The best practice is to declare interfaces in dedicated interface packages, but sometimes it may be more convenient for you to declare, create and use an interface all in one package. + +Prerequisites +------------- + +#. Install :doc:`ROS 2 <../Installation>`, and create your :doc:`workspace <../Tutorials/Beginner-Client-Libraries/Creating-A-Workspace/Creating-A-Workspace>`. +#. Make sure you understand how to :doc:`create packages <../Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package>`. + +Steps +----- + +.. note:: + + For our examples, we are using the ``msg`` interface type, but the steps below apply to all interface types. + +#. In your workspace ``src`` folder, create a ``more_interfaces`` CMake package with a folder for interface definitions. + For example: + + .. code-block:: console + + $ ros2 pkg create --build-type ament_cmake more_interfaces + $ mkdir -p more_interfaces/msg + + .. note:: + + In ROS 2, interfaces can only be defined in CMake packages. + You can also use `ament_cmake_python `__ to include Python libraries and nodes in a CMake package. + +#. In your interface definitions folder, create a file in which you provide the definitions for the interface. + For example, for a message interface, you can create an ``AddressBook.msg`` file that collects personal data: + + .. code-block:: text + + uint8 PHONE_TYPE_HOME=0 + uint8 PHONE_TYPE_WORK=1 + uint8 PHONE_TYPE_MOBILE=2 + string first_name + string last_name + string phone_number + uint8 phone_type + +#. In ``package.xml``, add the following code to register your package as part of interface groups: + ``rosidl_default_generators``: Needed to generate the code during the build. + ``rosidl_default_runtime``: Needed only at run time. + + .. code-block:: xml + + rosidl_default_generators + rosidl_default_runtime + rosidl_interface_packages + +#. In ``CMakeLists.txt``, add the required code to make the runtime libraries available and to generate source files from your interface definition. + For example: + + .. code-block:: cmake + + find_package(rosidl_default_generators REQUIRED) + set(msg_files "msg/AddressBook.msg") + rosidl_generate_interfaces(${PROJECT_NAME} ${msg_files}) + ament_export_dependencies(rosidl_default_runtime) + +#. In the ``more_interfaces/src`` folder, create a node to interact with your new interface. + For example, for a message interface, create ``publish_address_book.cpp`` with code to publish the message periodically. + + .. code-block:: c++ + + #include + #include + + #include "rclcpp/rclcpp.hpp" + #include "more_interfaces/msg/address_book.hpp" + + using namespace std::chrono_literals; + + class AddressBookPublisher : public rclcpp::Node + { + public: + AddressBookPublisher() + : Node("address_book_publisher") + { + address_book_publisher_ = + this->create_publisher("address_book", 10); + + auto publish_msg = [this]() -> void { + auto message = more_interfaces::msg::AddressBook(); + + message.first_name = "John"; + message.last_name = "Doe"; + message.phone_number = "1234567890"; + message.phone_type = message.PHONE_TYPE_MOBILE; + + std::cout << "Publishing Contact\nFirst:" << message.first_name << + " Last:" << message.last_name << std::endl; + + this->address_book_publisher_->publish(message); + }; + timer_ = this->create_wall_timer(1s, publish_msg); + } + + private: + rclcpp::Publisher::SharedPtr address_book_publisher_; + rclcpp::TimerBase::SharedPtr timer_; + }; + + + int main(int argc, char * argv[]) + { + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + + return 0; + } + +#. In ``CMakeLists.txt``, create a new target so the node builds correctly. + For example: + + .. code-block:: cmake + + find_package(rclcpp REQUIRED) + add_executable(publish_address_book src/publish_address_book.cpp) + target_link_libraries(publish_address_book rclcpp::rclcpp) + install(TARGETS publish_address_book DESTINATION lib/${PROJECT_NAME}) + +#. In ``CMakeLists.txt``, link the node to your interface. + For example: + + .. code-block:: cmake + + rosidl_get_typesupport_target(cpp_typesupport_target ${PROJECT_NAME} rosidl_typesupport_cpp) + target_link_libraries(publish_address_book "${cpp_typesupport_target}") + +#. To test your new interface, do the following: + + a) In your workspace root, build the package. + + b) Source the workspace and run the node that uses the interface. + + For example: + + .. tabs:: + + .. group-tab:: Linux + + .. code-block:: console + + $ cd ~/ros2_ws + $ colcon build --packages-up-to more_interfaces + $ source install/local_setup.bash + $ ros2 run more_interfaces publish_address_book + + .. group-tab:: macOS + + .. code-block:: console + + $ cd ~/ros2_ws + $ colcon build --packages-up-to more_interfaces + $ . install/local_setup.bash + $ ros2 run more_interfaces publish_address_book + + .. group-tab:: Windows + + .. code-block:: console + + $ cd /ros2_ws + $ colcon build --merge-install --packages-up-to more_interfaces + $ call install/local_setup.bat + $ ros2 run more_interfaces publish_address_book + + Or using Powershell: + + .. code-block:: console + + $ install/local_setup.ps1 + $ ros2 run more_interfaces publish_address_book + + c) Check the interface or interact with it. + + For example, for a message interface, you could open another terminal and use the following code: + + .. tabs:: + + .. group-tab:: Linux + + .. code-block:: console + + $ source install/setup.bash + $ ros2 topic echo /address_book + + .. group-tab:: macOS + + .. code-block:: console + + $ . install/setup.bash + $ ros2 topic echo /address_book + + .. group-tab:: Windows + + .. code-block:: console + + $ call install/setup.bat + $ ros2 topic echo /address_book + + Or using Powershell: + + .. code-block:: console + + $ install/setup.ps1 + $ ros2 topic echo /address_book + +Related content +--------------- + +.. ros-related-articles:: + +.. ros-related-packages:: + diff --git a/source/Tutorials/Beginner-Client-Libraries/Creating-A-Workspace/Creating-A-Workspace.rst b/source/Tutorials/Beginner-Client-Libraries/Creating-A-Workspace/Creating-A-Workspace.rst index 2ade50673cc..bbbd706b927 100644 --- a/source/Tutorials/Beginner-Client-Libraries/Creating-A-Workspace/Creating-A-Workspace.rst +++ b/source/Tutorials/Beginner-Client-Libraries/Creating-A-Workspace/Creating-A-Workspace.rst @@ -4,6 +4,10 @@ .. _ROS2Workspace: +.. meta:: + :area: ROS-framework + :experience: beginner, intermediate + Creating a workspace ==================== diff --git a/source/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.rst b/source/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.rst index 3104af34ef0..55720a2ab5a 100644 --- a/source/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.rst +++ b/source/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.rst @@ -4,6 +4,10 @@ .. _CreatePkg: +.. meta:: + :area: ROS-framework + :experience: beginner, intermediate + Creating a package ================== @@ -533,3 +537,8 @@ Next steps Next, let's add something meaningful to a package. You'll start with a simple publisher/subscriber system, which you can choose to write in either :doc:`C++ <./Writing-A-Simple-Cpp-Publisher-And-Subscriber>` or :doc:`Python <./Writing-A-Simple-Py-Publisher-And-Subscriber>`. + +Related content +--------------- + +.. ros-related-articles:: diff --git a/source/_static/related_packages.js b/source/_static/related_packages.js new file mode 100644 index 00000000000..8d7880e8f78 --- /dev/null +++ b/source/_static/related_packages.js @@ -0,0 +1,393 @@ +/** + * Populate ``.js-related-packages`` widgets from the rosdistro cache YAML. + * + * Depends on global ``pako`` (gzip) and ``yaml`` / ``jsyaml`` (js-yaml), loaded + * earlier via html_js_files in conf.py. + */ +(function () { + 'use strict'; + + /** @type {Record>>} */ + var cacheByDistro = {}; + + /** + * Resolve the js-yaml API regardless of how the bundle exposes it. + * + * @returns {{ load: function(string): unknown }} + */ + function yamlApi() { + var g = typeof window !== 'undefined' ? window : globalThis; + /* js-yaml UMD sets ``globalThis.jsyaml`` (see dist/js-yaml.min.js). */ + if (g.jsyaml && typeof g.jsyaml.load === 'function') { + return g.jsyaml; + } + if (g.yaml && typeof g.yaml.load === 'function') { + return g.yaml; + } + throw new Error('js-yaml is not loaded'); + } + + /** + * Directory containing ``related_packages.js`` (ends with slash or empty). + * + * @returns {string} + */ + function scriptBaseUrl() { + var nodes = document.getElementsByTagName('script'); + var i; + var src; + for (i = nodes.length - 1; i >= 0; i--) { + src = nodes[i].src; + if (src && src.indexOf('related_packages.js') !== -1) { + return src.replace(/related_packages\.js([?#].*)?$/i, ''); + } + } + return ''; + } + + /** + * @param {string} distro + * @returns {string|null} + */ + function bundledCacheUrl(distro) { + var base = scriptBaseUrl(); + if (!base) { + return null; + } + return base + 'rosdistro_cache/' + distro + '-cache.yaml.gz'; + } + + /** + * Prefer Sphinx-emitted ``data-bundled-cache-href`` (relative to page); then derive from script URL. + * + * @param {HTMLElement|null} widget + * @param {string} distro + * @returns {string|null} + */ + function resolveBundledAbsoluteUrl(widget, distro) { + var rel = widget && widget.getAttribute('data-bundled-cache-href'); + if (rel && typeof URL !== 'undefined') { + try { + return new URL(rel, window.location.href).href; + } catch (e1) { + /* ignore */ + } + } + return bundledCacheUrl(distro); + } + + /** + * Proxy URL configured by Sphinx via data attribute. + * + * @param {HTMLElement|null} widget + * @param {string} distro + * @returns {string|null} + */ + function resolveProxyUrl(widget, distro) { + var templateUrl = widget && widget.getAttribute('data-proxy-cache-href'); + if (!templateUrl) { + return null; + } + return templateUrl.replace('{distro}', encodeURIComponent(distro)); + } + + /** + * @param {string} distro + * @param {HTMLElement|null} sampleWidget widget from this page (for data-bundled-cache-href) + * @returns {Promise>} + */ + function loadXmls(distro, sampleWidget) { + var cacheKey = + distro + + '|' + + (sampleWidget ? sampleWidget.getAttribute('data-proxy-cache-href') || '' : '') + + '|' + + (sampleWidget ? sampleWidget.getAttribute('data-bundled-cache-href') || '' : ''); + if (cacheByDistro[cacheKey]) { + return cacheByDistro[cacheKey]; + } + cacheByDistro[cacheKey] = fetchAndParse( + distro, + resolveProxyUrl(sampleWidget, distro), + resolveBundledAbsoluteUrl(sampleWidget, distro) + ); + return cacheByDistro[cacheKey]; + } + + /** + * @param {string} distro + * @param {string|null} proxyUrl same-origin backend proxy endpoint (freshest) + * @param {string|null} bundledAbsolute resolved same-origin URL to gzip, if any + * @returns {Promise>} + */ + function fetchAndParse(distro, proxyUrl, bundledAbsolute) { + var remote = + 'https://repo.ros2.org/rosdistro_cache/' + encodeURIComponent(distro) + '-cache.yaml.gz'; + var urls = []; + if (proxyUrl) { + urls.push(proxyUrl); + } + if (bundledAbsolute) { + urls.push(bundledAbsolute); + } + /* Final fallback may still fail in browsers due to upstream CORS. */ + urls.push(remote); + + return tryUrls(urls); + } + + /** + * @param {string[]} urls + * @returns {Promise>} + */ + function tryUrls(urls) { + var i = 0; + + function next(lastErr) { + if (i >= urls.length) { + return Promise.reject(lastErr || new Error('failed to load rosdistro cache')); + } + var url = urls[i]; + i += 1; + var controller = typeof AbortController !== 'undefined' ? new AbortController() : null; + var timer = null; + if (controller && i === 1) { + /* Keep proxy attempt snappy so fallback isn't delayed. */ + timer = setTimeout(function () { + controller.abort(); + }, 6000); + } + return fetch(url, { cache: 'no-cache', signal: controller ? controller.signal : undefined }) + .then(function (res) { + if (timer) { + clearTimeout(timer); + } + if (!res.ok) { + throw new Error('HTTP ' + res.status + ' for ' + url); + } + return res.arrayBuffer(); + }) + .then(function (buf) { + var g = typeof window !== 'undefined' ? window : globalThis; + var inflated = g.pako.inflate(new Uint8Array(buf), { to: 'string' }); + var data = yamlApi().load(inflated); + var xmls = data && data.release_package_xmls; + if (!xmls || typeof xmls !== 'object') { + throw new Error('release_package_xmls missing in rosdistro cache'); + } + if (typeof console !== 'undefined' && console.info) { + console.info('related_packages: loaded rosdistro cache from', url); + } + return /** @type {Record} */ (xmls); + }) + .catch(function (err) { + if (timer) { + clearTimeout(timer); + } + if (typeof console !== 'undefined' && console.warn) { + console.warn('related_packages: failed', url, err); + } + /* Try next URL (e.g. bundled 404 then HTTPS remote — remote may hit CORS). */ + return next(err); + }); + } + + return next(null); + } + + /** + * @param {string} xmlStr + * @returns {string[]} + */ + function extractBuildTypes(xmlStr) { + var out = []; + var re = /]*>([^<]+)<\/build_type>/gi; + var m; + while ((m = re.exec(xmlStr)) !== null) { + out.push(m[1].trim()); + } + return out; + } + + /** + * @param {string} xmlStr + * @param {string} want + * @returns {boolean} + */ + function matchesBuildType(xmlStr, want) { + var types = extractBuildTypes(xmlStr); + var k; + for (k = 0; k < types.length; k += 1) { + if (types[k] === want) { + return true; + } + } + return false; + } + + /** + * @param {string} xmlStr + * @returns {string} + */ + function extractDescription(xmlStr) { + if (typeof DOMParser !== 'undefined') { + try { + var doc = new DOMParser().parseFromString(xmlStr, 'application/xml'); + var parseErr = doc.getElementsByTagName('parsererror'); + if (!parseErr.length) { + var nodes = doc.getElementsByTagName('description'); + if (nodes.length && nodes[0].textContent) { + return nodes[0].textContent.replace(/\s+/g, ' ').trim(); + } + } + } catch (err) { + /* Fall through to regex extraction. */ + } + } + + var match = /]*>([\s\S]*?)<\/description>/i.exec(xmlStr); + if (!match) { + return ''; + } + return match[1] + .replace(//g, '$1') + .replace(/<[^>]*>/g, ' ') + .replace(/\s+/g, ' ') + .trim(); + } + + /** + * @param {string} distro + * @param {string} pkg + * @returns {string} + */ + function docsPackageUrl(distro, pkg) { + return ( + 'https://docs.ros.org/en/' + + encodeURIComponent(distro) + + '/p/' + + encodeURIComponent(pkg) + + '/' + ); + } + + /** + * @param {HTMLElement} el + * @param {Error} err + */ + function showError(el, err) { + el.classList.remove('related-packages--loading'); + el.classList.add('related-packages--error'); + el.innerHTML = + ''; + if (typeof console !== 'undefined' && console.warn) { + console.warn('related_packages:', err); + } + } + + /** + * @param {HTMLElement} el + * @param {Record} xmls + */ + function fillWidget(el, xmls) { + var want = el.getAttribute('data-build-type') || ''; + var max = parseInt(el.getAttribute('data-max') || '10', 10); + var distro = el.getAttribute('data-distro') || 'rolling'; + + var names = Object.keys(xmls).filter(function (name) { + var xmlStr = xmls[name]; + if (typeof xmlStr !== 'string') { + return false; + } + return matchesBuildType(xmlStr, want); + }); + names.sort(function (a, b) { + return a.localeCompare(b); + }); + var picked = names.slice(0, max); + + var ul = document.createElement('ul'); + ul.className = 'related-packages__list'; + var j; + for (j = 0; j < picked.length; j += 1) { + var pkg = picked[j]; + var li = document.createElement('li'); + var a = document.createElement('a'); + var description = extractDescription(xmls[pkg] || ''); + a.href = docsPackageUrl(distro, pkg); + a.textContent = pkg; + a.rel = 'noopener noreferrer'; + li.appendChild(a); + li.appendChild(document.createTextNode(': ' + description)); + ul.appendChild(li); + } + + el.innerHTML = ''; + el.classList.remove('related-packages--loading'); + + if (picked.length === 0) { + var p = document.createElement('p'); + p.className = 'related-packages__empty'; + p.textContent = 'No packages matched this filter.'; + el.appendChild(p); + } else { + var intro = document.createElement('p'); + intro.className = 'related-packages__intro'; + intro.textContent = 'Packages/reference: '; + el.appendChild(intro); + el.appendChild(ul); + } + } + + function fillAll() { + var widgets = document.querySelectorAll('.js-related-packages'); + if (!widgets.length) { + return; + } + + /** @type {Record} */ + var byDistro = {}; + var idx; + for (idx = 0; idx < widgets.length; idx += 1) { + var el = widgets[idx]; + var d = el.getAttribute('data-distro') || 'rolling'; + if (!byDistro[d]) { + byDistro[d] = []; + } + byDistro[d].push(el); + } + + var distroKeys = Object.keys(byDistro); + var di; + for (di = 0; di < distroKeys.length; di += 1) { + (function (distro) { + var group = byDistro[distro]; + loadXmls(distro, group[0]).then( + function (xmls) { + var gi; + for (gi = 0; gi < group.length; gi += 1) { + fillWidget(group[gi], xmls); + } + }, + function (err) { + var ei; + for (ei = 0; ei < group.length; ei += 1) { + showError(group[ei], err); + } + } + ); + })(distroKeys[di]); + } + } + + if (typeof document !== 'undefined') { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', fillAll); + } else { + fillAll(); + } + } +})(); diff --git a/source/_static/vendor/js-yaml.min.js b/source/_static/vendor/js-yaml.min.js new file mode 100644 index 00000000000..bdd8eef542b --- /dev/null +++ b/source/_static/vendor/js-yaml.min.js @@ -0,0 +1,2 @@ +/*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).jsyaml={})}(this,(function(e){"use strict";function t(e){return null==e}var n={isNothing:t,isObject:function(e){return"object"==typeof e&&null!==e},toArray:function(e){return Array.isArray(e)?e:t(e)?[]:[e]},repeat:function(e,t){var n,i="";for(n=0;nl&&(t=i-l+(o=" ... ").length),n-i>l&&(n=i+l-(a=" ...").length),{str:o+e.slice(t,n).replace(/\t/g,"→")+a,pos:i-t+o.length}}function l(e,t){return n.repeat(" ",t-e.length)+e}var c=function(e,t){if(t=Object.create(t||null),!e.buffer)return null;t.maxLength||(t.maxLength=79),"number"!=typeof t.indent&&(t.indent=1),"number"!=typeof t.linesBefore&&(t.linesBefore=3),"number"!=typeof t.linesAfter&&(t.linesAfter=2);for(var i,r=/\r?\n|\r|\0/g,o=[0],c=[],s=-1;i=r.exec(e.buffer);)c.push(i.index),o.push(i.index+i[0].length),e.position<=i.index&&s<0&&(s=o.length-2);s<0&&(s=o.length-1);var u,p,f="",d=Math.min(e.line+t.linesAfter,c.length).toString().length,h=t.maxLength-(t.indent+d+3);for(u=1;u<=t.linesBefore&&!(s-u<0);u++)p=a(e.buffer,o[s-u],c[s-u],e.position-(o[s]-o[s-u]),h),f=n.repeat(" ",t.indent)+l((e.line-u+1).toString(),d)+" | "+p.str+"\n"+f;for(p=a(e.buffer,o[s],c[s],e.position,h),f+=n.repeat(" ",t.indent)+l((e.line+1).toString(),d)+" | "+p.str+"\n",f+=n.repeat("-",t.indent+d+3+p.pos)+"^\n",u=1;u<=t.linesAfter&&!(s+u>=c.length);u++)p=a(e.buffer,o[s+u],c[s+u],e.position-(o[s]-o[s+u]),h),f+=n.repeat(" ",t.indent)+l((e.line+u+1).toString(),d)+" | "+p.str+"\n";return f.replace(/\n$/,"")},s=["kind","multi","resolve","construct","instanceOf","predicate","represent","representName","defaultStyle","styleAliases"],u=["scalar","sequence","mapping"];var p=function(e,t){if(t=t||{},Object.keys(t).forEach((function(t){if(-1===s.indexOf(t))throw new o('Unknown option "'+t+'" is met in definition of "'+e+'" YAML type.')})),this.options=t,this.tag=e,this.kind=t.kind||null,this.resolve=t.resolve||function(){return!0},this.construct=t.construct||function(e){return e},this.instanceOf=t.instanceOf||null,this.predicate=t.predicate||null,this.represent=t.represent||null,this.representName=t.representName||null,this.defaultStyle=t.defaultStyle||null,this.multi=t.multi||!1,this.styleAliases=function(e){var t={};return null!==e&&Object.keys(e).forEach((function(n){e[n].forEach((function(e){t[String(e)]=n}))})),t}(t.styleAliases||null),-1===u.indexOf(this.kind))throw new o('Unknown kind "'+this.kind+'" is specified for "'+e+'" YAML type.')};function f(e,t){var n=[];return e[t].forEach((function(e){var t=n.length;n.forEach((function(n,i){n.tag===e.tag&&n.kind===e.kind&&n.multi===e.multi&&(t=i)})),n[t]=e})),n}function d(e){return this.extend(e)}d.prototype.extend=function(e){var t=[],n=[];if(e instanceof p)n.push(e);else if(Array.isArray(e))n=n.concat(e);else{if(!e||!Array.isArray(e.implicit)&&!Array.isArray(e.explicit))throw new o("Schema.extend argument should be a Type, [ Type ], or a schema definition ({ implicit: [...], explicit: [...] })");e.implicit&&(t=t.concat(e.implicit)),e.explicit&&(n=n.concat(e.explicit))}t.forEach((function(e){if(!(e instanceof p))throw new o("Specified list of YAML types (or a single Type object) contains a non-Type object.");if(e.loadKind&&"scalar"!==e.loadKind)throw new o("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.");if(e.multi)throw new o("There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit.")})),n.forEach((function(e){if(!(e instanceof p))throw new o("Specified list of YAML types (or a single Type object) contains a non-Type object.")}));var i=Object.create(d.prototype);return i.implicit=(this.implicit||[]).concat(t),i.explicit=(this.explicit||[]).concat(n),i.compiledImplicit=f(i,"implicit"),i.compiledExplicit=f(i,"explicit"),i.compiledTypeMap=function(){var e,t,n={scalar:{},sequence:{},mapping:{},fallback:{},multi:{scalar:[],sequence:[],mapping:[],fallback:[]}};function i(e){e.multi?(n.multi[e.kind].push(e),n.multi.fallback.push(e)):n[e.kind][e.tag]=n.fallback[e.tag]=e}for(e=0,t=arguments.length;e=0?"0b"+e.toString(2):"-0b"+e.toString(2).slice(1)},octal:function(e){return e>=0?"0o"+e.toString(8):"-0o"+e.toString(8).slice(1)},decimal:function(e){return e.toString(10)},hexadecimal:function(e){return e>=0?"0x"+e.toString(16).toUpperCase():"-0x"+e.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}}),x=new RegExp("^(?:[-+]?(?:[0-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");var I=/^[-+]?[0-9]+e/;var S=new p("tag:yaml.org,2002:float",{kind:"scalar",resolve:function(e){return null!==e&&!(!x.test(e)||"_"===e[e.length-1])},construct:function(e){var t,n;return n="-"===(t=e.replace(/_/g,"").toLowerCase())[0]?-1:1,"+-".indexOf(t[0])>=0&&(t=t.slice(1)),".inf"===t?1===n?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:".nan"===t?NaN:n*parseFloat(t,10)},predicate:function(e){return"[object Number]"===Object.prototype.toString.call(e)&&(e%1!=0||n.isNegativeZero(e))},represent:function(e,t){var i;if(isNaN(e))switch(t){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===e)switch(t){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===e)switch(t){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(n.isNegativeZero(e))return"-0.0";return i=e.toString(10),I.test(i)?i.replace("e",".e"):i},defaultStyle:"lowercase"}),O=b.extend({implicit:[A,v,C,S]}),j=O,T=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),N=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");var F=new p("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:function(e){return null!==e&&(null!==T.exec(e)||null!==N.exec(e))},construct:function(e){var t,n,i,r,o,a,l,c,s=0,u=null;if(null===(t=T.exec(e))&&(t=N.exec(e)),null===t)throw new Error("Date resolve error");if(n=+t[1],i=+t[2]-1,r=+t[3],!t[4])return new Date(Date.UTC(n,i,r));if(o=+t[4],a=+t[5],l=+t[6],t[7]){for(s=t[7].slice(0,3);s.length<3;)s+="0";s=+s}return t[9]&&(u=6e4*(60*+t[10]+ +(t[11]||0)),"-"===t[9]&&(u=-u)),c=new Date(Date.UTC(n,i,r,o,a,l,s)),u&&c.setTime(c.getTime()-u),c},instanceOf:Date,represent:function(e){return e.toISOString()}});var E=new p("tag:yaml.org,2002:merge",{kind:"scalar",resolve:function(e){return"<<"===e||null===e}}),M="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r";var L=new p("tag:yaml.org,2002:binary",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,n,i=0,r=e.length,o=M;for(n=0;n64)){if(t<0)return!1;i+=6}return i%8==0},construct:function(e){var t,n,i=e.replace(/[\r\n=]/g,""),r=i.length,o=M,a=0,l=[];for(t=0;t>16&255),l.push(a>>8&255),l.push(255&a)),a=a<<6|o.indexOf(i.charAt(t));return 0===(n=r%4*6)?(l.push(a>>16&255),l.push(a>>8&255),l.push(255&a)):18===n?(l.push(a>>10&255),l.push(a>>2&255)):12===n&&l.push(a>>4&255),new Uint8Array(l)},predicate:function(e){return"[object Uint8Array]"===Object.prototype.toString.call(e)},represent:function(e){var t,n,i="",r=0,o=e.length,a=M;for(t=0;t>18&63],i+=a[r>>12&63],i+=a[r>>6&63],i+=a[63&r]),r=(r<<8)+e[t];return 0===(n=o%3)?(i+=a[r>>18&63],i+=a[r>>12&63],i+=a[r>>6&63],i+=a[63&r]):2===n?(i+=a[r>>10&63],i+=a[r>>4&63],i+=a[r<<2&63],i+=a[64]):1===n&&(i+=a[r>>2&63],i+=a[r<<4&63],i+=a[64],i+=a[64]),i}}),_=Object.prototype.hasOwnProperty,D=Object.prototype.toString;var U=new p("tag:yaml.org,2002:omap",{kind:"sequence",resolve:function(e){if(null===e)return!0;var t,n,i,r,o,a=[],l=e;for(t=0,n=l.length;t>10),56320+(e-65536&1023))}for(var ie=new Array(256),re=new Array(256),oe=0;oe<256;oe++)ie[oe]=te(oe)?1:0,re[oe]=te(oe);function ae(e,t){this.input=e,this.filename=t.filename||null,this.schema=t.schema||K,this.onWarning=t.onWarning||null,this.legacy=t.legacy||!1,this.json=t.json||!1,this.listener=t.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=e.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.firstTabInLine=-1,this.documents=[]}function le(e,t){var n={name:e.filename,buffer:e.input.slice(0,-1),position:e.position,line:e.line,column:e.position-e.lineStart};return n.snippet=c(n),new o(t,n)}function ce(e,t){throw le(e,t)}function se(e,t){e.onWarning&&e.onWarning.call(null,le(e,t))}var ue={YAML:function(e,t,n){var i,r,o;null!==e.version&&ce(e,"duplication of %YAML directive"),1!==n.length&&ce(e,"YAML directive accepts exactly one argument"),null===(i=/^([0-9]+)\.([0-9]+)$/.exec(n[0]))&&ce(e,"ill-formed argument of the YAML directive"),r=parseInt(i[1],10),o=parseInt(i[2],10),1!==r&&ce(e,"unacceptable YAML version of the document"),e.version=n[0],e.checkLineBreaks=o<2,1!==o&&2!==o&&se(e,"unsupported YAML version of the document")},TAG:function(e,t,n){var i,r;2!==n.length&&ce(e,"TAG directive accepts exactly two arguments"),i=n[0],r=n[1],G.test(i)||ce(e,"ill-formed tag handle (first argument) of the TAG directive"),P.call(e.tagMap,i)&&ce(e,'there is a previously declared suffix for "'+i+'" tag handle'),V.test(r)||ce(e,"ill-formed tag prefix (second argument) of the TAG directive");try{r=decodeURIComponent(r)}catch(t){ce(e,"tag prefix is malformed: "+r)}e.tagMap[i]=r}};function pe(e,t,n,i){var r,o,a,l;if(t1&&(e.result+=n.repeat("\n",t-1))}function be(e,t){var n,i,r=e.tag,o=e.anchor,a=[],l=!1;if(-1!==e.firstTabInLine)return!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=a),i=e.input.charCodeAt(e.position);0!==i&&(-1!==e.firstTabInLine&&(e.position=e.firstTabInLine,ce(e,"tab characters must not be used in indentation")),45===i)&&z(e.input.charCodeAt(e.position+1));)if(l=!0,e.position++,ge(e,!0,-1)&&e.lineIndent<=t)a.push(null),i=e.input.charCodeAt(e.position);else if(n=e.line,we(e,t,3,!1,!0),a.push(e.result),ge(e,!0,-1),i=e.input.charCodeAt(e.position),(e.line===n||e.lineIndent>t)&&0!==i)ce(e,"bad indentation of a sequence entry");else if(e.lineIndentt?g=1:e.lineIndent===t?g=0:e.lineIndentt?g=1:e.lineIndent===t?g=0:e.lineIndentt)&&(y&&(a=e.line,l=e.lineStart,c=e.position),we(e,t,4,!0,r)&&(y?g=e.result:m=e.result),y||(de(e,f,d,h,g,m,a,l,c),h=g=m=null),ge(e,!0,-1),s=e.input.charCodeAt(e.position)),(e.line===o||e.lineIndent>t)&&0!==s)ce(e,"bad indentation of a mapping entry");else if(e.lineIndent=0))break;0===o?ce(e,"bad explicit indentation width of a block scalar; it cannot be less than one"):u?ce(e,"repeat of an indentation width identifier"):(p=t+o-1,u=!0)}if(Q(a)){do{a=e.input.charCodeAt(++e.position)}while(Q(a));if(35===a)do{a=e.input.charCodeAt(++e.position)}while(!J(a)&&0!==a)}for(;0!==a;){for(he(e),e.lineIndent=0,a=e.input.charCodeAt(e.position);(!u||e.lineIndentp&&(p=e.lineIndent),J(a))f++;else{if(e.lineIndent0){for(r=a,o=0;r>0;r--)(a=ee(l=e.input.charCodeAt(++e.position)))>=0?o=(o<<4)+a:ce(e,"expected hexadecimal character");e.result+=ne(o),e.position++}else ce(e,"unknown escape sequence");n=i=e.position}else J(l)?(pe(e,n,i,!0),ye(e,ge(e,!1,t)),n=i=e.position):e.position===e.lineStart&&me(e)?ce(e,"unexpected end of the document within a double quoted scalar"):(e.position++,i=e.position)}ce(e,"unexpected end of the stream within a double quoted scalar")}(e,d)?y=!0:!function(e){var t,n,i;if(42!==(i=e.input.charCodeAt(e.position)))return!1;for(i=e.input.charCodeAt(++e.position),t=e.position;0!==i&&!z(i)&&!X(i);)i=e.input.charCodeAt(++e.position);return e.position===t&&ce(e,"name of an alias node must contain at least one character"),n=e.input.slice(t,e.position),P.call(e.anchorMap,n)||ce(e,'unidentified alias "'+n+'"'),e.result=e.anchorMap[n],ge(e,!0,-1),!0}(e)?function(e,t,n){var i,r,o,a,l,c,s,u,p=e.kind,f=e.result;if(z(u=e.input.charCodeAt(e.position))||X(u)||35===u||38===u||42===u||33===u||124===u||62===u||39===u||34===u||37===u||64===u||96===u)return!1;if((63===u||45===u)&&(z(i=e.input.charCodeAt(e.position+1))||n&&X(i)))return!1;for(e.kind="scalar",e.result="",r=o=e.position,a=!1;0!==u;){if(58===u){if(z(i=e.input.charCodeAt(e.position+1))||n&&X(i))break}else if(35===u){if(z(e.input.charCodeAt(e.position-1)))break}else{if(e.position===e.lineStart&&me(e)||n&&X(u))break;if(J(u)){if(l=e.line,c=e.lineStart,s=e.lineIndent,ge(e,!1,-1),e.lineIndent>=t){a=!0,u=e.input.charCodeAt(e.position);continue}e.position=o,e.line=l,e.lineStart=c,e.lineIndent=s;break}}a&&(pe(e,r,o,!1),ye(e,e.line-l),r=o=e.position,a=!1),Q(u)||(o=e.position+1),u=e.input.charCodeAt(++e.position)}return pe(e,r,o,!1),!!e.result||(e.kind=p,e.result=f,!1)}(e,d,1===i)&&(y=!0,null===e.tag&&(e.tag="?")):(y=!0,null===e.tag&&null===e.anchor||ce(e,"alias node should not have any properties")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===g&&(y=c&&be(e,h))),null===e.tag)null!==e.anchor&&(e.anchorMap[e.anchor]=e.result);else if("?"===e.tag){for(null!==e.result&&"scalar"!==e.kind&&ce(e,'unacceptable node kind for ! tag; it should be "scalar", not "'+e.kind+'"'),s=0,u=e.implicitTypes.length;s"),null!==e.result&&f.kind!==e.kind&&ce(e,"unacceptable node kind for !<"+e.tag+'> tag; it should be "'+f.kind+'", not "'+e.kind+'"'),f.resolve(e.result,e.tag)?(e.result=f.construct(e.result,e.tag),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):ce(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")}return null!==e.listener&&e.listener("close",e),null!==e.tag||null!==e.anchor||y}function ke(e){var t,n,i,r,o=e.position,a=!1;for(e.version=null,e.checkLineBreaks=e.legacy,e.tagMap=Object.create(null),e.anchorMap=Object.create(null);0!==(r=e.input.charCodeAt(e.position))&&(ge(e,!0,-1),r=e.input.charCodeAt(e.position),!(e.lineIndent>0||37!==r));){for(a=!0,r=e.input.charCodeAt(++e.position),t=e.position;0!==r&&!z(r);)r=e.input.charCodeAt(++e.position);for(i=[],(n=e.input.slice(t,e.position)).length<1&&ce(e,"directive name must not be less than one character in length");0!==r;){for(;Q(r);)r=e.input.charCodeAt(++e.position);if(35===r){do{r=e.input.charCodeAt(++e.position)}while(0!==r&&!J(r));break}if(J(r))break;for(t=e.position;0!==r&&!z(r);)r=e.input.charCodeAt(++e.position);i.push(e.input.slice(t,e.position))}0!==r&&he(e),P.call(ue,n)?ue[n](e,n,i):se(e,'unknown document directive "'+n+'"')}ge(e,!0,-1),0===e.lineIndent&&45===e.input.charCodeAt(e.position)&&45===e.input.charCodeAt(e.position+1)&&45===e.input.charCodeAt(e.position+2)?(e.position+=3,ge(e,!0,-1)):a&&ce(e,"directives end mark is expected"),we(e,e.lineIndent-1,4,!1,!0),ge(e,!0,-1),e.checkLineBreaks&&H.test(e.input.slice(o,e.position))&&se(e,"non-ASCII line breaks are interpreted as content"),e.documents.push(e.result),e.position===e.lineStart&&me(e)?46===e.input.charCodeAt(e.position)&&(e.position+=3,ge(e,!0,-1)):e.position=55296&&i<=56319&&t+1=56320&&n<=57343?1024*(i-55296)+n-56320+65536:i}function Re(e){return/^\n* /.test(e)}function Be(e,t,n,i,r,o,a,l){var c,s,u=0,p=null,f=!1,d=!1,h=-1!==i,g=-1,m=De(s=Ye(e,0))&&s!==Oe&&!_e(s)&&45!==s&&63!==s&&58!==s&&44!==s&&91!==s&&93!==s&&123!==s&&125!==s&&35!==s&&38!==s&&42!==s&&33!==s&&124!==s&&61!==s&&62!==s&&39!==s&&34!==s&&37!==s&&64!==s&&96!==s&&function(e){return!_e(e)&&58!==e}(Ye(e,e.length-1));if(t||a)for(c=0;c=65536?c+=2:c++){if(!De(u=Ye(e,c)))return 5;m=m&&qe(u,p,l),p=u}else{for(c=0;c=65536?c+=2:c++){if(10===(u=Ye(e,c)))f=!0,h&&(d=d||c-g-1>i&&" "!==e[g+1],g=c);else if(!De(u))return 5;m=m&&qe(u,p,l),p=u}d=d||h&&c-g-1>i&&" "!==e[g+1]}return f||d?n>9&&Re(e)?5:a?2===o?5:2:d?4:3:!m||a||r(e)?2===o?5:2:1}function Ke(e,t,n,i,r){e.dump=function(){if(0===t.length)return 2===e.quotingType?'""':"''";if(!e.noCompatMode&&(-1!==Te.indexOf(t)||Ne.test(t)))return 2===e.quotingType?'"'+t+'"':"'"+t+"'";var a=e.indent*Math.max(1,n),l=-1===e.lineWidth?-1:Math.max(Math.min(e.lineWidth,40),e.lineWidth-a),c=i||e.flowLevel>-1&&n>=e.flowLevel;switch(Be(t,c,e.indent,l,(function(t){return function(e,t){var n,i;for(n=0,i=e.implicitTypes.length;n"+Pe(t,e.indent)+We(Me(function(e,t){var n,i,r=/(\n+)([^\n]*)/g,o=(l=e.indexOf("\n"),l=-1!==l?l:e.length,r.lastIndex=l,He(e.slice(0,l),t)),a="\n"===e[0]||" "===e[0];var l;for(;i=r.exec(e);){var c=i[1],s=i[2];n=" "===s[0],o+=c+(a||n||""===s?"":"\n")+He(s,t),a=n}return o}(t,l),a));case 5:return'"'+function(e){for(var t,n="",i=0,r=0;r=65536?r+=2:r++)i=Ye(e,r),!(t=je[i])&&De(i)?(n+=e[r],i>=65536&&(n+=e[r+1])):n+=t||Fe(i);return n}(t)+'"';default:throw new o("impossible error: invalid scalar style")}}()}function Pe(e,t){var n=Re(e)?String(t):"",i="\n"===e[e.length-1];return n+(i&&("\n"===e[e.length-2]||"\n"===e)?"+":i?"":"-")+"\n"}function We(e){return"\n"===e[e.length-1]?e.slice(0,-1):e}function He(e,t){if(""===e||" "===e[0])return e;for(var n,i,r=/ [^ ]/g,o=0,a=0,l=0,c="";n=r.exec(e);)(l=n.index)-o>t&&(i=a>o?a:l,c+="\n"+e.slice(o,i),o=i+1),a=l;return c+="\n",e.length-o>t&&a>o?c+=e.slice(o,a)+"\n"+e.slice(a+1):c+=e.slice(o),c.slice(1)}function $e(e,t,n,i){var r,o,a,l="",c=e.tag;for(r=0,o=n.length;r tag resolver accepts not "'+s+'" style');i=c.represent[s](t,s)}e.dump=i}return!0}return!1}function Ve(e,t,n,i,r,a,l){e.tag=null,e.dump=n,Ge(e,n,!1)||Ge(e,n,!0);var c,s=Ie.call(e.dump),u=i;i&&(i=e.flowLevel<0||e.flowLevel>t);var p,f,d="[object Object]"===s||"[object Array]"===s;if(d&&(f=-1!==(p=e.duplicates.indexOf(n))),(null!==e.tag&&"?"!==e.tag||f||2!==e.indent&&t>0)&&(r=!1),f&&e.usedDuplicates[p])e.dump="*ref_"+p;else{if(d&&f&&!e.usedDuplicates[p]&&(e.usedDuplicates[p]=!0),"[object Object]"===s)i&&0!==Object.keys(e.dump).length?(!function(e,t,n,i){var r,a,l,c,s,u,p="",f=e.tag,d=Object.keys(n);if(!0===e.sortKeys)d.sort();else if("function"==typeof e.sortKeys)d.sort(e.sortKeys);else if(e.sortKeys)throw new o("sortKeys must be a boolean or a function");for(r=0,a=d.length;r1024)&&(e.dump&&10===e.dump.charCodeAt(0)?u+="?":u+="? "),u+=e.dump,s&&(u+=Le(e,t)),Ve(e,t+1,c,!0,s)&&(e.dump&&10===e.dump.charCodeAt(0)?u+=":":u+=": ",p+=u+=e.dump));e.tag=f,e.dump=p||"{}"}(e,t,e.dump,r),f&&(e.dump="&ref_"+p+e.dump)):(!function(e,t,n){var i,r,o,a,l,c="",s=e.tag,u=Object.keys(n);for(i=0,r=u.length;i1024&&(l+="? "),l+=e.dump+(e.condenseFlow?'"':"")+":"+(e.condenseFlow?"":" "),Ve(e,t,a,!1,!1)&&(c+=l+=e.dump));e.tag=s,e.dump="{"+c+"}"}(e,t,e.dump),f&&(e.dump="&ref_"+p+" "+e.dump));else if("[object Array]"===s)i&&0!==e.dump.length?(e.noArrayIndent&&!l&&t>0?$e(e,t-1,e.dump,r):$e(e,t,e.dump,r),f&&(e.dump="&ref_"+p+e.dump)):(!function(e,t,n){var i,r,o,a="",l=e.tag;for(i=0,r=n.length;i",e.dump=c+" "+e.dump)}return!0}function Ze(e,t){var n,i,r=[],o=[];for(Je(e,r,o),n=0,i=o.length;n=0;)t[e]=0}const a=256,i=286,n=30,s=15,r=new Uint8Array([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]),o=new Uint8Array([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]),l=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]),h=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),d=new Array(576);e(d);const _=new Array(60);e(_);const f=new Array(512);e(f);const c=new Array(256);e(c);const u=new Array(29);e(u);const w=new Array(n);function m(t,e,a,i,n){this.static_tree=t,this.extra_bits=e,this.extra_base=a,this.elems=i,this.max_length=n,this.has_stree=t&&t.length}let b,g,p;function k(t,e){this.dyn_tree=t,this.max_code=0,this.stat_desc=e}e(w);const v=t=>t<256?f[t]:f[256+(t>>>7)],y=(t,e)=>{t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255},x=(t,e,a)=>{t.bi_valid>16-a?(t.bi_buf|=e<>16-t.bi_valid,t.bi_valid+=a-16):(t.bi_buf|=e<{x(t,a[2*e],a[2*e+1])},A=(t,e)=>{let a=0;do{a|=1&t,t>>>=1,a<<=1}while(--e>0);return a>>>1},E=(t,e,a)=>{const i=new Array(16);let n,r,o=0;for(n=1;n<=s;n++)o=o+a[n-1]<<1,i[n]=o;for(r=0;r<=e;r++){let e=t[2*r+1];0!==e&&(t[2*r]=A(i[e]++,e))}},R=t=>{let e;for(e=0;e{t.bi_valid>8?y(t,t.bi_buf):t.bi_valid>0&&(t.pending_buf[t.pending++]=t.bi_buf),t.bi_buf=0,t.bi_valid=0},U=(t,e,a,i)=>{const n=2*e,s=2*a;return t[n]{const i=t.heap[a];let n=a<<1;for(;n<=t.heap_len&&(n{let n,s,l,h,d=0;if(0!==t.sym_next)do{n=255&t.pending_buf[t.sym_buf+d++],n+=(255&t.pending_buf[t.sym_buf+d++])<<8,s=t.pending_buf[t.sym_buf+d++],0===n?z(t,s,e):(l=c[s],z(t,l+a+1,e),h=r[l],0!==h&&(s-=u[l],x(t,s,h)),n--,l=v(n),z(t,l,i),h=o[l],0!==h&&(n-=w[l],x(t,n,h)))}while(d{const a=e.dyn_tree,i=e.stat_desc.static_tree,n=e.stat_desc.has_stree,r=e.stat_desc.elems;let o,l,h,d=-1;for(t.heap_len=0,t.heap_max=573,o=0;o>1;o>=1;o--)S(t,a,o);h=r;do{o=t.heap[1],t.heap[1]=t.heap[t.heap_len--],S(t,a,1),l=t.heap[1],t.heap[--t.heap_max]=o,t.heap[--t.heap_max]=l,a[2*h]=a[2*o]+a[2*l],t.depth[h]=(t.depth[o]>=t.depth[l]?t.depth[o]:t.depth[l])+1,a[2*o+1]=a[2*l+1]=h,t.heap[1]=h++,S(t,a,1)}while(t.heap_len>=2);t.heap[--t.heap_max]=t.heap[1],((t,e)=>{const a=e.dyn_tree,i=e.max_code,n=e.stat_desc.static_tree,r=e.stat_desc.has_stree,o=e.stat_desc.extra_bits,l=e.stat_desc.extra_base,h=e.stat_desc.max_length;let d,_,f,c,u,w,m=0;for(c=0;c<=s;c++)t.bl_count[c]=0;for(a[2*t.heap[t.heap_max]+1]=0,d=t.heap_max+1;d<573;d++)_=t.heap[d],c=a[2*a[2*_+1]+1]+1,c>h&&(c=h,m++),a[2*_+1]=c,_>i||(t.bl_count[c]++,u=0,_>=l&&(u=o[_-l]),w=a[2*_],t.opt_len+=w*(c+u),r&&(t.static_len+=w*(n[2*_+1]+u)));if(0!==m){do{for(c=h-1;0===t.bl_count[c];)c--;t.bl_count[c]--,t.bl_count[c+1]+=2,t.bl_count[h]--,m-=2}while(m>0);for(c=h;0!==c;c--)for(_=t.bl_count[c];0!==_;)f=t.heap[--d],f>i||(a[2*f+1]!==c&&(t.opt_len+=(c-a[2*f+1])*a[2*f],a[2*f+1]=c),_--)}})(t,e),E(a,d,t.bl_count)},O=(t,e,a)=>{let i,n,s=-1,r=e[1],o=0,l=7,h=4;for(0===r&&(l=138,h=3),e[2*(a+1)+1]=65535,i=0;i<=a;i++)n=r,r=e[2*(i+1)+1],++o{let i,n,s=-1,r=e[1],o=0,l=7,h=4;for(0===r&&(l=138,h=3),i=0;i<=a;i++)if(n=r,r=e[2*(i+1)+1],!(++o{x(t,0+(i?1:0),3),Z(t),y(t,a),y(t,~a),a&&t.pending_buf.set(t.window.subarray(e,e+a),t.pending),t.pending+=a};var N=(t,e,i,n)=>{let s,r,o=0;t.level>0?(2===t.strm.data_type&&(t.strm.data_type=(t=>{let e,i=4093624447;for(e=0;e<=31;e++,i>>>=1)if(1&i&&0!==t.dyn_ltree[2*e])return 0;if(0!==t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return 1;for(e=32;e{let e;for(O(t,t.dyn_ltree,t.l_desc.max_code),O(t,t.dyn_dtree,t.d_desc.max_code),T(t,t.bl_desc),e=18;e>=3&&0===t.bl_tree[2*h[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e})(t),s=t.opt_len+3+7>>>3,r=t.static_len+3+7>>>3,r<=s&&(s=r)):s=r=i+5,i+4<=s&&-1!==e?L(t,e,i,n):4===t.strategy||r===s?(x(t,2+(n?1:0),3),D(t,d,_)):(x(t,4+(n?1:0),3),((t,e,a,i)=>{let n;for(x(t,e-257,5),x(t,a-1,5),x(t,i-4,4),n=0;n{F||((()=>{let t,e,a,h,k;const v=new Array(16);for(a=0,h=0;h<28;h++)for(u[h]=a,t=0;t<1<>=7;h(t.pending_buf[t.sym_buf+t.sym_next++]=e,t.pending_buf[t.sym_buf+t.sym_next++]=e>>8,t.pending_buf[t.sym_buf+t.sym_next++]=i,0===e?t.dyn_ltree[2*i]++:(t.matches++,e--,t.dyn_ltree[2*(c[i]+a+1)]++,t.dyn_dtree[2*v(e)]++),t.sym_next===t.sym_end),_tr_align:t=>{x(t,2,3),z(t,256,d),(t=>{16===t.bi_valid?(y(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):t.bi_valid>=8&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)})(t)}};var C=(t,e,a,i)=>{let n=65535&t|0,s=t>>>16&65535|0,r=0;for(;0!==a;){r=a>2e3?2e3:a,a-=r;do{n=n+e[i++]|0,s=s+n|0}while(--r);n%=65521,s%=65521}return n|s<<16|0};const M=new Uint32Array((()=>{let t,e=[];for(var a=0;a<256;a++){t=a;for(var i=0;i<8;i++)t=1&t?3988292384^t>>>1:t>>>1;e[a]=t}return e})());var H=(t,e,a,i)=>{const n=M,s=i+a;t^=-1;for(let a=i;a>>8^n[255&(t^e[a])];return-1^t},j={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"},K={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8};const{_tr_init:P,_tr_stored_block:Y,_tr_flush_block:G,_tr_tally:X,_tr_align:W}=B,{Z_NO_FLUSH:q,Z_PARTIAL_FLUSH:J,Z_FULL_FLUSH:Q,Z_FINISH:V,Z_BLOCK:$,Z_OK:tt,Z_STREAM_END:et,Z_STREAM_ERROR:at,Z_DATA_ERROR:it,Z_BUF_ERROR:nt,Z_DEFAULT_COMPRESSION:st,Z_FILTERED:rt,Z_HUFFMAN_ONLY:ot,Z_RLE:lt,Z_FIXED:ht,Z_DEFAULT_STRATEGY:dt,Z_UNKNOWN:_t,Z_DEFLATED:ft}=K,ct=258,ut=262,wt=42,mt=113,bt=666,gt=(t,e)=>(t.msg=j[e],e),pt=t=>2*t-(t>4?9:0),kt=t=>{let e=t.length;for(;--e>=0;)t[e]=0},vt=t=>{let e,a,i,n=t.w_size;e=t.hash_size,i=e;do{a=t.head[--i],t.head[i]=a>=n?a-n:0}while(--e);e=n,i=e;do{a=t.prev[--i],t.prev[i]=a>=n?a-n:0}while(--e)};let yt=(t,e,a)=>(e<{const e=t.state;let a=e.pending;a>t.avail_out&&(a=t.avail_out),0!==a&&(t.output.set(e.pending_buf.subarray(e.pending_out,e.pending_out+a),t.next_out),t.next_out+=a,e.pending_out+=a,t.total_out+=a,t.avail_out-=a,e.pending-=a,0===e.pending&&(e.pending_out=0))},zt=(t,e)=>{G(t,t.block_start>=0?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,xt(t.strm)},At=(t,e)=>{t.pending_buf[t.pending++]=e},Et=(t,e)=>{t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e},Rt=(t,e,a,i)=>{let n=t.avail_in;return n>i&&(n=i),0===n?0:(t.avail_in-=n,e.set(t.input.subarray(t.next_in,t.next_in+n),a),1===t.state.wrap?t.adler=C(t.adler,e,n,a):2===t.state.wrap&&(t.adler=H(t.adler,e,n,a)),t.next_in+=n,t.total_in+=n,n)},Zt=(t,e)=>{let a,i,n=t.max_chain_length,s=t.strstart,r=t.prev_length,o=t.nice_match;const l=t.strstart>t.w_size-ut?t.strstart-(t.w_size-ut):0,h=t.window,d=t.w_mask,_=t.prev,f=t.strstart+ct;let c=h[s+r-1],u=h[s+r];t.prev_length>=t.good_match&&(n>>=2),o>t.lookahead&&(o=t.lookahead);do{if(a=e,h[a+r]===u&&h[a+r-1]===c&&h[a]===h[s]&&h[++a]===h[s+1]){s+=2,a++;do{}while(h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&sr){if(t.match_start=e,r=i,i>=o)break;c=h[s+r-1],u=h[s+r]}}}while((e=_[e&d])>l&&0!=--n);return r<=t.lookahead?r:t.lookahead},Ut=t=>{const e=t.w_size;let a,i,n;do{if(i=t.window_size-t.lookahead-t.strstart,t.strstart>=e+(e-ut)&&(t.window.set(t.window.subarray(e,e+e-i),0),t.match_start-=e,t.strstart-=e,t.block_start-=e,t.insert>t.strstart&&(t.insert=t.strstart),vt(t),i+=e),0===t.strm.avail_in)break;if(a=Rt(t.strm,t.window,t.strstart+t.lookahead,i),t.lookahead+=a,t.lookahead+t.insert>=3)for(n=t.strstart-t.insert,t.ins_h=t.window[n],t.ins_h=yt(t,t.ins_h,t.window[n+1]);t.insert&&(t.ins_h=yt(t,t.ins_h,t.window[n+3-1]),t.prev[n&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=n,n++,t.insert--,!(t.lookahead+t.insert<3)););}while(t.lookahead{let a,i,n,s=t.pending_buf_size-5>t.w_size?t.w_size:t.pending_buf_size-5,r=0,o=t.strm.avail_in;do{if(a=65535,n=t.bi_valid+42>>3,t.strm.avail_outi+t.strm.avail_in&&(a=i+t.strm.avail_in),a>n&&(a=n),a>8,t.pending_buf[t.pending-2]=~a,t.pending_buf[t.pending-1]=~a>>8,xt(t.strm),i&&(i>a&&(i=a),t.strm.output.set(t.window.subarray(t.block_start,t.block_start+i),t.strm.next_out),t.strm.next_out+=i,t.strm.avail_out-=i,t.strm.total_out+=i,t.block_start+=i,a-=i),a&&(Rt(t.strm,t.strm.output,t.strm.next_out,a),t.strm.next_out+=a,t.strm.avail_out-=a,t.strm.total_out+=a)}while(0===r);return o-=t.strm.avail_in,o&&(o>=t.w_size?(t.matches=2,t.window.set(t.strm.input.subarray(t.strm.next_in-t.w_size,t.strm.next_in),0),t.strstart=t.w_size,t.insert=t.strstart):(t.window_size-t.strstart<=o&&(t.strstart-=t.w_size,t.window.set(t.window.subarray(t.w_size,t.w_size+t.strstart),0),t.matches<2&&t.matches++,t.insert>t.strstart&&(t.insert=t.strstart)),t.window.set(t.strm.input.subarray(t.strm.next_in-o,t.strm.next_in),t.strstart),t.strstart+=o,t.insert+=o>t.w_size-t.insert?t.w_size-t.insert:o),t.block_start=t.strstart),t.high_watern&&t.block_start>=t.w_size&&(t.block_start-=t.w_size,t.strstart-=t.w_size,t.window.set(t.window.subarray(t.w_size,t.w_size+t.strstart),0),t.matches<2&&t.matches++,n+=t.w_size,t.insert>t.strstart&&(t.insert=t.strstart)),n>t.strm.avail_in&&(n=t.strm.avail_in),n&&(Rt(t.strm,t.window,t.strstart,n),t.strstart+=n,t.insert+=n>t.w_size-t.insert?t.w_size-t.insert:n),t.high_water>3,n=t.pending_buf_size-n>65535?65535:t.pending_buf_size-n,s=n>t.w_size?t.w_size:n,i=t.strstart-t.block_start,(i>=s||(i||e===V)&&e!==q&&0===t.strm.avail_in&&i<=n)&&(a=i>n?n:i,r=e===V&&0===t.strm.avail_in&&a===i?1:0,Y(t,t.block_start,a,r),t.block_start+=a,xt(t.strm)),r?3:1)},Dt=(t,e)=>{let a,i;for(;;){if(t.lookahead=3&&(t.ins_h=yt(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),0!==a&&t.strstart-a<=t.w_size-ut&&(t.match_length=Zt(t,a)),t.match_length>=3)if(i=X(t,t.strstart-t.match_start,t.match_length-3),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=3){t.match_length--;do{t.strstart++,t.ins_h=yt(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart}while(0!=--t.match_length);t.strstart++}else t.strstart+=t.match_length,t.match_length=0,t.ins_h=t.window[t.strstart],t.ins_h=yt(t,t.ins_h,t.window[t.strstart+1]);else i=X(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++;if(i&&(zt(t,!1),0===t.strm.avail_out))return 1}return t.insert=t.strstart<2?t.strstart:2,e===V?(zt(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(zt(t,!1),0===t.strm.avail_out)?1:2},Tt=(t,e)=>{let a,i,n;for(;;){if(t.lookahead=3&&(t.ins_h=yt(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),t.prev_length=t.match_length,t.prev_match=t.match_start,t.match_length=2,0!==a&&t.prev_length4096)&&(t.match_length=2)),t.prev_length>=3&&t.match_length<=t.prev_length){n=t.strstart+t.lookahead-3,i=X(t,t.strstart-1-t.prev_match,t.prev_length-3),t.lookahead-=t.prev_length-1,t.prev_length-=2;do{++t.strstart<=n&&(t.ins_h=yt(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart)}while(0!=--t.prev_length);if(t.match_available=0,t.match_length=2,t.strstart++,i&&(zt(t,!1),0===t.strm.avail_out))return 1}else if(t.match_available){if(i=X(t,0,t.window[t.strstart-1]),i&&zt(t,!1),t.strstart++,t.lookahead--,0===t.strm.avail_out)return 1}else t.match_available=1,t.strstart++,t.lookahead--}return t.match_available&&(i=X(t,0,t.window[t.strstart-1]),t.match_available=0),t.insert=t.strstart<2?t.strstart:2,e===V?(zt(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(zt(t,!1),0===t.strm.avail_out)?1:2};function Ot(t,e,a,i,n){this.good_length=t,this.max_lazy=e,this.nice_length=a,this.max_chain=i,this.func=n}const It=[new Ot(0,0,0,0,St),new Ot(4,4,8,4,Dt),new Ot(4,5,16,8,Dt),new Ot(4,6,32,32,Dt),new Ot(4,4,16,16,Tt),new Ot(8,16,32,32,Tt),new Ot(8,16,128,128,Tt),new Ot(8,32,128,256,Tt),new Ot(32,128,258,1024,Tt),new Ot(32,258,258,4096,Tt)];function Ft(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=ft,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new Uint16Array(1146),this.dyn_dtree=new Uint16Array(122),this.bl_tree=new Uint16Array(78),kt(this.dyn_ltree),kt(this.dyn_dtree),kt(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new Uint16Array(16),this.heap=new Uint16Array(573),kt(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new Uint16Array(573),kt(this.depth),this.sym_buf=0,this.lit_bufsize=0,this.sym_next=0,this.sym_end=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}const Lt=t=>{if(!t)return 1;const e=t.state;return!e||e.strm!==t||e.status!==wt&&57!==e.status&&69!==e.status&&73!==e.status&&91!==e.status&&103!==e.status&&e.status!==mt&&e.status!==bt?1:0},Nt=t=>{if(Lt(t))return gt(t,at);t.total_in=t.total_out=0,t.data_type=_t;const e=t.state;return e.pending=0,e.pending_out=0,e.wrap<0&&(e.wrap=-e.wrap),e.status=2===e.wrap?57:e.wrap?wt:mt,t.adler=2===e.wrap?0:1,e.last_flush=-2,P(e),tt},Bt=t=>{const e=Nt(t);var a;return e===tt&&((a=t.state).window_size=2*a.w_size,kt(a.head),a.max_lazy_match=It[a.level].max_lazy,a.good_match=It[a.level].good_length,a.nice_match=It[a.level].nice_length,a.max_chain_length=It[a.level].max_chain,a.strstart=0,a.block_start=0,a.lookahead=0,a.insert=0,a.match_length=a.prev_length=2,a.match_available=0,a.ins_h=0),e},Ct=(t,e,a,i,n,s)=>{if(!t)return at;let r=1;if(e===st&&(e=6),i<0?(r=0,i=-i):i>15&&(r=2,i-=16),n<1||n>9||a!==ft||i<8||i>15||e<0||e>9||s<0||s>ht||8===i&&1!==r)return gt(t,at);8===i&&(i=9);const o=new Ft;return t.state=o,o.strm=t,o.status=wt,o.wrap=r,o.gzhead=null,o.w_bits=i,o.w_size=1<Ct(t,e,ft,15,8,dt),deflateInit2:Ct,deflateReset:Bt,deflateResetKeep:Nt,deflateSetHeader:(t,e)=>Lt(t)||2!==t.state.wrap?at:(t.state.gzhead=e,tt),deflate:(t,e)=>{if(Lt(t)||e>$||e<0)return t?gt(t,at):at;const a=t.state;if(!t.output||0!==t.avail_in&&!t.input||a.status===bt&&e!==V)return gt(t,0===t.avail_out?nt:at);const i=a.last_flush;if(a.last_flush=e,0!==a.pending){if(xt(t),0===t.avail_out)return a.last_flush=-1,tt}else if(0===t.avail_in&&pt(e)<=pt(i)&&e!==V)return gt(t,nt);if(a.status===bt&&0!==t.avail_in)return gt(t,nt);if(a.status===wt&&0===a.wrap&&(a.status=mt),a.status===wt){let e=ft+(a.w_bits-8<<4)<<8,i=-1;if(i=a.strategy>=ot||a.level<2?0:a.level<6?1:6===a.level?2:3,e|=i<<6,0!==a.strstart&&(e|=32),e+=31-e%31,Et(a,e),0!==a.strstart&&(Et(a,t.adler>>>16),Et(a,65535&t.adler)),t.adler=1,a.status=mt,xt(t),0!==a.pending)return a.last_flush=-1,tt}if(57===a.status)if(t.adler=0,At(a,31),At(a,139),At(a,8),a.gzhead)At(a,(a.gzhead.text?1:0)+(a.gzhead.hcrc?2:0)+(a.gzhead.extra?4:0)+(a.gzhead.name?8:0)+(a.gzhead.comment?16:0)),At(a,255&a.gzhead.time),At(a,a.gzhead.time>>8&255),At(a,a.gzhead.time>>16&255),At(a,a.gzhead.time>>24&255),At(a,9===a.level?2:a.strategy>=ot||a.level<2?4:0),At(a,255&a.gzhead.os),a.gzhead.extra&&a.gzhead.extra.length&&(At(a,255&a.gzhead.extra.length),At(a,a.gzhead.extra.length>>8&255)),a.gzhead.hcrc&&(t.adler=H(t.adler,a.pending_buf,a.pending,0)),a.gzindex=0,a.status=69;else if(At(a,0),At(a,0),At(a,0),At(a,0),At(a,0),At(a,9===a.level?2:a.strategy>=ot||a.level<2?4:0),At(a,3),a.status=mt,xt(t),0!==a.pending)return a.last_flush=-1,tt;if(69===a.status){if(a.gzhead.extra){let e=a.pending,i=(65535&a.gzhead.extra.length)-a.gzindex;for(;a.pending+i>a.pending_buf_size;){let n=a.pending_buf_size-a.pending;if(a.pending_buf.set(a.gzhead.extra.subarray(a.gzindex,a.gzindex+n),a.pending),a.pending=a.pending_buf_size,a.gzhead.hcrc&&a.pending>e&&(t.adler=H(t.adler,a.pending_buf,a.pending-e,e)),a.gzindex+=n,xt(t),0!==a.pending)return a.last_flush=-1,tt;e=0,i-=n}let n=new Uint8Array(a.gzhead.extra);a.pending_buf.set(n.subarray(a.gzindex,a.gzindex+i),a.pending),a.pending+=i,a.gzhead.hcrc&&a.pending>e&&(t.adler=H(t.adler,a.pending_buf,a.pending-e,e)),a.gzindex=0}a.status=73}if(73===a.status){if(a.gzhead.name){let e,i=a.pending;do{if(a.pending===a.pending_buf_size){if(a.gzhead.hcrc&&a.pending>i&&(t.adler=H(t.adler,a.pending_buf,a.pending-i,i)),xt(t),0!==a.pending)return a.last_flush=-1,tt;i=0}e=a.gzindexi&&(t.adler=H(t.adler,a.pending_buf,a.pending-i,i)),a.gzindex=0}a.status=91}if(91===a.status){if(a.gzhead.comment){let e,i=a.pending;do{if(a.pending===a.pending_buf_size){if(a.gzhead.hcrc&&a.pending>i&&(t.adler=H(t.adler,a.pending_buf,a.pending-i,i)),xt(t),0!==a.pending)return a.last_flush=-1,tt;i=0}e=a.gzindexi&&(t.adler=H(t.adler,a.pending_buf,a.pending-i,i))}a.status=103}if(103===a.status){if(a.gzhead.hcrc){if(a.pending+2>a.pending_buf_size&&(xt(t),0!==a.pending))return a.last_flush=-1,tt;At(a,255&t.adler),At(a,t.adler>>8&255),t.adler=0}if(a.status=mt,xt(t),0!==a.pending)return a.last_flush=-1,tt}if(0!==t.avail_in||0!==a.lookahead||e!==q&&a.status!==bt){let i=0===a.level?St(a,e):a.strategy===ot?((t,e)=>{let a;for(;;){if(0===t.lookahead&&(Ut(t),0===t.lookahead)){if(e===q)return 1;break}if(t.match_length=0,a=X(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,a&&(zt(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,e===V?(zt(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(zt(t,!1),0===t.strm.avail_out)?1:2})(a,e):a.strategy===lt?((t,e)=>{let a,i,n,s;const r=t.window;for(;;){if(t.lookahead<=ct){if(Ut(t),t.lookahead<=ct&&e===q)return 1;if(0===t.lookahead)break}if(t.match_length=0,t.lookahead>=3&&t.strstart>0&&(n=t.strstart-1,i=r[n],i===r[++n]&&i===r[++n]&&i===r[++n])){s=t.strstart+ct;do{}while(i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&nt.lookahead&&(t.match_length=t.lookahead)}if(t.match_length>=3?(a=X(t,1,t.match_length-3),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(a=X(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++),a&&(zt(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,e===V?(zt(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(zt(t,!1),0===t.strm.avail_out)?1:2})(a,e):It[a.level].func(a,e);if(3!==i&&4!==i||(a.status=bt),1===i||3===i)return 0===t.avail_out&&(a.last_flush=-1),tt;if(2===i&&(e===J?W(a):e!==$&&(Y(a,0,0,!1),e===Q&&(kt(a.head),0===a.lookahead&&(a.strstart=0,a.block_start=0,a.insert=0))),xt(t),0===t.avail_out))return a.last_flush=-1,tt}return e!==V?tt:a.wrap<=0?et:(2===a.wrap?(At(a,255&t.adler),At(a,t.adler>>8&255),At(a,t.adler>>16&255),At(a,t.adler>>24&255),At(a,255&t.total_in),At(a,t.total_in>>8&255),At(a,t.total_in>>16&255),At(a,t.total_in>>24&255)):(Et(a,t.adler>>>16),Et(a,65535&t.adler)),xt(t),a.wrap>0&&(a.wrap=-a.wrap),0!==a.pending?tt:et)},deflateEnd:t=>{if(Lt(t))return at;const e=t.state.status;return t.state=null,e===mt?gt(t,it):tt},deflateSetDictionary:(t,e)=>{let a=e.length;if(Lt(t))return at;const i=t.state,n=i.wrap;if(2===n||1===n&&i.status!==wt||i.lookahead)return at;if(1===n&&(t.adler=C(t.adler,e,a,0)),i.wrap=0,a>=i.w_size){0===n&&(kt(i.head),i.strstart=0,i.block_start=0,i.insert=0);let t=new Uint8Array(i.w_size);t.set(e.subarray(a-i.w_size,a),0),e=t,a=i.w_size}const s=t.avail_in,r=t.next_in,o=t.input;for(t.avail_in=a,t.next_in=0,t.input=e,Ut(i);i.lookahead>=3;){let t=i.strstart,e=i.lookahead-2;do{i.ins_h=yt(i,i.ins_h,i.window[t+3-1]),i.prev[t&i.w_mask]=i.head[i.ins_h],i.head[i.ins_h]=t,t++}while(--e);i.strstart=t,i.lookahead=2,Ut(i)}return i.strstart+=i.lookahead,i.block_start=i.strstart,i.insert=i.lookahead,i.lookahead=0,i.match_length=i.prev_length=2,i.match_available=0,t.next_in=r,t.input=o,t.avail_in=s,i.wrap=n,tt},deflateInfo:"pako deflate (from Nodeca project)"};const Ht=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var jt=function(t){const e=Array.prototype.slice.call(arguments,1);for(;e.length;){const a=e.shift();if(a){if("object"!=typeof a)throw new TypeError(a+"must be non-object");for(const e in a)Ht(a,e)&&(t[e]=a[e])}}return t},Kt=t=>{let e=0;for(let a=0,i=t.length;a=252?6:t>=248?5:t>=240?4:t>=224?3:t>=192?2:1;Yt[254]=Yt[254]=1;var Gt=t=>{if("function"==typeof TextEncoder&&TextEncoder.prototype.encode)return(new TextEncoder).encode(t);let e,a,i,n,s,r=t.length,o=0;for(n=0;n>>6,e[s++]=128|63&a):a<65536?(e[s++]=224|a>>>12,e[s++]=128|a>>>6&63,e[s++]=128|63&a):(e[s++]=240|a>>>18,e[s++]=128|a>>>12&63,e[s++]=128|a>>>6&63,e[s++]=128|63&a);return e},Xt=(t,e)=>{const a=e||t.length;if("function"==typeof TextDecoder&&TextDecoder.prototype.decode)return(new TextDecoder).decode(t.subarray(0,e));let i,n;const s=new Array(2*a);for(n=0,i=0;i4)s[n++]=65533,i+=r-1;else{for(e&=2===r?31:3===r?15:7;r>1&&i1?s[n++]=65533:e<65536?s[n++]=e:(e-=65536,s[n++]=55296|e>>10&1023,s[n++]=56320|1023&e)}}return((t,e)=>{if(e<65534&&t.subarray&&Pt)return String.fromCharCode.apply(null,t.length===e?t:t.subarray(0,e));let a="";for(let i=0;i{(e=e||t.length)>t.length&&(e=t.length);let a=e-1;for(;a>=0&&128==(192&t[a]);)a--;return a<0||0===a?e:a+Yt[t[a]]>e?a:e};var qt=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0};const Jt=Object.prototype.toString,{Z_NO_FLUSH:Qt,Z_SYNC_FLUSH:Vt,Z_FULL_FLUSH:$t,Z_FINISH:te,Z_OK:ee,Z_STREAM_END:ae,Z_DEFAULT_COMPRESSION:ie,Z_DEFAULT_STRATEGY:ne,Z_DEFLATED:se}=K;function re(t){this.options=jt({level:ie,method:se,chunkSize:16384,windowBits:15,memLevel:8,strategy:ne},t||{});let e=this.options;e.raw&&e.windowBits>0?e.windowBits=-e.windowBits:e.gzip&&e.windowBits>0&&e.windowBits<16&&(e.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new qt,this.strm.avail_out=0;let a=Mt.deflateInit2(this.strm,e.level,e.method,e.windowBits,e.memLevel,e.strategy);if(a!==ee)throw new Error(j[a]);if(e.header&&Mt.deflateSetHeader(this.strm,e.header),e.dictionary){let t;if(t="string"==typeof e.dictionary?Gt(e.dictionary):"[object ArrayBuffer]"===Jt.call(e.dictionary)?new Uint8Array(e.dictionary):e.dictionary,a=Mt.deflateSetDictionary(this.strm,t),a!==ee)throw new Error(j[a]);this._dict_set=!0}}function oe(t,e){const a=new re(e);if(a.push(t,!0),a.err)throw a.msg||j[a.err];return a.result}re.prototype.push=function(t,e){const a=this.strm,i=this.options.chunkSize;let n,s;if(this.ended)return!1;for(s=e===~~e?e:!0===e?te:Qt,"string"==typeof t?a.input=Gt(t):"[object ArrayBuffer]"===Jt.call(t)?a.input=new Uint8Array(t):a.input=t,a.next_in=0,a.avail_in=a.input.length;;)if(0===a.avail_out&&(a.output=new Uint8Array(i),a.next_out=0,a.avail_out=i),(s===Vt||s===$t)&&a.avail_out<=6)this.onData(a.output.subarray(0,a.next_out)),a.avail_out=0;else{if(n=Mt.deflate(a,s),n===ae)return a.next_out>0&&this.onData(a.output.subarray(0,a.next_out)),n=Mt.deflateEnd(this.strm),this.onEnd(n),this.ended=!0,n===ee;if(0!==a.avail_out){if(s>0&&a.next_out>0)this.onData(a.output.subarray(0,a.next_out)),a.avail_out=0;else if(0===a.avail_in)break}else this.onData(a.output)}return!0},re.prototype.onData=function(t){this.chunks.push(t)},re.prototype.onEnd=function(t){t===ee&&(this.result=Kt(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};var le={Deflate:re,deflate:oe,deflateRaw:function(t,e){return(e=e||{}).raw=!0,oe(t,e)},gzip:function(t,e){return(e=e||{}).gzip=!0,oe(t,e)},constants:K};const he=16209;var de=function(t,e){let a,i,n,s,r,o,l,h,d,_,f,c,u,w,m,b,g,p,k,v,y,x,z,A;const E=t.state;a=t.next_in,z=t.input,i=a+(t.avail_in-5),n=t.next_out,A=t.output,s=n-(e-t.avail_out),r=n+(t.avail_out-257),o=E.dmax,l=E.wsize,h=E.whave,d=E.wnext,_=E.window,f=E.hold,c=E.bits,u=E.lencode,w=E.distcode,m=(1<>>24,f>>>=p,c-=p,p=g>>>16&255,0===p)A[n++]=65535&g;else{if(!(16&p)){if(0==(64&p)){g=u[(65535&g)+(f&(1<>>=p,c-=p),c<15&&(f+=z[a++]<>>24,f>>>=p,c-=p,p=g>>>16&255,!(16&p)){if(0==(64&p)){g=w[(65535&g)+(f&(1<o){t.msg="invalid distance too far back",E.mode=he;break t}if(f>>>=p,c-=p,p=n-s,v>p){if(p=v-p,p>h&&E.sane){t.msg="invalid distance too far back",E.mode=he;break t}if(y=0,x=_,0===d){if(y+=l-p,p2;)A[n++]=x[y++],A[n++]=x[y++],A[n++]=x[y++],k-=3;k&&(A[n++]=x[y++],k>1&&(A[n++]=x[y++]))}else{y=n-v;do{A[n++]=A[y++],A[n++]=A[y++],A[n++]=A[y++],k-=3}while(k>2);k&&(A[n++]=A[y++],k>1&&(A[n++]=A[y++]))}break}}break}}while(a>3,a-=k,c-=k<<3,f&=(1<{const l=o.bits;let h,d,_,f,c,u,w=0,m=0,b=0,g=0,p=0,k=0,v=0,y=0,x=0,z=0,A=null;const E=new Uint16Array(16),R=new Uint16Array(16);let Z,U,S,D=null;for(w=0;w<=_e;w++)E[w]=0;for(m=0;m=1&&0===E[g];g--);if(p>g&&(p=g),0===g)return n[s++]=20971520,n[s++]=20971520,o.bits=1,0;for(b=1;b0&&(0===t||1!==g))return-1;for(R[1]=0,w=1;w<_e;w++)R[w+1]=R[w]+E[w];for(m=0;m852||2===t&&x>592)return 1;for(;;){Z=w-v,r[m]+1=u?(U=D[r[m]-u],S=A[r[m]-u]):(U=96,S=0),h=1<>v)+d]=Z<<24|U<<16|S|0}while(0!==d);for(h=1<>=1;if(0!==h?(z&=h-1,z+=h):z=0,m++,0==--E[w]){if(w===g)break;w=e[a+r[m]]}if(w>p&&(z&f)!==_){for(0===v&&(v=p),c+=b,k=w-v,y=1<852||2===t&&x>592)return 1;_=z&f,n[_]=p<<24|k<<16|c-s|0}}return 0!==z&&(n[c+z]=w-v<<24|64<<16|0),o.bits=p,0};const{Z_FINISH:be,Z_BLOCK:ge,Z_TREES:pe,Z_OK:ke,Z_STREAM_END:ve,Z_NEED_DICT:ye,Z_STREAM_ERROR:xe,Z_DATA_ERROR:ze,Z_MEM_ERROR:Ae,Z_BUF_ERROR:Ee,Z_DEFLATED:Re}=K,Ze=16180,Ue=16190,Se=16191,De=16192,Te=16194,Oe=16199,Ie=16200,Fe=16206,Le=16209,Ne=t=>(t>>>24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24);function Be(){this.strm=null,this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new Uint16Array(320),this.work=new Uint16Array(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}const Ce=t=>{if(!t)return 1;const e=t.state;return!e||e.strm!==t||e.mode16211?1:0},Me=t=>{if(Ce(t))return xe;const e=t.state;return t.total_in=t.total_out=e.total=0,t.msg="",e.wrap&&(t.adler=1&e.wrap),e.mode=Ze,e.last=0,e.havedict=0,e.flags=-1,e.dmax=32768,e.head=null,e.hold=0,e.bits=0,e.lencode=e.lendyn=new Int32Array(852),e.distcode=e.distdyn=new Int32Array(592),e.sane=1,e.back=-1,ke},He=t=>{if(Ce(t))return xe;const e=t.state;return e.wsize=0,e.whave=0,e.wnext=0,Me(t)},je=(t,e)=>{let a;if(Ce(t))return xe;const i=t.state;return e<0?(a=0,e=-e):(a=5+(e>>4),e<48&&(e&=15)),e&&(e<8||e>15)?xe:(null!==i.window&&i.wbits!==e&&(i.window=null),i.wrap=a,i.wbits=e,He(t))},Ke=(t,e)=>{if(!t)return xe;const a=new Be;t.state=a,a.strm=t,a.window=null,a.mode=Ze;const i=je(t,e);return i!==ke&&(t.state=null),i};let Pe,Ye,Ge=!0;const Xe=t=>{if(Ge){Pe=new Int32Array(512),Ye=new Int32Array(32);let e=0;for(;e<144;)t.lens[e++]=8;for(;e<256;)t.lens[e++]=9;for(;e<280;)t.lens[e++]=7;for(;e<288;)t.lens[e++]=8;for(me(1,t.lens,0,288,Pe,0,t.work,{bits:9}),e=0;e<32;)t.lens[e++]=5;me(2,t.lens,0,32,Ye,0,t.work,{bits:5}),Ge=!1}t.lencode=Pe,t.lenbits=9,t.distcode=Ye,t.distbits=5},We=(t,e,a,i)=>{let n;const s=t.state;return null===s.window&&(s.wsize=1<=s.wsize?(s.window.set(e.subarray(a-s.wsize,a),0),s.wnext=0,s.whave=s.wsize):(n=s.wsize-s.wnext,n>i&&(n=i),s.window.set(e.subarray(a-i,a-i+n),s.wnext),(i-=n)?(s.window.set(e.subarray(a-i,a),0),s.wnext=i,s.whave=s.wsize):(s.wnext+=n,s.wnext===s.wsize&&(s.wnext=0),s.whaveKe(t,15),inflateInit2:Ke,inflate:(t,e)=>{let a,i,n,s,r,o,l,h,d,_,f,c,u,w,m,b,g,p,k,v,y,x,z=0;const A=new Uint8Array(4);let E,R;const Z=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]);if(Ce(t)||!t.output||!t.input&&0!==t.avail_in)return xe;a=t.state,a.mode===Se&&(a.mode=De),r=t.next_out,n=t.output,l=t.avail_out,s=t.next_in,i=t.input,o=t.avail_in,h=a.hold,d=a.bits,_=o,f=l,x=ke;t:for(;;)switch(a.mode){case Ze:if(0===a.wrap){a.mode=De;break}for(;d<16;){if(0===o)break t;o--,h+=i[s++]<>>8&255,a.check=H(a.check,A,2,0),h=0,d=0,a.mode=16181;break}if(a.head&&(a.head.done=!1),!(1&a.wrap)||(((255&h)<<8)+(h>>8))%31){t.msg="incorrect header check",a.mode=Le;break}if((15&h)!==Re){t.msg="unknown compression method",a.mode=Le;break}if(h>>>=4,d-=4,y=8+(15&h),0===a.wbits&&(a.wbits=y),y>15||y>a.wbits){t.msg="invalid window size",a.mode=Le;break}a.dmax=1<>8&1),512&a.flags&&4&a.wrap&&(A[0]=255&h,A[1]=h>>>8&255,a.check=H(a.check,A,2,0)),h=0,d=0,a.mode=16182;case 16182:for(;d<32;){if(0===o)break t;o--,h+=i[s++]<>>8&255,A[2]=h>>>16&255,A[3]=h>>>24&255,a.check=H(a.check,A,4,0)),h=0,d=0,a.mode=16183;case 16183:for(;d<16;){if(0===o)break t;o--,h+=i[s++]<>8),512&a.flags&&4&a.wrap&&(A[0]=255&h,A[1]=h>>>8&255,a.check=H(a.check,A,2,0)),h=0,d=0,a.mode=16184;case 16184:if(1024&a.flags){for(;d<16;){if(0===o)break t;o--,h+=i[s++]<>>8&255,a.check=H(a.check,A,2,0)),h=0,d=0}else a.head&&(a.head.extra=null);a.mode=16185;case 16185:if(1024&a.flags&&(c=a.length,c>o&&(c=o),c&&(a.head&&(y=a.head.extra_len-a.length,a.head.extra||(a.head.extra=new Uint8Array(a.head.extra_len)),a.head.extra.set(i.subarray(s,s+c),y)),512&a.flags&&4&a.wrap&&(a.check=H(a.check,i,c,s)),o-=c,s+=c,a.length-=c),a.length))break t;a.length=0,a.mode=16186;case 16186:if(2048&a.flags){if(0===o)break t;c=0;do{y=i[s+c++],a.head&&y&&a.length<65536&&(a.head.name+=String.fromCharCode(y))}while(y&&c>9&1,a.head.done=!0),t.adler=a.check=0,a.mode=Se;break;case 16189:for(;d<32;){if(0===o)break t;o--,h+=i[s++]<>>=7&d,d-=7&d,a.mode=Fe;break}for(;d<3;){if(0===o)break t;o--,h+=i[s++]<>>=1,d-=1,3&h){case 0:a.mode=16193;break;case 1:if(Xe(a),a.mode=Oe,e===pe){h>>>=2,d-=2;break t}break;case 2:a.mode=16196;break;case 3:t.msg="invalid block type",a.mode=Le}h>>>=2,d-=2;break;case 16193:for(h>>>=7&d,d-=7&d;d<32;){if(0===o)break t;o--,h+=i[s++]<>>16^65535)){t.msg="invalid stored block lengths",a.mode=Le;break}if(a.length=65535&h,h=0,d=0,a.mode=Te,e===pe)break t;case Te:a.mode=16195;case 16195:if(c=a.length,c){if(c>o&&(c=o),c>l&&(c=l),0===c)break t;n.set(i.subarray(s,s+c),r),o-=c,s+=c,l-=c,r+=c,a.length-=c;break}a.mode=Se;break;case 16196:for(;d<14;){if(0===o)break t;o--,h+=i[s++]<>>=5,d-=5,a.ndist=1+(31&h),h>>>=5,d-=5,a.ncode=4+(15&h),h>>>=4,d-=4,a.nlen>286||a.ndist>30){t.msg="too many length or distance symbols",a.mode=Le;break}a.have=0,a.mode=16197;case 16197:for(;a.have>>=3,d-=3}for(;a.have<19;)a.lens[Z[a.have++]]=0;if(a.lencode=a.lendyn,a.lenbits=7,E={bits:a.lenbits},x=me(0,a.lens,0,19,a.lencode,0,a.work,E),a.lenbits=E.bits,x){t.msg="invalid code lengths set",a.mode=Le;break}a.have=0,a.mode=16198;case 16198:for(;a.have>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<>>=m,d-=m,a.lens[a.have++]=g;else{if(16===g){for(R=m+2;d>>=m,d-=m,0===a.have){t.msg="invalid bit length repeat",a.mode=Le;break}y=a.lens[a.have-1],c=3+(3&h),h>>>=2,d-=2}else if(17===g){for(R=m+3;d>>=m,d-=m,y=0,c=3+(7&h),h>>>=3,d-=3}else{for(R=m+7;d>>=m,d-=m,y=0,c=11+(127&h),h>>>=7,d-=7}if(a.have+c>a.nlen+a.ndist){t.msg="invalid bit length repeat",a.mode=Le;break}for(;c--;)a.lens[a.have++]=y}}if(a.mode===Le)break;if(0===a.lens[256]){t.msg="invalid code -- missing end-of-block",a.mode=Le;break}if(a.lenbits=9,E={bits:a.lenbits},x=me(1,a.lens,0,a.nlen,a.lencode,0,a.work,E),a.lenbits=E.bits,x){t.msg="invalid literal/lengths set",a.mode=Le;break}if(a.distbits=6,a.distcode=a.distdyn,E={bits:a.distbits},x=me(2,a.lens,a.nlen,a.ndist,a.distcode,0,a.work,E),a.distbits=E.bits,x){t.msg="invalid distances set",a.mode=Le;break}if(a.mode=Oe,e===pe)break t;case Oe:a.mode=Ie;case Ie:if(o>=6&&l>=258){t.next_out=r,t.avail_out=l,t.next_in=s,t.avail_in=o,a.hold=h,a.bits=d,de(t,f),r=t.next_out,n=t.output,l=t.avail_out,s=t.next_in,i=t.input,o=t.avail_in,h=a.hold,d=a.bits,a.mode===Se&&(a.back=-1);break}for(a.back=0;z=a.lencode[h&(1<>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<>p)],m=z>>>24,b=z>>>16&255,g=65535&z,!(p+m<=d);){if(0===o)break t;o--,h+=i[s++]<>>=p,d-=p,a.back+=p}if(h>>>=m,d-=m,a.back+=m,a.length=g,0===b){a.mode=16205;break}if(32&b){a.back=-1,a.mode=Se;break}if(64&b){t.msg="invalid literal/length code",a.mode=Le;break}a.extra=15&b,a.mode=16201;case 16201:if(a.extra){for(R=a.extra;d>>=a.extra,d-=a.extra,a.back+=a.extra}a.was=a.length,a.mode=16202;case 16202:for(;z=a.distcode[h&(1<>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<>p)],m=z>>>24,b=z>>>16&255,g=65535&z,!(p+m<=d);){if(0===o)break t;o--,h+=i[s++]<>>=p,d-=p,a.back+=p}if(h>>>=m,d-=m,a.back+=m,64&b){t.msg="invalid distance code",a.mode=Le;break}a.offset=g,a.extra=15&b,a.mode=16203;case 16203:if(a.extra){for(R=a.extra;d>>=a.extra,d-=a.extra,a.back+=a.extra}if(a.offset>a.dmax){t.msg="invalid distance too far back",a.mode=Le;break}a.mode=16204;case 16204:if(0===l)break t;if(c=f-l,a.offset>c){if(c=a.offset-c,c>a.whave&&a.sane){t.msg="invalid distance too far back",a.mode=Le;break}c>a.wnext?(c-=a.wnext,u=a.wsize-c):u=a.wnext-c,c>a.length&&(c=a.length),w=a.window}else w=n,u=r-a.offset,c=a.length;c>l&&(c=l),l-=c,a.length-=c;do{n[r++]=w[u++]}while(--c);0===a.length&&(a.mode=Ie);break;case 16205:if(0===l)break t;n[r++]=a.length,l--,a.mode=Ie;break;case Fe:if(a.wrap){for(;d<32;){if(0===o)break t;o--,h|=i[s++]<{if(Ce(t))return xe;let e=t.state;return e.window&&(e.window=null),t.state=null,ke},inflateGetHeader:(t,e)=>{if(Ce(t))return xe;const a=t.state;return 0==(2&a.wrap)?xe:(a.head=e,e.done=!1,ke)},inflateSetDictionary:(t,e)=>{const a=e.length;let i,n,s;return Ce(t)?xe:(i=t.state,0!==i.wrap&&i.mode!==Ue?xe:i.mode===Ue&&(n=1,n=C(n,e,a,0),n!==i.check)?ze:(s=We(t,e,a,a),s?(i.mode=16210,Ae):(i.havedict=1,ke)))},inflateInfo:"pako inflate (from Nodeca project)"};var Je=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1};const Qe=Object.prototype.toString,{Z_NO_FLUSH:Ve,Z_FINISH:$e,Z_OK:ta,Z_STREAM_END:ea,Z_NEED_DICT:aa,Z_STREAM_ERROR:ia,Z_DATA_ERROR:na,Z_MEM_ERROR:sa}=K;function ra(t){this.options=jt({chunkSize:65536,windowBits:15,to:""},t||{});const e=this.options;e.raw&&e.windowBits>=0&&e.windowBits<16&&(e.windowBits=-e.windowBits,0===e.windowBits&&(e.windowBits=-15)),!(e.windowBits>=0&&e.windowBits<16)||t&&t.windowBits||(e.windowBits+=32),e.windowBits>15&&e.windowBits<48&&0==(15&e.windowBits)&&(e.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new qt,this.strm.avail_out=0;let a=qe.inflateInit2(this.strm,e.windowBits);if(a!==ta)throw new Error(j[a]);if(this.header=new Je,qe.inflateGetHeader(this.strm,this.header),e.dictionary&&("string"==typeof e.dictionary?e.dictionary=Gt(e.dictionary):"[object ArrayBuffer]"===Qe.call(e.dictionary)&&(e.dictionary=new Uint8Array(e.dictionary)),e.raw&&(a=qe.inflateSetDictionary(this.strm,e.dictionary),a!==ta)))throw new Error(j[a])}function oa(t,e){const a=new ra(e);if(a.push(t),a.err)throw a.msg||j[a.err];return a.result}ra.prototype.push=function(t,e){const a=this.strm,i=this.options.chunkSize,n=this.options.dictionary;let s,r,o;if(this.ended)return!1;for(r=e===~~e?e:!0===e?$e:Ve,"[object ArrayBuffer]"===Qe.call(t)?a.input=new Uint8Array(t):a.input=t,a.next_in=0,a.avail_in=a.input.length;;){for(0===a.avail_out&&(a.output=new Uint8Array(i),a.next_out=0,a.avail_out=i),s=qe.inflate(a,r),s===aa&&n&&(s=qe.inflateSetDictionary(a,n),s===ta?s=qe.inflate(a,r):s===na&&(s=aa));a.avail_in>0&&s===ea&&a.state.wrap>0&&0!==t[a.next_in];)qe.inflateReset(a),s=qe.inflate(a,r);switch(s){case ia:case na:case aa:case sa:return this.onEnd(s),this.ended=!0,!1}if(o=a.avail_out,a.next_out&&(0===a.avail_out||s===ea))if("string"===this.options.to){let t=Wt(a.output,a.next_out),e=a.next_out-t,n=Xt(a.output,t);a.next_out=e,a.avail_out=i-e,e&&a.output.set(a.output.subarray(t,t+e),0),this.onData(n)}else this.onData(a.output.length===a.next_out?a.output:a.output.subarray(0,a.next_out));if(s!==ta||0!==o){if(s===ea)return s=qe.inflateEnd(this.strm),this.onEnd(s),this.ended=!0,!0;if(0===a.avail_in)break}}return!0},ra.prototype.onData=function(t){this.chunks.push(t)},ra.prototype.onEnd=function(t){t===ta&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=Kt(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};var la={Inflate:ra,inflate:oa,inflateRaw:function(t,e){return(e=e||{}).raw=!0,oa(t,e)},ungzip:oa,constants:K};const{Deflate:ha,deflate:da,deflateRaw:_a,gzip:fa}=le,{Inflate:ca,inflate:ua,inflateRaw:wa,ungzip:ma}=la;var ba=ha,ga=da,pa=_a,ka=fa,va=ca,ya=ua,xa=wa,za=ma,Aa=K,Ea={Deflate:ba,deflate:ga,deflateRaw:pa,gzip:ka,Inflate:va,inflate:ya,inflateRaw:xa,ungzip:za,constants:Aa};t.Deflate=ba,t.Inflate=va,t.constants=Aa,t.default=Ea,t.deflate=ga,t.deflateRaw=pa,t.gzip=ka,t.inflate=ya,t.inflateRaw=xa,t.ungzip=za,Object.defineProperty(t,"__esModule",{value:!0})})); diff --git a/tools/rosdistro_cache_proxy.py b/tools/rosdistro_cache_proxy.py new file mode 100644 index 00000000000..f0334156de3 --- /dev/null +++ b/tools/rosdistro_cache_proxy.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +"""Tiny local proxy for ROS distro cache gzip files. + +Why this exists: +- Browsers may not be able to fetch repo.ros2.org directly due to CORS. +- This proxy is same-origin relative to your local docs server workflow. +- It enables "proxy-first, bundled-fallback" runtime behavior. + +Endpoint: + /api/rosdistro-cache/-cache.yaml.gz + +Example: + python tools/rosdistro_cache_proxy.py --port 9000 + # Then point conf.py setting to: + # ros_related_packages_proxy_url = 'http://127.0.0.1:9000/api/rosdistro-cache/{distro}-cache.yaml.gz' +""" + +from __future__ import annotations + +import argparse +import gzip +import re +import socket +import traceback +import time +import urllib.error +import urllib.parse +import urllib.request +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from typing import Dict, Tuple + +UPSTREAM_TEMPLATE = 'https://repo.ros2.org/rosdistro_cache/{distro}-cache.yaml.gz' +DISTRO_RE = re.compile(r'^[a-z0-9][a-z0-9_-]*$', re.IGNORECASE) +PATH_RE = re.compile(r'^/api/rosdistro-cache/([a-z0-9_-]+)-cache\.yaml\.gz$', re.IGNORECASE) + + +class CacheStore: + """Simple in-memory TTL cache for gzip bytes by distro.""" + + def __init__(self, ttl_seconds: int) -> None: + self._ttl = max(0, ttl_seconds) + self._data: Dict[str, Tuple[float, bytes]] = {} + + def get(self, distro: str) -> bytes | None: + record = self._data.get(distro) + if record is None: + return None + expires_at, payload = record + if time.time() >= expires_at: + self._data.pop(distro, None) + return None + return payload + + def put(self, distro: str, payload: bytes) -> None: + self._data[distro] = (time.time() + self._ttl, payload) + + +class ProxyHandler(BaseHTTPRequestHandler): + """HTTP handler serving rosdistro cache gzip responses.""" + + server_version = 'RostdistroCacheProxy/1.0' + cache: CacheStore + timeout_seconds: int + + def _send_cors_headers(self) -> None: + """Allow browser fetches from local docs hosts on another port.""" + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS') + self.send_header('Access-Control-Allow-Headers', 'Content-Type') + + def do_OPTIONS(self) -> None: # noqa: N802 (BaseHTTPRequestHandler interface) + self.send_response(204) + self._send_cors_headers() + self.end_headers() + + def do_GET(self) -> None: # noqa: N802 (BaseHTTPRequestHandler interface) + try: + path = urllib.parse.urlparse(self.path).path + match = PATH_RE.match(path) + if not match: + self.send_error(404, 'Unknown path') + return + + distro = match.group(1).lower() + if not DISTRO_RE.match(distro): + self.send_error(400, 'Invalid distro name') + return + + if not hasattr(self, 'cache'): + raise RuntimeError('Proxy handler is missing cache configuration') + if not hasattr(self, 'timeout_seconds'): + raise RuntimeError('Proxy handler is missing timeout configuration') + + payload = self.cache.get(distro) + if payload is None: + try: + payload = self._fetch_upstream(distro) + except urllib.error.HTTPError as exc: + self.send_error(exc.code, f'Upstream HTTP error: {exc.reason}') + return + except urllib.error.URLError as exc: + self.send_error(502, f'Upstream URL error: {exc.reason}') + return + except TimeoutError: + self.send_error(504, 'Upstream timeout') + return + except ValueError as exc: + self.send_error(502, f'Bad upstream payload: {exc}') + return + self.cache.put(distro, payload) + + self.send_response(200) + self._send_cors_headers() + self.send_header('Content-Type', 'application/gzip') + self.send_header('Cache-Control', 'public, max-age=300') + self.send_header('Content-Length', str(len(payload))) + self.end_headers() + self.wfile.write(payload) + except Exception as exc: # pragma: no cover - defensive safety net for local proxy. + traceback.print_exc() + self.send_error(500, f'Proxy internal error: {exc}') + + def log_message(self, fmt: str, *args) -> None: + """Compact log format.""" + super().log_message('[proxy] ' + fmt, *args) + + def _fetch_upstream(self, distro: str) -> bytes: + url = UPSTREAM_TEMPLATE.format(distro=distro) + request = urllib.request.Request(url, headers={'User-Agent': 'ros2-docs-cache-proxy/1.0'}) + with urllib.request.urlopen(request, timeout=self.timeout_seconds) as response: + payload = response.read() + + # Quick sanity check: must be valid gzip bytes. + try: + gzip.decompress(payload) + except OSError as exc: + raise ValueError('response is not valid gzip') from exc + return payload + + +def _assert_port_free(host: str, port: int) -> None: + """Fail fast when another local server already owns the port (common on Windows).""" + probe = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + if hasattr(socket, 'SO_EXCLUSIVEADDRUSE'): + probe.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1) + probe.bind((host, port)) + except OSError as exc: + raise SystemExit( + f'Port {port} on {host} is already in use ({exc}).\n' + 'Stop leftover python/http.server processes or pass --port with a free value.' + ) from exc + finally: + probe.close() + + +def main() -> None: + parser = argparse.ArgumentParser(description='Local proxy for rosdistro cache gz files.') + parser.add_argument('--host', default='127.0.0.1', help='Listen host (default: 127.0.0.1)') + parser.add_argument('--port', type=int, default=9000, help='Listen port (default: 9000)') + parser.add_argument( + '--cache-ttl', + type=int, + default=300, + help='In-memory cache TTL seconds (default: 300)', + ) + parser.add_argument( + '--upstream-timeout', + type=int, + default=20, + help='Upstream timeout seconds (default: 20)', + ) + args = parser.parse_args() + + _assert_port_free(args.host, args.port) + + cache = CacheStore(ttl_seconds=args.cache_ttl) + + class ConfiguredProxyHandler(ProxyHandler): + """Proxy handler class with shared cache and timeout configuration.""" + + ConfiguredProxyHandler.cache = cache + ConfiguredProxyHandler.timeout_seconds = args.upstream_timeout + + server = ThreadingHTTPServer((args.host, args.port), ConfiguredProxyHandler) + print( + f'Proxy running on http://{args.host}:{args.port} ' + '(endpoint: /api/rosdistro-cache/-cache.yaml.gz)' + ) + server.serve_forever() + + +if __name__ == '__main__': + main() + diff --git a/tools/serve_docs_with_proxy.py b/tools/serve_docs_with_proxy.py new file mode 100644 index 00000000000..1da6da90fef --- /dev/null +++ b/tools/serve_docs_with_proxy.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +"""Serve built Sphinx HTML and rosdistro cache API on one origin (local testing). + +Use this instead of ``python -m http.server`` when testing proxy-first related +packages. The browser can fetch ``/api/rosdistro-cache/-cache.yaml.gz`` +same-origin (no cross-port CORS quirks). + +Build docs (proxy URL is the conf.py default; env override optional):: + + make html + +Then run:: + + python tools/serve_docs_with_proxy.py + +Open http://127.0.0.1:8000/... and check DevTools Network for a 200 on +``/api/rosdistro-cache/rolling-cache.yaml.gz``. +""" + +from __future__ import annotations + +import argparse +import gzip +import re +import socket +import urllib.error +import urllib.parse +import urllib.request +from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path + +UPSTREAM_TEMPLATE = 'https://repo.ros2.org/rosdistro_cache/{distro}-cache.yaml.gz' +DISTRO_RE = re.compile(r'^[a-z0-9][a-z0-9_-]*$', re.IGNORECASE) +PATH_RE = re.compile(r'^/api/rosdistro-cache/([a-z0-9_-]+)-cache\.yaml\.gz$', re.IGNORECASE) + + +class CacheStore: + """Simple in-memory TTL cache for gzip bytes by distro.""" + + def __init__(self, ttl_seconds: int) -> None: + import time + + self._time = time + self._ttl = max(0, ttl_seconds) + self._data: dict[str, tuple[float, bytes]] = {} + + def get(self, distro: str) -> bytes | None: + record = self._data.get(distro) + if record is None: + return None + expires_at, payload = record + if self._time.time() >= expires_at: + self._data.pop(distro, None) + return None + return payload + + def put(self, distro: str, payload: bytes) -> None: + self._data[distro] = (self._time.time() + self._ttl, payload) + + +def _fetch_upstream(distro: str, timeout_seconds: int) -> bytes: + url = UPSTREAM_TEMPLATE.format(distro=distro) + request = urllib.request.Request(url, headers={'User-Agent': 'ros2-docs-cache-proxy/1.0'}) + with urllib.request.urlopen(request, timeout=timeout_seconds) as response: + payload = response.read() + try: + gzip.decompress(payload) + except OSError as exc: + raise ValueError('response is not valid gzip') from exc + return payload + + +class DocsWithProxyHandler(SimpleHTTPRequestHandler): + """Static files from *directory*; ``/api/rosdistro-cache/...`` proxied upstream.""" + + cache: CacheStore + upstream_timeout: int + + def _send_cors_headers(self) -> None: + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS') + self.send_header('Access-Control-Allow-Headers', 'Content-Type') + + def do_OPTIONS(self) -> None: # noqa: N802 + path = urllib.parse.urlparse(self.path).path + if PATH_RE.match(path): + self.send_response(204) + self._send_cors_headers() + self.end_headers() + return + super().do_OPTIONS() + + def do_GET(self) -> None: # noqa: N802 + path = urllib.parse.urlparse(self.path).path + match = PATH_RE.match(path) + if not match: + return super().do_GET() + + distro = match.group(1).lower() + if not DISTRO_RE.match(distro): + self.send_error(400, 'Invalid distro name') + return + + payload = self.cache.get(distro) + if payload is None: + try: + payload = _fetch_upstream(distro, self.upstream_timeout) + except urllib.error.HTTPError as exc: + self.send_error(exc.code, f'Upstream HTTP error: {exc.reason}') + return + except urllib.error.URLError as exc: + self.send_error(502, f'Upstream URL error: {exc.reason}') + return + except TimeoutError: + self.send_error(504, 'Upstream timeout') + return + except ValueError as exc: + self.send_error(502, f'Bad upstream payload: {exc}') + return + self.cache.put(distro, payload) + + self.send_response(200) + self._send_cors_headers() + self.send_header('Content-Type', 'application/gzip') + self.send_header('Cache-Control', 'public, max-age=300') + self.send_header('Content-Length', str(len(payload))) + self.end_headers() + self.wfile.write(payload) + + def log_message(self, fmt: str, *args) -> None: + super().log_message('[docs+proxy] ' + fmt, *args) + + +def _assert_port_free(host: str, port: int) -> None: + """Fail fast when another local server already owns the port (common on Windows).""" + probe = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + if hasattr(socket, 'SO_EXCLUSIVEADDRUSE'): + probe.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1) + probe.bind((host, port)) + except OSError as exc: + raise SystemExit( + f'Port {port} on {host} is already in use ({exc}).\n' + 'Stop leftover python/http.server processes or pass --port with a free value.' + ) from exc + finally: + probe.close() + + +def main() -> None: + repo = Path(__file__).resolve().parents[1] + default_html = repo / 'build' / 'html' + + parser = argparse.ArgumentParser( + description='Serve build/html and /api/rosdistro-cache/ on one port.', + ) + parser.add_argument('--host', default='127.0.0.1', help='Listen host (default: 127.0.0.1)') + parser.add_argument('--port', type=int, default=8000, help='Listen port (default: 8000)') + parser.add_argument( + '--directory', + type=Path, + default=default_html, + help=f'HTML root (default: {default_html})', + ) + parser.add_argument('--cache-ttl', type=int, default=300, help='Proxy cache TTL seconds') + parser.add_argument('--upstream-timeout', type=int, default=20, help='Upstream timeout seconds') + args = parser.parse_args() + + html_dir = args.directory.resolve() + if not html_dir.is_dir(): + raise SystemExit(f'HTML directory not found: {html_dir}\nRun make html first.') + + _assert_port_free(args.host, args.port) + + cache_store = CacheStore(ttl_seconds=args.cache_ttl) + html_dir_str = str(html_dir) + + class ConfiguredHandler(DocsWithProxyHandler): + """Handler with shared cache and HTML root.""" + + def __init__(self, request, client_address, server): + super().__init__(request, client_address, server, directory=html_dir_str) + + ConfiguredHandler.cache = cache_store + ConfiguredHandler.upstream_timeout = args.upstream_timeout + + server = ThreadingHTTPServer((args.host, args.port), ConfiguredHandler) + print(f'Serving {html_dir}') + print(f'Open http://{args.host}:{args.port}/') + print('API: /api/rosdistro-cache/-cache.yaml.gz') + print( + 'Proxy URL default is in conf.py; override with ROS_RELATED_PACKAGES_PROXY_URL if needed' + ) + server.serve_forever() + + +if __name__ == '__main__': + main() From 630d7cc33845cded560de9d763785a5a4c58d0f4 Mon Sep 17 00:00:00 2001 From: Kacper Bojakowski Date: Sat, 30 May 2026 02:24:35 +0200 Subject: [PATCH 2/2] Update directives to be lists only This allows users to add entries manually before or after the list generated by the directive. --- plugins/ros_related_articles.py | 126 +++++++++++++++--- plugins/ros_related_packages.py | 4 + .../Migrating-Interfaces.rst | 2 + ...ingle-Package-Define-And-Use-Interface.rst | 6 + .../Creating-A-Workspace.rst | 2 + .../Creating-Your-First-ROS2-Package.rst | 2 + source/_static/related_packages.js | 52 ++++++-- 7 files changed, 164 insertions(+), 30 deletions(-) diff --git a/plugins/ros_related_articles.py b/plugins/ros_related_articles.py index be55a54a6e0..c214be66303 100644 --- a/plugins/ros_related_articles.py +++ b/plugins/ros_related_articles.py @@ -87,20 +87,114 @@ def _normalized_value(raw: str) -> str: return ' '.join(raw.strip().lower().split()) +def _previous_sibling(node: nodes.Node) -> nodes.Node | None: + """Return the node immediately before *node* among its parent's children.""" + parent = node.parent + if parent is None: + return None + children = parent.children + idx = children.index(node) + if idx == 0: + return None + return children[idx - 1] + + +def _next_sibling(node: nodes.Node) -> nodes.Node | None: + """Return the node immediately after *node* among its parent's children.""" + parent = node.parent + if parent is None: + return None + children = parent.children + idx = children.index(node) + if idx + 1 >= len(children): + return None + return children[idx + 1] + + +def _ensure_class(node: nodes.Element, class_name: str) -> None: + """Append *class_name* to *node* if it is not already present.""" + classes = list(node.get('classes', []) or []) + if class_name not in classes: + classes.append(class_name) + node['classes'] = classes + + +def _append_article_items( + bullet_list: nodes.bullet_list, + matches: List[RelatedArticle], + app, + fromdocname: str, +) -> None: + """Append related-article links as list items to *bullet_list*.""" + for item in matches: + refuri = app.builder.get_relative_uri(fromdocname, item['docname']) + link = nodes.reference('', item['title'], refuri=refuri) + entry = nodes.list_item() + para = nodes.paragraph() + para += link + entry += para + bullet_list += entry + + +def _absorb_bullet_list( + target: nodes.bullet_list, + source: nodes.bullet_list, +) -> None: + """Move all list items from *source* onto the end of *target*.""" + for child in list(source.children): + if isinstance(child, nodes.list_item): + source.remove(child) + target.append(child) + + +def _resolve_related_articles_list( + node: RosRelatedArticlesNode, + matches: List[RelatedArticle], + app, + fromdocname: str, +) -> None: + """Replace *node* with generated links, merging adjacent manual bullet lists.""" + prev = _previous_sibling(node) + next_sib = _next_sibling(node) + prev_list = prev if isinstance(prev, nodes.bullet_list) else None + next_list = next_sib if isinstance(next_sib, nodes.bullet_list) else None + + if prev_list is not None: + target = prev_list + _ensure_class(target, 'related-articles') + else: + target = nodes.bullet_list(classes=['related-articles']) + + _append_article_items(target, matches, app, fromdocname) + + if next_list is not None: + _absorb_bullet_list(target, next_list) + next_list.replace_self([]) + + if prev_list is not None: + node.replace_self([]) + else: + node.replace_self(target) + + class RosRelatedArticlesNode(nodes.General, nodes.Element): """Placeholder node replaced during ``doctree-resolved``.""" class RosRelatedArticlesDirective(SphinxDirective): - """Emit a placeholder for static related-article links. + """Emit a placeholder replaced by a bullet list of related article links. - Uses page metadata values from ``.. meta::``: + Write the section intro (e.g. ``Related articles:``) in the RST source + before this directive. Optional bullet items immediately before or after + the directive are merged into the same list as the generated links. .. code-block:: rst .. meta:: :area: Tutorials :experience: Beginner + + Uses page metadata values from ``.. meta::`` (see above). """ has_content = False @@ -173,7 +267,7 @@ def build_related_articles_index(app, env) -> None: def resolve_related_articles(app, doctree, fromdocname) -> None: - """Replace placeholders with static paragraph + list markup.""" + """Replace placeholders with a static bullet list.""" index: List[RelatedArticle] = getattr(app.env, 'ros_related_articles_index', []) for node in list(doctree.traverse(RosRelatedArticlesNode)): area = _normalized_value(str(node.get('area', ''))) @@ -189,27 +283,19 @@ def resolve_related_articles(app, doctree, fromdocname) -> None: matches.sort(key=lambda item: item['title'].lower()) matches = matches[:max_items] + prev = _previous_sibling(node) + next_sib = _next_sibling(node) + prev_list = prev if isinstance(prev, nodes.bullet_list) else None + next_list = next_sib if isinstance(next_sib, nodes.bullet_list) else None + if not matches: + if prev_list is not None and next_list is not None: + _absorb_bullet_list(prev_list, next_list) + next_list.replace_self([]) node.replace_self([]) continue - container = nodes.container(classes=['related-articles']) - intro = nodes.paragraph() - intro += nodes.Text('Related articles:') - container += intro - - bullets = nodes.bullet_list() - for item in matches: - refuri = app.builder.get_relative_uri(fromdocname, item['docname']) - link = nodes.reference('', item['title'], refuri=refuri) - entry = nodes.list_item() - para = nodes.paragraph() - para += link - entry += para - bullets += entry - container += bullets - - node.replace_self(container) + _resolve_related_articles_list(node, matches, app, fromdocname) def setup(app): diff --git a/plugins/ros_related_packages.py b/plugins/ros_related_packages.py index 8321f18397c..730299e69a6 100644 --- a/plugins/ros_related_packages.py +++ b/plugins/ros_related_packages.py @@ -109,6 +109,10 @@ def _positive_int_option(argument: str) -> int: class RosRelatedPackagesDirective(SphinxDirective): """Emit a placeholder ``div`` filled at runtime by ``related_packages.js``. + Write the section intro (e.g. ``Packages/reference:``) in the RST source + before this directive. Optional bullet items immediately before or after + the directive are merged into the same list at runtime. + Filter criteria (currently ``build-type``) should be supplied as **HTML meta tags** via Docutils ``.. meta::`` so values appear in ```` and not in the page body:: diff --git a/source/How-To-Guides/Migrating-from-ROS1/Migrating-Interfaces.rst b/source/How-To-Guides/Migrating-from-ROS1/Migrating-Interfaces.rst index 9d0ffd63251..bb5a8403adb 100644 --- a/source/How-To-Guides/Migrating-from-ROS1/Migrating-Interfaces.rst +++ b/source/How-To-Guides/Migrating-from-ROS1/Migrating-Interfaces.rst @@ -59,4 +59,6 @@ This will replace ``add_message_files`` and ``add_service_files`` listing of all Related content --------------- +Related articles: + .. ros-related-articles:: \ No newline at end of file diff --git a/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst b/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst index 306da433467..1ef34bb21db 100644 --- a/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst +++ b/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst @@ -262,7 +262,13 @@ Steps Related content --------------- +Related articles: + +* some stuff here + .. ros-related-articles:: +Packages/reference: + .. ros-related-packages:: diff --git a/source/Tutorials/Beginner-Client-Libraries/Creating-A-Workspace/Creating-A-Workspace.rst b/source/Tutorials/Beginner-Client-Libraries/Creating-A-Workspace/Creating-A-Workspace.rst index 7a92e031f7d..b48295a44fc 100644 --- a/source/Tutorials/Beginner-Client-Libraries/Creating-A-Workspace/Creating-A-Workspace.rst +++ b/source/Tutorials/Beginner-Client-Libraries/Creating-A-Workspace/Creating-A-Workspace.rst @@ -412,4 +412,6 @@ Now that you understand the details behind creating, building and sourcing your Related content --------------- +Related articles: + .. ros-related-articles:: diff --git a/source/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.rst b/source/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.rst index 2d246cf1fcf..26c96381a39 100644 --- a/source/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.rst +++ b/source/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.rst @@ -545,4 +545,6 @@ You'll start with a simple publisher/subscriber system, which you can choose to Related content --------------- +Related articles: + .. ros-related-articles:: diff --git a/source/_static/related_packages.js b/source/_static/related_packages.js index 8d7880e8f78..b8120e10393 100644 --- a/source/_static/related_packages.js +++ b/source/_static/related_packages.js @@ -308,9 +308,19 @@ return a.localeCompare(b); }); var picked = names.slice(0, max); + var prevList = el.previousElementSibling; + prevList = prevList && prevList.tagName === 'UL' ? prevList : null; + var nextList = el.nextElementSibling; + nextList = nextList && nextList.tagName === 'UL' ? nextList : null; + var mergeList; + + if (prevList) { + mergeList = prevList; + } else { + mergeList = document.createElement('ul'); + mergeList.className = 'related-packages__list'; + } - var ul = document.createElement('ul'); - ul.className = 'related-packages__list'; var j; for (j = 0; j < picked.length; j += 1) { var pkg = picked[j]; @@ -322,24 +332,46 @@ a.rel = 'noopener noreferrer'; li.appendChild(a); li.appendChild(document.createTextNode(': ' + description)); - ul.appendChild(li); + mergeList.appendChild(li); } - el.innerHTML = ''; el.classList.remove('related-packages--loading'); if (picked.length === 0) { + el.innerHTML = ''; + if (prevList && nextList) { + while (nextList.firstChild) { + prevList.appendChild(nextList.firstChild); + } + nextList.remove(); + prevList.classList.add('related-packages__list'); + } + if (prevList || nextList) { + el.remove(); + return; + } var p = document.createElement('p'); p.className = 'related-packages__empty'; p.textContent = 'No packages matched this filter.'; el.appendChild(p); - } else { - var intro = document.createElement('p'); - intro.className = 'related-packages__intro'; - intro.textContent = 'Packages/reference: '; - el.appendChild(intro); - el.appendChild(ul); + return; } + + if (nextList) { + while (nextList.firstChild) { + mergeList.appendChild(nextList.firstChild); + } + nextList.remove(); + } + + if (prevList) { + mergeList.classList.add('related-packages__list'); + el.remove(); + return; + } + + el.parentNode.insertBefore(mergeList, el); + el.remove(); } function fillAll() {