Repository: pcubillos/bibmanager

Issue:
ISBN duplicates problem
An edited book has many contributions. Each contribution has its own DOI number, but also shares its parent book's ISBN. Contributions and parent reference are now regarded as duplicates in bibmanager and merge always triggers asking. I have tried to disable exporting ISBN of contributions to bibfile. And this solved the annoyance of asking. But if only focusing on bibfile, this workaround seems lost ISBN information then. Is there a better way to settle this problem down?

Discussion:

Patch:
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 44ed2ef..a3ea45c 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -2372,3 +2372,26 @@ Propagated typo corrections into the docs.
 *****
 
 Updated link of license badge to point to RTD docs. 
+
+
+*****  Sat Jun 12 19:01:21 CEST 2021  *****
+
+In bm.remove_dubplicates() added additional check for DOI after
+identical ISBN values in case the entries refer to different chapters
+of a same book.
+Added respective tests.
+
+*****
+
+Small tweak to docs (put latest tutorial video first).
+
+*****
+
+Bumped bibmanager to version 1.3.4
+
+*****
+
+Added mock_init to test_duplicate_isbn_same_unknown_doi() tests
+because the code needs the config file initialized to set the screen
+output style.
+
diff --git a/bibmanager/VERSION.py b/bibmanager/VERSION.py
index 1d5038e..c3c2aca 100644
--- a/bibmanager/VERSION.py
+++ b/bibmanager/VERSION.py
@@ -2,4 +2,4 @@
 # bibmanager is open-source software under the MIT license (see LICENSE).
 
 # bibmanager Version:
-__version__ = "1.3.3"
+__version__ = "1.3.4"
diff --git a/bibmanager/bib_manager/bib_manager.py b/bibmanager/bib_manager/bib_manager.py
index 20aa2fe..a8a00a3 100644
--- a/bibmanager/bib_manager/bib_manager.py
+++ b/bibmanager/bib_manager/bib_manager.py
@@ -461,6 +461,22 @@ def remove_duplicates(bibs, field):
         if nbibs == 1:
             continue
 
+        # If field is isbn, check doi to differentiate chapters from same book:
+        if field == 'isbn':
+            dois = [
+                bibs[idx].doi if bibs[idx].doi is not None else ""
+                for idx in indices]
+            u_doi, doi_inv, doi_counts = np.unique(
+                dois, return_inverse=True, return_counts=True)
+            doi_multis = np.where((doi_counts > 1) & (ubib != ""))[0]
+            single_dois = u_doi[doi_counts==1]
+            indices = [
+                idx for idx,doi in zip(indices,dois)
+                if doi not in single_dois]
+            nbibs = len(indices)
+            if nbibs <= 1:
+                continue
+
         # Query the user:
         labels = [idx + " ENTRY:\n" for idx in u.ordinal(np.arange(nbibs)+1)]
         display_bibs(labels, [bibs[i] for i in indices])
diff --git a/docs/index.rst b/docs/index.rst
index 3a49ea1..63494ca 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -50,13 +50,13 @@ Check out this video tutorial to get started with ``bibmanager``:
 
 .. raw:: html
 
-    <iframe width="720" height="405" src="https://www.youtube.com/embed/WVmhdwVNXOE" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
+    <iframe width="720" height="405" src="https://www.youtube.com/embed/qewdBx0M8VE" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
 
-And the new features for version 1.3+:
+And this one covering some other features:
 
 .. raw:: html
 
