papers-cli 2.10__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.10 → papers_cli-2.11}/PKG-INFO +1 -1
- {papers_cli-2.10 → papers_cli-2.11}/papers/__main__.py +47 -38
- {papers_cli-2.10 → papers_cli-2.11}/papers/_version.py +2 -2
- {papers_cli-2.10 → papers_cli-2.11}/papers/bib.py +16 -7
- {papers_cli-2.10 → papers_cli-2.11}/papers/duplicate.py +11 -6
- {papers_cli-2.10 → papers_cli-2.11}/papers/extract.py +2 -4
- {papers_cli-2.10 → papers_cli-2.11}/papers_cli.egg-info/PKG-INFO +1 -1
- {papers_cli-2.10 → papers_cli-2.11}/tests/test_add.py +1 -1
- {papers_cli-2.10 → papers_cli-2.11}/tests/test_duplicates.py +1 -1
- {papers_cli-2.10 → papers_cli-2.11}/.gitattributes +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/.github/workflows/pypi.yml +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/.github/workflows/tests.yml +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/.gitignore +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/LICENSE +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/README.md +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/papers/__init__.py +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/papers/config.py +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/papers/encoding.py +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/papers/filename.py +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/papers/latexenc.py +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/papers/utils.py +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/papers_cli.egg-info/SOURCES.txt +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/papers_cli.egg-info/dependency_links.txt +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/papers_cli.egg-info/entry_points.txt +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/papers_cli.egg-info/requires.txt +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/papers_cli.egg-info/top_level.txt +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/pyproject.toml +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/requirements.txt +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/setup.cfg +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/setup.py +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/tests/__init__.py +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/tests/common.py +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/tests/config.py +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/tests/download.py +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/tests/downloadedpapers/.gitignore +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/tests/downloadedpapers/bg-8-515-2011.txt +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/tests/test_biblio.py +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/tests/test_encoding.py +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/tests/test_extract.py +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/tests/test_filecheck.py +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/tests/test_install.py +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/tests/test_list.py +0 -0
- {papers_cli-2.10 → papers_cli-2.11}/tests/test_misc.py +0 -0
- {papers_cli-2.10 → 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):
|
|
@@ -221,7 +221,7 @@ def fetch_crossref_by_doi(doi):
|
|
|
221
221
|
# @cached('crossref-bibtex.json')
|
|
222
222
|
def fetch_bibtex_by_doi(doi):
|
|
223
223
|
json_data = fetch_crossref_by_doi(doi)
|
|
224
|
-
return crossref_to_bibtex(json_data)
|
|
224
|
+
return crossref_to_bibtex(json_data['message'])
|
|
225
225
|
|
|
226
226
|
@cached('crossref-json.json')
|
|
227
227
|
def fetch_json_by_doi(doi):
|
|
@@ -308,8 +308,7 @@ def format_authors(authors):
|
|
|
308
308
|
return " and ".join(author_list)
|
|
309
309
|
|
|
310
310
|
|
|
311
|
-
def crossref_to_bibtex(
|
|
312
|
-
message = json_data['message']
|
|
311
|
+
def crossref_to_bibtex(message):
|
|
313
312
|
entry_type = message.get('type', 'misc') # Default to 'misc' if type is not specified
|
|
314
313
|
|
|
315
314
|
# Common fields
|
|
@@ -413,7 +412,6 @@ def fetch_bibtex_by_fulltext_crossref(txt, **kw):
|
|
|
413
412
|
return crossref_to_bibtex(result).strip()
|
|
414
413
|
|
|
415
414
|
|
|
416
|
-
|
|
417
415
|
def fetch_entry(e):
|
|
418
416
|
if 'doi' in e and isvaliddoi(e['doi']):
|
|
419
417
|
bibtex = fetch_bibtex_by_doi(e['doi'])
|
|
@@ -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
|
|
File without changes
|