Repository: texworld/betterbib (language: python)

Issue:
Using LaTeX accents
Is there a way to configure betterbib to you latex accents instead of unicode characters?
I prefer using latex accents because it requires fewer packages when using the bib file.


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 400fd88..f79b34c 100644
--- a/README.md
+++ b/README.md
@@ -21,13 +21,13 @@ comparing them with online sources and correcting whatever entries are faulty.
 
 Install with
 
-```
+```sh
 pip install betterbib
 ```
 
 and run simply run
 
-```
+```sh
 betterbib update in.bib   # or short `betterbib up`
 ```
 
diff --git a/setup.cfg b/setup.cfg
index 7bbc4c3..9ca482d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = betterbib
-version = 4.0.5
+version = 4.0.6
 author = Nico Schlömer
 author_email = nico.schloemer@gmail.com
 description = Better BibTeX data
diff --git a/src/betterbib/__init__.py b/src/betterbib/__init__.py
index ea0a599..e258bad 100644
--- a/src/betterbib/__init__.py
+++ b/src/betterbib/__init__.py
@@ -8,11 +8,11 @@
 from .tools import (
     create_dict,
     decode,
+    merge,
     pybtex_to_bibtex_string,
     pybtex_to_dict,
     to_string,
     translate_month,
-    update,
 )
 
 __all__ = [
@@ -24,7 +24,7 @@
     "pybtex_to_dict",
     "pybtex_to_bibtex_string",
     "to_string",
-    "update",
+    "merge",
     "translate_month",
     "Crossref",
     "Dblp",
diff --git a/src/betterbib/cli/_update.py b/src/betterbib/cli/_update.py
index e277b96..9ba051d 100644
--- a/src/betterbib/cli/_update.py
+++ b/src/betterbib/cli/_update.py
@@ -27,7 +27,12 @@ def run(args):
         d = sanitize_title(d)
         d = journal_abbrev(d, args.long_journal_names, args.extra_abbrev_file)
 
-        string = to_string(d, args.delimiter_type, tab_indent=args.tab_indent)
+        string = to_string(
+            d,
+            args.delimiter_type,
+            tab_indent=args.tab_indent,
+            unicode=not args.latex_output,
+        )
 
         write(string, infile if args.in_place else None)
 
@@ -63,4 +68,11 @@ def add_args(parser):
         metavar="N",
         help="number of concurrent HTTPS requests (default: 10)",
     )
+    parser.add_argument(
+        "-a",
+        "--latex-output",
+        action="store_true",
+        default=False,
+        help="force LaTeX output (default: unicode)",
+    )
     return parser
diff --git a/src/betterbib/crossref.py b/src/betterbib/crossref.py
index 0936b07..9363687 100644
--- a/src/betterbib/crossref.py
+++ b/src/betterbib/crossref.py
@@ -131,7 +131,7 @@ def get_by_doi(self, doi):
         result = data["message"]
         return self._crossref_to_pybtex(result)
 
-    def find_unique(self, entry):
+    def find_unique(self, entry: pybtex.database.Entry) -> pybtex.database.Entry:
         d = pybtex_to_dict(entry)
 
         if "title" not in d and "doi" not in d and "author" not in d:
@@ -163,11 +163,15 @@ def find_unique(self, entry):
             pass
 
         # kick out empty strings
-        L = filter(None, L)
+        # list() is actually no necessary, but not using it here and print()ing
+        # the "naked" filter exhausts the filter and leads to errors
+        # downstream. Has happened more than once when debugging.
+        L = list(filter(None, L))
 
         # Simply plug the dict together to a search query. Typical query:
         # https://api.crossref.org/works?query=vanroose+schl%C3%B6mer&rows=5
         translator = LatexNodes2Text()
+
         payload = translator.latex_to_text(" ".join(L)).replace(" ", "+")
 
         params = {"query": payload, "rows": 2}  # max number of results
@@ -199,7 +203,6 @@ def find_unique(self, entry):
                 results,
             )
         )
-
         if not cleaned_results:
             raise NotFoundError("No match of proper type")
 
diff --git a/src/betterbib/sync.py b/src/betterbib/sync.py
index 0518b3f..b7602a2 100644
--- a/src/betterbib/sync.py
+++ b/src/betterbib/sync.py
@@ -1,13 +1,21 @@
-import concurrent.futures
+from __future__ import annotations
 