-    <iframe width="720" height="405" src="https://www.youtube.com/embed/qewdBx0M8VE" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
+    <iframe width="720" height="405" src="https://www.youtube.com/embed/WVmhdwVNXOE" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
 
 
 .. _team:


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 501e67a..e6c810b 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -233,20 +233,58 @@ archivePrefix = "arXiv",
   publisher={AIP Publishing}
 }"""
 
+    isbn_doi1 = """
+@INBOOK{2018haex.bookE.116P,
+       author = {{Parmentier}, Vivien and {Crossfield}, Ian J.~M.},
+        title = "{Exoplanet Phase Curves: Observations and Theory}",
+         year = 2018,
+          doi = {10.1007/978-3-319-55333-7\_116},
+         isbn = "978-3-319-55333-7",
+}"""
+
+    isbn_doi2 = """
+@INBOOK{2018haex.bookE.147C,
+       author = {{Cowan}, Nicolas B. and {Fujii}, Yuka},
+        title = "{Mapping Exoplanets}",
+         year = 2018,
+          doi = {10.1007/978-3-319-55333-7\_147},
+         isbn = "978-3-319-55333-7",
+}"""
+
+    isbn_no_doi1 = """
+@INBOOK{2018haex.bookE.116P,
+       author = {{Parmentier}, Vivien and {Crossfield}, Ian J.~M.},
+        title = "{Exoplanet Phase Curves: Observations and Theory}",
+         year = 2018,
+         isbn = "978-3-319-55333-7",
+}"""
+
+    isbn_no_doi2 = """
+@INBOOK{2018haex.bookE.147C,
+       author = {{Cowan}, Nicolas B. and {Fujii}, Yuka},
+        title = "{Mapping Exoplanets}",
+         year = 2018,
+         isbn = "978-3-319-55333-7",
+}"""
+
     data = {
-        'jones_minimal':      jones_minimal,
-        'jones_no_year':      jones_no_year,
-        'jones_no_title':     jones_no_title,
-        'jones_no_author':    jones_no_author,
-        'jones_braces':       jones_braces,
-        'beaulieu_apj':       beaulieu_apj,
-        'beaulieu_arxiv':     beaulieu_arxiv,
+        'jones_minimal': jones_minimal,
+        'jones_no_year': jones_no_year,
+        'jones_no_title': jones_no_title,
+        'jones_no_author': jones_no_author,
+        'jones_braces': jones_braces,
+        'beaulieu_apj': beaulieu_apj,
+        'beaulieu_arxiv': beaulieu_arxiv,
         'beaulieu_arxiv_dup': beaulieu_arxiv_dup,
-        'hunter':             hunter,
-        'oliphant_dup':       oliphant_dup,
-        'no_oliphant':        no_oliphant,
-        'sing':               sing,
-        'stodden':            stodden,
+        'hunter': hunter,
+        'oliphant_dup': oliphant_dup,
+        'no_oliphant': no_oliphant,
+        'sing': sing,
+        'stodden': stodden,
+        'isbn_doi1': isbn_doi1,
+        'isbn_doi2': isbn_doi2,
+        'isbn_no_doi1': isbn_no_doi1,
+        'isbn_no_doi2': isbn_no_doi2,
            }
     return data
 
diff --git a/tests/test_bib_manager.py b/tests/test_bib_manager.py
index 83edf8d..10922b9 100644
--- a/tests/test_bib_manager.py
+++ b/tests/test_bib_manager.py
@@ -801,6 +801,29 @@ def test_merge_duplicate_title_add(bibs, mock_init_sample, mock_input):
     assert bibs['no_oliphant'] in loaded_bibs
 
 
+def test_duplicate_isbn_different_doi(capfd, entries):
+    text = entries['isbn_doi1'] + entries['isbn_doi2']
+    bibs = bm.read_file(text=text)
+    assert len(bibs) == 2
+    captured = capfd.readouterr()
+    assert captured.out == ''
+
+
+def test_duplicate_isbn_doi_vs_no_doi(capfd, entries):
+    text = entries['isbn_doi1'] + entries['isbn_no_doi2']
+    bibs = bm.read_file(text=text)
+    assert len(bibs) == 2
+    captured = capfd.readouterr()
+    assert captured.out == ''
+
+
+@pytest.mark.parametrize('mock_input', [['']], indirect=True)
+def test_duplicate_isbn_same_unknown_doi(mock_init, mock_input, entries):
+    text = entries['isbn_no_doi1'] + entries['isbn_no_doi2']
+    bibs = bm.read_file(text=text)
+    assert len(bibs) == 1
+
+
 def test_init_from_scratch(mock_home):
     shutil.rmtree(u.HOME, ignore_errors=True)
     bm.init(bibfile=None)