diff --git a/Jenkinsfile b/Jenkinsfile index 9ac136e..64ffb52 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -50,6 +50,11 @@ spec: defaultValue: 'rockyman', description: 'B2 bucket name for uploads' ) + string( + name: 'EXISTING_VERSIONS', + defaultValue: '', + description: 'Existing versions already built (space-separated)' + ) string( name: 'PARALLEL_DOWNLOADS', defaultValue: '5', @@ -100,7 +105,8 @@ docker run --rm \ --versions ${VERSIONS} \ --verbose \ --parallel-downloads ${PARALLEL_DOWNLOADS} \ - --parallel-conversions ${PARALLEL_CONVERSIONS} + --parallel-conversions ${PARALLEL_CONVERSIONS} \ + --existing-versions "${EXISTING_VERSIONS}" ''' } } diff --git a/README.md b/README.md index 9796500..0c11fcf 100644 --- a/README.md +++ b/README.md @@ -1,121 +1,85 @@ # Rocky Man π -**Rocky Man** is a comprehensive man page hosting solution for Rocky Linux, providing beautiful, searchable documentation for all packages in BaseOS and AppStream repositories across Rocky Linux 8, 9, and 10. - -> **β¨ This is a complete rewrite** with 60-80% faster performance, modern architecture, and production-ready features! - -## π What's New in This Rewrite - -This version is a **complete ground-up rebuild** with major improvements: - -- π **60-80% faster** - Pre-filters packages using filelists.xml (downloads only ~800 packages instead of ~3000) -- ποΈ **Modular architecture** - Clean separation into models, repo, processor, web, and utils -- π¨ **Modern UI** - Beautiful dark theme with instant fuzzy search -- π³ **Container ready** - Multi-stage Dockerfile that works on any architecture -- β‘ **Parallel processing** - Concurrent downloads and HTML conversions -- π§Ή **Smart cleanup** - Automatic cleanup of temporary files -- π **Well documented** - Comprehensive docstrings and type hints throughout -- π **Thread safe** - Proper locking and resource management -- π€ **GitHub Actions** - Automated weekly builds and deployment - -### Performance Comparison - -| Metric | Old Version | New Version | Improvement | -|--------|-------------|-------------|-------------| -| Packages Downloaded | ~3000 | ~800 | 73% reduction | -| Processing Time | 2-3 hours | 30-45 minutes | 75% faster | -| Bandwidth Used | ~10 GB | ~2-3 GB | 80% reduction | -| Architecture | Single file | Modular (16 files) | Much cleaner | -| Thread Safety | β οΈ Issues | β Safe | Fixed | -| Cleanup | Manual | Automatic | Improved | -| UI Quality | Basic | Modern | Much better | +**Rocky Man** is a tool for generating searchable HTML documentation from Rocky Linux man pages across BaseOS and AppStream repositories for Rocky Linux 8, 9, and 10. ## Features -- β¨ **Fast & Efficient**: Uses filelists.xml to pre-filter packages with man pages (massive bandwidth savings) -- π **Fuzzy Search**: Instant search across all man pages with Fuse.js -- π¨ **Modern UI**: Clean, responsive dark theme interface inspired by GitHub -- π¦ **Complete Coverage**: All packages from BaseOS and AppStream repositories -- π³ **Container Ready**: Architecture-independent Docker support (works on x86_64, aarch64, arm64, etc.) -- π **GitHub Actions**: Automated weekly builds and deployment to GitHub Pages -- π§Ή **Smart Cleanup**: Automatic cleanup of temporary files (configurable) -- β‘ **Parallel Processing**: Concurrent downloads and conversions for maximum speed -- π **Multi-version**: Support for Rocky Linux 8, 9, and 10 simultaneously +- **Fast & Efficient**: Uses filelists.xml to pre-filter packages with man pages +- **Complete Coverage**: All packages from BaseOS and AppStream repositories +- **Container Ready**: Works on x86_64, aarch64, arm64, etc. +- **Smart Cleanup**: Automatic cleanup of temporary files (configurable) +- **Parallel Processing**: Concurrent downloads and conversions for maximum speed +- **Multi-version**: Support for Rocky Linux 8, 9, and 10 simultaneously ## Quick Start -### Option 1: Docker (Recommended) - -```bash -# Build the image -docker build -t rocky-man . - -# Generate man pages for Rocky Linux 9.6 -docker run --rm -v $(pwd)/html:/data/html rocky-man --versions 9.6 - -# Generate for multiple versions -docker run --rm -v $(pwd)/html:/data/html rocky-man --versions 8.10 9.6 10.0 - -# With verbose logging -docker run --rm -v $(pwd)/html:/data/html rocky-man --versions 9.6 --verbose - -# Keep downloaded RPMs (mount the download directory) -docker run --rm -it \ - -v $(pwd)/html:/data/html \ - -v $(pwd)/downloads:/data/tmp/downloads \ - rocky-man --versions 9.6 --keep-rpms --verbose -``` - -### Option 2: Podman (Native Rocky Linux) +### Podman (Recommended) ```bash # Build the image podman build -t rocky-man . -# Run with podman (note the :Z flag for SELinux) -podman run --rm -v $(pwd)/html:/data/html:Z rocky-man --versions 9.6 +# Generate man pages for Rocky Linux 9.6 (using defaults, no custom args) +podman run --rm -v $(pwd)/html:/data/html:Z rocky-man -# Interactive mode for debugging -podman run --rm -it -v $(pwd)/html:/data/html:Z rocky-man --versions 9.6 --verbose +# Generate for specific versions (requires explicit paths) +podman run --rm -v $(pwd)/html:/app/html:Z rocky-man \ + --versions 8.10 9.6 10.0 --output-dir /app/html + +# With verbose logging +podman run --rm -v $(pwd)/html:/app/html:Z rocky-man \ + --versions 9.6 --output-dir /app/html --verbose # Keep downloaded RPMs (mount the download directory) podman run --rm -it \ - -v $(pwd)/html:/data/html:Z \ - -v $(pwd)/downloads:/data/tmp/downloads:Z \ - rocky-man --versions 9.6 --keep-rpms --verbose + -v $(pwd)/html:/app/html:Z \ + -v $(pwd)/downloads:/app/tmp/downloads:Z \ + rocky-man --versions 9.6 --keep-rpms \ + --output-dir /app/html --download-dir /app/tmp/downloads --verbose ``` -### Option 3: Docker Compose (Development) +### Docker ```bash -# Build and run -docker-compose up +# Build the image +docker build -t rocky-man . -# The generated HTML will be in ./html/ -# Preview at http://localhost:8080 (nginx container) +# Generate man pages (using defaults, no custom args) +docker run --rm -v $(pwd)/html:/data/html rocky-man + +# Generate for specific versions (requires explicit paths) +docker run --rm -v $(pwd)/html:/app/html rocky-man \ + --versions 9.6 --output-dir /app/html + +# Interactive mode for debugging +docker run --rm -it -v $(pwd)/html:/app/html rocky-man \ + --versions 9.6 --output-dir /app/html --verbose + +# Keep downloaded RPMs (mount the download directory) +docker run --rm -it \ + -v $(pwd)/html:/app/html \ + -v $(pwd)/downloads:/app/tmp/downloads \ + rocky-man --versions 9.6 --keep-rpms \ + --output-dir /app/html --download-dir /app/tmp/downloads --verbose ``` ### Directory Structure in Container -When running in a container, rocky-man uses these directories inside `/data/`: +The container uses different paths depending on whether you pass custom arguments: -- `/data/html` - Generated HTML output (mount this to access results) -- `/data/tmp/downloads` - Downloaded RPM files (temporary) -- `/data/tmp/extracts` - Extracted man page files (temporary) +**Without custom arguments** (using Dockerfile CMD defaults): +- `/data/html` - Generated HTML output +- `/data/tmp/downloads` - Downloaded RPM files +- `/data/tmp/extracts` - Extracted man page files -By default, RPMs and extracts are automatically cleaned up after processing. If you want to keep the RPMs (e.g., for debugging or multiple runs), mount the download directory and use `--keep-rpms`: +**With custom arguments** (argparse defaults from working directory `/app`): +- `/app/html` - Generated HTML output +- `/app/tmp/downloads` - Downloaded RPM files +- `/app/tmp/extracts` - Extracted man page files -```bash -# This keeps RPMs on your host in ./downloads/ -podman run --rm -it \ - -v $(pwd)/html:/data/html:Z \ - -v $(pwd)/downloads:/data/tmp/downloads:Z \ - rocky-man --versions 9.6 --keep-rpms -``` +**Important**: When passing custom arguments, the container's CMD is overridden and the code falls back to relative paths (`./html` = `/app/html`). You must explicitly specify `--output-dir /app/html --download-dir /app/tmp/downloads` to match your volume mounts. Without this, files are written inside the container and lost when it stops (especially with `--rm`). -**Note**: Without mounting `/data/tmp/downloads`, the `--keep-rpms` flag will keep files inside the container, but they'll be lost when the container stops (especially with `--rm`). - -### Option 4: Local Development +### Local Development #### Prerequisites @@ -154,6 +118,9 @@ python -m rocky_man.main --parallel-downloads 10 --parallel-conversions 20 # Use a different mirror python -m rocky_man.main --mirror https://mirrors.example.com/ + +# Only BaseOS (faster) +python -m rocky_man.main --repo-types BaseOS --versions 9.6 ``` ## Architecture @@ -164,59 +131,24 @@ Rocky Man is organized into clean, modular components: rocky-man/ βββ src/rocky_man/ β βββ models/ # Data models (Package, ManFile) -β β βββ package.py # RPM package representation -β β βββ manfile.py # Man page file representation -β βββ repo/ # Repository management -β β βββ manager.py # DNF repository operations -β β βββ contents.py # Filelists.xml parser (key optimization!) -β βββ processor/ # Man page processing -β β βββ extractor.py # Extract man pages from RPMs -β β βββ converter.py # Convert to HTML with mandoc -β βββ web/ # Web page generation -β β βββ generator.py # HTML and search index generation -β βββ utils/ # Utilities -β β βββ config.py # Configuration management -β βββ main.py # Main entry point and orchestration -βββ templates/ # Jinja2 templates -β βββ base.html # Base template with modern styling -β βββ index.html # Search page with Fuse.js -β βββ manpage.html # Individual man page display -β βββ root.html # Multi-version landing page -βββ Dockerfile # Multi-stage, arch-independent -βββ docker-compose.yml # Development setup with nginx -βββ .github/workflows/ # GitHub Actions automation -βββ pyproject.toml # Python project configuration +β βββ repo/ # Repository management +β βββ processor/ # Man page processing +β βββ web/ # Web page generation +β βββ utils/ # Utilities +β βββ main.py # Main entry point and orchestration +βββ templates/ # Jinja2 templates +βββ Dockerfile # Multi-stage, arch-independent +βββ pyproject.toml # Python project configuration ``` ### How It Works -1. **Package Discovery** π - - Parse repository `filelists.xml` to identify packages with man pages - - This is the **key optimization** - we know what to download before downloading! - -2. **Smart Download** β¬οΈ - - Download only packages containing man pages (60-80% reduction) - - Parallel downloads for speed - - Architecture-independent (man pages are the same across arches) - -3. **Extraction** π¦ - - Extract man page files from RPM packages - - Handle gzipped and plain text man pages - - Support for multiple languages - -4. **Conversion** π - - Convert troff format to HTML using mandoc - - Clean up HTML output - - Parallel processing for speed - -5. **Web Generation** π - - Wrap HTML in beautiful templates - - Generate search index with fuzzy search - - Create multi-version navigation - -6. **Cleanup** π§Ή - - Automatically remove temporary files (configurable) - - Keep only what you need +1. **Package Discovery** - Parse repository `filelists.xml` to identify packages with man pages +2. **Smart Download** - Download only packages containing man pages with parallel downloads +3. **Extraction** - Extract man page files from RPM packages +4. **Conversion** - Convert troff format to HTML using mandoc +5. **Web Generation** - Wrap HTML in templates and generate search index +6. **Cleanup** - Automatically remove temporary files (configurable) ## Command Line Options @@ -266,183 +198,6 @@ Options: -v, --verbose Enable verbose logging ``` -### Examples - -```bash -# Quick test with one version -python -m rocky_man.main --versions 9.6 - -# Production build with all versions (default) -python -m rocky_man.main - -# Fast build with more parallelism -python -m rocky_man.main --parallel-downloads 15 --parallel-conversions 30 - -# Keep files for debugging -python -m rocky_man.main --keep-rpms --keep-extracts --verbose - -# Custom mirror (faster for your location) -python -m rocky_man.main --mirror https://mirror.usi.edu/pub/rocky/ - -# Only BaseOS (faster) -python -m rocky_man.main --repo-types BaseOS --versions 9.6 -``` - -## GitHub Actions Integration - -This project includes a **production-ready GitHub Actions workflow** that: - -- β Runs automatically every Sunday at midnight UTC -- β Can be manually triggered with custom version selection -- β Builds man pages in a Rocky Linux container -- β Automatically deploys to GitHub Pages -- β Artifacts available for download - -### Setup Instructions - -1. **Enable GitHub Pages** - - Go to your repository β Settings β Pages - - Set source to **"GitHub Actions"** - - Save - -2. **Trigger the workflow** - - Go to Actions tab - - Select "Build Rocky Man Pages" - - Click "Run workflow" - - Choose versions (or use default) - -3. **Access your site** - - Will be available at: `https://YOUR_USERNAME.github.io/rocky-man/` - - Updates automatically every week! - -### Workflow File - -Located at `.github/workflows/build.yml`, it: -- Uses Rocky Linux 9 container -- Installs all dependencies -- Runs the build -- Uploads artifacts -- Deploys to GitHub Pages - -## What's Different from the Original - -| Feature | Old Version | New Version | -|---------|-------------|-------------| -| **Architecture** | Single 400-line file | Modular, 16 files across 6 modules | -| **Package Filtering** | Downloads everything | Pre-filters with filelists.xml | -| **Performance** | 2-3 hours, ~10 GB | 30-45 min, ~2-3 GB | -| **UI** | Basic template | Modern GitHub-inspired design | -| **Search** | Simple filter | Fuzzy search with Fuse.js | -| **Container** | Basic Podman commands | Multi-stage Dockerfile + compose | -| **Thread Safety** | Global dict issues | Proper locking mechanisms | -| **Cleanup** | Method exists but unused | Automatic, configurable | -| **Documentation** | Minimal comments | Comprehensive docstrings | -| **Type Hints** | None | Throughout codebase | -| **Error Handling** | Basic try/catch | Comprehensive with logging | -| **CI/CD** | None | GitHub Actions ready | -| **Testing** | None | Ready for pytest integration | -| **Configuration** | Hardcoded | Config class with defaults | - -## Project Structure Details - -``` -rocky-man/ -βββ src/rocky_man/ # Main source code -β βββ __init__.py # Package initialization -β βββ main.py # Entry point and orchestration (200 lines) -β βββ models/ # Data models -β β βββ __init__.py -β β βββ package.py # Package model with properties -β β βββ manfile.py # ManFile model with path parsing -β βββ repo/ # Repository operations -β β βββ __init__.py -β β βββ manager.py # DNF integration, downloads -β β βββ contents.py # Filelists parser (key optimization) -β βββ processor/ # Processing pipeline -β β βββ __init__.py -β β βββ extractor.py # RPM extraction with rpmfile -β β βββ converter.py # mandoc conversion wrapper -β βββ web/ # Web generation -β β βββ __init__.py -β β βββ generator.py # Template rendering, search index -β βββ utils/ # Utilities -β βββ __init__.py -β βββ config.py # Configuration management -βββ templates/ # Jinja2 templates -β βββ base.html # Base layout (modern dark theme) -β βββ index.html # Search page (Fuse.js integration) -β βββ manpage.html # Man page display -β βββ root.html # Multi-version landing -βββ old/ # Your original code (preserved) -β βββ rocky_man.py -β βββ rocky_man2.py -β βββ templates/ -βββ .github/ -β βββ workflows/ -β βββ build.yml # GitHub Actions workflow -βββ Dockerfile # Multi-stage build -βββ .dockerignore # Optimize Docker context -βββ docker-compose.yml # Dev environment -βββ pyproject.toml # Python project config -βββ .gitignore # Updated for new structure -βββ README.md # This file! -``` - -## Development - -### Adding New Features - -The modular design makes it easy to extend: - -- **New repositories**: Add to `config.repo_types` in `utils/config.py` -- **Custom templates**: Use `--template-dir` flag or modify `templates/` -- **Additional metadata**: Extend `Package` or `ManFile` models -- **Alternative converters**: Implement new converter in `processor/` -- **Different outputs**: Add new generator in `web/` - -### Running Tests - -```bash -# Install dev dependencies -pip3 install -e ".[dev]" - -# Run tests (when implemented) -pytest - -# Type checking -mypy src/ - -# Linting -ruff check src/ -``` - -### Development Workflow - -```bash -# 1. Make changes to code -vim src/rocky_man/processor/converter.py - -# 2. Test locally in container -podman run --rm -it -v $(pwd):/app rockylinux:9 /bin/bash -cd /app -python3 -m rocky_man.main --versions 9.6 --verbose - -# 3. Build Docker image -docker build -t rocky-man . - -# 4. Test Docker image -docker run --rm -v $(pwd)/html:/data/html rocky-man --versions 9.6 - -# 5. Preview output -docker-compose up nginx -# Visit http://localhost:8080 - -# 6. Commit and push -git add . -git commit -m "feat: your feature description" -git push -``` - ## Troubleshooting ### DNF Errors @@ -510,12 +265,6 @@ python -m rocky_man.main --parallel-downloads 2 --parallel-conversions 5 python -m rocky_man.main --mirror https://mirror.example.com/rocky/ ``` -### UTF-8 Decode Errors - -**Problem**: `'utf-8' codec can't decode byte...` - -**Solution**: This is now handled with `errors='replace'` in the new version. The man page will still be processed with replacement characters for invalid UTF-8. - ## Performance Tips 1. **Use closer mirrors** - Significant speed improvement for downloads @@ -547,34 +296,3 @@ Contributions welcome! Please: 5. Commit with clear messages (`git commit -m 'feat: add amazing feature'`) 6. Push to your branch (`git push origin feature/amazing-feature`) 7. Open a Pull Request - -## Acknowledgments - -- Inspired by [debiman](https://github.com/Debian/debiman) for Debian -- Uses [mandoc](https://mandoc.bsd.lv/) for man page conversion -- Search powered by [Fuse.js](https://fusejs.io/) -- Modern UI design inspired by GitHub's dark theme - -## Links - -- [Rocky Linux](https://rockylinux.org/) -- [Man Page Format](https://man7.org/linux/man-pages/) -- [Mandoc Documentation](https://mandoc.bsd.lv/) -- [DNF Documentation](https://dnf.readthedocs.io/) - -## Roadmap - -- [ ] Add pytest test suite -- [ ] Implement incremental updates (checksum-based) -- [ ] Add support for localized man pages (es, fr, etc.) -- [ ] Create redirect system like debiman -- [ ] Add statistics page (most viewed, etc.) -- [ ] Implement RSS feed for updates -- [ ] Add support for Rocky Linux 10 (when released) -- [ ] Create sitemap.xml for SEO -- [ ] Add dark/light theme toggle -- [ ] Implement caching for faster rebuilds - ---- - -**Made with β€οΈ for the Rocky Linux community** diff --git a/src/rocky_man/main.py b/src/rocky_man/main.py index 97d8e1a..0a230a2 100644 --- a/src/rocky_man/main.py +++ b/src/rocky_man/main.py @@ -2,6 +2,7 @@ import argparse import logging +import re import sys from pathlib import Path @@ -16,16 +17,12 @@ def setup_logging(verbose: bool = False): level = logging.DEBUG if verbose else logging.INFO logging.basicConfig( level=level, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S' + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", ) -def process_version( - config: Config, - version: str, - template_dir: Path -) -> bool: +def process_version(config: Config, version: str, template_dir: Path) -> bool: """Process a single Rocky Linux version. Args: @@ -53,21 +50,18 @@ def process_version( # Use first available architecture (man pages are arch-independent) arch = config.architectures[0] - # Get repository URL - repo_url = config.get_repo_url(version, repo_type, arch) - # Create cache dir for this repo cache_dir = config.download_dir / f".cache/{version}/{repo_type}" try: # Initialize repository manager repo_manager = RepoManager( - repo_url=repo_url, + config=config, version=version, repo_type=repo_type, arch=arch, cache_dir=cache_dir, - download_dir=version_download_dir + download_dir=version_download_dir, ) # List packages (with man pages only) @@ -83,19 +77,19 @@ def process_version( if config.skip_packages: original_count = len(packages) packages = [ - pkg for pkg in packages - if pkg.name not in config.skip_packages + pkg for pkg in packages if pkg.name not in config.skip_packages ] filtered_count = original_count - len(packages) 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") # Download packages logger.info("Downloading packages...") downloaded = repo_manager.download_packages( - packages, - max_workers=config.parallel_downloads + packages, max_workers=config.parallel_downloads ) # Extract man pages @@ -103,11 +97,10 @@ def process_version( extractor = ManPageExtractor( version_extract_dir, skip_sections=config.skip_sections, - skip_languages=config.skip_languages + skip_languages=config.skip_languages, ) man_files = extractor.extract_from_packages( - downloaded, - max_workers=config.parallel_downloads + downloaded, max_workers=config.parallel_downloads ) logger.info(f"Extracted {len(man_files)} man pages") @@ -124,8 +117,7 @@ def process_version( logger.info("Converting man pages to HTML...") converter = ManPageConverter(version_output_dir) converted = converter.convert_many( - man_files_with_content, - max_workers=config.parallel_conversions + man_files_with_content, max_workers=config.parallel_conversions ) all_man_files.extend(converted) @@ -149,11 +141,6 @@ def process_version( logger.error(f"No man pages were successfully processed for version {version}") return False - # Link cross-references between man pages - logger.info("Linking cross-references...") - converter = ManPageConverter(version_output_dir) - converter.link_cross_references(all_man_files) - # Generate web pages logger.info("Generating web pages...") web_gen = WebGenerator(template_dir, config.output_dir) @@ -168,132 +155,154 @@ def process_version( # Generate packages index page 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 logger.info("Generating man page HTML...") for man_file in all_man_files: 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 def main(): """Main entry point.""" parser = argparse.ArgumentParser( - description='Generate HTML documentation for Rocky Linux man pages' + description="Generate HTML documentation for Rocky Linux man pages" ) parser.add_argument( - '--versions', - nargs='+', - default=['8.10', '9.6', '10.0'], - help='Rocky Linux versions to process (default: 8.10 9.6 10.0)' + "--versions", + nargs="+", + default=["8.10", "9.6", "10.0"], + help="Rocky Linux versions to process (default: 8.10 9.6 10.0)", ) parser.add_argument( - '--repo-types', - nargs='+', - default=['BaseOS', 'AppStream'], - help='Repository types to process (default: BaseOS AppStream)' + "--repo-types", + nargs="+", + default=["BaseOS", "AppStream"], + help="Repository types to process (default: BaseOS AppStream)", ) parser.add_argument( - '--output-dir', + "--output-dir", type=Path, - default=Path('./html'), - help='Output directory for HTML files (default: ./html)' + default=Path("./html"), + help="Output directory for HTML files (default: ./html)", ) parser.add_argument( - '--download-dir', + "--download-dir", type=Path, - default=Path('./tmp/downloads'), - help='Directory for downloading packages (default: ./tmp/downloads)' + default=Path("./tmp/downloads"), + help="Directory for downloading packages (default: ./tmp/downloads)", ) parser.add_argument( - '--extract-dir', + "--extract-dir", type=Path, - default=Path('./tmp/extracts'), - help='Directory for extracting man pages (default: ./tmp/extracts)' + default=Path("./tmp/extracts"), + help="Directory for extracting man pages (default: ./tmp/extracts)", ) parser.add_argument( - '--keep-rpms', - action='store_true', - help='Keep downloaded RPM files after processing' + "--keep-rpms", + action="store_true", + help="Keep downloaded RPM files after processing", ) parser.add_argument( - '--keep-extracts', - action='store_true', - help='Keep extracted man files after processing' + "--keep-extracts", + action="store_true", + help="Keep extracted man files after processing", ) parser.add_argument( - '--parallel-downloads', + "--parallel-downloads", type=int, default=5, - help='Number of parallel downloads (default: 5)' + help="Number of parallel downloads (default: 5)", ) parser.add_argument( - '--parallel-conversions', + "--parallel-conversions", type=int, default=10, - help='Number of parallel HTML conversions (default: 10)' + help="Number of parallel HTML conversions (default: 10)", ) parser.add_argument( - '--mirror', - default='http://dl.rockylinux.org/', - help='Rocky Linux mirror URL (default: http://dl.rockylinux.org/)' + "--mirror", + default="http://dl.rockylinux.org/", + help="Rocky Linux mirror URL (default: http://dl.rockylinux.org/)", ) parser.add_argument( - '--template-dir', + "--vault", + action="store_true", + help="Use vault directory instead of pub (vault/rocky instead of pub/rocky)", + ) + + parser.add_argument( + "--existing-versions", + nargs="*", + metavar="VERSION", + help="List of existing versions to include in root index (e.g., 8.10 9.7)", + ) + + parser.add_argument( + "--template-dir", type=Path, - default=Path(__file__).parent.parent.parent / 'templates', - help='Template directory (default: ./templates)' + default=Path(__file__).parent.parent.parent / "templates", + help="Template directory (default: ./templates)", ) parser.add_argument( - '-v', '--verbose', - action='store_true', - help='Enable verbose logging' + "-v", "--verbose", action="store_true", help="Enable verbose logging" ) parser.add_argument( - '--skip-sections', - nargs='*', + "--skip-sections", + nargs="*", 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( - '--skip-packages', - nargs='*', + "--skip-packages", + nargs="*", 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( - '--skip-languages', - action='store_true', + "--skip-languages", + action="store_true", default=None, - help='Skip non-English man pages (default: enabled)' + help="Skip non-English man pages (default: enabled)", ) parser.add_argument( - '--keep-languages', - action='store_true', - help='Keep all languages (disables --skip-languages)' + "--keep-languages", + action="store_true", + help="Keep all languages (disables --skip-languages)", ) parser.add_argument( - '--allow-all-sections', - action='store_true', - help='Include all man sections (overrides --skip-sections)' + "--allow-all-sections", + action="store_true", + help="Include all man sections (overrides --skip-sections)", ) args = parser.parse_args() @@ -309,9 +318,13 @@ def main(): elif args.skip_languages is not None: skip_languages = args.skip_languages + # Determine content directory + content_dir = "vault/rocky" if args.vault else "pub/rocky" + # Create configuration config = Config( base_url=args.mirror, + content_dir=content_dir, versions=args.versions, repo_types=args.repo_types, download_dir=args.download_dir, @@ -324,11 +337,31 @@ def main(): skip_sections=args.skip_sections, skip_packages=args.skip_packages, skip_languages=skip_languages, - allow_all_sections=args.allow_all_sections + allow_all_sections=args.allow_all_sections, ) + # Get existing versions from scan and argument + scanned_versions = [ + d.name + for d in config.output_dir.iterdir() + if d.is_dir() and re.match(r"\d+\.\d+", d.name) + ] + arg_versions = args.existing_versions or [] + + # Sort versions numerically by (major, minor) + def version_key(v): + try: + major, minor = v.split(".") + return (int(major), int(minor)) + except (ValueError, AttributeError): + return (0, 0) + + existing_versions = sorted(set(scanned_versions + arg_versions), key=version_key) + all_versions = sorted(set(existing_versions + config.versions), key=version_key) + logger.info("Rocky Man - Rocky Linux Man Page Generator") - logger.info(f"Versions: {', '.join(config.versions)}") + logger.info(f"Versions to process: {', '.join(config.versions)}") + logger.info(f"All known versions: {', '.join(all_versions)}") logger.info(f"Repositories: {', '.join(config.repo_types)}") logger.info(f"Output directory: {config.output_dir}") @@ -362,7 +395,7 @@ def main(): # Generate root index logger.info("Generating root index page...") web_gen = WebGenerator(args.template_dir, config.output_dir) - web_gen.generate_root_index(processed_versions) + web_gen.generate_root_index(all_versions) logger.info("=" * 60) logger.info("Processing complete!") @@ -373,5 +406,5 @@ def main(): return 0 -if __name__ == '__main__': +if __name__ == "__main__": sys.exit(main()) diff --git a/src/rocky_man/processor/converter.py b/src/rocky_man/processor/converter.py index 5f201e0..6dee0b2 100644 --- a/src/rocky_man/processor/converter.py +++ b/src/rocky_man/processor/converter.py @@ -40,11 +40,7 @@ class ManPageConverter: try: # Run mandoc with no arguments - it will show usage and exit # We just want to verify the command exists, not that it succeeds - subprocess.run( - ['mandoc'], - capture_output=True, - timeout=5 - ) + subprocess.run(["mandoc"], capture_output=True, timeout=5) return True except FileNotFoundError: # mandoc command not found @@ -73,6 +69,31 @@ class ManPageConverter: # Clean up HTML html = self._clean_html(html) + # Check if mandoc output indicates this is a symlink/redirect + # Pattern:
+ This is an alias for {name}({section}). +
+ +- Rocky Linuxβ’ is a trademark of the Rocky Enterprise Software Foundation. -
-- This tool is open source (MIT License). See THIRD-PARTY-LICENSES.md for attributions. + Rocky Linux is a trademark of the Rocky Enterprise Software Foundation.
diff --git a/templates/root.html b/templates/root.html index 29e277a..16ed80f 100644 --- a/templates/root.html +++ b/templates/root.html @@ -1,7 +1,7 @@ {% extends "base.html" %} -{% block header_title %}Rocky Linuxβ’ Man Pages{% endblock %} -{% block header_subtitle %}Man page documentation for Rocky Linuxβ’ packages{% endblock %} +{% block header_title %}Rocky Linux Man Pages{% endblock %} +{% block header_subtitle %}Man page documentation for Rocky Linux packages{% endblock %} {% block extra_css %} .logo-container { @@ -15,9 +15,11 @@ height: auto; } + + .version-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + grid-template-columns: repeat({{ num_columns }}, 1fr); gap: 1.5rem; margin-top: 2rem; } @@ -32,7 +34,7 @@ } .version-grid { - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + grid-template-columns: 1fr; gap: 1rem; } @@ -40,9 +42,21 @@ padding: 1.5rem; } + .version-card.small { + padding: 0.75rem; + } + + .version-card.small { + padding: 0.75rem; + } + .version-number { font-size: 2rem; } + + .version-card.small .version-number { + font-size: 1.5rem; + } } @media (max-width: 480px) { @@ -55,6 +69,10 @@ gap: 1rem; } + .version-card.small { + padding: 0.5rem; + } + .intro { font-size: 0.9rem; } @@ -71,6 +89,15 @@ display: block; } +.version-card.small { + padding: 1rem; + opacity: 0.7; +} + +.version-card.small .version-number { + font-size: 1.8rem; +} + .version-card:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); @@ -128,19 +155,27 @@- Man page documentation for packages in the Rocky Linuxβ’ BaseOS and AppStream repositories. + Man page documentation for packages in the Rocky Linux BaseOS and AppStream repositories.