Skip to content

Commit cf4f5f0

Browse files
author
Gerit Wagner
committed
update
1 parent 91c8496 commit cf4f5f0

1 file changed

Lines changed: 60 additions & 42 deletions

File tree

colrev/packages/toc_sync/src/toc_sync.py

Lines changed: 60 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,7 @@
2121
- If include_forthcoming: true, items missing volume/issue render under a dedicated:
2222
## Forthcoming
2323
section.
24-
- Re-running the script:
25-
- **always refreshes "Forthcoming"** (if enabled)
26-
- **appends only strictly newer numeric issues** than the latest one already in the file
27-
- Can also create a new TOC file interactively with `--new-toc`, and **immediately updates it**.
24+
- Can also create a new TOC file interactively with `--new-toc`, and immediately updates it.
2825
2926
Requirements
3027
------------
@@ -35,17 +32,17 @@
3532
Usage
3633
-----
3734
# Update/append TOCs in all *.md files with valid front matter
38-
python md_toc_generator.py
35+
toc-sync
3936
4037
# Limit to a file/glob
41-
python md_toc_generator.py --only journal_toc.md
42-
python md_toc_generator.py --only "toc-*.md"
38+
toc-sync --only journal_toc.md
39+
toc-sync --only "toc-*.md"
4340
44-
# Force a full rewrite (not just append newer issues)
45-
python md_toc_generator.py --rewrite
41+
# Force a full rewrite (not just incremental insert at top)
42+
toc-sync --rewrite
4643
4744
# Create a new TOC interactively (ISSN lookup via Crossref) and update it
48-
python md_toc_generator.py --new-toc \
45+
toc-sync --new-toc \
4946
--out toc-mis-quarterly.md \
5047
--new-pdfs-dir "/home/user/papers" \
5148
--new-format title_author_doi \
@@ -63,12 +60,7 @@
6360
from collections import OrderedDict
6461
from dataclasses import dataclass
6562
from pathlib import Path
66-
from typing import Any
67-
from typing import Dict
68-
from typing import Iterable
69-
from typing import List
70-
from typing import Optional
71-
from typing import Tuple
63+
from typing import Any, Dict, Iterable, List, Optional, Tuple
7264

7365
import colrev.record.record
7466

