papers-cli 2.6__tar.gz → 2.7__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.
Files changed (44) hide show
  1. papers_cli-2.7/.github/workflows/pypi.yml +36 -0
  2. {papers_cli-2.6 → papers_cli-2.7}/PKG-INFO +1 -1
  3. {papers_cli-2.6 → papers_cli-2.7}/papers/__main__.py +61 -12
  4. {papers_cli-2.6 → papers_cli-2.7}/papers/_version.py +2 -2
  5. {papers_cli-2.6 → papers_cli-2.7}/papers/bib.py +23 -15
  6. {papers_cli-2.6 → papers_cli-2.7}/papers/duplicate.py +1 -1
  7. {papers_cli-2.6 → papers_cli-2.7}/papers_cli.egg-info/PKG-INFO +1 -1
  8. {papers_cli-2.6 → papers_cli-2.7}/papers_cli.egg-info/SOURCES.txt +1 -0
  9. {papers_cli-2.6 → papers_cli-2.7}/.gitattributes +0 -0
  10. {papers_cli-2.6 → papers_cli-2.7}/.github/workflows/tests.yml +0 -0
  11. {papers_cli-2.6 → papers_cli-2.7}/.gitignore +0 -0
  12. {papers_cli-2.6 → papers_cli-2.7}/LICENSE +0 -0
  13. {papers_cli-2.6 → papers_cli-2.7}/README.md +0 -0
  14. {papers_cli-2.6 → papers_cli-2.7}/papers/__init__.py +0 -0
  15. {papers_cli-2.6 → papers_cli-2.7}/papers/config.py +0 -0
  16. {papers_cli-2.6 → papers_cli-2.7}/papers/encoding.py +0 -0
  17. {papers_cli-2.6 → papers_cli-2.7}/papers/extract.py +0 -0
  18. {papers_cli-2.6 → papers_cli-2.7}/papers/filename.py +0 -0
  19. {papers_cli-2.6 → papers_cli-2.7}/papers/latexenc.py +0 -0
  20. {papers_cli-2.6 → papers_cli-2.7}/papers/utils.py +0 -0
  21. {papers_cli-2.6 → papers_cli-2.7}/papers_cli.egg-info/dependency_links.txt +0 -0
  22. {papers_cli-2.6 → papers_cli-2.7}/papers_cli.egg-info/entry_points.txt +0 -0
  23. {papers_cli-2.6 → papers_cli-2.7}/papers_cli.egg-info/requires.txt +0 -0
  24. {papers_cli-2.6 → papers_cli-2.7}/papers_cli.egg-info/top_level.txt +0 -0
  25. {papers_cli-2.6 → papers_cli-2.7}/pyproject.toml +0 -0
  26. {papers_cli-2.6 → papers_cli-2.7}/requirements.txt +0 -0
  27. {papers_cli-2.6 → papers_cli-2.7}/setup.cfg +0 -0
  28. {papers_cli-2.6 → papers_cli-2.7}/setup.py +0 -0
  29. {papers_cli-2.6 → papers_cli-2.7}/tests/__init__.py +0 -0
  30. {papers_cli-2.6 → papers_cli-2.7}/tests/common.py +0 -0
  31. {papers_cli-2.6 → papers_cli-2.7}/tests/config.py +0 -0
  32. {papers_cli-2.6 → papers_cli-2.7}/tests/download.py +0 -0
  33. {papers_cli-2.6 → papers_cli-2.7}/tests/downloadedpapers/.gitignore +0 -0
  34. {papers_cli-2.6 → papers_cli-2.7}/tests/downloadedpapers/bg-8-515-2011.txt +0 -0
  35. {papers_cli-2.6 → papers_cli-2.7}/tests/test_add.py +0 -0
  36. {papers_cli-2.6 → papers_cli-2.7}/tests/test_biblio.py +0 -0
  37. {papers_cli-2.6 → papers_cli-2.7}/tests/test_duplicates.py +0 -0
  38. {papers_cli-2.6 → papers_cli-2.7}/tests/test_encoding.py +0 -0
  39. {papers_cli-2.6 → papers_cli-2.7}/tests/test_extract.py +0 -0
  40. {papers_cli-2.6 → papers_cli-2.7}/tests/test_filecheck.py +0 -0
  41. {papers_cli-2.6 → papers_cli-2.7}/tests/test_install.py +0 -0
  42. {papers_cli-2.6 → papers_cli-2.7}/tests/test_list.py +0 -0
  43. {papers_cli-2.6 → papers_cli-2.7}/tests/test_misc.py +0 -0
  44. {papers_cli-2.6 → papers_cli-2.7}/tests/test_undo.py +0 -0
