blob: bbbad01b3a9eff0bae57305bd55ecb12d01e741f [file]
#!/usr/bin/env python3
# ***************************************************************************************************************************
# * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
# * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
# * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
# * with the License. You may obtain a copy of the License at *
# * *
# * http://www.apache.org/licenses/LICENSE-2.0 *
# * *
# * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
# * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
# * specific language governing permissions and limitations under the License. *
# ***************************************************************************************************************************
"""
Script to check for correct topic links in the Juneau source tree.
This script:
1. Scans /docs/pages/topics for all markdown files
2. Extracts slug names and titles from the frontmatter
3. Scans the entire source tree for links to https://juneau.apache.org/docs/topics/SLUG">TITLE</a>
4. Reports any mismatches between expected and actual slug/title combinations
"""
import os
import re
import sys
from pathlib import Path
def extract_topic_info(docs_dir):
"""Extract slug and title information from all topic markdown files."""
topics = {}
topics_dir = Path(docs_dir) / "pages" / "topics"
if not topics_dir.exists():
print(f"ERROR: Topics directory not found: {topics_dir}")
return topics
for md_file in topics_dir.glob("*.md"):
if md_file.name == "README.md":
continue
try:
with open(md_file, 'r', encoding='utf-8') as f:
content = f.read()
# Extract frontmatter
frontmatter_match = re.search(r'^---\n(.*?)\n---', content, re.DOTALL)
if frontmatter_match:
frontmatter = frontmatter_match.group(1)
# Extract title and slug
title_match = re.search(r'^title:\s*["\']?([^"\']+)["\']?', frontmatter, re.MULTILINE)
slug_match = re.search(r'^slug:\s*([^\s]+)', frontmatter, re.MULTILINE)
if title_match and slug_match:
title = title_match.group(1).strip()
slug = slug_match.group(1).strip()
topics[slug] = title
print(f"Found topic: {slug} -> {title}")
else:
print(f"WARNING: Could not extract title/slug from {md_file.name}")
else:
print(f"WARNING: No frontmatter found in {md_file.name}")
except Exception as e:
print(f"ERROR: Failed to process {md_file.name}: {e}")
return topics
def find_topic_links(source_dir): # NOSONAR python:S3776 -- Cognitive complexity is acceptable for this utility function
"""Find all topic links in the source tree."""
links = []
# Pattern to match topic links
link_pattern = re.compile(r'https://juneau\.apache\.org/docs/topics/([^"]+)">([^<]+)</a>')
# File extensions to search
extensions = {'.java', '.md', '.xml', '.properties', '.txt', '.adoc', '.rst'}
for root, dirs, files in os.walk(source_dir):
# Skip certain directories
dirs[:] = [d for d in dirs if not d.startswith('.') and d not in {'target', 'node_modules', 'build', 'dist', 'scripts'}]
for file in files:
# Skip report files generated by this script
if file.startswith('topic-link-check-') and file.endswith('.txt'):
continue
if any(file.endswith(ext) for ext in extensions):
file_path = Path(root) / file
try:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# For markdown files, exclude code blocks
if file.endswith('.md'):
# Remove code blocks (```...```) from content before searching
import re as re_module
code_block_pattern = re_module.compile(r'```.*?```', re_module.DOTALL)
content = code_block_pattern.sub('', content)
# Also remove inline code (`...`)
inline_code_pattern = re_module.compile(r'`[^`]+`')
content = inline_code_pattern.sub('', content)
for match in link_pattern.finditer(content):
slug = match.group(1)
title = match.group(2)
links.append({
'file': str(file_path.relative_to(source_dir)),
'line': content[:match.start()].count('\n') + 1,
'slug': slug,
'title': title,
'full_match': match.group(0)
})
except Exception as e:
print(f"ERROR: Failed to process {file_path}: {e}")
return links
def check_links(links, topics):
"""Check if the found links match the expected slug/title combinations."""
warnings = []
for link in links:
slug = link['slug']
title = link['title']
if slug in topics:
expected_title = topics[slug]
if title != expected_title:
warnings.append({
'type': 'title_mismatch',
'file': link['file'],
'line': link['line'],
'slug': slug,
'expected_title': expected_title,
'actual_title': title,
'full_match': link['full_match']
})
else:
warnings.append({
'type': 'unknown_slug',
'file': link['file'],
'line': link['line'],
'slug': slug,
'title': title,
'full_match': link['full_match']
})
return warnings
def main():
# Get the script directory (should be /juneau/scripts)
script_dir = Path(__file__).parent
juneau_root = script_dir.parent
docs_dir = juneau_root / "docs"
print("Juneau Topic Link Checker")
print("=" * 50)
# Extract topic information
print("\nExtracting topic information from docs...")
topics = extract_topic_info(docs_dir)
if not topics:
print("ERROR: No topics found. Check if docs directory exists and contains topic files.")
sys.exit(1)
print(f"\nFound {len(topics)} topics")
# Find all topic links
print("\nScanning source tree for topic links...")
links = find_topic_links(juneau_root)
print(f"Found {len(links)} topic links")
# Check for issues
print("\nChecking links for issues...")
warnings = check_links(links, topics)
# Report results
if warnings:
print(f"\nWARNINGS ({len(warnings)} found):")
print("=" * 50)
for warning in warnings:
if warning['type'] == 'title_mismatch':
print("TITLE MISMATCH:")
print(f" File: {warning['file']}:{warning['line']}")
print(f" Slug: {warning['slug']}")
print(f" Expected title: '{warning['expected_title']}'")
print(f" Actual title: '{warning['actual_title']}'")
print(f" Link: {warning['full_match']}")
print()
elif warning['type'] == 'unknown_slug':
print("UNKNOWN SLUG:")
print(f" File: {warning['file']}:{warning['line']}")
print(f" Slug: {warning['slug']}")
print(f" Title: '{warning['title']}'")
print(f" Link: {warning['full_match']}")
print()
print(f"Total warnings: {len(warnings)}")
print("\nNote: Warnings are informational only and do not fail the build.")
sys.exit(0) # Exit successfully with warnings
else:
print("\n✓ All topic links are correct!")
sys.exit(0)
if __name__ == "__main__":
main()