CUSP-1256 (#1)
* Complete refactor Signed-off-by: Stephen Simpson <ssimpson89@users.noreply.github.com> * Complete refactor Signed-off-by: Stephen Simpson <ssimpson89@users.noreply.github.com> --------- Signed-off-by: Stephen Simpson <ssimpson89@users.noreply.github.com>
This commit is contained in:
297
src/rocky_man/web/generator.py
Normal file
297
src/rocky_man/web/generator.py
Normal file
@@ -0,0 +1,297 @@
|
||||
"""Web page generator for Rocky Man."""
|
||||
|
||||
import gzip
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Any
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
|
||||
from ..models import ManFile
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WebGenerator:
|
||||
"""Generates web pages and search index for Rocky Man.
|
||||
|
||||
Handles:
|
||||
- Generating index/search page
|
||||
- Wrapping man page HTML in templates
|
||||
- Creating search index JSON
|
||||
"""
|
||||
|
||||
def __init__(self, template_dir: Path, output_dir: Path):
|
||||
"""Initialize web generator.
|
||||
|
||||
Args:
|
||||
template_dir: Directory containing Jinja2 templates
|
||||
output_dir: Directory for HTML output
|
||||
"""
|
||||
self.template_dir = Path(template_dir)
|
||||
self.output_dir = Path(output_dir)
|
||||
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Setup Jinja2 environment
|
||||
self.env = Environment(
|
||||
loader=FileSystemLoader(str(self.template_dir)),
|
||||
autoescape=select_autoescape(['html', 'xml'])
|
||||
)
|
||||
|
||||
def generate_manpage_html(self, man_file: ManFile, version: str) -> bool:
|
||||
"""Generate complete HTML page for a man page.
|
||||
|
||||
Args:
|
||||
man_file: ManFile with html_content already set
|
||||
version: Rocky Linux version
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
if not man_file.html_content:
|
||||
logger.warning(f"No HTML content for {man_file.display_name}")
|
||||
return False
|
||||
|
||||
try:
|
||||
template = self.env.get_template('manpage.html')
|
||||
|
||||
html = template.render(
|
||||
title=f"{man_file.display_name} - {man_file.package_name} - Rocky Linux {version}",
|
||||
header_title=man_file.display_name,
|
||||
package_name=man_file.package_name,
|
||||
version=version,
|
||||
section=man_file.section,
|
||||
language=man_file.language or 'en',
|
||||
content=man_file.html_content
|
||||
)
|
||||
|
||||
# Ensure output path is set
|
||||
if not man_file.html_path:
|
||||
man_file.html_path = self._get_manpage_path(man_file, version)
|
||||
|
||||
man_file.html_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(man_file.html_path, 'w', encoding='utf-8') as f:
|
||||
f.write(html)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating HTML for {man_file.display_name}: {e}")
|
||||
return False
|
||||
|
||||
def generate_index(self, version: str, search_data: Dict[str, Any]) -> bool:
|
||||
"""Generate search/index page for a version.
|
||||
|
||||
Args:
|
||||
version: Rocky Linux version
|
||||
search_data: Search index data
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
try:
|
||||
template = self.env.get_template('index.html')
|
||||
|
||||
html = template.render(
|
||||
title=f"Rocky Linux {version} Man Pages",
|
||||
version=version,
|
||||
total_pages=len(search_data),
|
||||
packages=sorted(search_data.keys())
|
||||
)
|
||||
|
||||
index_path = self.output_dir / version / 'index.html'
|
||||
index_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(index_path, 'w', encoding='utf-8') as f:
|
||||
f.write(html)
|
||||
|
||||
logger.info(f"Generated index for version {version}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating index for {version}: {e}")
|
||||
return False
|
||||
|
||||
def generate_packages_index(self, version: str, search_data: Dict[str, Any]) -> bool:
|
||||
"""Generate full packages index page.
|
||||
|
||||
Args:
|
||||
version: Rocky Linux version
|
||||
search_data: Search index data
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
try:
|
||||
# Group packages by first letter
|
||||
packages_by_letter = {}
|
||||
|
||||
for pkg_name, pages in search_data.items():
|
||||
first_char = pkg_name[0].upper()
|
||||
if not first_char.isalpha():
|
||||
first_char = 'other'
|
||||
|
||||
if first_char not in packages_by_letter:
|
||||
packages_by_letter[first_char] = []
|
||||
|
||||
packages_by_letter[first_char].append({
|
||||
'name': pkg_name,
|
||||
'count': len(pages)
|
||||
})
|
||||
|
||||
# Sort packages within each letter
|
||||
for letter in packages_by_letter:
|
||||
packages_by_letter[letter].sort(key=lambda x: x['name'])
|
||||
|
||||
template = self.env.get_template('packages.html')
|
||||
|
||||
html = template.render(
|
||||
title=f"All Packages - Rocky Linux {version}",
|
||||
version=version,
|
||||
total_packages=len(search_data),
|
||||
packages_by_letter=packages_by_letter
|
||||
)
|
||||
|
||||
output_path = self.output_dir / version / 'packages.html'
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(html)
|
||||
|
||||
logger.info(f"Generated packages index for version {version}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating packages index for {version}: {e}")
|
||||
return False
|
||||
|
||||
def generate_search_index(
|
||||
self,
|
||||
man_files: List[ManFile],
|
||||
version: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Generate search index from man files.
|
||||
|
||||
Args:
|
||||
man_files: List of ManFile objects
|
||||
version: Rocky Linux version
|
||||
|
||||
Returns:
|
||||
Search index dictionary
|
||||
"""
|
||||
index = {}
|
||||
|
||||
for man_file in man_files:
|
||||
pkg_name = man_file.package_name
|
||||
|
||||
if pkg_name not in index:
|
||||
index[pkg_name] = {}
|
||||
|
||||
# Create entry for this man page
|
||||
entry = {
|
||||
'name': man_file.name,
|
||||
'section': man_file.section,
|
||||
'display_name': man_file.display_name,
|
||||
'language': man_file.language or 'en',
|
||||
'url': man_file.uri_path,
|
||||
'full_name': f"{man_file.package_name} - {man_file.display_name}"
|
||||
}
|
||||
|
||||
# Use display name as key (handles duplicates with different sections)
|
||||
key = man_file.display_name
|
||||
if man_file.language:
|
||||
key = f"{key}.{man_file.language}"
|
||||
|
||||
index[pkg_name][key] = entry
|
||||
|
||||
return index
|
||||
|
||||
def save_search_index(self, index: Dict[str, Any], version: str) -> bool:
|
||||
"""Save search index as JSON (both plain and gzipped).
|
||||
|
||||
Args:
|
||||
index: Search index dictionary
|
||||
version: Rocky Linux version
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
try:
|
||||
version_dir = self.output_dir / version
|
||||
version_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
json_path = version_dir / 'search.json'
|
||||
gz_path = version_dir / 'search.json.gz'
|
||||
|
||||
# Sort for consistency
|
||||
sorted_index = {k: index[k] for k in sorted(index)}
|
||||
|
||||
# Save plain JSON
|
||||
with open(json_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(sorted_index, f, indent=2)
|
||||
|
||||
# Save gzipped JSON
|
||||
with gzip.open(gz_path, 'wt', encoding='utf-8') as f:
|
||||
json.dump(sorted_index, f)
|
||||
|
||||
logger.info(f"Saved search index for {version} ({len(index)} packages)")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving search index: {e}")
|
||||
return False
|
||||
|
||||
def _get_manpage_path(self, man_file: ManFile, version: str) -> Path:
|
||||
"""Get output path for a man page HTML file.
|
||||
|
||||
Args:
|
||||
man_file: ManFile object
|
||||
version: Rocky Linux version
|
||||
|
||||
Returns:
|
||||
Path for HTML file
|
||||
"""
|
||||
version_dir = self.output_dir / version
|
||||
pkg_dir = version_dir / man_file.package_name
|
||||
section_dir = pkg_dir / f"man{man_file.section}"
|
||||
|
||||
return section_dir / man_file.html_filename
|
||||
|
||||
def generate_root_index(self, versions: List[str]) -> bool:
|
||||
"""Generate root index page linking to all versions.
|
||||
|
||||
Args:
|
||||
versions: List of Rocky Linux versions
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
try:
|
||||
template = self.env.get_template('root.html')
|
||||
|
||||
# Sort versions numerically (e.g., 8.10, 9.6, 10.0)
|
||||
def version_key(v):
|
||||
try:
|
||||
parts = v.split('.')
|
||||
return tuple(int(p) for p in parts)
|
||||
except (ValueError, AttributeError):
|
||||
return (0, 0)
|
||||
|
||||
html = template.render(
|
||||
title="Rocky Linux Man Pages",
|
||||
versions=sorted(versions, key=version_key)
|
||||
)
|
||||
|
||||
index_path = self.output_dir / 'index.html'
|
||||
|
||||
with open(index_path, 'w', encoding='utf-8') as f:
|
||||
f.write(html)
|
||||
|
||||
logger.info("Generated root index page")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating root index: {e}")
|
||||
return False
|
||||
Reference in New Issue
Block a user