@@ -0,0 +1,36 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - '*'
7
+
8
+ jobs:
9
+ build-and-publish:
10
+ runs-on: ubuntu-latest
11
+ environment:
12
+ name: pypi
13
+ permissions:
14
+ id-token: write # This is required for OIDC
15
+ contents: read
16
+
17
+ steps:
18
+ - name: Checkout code
19
+ uses: actions/checkout@v2
20
+
21
+ - name: Set up Python
22
+ uses: actions/setup-python@v2
23
+ with:
24
+ python-version: '3.x'
25
+
26
+ - name: Install dependencies
27
+ run: |
28
+ python -m pip install --upgrade pip
29
+ pip install setuptools wheel twine
30
+
31
+ - name: Build distribution
32
+ run: |
33
+ python setup.py sdist bdist_wheel
34
+
35
+ - name: Publish to PyPI
36
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: papers-cli
3
- Version: 2.6
3
+ Version: 2.7
4
4
  Summary: utilities to keep your PDF library organized
5
5
  Author-email: Mahé Perrette <mahe.perrette@gmail.com>
6
6
  License: MIT
@@ -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 not o.doi:
518
- logger.error('Please provide either a PDF file or BIBTEX entry or specify `--doi DOI`')
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
- for f in biblio.get_files(e):
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',
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '2.6'
16
- __version_tuple__ = version_tuple = (2, 6)
15
+ __version__ = version = '2.7'
16
+ __version_tuple__ = version_tuple = (2, 7)
@@ -285,8 +285,8 @@ 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
+ print("entering...")
289
+ return self.insert_entry_check(entry, update_key=update_key, rename=rename, copy=copy, **checkopt)
290
290
  else:
291
291
  logger.debug('check duplicates : FALSE')
292
292
 
@@ -312,13 +312,14 @@ class Biblio:
312
312
 
313
313
  if rename: self.rename_entry_files(entry, copy=copy)
314
314
 
315
- def insert_entry_check(self, entry, update_key=False, mergefiles=True, on_conflict='i', rename=False, copy=False):
315
+ return [ entry ]
316
316
 
317
+ def insert_entry_check(self, entry, update_key=False, mergefiles=True, on_conflict='i', rename=False, copy=False):
317
318
  duplicates = [e for e in self.entries if self.eq(e, entry)]
318
319
 
319
320
  if not duplicates:
320
321
  logger.debug('not a duplicate')
321
- self.insert_entry(entry, update_key, rename=rename, copy=copy)
322
+ return self.insert_entry(entry, update_key, rename=rename, copy=copy)
322
323
 
323
324
 
324
325
  else:
@@ -336,7 +337,7 @@ class Biblio:
336
337
  logger.debug('exact duplicate')
337
338
  if rename: self.rename_entry_files(candidate, copy=copy)
338
339
  print("Existing entry:", self.key(entry))
339
- return # do nothing
340
+ return [ entry ] # do nothing
340
341
 
341
342
  if update_key and entry['ID'] != candidate['ID']:
342
343
  logger.info('duplicate :: update key to match existing entry: {} => {}'.format(entry['ID'], candidate['ID']))
@@ -355,13 +356,15 @@ class Biblio:
355
356
  entry = candidate
356
357
  if rename: self.rename_entry_files(candidate, copy=copy)
357
358
  print("Existing entry:", self.key(entry))
358
- return # do nothing
359
+ return [ entry ] # do nothing
359
360
 
360
361
  logger.debug('conflict resolution: '+on_conflict)
361
362
  resolved = conflict_resolution_on_insert(candidate, entry, mode=on_conflict)
