From 012f30c3d01f392c44ec4047b055686b302c2557 Mon Sep 17 00:00:00 2001 From: Michael Catanzaro Date: Thu, 30 Jan 2025 13:21:23 -0600 Subject: [PATCH] Add URL handler to open help files in web browser Resolves: RHEL-76398 --- url-handler.patch | 304 ++++++++++++++++++++++++++++++++++++++++++++++ yelp-tools.spec | 23 +++- 2 files changed, 325 insertions(+), 2 deletions(-) create mode 100644 url-handler.patch diff --git a/url-handler.patch b/url-handler.patch new file mode 100644 index 0000000..859839e --- /dev/null +++ b/url-handler.patch @@ -0,0 +1,304 @@ +From 66d2d8d8239f781f20f913dbe388b52e14f8dffb Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 18 May 2023 11:28:37 -0400 +Subject: [PATCH] tools: Add a url handler that generates html from + documentation + +Some systems don't install yelp by default but still want to show +documentation. + +This commit adds a small url handler to automatically convert +documentation to html format and then launch a web browser to show +it. +--- + meson_options.txt | 4 + + tools/meson.build | 24 +++ + tools/yelp-build-url-handler.desktop.in | 6 + + tools/yelp-build-url-handler.in | 212 ++++++++++++++++++++++++ + 4 files changed, 246 insertions(+) + create mode 100644 tools/yelp-build-url-handler.desktop.in + create mode 100755 tools/yelp-build-url-handler.in + +diff --git a/meson_options.txt b/meson_options.txt +index 0526e6f..03e9050 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -5,3 +5,7 @@ option('yelpm4', + option('help', + type: 'boolean', value: false, + description: 'Install help files') ++ ++option('url_handler', ++ type: 'feature', value: 'disabled', ++ description: 'Install URL handler to open help in web browesr, use when not building Yelp') +diff --git a/tools/meson.build b/tools/meson.build +index 35187ca..b2518d6 100644 +--- a/tools/meson.build ++++ b/tools/meson.build +@@ -1,5 +1,10 @@ + yelp_tools_in = configuration_data() + yelp_tools_in.set('DATADIR', pkgdir) ++yelp_tools_in.set('HELPDIR', join_paths ( ++ datadir, ++ 'help', ++ ) ++) + + yelp_tools_in.set('YELP_JS_DIR', yelp_js_dir) + +@@ -48,3 +53,22 @@ if get_option('yelpm4') == true + ) + ) + endif ++ ++if get_option('url_handler').enabled() ++ configure_file( ++ input: 'yelp-build-url-handler.in', ++ output: 'yelp-build-url-handler', ++ configuration: yelp_tools_in, ++ install: true, ++ install_dir: bindir, ++ ) ++ ++ configure_file( ++ input: 'yelp-build-url-handler.desktop.in', ++ output: '@BASENAME@', ++ configuration: { ++ 'bindir': join_paths(get_option('prefix'), get_option('bindir')) ++ }, ++ install_dir: join_paths(datadir, 'applications') ++ ) ++endif +diff --git a/tools/yelp-build-url-handler.desktop.in b/tools/yelp-build-url-handler.desktop.in +new file mode 100644 +index 0000000..9ead646 +--- /dev/null ++++ b/tools/yelp-build-url-handler.desktop.in +@@ -0,0 +1,6 @@ ++[Desktop Entry] ++Name=Yelp URL Handler ++Exec=@bindir@/yelp-build-url-handler %u ++Type=Application ++MimeType=x-scheme-handler/help; ++NoDisplay=true +diff --git a/tools/yelp-build-url-handler.in b/tools/yelp-build-url-handler.in +new file mode 100755 +index 0000000..8742d67 +--- /dev/null ++++ b/tools/yelp-build-url-handler.in +@@ -0,0 +1,212 @@ ++#!/usr/bin/python3 ++# ++# yelp-build-url-handler ++# Copyright (C) 2023, 2024 Red Hat Inc. ++# ++# This program is free software; you can redistribute it and/or modify ++# it under the terms of the GNU General Public License as published by ++# the Free Software Foundation; either version 2 of the License, or ++# (at your option) any later version. ++# ++# This program is distributed in the hope that it will be useful, but ++# WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++# General Public License for more details. ++# ++# You should have received a copy of the GNU General Public License ++# along with this program; if not, write to the Free Software ++# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++ ++import os ++import sys ++import subprocess ++import urllib.parse ++import gi ++import errno ++import dbus ++from dbus import Interface, SessionBus ++ ++gi.require_version('Gio', '2.0') ++from gi.repository import Gio, GLib ++ ++class YelpBuildUrlHandler: ++ def main(self): ++ if len(sys.argv) != 2: ++ print('Usage: yelp-build-url-handler ', file=sys.stderr) ++ return 1 ++ url = sys.argv[1] ++ return self.handle_url(url) ++ ++ def determine_paths_from_url(self, url): ++ parsed_url = urllib.parse.urlparse(url) ++ if parsed_url.scheme != 'help': ++ print(f'Invalid URI scheme: {parsed_url.scheme}', file=sys.stderr) ++ return None ++ ++ path = parsed_url.path.lstrip('/') ++ languages = GLib.get_language_names() + ['C'] ++ ++ for language in languages: ++ base_path = os.path.join('@HELPDIR@', language) ++ cache_path = os.path.join(GLib.get_user_cache_dir(), 'yelp-build', language) ++ full_source_path = os.path.join(base_path, path) ++ ++ if os.path.isdir(full_source_path): ++ source_dir = full_source_path ++ output_dir = os.path.join(cache_path, path) ++ output_file = os.path.join(output_dir, 'index.html') ++ return { ++ 'source_dir': source_dir, ++ 'output_dir': output_dir, ++ 'output_file': output_file, ++ 'full_source_path': full_source_path ++ } ++ else: ++ full_source_file = full_source_path + '.page' ++ if os.path.isfile(full_source_file): ++ source_dir = os.path.dirname(full_source_file) ++ output_file = os.path.join(cache_path, path + '.html') ++ output_dir = os.path.dirname(output_file) ++ return { ++ 'source_dir': source_dir, ++ 'output_dir': output_dir, ++ 'output_file': output_file, ++ 'full_source_path': full_source_file ++ } ++ ++ print(f'Help content not found for path: {path}', file=sys.stderr) ++ return None ++ ++ def handle_url(self, url): ++ paths = self.determine_paths_from_url(url) ++ ++ if paths is None: ++ return 1 ++ ++ source_dir = paths['source_dir'] ++ output_dir = paths['output_dir'] ++ output_file = paths['output_file'] ++ full_source_path = paths['full_source_path'] ++ ++ cache_stale = True ++ try: ++ output_status = os.stat(output_dir) ++ source_status = os.stat(source_dir) ++ if output_status.st_mtime < source_status.st_mtime: ++ cache_stale = False ++ except OSError as e: ++ pass ++ ++ if cache_stale: ++ cache_stale = not self.generate_html(source_dir, output_dir) ++ ++ if cache_stale: ++ print(f'Failed to generate HTML for path {full_source_path}', file=sys.stderr) ++ return 1 ++ ++ sandboxed_dir = self.grant_access_to_browser(output_dir) ++ if sandboxed_dir is None: ++ print(f'Failed to grant browser access to generated HTML for path {full_source_path}', file=sys.stderr) ++ return 1 ++ ++ basename = os.path.basename(output_file) ++ sandboxed_file = os.path.join(sandboxed_dir, basename) ++ redirect_file = self.write_redirect_file(sandboxed_dir, sandboxed_file) ++ if redirect_file is None: ++ return 1 ++ ++ if not self.show_generated_html(redirect_file): ++ print('Failed to start browser', file=sys.stderr) ++ return 1 ++ ++ def generate_html(self, source_dir, output_dir): ++ os.makedirs(output_dir, exist_ok=True) ++ argv = ['yelp-build', 'html', '.', '-o', output_dir] ++ try: ++ subprocess.run(argv, cwd=source_dir, check=True) ++ return True ++ except subprocess.CalledProcessError as e: ++ print(f'Could not run "{" ".join(argv)}": {e}', file=sys.stderr) ++ return False ++ ++ def grant_access_to_browser(self, output_dir): ++ default_browser = Gio.AppInfo.get_default_for_type('text/html', False) ++ if default_browser is None: ++ print('Default web browser not found!', file=sys.stderr) ++ return None ++ ++ app_id = default_browser.get_id() ++ if app_id is None: ++ print('Default web browser has no app ID!', file=sys.stderr) ++ return None ++ ++ app_id = app_id.removesuffix('.desktop') ++ ++ try: ++ dir_fd = os.open(output_dir, os.O_PATH) ++ except OSError as e: ++ print(f'Failed to open directory {output_dir}: {e}', file=sys.stderr) ++ return None ++ ++ bus = SessionBus() ++ try: ++ portal_documents_object = bus.get_object('org.freedesktop.portal.Documents', '/org/freedesktop/portal/documents') ++ portal_documents = Interface(portal_documents_object, dbus_interface='org.freedesktop.portal.Documents') ++ except dbus.DBusException as e: ++ print(f'Failed to connect to Documents portal: {e}', file=sys.stderr) ++ os.close(dir_fd) ++ return None ++ ++ flags = ( ++ 1 << 0 # ReuseExisting ++ | 1 << 1 # Persistent ++ | 1 << 3 # ExportDirectory ++ ) ++ permissions = ['read'] ++ ++ try: ++ doc_with_signature, *_ = portal_documents.AddFull([dbus.types.UnixFd(dir_fd)], flags, app_id, permissions) ++ if not doc_with_signature: ++ print('No document IDs returned from portal', file=sys.stderr) ++ os.close(dir_fd) ++ return None ++ ++ doc_id, *_ = doc_with_signature ++ sandboxed_dir = os.path.join(GLib.get_user_runtime_dir(), 'doc', doc_id, os.path.basename(output_dir)) ++ os.close(dir_fd) ++ except dbus.DBusException as e: ++ print(f'Failed to call AddFull method: {e}', file=sys.stderr) ++ os.close(dir_fd) ++ return None ++ ++ return sandboxed_dir ++ ++ def show_generated_html(self, output): ++ try: ++ uri = GLib.filename_to_uri(output) ++ Gio.AppInfo.launch_default_for_uri(uri, None) ++ except Exception as e: ++ print(f'Failed to launch default application: {e}', file=sys.stderr) ++ return False ++ ++ return True ++ ++ def write_redirect_file(self, sandboxed_dir, sandboxed_file): ++ html = f'' ++ redirect_file = os.path.join(sandboxed_dir, 'yelp-build-redirect.html') ++ ++ try: ++ with open(redirect_file, 'w') as f: ++ f.write(html) ++ except Exception as e: ++ print(f'Error writing redirect file {redirect_file}: {e}', file=sys.stderr) ++ return None ++ ++ return redirect_file ++ ++if __name__ == '__main__': ++ try: ++ sys.exit(YelpBuildUrlHandler().main()) ++ except KeyboardInterrupt: ++ sys.exit(1) ++ +-- +GitLab + diff --git a/yelp-tools.spec b/yelp-tools.spec index 024505c..0641c4e 100644 --- a/yelp-tools.spec +++ b/yelp-tools.spec @@ -1,8 +1,10 @@ %global tarball_version %%(echo %{version} | tr '~' '.') +%bcond url_handler 0%{?rhel} + Name: yelp-tools Version: 42.1 -Release: 7%{?dist} +Release: 8%{?dist} Summary: Create, manage, and publish documentation for Yelp License: GPL-2.0-or-later @@ -10,6 +12,9 @@ URL: https://wiki.gnome.org/Apps/Yelp/Tools Source0: https://download.gnome.org/sources/%{name}/42/%{name}-%{tarball_version}.tar.xz BuildArch: noarch +# https://gitlab.gnome.org/GNOME/yelp-tools/-/merge_requests/12 +Patch: url-handler.patch + BuildRequires: meson BuildRequires: pkgconfig(yelp-xsl) BuildRequires: python3-lxml @@ -33,7 +38,11 @@ wraps things up in a developer-friendly way. %autosetup -p1 -n %{name}-%{tarball_version} %build -%meson +%meson \ +%if %{with url_handler} + -Durl_handler=enabled \ +%endif + %{nil} %meson_build %install @@ -43,12 +52,22 @@ wraps things up in a developer-friendly way. %doc AUTHORS README.md NEWS %license COPYING COPYING.GPL %{_bindir}/yelp-build +%if %{with url_handler} +%{_bindir}/yelp-build-url-handler +%endif %{_bindir}/yelp-check %{_bindir}/yelp-new +%if %{with url_handler} +%{_datadir}/applications/yelp-build-url-handler.desktop +%endif %{_datadir}/yelp-tools %{_datadir}/aclocal/yelp.m4 %changelog +* Mon Jan 27 2025 Michael Catanzaro - 42.1-8 +- Add URL handler to open help files in web browser + Resolves: RHEL-76398 + * Tue Oct 29 2024 Troy Dawson - 42.1-7 - Bump release for October 2024 mass rebuild: Resolves: RHEL-64018