+from concurrent.futures import ThreadPoolExecutor, as_completed
+
+import pybtex
+import pybtex.database
 from rich.progress import track
 
 from . import crossref, dblp, errors, tools
 
 
 def sync(
-    d: dict, source: str, long_journal_name: bool, max_workers: int, verbose: bool
-):
+    d: dict[str, pybtex.database.Entry],
+    source: str,
+    long_journal_name: bool,
+    max_workers: int,
+    verbose: bool,
+) -> dict[str, pybtex.database.Entry]:
     """
     Sync a bibtex dict with an external source
 
@@ -29,13 +37,13 @@ def sync(
         src = dblp.Dblp()
 
     num_success = 0
-    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
+    with ThreadPoolExecutor(max_workers=max_workers) as executor:
         responses = {
             executor.submit(src.find_unique, entry): (bib_id, tools.decode(entry))
             for bib_id, entry in d.items()
         }
         for future in track(
-            concurrent.futures.as_completed(responses),
+            as_completed(responses),
             total=len(responses),
             description="Syncing...",
             disable=not verbose,
@@ -49,7 +57,8 @@ def sync(
                 print(e.args[0])
             else:
                 num_success += 1
-                d[bib_id] = tools.update(entry, data)
+                d[bib_id] = tools.merge(entry, data)
+
     if verbose:
         print(f"\n\nTotal number of entries: {len(d)}")
         print(f"Found: {num_success}")
diff --git a/src/betterbib/tools.py b/src/betterbib/tools.py
index c2dfe45..af9d6b6 100644
--- a/src/betterbib/tools.py
+++ b/src/betterbib/tools.py
@@ -10,9 +10,10 @@
 
 import appdirs
 import enchant
-import requests
 
 # for enhanced error messages when parsing
+import pybtex.database
+import requests
 from pybtex.database.input import bibtex
 from pylatexenc.latex2text import LatexNodes2Text
 from pylatexenc.latexencode import unicode_to_latex
@@ -41,14 +42,15 @@ def pybtex_to_dict(entry):
     """dict representation of BibTeX entry."""
     d = {}
     d["genre"] = entry.type
+    transform = unicode_to_latex
     for key, persons in entry.persons.items():
         d[key.lower()] = [
             {
-                "first": p.first_names,
-                "middle": p.middle_names,
-                "prelast": p.prelast_names,
-                "last": p.last_names,
-                "lineage": p.lineage_names,
+                "first": [transform(string) for string in p.first_names],
+                "middle": [transform(string) for string in p.middle_names],
+                "prelast": [transform(string) for string in p.prelast_names],
+                "last": [transform(string) for string in p.last_names],
+                "lineage": [transform(string) for string in p.lineage_names],
             }
             for p in persons
         ]
@@ -195,6 +197,7 @@ def pybtex_to_bibtex_string(
     delimiters: tuple[str, str] = ("{", "}"),
     indent: str = " ",
     sort: bool = False,
+    unicode: bool = True,
 ):
     """String representation of BibTeX entry."""
     out = f"@{entry.type}{{{bibtex_key},\n{indent}"
@@ -204,6 +207,8 @@ def pybtex_to_bibtex_string(
 
     for key, persons in entry.persons.items():
         persons_str = " and ".join([_get_person_str(p) for p in persons])
+        if not unicode:
+            persons_str = unicode_to_latex(persons_str)
         content.append(f"{key.lower()} = {left}{persons_str}{right}")
 
     keys = entry.fields.keys()
@@ -223,12 +228,13 @@ def pybtex_to_bibtex_string(
         try:
             if key not in ["url", "doi"]:
                 # Parse the original value to get a unified version
-                value = unicode_to_latex(translator.latex_to_text(value))
-                # Replace doubled spaces
-                value = value.replace("  ", " ")
+                value = translator.latex_to_text(value)
+                # back to latex to escape "&" etc.
+                value = unicode_to_latex(value)
+                # Replace multiple spaces by one
+                value = re.sub(" +", " ", value)
                 # Remove trailing spaces
                 value = value.rstrip()
-
         except TypeError:
             # expected unicode for encode input, but got int instead
             pass
@@ -384,7 +390,13 @@ def heuristic_unique_result(results, d):
 
 # This used to be a write() function, but beware of exceptions! Files would get
 # unintentionally overridden, see <https://github.com/nschloe/betterbib/issues/184>
-def to_string(od: dict, delimiter_type: str, tab_indent: bool, preamble: list = []):
+def to_string(
+    od: dict[str, pybtex.database.Entry],
+    delimiter_type: str,
+    tab_indent: bool,
+    preamble: list = [],
+    unicode: bool = True,
+) -> str:
     """
     Creates a string representing the bib entries
 
@@ -417,6 +429,7 @@ def to_string(od: dict, delimiter_type: str, tab_indent: bool, preamble: list =
                 bib_id,
                 delimiters=delimiters,
                 indent="\t" if tab_indent else " ",
+                unicode=unicode,
             )
             for bib_id, d in od.items()
         ]
@@ -424,7 +437,7 @@ def to_string(od: dict, delimiter_type: str, tab_indent: bool, preamble: list =
     return "\n\n".join(segments) + "\n"
 
 
-def update(entry1, entry2):
+def merge(entry1, entry2):
     """Create a merged BibTeX entry with the data from entry2 taking precedence."""
     out = entry1
     if entry2 is not None:


Test patch:
diff --git a/tests/test_tools.py b/tests/test_tools.py
index cd5a886..f582696 100644
--- a/tests/test_tools.py
+++ b/tests/test_tools.py
@@ -11,7 +11,7 @@
 data_file_exists = Path(this_dir / "../src/betterbib/data/journals.json").is_file()
 
 
-def test_update():
+def test_merge():
     entry1 = pybtex.database.Entry(
         "article",
         fields={"title": "Yes", "year": 2000},
@@ -28,7 +28,7 @@ def test_update():
         persons={"author": [pybtex.database.Person("Doe, John")]},
     )
 
-    merged = betterbib.update(entry1, entry2)
+    merged = betterbib.merge(entry1, entry2)
 
     assert betterbib.pybtex_to_bibtex_string(
         merged, "key", sort=True