#!/usr/bin/env python3 """ Generate Hugo content pages from data files. Each page has a minimal front matter block (for Hugo taxonomy / metadata) plus a rendered Markdown body so generic themes display content without custom templates. Reads: site/data/venues.yaml master venue list site/data/rankings/icore.csv ICORE conference ranks site/data/rankings/scimago.csv SCImago journal ranks (optional) site/data/deadlines.yaml deadline data site/data/papers/--candidates.yaml paper pools site/data/papers/--digest.yaml agent-curated selections Writes: site/content/venues/conferences//_index.md site/content/venues/journals//_index.md site/content/calendar/_index.md site/content/digests/-/index.md PRESERVED_VENUE_FIELDS are never overwritten once set (manual edits survive). """ import argparse import csv import re import sys from datetime import datetime, timedelta, timezone from pathlib import Path import yaml PRESERVED_VENUE_FIELDS = {"notes", "deadline_source"} # Fields removed from existing front matter on every regeneration: # - "url" reserved by Hugo (overrides page URL) # - "papers" moved from front matter to rendered markdown body _STRIP_FROM_EXISTING = {"url", "papers"} # --------------------------------------------------------------------------- # Front matter I/O # --------------------------------------------------------------------------- def read_hugo_file(path: Path) -> tuple[dict, str]: if not path.exists(): return {}, "" text = path.read_text(encoding="utf-8") if text.startswith("---"): parts = text.split("---", 2) if len(parts) == 3: return yaml.safe_load(parts[1]) or {}, parts[2] return {}, text def write_hugo_file(path: Path, fm: dict, body: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) fm_str = yaml.dump(fm, allow_unicode=True, sort_keys=False, default_flow_style=False) path.write_text(f"---\n{fm_str}---\n{body}", encoding="utf-8") def merge_fm(existing: dict, new_data: dict, preserved: set[str]) -> dict: result = {k: v for k, v in existing.items() if k not in _STRIP_FROM_EXISTING} for key, value in new_data.items(): if key in preserved and result.get(key): continue result[key] = value return result # --------------------------------------------------------------------------- # Data loaders # --------------------------------------------------------------------------- def load_icore(path: str) -> dict[str, str]: p = Path(path) if not p.exists(): return {} ranks: dict[str, str] = {} with open(p, newline="", encoding="utf-8") as f: sample = f.read(4096); f.seek(0) sep = ";" if sample.count(";") > sample.count(",") else "," for row in csv.DictReader(f, delimiter=sep): acronym = (row.get("Acronym") or row.get("acronym") or "").strip() rank = (row.get("Rank") or row.get("rank") or row.get("CORE Rank") or "").strip() if acronym: ranks[acronym.upper()] = rank return ranks def load_scimago(path: str) -> dict[str, dict]: p = Path(path) if not p.exists(): return {} journals: dict[str, dict] = {} with open(p, newline="", encoding="utf-8") as f: sample = f.read(4096); f.seek(0) sep = ";" if sample.count(";") > sample.count(",") else "," for row in csv.DictReader(f, delimiter=sep): title = (row.get("Title") or row.get("title") or "").strip().lower() if title: journals[title] = { "scimago_quartile": (row.get("SJR Best Quartile") or row.get("Best Quartile") or "").strip(), "scimago_sjr": (row.get("SJR") or "").strip(), "scimago_h_index": (row.get("H index") or row.get("H Index") or "").strip(), } return journals def scimago_lookup(data: dict[str, dict], full_name: str, issn: str) -> dict: entry = data.get(full_name.lower()) if entry: return entry needle = full_name.lower() for title, entry in data.items(): if needle in title or title in needle: return entry return {} def load_deadlines(path: str) -> dict[str, dict]: p = Path(path) if not p.exists(): return {} with open(p) as f: data = yaml.safe_load(f) or {} return data.get("deadlines", {}) def load_venue_digests(papers_dir: Path) -> dict[str, list[dict]]: """Return {acronym: [digest_info_dict, ...]} sorted newest-first.""" result: dict[str, list[dict]] = {} if not papers_dir.exists(): return result for f in sorted(papers_dir.glob("*-digest.yaml"), reverse=True): with open(f, encoding="utf-8") as fp: d = yaml.safe_load(fp) or {} venue = d.get("venue", "") year = d.get("year") if venue and year: result.setdefault(venue, []).append({ "year": int(year), "slug": f"{venue}-{year}", "paper_count": len(d.get("selected") or d.get("papers") or []), "tags": d.get("tags", []), "date": str(d.get("date", f"{year}-01-01")), }) return result # --------------------------------------------------------------------------- # Markdown body renderers # --------------------------------------------------------------------------- def _conf_body(fm: dict, digests: list[dict] | None = None) -> str: rank = fm.get("icore_rank") or "—" hp = fm.get("homepage") or "" dl = fm.get("next_deadline") or "TBD" abstract_dl = fm.get("abstract_deadline") or "" notification = fm.get("notification") or "" camera_ready = fm.get("camera_ready") or "" event = fm.get("next_event") or "—" location = fm.get("location") or "" cfp_url = fm.get("cfp_url") or "" domains = ", ".join(f"`{d}`" for d in (fm.get("domain") or [])) notes = fm.get("notes") or "" src = fm.get("deadline_source") or "" title = fm.get("title", "") lines = [f"\n*{fm.get('full_name', '')}*\n"] lines += [ "| | |", "|---|---|", f"| **ICORE Rank** | **{rank}** |", f"| **Domains** | {domains} |", ] if hp: lines.append(f"| **Website** | [{hp}]({hp}) |") lines += [ "\n## Upcoming Deadline\n", "| | |", "|---|---|", ] if abstract_dl and abstract_dl != dl: lines.append(f"| **Abstract deadline** | {abstract_dl} |") lines.append(f"| **Paper deadline** | {dl} |") if notification: lines.append(f"| **Notification** | {notification} |") if camera_ready: lines.append(f"| **Camera-ready** | {camera_ready} |") lines.append(f"| **Event dates** | {event} |") if location: lines.append(f"| **Location** | {location} |") if cfp_url: lines.append(f"| **CFP** | [{cfp_url}]({cfp_url}) |") if src: lines.append(f"| **Source** | `{src}` |") if digests: lines += ["\n## Digests\n"] for d in sorted(digests, key=lambda x: x["year"], reverse=True): slug = d["slug"].lower() year = d["year"] count = d.get("paper_count", 0) suffix = f"— {count} papers" if count else "*(digest pending)*" lines.append(f"- [{title} {year}](/digests/{slug}/) {suffix}") if notes: lines += ["\n## Notes\n", notes] return "\n".join(lines) + "\n" def _journal_body(fm: dict, digests: list[dict] | None = None) -> str: q = fm.get("scimago_quartile") or "—" sjr = fm.get("scimago_sjr") or "—" h = fm.get("scimago_h_index") or "—" issn = fm.get("issn") or "—" hp = fm.get("homepage") or "" model = (fm.get("submission_model") or "rolling").title() domains = ", ".join(f"`{d}`" for d in (fm.get("domain") or [])) notes = fm.get("notes") or "" title = fm.get("title", "") lines = [f"\n*{fm.get('full_name', '')}*\n"] lines += [ "| | |", "|---|---|", f"| **SCImago Quartile** | **{q}** |", f"| **SJR** | {sjr} |", f"| **H-Index** | {h} |", f"| **ISSN** | {issn} |", f"| **Submission model** | {model} |", f"| **Domains** | {domains} |", ] if hp: lines.append(f"| **Website** | [{hp}]({hp}) |") if digests: lines += ["\n## Digests\n"] for d in sorted(digests, key=lambda x: x["year"], reverse=True): slug = d["slug"].lower() year = d["year"] count = d.get("paper_count", 0) suffix = f"— {count} papers" if count else "*(digest pending)*" lines.append(f"- [{title} {year}](/digests/{slug}/) {suffix}") if notes: lines += ["\n## Notes\n", notes] return "\n".join(lines) + "\n" # --------------------------------------------------------------------------- # Date helpers # --------------------------------------------------------------------------- def _parse_date(s: str) -> str | None: """'Apr 1, 2026' → '2026-04-01', or None if unparseable.""" for fmt in ("%b %d, %Y", "%B %d, %Y"): try: return datetime.strptime(s.strip(), fmt).strftime("%Y-%m-%d") except ValueError: pass return None def _parse_date_range(s: str) -> tuple[str | None, str | None]: """Return (start_iso, end_iso_exclusive) from a date-range string. Handles: 'Apr 13, 2026 - Apr 16, 2026' → ('2026-04-13', '2026-04-17') 'Nov 17-19, 2026' → ('2026-11-17', '2026-11-20') 'Apr 1, 2026' → ('2026-04-01', None) """ s = s.strip() if " - " in s: left, right = s.split(" - ", 1) start = _parse_date(left) end_s = _parse_date(right) if start and end_s: end_dt = datetime.strptime(end_s, "%Y-%m-%d") + timedelta(days=1) return start, end_dt.strftime("%Y-%m-%d") m = re.match(r"(\w+)\s+(\d+)-(\d+),\s+(\d{4})", s) if m: month, d1, d2, year = m.groups() start = _parse_date(f"{month} {d1}, {year}") try: end_dt = datetime.strptime(f"{month} {d2}, {year}", "%b %d, %Y") + timedelta(days=1) if start: return start, end_dt.strftime("%Y-%m-%d") except ValueError: pass return _parse_date(s), None # --------------------------------------------------------------------------- # Venue section index body renderers # --------------------------------------------------------------------------- _ICORE_ORDER = {"A*": 0, "A": 1, "B": 2, "C": 3} _QUARTILE_ORDER = {"Q1": 0, "Q2": 1, "Q3": 2, "Q4": 3} def _venues_body(venues: dict) -> str: n_conf = len(venues.get("conferences") or []) n_jour = len(venues.get("journals") or []) return ( f"\nTracking **{n_conf} conferences** and **{n_jour} journals** for this domain.\n\n" "- **[Conferences →](/venues/conferences/)** ranked by ICORE (A*, A, B, C)\n" "- **[Journals →](/venues/journals/)** ranked by SCImago quartile (Q1–Q4)\n" ) def _conferences_section_body(venues: dict, icore: dict, deadlines: dict) -> str: rows = [] for conf in venues.get("conferences") or []: acronym = conf.get("acronym", "") rank = icore.get(acronym.upper(), "—") or "—" dl = deadlines.get(acronym, {}) abstract = dl.get("abstract_deadline") or "" paper = dl.get("submission_deadline") or dl.get("abstract_deadline") or "TBD" notif = dl.get("notification") or "" event = dl.get("event_dates") or "—" location = dl.get("location") or "" rows.append((_ICORE_ORDER.get(rank, 99), acronym, conf.get("full_name", ""), rank, abstract, paper, notif, event, location)) rows.sort(key=lambda r: (r[0], r[1])) lines = [ "\n| Conference | Full name | ICORE | Abstract | Paper deadline | Notification | Event | Location |", "|---|---|---|---|---|---|---|---|", ] for _, acronym, full_name, rank, abstract, paper, notif, event, location in rows: link = f"[{acronym}](/venues/conferences/{acronym.lower()}/)" abs_cell = abstract if (abstract and abstract != paper) else "" lines.append(f"| {link} | {full_name} | **{rank}** | {abs_cell} | {paper} | {notif} | {event} | {location} |") return "\n".join(lines) + "\n" def _journals_section_body(venues: dict) -> str: rows = [] for j in venues.get("journals") or []: acronym = j.get("acronym", "") q = j.get("scimago_quartile") or "—" sjr = j.get("scimago_sjr") or "—" h = j.get("scimago_h_index") or "—" model = (j.get("submission_model") or "rolling").title() domains = ", ".join(f"`{d}`" for d in j.get("domain", [])) rows.append((_QUARTILE_ORDER.get(q, 99), acronym, j.get("full_name", ""), q, sjr, h, model, domains)) rows.sort(key=lambda r: (r[0], r[1])) lines = [ "\n| Journal | Full name | SCImago | SJR | H-Index | Model | Domains |", "|---|---|---|---|---|---|---|", ] for _, acronym, full_name, q, sjr, h, model, domains in rows: link = f"[{acronym}](/venues/journals/{acronym.lower()}/)" lines.append(f"| {link} | {full_name} | **{q}** | {sjr} | {h} | {model} | {domains} |") return "\n".join(lines) + "\n" # --------------------------------------------------------------------------- # Calendar events builder (for FullCalendar) # --------------------------------------------------------------------------- def _calendar_events(venues: dict, deadlines: dict) -> list[dict]: events: list[dict] = [] for conf in venues.get("conferences") or []: acronym = conf.get("acronym", "") dl = deadlines.get(acronym, {}) url = f"/venues/conferences/{acronym.lower()}/" location = dl.get("location") or "" cfp_url = dl.get("cfp_url") or "" for field, label, color in [ ("abstract_deadline", "abstract deadline", "#f4a261"), ("submission_deadline", "paper deadline", "#e63946"), ("notification", "notification", "#2a9d8f"), ]: iso = _parse_date(dl.get(field) or "") if iso: ev: dict = {"title": f"{acronym} — {label}", "start": iso, "url": url, "color": color, "allDay": True} props: dict = {} if location: props["location"] = location if cfp_url: props["cfp_url"] = cfp_url if props: ev["extendedProps"] = props events.append(ev) start, end = _parse_date_range(dl.get("event_dates") or "") if start: ev = {"title": acronym, "start": start, "url": url, "color": "#457b9d", "allDay": True} if end: ev["end"] = end if location: ev["extendedProps"] = {"location": location} events.append(ev) return events def _calendar_body(venues: dict, icore: dict, deadlines: dict) -> str: def parse_deadline(s: str): for fmt in ("%b %d, %Y", "%b %d %Y"): try: return datetime.strptime(s.strip(), fmt) except ValueError: pass return datetime.max rows = [] for conf in venues.get("conferences") or []: acronym = conf.get("acronym", "") rank = icore.get(acronym.upper(), "—") or "—" dl_data = deadlines.get(acronym, {}) abstract = dl_data.get("abstract_deadline") or "" paper = dl_data.get("submission_deadline") or dl_data.get("abstract_deadline") or "TBD" notif = dl_data.get("notification") or "" event = dl_data.get("event_dates") or "—" location = dl_data.get("location") or "" rows.append((acronym, abstract, paper, notif, event, location, rank)) rows.sort(key=lambda r: (parse_deadline(r[2]), r[0])) lines = [ "\n| Conference | Abstract | Paper deadline | Notification | Event dates | Location | ICORE |", "|---|---|---|---|---|---|---|", ] for acronym, abstract, paper, notif, event, location, rank in rows: abs_cell = abstract if (abstract and abstract != paper) else "" lines.append( f"| [{acronym}](/venues/conferences/{acronym.lower()}/) " f"| {abs_cell} | {paper} | {notif} | {event} | {location} | {rank} |" ) lines += [ "", "> Deadlines sourced from conference websites and [WikiCFP](http://www.wikicfp.com/).", "", ] return "\n".join(lines) + "\n" def _digest_body(papers: list[dict]) -> str: if not papers: return "\nNo papers selected yet.\n" lines = [f"\n{len(papers)} papers selected.\n"] for p in papers: title = p.get("title") or "Untitled" authors = p.get("authors") or [] author_str = ( ", ".join(authors[:4]) + (" *et al.*" if len(authors) > 4 else "") if authors else "" ) tldr = p.get("tldr") or "" why = p.get("why_notable") or "" link = p.get("url") or (f"https://doi.org/{p['doi']}" if p.get("doi") else "") lines += ["---", f"\n### {title}\n"] if author_str: lines.append(f"*{author_str}*\n") if tldr: lines.append(f"**TL;DR** — {tldr}\n") if why: lines.append(f"**Why notable** — {why}\n") if link: lines.append(f"[→ Read paper]({link})\n") return "\n".join(lines) + "\n" # --------------------------------------------------------------------------- # Content generators # --------------------------------------------------------------------------- def gen_conferences(venues: dict, icore: dict, deadlines: dict, root: Path, venue_digests: dict[str, list[dict]] | None = None) -> int: count = 0 for conf in venues.get("conferences") or []: acronym = conf.get("acronym", "").strip() if not acronym: continue path = root / "venues" / "conferences" / acronym / "_index.md" existing_fm, _ = read_hugo_file(path) new_fm: dict = { "title": acronym, "full_name": conf.get("full_name", ""), "domain": conf.get("domain", []), "type": "conference", "icore_rank": icore.get(acronym.upper(), ""), "homepage": conf.get("url", ""), "dblp_key": conf.get("dblp_key", ""), "draft": False, } dl = deadlines.get(acronym, {}) if dl.get("submission_deadline"): new_fm["next_deadline"] = dl["submission_deadline"] elif dl.get("abstract_deadline"): new_fm["next_deadline"] = dl["abstract_deadline"] if dl.get("abstract_deadline") and dl.get("submission_deadline"): new_fm["abstract_deadline"] = dl["abstract_deadline"] if dl.get("notification"): new_fm["notification"] = dl["notification"] if dl.get("camera_ready"): new_fm["camera_ready"] = dl["camera_ready"] if dl.get("event_dates"): new_fm["next_event"] = dl["event_dates"] if dl.get("location"): new_fm["location"] = dl["location"] if dl.get("cfp_url"): new_fm["cfp_url"] = dl["cfp_url"] if dl.get("wikicfp_event_id"): new_fm["deadline_source"] = f"wikicfp:{dl['wikicfp_event_id']}" fm = merge_fm(existing_fm, new_fm, PRESERVED_VENUE_FIELDS) digests = (venue_digests or {}).get(acronym) write_hugo_file(path, fm, _conf_body(fm, digests)) count += 1 print(f" {path.relative_to(root.parent)}", file=sys.stderr) return count def gen_journals(venues: dict, scimago: dict[str, dict], root: Path, venue_digests: dict[str, list[dict]] | None = None) -> int: count = 0 for journal in venues.get("journals") or []: acronym = journal.get("acronym", "").strip() if not acronym: continue path = root / "venues" / "journals" / acronym / "_index.md" existing_fm, _ = read_hugo_file(path) inline_sjr = { k: journal[k] for k in ("scimago_quartile", "scimago_sjr", "scimago_h_index") if journal.get(k) } sjr_data = inline_sjr or scimago_lookup(scimago, journal.get("full_name", ""), journal.get("issn", "")) new_fm: dict = { "title": acronym, "full_name": journal.get("full_name", ""), "domain": journal.get("domain", []), "type": "journal", "issn": journal.get("issn", ""), "homepage": journal.get("url", ""), "submission_model": journal.get("submission_model", "rolling"), "dblp_key": journal.get("dblp_key", ""), "draft": False, **sjr_data, } fm = merge_fm(existing_fm, new_fm, PRESERVED_VENUE_FIELDS) digests = (venue_digests or {}).get(acronym) write_hugo_file(path, fm, _journal_body(fm, digests)) count += 1 print(f" {path.relative_to(root.parent)}", file=sys.stderr) return count def gen_calendar(venues: dict, icore: dict, deadlines: dict, root: Path) -> None: path = root / "calendar" / "_index.md" existing_fm, _ = read_hugo_file(path) new_fm = { "title": "Submission Deadlines", "layout": "calendar", "generated": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), "events": _calendar_events(venues, deadlines), "draft": False, } fm = merge_fm(existing_fm, new_fm, set()) write_hugo_file(path, fm, _calendar_body(venues, icore, deadlines)) print(f" {path.relative_to(root.parent)}", file=sys.stderr) def gen_section_indexes(venues: dict, icore: dict, deadlines: dict, root: Path) -> None: # venues/_index.md p = root / "venues" / "_index.md" fm = merge_fm(read_hugo_file(p)[0], {"title": "Venues", "draft": False}, set()) write_hugo_file(p, fm, _venues_body(venues)) print(f" {p.relative_to(root.parent)}", file=sys.stderr) # venues/conferences/_index.md p = root / "venues" / "conferences" / "_index.md" n = len(venues.get("conferences") or []) fm = merge_fm(read_hugo_file(p)[0], {"title": "Conferences", "description": f"{n} tracked conferences", "draft": False}, set()) write_hugo_file(p, fm, _conferences_section_body(venues, icore, deadlines)) print(f" {p.relative_to(root.parent)}", file=sys.stderr) # venues/journals/_index.md p = root / "venues" / "journals" / "_index.md" n = len(venues.get("journals") or []) fm = merge_fm(read_hugo_file(p)[0], {"title": "Journals", "description": f"{n} tracked journals", "draft": False}, set()) write_hugo_file(p, fm, _journals_section_body(venues)) print(f" {p.relative_to(root.parent)}", file=sys.stderr) def _upcoming_year(acronym: str, deadlines: dict) -> int: """Extract the year of the upcoming event from deadlines, falling back to current year.""" event_dates = (deadlines.get(acronym) or {}).get("event_dates") or "" m = re.search(r'\b(20\d\d)\b', event_dates) return int(m.group(1)) if m else datetime.now().year def gen_digests(papers_dir: Path, root: Path, venues: dict | None = None, deadlines: dict | None = None) -> int: real_slugs: set[str] = set() count = 0 if papers_dir.exists(): for digest_file in sorted(papers_dir.glob("*-digest.yaml")): with open(digest_file, encoding="utf-8") as f: digest = yaml.safe_load(f) or {} venue = digest.get("venue", "") year = digest.get("year") if not venue or not year: print(f" Skipping {digest_file.name}: missing venue or year", file=sys.stderr) continue candidates: dict[str, dict] = {} candidate_file = papers_dir / f"{venue}-{year}-candidates.yaml" if candidate_file.exists(): with open(candidate_file, encoding="utf-8") as f: pool = yaml.safe_load(f) or {} for p in pool.get("papers", []): key = p.get("dblp_key") or p.get("openalex_id") or p.get("doi") or p.get("title", "") candidates[key] = p items = digest.get("selected") or [] if not items: for p in digest.get("papers") or []: items.append({ "title": p.get("title", ""), "authors": p.get("authors", []), "tldr": p.get("reason", ""), "why_notable": "", }) papers_out = [] for sel in items: key = sel.get("dblp_key") or sel.get("openalex_id") or sel.get("doi") or sel.get("title", "") papers_out.append({**candidates.get(key, {}), **sel}) slug = f"{venue}-{year}" real_slugs.add(slug) path = root / "digests" / slug / "index.md" existing_fm, _ = read_hugo_file(path) new_fm = { "title": f"{venue} {year} Digest", "venue": venue, "year": int(year), "date": str(digest.get("date", f"{year}-01-01")), "tags": digest.get("tags", []), "paper_count": len(papers_out), "draft": False, } fm = merge_fm(existing_fm, new_fm, set()) write_hugo_file(path, fm, _digest_body(papers_out)) count += 1 print(f" {path.relative_to(root.parent)}", file=sys.stderr) # Generate stubs for every venue that has no digest for its upcoming edition if venues and deadlines is not None: all_acronyms = ( [c.get("acronym", "") for c in (venues.get("conferences") or [])] + [j.get("acronym", "") for j in (venues.get("journals") or [])] ) for acronym in all_acronyms: if not acronym: continue year = _upcoming_year(acronym, deadlines) slug = f"{acronym}-{year}" if slug in real_slugs: continue path = root / "digests" / slug / "index.md" if path.exists(): continue # never overwrite existing stubs (may have been customised) new_fm = { "title": f"{acronym} {year} Digest", "venue": acronym, "year": year, "date": f"{year}-01-01", "tags": [], "paper_count": 0, "draft": False, "build": {"list": "never"}, } write_hugo_file(path, new_fm, _digest_body([])) count += 1 print(f" {path.relative_to(root.parent)} (stub)", file=sys.stderr) return count # --------------------------------------------------------------------------- # Entry point # --------------------------------------------------------------------------- def main() -> None: ap = argparse.ArgumentParser(description="Generate Hugo content from data files.") ap.add_argument("--venues", default="site/data/venues.yaml") ap.add_argument("--icore", default="site/data/rankings/icore.csv") ap.add_argument("--scimago", default="site/data/rankings/scimago.csv") ap.add_argument("--deadlines", default="site/data/deadlines.yaml") ap.add_argument("--papers-dir", default="site/data/papers", dest="papers_dir") ap.add_argument("--content", default="site/content") args = ap.parse_args() venues_path = Path(args.venues) if not venues_path.exists(): print(f"Error: {venues_path} not found. Populate site/data/venues.yaml first.", file=sys.stderr) sys.exit(1) with open(venues_path, encoding="utf-8") as f: venues = yaml.safe_load(f) or {} content_root = Path(args.content) papers_dir = Path(args.papers_dir) icore = load_icore(args.icore) scimago = load_scimago(args.scimago) deadlines = load_deadlines(args.deadlines) venue_digests = load_venue_digests(papers_dir) if icore: print(f"Loaded {len(icore)} ICORE entries", file=sys.stderr) if scimago: print(f"Loaded {len(scimago)} SCImago entries", file=sys.stderr) if deadlines: print(f"Loaded deadlines for {len(deadlines)} venues", file=sys.stderr) print("\nGenerating venue section indexes …", file=sys.stderr) gen_section_indexes(venues, icore, deadlines, content_root) print("\nGenerating conference pages …", file=sys.stderr) n_conf = gen_conferences(venues, icore, deadlines, content_root, venue_digests) print("\nGenerating journal pages …", file=sys.stderr) n_jour = gen_journals(venues, scimago, content_root, venue_digests) print("\nGenerating calendar page …", file=sys.stderr) gen_calendar(venues, icore, deadlines, content_root) print("\nGenerating digest pages …", file=sys.stderr) n_dig = gen_digests(papers_dir, content_root, venues, deadlines) print(f"\nDone: {n_conf} conferences, {n_jour} journals, {n_dig} digests", file=sys.stderr) if __name__ == "__main__": main()