Merge remote-tracking branch 'igor-ramazanov/contrib/gendocs.sh'

This commit is contained in:
Maxime Coste 2025-02-28 23:31:12 +11:00
commit 11f6319830
2 changed files with 336 additions and 21 deletions

315
contrib/gendocs.py Executable file
View file

@ -0,0 +1,315 @@
#!/usr/bin/env python3
# This script generates a static documentation web site
# by parsing the `**/*.asiidoc` files from the repository.
#
# Dependencies:
# * Python 3
# * `xdg-open` for opening the final result in a web browser.
# * `antora` - a static documentation web site generator,
# https://docs.antora.org/antora/latest
#
# Usage:
# ```console
# $ ./contrib/gendocs.py
# ```
#
# After running it should open the generated web site in a browser.
#
from dataclasses import dataclass
from typing import Optional
import glob
import itertools
import os
import pathlib
import re
import shutil
import subprocess
# Get the script directory.
script_dir = os.path.dirname(os.path.realpath(__file__))
# Switch to the projects root dir.
os.chdir(os.path.join(script_dir, ".."))
# Recreating the final output dir to start from scratch.
shutil.rmtree("doc_gen", ignore_errors=True)
os.makedirs("doc_gen", exist_ok=True)
# Antora fails if the repo contains broken symbolic links.
# shutil.rmtree("libexec", ignore_errors=True)
# Canonical Antora paths.
# See: https://docs.antora.org/antora/latest/standard-directories.
# https://docs.antora.org/antora/latest/root-module-directory.
os.makedirs("doc_gen/modules/ROOT/images", exist_ok=True)
os.makedirs("doc_gen/modules/ROOT/pages", exist_ok=True)
# Put necessary images to the Antora canonical directory.
# See: https://docs.antora.org/antora/latest/images-directory.
for gif_file in glob.glob("doc/*.gif"):
shutil.copy(gif_file, "doc_gen/modules/ROOT/images/")
# Fix links according to the Antora specification.
# See: https://docs.antora.org/antora/latest/page/xref.
def fix_links(path):
@dataclass
class Link:
path: Optional[str]
file: Optional[str]
fragment: Optional[str]
title: str
def __init__(self, path, file, fragment, title):
self.path = path
self.file = file
self.fragment = fragment
self.title = title
def dropwhile(predicate, string):
return "".join(
itertools.dropwhile(
predicate,
string,
)
)
def dropwhileright(predicate, string):
return "".join(
reversed(
list(
itertools.dropwhile(
predicate,
reversed(string),
)
)
)
)
def takewhile(predicate, string):
return "".join(
itertools.takewhile(
predicate,
string,
)
)
def untag(string):
no_opening = dropwhile(lambda c: c == "<", string)
no_closing = dropwhileright(lambda c: c == ">", no_opening)
return no_closing
def parse(string):
untagged = untag(string)
prefix, title = untagged.split(",", 1)
title = title.strip()
fragment = dropwhile(lambda c: c != "#", prefix)
fragment = fragment if fragment else None
fragmentless = takewhile(lambda c: c != "#", prefix)
segments = fragmentless.split("/")
path, file = (
("/".join(segments[:-1]), segments[-1])
if "/" in fragmentless
else (None, fragmentless)
)
return Link(path, file, fragment, title)
def render(link):
if link.path and link.file and link.fragment == "#":
return f"xref:{link.path}/{link.file}.adoc[{link.title}]"
elif link.path and link.file and link.fragment:
return f"xref:{link.path}/{link.file}.adoc{link.fragment}[{link.title}]"
elif not link.path and link.file and link.fragment == "#":
return f"xref:./{link.file}.adoc[{link.title}]"
elif not link.path and link.file and link.fragment:
return f"xref:./{link.file}.adoc{link.fragment}[{link.title}]"
elif not link.path and link.file and not link.fragment:
return f"<<{link.file},{link.title}>>"
else:
raise RuntimeError(f"Failed to render link: {link}")
def process(m):
string = m.group(0)
return render(parse(string)) if "," in string else string
content = None
with open(path, "r") as file:
content = file.read()
# Fix image links according the Antora specification.
# See: https://docs.antora.org/antora/latest/page/image-resource-id-examples.
content = content.replace("image::doc/", "image::")
with open(path, "w") as file:
file.write(re.sub(r"<<[^>]+>>", process, content))
# A useful documentation page.
# Add the `.adoc` extension to include it into the result.
shutil.copy(
"VIMTOKAK",
"doc_gen/modules/ROOT/pages/VIMTOKAK.adoc",
)
fix_links("doc_gen/modules/ROOT/pages/VIMTOKAK.adoc")
for source in glob.glob("**/*.asciidoc", recursive=True):
# Create directories structure matching the project's original structure.
# See: https://docs.antora.org/antora/latest/pages-directory.
page_dir = os.path.join(
"doc_gen/modules/ROOT/pages",
os.path.dirname(source),
)
os.makedirs(page_dir, exist_ok=True)
# Copy the `asciidoc` file into the Antora `pages` directory
# with the mandatory `.adoc` filename extension.
adoc = os.path.join(page_dir, pathlib.Path(source).stem + ".adoc")
shutil.copy(source, adoc)
if source == "README.asciidoc":
# Update the filename so it reflects the content.
# The filename is used for navigation links.
shutil.move(
"doc_gen/modules/ROOT/pages/README.adoc",
"doc_gen/modules/ROOT/pages/index.adoc",
)
adoc = "doc_gen/modules/ROOT/pages/index.adoc"
elif source == "test/README.asciidoc":
# The file name is used for navigation links.
# Update so it reflects the content.
shutil.move(
"doc_gen/modules/ROOT/pages/test/README.adoc",
"doc_gen/modules/ROOT/pages/test/tests.adoc",
)
adoc = "doc_gen/modules/ROOT/pages/test/tests.adoc"
fix_links(adoc)
# A navigation file for the sidebar.
# See: https://docs.antora.org/antora/latest/navigation/single-list.
#
# TODO: Generate automatically.
nav_content = """
* xref:index.adoc[Getting Started]
* xref:doc/pages/commands.adoc[Commands]
* xref:doc/pages/expansions.adoc[Expansions]
* xref:doc/pages/execeval.adoc[Exec Eval]
* xref:doc/pages/scopes.adoc[Scopes]
* xref:doc/pages/faces.adoc[Faces]
* xref:doc/pages/buffers.adoc[Buffers]
* xref:doc/pages/registers.adoc[Registers]
* xref:doc/pages/mapping.adoc[Mapping]
* xref:doc/pages/hooks.adoc[Hooks]
* xref:doc/pages/command-parsing.adoc[Command Parsing]
* xref:doc/pages/keys.adoc[Keys]
* xref:doc/pages/regex.adoc[Regex]
* xref:doc/pages/options.adoc[Options]
* xref:doc/pages/highlighters.adoc[Highlighters]
* xref:doc/pages/modes.adoc[Modes]
* xref:doc/pages/keymap.adoc[KEYMAP]
* xref:doc/pages/faq.adoc[FAQ]
* xref:doc/pages/changelog.adoc[Changelog]
* xref:doc/design.adoc[Design]
* xref:doc/coding-style.adoc[Coding Style]
* xref:doc/writing_scripts.adoc[Writing Scripts]
* xref:doc/json_ui.adoc[JSON UI]
* xref:doc/autoedit.adoc[Autoedit]
* xref:doc/interfacing.adoc[Interfacing]
* xref:rc/tools/lint.adoc[Linting]
* xref:rc/tools/autorestore.adoc[Autorestore]
* xref:rc/tools/doc.adoc[Doc]
* xref:test/tests.adoc[Tests]
* xref:VIMTOKAK.adoc[Vi(m) to Kakoune]
"""
with open("doc_gen/modules/ROOT/nav.adoc", "w") as f:
f.write(nav_content)
# Antora component description file.
# See: https://docs.antora.org/antora/latest/component-version-descriptor.
antora_yml_content = """
name: Kakoune
nav:
- modules/ROOT/nav.adoc
title: Kakoune
version: latest
"""
with open("doc_gen/antora.yml", "w") as f:
f.write(antora_yml_content)
# Antora playbook file.
# See: https://docs.antora.org/antora/latest/playbook.
antora_playbook_content = """
asciidoc:
attributes:
# Do not complain on missing attributes,
# TODO: fix and turn to a fatal warning
attribute-missing: skip
# To fix links
idprefix: ""
# To fix links
idseparator: "-"
# Better to be reproducible, in general
reproducible: true
# More convenient to turn sections to IDs
sectids: true
# More convenient to have sections as links
sectlinks: true
# Do not want to miss something
sectnumlevels: 5
# More convenient to number the sections
sectnums: true
# Do not want to miss something
toclevels: 5
sourcemap: true
content:
sources:
- url: ./..
start_path: doc_gen
branches: ["HEAD"]
runtime:
cache_dir: ./doc_gen/cache # More convenient for the development
fetch: true # More convenient for the development
log:
failure_level: fatal
level: warn
output:
clean: true # More convenient for the development
dir: ./build # Simpler to have it explicit in code
site:
title: Kakoune Docs
ui:
bundle:
url: https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/HEAD/raw/build/ui-bundle.zip?job=bundle-stable
"""
with open("doc_gen/antora-playbook.yml", "w") as f:
f.write(antora_playbook_content)
# Finally, generate the documentation,
# results will be saved to the output directory
# as specified in the `antora-playbook.yml`.
subprocess.run(["antora", "generate", "doc_gen/antora-playbook.yml"])
subprocess.run(["xdg-open", "./doc_gen/build/Kakoune/latest/index.html"])