@@ -259,7 +251,7 @@ def _iter_pairs_desc(
259251

260252

261253
# -----------------------------
262-
# Incremental update (append only NEWER numeric issues)
254+
# Incremental update (insert at TOP after header)
263255
# + always refresh Forthcoming; normalize legacy headings
264256
# -----------------------------
265257

@@ -338,6 +330,22 @@ def _parse_existing_headings(
338330
return present, latest_key, has_forthcoming
339331

340332

333+
def _render_forthcoming_block(records: List[dict], cfg: TocConfig) -> List[str]:
334+
lines = ["## Forthcoming\n\n"]
335+
for d in records:
336+
lines.append(_record_to_md_line(d, cfg) + "\n")
337+
lines.append("\n")
338+
return lines
339+
340+
341+
def _render_issue_block(vol: str, iss: str, items: List[dict], cfg: TocConfig) -> List[str]:
342+
lines = [f"## Volume {vol} - Number {iss}\n\n"]
343+
for d in items:
344+
lines.append(_record_to_md_line(d, cfg) + "\n")
345+
lines.append("\n")
346+
return lines
347+
348+
341349
def _write_forthcoming_section(
342350
f: typing.TextIO, records: List[dict], cfg: TocConfig
343351
) -> None:
@@ -390,6 +398,12 @@ def _write_full_markdown(
390398
def _append_incremental(
391399
grouped: Dict[str, Dict[str, List[dict]]], out_path: Path, cfg: TocConfig
392400
) -> None:
401+
"""
402+
Incremental update that:
403+
- Removes any existing 'Forthcoming' block and re-inserts a fresh one
404+
- Inserts strictly newer issues (vs. latest existing) right AFTER the header
405+
- Keeps existing content intact below the inserted block
406+
"""
393407
if not out_path.exists():
394408
_write_full_markdown(grouped, out_path, cfg)
395409
return
@@ -398,9 +412,12 @@ def _append_incremental(
398412
lines = f.readlines()
399413
lines = _normalize_legacy_forthcoming(lines)
400414

401-
present_pairs, latest_existing, has_forthcoming = _parse_existing_headings(lines)
415+
present_pairs, latest_existing, _has_forthcoming = _parse_existing_headings(lines)
416+
417+
# ---- Build insertion block (to be placed after header)
418+
insertion: List[str] = []
402419

403-
# ---- 1) Always refresh Forthcoming (when configured and available)
420+
# 1) Always refresh Forthcoming (when configured and available)
404421
forthcoming_records = None
405422
if (
406423
cfg.include_forthcoming
@@ -415,25 +432,17 @@ def _append_incremental(
415432
if rng is not None:
416433
start, end = rng
417434
del lines[start:end]
418-
if lines and not lines[-1].endswith("\n"):
419-
lines[-1] += "\n"
420-
421-
# Write back without Forthcoming, then append fresh Forthcoming section
422-
with out_path.open("w", encoding="utf-8") as f:
423-
f.writelines(lines)
424-
_write_forthcoming_section(f, forthcoming_records, cfg)
425-
426-
# Reload lines for step 2
427-
with out_path.open("r", encoding="utf-8") as f:
428-
lines = f.readlines()
435+
# Add fresh forthcoming to insertion block
436+
insertion.extend(_render_forthcoming_block(forthcoming_records, cfg))
429437

430-
# ---- 2) Append strictly newer numbered issues
438+
# 2) Determine strictly newer numbered issues
431439
all_pairs_sorted: List[Tuple[str, str]] = []
432440
for vol, issues in grouped.items():
433441
for iss in issues.keys():
434442
if vol == FORTHCOMING_VOL and iss == FORTHCOMING_ISS:
435-
continue # handled already
443+
continue # forthcoming handled above
436444
all_pairs_sorted.append((vol, iss))
445+
# sort ASC by (vol, iss)
437446
all_pairs_sorted.sort(key=lambda p: _pair_sort_key(*p))
438447

439448
to_add: List[Tuple[str, str]] = []
@@ -445,17 +454,26 @@ def _append_incremental(
445454
if key > latest_existing:
446455
to_add.append((vol, iss))
447456

448-
if not to_add:
457+
# Insert newer issues in NEWEST-FIRST order at the top
458+
to_add.sort(key=lambda p: _pair_sort_key(*p), reverse=True)
459+
460+
for vol, iss in to_add:
461+
insertion.extend(_render_issue_block(vol, iss, grouped[vol][iss], cfg))
462+
463+
# If nothing to insert, nothing to do
464+
if not insertion:
449465
return
450466

451-
with out_path.open("a", encoding="utf-8") as f:
452-
if not lines or not lines[-1].endswith("\n"):
453-
f.write("\n")
454-
for vol, iss in to_add:
455-
f.write(f"## Volume {vol} - Number {iss}\n\n")
456-
for d in grouped[vol][iss]:
457-
f.write(_record_to_md_line(d, cfg) + "\n")
458-
f.write("\n")
467+
# ---- Find header end (first "## " or EOF if no headings yet)
468+
first_h2_idx = next((i for i, ln in enumerate(lines) if ln.startswith("## ")), len(lines))
469+
470+
# Ensure header ends with a blank line
471+
if first_h2_idx == len(lines) or (first_h2_idx > 0 and not lines[first_h2_idx - 1].endswith("\n")):
472+
pass # we'll just splice raw; existing newlines are preserved
473+
474+
new_lines = lines[:first_h2_idx] + insertion + lines[first_h2_idx:]
475+
476+
out_path.write_text("".join(new_lines), encoding="utf-8")
459477

460478

461479
# -----------------------------
@@ -669,7 +687,7 @@ def main() -> None:
669687
parser.add_argument(
670688
"--rewrite",
671689
action="store_true",
672-
help="Rewrite full TOC instead of appending only newer issues",
690+
help="Rewrite full TOC instead of incremental insert-at-top",
673691
)
674692

675693
# New TOC creation options

0 commit comments

Comments
 (0)