362
363
  self.entries.remove(candidate) # maybe in resolved entries
364
+ entries = []
363
365
  for e in resolved:
364
- self.insert_entry(e, update_key, rename=rename, copy=copy)
366
+ entries.extend( self.insert_entry(e, update_key, rename=rename, copy=copy) )
367
+ return entries
365
368
 
366
369
 
367
370
  def generate_key(self, entry):
@@ -389,6 +392,7 @@ class Biblio:
389
392
  bib = bibtexparser.loads(bibtex)
390
393
  if convert_to_unicode:
391
394
  bib = bibtexparser.customization.convert_to_unicode(bib)
395
+ entries = []
392
396
  for e in bib.entries:
393
397
  files = []
394
398
  if "file" in e:
@@ -399,7 +403,8 @@ class Biblio:
399
403
  if files:
400
404
  self.set_files(e, files)
401
405
 
402
- self.insert_entry(e, **kw)
406
+ entries.extend( self.insert_entry(e, **kw) )
407
+ return entries
403
408
 
404
409
 
405
410
  def add_bibtex_file(self, file, **kw):
@@ -409,8 +414,7 @@ class Biblio:
409
414
 
410
415
  def fetch_doi(self, doi, **kw):
411
416
  bibtex = fetch_bibtex_by_doi(doi)
412
- self.add_bibtex(bibtex, **kw)
413
-
417
+ return self.add_bibtex(bibtex, **kw)
414
418
 
415
419
  def add_pdf(self, pdf, attachments=None, search_doi=True, search_fulltext=True, scholar=False, doi=None, **kw):
416
420
 
@@ -435,10 +439,10 @@ class Biblio:
435
439
 
436
440
  kw.pop('update_key', True)
437
441
  # logger.warning('fetched key is always updated when adding PDF to existing bib')
438
- self.insert_entry(entry, update_key=True, **kw)
442
+ return self.insert_entry(entry, update_key=True, **kw)
439
443
 
440
444
 
441
- def scan_dir(self, direc, search_doi=True, search_fulltext=True, **kw):
445
+ def scan_dir_iter(self, direc, search_doi=True, search_fulltext=True, **kw):
442
446
 
443
447
  for root, direcs, files in os.walk(direc):
444
448
  dirname = os.path.basename(root)
@@ -450,7 +454,7 @@ class Biblio:
450
454
  logger.debug('read from hidden bibtex')
451
455
  try:
452
456
  entry = read_entry_dir(root, relative_to=self.relative_to)
453
- self.insert_entry(entry, **kw)
457
+ yield from self.insert_entry(entry, **kw)
454
458
  except Exception:
455
459
  logger.warning(root+'::'+str(error))
456
460
  continue
@@ -461,13 +465,17 @@ class Biblio:
461
465
  path = os.path.join(root, file)
462
466
  try:
463
467
  if file.endswith('.pdf'):
464
- self.add_pdf(path, search_doi=search_doi, search_fulltext=search_fulltext, **kw)
468
+ yield from self.add_pdf(path, search_doi=search_doi, search_fulltext=search_fulltext, **kw)
465
469
  elif file.endswith('.bib'):
466
- self.add_bibtex_file(path, **kw)
470
+ yield from self.add_bibtex_file(path, **kw)
467
471
  except Exception as error:
468
472
  logger.warning(path+'::'+str(error))
469
473
  continue
470
474
 
475
+ def scan_dir(self, direc, **kw):
476
+ " like scan_dir_iter but returns a list"
477
+ return list(self.scan_dir_iter(direc, **kw))
478
+
471
479
 
472
480
  def format(self):
473
481
  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):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: papers-cli
3
- Version: 2.6
3
+ Version: 2.7
4
4
  Summary: utilities to keep your PDF library organized
5
5
  Author-email: Mahé Perrette <mahe.perrette@gmail.com>
6
6
  License: MIT
@@ -5,6 +5,7 @@ README.md
5
5
  pyproject.toml
6
6
  requirements.txt
7
7
  setup.py
8
+ .github/workflows/pypi.yml
8
9
  .github/workflows/tests.yml
9
10
  papers/__init__.py
10
11
  papers/__main__.py
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