CUSP-1342 - Fix See Also
Signed-off-by: Stephen Simpson <ssimpson89@users.noreply.github.com>
This commit is contained in:
@@ -16,16 +16,12 @@ def setup_logging(verbose: bool = False):
|
|||||||
level = logging.DEBUG if verbose else logging.INFO
|
level = logging.DEBUG if verbose else logging.INFO
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=level,
|
level=level,
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||||
datefmt='%Y-%m-%d %H:%M:%S'
|
datefmt="%Y-%m-%d %H:%M:%S",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def process_version(
|
def process_version(config: Config, version: str, template_dir: Path) -> bool:
|
||||||
config: Config,
|
|
||||||
version: str,
|
|
||||||
template_dir: Path
|
|
||||||
) -> bool:
|
|
||||||
"""Process a single Rocky Linux version.
|
"""Process a single Rocky Linux version.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -67,7 +63,7 @@ def process_version(
|
|||||||
repo_type=repo_type,
|
repo_type=repo_type,
|
||||||
arch=arch,
|
arch=arch,
|
||||||
cache_dir=cache_dir,
|
cache_dir=cache_dir,
|
||||||
download_dir=version_download_dir
|
download_dir=version_download_dir,
|
||||||
)
|
)
|
||||||
|
|
||||||
# List packages (with man pages only)
|
# List packages (with man pages only)
|
||||||
@@ -83,19 +79,19 @@ def process_version(
|
|||||||
if config.skip_packages:
|
if config.skip_packages:
|
||||||
original_count = len(packages)
|
original_count = len(packages)
|
||||||
packages = [
|
packages = [
|
||||||
pkg for pkg in packages
|
pkg for pkg in packages if pkg.name not in config.skip_packages
|
||||||
if pkg.name not in config.skip_packages
|
|
||||||
]
|
]
|
||||||
filtered_count = original_count - len(packages)
|
filtered_count = original_count - len(packages)
|
||||||
if filtered_count > 0:
|
if filtered_count > 0:
|
||||||
logger.info(f"Filtered out {filtered_count} packages based on skip list")
|
logger.info(
|
||||||
|
f"Filtered out {filtered_count} packages based on skip list"
|
||||||
|
)
|
||||||
logger.info(f"Processing {len(packages)} packages")
|
logger.info(f"Processing {len(packages)} packages")
|
||||||
|
|
||||||
# Download packages
|
# Download packages
|
||||||
logger.info("Downloading packages...")
|
logger.info("Downloading packages...")
|
||||||
downloaded = repo_manager.download_packages(
|
downloaded = repo_manager.download_packages(
|
||||||
packages,
|
packages, max_workers=config.parallel_downloads
|
||||||
max_workers=config.parallel_downloads
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Extract man pages
|
# Extract man pages
|
||||||
@@ -103,11 +99,10 @@ def process_version(
|
|||||||
extractor = ManPageExtractor(
|
extractor = ManPageExtractor(
|
||||||
version_extract_dir,
|
version_extract_dir,
|
||||||
skip_sections=config.skip_sections,
|
skip_sections=config.skip_sections,
|
||||||
skip_languages=config.skip_languages
|
skip_languages=config.skip_languages,
|
||||||
)
|
)
|
||||||
man_files = extractor.extract_from_packages(
|
man_files = extractor.extract_from_packages(
|
||||||
downloaded,
|
downloaded, max_workers=config.parallel_downloads
|
||||||
max_workers=config.parallel_downloads
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Extracted {len(man_files)} man pages")
|
logger.info(f"Extracted {len(man_files)} man pages")
|
||||||
@@ -124,8 +119,7 @@ def process_version(
|
|||||||
logger.info("Converting man pages to HTML...")
|
logger.info("Converting man pages to HTML...")
|
||||||
converter = ManPageConverter(version_output_dir)
|
converter = ManPageConverter(version_output_dir)
|
||||||
converted = converter.convert_many(
|
converted = converter.convert_many(
|
||||||
man_files_with_content,
|
man_files_with_content, max_workers=config.parallel_conversions
|
||||||
max_workers=config.parallel_conversions
|
|
||||||
)
|
)
|
||||||
|
|
||||||
all_man_files.extend(converted)
|
all_man_files.extend(converted)
|
||||||
@@ -163,132 +157,141 @@ def process_version(
|
|||||||
# Generate packages index page
|
# Generate packages index page
|
||||||
web_gen.generate_packages_index(version, search_index)
|
web_gen.generate_packages_index(version, search_index)
|
||||||
|
|
||||||
|
# Set HTML paths for all man files
|
||||||
|
for man_file in all_man_files:
|
||||||
|
if not man_file.html_path:
|
||||||
|
man_file.html_path = web_gen._get_manpage_path(man_file, version)
|
||||||
|
|
||||||
|
# Link cross-references between man pages
|
||||||
|
logger.info("Linking cross-references...")
|
||||||
|
converter.link_cross_references(all_man_files, version)
|
||||||
|
|
||||||
# Wrap man pages in templates
|
# Wrap man pages in templates
|
||||||
logger.info("Generating man page HTML...")
|
logger.info("Generating man page HTML...")
|
||||||
for man_file in all_man_files:
|
for man_file in all_man_files:
|
||||||
web_gen.generate_manpage_html(man_file, version)
|
web_gen.generate_manpage_html(man_file, version)
|
||||||
|
|
||||||
logger.info(f"Successfully processed {len(all_man_files)} man pages for Rocky Linux {version}")
|
logger.info(
|
||||||
|
f"Successfully processed {len(all_man_files)} man pages for Rocky Linux {version}"
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Main entry point."""
|
"""Main entry point."""
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description='Generate HTML documentation for Rocky Linux man pages'
|
description="Generate HTML documentation for Rocky Linux man pages"
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--versions',
|
"--versions",
|
||||||
nargs='+',
|
nargs="+",
|
||||||
default=['8.10', '9.6', '10.0'],
|
default=["8.10", "9.6", "10.0"],
|
||||||
help='Rocky Linux versions to process (default: 8.10 9.6 10.0)'
|
help="Rocky Linux versions to process (default: 8.10 9.6 10.0)",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--repo-types',
|
"--repo-types",
|
||||||
nargs='+',
|
nargs="+",
|
||||||
default=['BaseOS', 'AppStream'],
|
default=["BaseOS", "AppStream"],
|
||||||
help='Repository types to process (default: BaseOS AppStream)'
|
help="Repository types to process (default: BaseOS AppStream)",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--output-dir',
|
"--output-dir",
|
||||||
type=Path,
|
type=Path,
|
||||||
default=Path('./html'),
|
default=Path("./html"),
|
||||||
help='Output directory for HTML files (default: ./html)'
|
help="Output directory for HTML files (default: ./html)",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--download-dir',
|
"--download-dir",
|
||||||
type=Path,
|
type=Path,
|
||||||
default=Path('./tmp/downloads'),
|
default=Path("./tmp/downloads"),
|
||||||
help='Directory for downloading packages (default: ./tmp/downloads)'
|
help="Directory for downloading packages (default: ./tmp/downloads)",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--extract-dir',
|
"--extract-dir",
|
||||||
type=Path,
|
type=Path,
|
||||||
default=Path('./tmp/extracts'),
|
default=Path("./tmp/extracts"),
|
||||||
help='Directory for extracting man pages (default: ./tmp/extracts)'
|
help="Directory for extracting man pages (default: ./tmp/extracts)",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--keep-rpms',
|
"--keep-rpms",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Keep downloaded RPM files after processing'
|
help="Keep downloaded RPM files after processing",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--keep-extracts',
|
"--keep-extracts",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Keep extracted man files after processing'
|
help="Keep extracted man files after processing",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--parallel-downloads',
|
"--parallel-downloads",
|
||||||
type=int,
|
type=int,
|
||||||
default=5,
|
default=5,
|
||||||
help='Number of parallel downloads (default: 5)'
|
help="Number of parallel downloads (default: 5)",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--parallel-conversions',
|
"--parallel-conversions",
|
||||||
type=int,
|
type=int,
|
||||||
default=10,
|
default=10,
|
||||||
help='Number of parallel HTML conversions (default: 10)'
|
help="Number of parallel HTML conversions (default: 10)",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--mirror',
|
"--mirror",
|
||||||
default='http://dl.rockylinux.org/',
|
default="http://dl.rockylinux.org/",
|
||||||
help='Rocky Linux mirror URL (default: http://dl.rockylinux.org/)'
|
help="Rocky Linux mirror URL (default: http://dl.rockylinux.org/)",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--template-dir',
|
"--template-dir",
|
||||||
type=Path,
|
type=Path,
|
||||||
default=Path(__file__).parent.parent.parent / 'templates',
|
default=Path(__file__).parent.parent.parent / "templates",
|
||||||
help='Template directory (default: ./templates)'
|
help="Template directory (default: ./templates)",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-v', '--verbose',
|
"-v", "--verbose", action="store_true", help="Enable verbose logging"
|
||||||
action='store_true',
|
|
||||||
help='Enable verbose logging'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--skip-sections',
|
"--skip-sections",
|
||||||
nargs='*',
|
nargs="*",
|
||||||
default=None,
|
default=None,
|
||||||
help='Man sections to skip (default: 3 3p 3pm). Use empty list to skip none.'
|
help="Man sections to skip (default: 3 3p 3pm). Use empty list to skip none.",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--skip-packages',
|
"--skip-packages",
|
||||||
nargs='*',
|
nargs="*",
|
||||||
default=None,
|
default=None,
|
||||||
help='Package names to skip (default: lapack dpdk-devel gl-manpages). Use empty list to skip none.'
|
help="Package names to skip (default: lapack dpdk-devel gl-manpages). Use empty list to skip none.",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--skip-languages',
|
"--skip-languages",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
default=None,
|
default=None,
|
||||||
help='Skip non-English man pages (default: enabled)'
|
help="Skip non-English man pages (default: enabled)",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--keep-languages',
|
"--keep-languages",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Keep all languages (disables --skip-languages)'
|
help="Keep all languages (disables --skip-languages)",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--allow-all-sections',
|
"--allow-all-sections",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Include all man sections (overrides --skip-sections)'
|
help="Include all man sections (overrides --skip-sections)",
|
||||||
)
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -319,7 +322,7 @@ def main():
|
|||||||
skip_sections=args.skip_sections,
|
skip_sections=args.skip_sections,
|
||||||
skip_packages=args.skip_packages,
|
skip_packages=args.skip_packages,
|
||||||
skip_languages=skip_languages,
|
skip_languages=skip_languages,
|
||||||
allow_all_sections=args.allow_all_sections
|
allow_all_sections=args.allow_all_sections,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info("Rocky Man - Rocky Linux Man Page Generator")
|
logger.info("Rocky Man - Rocky Linux Man Page Generator")
|
||||||
@@ -368,5 +371,5 @@ def main():
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|||||||
@@ -227,9 +227,91 @@ class ManPageConverter:
|
|||||||
<a href="{target_filename}" style="color: var(--accent-primary); text-decoration: none; font-weight: 500;">View the manual page</a>
|
<a href="{target_filename}" style="color: var(--accent-primary); text-decoration: none; font-weight: 500;">View the manual page</a>
|
||||||
</p>
|
</p>
|
||||||
</div>'''
|
</div>'''
|
||||||
|
|
||||||
return html
|
return html
|
||||||
|
|
||||||
|
def link_cross_references(self, man_files: List[ManFile], version: str) -> None:
|
||||||
|
"""Add hyperlinks to cross-references in SEE ALSO sections.
|
||||||
|
|
||||||
|
Goes through all converted HTML files and converts man page references
|
||||||
|
like pty(4) into working hyperlinks.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
man_files: List of all converted ManFile objects
|
||||||
|
"""
|
||||||
|
# Build lookup index: (name, section) -> relative_path
|
||||||
|
lookup = {}
|
||||||
|
for mf in man_files:
|
||||||
|
key = (mf.name.lower(), str(mf.section))
|
||||||
|
if key not in lookup:
|
||||||
|
# Store the relative path from the version root
|
||||||
|
lookup[key] = f"{mf.package_name}/man{mf.section}/{mf.html_filename}"
|
||||||
|
|
||||||
|
logger.info(f"Linking cross-references across {len(man_files)} man pages...")
|
||||||
|
|
||||||
|
# Process each man page HTML content
|
||||||
|
for man_file in man_files:
|
||||||
|
if not man_file.html_content:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
html = man_file.html_content
|
||||||
|
|
||||||
|
# Find and replace man page references
|
||||||
|
# Mandoc outputs references as: <b>name</b>(section)
|
||||||
|
# Pattern matches both <b>name</b>(section) and plain name(section)
|
||||||
|
pattern = (
|
||||||
|
r"<b>([\w\-_.]+)</b>\((\d+[a-z]*)\)|\b([\w\-_.]+)\((\d+[a-z]*)\)"
|
||||||
|
)
|
||||||
|
|
||||||
|
def replace_reference(match):
|
||||||
|
full_match = match.group(0)
|
||||||
|
|
||||||
|
# Check if this match is already inside an <a> tag
|
||||||
|
# Look back up to 500 chars for context
|
||||||
|
before_text = html[max(0, match.start() - 500) : match.start()]
|
||||||
|
|
||||||
|
# Find the last <a and last </a> before this match
|
||||||
|
last_open = before_text.rfind("<a ")
|
||||||
|
last_close = before_text.rfind("</a>")
|
||||||
|
|
||||||
|
# If the last <a> is after the last </a>, we're inside a link
|
||||||
|
if last_open > last_close:
|
||||||
|
return full_match
|
||||||
|
|
||||||
|
if match.group(1): # <b>name</b>(section) format
|
||||||
|
name = match.group(1).lower()
|
||||||
|
section = match.group(2)
|
||||||
|
else: # plain name(section) format
|
||||||
|
name = match.group(3).lower()
|
||||||
|
section = match.group(4)
|
||||||
|
|
||||||
|
# Look up the referenced man page
|
||||||
|
key = (name, section)
|
||||||
|
if key in lookup:
|
||||||
|
# Calculate relative path from current file to target
|
||||||
|
target_path = lookup[key]
|
||||||
|
# File structure: output_dir/version/package_name/manN/file.html
|
||||||
|
# Need to go up 3 levels to reach output root, then down to version/target
|
||||||
|
# Current: version/package_name/manN/file.html
|
||||||
|
# Target: version/other_package/manM/file.html
|
||||||
|
rel_path = f"../../../{version}/{target_path}"
|
||||||
|
return f'<a href="{rel_path}">{full_match}</a>'
|
||||||
|
|
||||||
|
return full_match
|
||||||
|
|
||||||
|
updated_html = re.sub(pattern, replace_reference, html)
|
||||||
|
|
||||||
|
# Update the content if something changed
|
||||||
|
if updated_html != html:
|
||||||
|
man_file.html_content = updated_html
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(
|
||||||
|
f"Error linking references in {man_file.display_name}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("Cross-reference linking complete")
|
||||||
|
|
||||||
def _get_output_path(self, man_file: ManFile) -> Path:
|
def _get_output_path(self, man_file: ManFile) -> Path:
|
||||||
"""Determine output path for HTML file.
|
"""Determine output path for HTML file.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user