Add URL handler to open help files in web browser

Resolves: RHEL-76398
This commit is contained in:
Michael Catanzaro 2025-01-30 13:21:23 -06:00
parent 436d41f0ca
commit 012f30c3d0
2 changed files with 325 additions and 2 deletions

304
url-handler.patch Normal file
View File

@ -0,0 +1,304 @@
From 66d2d8d8239f781f20f913dbe388b52e14f8dffb Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
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 <URL>', 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'<meta http-equiv="refresh" content="0; url=file://{sandboxed_file}">'
+ 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

View File

@ -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 <mcatanzaro@redhat.com> - 42.1-8
- Add URL handler to open help files in web browser
Resolves: RHEL-76398
* Tue Oct 29 2024 Troy Dawson <tdawson@redhat.com> - 42.1-7
- Bump release for October 2024 mass rebuild:
Resolves: RHEL-64018