Files
webbanalys/webbanalys.py
2025-12-13 17:33:36 +01:00

483 lines
16 KiB
Python

#!/usr/bin/env python3
"""
╔═══════════════════════════════════════════════════════════════════════╗
║ WEBBPLATSANALYS RAPPORTGENERATOR ║
║ ║
║ Analysera webbplatser och generera professionella PDF-rapporter ║
╚═══════════════════════════════════════════════════════════════════════╝
Usage:
python webbanalys.py scan <url> # Run analysis
python webbanalys.py generate # Generate PDF report
python webbanalys.py run <url> # 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 <url>")
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()