From 82d3689143052c7d1f319dc6e8209be55c5b26f1 Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Thu, 10 Apr 2025 17:21:01 -0400 Subject: [PATCH 1/3] docs: Replace grass-stable URLs by relative URLs The core and addons doc mostly use absolute paths to refer to each other. However, when the docs are build together, the links may point to a wrong version. This is now even a bigger issue with Sphinx-based Python doc which is more linked from the main doc than it was before. Having the absolute URLs pointing to grass-stable is still a good solution. It is straigforward. It works locally and when each piece is build separately. The new script and the build step in CI is done after the two documentations are put together and with anticipation that libpython will be build into it. The replacement is done on Markdown level, so MkDocs will complain that the libpython links are broken, but that does not break the build and hopefully having both MkDocs and Sphinx docs is something to change in the future. No replacements are done for links from libpython, but there are currently none. The only links which are there are relative already. --- .github/workflows/documentation.yml | 9 ++ utils/replace_links.py | 124 ++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100755 utils/replace_links.py diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index c10bdd40491..2c88cc52ef9 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -135,6 +135,15 @@ jobs: cp -rv $name addons done + - name: Make links within doc relative + run: | + python grass/utils/replace_links.py \ + --target-dirs "" addons \ + --new-base-url "" \ + --verbose \ + https://grass.osgeo.org/grass-stable/manuals + "$MKDOCS_DIR/source/" + - name: Get mkdocs run: | pip install -r "grass/man/mkdocs/requirements.txt" diff --git a/utils/replace_links.py b/utils/replace_links.py new file mode 100755 index 00000000000..11d62167a3c --- /dev/null +++ b/utils/replace_links.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 + +import os +import re +import argparse +import difflib +from pathlib import Path + + +def replace_links_in_markdown( + filepath, replace_url, replacement, dry_run=False, verbose=False, diff=False +): + content = Path(filepath).read_text() + + # Match Markdown links/images with multiline labels. + pattern = re.compile( + r"(!?\[.*?\])\(\s*" + re.escape(replace_url) + r"([^)]*)?\s*\)", re.DOTALL + ) + + if pattern.search(content): + new_content = pattern.sub( + lambda m: f"{m.group(1)}({replacement}{m.group(2) or ''})", content + ) + if not dry_run: + Path(filepath).write_text(new_content) + if (verbose or dry_run) and not diff: + text = "Would update" if dry_run else "Updated" + print(f"{text}: {filepath}") + if diff: + difference = difflib.unified_diff( + content.splitlines(), + new_content.splitlines(), + fromfile=f"a/{filepath}", + tofile=f"b/{filepath}", + lineterm="", + ) + print("\n".join(difference)) + + +def ensure_trailing_slash(url): + return url.rstrip("/") + "/" if url else "" + + +def replace_grass_links(args): + # Normalize URL format. + replace_url = ensure_trailing_slash(args.replace_url) + new_base_url = ensure_trailing_slash(args.new_base_url) + + for subdir in args.target_dirs: + dir_path = os.path.join(args.root_dir, subdir) + replacement = new_base_url if subdir == "" else f"{new_base_url}.." + + # This assumes that the directory exists. + for file_path in Path.iterdir(dir_path): + if os.path.isfile(file_path) and file_path.suffix == args.file_ext: + replace_links_in_markdown( + file_path, + replace_url, + replacement, + dry_run=args.dry_run, + verbose=args.verbose, + ) + + +def main(): + parser = argparse.ArgumentParser( + description="Replace GRASS manual URLs in Markdown links with relative or custom paths.", + epilog=""" +Example: + python replace_grass_links.py \\ + "https://example.org/manuals" \\ + ./docs \\ + --target-dirs "" addons libpython \\ + --new-base-url "" \\ + --file-ext .md \\ + --verbose +""", + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + "replace_url", + default="", + help="URL string to replace (default: %(default)s)", + ) + parser.add_argument( + "root_dir", + help="Path to the root directory containing Markdown files and subdirectories.", + ) + parser.add_argument( + "--new-base-url", + default="", + help="Replacement string for the base URL (default: empty for relative links)", + ) + parser.add_argument( + "--target-dirs", + nargs="+", + default=[""], + help="Subdirectories to process relative to root (default: %(default)s)", + ) + parser.add_argument( + "--file-ext", + default=".md", + help="File extension to process (default: %(default)s)", + ) + parser.add_argument( + "--dry-run", action="store_true", help="Preview changes without modifying files" + ) + parser.add_argument( + "--diff", + action="store_true", + help="Print difference between new and old content", + ) + parser.add_argument( + "--verbose", + action="store_true", + help="Print every file that gets (or would be) updated", + ) + + args = parser.parse_args() + replace_grass_links(args) + + +if __name__ == "__main__": + main() From 9d14072f92095cf245e0e984dc971ef08c6def2a Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Thu, 10 Apr 2025 17:24:25 -0400 Subject: [PATCH 2/3] docs: Replace grass-stable URLs by relative URLs The core and addons doc mostly use absolute paths to refer to each other. However, when the docs are build together, the links may point to a wrong version. This is now even a bigger issue with Sphinx-based Python doc which is more linked from the main doc than it was before. Having the absolute URLs pointing to grass-stable is still a good solution. It is straigforward. It works locally and when each piece is build separately. The new script and the build step in CI is done after the two documentations are put together and with anticipation that libpython will be build into it. The replacement is done on Markdown level, so MkDocs will complain that the libpython links are broken, but that does not break the build and hopefully having both MkDocs and Sphinx docs is something to change in the future. No replacements are done for links from libpython, but there are currently none. The only links which are there are relative already. --- utils/replace_links.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/utils/replace_links.py b/utils/replace_links.py index 11d62167a3c..0f6374c2521 100755 --- a/utils/replace_links.py +++ b/utils/replace_links.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -import os import re import argparse import difflib @@ -8,15 +7,13 @@ def replace_links_in_markdown( - filepath, replace_url, replacement, dry_run=False, verbose=False, diff=False + filepath, replace_url, replacement, dry_run, verbose, diff ): content = Path(filepath).read_text() - # Match Markdown links/images with multiline labels. pattern = re.compile( r"(!?\[.*?\])\(\s*" + re.escape(replace_url) + r"([^)]*)?\s*\)", re.DOTALL ) - if pattern.search(content): new_content = pattern.sub( lambda m: f"{m.group(1)}({replacement}{m.group(2) or ''})", content @@ -47,17 +44,17 @@ def replace_grass_links(args): new_base_url = ensure_trailing_slash(args.new_base_url) for subdir in args.target_dirs: - dir_path = os.path.join(args.root_dir, subdir) + dir_path = Path(args.root_dir) / subdir replacement = new_base_url if subdir == "" else f"{new_base_url}.." - # This assumes that the directory exists. for file_path in Path.iterdir(dir_path): - if os.path.isfile(file_path) and file_path.suffix == args.file_ext: + if file_path.is_file() and file_path.suffix == args.file_ext: replace_links_in_markdown( file_path, replace_url, replacement, dry_run=args.dry_run, + diff=args.diff, verbose=args.verbose, ) From a83773bf92e16b9f1700050dcc17e3106904ec7c Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Sat, 12 Apr 2025 16:25:59 -0400 Subject: [PATCH 3/3] Add missing line continuation --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 2c88cc52ef9..99747382e95 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -141,7 +141,7 @@ jobs: --target-dirs "" addons \ --new-base-url "" \ --verbose \ - https://grass.osgeo.org/grass-stable/manuals + https://grass.osgeo.org/grass-stable/manuals \ "$MKDOCS_DIR/source/" - name: Get mkdocs