papers-cli 2.6.1__tar.gz → 2.7.1__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.6.1 → papers_cli-2.7.1}/PKG-INFO +1 -1
- {papers_cli-2.6.1 → papers_cli-2.7.1}/papers/__main__.py +61 -12
- {papers_cli-2.6.1 → papers_cli-2.7.1}/papers/_version.py +2 -2
- {papers_cli-2.6.1 → papers_cli-2.7.1}/papers/bib.py +22 -15
- {papers_cli-2.6.1 → papers_cli-2.7.1}/papers/duplicate.py +2 -4
- {papers_cli-2.6.1 → papers_cli-2.7.1}/papers_cli.egg-info/PKG-INFO +1 -1
- {papers_cli-2.6.1 → papers_cli-2.7.1}/.gitattributes +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/.github/workflows/pypi.yml +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/.github/workflows/tests.yml +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/.gitignore +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/LICENSE +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/README.md +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/papers/__init__.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/papers/config.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/papers/encoding.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/papers/extract.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/papers/filename.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/papers/latexenc.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/papers/utils.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/papers_cli.egg-info/SOURCES.txt +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/papers_cli.egg-info/dependency_links.txt +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/papers_cli.egg-info/entry_points.txt +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/papers_cli.egg-info/requires.txt +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/papers_cli.egg-info/top_level.txt +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/pyproject.toml +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/requirements.txt +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/setup.cfg +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/setup.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/tests/__init__.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/tests/common.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/tests/config.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/tests/download.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/tests/downloadedpapers/.gitignore +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/tests/downloadedpapers/bg-8-515-2011.txt +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/tests/test_add.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/tests/test_biblio.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/tests/test_duplicates.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/tests/test_encoding.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/tests/test_extract.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/tests/test_filecheck.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/tests/test_install.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/tests/test_list.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/tests/test_misc.py +0 -0
- {papers_cli-2.6.1 → papers_cli-2.7.1}/tests/test_undo.py +0 -0
|
@@ -9,12 +9,14 @@ import subprocess as sp
|
|
|
9
9
|
import shutil
|
|
10
10
|
import itertools
|
|
11
11
|
import fnmatch # unix-like match
|
|
12
|
+
import hashlib
|
|
13
|
+
import time
|
|
12
14
|
|
|
13
15
|
import papers
|
|
14
16
|
from papers import logger
|
|
15
17
|
from papers.extract import extract_pdf_doi, isvaliddoi, extract_pdf_metadata
|
|
16
18
|
from papers.extract import fetch_bibtex_by_doi
|
|
17
|
-
from papers.encoding import parse_file, format_file, family_names, format_entries
|
|
19
|
+
from papers.encoding import parse_file, format_file, family_names, format_entries, standard_name
|
|
18
20
|
from papers.config import bcolors, Config, search_config, CONFIG_FILE, CONFIG_FILE_LOCAL, DATA_DIR, CONFIG_FILE_LEGACY, BACKUP_DIR
|
|
19
21
|
from papers.duplicate import list_duplicates, list_uniques, edit_entries
|
|
20
22
|
from papers.bib import Biblio, FUZZY_RATIO, DEFAULT_SIMILARITY, entry_filecheck, backupfile as backupfile_func, isvalidkey
|
|
@@ -223,6 +225,12 @@ def savebib(biblio, config):
|
|
|
223
225
|
# config.gitcommit()
|
|
224
226
|
|
|
225
227
|
|
|
228
|
+
def view_entry_files(biblio, entry):
|
|
229
|
+
for f in biblio.get_files(entry):
|
|
230
|
+
logger.info(f"opening {f} ...")
|
|
231
|
+
view_pdf(f)
|
|
232
|
+
|
|
233
|
+
|
|
226
234
|
def set_keyformat_config_from_cmd(o, config):
|
|
227
235
|
"""
|
|
228
236
|
Given options and a config state, applies the desired key options to the config.
|
|
@@ -505,6 +513,8 @@ def addcmd(parser, o, config):
|
|
|
505
513
|
kw = {'on_conflict':o.mode, 'check_duplicate':not o.no_check_duplicate,
|
|
506
514
|
'mergefiles':not o.no_merge_files, 'update_key':o.update_key}
|
|
507
515
|
|
|
516
|
+
entries = []
|
|
517
|
+
|
|
508
518
|
if len(o.file) > 1:
|
|
509
519
|
if o.attachment:
|
|
510
520
|
logger.error('--attachment is only valid for one PDF / BIBTEX entry')
|
|
@@ -514,35 +524,37 @@ def addcmd(parser, o, config):
|
|
|
514
524
|
raise PapersExit()
|
|
515
525
|
|
|
516
526
|
if len(o.file) == 0:
|
|
517
|
-
if
|
|
518
|
-
|
|
527
|
+
if o.edit:
|
|
528
|
+
pass
|
|
529
|
+
elif not o.doi:
|
|
530
|
+
logger.error('Please provide either a PDF file or BIBTEX entry or specify `--doi DOI` or `--edit`')
|
|
519
531
|
raise PapersExit()
|
|
520
532
|
elif o.no_query_doi:
|
|
521
533
|
logger.error('If no file is present, --no-query-doi is not compatible with --doi')
|
|
522
534
|
raise PapersExit()
|
|
523
535
|
else:
|
|
524
|
-
biblio.fetch_doi(o.doi, attachments=o.attachment, rename=o.rename, copy=o.copy, **kw)
|
|
536
|
+
entries.extend( biblio.fetch_doi(o.doi, attachments=o.attachment, rename=o.rename, copy=o.copy, **kw) )
|
|
525
537
|
|
|
526
538
|
for file in o.file:
|
|
527
539
|
try:
|
|
528
540
|
if os.path.isdir(file):
|
|
529
541
|
if o.recursive:
|
|
530
|
-
biblio.scan_dir(file, rename=o.rename, copy=o.copy,
|
|
542
|
+
entries.extend( biblio.scan_dir(file, rename=o.rename, copy=o.copy,
|
|
531
543
|
search_doi=not o.no_query_doi,
|
|
532
544
|
search_fulltext=not o.no_query_fulltext,
|
|
533
|
-
**kw)
|
|
545
|
+
**kw) )
|
|
534
546
|
else:
|
|
535
547
|
raise ValueError(file+' is a directory, requires --recursive to explore')
|
|
536
548
|
|
|
537
549
|
elif file.endswith('.pdf'):
|
|
538
|
-
biblio.add_pdf(file, attachments=o.attachment, rename=o.rename, copy=o.copy,
|
|
550
|
+
entries.extend( biblio.add_pdf(file, attachments=o.attachment, rename=o.rename, copy=o.copy,
|
|
539
551
|
search_doi=not o.no_query_doi,
|
|
540
552
|
search_fulltext=not o.no_query_fulltext,
|
|
541
553
|
scholar=o.scholar, doi=o.doi,
|
|
542
|
-
**kw)
|
|
554
|
+
**kw) )
|
|
543
555
|
|
|
544
556
|
else: # file.endswith('.bib'):
|
|
545
|
-
biblio.add_bibtex_file(file, attachments=o.attachment, rename=o.rename, copy=o.copy, **kw)
|
|
557
|
+
entries.extend( biblio.add_bibtex_file(file, attachments=o.attachment, rename=o.rename, copy=o.copy, **kw) )
|
|
546
558
|
|
|
547
559
|
except Exception as error:
|
|
548
560
|
# print(error)
|
|
@@ -554,8 +566,43 @@ def addcmd(parser, o, config):
|
|
|
554
566
|
logger.error('use --ignore to add other files anyway')
|
|
555
567
|
raise PapersExit()
|
|
556
568
|
|
|
569
|
+
# The list of new entries potentially contains duplicates if more than one file is added sequentially
|
|
570
|
+
# If action is required on the added entry, we need to make sure we're consistent with the biblio.
|
|
571
|
+
if len(entries) > 1 and (o.edit or o.open):
|
|
572
|
+
unique_keys = set(biblio.key(e) for e in entries)
|
|
573
|
+
entries = [e for e in biblio.entries if biblio.key(e) in unique_keys]
|
|
574
|
+
|
|
575
|
+
if o.edit:
|
|
576
|
+
if len(entries) == 0:
|
|
577
|
+
entries = [{
|
|
578
|
+
"ID": "EDIT-NEW-KEY",
|
|
579
|
+
"ENTRYTYPE": "article",
|
|
580
|
+
"author": standard_name("John Doe and Jane Roe"),
|
|
581
|
+
"title": "",
|
|
582
|
+
"journal": "",
|
|
583
|
+
"year": "",
|
|
584
|
+
"doi": o.doi or "",
|
|
585
|
+
"file": format_file(o.attachment or [], relative_to=biblio.relative_to),
|
|
586
|
+
}]
|
|
587
|
+
|
|
588
|
+
try:
|
|
589
|
+
otherentries = [e for e in biblio.entries if e not in entries]
|
|
590
|
+
entries = edit_entries(entries)
|
|
591
|
+
entries = [{k:v for k,v in e.items() if v != ""} for e in entries]
|
|
592
|
+
biblio.db.entries = otherentries
|
|
593
|
+
for e in entries:
|
|
594
|
+
biblio.insert_entry(e, **kw)
|
|
595
|
+
|
|
596
|
+
except Exception as error:
|
|
597
|
+
logger.error(str(error))
|
|
598
|
+
return
|
|
599
|
+
|
|
557
600
|
savebib(biblio, config)
|
|
558
601
|
|
|
602
|
+
if o.open:
|
|
603
|
+
for e in entries:
|
|
604
|
+
view_entry_files(biblio, e)
|
|
605
|
+
|
|
559
606
|
def checkcmd(parser, o, config):
|
|
560
607
|
"""
|
|
561
608
|
Loops over the entire bib file that the Papers install sees, and checks each entry for formatting and for the existance of duplicates. Then writes the Biblio object back to your Bibtex file.
|
|
@@ -801,9 +848,7 @@ def listcmd(parser, o, config):
|
|
|
801
848
|
|
|
802
849
|
elif o.open:
|
|
803
850
|
for e in entries:
|
|
804
|
-
|
|
805
|
-
logger.info(f"opening {f} ...")
|
|
806
|
-
view_pdf(f)
|
|
851
|
+
view_entry_files(biblio, e)
|
|
807
852
|
|
|
808
853
|
elif o.field:
|
|
809
854
|
# entries = [{k:e[k] for k in e if k in o.field+['ID','ENTRYTYPE']} for e in entries]
|
|
@@ -985,6 +1030,10 @@ def get_parser(config=None):
|
|
|
985
1030
|
grp.add_argument('-c','--copy', action='store_true',
|
|
986
1031
|
help='copy file instead of moving them')
|
|
987
1032
|
|
|
1033
|
+
grp = addp.add_argument_group('actions')
|
|
1034
|
+
grp.add_argument('-e', '--edit', action='store_true', help='edit entry')
|
|
1035
|
+
grp.add_argument('-o', '--open', action='store_true', help='open files')
|
|
1036
|
+
|
|
988
1037
|
# check
|
|
989
1038
|
# =====
|
|
990
1039
|
checkp = subparsers.add_parser('check', description='check and fix entries',
|
|
@@ -285,8 +285,7 @@ class Biblio:
|
|
|
285
285
|
# additional checks on DOI etc...
|
|
286
286
|
if check_duplicate:
|
|
287
287
|
logger.debug('check duplicates : TRUE')
|
|
288
|
-
self.insert_entry_check(entry, update_key=update_key, rename=rename, copy=copy, **checkopt)
|
|
289
|
-
return
|
|
288
|
+
return self.insert_entry_check(entry, update_key=update_key, rename=rename, copy=copy, **checkopt)
|
|
290
289
|
else:
|
|
291
290
|
logger.debug('check duplicates : FALSE')
|
|
292
291
|
|
|
@@ -312,13 +311,14 @@ class Biblio:
|
|
|
312
311
|
|
|
313
312
|
if rename: self.rename_entry_files(entry, copy=copy)
|
|
314
313
|
|
|
315
|
-
|
|
314
|
+
return [ entry ]
|
|
316
315
|
|
|
316
|
+
def insert_entry_check(self, entry, update_key=False, mergefiles=True, on_conflict='i', rename=False, copy=False):
|
|
317
317
|
duplicates = [e for e in self.entries if self.eq(e, entry)]
|
|
318
318
|
|
|
319
319
|
if not duplicates:
|
|
320
320
|
logger.debug('not a duplicate')
|
|
321
|
-
self.insert_entry(entry, update_key, rename=rename, copy=copy)
|
|
321
|
+
return self.insert_entry(entry, update_key, rename=rename, copy=copy)
|
|
322
322
|
|
|
323
323
|
|
|
324
324
|
else:
|
|
@@ -336,7 +336,7 @@ class Biblio:
|
|
|
336
336
|
logger.debug('exact duplicate')
|
|
337
337
|
if rename: self.rename_entry_files(candidate, copy=copy)
|
|
338
338
|
print("Existing entry:", self.key(entry))
|
|
339
|
-
return
|
|
339
|
+
return [ entry ] # do nothing
|
|
340
340
|
|
|
341
341
|
if update_key and entry['ID'] != candidate['ID']:
|
|
342
342
|
logger.info('duplicate :: update key to match existing entry: {} => {}'.format(entry['ID'], candidate['ID']))
|
|
@@ -355,13 +355,15 @@ class Biblio:
|
|
|
355
355
|
entry = candidate
|
|
356
356
|
if rename: self.rename_entry_files(candidate, copy=copy)
|
|
357
357
|
print("Existing entry:", self.key(entry))
|
|
358
|
-
return
|
|
358
|
+
return [ entry ] # do nothing
|
|
359
359
|
|
|
360
360
|
logger.debug('conflict resolution: '+on_conflict)
|
|
361
361
|
resolved = conflict_resolution_on_insert(candidate, entry, mode=on_conflict)
|
|
362
362
|
self.entries.remove(candidate) # maybe in resolved entries
|
|
363
|
+
entries = []
|
|
363
364
|
for e in resolved:
|
|
364
|
-
self.insert_entry(e, update_key, rename=rename, copy=copy)
|
|
365
|
+
entries.extend( self.insert_entry(e, update_key, rename=rename, copy=copy) )
|
|
366
|
+
return entries
|
|
365
367
|
|
|
366
368
|
|
|
367
369
|
def generate_key(self, entry):
|
|
@@ -389,6 +391,7 @@ class Biblio:
|
|
|
389
391
|
bib = bibtexparser.loads(bibtex)
|
|
390
392
|
if convert_to_unicode:
|
|
391
393
|
bib = bibtexparser.customization.convert_to_unicode(bib)
|
|
394
|
+
entries = []
|
|
392
395
|
for e in bib.entries:
|
|
393
396
|
files = []
|
|
394
397
|
if "file" in e:
|
|
@@ -399,7 +402,8 @@ class Biblio:
|
|
|
399
402
|
if files:
|
|
400
403
|
self.set_files(e, files)
|
|
401
404
|
|
|
402
|
-
self.insert_entry(e, **kw)
|
|
405
|
+
entries.extend( self.insert_entry(e, **kw) )
|
|
406
|
+
return entries
|
|
403
407
|
|
|
404
408
|
|
|
405
409
|
def add_bibtex_file(self, file, **kw):
|
|
@@ -409,8 +413,7 @@ class Biblio:
|
|
|
409
413
|
|
|
410
414
|
def fetch_doi(self, doi, **kw):
|
|
411
415
|
bibtex = fetch_bibtex_by_doi(doi)
|
|
412
|
-
self.add_bibtex(bibtex, **kw)
|
|
413
|
-
|
|
416
|
+
return self.add_bibtex(bibtex, **kw)
|
|
414
417
|
|
|
415
418
|
def add_pdf(self, pdf, attachments=None, search_doi=True, search_fulltext=True, scholar=False, doi=None, **kw):
|
|
416
419
|
|
|
@@ -435,10 +438,10 @@ class Biblio:
|
|
|
435
438
|
|
|
436
439
|
kw.pop('update_key', True)
|
|
437
440
|
# logger.warning('fetched key is always updated when adding PDF to existing bib')
|
|
438
|
-
self.insert_entry(entry, update_key=True, **kw)
|
|
441
|
+
return self.insert_entry(entry, update_key=True, **kw)
|
|
439
442
|
|
|
440
443
|
|
|
441
|
-
def
|
|
444
|
+
def scan_dir_iter(self, direc, search_doi=True, search_fulltext=True, **kw):
|
|
442
445
|
|
|
443
446
|
for root, direcs, files in os.walk(direc):
|
|
444
447
|
dirname = os.path.basename(root)
|
|
@@ -450,7 +453,7 @@ class Biblio:
|
|
|
450
453
|
logger.debug('read from hidden bibtex')
|
|
451
454
|
try:
|
|
452
455
|
entry = read_entry_dir(root, relative_to=self.relative_to)
|
|
453
|
-
self.insert_entry(entry, **kw)
|
|
456
|
+
yield from self.insert_entry(entry, **kw)
|
|
454
457
|
except Exception:
|
|
455
458
|
logger.warning(root+'::'+str(error))
|
|
456
459
|
continue
|
|
@@ -461,13 +464,17 @@ class Biblio:
|
|
|
461
464
|
path = os.path.join(root, file)
|
|
462
465
|
try:
|
|
463
466
|
if file.endswith('.pdf'):
|
|
464
|
-
self.add_pdf(path, search_doi=search_doi, search_fulltext=search_fulltext, **kw)
|
|
467
|
+
yield from self.add_pdf(path, search_doi=search_doi, search_fulltext=search_fulltext, **kw)
|
|
465
468
|
elif file.endswith('.bib'):
|
|
466
|
-
self.add_bibtex_file(path, **kw)
|
|
469
|
+
yield from self.add_bibtex_file(path, **kw)
|
|
467
470
|
except Exception as error:
|
|
468
471
|
logger.warning(path+'::'+str(error))
|
|
469
472
|
continue
|
|
470
473
|
|
|
474
|
+
def scan_dir(self, direc, **kw):
|
|
475
|
+
" like scan_dir_iter but returns a list"
|
|
476
|
+
return list(self.scan_dir_iter(direc, **kw))
|
|
477
|
+
|
|
471
478
|
|
|
472
479
|
def format(self):
|
|
473
480
|
return bibtexparser.dumps(self.db)
|
|
@@ -46,7 +46,7 @@ def groupby_equal(entries, eq=None):
|
|
|
46
46
|
else:
|
|
47
47
|
group = groups[k]
|
|
48
48
|
group.append(e)
|
|
49
|
-
return sorted(groups.items())
|
|
49
|
+
return sorted(groups.items())
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
def search_duplicates(entries, key=None, eq=None, issorted=False, filter_key=None):
|
|
@@ -580,9 +580,7 @@ def conflict_resolution_on_insert(old, new, mode='i'):
|
|
|
580
580
|
(E)dit split (not a duplicate)
|
|
581
581
|
(s)kip
|
|
582
582
|
(a)ppend anyway
|
|
583
|
-
(r)aise'''.strip()
|
|
584
|
-
.replace('(u)','('+_colordiffline('u','+')+')') # green lines will be added
|
|
585
|
-
.replace('(o)','('+_colordiffline('o','-')+')') + bcolors.ENDC
|
|
583
|
+
(r)aise'''.strip() + bcolors.ENDC
|
|
586
584
|
)
|
|
587
585
|
# .replace('(s)','('+_colordiffline('s','-')+')'))
|
|
588
586
|
choices = list('uUoeEsar')
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|