View file

@ -1,27 +1,27 @@
= Keymap
---
┌───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┲━━━━━━━━━━━━━━┓
│ upper│ cmdout│convtab│ │selpipe│sel all│ │ align│pattern│ rotate│ rotate│ trim│ ┃ ⇤ ┃
├┄┄CASE┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┨ ┃
│ lower│ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 0 │ │ ┃ ┃
┢━━━━━━━┷━━━┱───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┺━━━┳━━━━━━━━━━┫
┃ ↹ ┃ record│ ᵐʷ│ ᵐʷ│ paste│ ᵐʳ│ │ redo │ INSERT│ above│ before│ ᵐ│ ᵐ┃ ┃
┃ ┠┄MACRO┄┤ next│ word├REPLACE┤to char├┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄OPEN┄┄┼┄PASTE┄┤ object│ object┃ ⏎ ┃
┃ ┃ replay│ word│ end│ char│ │ yank │ undo │ insert│ below│ after│ begin│ end┃ ┃
┣━━━━━━━━━━━┻━┱─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┺━┓ ┃
┃ ⇬ ┃ APPEND│ split│ │ ᵐʳ│ ᵐᵍ│ ᵐˡ│ ᵐ│ ᵐ│ ᵐˡ│cmdline│use reg│ pipe┃ ┃
┃ ┠┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┤ find│ goto │ │ │ │ ├┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┨ ┃
┃ ┃ append│ select│ delete│ char│ │ ← │ ↓ │ ↑ │ → │ cursor│ │eschook┃ ┃
┣━━━━━━━━━┳━━━┹───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┲━━━┷━━━━━━━┻━━━━━━━━┫
┃ ┃ indent│ save│ ᵐ│copysel│ ᵛ│ ᵐʷ│ ᵐʳ│ ᵐʳ│ dedent│ indent│ ᵐʳ┃ ┃
┃ ┠┄┄┄┄┄┄┄┼┄MARKS┄┤ select├┄┄┄┄┄┄┄┤ view│ prev│ search│ match├┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┤ search┃ ┃
┃ ⇧ ┃ dedent│restore│ line│ change│ cmds│ word│ next│ char│clrsels│ repeat│ ┃ ⇧ ┃
┣━━━━━━━━━┻┳━━━━━━┷━━━┳━━━┷━━━━━┱─┴───────┴───────┴───────┴───────┴───────┴─┲━━━━━┷━━━━┳━━┷━━━━━━━╋━━━━━━━━━┳━━━━━━━━━━┫
┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃
┃ ┃ ┃ ┃ user mappings ┃ ┃ ┃ ┃ ┃
┃Ctrl ┃ ┃Alt ┃ ┃AltGr ┃ ┃ ┃Ctrl ┃
┗━━━━━━━━━━┻━━━━━━━━━━┻━━━━━━━━━┹───────────────────────────────────────────┺━━━━━━━━━━┻━━━━━━━━━━┻━━━━━━━━━┻━━━━━━━━━━┛
┌───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┲━━━━━━━━━━━━━━┓
│ upper│ cmdout│convtab│ │selpipe│sel all│ │ align│pattern│ rotate│ rotate│ trim│ ┃ ⇤ ┃
├┄┄CASE┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┨ ┃
│ lower│ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 0 │ │ ┃ ┃
┢━━━━━━━┷━━━┱───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┺━━━┳━━━━━━━━━━┫
┃ ↹ ┃ record│ ᵐʷ│ ᵐʷ│ paste│ ᵐʳ│ │ redo │ INSERT│ above│ before│ ᵐ│ ᵐ┃ ┃
┃ ┠┄MACRO┄┤ next│ word├REPLACE┤to char├┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄OPEN┄┄┼┄PASTE┄┤ object│ object┃ ⏎ ┃
┃ ┃ replay│ word│ end│ char│ │ yank │ undo │ insert│ below│ after│ begin│ end┃ ┃
┣━━━━━━━━━━━┻━┱─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┺━┓ ┃
┃ ⇬ ┃ APPEND│ split│ │ ᵐʳ│ ᵐᵍ│ ᵐˡ│ ᵐ│ ᵐ│ ᵐˡ│cmdline│use reg│ pipe┃ ┃
┃ ┠┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┤ find│ goto │ │ │ │ ├┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┨ ┃
┃ ┃ append│ select│ delete│ char│ │ ← │ ↓ │ ↑ │ → │ cursor│ │eschook┃ ┃
┣━━━━━━━━━┳━━━┹───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┲━━━┷━━━━━━━┻━━━━━━━━┫
┃ ┃ indent│ save│ ᵐ│copysel│ ᵛ│ ᵐʷ│ ᵐʳ│ ᵐʳ│ dedent│ indent│ ᵐʳ┃ ┃
┃ ┠┄┄┄┄┄┄┄┼┄MARKS┄┤ select├┄┄┄┄┄┄┄┤ view│ prev│ search│ match├┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┤ search┃ ┃
┃ ⇧ ┃ dedent│restore│ line│ change│ cmds│ word│ next│ char│clrsels│ repeat│ ┃ ⇧ ┃
┣━━━━━━━━━┻┳━━━━━━┷━━━┳━━━┷━━━━━┱─┴───────┴───────┴───────┴───────┴───────┴─┲━━━━━┷━━━━┳━━┷━━━━━━━╋━━━━━━━━━┳━━━━━━━━━━┫
┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃
┃ ┃ ┃ ┃ user mappings ┃ ┃ ┃ ┃ ┃
┃Ctrl ┃ ┃Alt ┃ ┃AltGr ┃ ┃ ┃Ctrl ┃
┗━━━━━━━━━━┻━━━━━━━━━━┻━━━━━━━━━┹───────────────────────────────────────────┺━━━━━━━━━━┻━━━━━━━━━━┻━━━━━━━━━┻━━━━━━━━━━┛
ʳ: alt reverses direction
ʷ: alt uses WORD instead of word (that is non blank instead of [a-z_])