zotero-addons-scraper/src/zotero_scraper/__main__.py
2025-12-23 17:43:15 +08:00

220 lines
6.0 KiB
Python

"""Command-line interface for Zotero addons scraper."""
import argparse
import sys
from pathlib import Path
from .cache.release_cache import ReleaseCache
from .config.settings import ScraperConfig
from .services.cache_builder import ReleaseCacheBuilder
from .services.cache_scraper import CacheScraper
from .utils.logging import setup_logging
def parse_args() -> argparse.Namespace:
"""Parse command line arguments."""
parser = argparse.ArgumentParser(
description="Scrape Zotero addon information from GitHub"
)
# Mode options
parser.add_argument(
"--build-cache-only",
action="store_true",
help="Only build/update release cache, don't generate output",
)
parser.add_argument(
"--skip-build-cache",
action="store_true",
help="Skip building cache, only generate output from existing cache",
)
# Release cache options
parser.add_argument(
"--release-cache-dir",
type=str,
default="release_cache",
help="Release cache directory (default: release_cache)",
)
parser.add_argument(
"--full-rebuild",
action="store_true",
help="Rebuild cache from scratch, ignoring existing cache",
)
parser.add_argument(
"--parallel",
action="store_true",
help="Build cache in parallel (faster but uses more resources)",
)
parser.add_argument(
"--max-workers",
type=int,
default=4,
help="Max parallel workers for cache building (default: 4)",
)
# GitHub options
parser.add_argument(
"--github_repository",
type=str,
default=None,
help="GitHub repository (owner/repo) for publishing",
)
parser.add_argument(
"--github_token",
type=str,
default=None,
help="GitHub API token",
)
# Input/Output options
parser.add_argument(
"-i",
"--input",
type=str,
default="addons",
help="Input addon config directory (default: addons)",
)
parser.add_argument(
"-o",
"--output",
type=str,
default="addon_infos.json",
help="Output JSON file path (default: addon_infos.json)",
)
# Cache options
parser.add_argument(
"--cache_directory",
type=str,
default="caches",
help="XPI download cache directory (default: caches)",
)
parser.add_argument(
"--runtime_xpi_directory",
type=str,
default="xpis",
help="Runtime XPI directory (default: xpis)",
)
# Fallback options
parser.add_argument(
"--previous_info_urls",
nargs="+",
default=[],
help="URLs to previous addon_infos.json for fallback",
)
# Publish options
parser.add_argument(
"--create_release",
type=lambda x: x.lower() in ("true", "1", "yes"),
default=True,
help="Create GitHub release (default: True)",
)
# Logging
parser.add_argument(
"--log_level",
type=str,
default="INFO",
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
help="Logging level (default: INFO)",
)
return parser.parse_args()
def main() -> int:
"""Main entry point.
Default flow:
1. Build/update release cache (parse new releases)
2. Generate addon_infos.json from cache
3. Publish to GitHub release (if --create_release)
"""
args = parse_args()
# Setup logging
import logging
log_level = getattr(logging, args.log_level.upper())
setup_logging(level=log_level)
logger = logging.getLogger("zotero_scraper")
# Validate arguments
if args.create_release and not args.github_repository:
logger.error("--github_repository is required when --create_release is True")
return 1
# Create configuration
config = ScraperConfig.from_args(args)
# Ensure directories exist
config.cache.cache_dir.mkdir(parents=True, exist_ok=True)
config.cache.runtime_xpi_dir.mkdir(parents=True, exist_ok=True)
# Initialize release cache
release_cache_dir = Path(args.release_cache_dir)
release_cache = ReleaseCache(release_cache_dir)
# Step 1: Build/update cache (unless skipped)
if not args.skip_build_cache:
logger.info("Step 1: Building/updating release cache...")
builder = ReleaseCacheBuilder(config, release_cache)
if config.github.token:
builder.github.get_rate_limit()
if args.parallel:
stats = builder.build_cache_parallel(
full_rebuild=args.full_rebuild,
max_workers=args.max_workers,
)
else:
stats = builder.build_cache(
full_rebuild=args.full_rebuild,
)
logger.info(f"Cache build stats: {stats}")
if args.build_cache_only:
logger.info("Done! (build-cache-only mode)")
return 0
else:
if not release_cache_dir.exists():
logger.error(
f"Release cache directory not found: {release_cache_dir}. "
"Cannot use --skip-build-cache without existing cache."
)
return 1
logger.info("Step 1: Skipped (using existing cache)")
# Step 2: Generate addon_infos.json from cache
logger.info("Step 2: Generating addon_infos.json from cache...")
scraper = CacheScraper(config, release_cache)
if config.github.token:
scraper.github.get_rate_limit()
addon_infos = scraper.scrape_all()
logger.info(f"Generated info for {len(addon_infos)} addons")
# Step 3: Publish to GitHub release (if requested)
if args.create_release and args.github_repository:
logger.info("Step 3: Publishing to GitHub release...")
scraper.publisher.publish(config.output_file)
scraper.publisher.cleanup_caches(keep_count=1)
else:
logger.info("Step 3: Skipped (--create_release is False or no repository)")
logger.info("Done!")
return 0
if __name__ == "__main__":
sys.exit(main())