#!/usr/bin/env python3 """Merge the per-contribution .bib files into one combined_refs.bib. BibTeX errors with "Repeated entry" whenever a cited key is defined in more than one database, which it is 135 times across these 14 files. It resolves such a clash by keeping the *first* definition in \bibliography order; this script does exactly the same, so the rendered bibliography is unchanged while the errors go away. The per-contribution .bib files stay the source of truth -- regenerate with `python3 merge_bibs.py`, do not hand-edit combined_refs.bib. """ import os, re, sys, collections # Paths below are relative to this script, so it works whatever directory the # caller (VS Code's build recipe, say) happens to run it from. HERE = os.path.dirname(os.path.abspath(__file__)) FILES = """ectWS SDGlazek-bibliography kievsky/paper1 capitani/capitani_y deltuva/deltuva madeira/references wu/few_boson_unitarity_Wu/refs mitra/mitra dawid_main Jackura/bibi Mai/NON-INSPIRE Mai/main vanKolck/vankolck Nicholson/LN""".split() OUT = 'combined_refs.bib' def entries(path): """Yield (kind, key, raw) per top-level @entry, matching braces.""" s = open(path, encoding='utf-8', errors='replace').read() out, i = [], 0 for m in re.finditer(r'@(\w+)\s*[{(]\s*([^,\s{}]*)\s*,', s): if m.start() < i: continue j = s.index('{', m.start()) depth, k = 0, j while k < len(s): if s[k] == '{': depth += 1 elif s[k] == '}': depth -= 1 if depth == 0: break k += 1 out.append((m.group(1).lower(), m.group(2), s[m.start():k + 1])) i = k + 1 return out def merge(): seen, order, strings = {}, [], [] dups = collections.defaultdict(list) for f in FILES: for kind, key, raw in entries(f + '.bib'): if kind == 'string': if raw not in strings: strings.append(raw) continue if kind in ('preamble', 'comment') or not key: continue # BibTeX matches cite keys case-insensitively, so Dawid:2023kxu and # dawid:2023kxu are one entry to it and clash; dedupe on that basis. lk = key.lower() if lk in seen: dups[lk].append(f + '.bib') else: seen[lk] = (f + '.bib', raw) order.append(lk) with open(OUT, 'w', encoding='utf-8') as fh: fh.write('%% GENERATED by merge_bibs.py -- do not edit. Edit the per-contribution\n' '%% .bib files listed in that script and re-run it.\n' '%% First-wins merge, reproducing BibTeX\'s own duplicate resolution.\n\n') for s in strings: fh.write(s + '\n') fh.write('\n') for key in order: src, raw = seen[key] fh.write('%% from %s\n%s\n\n' % (src, raw)) print('%d unique entries -> %s' % (len(order), OUT)) print('%d duplicate definitions dropped (%d distinct keys)' % (sum(len(v) for v in dups.values()), len(dups))) if __name__ == '__main__': os.chdir(HERE) merge()