papers-cli 2.9.1__tar.gz → 2.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {papers_cli-2.9.1 → papers_cli-2.11}/PKG-INFO +1 -1
- {papers_cli-2.9.1 → papers_cli-2.11}/papers/__main__.py +47 -38
- {papers_cli-2.9.1 → papers_cli-2.11}/papers/_version.py +2 -2
- {papers_cli-2.9.1 → papers_cli-2.11}/papers/bib.py +16 -7
- {papers_cli-2.9.1 → papers_cli-2.11}/papers/duplicate.py +11 -6
- {papers_cli-2.9.1 → papers_cli-2.11}/papers/extract.py +107 -63
- {papers_cli-2.9.1 → papers_cli-2.11}/papers_cli.egg-info/PKG-INFO +1 -1
- {papers_cli-2.9.1 → papers_cli-2.11}/tests/common.py +16 -22
- {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_add.py +1 -1
- {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_duplicates.py +1 -1
- {papers_cli-2.9.1 → papers_cli-2.11}/.gitattributes +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/.github/workflows/pypi.yml +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/.github/workflows/tests.yml +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/.gitignore +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/LICENSE +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/README.md +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/papers/__init__.py +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/papers/config.py +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/papers/encoding.py +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/papers/filename.py +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/papers/latexenc.py +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/papers/utils.py +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/papers_cli.egg-info/SOURCES.txt +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/papers_cli.egg-info/dependency_links.txt +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/papers_cli.egg-info/entry_points.txt +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/papers_cli.egg-info/requires.txt +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/papers_cli.egg-info/top_level.txt +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/pyproject.toml +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/requirements.txt +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/setup.cfg +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/setup.py +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/tests/__init__.py +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/tests/config.py +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/tests/download.py +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/tests/downloadedpapers/.gitignore +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/tests/downloadedpapers/bg-8-515-2011.txt +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_biblio.py +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_encoding.py +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_extract.py +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_filecheck.py +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_install.py +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_list.py +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_misc.py +0 -0
- {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_undo.py +0 -0
|
@@ -19,7 +19,7 @@ from papers.extract import fetch_bibtex_by_doi
|
|
|
19
19
|
from papers.encoding import parse_file, format_file, family_names, format_entries, standard_name, format_entry, parse_keywords, format_key
|
|
20
20
|
from papers.config import bcolors, Config, search_config, CONFIG_FILE, CONFIG_FILE_LOCAL, DATA_DIR, CONFIG_FILE_LEGACY, BACKUP_DIR
|
|
21
21
|
from papers.duplicate import list_duplicates, list_uniques, edit_entries
|
|
22
|
-
from papers.bib import Biblio, FUZZY_RATIO, DEFAULT_SIMILARITY, entry_filecheck, backupfile as backupfile_func, isvalidkey
|
|
22
|
+
from papers.bib import Biblio, FUZZY_RATIO, DEFAULT_SIMILARITY, entry_filecheck, backupfile as backupfile_func, isvalidkey, DuplicateKeyError
|
|
23
23
|
from papers.utils import move, checksum, view_pdf, open_folder
|
|
24
24
|
from papers import __version__
|
|
25
25
|
|
|
@@ -538,30 +538,45 @@ def addcmd(parser, o, config):
|
|
|
538
538
|
|
|
539
539
|
biblio = get_biblio(config)
|
|
540
540
|
|
|
541
|
-
kw = {'on_conflict':o.mode, 'check_duplicate':not o.no_check_duplicate,
|
|
542
|
-
'mergefiles':not o.no_merge_files, 'update_key':o.update_key}
|
|
543
|
-
|
|
544
541
|
entries = []
|
|
545
542
|
|
|
543
|
+
metadata = {k: v for k, v in o.metadata or []}
|
|
544
|
+
if o.doi: metadata['doi'] = o.doi
|
|
545
|
+
if o.key: metadata['ID'] = o.key
|
|
546
|
+
if o.title: metadata['title'] = o.title
|
|
547
|
+
if o.author: metadata['author'] = o.author
|
|
548
|
+
if o.journal: metadata['journal'] = o.journal
|
|
549
|
+
if o.year: metadata['year'] = o.year
|
|
550
|
+
if o.type: metadata['ENTRYTYPE'] = o.type
|
|
551
|
+
if "author" in metadata:
|
|
552
|
+
metadata["author"] = standard_name(metadata["author"])
|
|
553
|
+
if o.attachment:
|
|
554
|
+
metadata['file'] = format_file(biblio.get_files(metadata) + o.attachment, relative_to=biblio.relative_to)
|
|
555
|
+
|
|
546
556
|
if len(o.file) > 1:
|
|
547
|
-
if
|
|
548
|
-
logger.error('--attachment
|
|
557
|
+
if metadata:
|
|
558
|
+
logger.error('--doi, --metadata, --key, --attachment and other metadata keys are only valid for one PDF / BIBTEX entry')
|
|
549
559
|
raise PapersExit()
|
|
550
|
-
|
|
551
|
-
|
|
560
|
+
|
|
561
|
+
kw = {'on_conflict':o.mode, 'check_duplicate':not o.no_check_duplicate,
|
|
562
|
+
'mergefiles':not o.no_merge_files, 'update_key':o.update_key, 'metadata':metadata}
|
|
563
|
+
|
|
564
|
+
if len(o.file) == 0 and o.doi and not o.no_query_doi:
|
|
565
|
+
entries.extend( biblio.fetch_doi(o.doi, attachments=o.attachment, rename=o.rename, copy=o.copy, **kw) )
|
|
566
|
+
|
|
567
|
+
elif len(o.file) == 0:
|
|
568
|
+
if not o.edit and not o.metadata and not o.key and not o.doi and not o.attachment and not o.title and not o.author and not o.journal and not o.year:
|
|
569
|
+
logger.error("No entry added: use --doi, --metadata, --key, --attachment, --title, --author, --journal, --year to add a new entry")
|
|
552
570
|
raise PapersExit()
|
|
553
571
|
|
|
554
|
-
|
|
572
|
+
metadata.setdefault('ID', biblio.generate_key(metadata))
|
|
573
|
+
metadata.setdefault('ENTRYTYPE', 'article')
|
|
555
574
|
if o.edit:
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
logger.error('If no file is present, --no-query-doi is not compatible with --doi')
|
|
562
|
-
raise PapersExit()
|
|
563
|
-
else:
|
|
564
|
-
entries.extend( biblio.fetch_doi(o.doi, attachments=o.attachment, rename=o.rename, copy=o.copy, **kw) )
|
|
575
|
+
metadata = edit_entries(metadata)
|
|
576
|
+
o.edit = False
|
|
577
|
+
|
|
578
|
+
entries.extend( biblio.insert_entry(metadata, **kw) )
|
|
579
|
+
|
|
565
580
|
|
|
566
581
|
for file in o.file:
|
|
567
582
|
try:
|
|
@@ -601,30 +616,18 @@ def addcmd(parser, o, config):
|
|
|
601
616
|
entries = [e for e in biblio.entries if biblio.key(e) in unique_keys]
|
|
602
617
|
|
|
603
618
|
if o.edit:
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
"ID": "EDIT-NEW-KEY",
|
|
607
|
-
"ENTRYTYPE": "article",
|
|
608
|
-
"author": standard_name("John Doe and Jane Roe"),
|
|
609
|
-
"title": "",
|
|
610
|
-
"journal": "",
|
|
611
|
-
"year": "",
|
|
612
|
-
"doi": o.doi or "",
|
|
613
|
-
"file": format_file(o.attachment or [], relative_to=biblio.relative_to),
|
|
614
|
-
}]
|
|
619
|
+
entry_keys = [biblio.key(e) for e in entries]
|
|
620
|
+
otherentries = [e for e in biblio.entries if biblio.key(e) not in entry_keys]
|
|
615
621
|
|
|
616
622
|
try:
|
|
617
|
-
otherentries = [e for e in biblio.entries if e not in entries]
|
|
618
623
|
entries = edit_entries(entries)
|
|
619
|
-
entries = [{k:v for k,v in e.items() if v != ""} for e in entries]
|
|
620
|
-
biblio.db.entries = otherentries
|
|
621
|
-
for e in entries:
|
|
622
|
-
biblio.insert_entry(e, **kw)
|
|
623
|
-
|
|
624
624
|
except Exception as error:
|
|
625
625
|
logger.error(str(error))
|
|
626
626
|
return
|
|
627
627
|
|
|
628
|
+
entries = [{k:v for k,v in e.items() if v != ""} for e in entries]
|
|
629
|
+
biblio.db.entries = otherentries + entries
|
|
630
|
+
|
|
628
631
|
savebib(biblio, config)
|
|
629
632
|
|
|
630
633
|
if o.open:
|
|
@@ -888,7 +891,6 @@ def statuscmd(parser, o, config):
|
|
|
888
891
|
print(config.status(check_files=not o.no_check_files, verbose=o.verbose))
|
|
889
892
|
|
|
890
893
|
|
|
891
|
-
|
|
892
894
|
def get_parser(config=None):
|
|
893
895
|
if config is None:
|
|
894
896
|
config = papers.config.Config()
|
|
@@ -1022,8 +1024,15 @@ def get_parser(config=None):
|
|
|
1022
1024
|
grp.add_argument('--ignore-errors', action='store_true',
|
|
1023
1025
|
help='ignore errors when adding multiple files')
|
|
1024
1026
|
|
|
1025
|
-
grp = addp.add_argument_group('
|
|
1027
|
+
grp = addp.add_argument_group('metadata')
|
|
1026
1028
|
grp.add_argument('--doi', help='provide DOI -- skip parsing PDF')
|
|
1029
|
+
grp.add_argument('--key', help='manually set the key')
|
|
1030
|
+
grp.add_argument('--type', help='document type')
|
|
1031
|
+
grp.add_argument('--title', help='manually set the title')
|
|
1032
|
+
grp.add_argument('--author', help='manually set the author')
|
|
1033
|
+
grp.add_argument('--journal', help='manually set the journal')
|
|
1034
|
+
grp.add_argument('--year', help='manually set the year')
|
|
1035
|
+
grp.add_argument('--metadata', nargs="+", metavar="KEY=VALUE", type=lambda meta: meta.split('=', 1), help='the metadata fields manually')
|
|
1027
1036
|
grp.add_argument('--no-query-doi', action='store_true', help='do not attempt to parse and query doi')
|
|
1028
1037
|
grp.add_argument('--no-query-fulltext', action='store_true', help='do not attempt to query fulltext in case doi query fails')
|
|
1029
1038
|
grp.add_argument('--scholar', action='store_true', help='use google scholar instead of crossref')
|
|
@@ -1298,7 +1307,7 @@ class PapersExit(Exception):
|
|
|
1298
1307
|
def main_clean_exit(args=None):
|
|
1299
1308
|
try:
|
|
1300
1309
|
main(args)
|
|
1301
|
-
except PapersExit as error:
|
|
1310
|
+
except (PapersExit, DuplicateKeyError) as error:
|
|
1302
1311
|
if logger.getEffectiveLevel() == logging.DEBUG:
|
|
1303
1312
|
raise
|
|
1304
1313
|
if error.args:
|
|
@@ -279,9 +279,16 @@ class Biblio:
|
|
|
279
279
|
return bisect.bisect_left(keys, self.key(entry))
|
|
280
280
|
|
|
281
281
|
|
|
282
|
-
def insert_entry(self, entry, update_key=False, check_duplicate=False, rename=False, copy=False, **checkopt):
|
|
282
|
+
def insert_entry(self, entry, update_key=False, check_duplicate=False, rename=False, copy=False, metadata={}, **checkopt):
|
|
283
283
|
"""
|
|
284
284
|
"""
|
|
285
|
+
if metadata:
|
|
286
|
+
files = self.get_files(entry) + self.get_files(metadata)
|
|
287
|
+
self.set_files(metadata, files)
|
|
288
|
+
|
|
289
|
+
if update_key:
|
|
290
|
+
entry['ID'] = self.generate_key(entry)
|
|
291
|
+
|
|
285
292
|
# additional checks on DOI etc...
|
|
286
293
|
if check_duplicate:
|
|
287
294
|
logger.debug('check duplicates : TRUE')
|
|
@@ -298,15 +305,16 @@ class Biblio:
|
|
|
298
305
|
newkey = self.append_abc_to_key(entry) # add abc
|
|
299
306
|
logger.info('update key: {} => {}'.format(entry['ID'], newkey))
|
|
300
307
|
entry['ID'] = newkey
|
|
301
|
-
print(format_entry(self, entry, prefix="Updated"))
|
|
302
308
|
|
|
303
309
|
else:
|
|
304
|
-
|
|
310
|
+
print(format_entry(self, entry, prefix="New entry"))
|
|
311
|
+
print(format_entry(self, self.entries[i], prefix="Conflicts with"))
|
|
312
|
+
raise DuplicateKeyError('Key conflict. Try update_key is True?')
|
|
305
313
|
|
|
306
314
|
else:
|
|
307
315
|
logger.info('new entry: '+self.key(entry))
|
|
308
|
-
print(format_entry(self, entry, prefix="Added"))
|
|
309
316
|
|
|
317
|
+
print(format_entry(self, entry, prefix="Added"))
|
|
310
318
|
self.entries.insert(i, entry)
|
|
311
319
|
|
|
312
320
|
if rename: self.rename_entry_files(entry, copy=copy)
|
|
@@ -413,8 +421,10 @@ class Biblio:
|
|
|
413
421
|
|
|
414
422
|
def fetch_doi(self, doi, **kw):
|
|
415
423
|
bibtex = fetch_bibtex_by_doi(doi)
|
|
424
|
+
kw["update_key"] = True # fetched key is always updated
|
|
416
425
|
return self.add_bibtex(bibtex, **kw)
|
|
417
426
|
|
|
427
|
+
|
|
418
428
|
def add_pdf(self, pdf, attachments=None, search_doi=True, search_fulltext=True, scholar=False, doi=None, **kw):
|
|
419
429
|
|
|
420
430
|
if doi:
|
|
@@ -436,9 +446,8 @@ class Biblio:
|
|
|
436
446
|
entry['ID'] = self.generate_key(entry)
|
|
437
447
|
logger.debug('generated PDF key: '+entry['ID'])
|
|
438
448
|
|
|
439
|
-
kw
|
|
440
|
-
|
|
441
|
-
return self.insert_entry(entry, update_key=True, **kw)
|
|
449
|
+
kw["update_key"] = False # already updated
|
|
450
|
+
return self.insert_entry(entry, **kw)
|
|
442
451
|
|
|
443
452
|
|
|
444
453
|
def scan_dir_iter(self, direc, search_doi=True, search_fulltext=True, **kw):
|
|
@@ -341,7 +341,6 @@ def choose_entry_interactive(entries, extra=[], msg='', select=False, best=None)
|
|
|
341
341
|
return _ask_pick_loop(entries, extra, select)
|
|
342
342
|
|
|
343
343
|
|
|
344
|
-
|
|
345
344
|
def edit_entries(entries, diff=False, ndiff=False, editor=None):
|
|
346
345
|
'''edit entries and insert result in database
|
|
347
346
|
'''
|
|
@@ -367,12 +366,18 @@ def edit_entries(entries, diff=False, ndiff=False, editor=None):
|
|
|
367
366
|
|
|
368
367
|
if res == 0:
|
|
369
368
|
logger.info('sucessfully edited file, insert edited entries')
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
369
|
+
db_updated = bibtexparser.loads(open(filename).read())
|
|
370
|
+
keys_before = [e.get('ID') for e in entries]
|
|
371
|
+
keys_after = [e.get('ID') for e in db_updated.entries]
|
|
372
|
+
if set(keys_before) != set(keys_after):
|
|
373
|
+
print(f"{bcolors.WARNING}Keys edited ->", bcolors.OKBLUE ," ".join(sorted(set(keys_before).difference(keys_after))), bcolors.ENDC,
|
|
374
|
+
"=>", bcolors.OKBLUE,
|
|
375
|
+
" ".join(sorted(set(keys_after).difference(keys_before))), bcolors.ENDC) if keys_after else '(deleted)'
|
|
376
|
+
return db_updated.entries
|
|
375
377
|
|
|
378
|
+
else:
|
|
379
|
+
logger.info('failed to edit file')
|
|
380
|
+
return db
|
|
376
381
|
|
|
377
382
|
|
|
378
383
|
def score(e):
|
|
@@ -14,7 +14,7 @@ from papers.encoding import family_names
|
|
|
14
14
|
from bibtexparser.customization import convert_to_unicode
|
|
15
15
|
|
|
16
16
|
my_etiquette = Etiquette('papers', papers.__version__, 'https://github.com/perrette/papers', 'mahe.perrette@gmail.com')
|
|
17
|
-
|
|
17
|
+
work = Works(etiquette=my_etiquette)
|
|
18
18
|
|
|
19
19
|
class DOIParsingError(ValueError):
|
|
20
20
|
pass
|
|
@@ -208,27 +208,29 @@ def extract_pdf_metadata(pdf, search_doi=True, search_fulltext=True, maxpages=10
|
|
|
208
208
|
txt = pdfhead(pdf, maxpages, minwords, image=image)
|
|
209
209
|
return extract_txt_metadata(txt, search_doi, search_fulltext, **kw)
|
|
210
210
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
def fetch_bibtex_by_doi(doi):
|
|
215
|
-
url = "http://api.crossref.org/works/"+doi+"/transform/application/x-bibtex"
|
|
216
|
-
work = Works(etiquette=my_etiquette)
|
|
211
|
+
@cached('crossref.json')
|
|
212
|
+
def fetch_crossref_by_doi(doi):
|
|
213
|
+
url = "http://api.crossref.org/works/"+doi
|
|
217
214
|
response = work.do_http_request('get', url, custom_header={'user-agent': str(work.etiquette)})
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
215
|
+
try:
|
|
216
|
+
response.raise_for_status()
|
|
217
|
+
except Exception as error:
|
|
218
|
+
raise DOIRequestError(repr(doi)+': '+repr(error))
|
|
219
|
+
return response.json()
|
|
222
220
|
|
|
221
|
+
# @cached('crossref-bibtex.json')
|
|
222
|
+
def fetch_bibtex_by_doi(doi):
|
|
223
|
+
json_data = fetch_crossref_by_doi(doi)
|
|
224
|
+
return crossref_to_bibtex(json_data['message'])
|
|
223
225
|
|
|
224
|
-
@cached('crossref.json')
|
|
226
|
+
@cached('crossref-json.json')
|
|
225
227
|
def fetch_json_by_doi(doi):
|
|
226
228
|
url = "http://api.crossref.org/works/"+doi+"/transform/application/json"
|
|
227
|
-
work = Works(etiquette=my_etiquette)
|
|
228
229
|
jsontxt = work.do_http_request('get', url, custom_header={'user-agent': str(work.etiquette)}).text
|
|
229
230
|
return jsontxt.dumps(json)
|
|
230
231
|
|
|
231
232
|
|
|
233
|
+
|
|
232
234
|
def _get_page_fast(pagerequest):
|
|
233
235
|
"""Return the data for a page on scholar.google.com"""
|
|
234
236
|
from scholarly import scholarly
|
|
@@ -267,11 +269,6 @@ def fetch_bibtex_by_fulltext_scholar(txt, assess_results=True):
|
|
|
267
269
|
|
|
268
270
|
return scholarly.bibtex(result)
|
|
269
271
|
|
|
270
|
-
|
|
271
|
-
def _crossref_get_author(res, sep='; '):
|
|
272
|
-
return sep.join([p.get('given','') + p['family'] for p in res.get('author',[]) if 'family' in p])
|
|
273
|
-
|
|
274
|
-
|
|
275
272
|
def _crossref_score(txt, r):
|
|
276
273
|
# high score means high similarity
|
|
277
274
|
from rapidfuzz.fuzz import token_set_ratio
|
|
@@ -285,56 +282,104 @@ def _crossref_score(txt, r):
|
|
|
285
282
|
score += token_set_ratio(r['abstract'], txt)
|
|
286
283
|
return score
|
|
287
284
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
#
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
285
|
+
def map_crossref_to_bibtex_type(crossref_type):
|
|
286
|
+
mapping = {
|
|
287
|
+
"journal-article": "article",
|
|
288
|
+
"book": "book",
|
|
289
|
+
"book-chapter": "incollection", # or sometimes inbook
|
|
290
|
+
"proceedings-article": "inproceedings",
|
|
291
|
+
"conference-paper": "inproceedings",
|
|
292
|
+
"report": "techreport",
|
|
293
|
+
"thesis": "phdthesis", # or "mastersthesis" based on additional info
|
|
294
|
+
}
|
|
295
|
+
return mapping.get(crossref_type, "misc")
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def format_authors(authors):
|
|
299
|
+
"""Converts a list of author dicts to a BibTeX-friendly string."""
|
|
300
|
+
author_list = []
|
|
301
|
+
for author in authors:
|
|
302
|
+
given = author.get("given", "")
|
|
303
|
+
family = author.get("family", "")
|
|
304
|
+
# Format as "Family, Given" for BibTeX
|
|
305
|
+
formatted = f"{family}, {given}" if family else given
|
|
306
|
+
# author_list.append(unidecode(formatted))
|
|
307
|
+
author_list.append(formatted)
|
|
308
|
+
return " and ".join(author_list)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def crossref_to_bibtex(message):
|
|
312
|
+
entry_type = message.get('type', 'misc') # Default to 'misc' if type is not specified
|
|
313
|
+
|
|
314
|
+
# Common fields
|
|
315
|
+
bib_entry = {
|
|
316
|
+
'title': message.get('title', [''])[0],
|
|
317
|
+
'author': format_authors(message.get('author', [])),
|
|
318
|
+
'doi': message.get('DOI', ''),
|
|
319
|
+
'url': message.get('URL', ''),
|
|
320
|
+
'ENTRYTYPE': map_crossref_to_bibtex_type(entry_type),
|
|
321
|
+
'ID': message.get('DOI', '')
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
# Year: Look for publication date in several possible places
|
|
325
|
+
date_parts = None
|
|
326
|
+
for date_field in ["published-print", "published-online", "issued"]:
|
|
327
|
+
if date_field in message and "date-parts" in message[date_field]:
|
|
328
|
+
date_parts = message[date_field]["date-parts"][0]
|
|
329
|
+
break
|
|
330
|
+
if date_parts and len(date_parts) > 0:
|
|
331
|
+
bib_entry["year"] = str(date_parts[0])
|
|
326
332
|
else:
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
333
|
+
bib_entry["year"] = "0000"
|
|
334
|
+
|
|
335
|
+
# Fields specific to entry types
|
|
336
|
+
if entry_type == 'journal-article':
|
|
337
|
+
bib_entry.update({
|
|
338
|
+
'journal': message.get('container-title', [''])[0],
|
|
339
|
+
'volume': message.get('volume', ''),
|
|
340
|
+
'number': message.get('issue', ''),
|
|
341
|
+
'pages': message.get('page', ''),
|
|
342
|
+
})
|
|
343
|
+
elif entry_type == 'book':
|
|
344
|
+
bib_entry.update({
|
|
345
|
+
'publisher': message.get('publisher', ''),
|
|
346
|
+
'address': message.get('publisher-location', ''),
|
|
347
|
+
'editor': format_authors(message.get('editor', [])),
|
|
348
|
+
'isbn': message.get('ISBN', [''])[0],
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
elif entry_type == 'proceedings-article':
|
|
353
|
+
bib_entry.update({
|
|
354
|
+
'booktitle': message.get('container-title', [''])[0],
|
|
355
|
+
'publisher': message.get('publisher', ''),
|
|
356
|
+
'editor': format_authors(message.get('editor', [])),
|
|
357
|
+
})
|
|
358
|
+
elif entry_type == 'report':
|
|
359
|
+
bib_entry.update({
|
|
360
|
+
'institution': message.get('institution', {}).get('name', ''),
|
|
361
|
+
})
|
|
362
|
+
elif entry_type == 'thesis':
|
|
363
|
+
bib_entry.update({
|
|
364
|
+
'school': message.get('institution', {}).get('name', ''),
|
|
365
|
+
'type': message.get('type', ''),
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
bib_entry = {k: v for k, v in bib_entry.items() if v} # Remove empty fields
|
|
369
|
+
|
|
370
|
+
# Create a BibDatabase object
|
|
330
371
|
db = bibtexparser.bibdatabase.BibDatabase()
|
|
331
|
-
db.entries
|
|
332
|
-
|
|
372
|
+
db.entries = [bib_entry]
|
|
373
|
+
|
|
374
|
+
# Write to a BibTeX string
|
|
375
|
+
writer = bibtexparser.bwriter.BibTexWriter()
|
|
376
|
+
bibtex_str = writer.write(db)
|
|
377
|
+
|
|
378
|
+
return bibtex_str
|
|
333
379
|
|
|
334
380
|
|
|
335
381
|
# @cached('crossref-bibtex-fulltext.json', hashed_key=True)
|
|
336
382
|
def fetch_bibtex_by_fulltext_crossref(txt, **kw):
|
|
337
|
-
work = Works(etiquette=my_etiquette)
|
|
338
383
|
logger.debug('crossref fulltext seach:\n'+txt)
|
|
339
384
|
|
|
340
385
|
# get the most likely match of the first results
|
|
@@ -367,7 +412,6 @@ def fetch_bibtex_by_fulltext_crossref(txt, **kw):
|
|
|
367
412
|
return crossref_to_bibtex(result).strip()
|
|
368
413
|
|
|
369
414
|
|
|
370
|
-
|
|
371
415
|
def fetch_entry(e):
|
|
372
416
|
if 'doi' in e and isvaliddoi(e['doi']):
|
|
373
417
|
bibtex = fetch_bibtex_by_doi(e['doi'])
|
|
@@ -77,23 +77,21 @@ def run(cmd, sp_cmd=None, **kw):
|
|
|
77
77
|
def prepare_paper():
|
|
78
78
|
pdf = downloadpdf('bg-8-515-2011.pdf')
|
|
79
79
|
doi = '10.5194/bg-8-515-2011'
|
|
80
|
-
key = '
|
|
80
|
+
key = '10.5194/bg-8-515-2011'
|
|
81
81
|
newkey = 'perrette_yool2011'
|
|
82
82
|
year = '2011'
|
|
83
|
-
bibtex = """@article{
|
|
83
|
+
bibtex = """@article{10.5194/bg-8-515-2011,
|
|
84
84
|
author = {Perrette, M. and Yool, A. and Quartly, G. D. and Popova, E. E.},
|
|
85
85
|
doi = {10.5194/bg-8-515-2011},
|
|
86
|
-
issn = {1726-4189},
|
|
87
86
|
journal = {Biogeosciences},
|
|
88
|
-
month = {February},
|
|
89
87
|
number = {2},
|
|
90
|
-
pages = {515
|
|
91
|
-
publisher = {Copernicus GmbH},
|
|
88
|
+
pages = {515-524},
|
|
92
89
|
title = {Near-ubiquity of ice-edge blooms in the Arctic},
|
|
93
|
-
url = {
|
|
90
|
+
url = {https://doi.org/10.5194/bg-8-515-2011},
|
|
94
91
|
volume = {8},
|
|
95
92
|
year = {2011}
|
|
96
|
-
}
|
|
93
|
+
}
|
|
94
|
+
"""
|
|
97
95
|
|
|
98
96
|
file_rename = "perrette_et_al_2011_near-ubiquity-of-ice-edge-blooms-in-the-arctic.pdf"
|
|
99
97
|
# The above corresponds to --name-template "{authorX}_{year}_{title}"
|
|
@@ -104,20 +102,17 @@ def prepare_paper2():
|
|
|
104
102
|
pdf = downloadpdf('esd-4-11-2013.pdf')
|
|
105
103
|
si = downloadpdf('esd-4-11-2013-supplement.pdf')
|
|
106
104
|
doi = '10.5194/esd-4-11-2013'
|
|
107
|
-
key = '
|
|
105
|
+
key = '10.5194/esd-4-11-2013'
|
|
108
106
|
newkey = 'perrette_landerer2013'
|
|
109
107
|
year = '2013'
|
|
110
|
-
bibtex = """@article{
|
|
108
|
+
bibtex = """@article{10.5194/esd-4-11-2013,
|
|
111
109
|
author = {Perrette, M. and Landerer, F. and Riva, R. and Frieler, K. and Meinshausen, M.},
|
|
112
110
|
doi = {10.5194/esd-4-11-2013},
|
|
113
|
-
issn = {2190-4987},
|
|
114
111
|
journal = {Earth System Dynamics},
|
|
115
|
-
month = {January},
|
|
116
112
|
number = {1},
|
|
117
|
-
pages = {11
|
|
118
|
-
publisher = {Copernicus GmbH},
|
|
113
|
+
pages = {11-29},
|
|
119
114
|
title = {A scaling approach to project regional sea level rise and its uncertainties},
|
|
120
|
-
url = {
|
|
115
|
+
url = {https://doi.org/10.5194/esd-4-11-2013},
|
|
121
116
|
volume = {4},
|
|
122
117
|
year = {2013}
|
|
123
118
|
}"""
|
|
@@ -185,19 +180,18 @@ class BibTest(unittest.TestCase):
|
|
|
185
180
|
|
|
186
181
|
|
|
187
182
|
|
|
188
|
-
bibtex = """@article{
|
|
189
|
-
author = {M.
|
|
183
|
+
bibtex = """@article{10.5194/bg-8-515-2011,
|
|
184
|
+
author = {Perrette, M. and Yool, A. and Quartly, G. D. and Popova, E. E.},
|
|
190
185
|
doi = {10.5194/bg-8-515-2011},
|
|
191
186
|
journal = {Biogeosciences},
|
|
192
|
-
link = {https://doi.org/10.5194%2Fbg-8-515-2011},
|
|
193
|
-
month = {feb},
|
|
194
187
|
number = {2},
|
|
195
|
-
pages = {515
|
|
196
|
-
publisher = {Copernicus {GmbH}},
|
|
188
|
+
pages = {515-524},
|
|
197
189
|
title = {Near-ubiquity of ice-edge blooms in the Arctic},
|
|
190
|
+
url = {https://doi.org/10.5194/bg-8-515-2011},
|
|
198
191
|
volume = {8},
|
|
199
192
|
year = {2011}
|
|
200
|
-
}
|
|
193
|
+
}
|
|
194
|
+
"""
|
|
201
195
|
|
|
202
196
|
bibtex2 = """@article{SomeOneElse2000,
|
|
203
197
|
author = {Some One},
|
|
@@ -305,7 +305,7 @@ class TestAddConflict(BibTest):
|
|
|
305
305
|
title = {Something else entirely}
|
|
306
306
|
}"""
|
|
307
307
|
|
|
308
|
-
bibtex_conflict_key_fixed = """@article{
|
|
308
|
+
bibtex_conflict_key_fixed = """@article{perrette_author0000,
|
|
309
309
|
author = {M. Perrette and Another author},
|
|
310
310
|
doi = {10.5194/bg-8-515-2011XXX},
|
|
311
311
|
title = {Something else entirely}
|
|
@@ -266,7 +266,7 @@ class TestAddResolveDuplicate(BibTest):
|
|
|
266
266
|
|
|
267
267
|
def test_conflict_updated_from_original_but_originalkey(self):
|
|
268
268
|
|
|
269
|
-
expected = """@article{
|
|
269
|
+
expected = """@article{fieldRareYear,
|
|
270
270
|
author = {New Author Field},
|
|
271
271
|
doi = {10.5194/bg-8-515-2011},
|
|
272
272
|
journal = {ConflictJournal},
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|