summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/linux.yml2
-rw-r--r--.github/workflows/macos.yml2
-rw-r--r--doc/cool-uris.yaml63
-rw-r--r--meson.build23
-rwxr-xr-xscripts/ensure-stable-doc-urls.py232
5 files changed, 318 insertions, 4 deletions
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 5ba828f..aac3081 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -26,7 +26,7 @@ jobs:
python-version: '3.9'
- name: Install dependencies
run: |
- python -m pip install --upgrade meson
+ python -m pip install --upgrade meson PyYAML
sudo apt update
sudo apt install -y \
doxygen libxcb-xkb-dev valgrind ninja-build \
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index c57a0d7..cde0989 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -23,7 +23,7 @@ jobs:
python-version: '3.9'
- name: Install dependencies
run: |
- python -m pip install --upgrade meson
+ python -m pip install --upgrade meson PyYAML
brew install libxml2 doxygen bison ninja
brew link bison --force
env:
diff --git a/doc/cool-uris.yaml b/doc/cool-uris.yaml
new file mode 100644
index 0000000..b739ee5
--- /dev/null
+++ b/doc/cool-uris.yaml
@@ -0,0 +1,63 @@
+# WARNING: This file is autogenerated by: scripts/ensure-stable-doc-urls.py
+# Do not edit manually.
+annotated.html: []
+classes.html: []
+deprecated.html: []
+dir_63ce773eee1f9b680e6e312b48cc99ca.html: []
+dir_891596f32582d3133e8915e72908625f.html: []
+dir_d44c64559bbebec7f509842c48db8b23.html: []
+dir_e68e8157741866f444e17edd764ebbae.html: []
+files.html: []
+functions.html: []
+functions_func.html: []
+functions_type.html: []
+functions_vars.html: []
+globals.html: []
+globals_defs.html: []
+globals_enum.html: []
+globals_eval.html: []
+globals_func.html: []
+globals_type.html: []
+graph_legend.html: []
+group__components.html: []
+group__compose.html: []
+group__context.html: []
+group__include-path.html: []
+group__keymap.html: []
+group__keysyms.html: []
+group__logging.html: []
+group__registry.html: []
+group__state.html: []
+group__x11.html: []
+index.html: []
+keymap-text-format-v1.html:
+- md_doc_keymap_format_text_v1.html
+md_doc_quick_guide.html: []
+md_doc_user_configuration.html: []
+modules.html: []
+pages.html: []
+rule-file-format.html:
+- md_doc_rules_format.html
+structrxkb__context.html: []
+structrxkb__iso3166__code.html: []
+structrxkb__iso639__code.html: []
+structrxkb__layout.html: []
+structrxkb__model.html: []
+structrxkb__option.html: []
+structrxkb__option__group.html: []
+structxkb__compose__state.html: []
+structxkb__compose__table.html: []
+structxkb__context.html: []
+structxkb__keymap.html: []
+structxkb__rule__names.html: []
+structxkb__state.html: []
+todo.html: []
+xkb-intro.html: []
+xkbcommon-compatibility.html:
+- md_doc_compat.html
+xkbcommon-compose_8h.html: []
+xkbcommon-keysyms_8h.html: []
+xkbcommon-names_8h.html: []
+xkbcommon-x11_8h.html: []
+xkbcommon_8h.html: []
+xkbregistry_8h.html: []
diff --git a/meson.build b/meson.build
index b64427d..2cd1ee7 100644
--- a/meson.build
+++ b/meson.build
@@ -819,15 +819,34 @@ You can disable the documentation with -Denable-docs=false.''')
)
# TODO: Meson should provide this.
docdir = get_option('datadir')/'doc'/meson.project_name()
- custom_target(
+ doc_gen = custom_target(
'doc',
input: [doxyfile] + doxygen_input,
output: 'html',
- command: [doxygen_wrapper, doxygen, meson.current_build_dir()/'Doxyfile', meson.current_source_dir()],
+ command: [
+ doxygen_wrapper,
+ doxygen,
+ meson.current_build_dir()/'Doxyfile',
+ meson.current_source_dir(),
+ ],
install: true,
install_dir: docdir,
build_by_default: true,
)
+ ensure_stable_urls = find_program('scripts'/'ensure-stable-doc-urls.py')
+ custom_target(
+ 'doc-cool-uris',
+ input: [doc_gen, 'doc'/'cool-uris.yaml'],
+ output: 'html-xtra',
+ command: [
+ ensure_stable_urls,
+ 'generate-redirections',
+ meson.current_source_dir()/'doc'/'cool-uris.yaml',
+ meson.current_build_dir()/'html'
+ ],
+ install: false,
+ build_by_default: true,
+ )
endif
configure_file(output: 'config.h', configuration: configh_data)
diff --git a/scripts/ensure-stable-doc-urls.py b/scripts/ensure-stable-doc-urls.py
new file mode 100755
index 0000000..27f8232
--- /dev/null
+++ b/scripts/ensure-stable-doc-urls.py
@@ -0,0 +1,232 @@
+#!/usr/bin/env python3
+
+# Doc URLs may change with time because they depend on Doxygen machinery.
+# This is unfortunate because it is good practice to keep valid URLs.
+# See: “Cool URIs don’t change” at https://www.w3.org/Provider/Style/URI.html.
+#
+# There is no built-in solution in Doxygen that we are aware of.
+# The solution proposed here is to maintain a registry of all URLs and manage
+# legacy URLs as redirections to their canonical page.
+
+import argparse
+from enum import IntFlag
+import glob
+from itertools import chain
+from pathlib import Path
+from string import Template
+from typing import NamedTuple, Sequence
+
+import yaml
+
+
+class Update(NamedTuple):
+ new: str
+ old: str
+
+
+class ExitCode(IntFlag):
+ NORMAL = 0
+ INVALID_UPDATES = 1 << 4
+ MISSING_UPDATES = 1 << 5
+
+
+THIS_SCRIPT_PATH = Path(__file__)
+RELATIVE_SCRIPT_PATH = THIS_SCRIPT_PATH.relative_to(THIS_SCRIPT_PATH.parent.parent)
+
+REDIRECTION_DELAY = 6 # in seconds. Note: at least 6s for accessibility
+
+# NOTE: The redirection works with the HTML tag: <meta http-equiv="refresh">.
+# See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#http-equiv
+#
+# NOTE: This page is a simplified version of the Doxygen-generated ones.
+# It does use the current stylesheets, but it may break if the theme is updated.
+# Ideally, we would just let Doxygen generate them, but I (Wismill) could not
+# find a way to do this with the redirection feature.
+REDIRECTION_PAGE_TEMPLATE = Template(
+ """<!DOCTYPE HTML>
+<html lang="en-US">
+ <head>
+ <meta charset="UTF-8">
+ <meta http-equiv="refresh" content="${delay}; url=${canonical}">
+ <link href="doxygen.css" rel="stylesheet" type="text/css">
+ <link href="doxygen-extra.css" rel="stylesheet" type="text/css">
+ <title>xkbcommon: Page Redirection</title>
+ </head>
+ <body>
+ <div id="top">
+ <div id="titlearea" style="padding: 1em 0 1em 0.5em;">
+ <div id="projectname">
+ libxkbcommon
+ </div>
+ </div>
+ </div>
+ <div>
+ <div class="header">
+ <div class="headertitle">
+ <div class="title">🔀 Redirection</div>
+ </div>
+ </div>
+ <div class="contents">
+ <p>This page has been moved.</p>
+ <p>
+ If you are not redirected automatically,
+ follow the <a href="${canonical}">link to the current page</a>.
+ </p>
+ </div>
+ </div>
+ </body>
+</html>
+"""
+)
+
+
+def parse_page_update(update: str) -> Update:
+ updateʹ = Update(*update.split("="))
+ if updateʹ.new == updateʹ.old:
+ raise ValueError(f"Invalid update: {updateʹ}")
+ return updateʹ
+
+
+def update_registry(registry_path: Path, doc_dir: Path, updates: Sequence[str]):
+ """
+ Update the URL registry by:
+ • Adding new pages
+ • Updating page aliases
+ """
+ # Parse updates
+ updates_ = dict(map(parse_page_update, updates))
+ # Load previous registry
+ with registry_path.open("rt", encoding="utf-8") as fd:
+ registry = yaml.safe_load(fd) or {}
+ # Expected updates
+ missing_updates = set(file for file in registry if not (doc_dir / file).is_file())
+ # Update
+ invalid_updates = set(updates_)
+ redirections = frozenset(chain(*registry.values()))
+ for file in glob.iglob("**/*.html", root_dir=doc_dir, recursive=True):
+ # Skip redirection pages
+ if file in redirections:
+ continue
+ # Get previous entry and potential update
+ old = updates_.get(file)
+ if old:
+ # Update old entry
+ invalid_updates.remove(file)
+ entry = registry.get(old)
+ if entry is None:
+ raise ValueError(f"Invalid update: {file}<-{old}")
+ else:
+ del registry[old]
+ missing_updates.remove(old)
+ registry[file] = [e for e in [old] + entry if e != file]
+ print(f"[INFO] Updated: “{old}” to “{file}”")
+ else:
+ entry = registry.get(file)
+ if entry is None:
+ # New entry
+ registry[file] = []
+ print(f"[INFO] Added: {file}")
+ else:
+ # Keep previous entry
+ pass
+ exit_code = ExitCode.NORMAL
+ # Check
+ if invalid_updates:
+ for update in invalid_updates:
+ print(f"[ERROR] Update not processed: {update}")
+ exit_code |= ExitCode.INVALID_UPDATES
+ if missing_updates:
+ for old in missing_updates:
+ print(f"[ERROR] “{old}” not found and has no update.")
+ exit_code |= ExitCode.MISSING_UPDATES
+ if exit_code:
+ print(f"[ERROR] Processing interrupted: please fix the errors above.")
+ exit(exit_code.value)
+ # Write changes
+ with registry_path.open("wt", encoding="utf-8") as fd:
+ fd.write(f"# WARNING: This file is autogenerated by: {RELATIVE_SCRIPT_PATH}\n")
+ fd.write(f"# Do not edit manually.\n")
+ yaml.dump(
+ registry,
+ fd,
+ )
+
+
+def generate_redirections(registry_path: Path, doc_dir: Path):
+ """
+ Create redirection pages using the aliases in the given URL registry.
+ """
+ cool = True
+ # Load registry
+ with registry_path.open("rt", encoding="utf-8") as fd:
+ registry = yaml.safe_load(fd) or {}
+ for canonical, aliases in registry.items():
+ # Check canonical path is up-to-date
+ if not (doc_dir / canonical).is_file():
+ cool = False
+ print(
+ f"ERROR: missing canonical documentation page “{canonical}”. "
+ f"Please update “{registry_path}” using b{RELATIVE_SCRIPT_PATH}”."
+ )
+ # Add a redirection page
+ for alias in aliases:
+ path = doc_dir / alias
+ with path.open("wt", encoding="utf-8") as fd:
+ fd.write(
+ REDIRECTION_PAGE_TEMPLATE.substitute(
+ canonical=canonical, delay=REDIRECTION_DELAY
+ )
+ )
+ if not cool:
+ exit(1)
+
+
+def add_registry_argument(parser):
+ parser.add_argument(
+ "registry",
+ type=Path,
+ help="Path to the doc URI registry.",
+ )
+
+
+def add_docdir_argument(parser):
+ parser.add_argument(
+ "docdir",
+ type=Path,
+ metavar="DOC_DIR",
+ help="Path to the generated HTML documentation directory.",
+ )
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(
+ description="Tool to ensure HTML documentation has stable URLs"
+ )
+ subparsers = parser.add_subparsers()
+
+ parser_registry = subparsers.add_parser(
+ "update-registry", help="Update the registry of URIs"
+ )
+ add_registry_argument(parser_registry)
+ add_docdir_argument(parser_registry)
+ parser_registry.add_argument(
+ "updates",
+ nargs="*",
+ type=str,
+ help="Update: new=previous entries",
+ )
+ parser_registry.set_defaults(
+ run=lambda args: update_registry(args.registry, args.docdir, args.updates)
+ )
+
+ parser_redirections = subparsers.add_parser(
+ "generate-redirections", help="Generate URIs redirections"
+ )
+ add_registry_argument(parser_redirections)
+ add_docdir_argument(parser_redirections)
+ parser_redirections.set_defaults(
+ run=lambda args: generate_redirections(args.registry, args.docdir)
+ )
+
+ args = parser.parse_args()
+ args.run(args)