[docs]defstrip_emojis(text:str)->str:"""Remove emoji characters from text."""returnEMOJI_RE.sub("",text)
[docs]defmerge_bibtex_files(bibfiles:list[Path],merged_path:Path):"""Merge multiple BibTeX files into a single file (earlier takes precedence)."""merged_content=""forbibfileinbibfiles:typer.echo(f"Merging {bibfile}")merged_content+=bibfile.read_text(encoding="utf-8")+"\n"# Strip emojis from merged contentcleaned_content=strip_emojis(merged_content)merged_path.write_text(cleaned_content,encoding="utf-8")typer.echo(f"Merged and cleaned {len(bibfiles)} files into {merged_path}")
[docs]defrun_bibtex_tidy_dedupe(input_bib:Path)->tuple[str,dict[str,str]]:"""Run bibtex-tidy to deduplicate, returning (deduplicated text, old→new mapping)."""cmd=["bibtex-tidy","--duplicates=doi,citation,key","--merge=first","--omit=abstract,note","--remove-empty-fields","--remove-dupe-fields","--escape","--sort-fields","--strip-comments","--modify","--v2",str(input_bib),]typer.echo(f"Running: {' '.join(cmd)}")result=subprocess.run(cmd,capture_output=True,text=True)ifresult.returncode!=0:typer.echo("bibtex-tidy failed:")typer.echo(result.stdout)typer.echo(result.stderr)raiseRuntimeError("bibtex-tidy failed")# Parse duplicate mappings from stderrkey_updates={}forlineinresult.stdout.splitlines():ifm:=DUPLICATE_RE.search(line):old_key,new_key=m.groups()key_updates[old_key]=new_keyreturninput_bib.read_text(),key_updates
[docs]defmerge_and_dedupe(bibfiles:list[Path],output:Path=Path("merged.bib"),mapping:Path=Path("duplicate_keys.json"),):""" Merge multiple BibTeX files and deduplicate using bibtex-tidy. Writes the deduplicated BibTeX to --output and a JSON map of removed duplicate keys to kept keys to --mapping. Earlier files take precedence. Requires `bibtex-tidy` to be installed and on PATH. """withtempfile.NamedTemporaryFile(delete=False,suffix=".bib")astmp:merged_path=Path(tmp.name)tmp.close()try:merge_bibtex_files(bibfiles,merged_path)dedup_text,key_updates=run_bibtex_tidy_dedupe(merged_path)output.write_text(dedup_text,encoding="utf-8")mapping.write_text(json.dumps(key_updates,indent=2))typer.echo(f"Wrote deduplicated bib to {output}")typer.echo(f"Wrote deduplication key map to {mapping}")finally:merged_path.unlink()# Clean up temporary file
[docs]defupdate_citation(duplicate_keys:Path,files:list[Path]):r""" Update LaTeX citation keys using a JSON mapping. Use the mapping produced by `merge-bibs` (old→new keys) to rewrite citation commands (e.g. \cite, \citet) across one or more files. """key_updates=json.loads(duplicate_keys.read_text())cite_pattern=re.compile(r"(\\cite\w*\{([^}]+)\})")defreplace_cite_keys(match):cite_command=match.group(1)keys_str=match.group(2)keys=[k.strip()forkinkeys_str.split(",")]updated_keys=[key_updates.get(k,k)forkinkeysifkey_updates.get(k,k)isnotNone]returnf"{cite_command.split('{')[0]}{{{','.join(updated_keys)}}}"forfileinfiles:withopen(file)asf:updated=[]forlineinf:updated.append(cite_pattern.sub(replace_cite_keys,line))withopen(file,"w")asf:f.writelines(updated)