#!/usr/bin/env python3 """ ╔═══════════════════════════════════════════════════════════════════════╗ ║ WEBBPLATSANALYS RAPPORTGENERATOR ║ ║ ║ ║ Analysera webbplatser och generera professionella PDF-rapporter ║ ╚═══════════════════════════════════════════════════════════════════════╝ Usage: python webbanalys.py scan # Run analysis python webbanalys.py generate # Generate PDF report python webbanalys.py run # Scan + generate in one step Requirements (NixOS): nix-shell shell.nix Requirements (pip): pip install -r requirements.txt """ import argparse import json import subprocess import shutil import sys from pathlib import Path from datetime import datetime # Ensure lib is importable sys.path.insert(0, str(Path(__file__).parent)) from lib.analyzer import load_ci_result, load_all_audits from lib.pdf_report import generate_pdf BANNER = """ ╔═══════════════════════════════════════════════════════════════════════╗ ║ WEBBPLATSANALYS RAPPORTGENERATOR ║ ╚═══════════════════════════════════════════════════════════════════════╝ """ def load_config(config_path: Path) -> dict: """Load project configuration from JSON file.""" if config_path.exists(): with open(config_path, "r", encoding="utf-8") as f: return json.load(f) return {} def save_config(config_path: Path, config: dict): """Save project configuration to JSON file.""" with open(config_path, "w", encoding="utf-8") as f: json.dump(config, f, indent=2, ensure_ascii=False) def find_data_files(base_path: Path) -> tuple[Path, Path]: """Find ci-result.json and reports directory.""" search_paths = [ base_path / "rapport", base_path / ".unlighthouse", base_path, ] ci_result = None reports_dir = None for path in search_paths: if not path.exists(): continue candidate = path / "ci-result.json" if candidate.exists(): ci_result = candidate candidate = path / "reports" if candidate.exists() and candidate.is_dir(): reports_dir = candidate if ci_result and reports_dir: break return ci_result, reports_dir def find_logo(base_path: Path) -> Path: """Find logo file in project.""" # Prefer PNG/JPG, then SVG for pattern in ["*logo*.png", "*logo*.jpg", "*.png", "*.jpg"]: for f in base_path.glob(pattern): if f.is_file() and f.suffix.lower() in [".png", ".jpg", ".jpeg"]: return f # Check for SVG (will be converted) for pattern in ["*logo*.svg", "*.svg"]: for f in base_path.glob(pattern): if f.is_file(): return f return None def convert_svg_to_png(svg_path: Path) -> Path: """Convert SVG to PNG for PDF embedding.""" try: import cairosvg png_path = svg_path.with_suffix(".png") if not png_path.exists() or png_path.stat().st_mtime < svg_path.stat().st_mtime: cairosvg.svg2png(url=str(svg_path), write_to=str(png_path), output_width=200) return png_path except ImportError: return None except Exception: return None def cmd_generate(args, base_path: Path): """Generate PDF report.""" config_path = base_path / "webbanalys.json" config = load_config(config_path) # Find data files print("\n🔍 Söker efter analysdata...") ci_result_path, reports_dir = find_data_files(base_path) if not ci_result_path: print("❌ Kunde inte hitta ci-result.json") print(" Kör först: unlighthouse-ci --site ") print(f" Sökte i: {base_path}") sys.exit(1) if not reports_dir: print("❌ Kunde inte hitta reports/ katalog") sys.exit(1) print(f" ✓ Hittade data i: {ci_result_path.parent}") # Determine parameters (CLI > config > default) customer = args.customer or config.get("customer", "") site_url = args.url or config.get("site_url", "") consultant = args.consultant or config.get("consultant", "") # Find logo logo_path = None if args.logo: logo_path = Path(args.logo) elif config.get("logo"): logo_path = base_path / config["logo"] else: logo_path = find_logo(base_path) # Convert SVG to PNG if needed if logo_path and logo_path.exists(): if logo_path.suffix.lower() == ".svg": png_path = convert_svg_to_png(logo_path) if png_path: logo_path = png_path print(f" ✓ Logotyp: {logo_path.name} (konverterad från SVG)") else: print(f" ⚠ Kunde inte konvertera SVG-logotyp") logo_path = None else: print(f" ✓ Logotyp: {logo_path.name}") # Load data print("\n📊 Laddar analysdata...") data = load_ci_result(ci_result_path) # Extract site URL from data if not provided if not site_url: routes = data.get("routes", []) if routes: first_path = routes[0].get("path", "") # Try to construct from common patterns for route in routes[:5]: path = route.get("path", "") if path.startswith("http"): site_url = "/".join(path.split("/")[:3]) break print(f" Webbplats: {site_url or '(okänd)'}") if customer: print(f" Kund: {customer}") # Analyze print("\n🔬 Analyserar sidor...") all_audits, resource_issues, element_issues, page_count = load_all_audits( reports_dir, site_url ) print(f" Sidor analyserade: {page_count}") print(f" Problemtyper: {len(all_audits)}") print(f" Resursproblem: {len(resource_issues)}") print(f" Elementproblem: {len(element_issues)}") # Overall score overall = data["summary"]["score"] * 100 if overall >= 90: score_emoji = "🟢" elif overall >= 50: score_emoji = "🟡" else: score_emoji = "🔴" print(f"\n Övergripande betyg: {score_emoji} {overall:.0f}/100") # Determine output path if args.output: output_path = Path(args.output) else: date_str = datetime.now().strftime("%Y%m%d") site_slug = site_url.replace("https://", "").replace("http://", "").replace("/", "_").replace(".", "_") output_path = base_path / f"rapport_{site_slug}_{date_str}.pdf" # Generate report print(f"\n📝 Genererar rapport...") generate_pdf( output_path=output_path, data=data, all_audits=all_audits, resource_issues=resource_issues, element_issues=element_issues, page_count=page_count, customer_name=customer, site_url=site_url, logo_path=logo_path if logo_path and logo_path.exists() else None, consultant_name=consultant ) file_size = output_path.stat().st_size / 1024 print(f"\n✅ Rapport genererad!") print(f" 📄 {output_path}") print(f" 📦 {file_size:.0f} KB") # Offer to save config if not config and (customer or site_url): print("\n💾 Spara inställningar för framtida rapporter? (y/n): ", end="") try: if input().lower() == "y": new_config = {} if customer: new_config["customer"] = customer if site_url: new_config["site_url"] = site_url if consultant: new_config["consultant"] = consultant if logo_path: new_config["logo"] = str(logo_path.relative_to(base_path)) save_config(config_path, new_config) print(f" ✓ Sparad till {config_path.name}") except (EOFError, KeyboardInterrupt): pass def cmd_init(args, base_path: Path): """Initialize project configuration.""" config_path = base_path / "webbanalys.json" print("\n🔧 Konfigurera projekt") print(" (tryck Enter för att hoppa över)\n") config = load_config(config_path) try: customer = input(f" Kundnamn [{config.get('customer', '')}]: ").strip() if customer: config["customer"] = customer site_url = input(f" Webbplats URL [{config.get('site_url', '')}]: ").strip() if site_url: config["site_url"] = site_url consultant = input(f" Konsultnamn [{config.get('consultant', '')}]: ").strip() if consultant: config["consultant"] = consultant save_config(config_path, config) print(f"\n✅ Konfiguration sparad till {config_path.name}") except (EOFError, KeyboardInterrupt): print("\n Avbrutet.") def cmd_info(args, base_path: Path): """Show project information.""" config_path = base_path / "webbanalys.json" config = load_config(config_path) print("\n📋 Projektinformation") print(f" Mapp: {base_path}") if config: print(f"\n Konfiguration ({config_path.name}):") for key, value in config.items(): print(f" {key}: {value}") else: print(f"\n ⚠ Ingen konfiguration hittad") print(f" Kör: python webbanalys.py init") # Check for data ci_result, reports_dir = find_data_files(base_path) print(f"\n Analysdata:") if ci_result: print(f" ✓ ci-result.json: {ci_result}") else: print(f" ✗ ci-result.json saknas") if reports_dir: report_count = len(list(reports_dir.glob("*/lighthouse.json"))) print(f" ✓ reports/: {report_count} sidor") else: print(f" ✗ reports/ saknas") # Check for logo logo = find_logo(base_path) if logo: print(f" ✓ Logotyp: {logo.name}") def cmd_scan(args, base_path: Path): """Run Unlighthouse scan.""" config_path = base_path / "webbanalys.json" config = load_config(config_path) # Determine URL site_url = args.url or config.get("site_url", "") if not site_url: print("❌ Ingen URL angiven.") print(" Använd: python webbanalys.py scan https://example.com") print(" Eller kör 'python webbanalys.py init' först") sys.exit(1) # Ensure URL has protocol if not site_url.startswith("http"): site_url = "https://" + site_url samples = args.samples or config.get("samples", 3) output_path = base_path / "rapport" print(f"\n🔍 Startar webbanalys...") print(f" URL: {site_url}") print(f" Samples: {samples}") print(f" Output: {output_path}") # Check for pnpm pnpm_path = shutil.which("pnpm") npx_path = shutil.which("npx") if pnpm_path: cmd = [ "pnpm", "dlx", "-p", "@unlighthouse/cli", "unlighthouse-ci", "--site", site_url, "--samples", str(samples), "--output-path", str(output_path), "--reporter", "jsonExpanded" ] elif npx_path: cmd = [ "npx", "-y", "@unlighthouse/cli", "unlighthouse-ci", "--site", site_url, "--samples", str(samples), "--output-path", str(output_path), "--reporter", "jsonExpanded" ] else: print("❌ Varken pnpm eller npx hittades.") print(" Installera Node.js och pnpm/npm först.") sys.exit(1) print(f"\n Kör: {' '.join(cmd)}\n") print("─" * 70) try: result = subprocess.run(cmd, cwd=base_path) print("─" * 70) if result.returncode == 0: print("\n✅ Analys klar!") # Update config with URL if site_url and site_url != config.get("site_url"): config["site_url"] = site_url config["samples"] = samples save_config(config_path, config) return True else: print(f"\n❌ Analys misslyckades (kod {result.returncode})") return False except KeyboardInterrupt: print("\n\n⚠ Avbruten av användaren") return False except Exception as e: print(f"\n❌ Fel: {e}") return False def cmd_run(args, base_path: Path): """Run scan + generate in one step.""" # First scan if cmd_scan(args, base_path): print("\n" + "═" * 70 + "\n") # Then generate cmd_generate(args, base_path) def main(): parser = argparse.ArgumentParser( description="Analysera webbplatser och generera PDF-rapporter", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Kommandon: scan Kör Unlighthouse-analys generate Generera PDF-rapport från befintlig data run Kör analys + generera rapport (allt-i-ett) init Konfigurera projekt info Visa projektinformation Exempel: python webbanalys.py scan https://example.com python webbanalys.py scan -u https://example.com -s 5 python webbanalys.py generate -c "Företag AB" python webbanalys.py run https://example.com -c "Företag AB" """ ) subparsers = parser.add_subparsers(dest="command", help="Kommando") # Scan command scan_parser = subparsers.add_parser("scan", help="Kör Unlighthouse-analys") scan_parser.add_argument("url", nargs="?", help="Webbplats-URL") scan_parser.add_argument("--samples", "-s", type=int, default=3, help="Antal samples per sida (default: 3)") # Generate command gen_parser = subparsers.add_parser("generate", help="Generera PDF-rapport") gen_parser.add_argument("--customer", "-c", help="Kundnamn") gen_parser.add_argument("--url", "-u", help="Webbplats-URL") gen_parser.add_argument("--consultant", help="Konsultnamn") gen_parser.add_argument("--logo", "-l", help="Sökväg till logotyp (PNG/SVG)") gen_parser.add_argument("--output", "-o", help="Utdatafil") # Run command (scan + generate) run_parser = subparsers.add_parser("run", help="Analysera + generera rapport") run_parser.add_argument("url", nargs="?", help="Webbplats-URL") run_parser.add_argument("--samples", "-s", type=int, default=3, help="Antal samples per sida (default: 3)") run_parser.add_argument("--customer", "-c", help="Kundnamn") run_parser.add_argument("--consultant", help="Konsultnamn") run_parser.add_argument("--logo", "-l", help="Sökväg till logotyp (PNG/SVG)") run_parser.add_argument("--output", "-o", help="Utdatafil") # Init command subparsers.add_parser("init", help="Konfigurera projekt") # Info command subparsers.add_parser("info", help="Visa projektinformation") args = parser.parse_args() base_path = Path.cwd() print(BANNER) if args.command == "scan": cmd_scan(args, base_path) elif args.command == "generate": cmd_generate(args, base_path) elif args.command == "run": cmd_run(args, base_path) elif args.command == "init": cmd_init(args, base_path) elif args.command == "info": cmd_info(args, base_path) else: # Default: show help parser.print_help() print("\n💡 Snabbstart:") print(" python webbanalys.py run https://example.com -c 'Företag AB'") if __name__ == "__main__": main()