papers-cli 2.14__tar.gz → 2.14.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.
Files changed (45) hide show
  1. {papers_cli-2.14 → papers_cli-2.14.1}/.github/workflows/tests.yml +0 -1
  2. {papers_cli-2.14 → papers_cli-2.14.1}/PKG-INFO +4 -4
  3. {papers_cli-2.14 → papers_cli-2.14.1}/README.md +2 -3
  4. {papers_cli-2.14 → papers_cli-2.14.1}/papers/__main__.py +28 -1
  5. {papers_cli-2.14 → papers_cli-2.14.1}/papers/_version.py +2 -2
  6. {papers_cli-2.14 → papers_cli-2.14.1}/papers/bib.py +6 -8
  7. {papers_cli-2.14 → papers_cli-2.14.1}/papers/config.py +2 -2
  8. {papers_cli-2.14 → papers_cli-2.14.1}/papers/duplicate.py +0 -6
  9. {papers_cli-2.14 → papers_cli-2.14.1}/papers/extract.py +32 -4
  10. {papers_cli-2.14 → papers_cli-2.14.1}/papers/filename.py +34 -15
  11. {papers_cli-2.14 → papers_cli-2.14.1}/papers_cli.egg-info/PKG-INFO +4 -4
  12. {papers_cli-2.14 → papers_cli-2.14.1}/papers_cli.egg-info/requires.txt +1 -0
  13. {papers_cli-2.14 → papers_cli-2.14.1}/pyproject.toml +2 -0
  14. {papers_cli-2.14 → papers_cli-2.14.1}/requirements.txt +1 -0
  15. {papers_cli-2.14 → papers_cli-2.14.1}/tests/test_add.py +2 -2
  16. {papers_cli-2.14 → papers_cli-2.14.1}/tests/test_duplicates.py +1 -1
  17. {papers_cli-2.14 → papers_cli-2.14.1}/.gitattributes +0 -0
  18. {papers_cli-2.14 → papers_cli-2.14.1}/.github/FUNDING.yml +0 -0
  19. {papers_cli-2.14 → papers_cli-2.14.1}/.github/workflows/pypi.yml +0 -0
  20. {papers_cli-2.14 → papers_cli-2.14.1}/.gitignore +0 -0
  21. {papers_cli-2.14 → papers_cli-2.14.1}/LICENSE +0 -0
  22. {papers_cli-2.14 → papers_cli-2.14.1}/papers/__init__.py +0 -0
  23. {papers_cli-2.14 → papers_cli-2.14.1}/papers/encoding.py +0 -0
  24. {papers_cli-2.14 → papers_cli-2.14.1}/papers/latexenc.py +0 -0
  25. {papers_cli-2.14 → papers_cli-2.14.1}/papers/utils.py +0 -0
  26. {papers_cli-2.14 → papers_cli-2.14.1}/papers_cli.egg-info/SOURCES.txt +0 -0
  27. {papers_cli-2.14 → papers_cli-2.14.1}/papers_cli.egg-info/dependency_links.txt +0 -0
  28. {papers_cli-2.14 → papers_cli-2.14.1}/papers_cli.egg-info/entry_points.txt +0 -0
  29. {papers_cli-2.14 → papers_cli-2.14.1}/papers_cli.egg-info/top_level.txt +0 -0
  30. {papers_cli-2.14 → papers_cli-2.14.1}/setup.cfg +0 -0
  31. {papers_cli-2.14 → papers_cli-2.14.1}/setup.py +0 -0
  32. {papers_cli-2.14 → papers_cli-2.14.1}/tests/__init__.py +0 -0
  33. {papers_cli-2.14 → papers_cli-2.14.1}/tests/common.py +0 -0
  34. {papers_cli-2.14 → papers_cli-2.14.1}/tests/config.py +0 -0
  35. {papers_cli-2.14 → papers_cli-2.14.1}/tests/download.py +0 -0
  36. {papers_cli-2.14 → papers_cli-2.14.1}/tests/downloadedpapers/.gitignore +0 -0
  37. {papers_cli-2.14 → papers_cli-2.14.1}/tests/downloadedpapers/bg-8-515-2011.txt +0 -0
  38. {papers_cli-2.14 → papers_cli-2.14.1}/tests/test_biblio.py +0 -0
  39. {papers_cli-2.14 → papers_cli-2.14.1}/tests/test_encoding.py +0 -0
  40. {papers_cli-2.14 → papers_cli-2.14.1}/tests/test_extract.py +0 -0
  41. {papers_cli-2.14 → papers_cli-2.14.1}/tests/test_filecheck.py +0 -0
  42. {papers_cli-2.14 → papers_cli-2.14.1}/tests/test_install.py +0 -0
  43. {papers_cli-2.14 → papers_cli-2.14.1}/tests/test_list.py +0 -0
  44. {papers_cli-2.14 → papers_cli-2.14.1}/tests/test_misc.py +0 -0
  45. {papers_cli-2.14 → papers_cli-2.14.1}/tests/test_undo.py +0 -0
@@ -22,6 +22,5 @@ jobs:
22
22
  run: |
23
23
  python -m pip install --upgrade pip
24
24
  pip install tox tox-gh-actions
25
- sudo apt install poppler-utils
26
25
  - name: Test with tox
27
26
  run: tox
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: papers-cli
3
- Version: 2.14
3
+ Version: 2.14.1
4
4
  Summary: utilities to keep your PDF library organized
5
5
  Author-email: Mahé Perrette <mahe.perrette@gmail.com>
6
6
  License: MIT
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3
10
10
  Requires-Python: >=3.9
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
+ Requires-Dist: PyMuPDF
13
14
  Requires-Dist: unidecode
14
15
  Requires-Dist: crossrefapi
15
16
  Requires-Dist: bibtexparser
@@ -44,7 +45,7 @@ command-line bibliography management tool. Aims:
44
45
  ## Dependencies
45
46
 
46
47
  - python 3.9+
47
- - [poppler-utils](https://en.wikipedia.org/wiki/Poppler_(software)) (only:`pdftotext`): convert PDF to text for parsing
48
+ - [PyMuPDF](https://github.com/pymupdf/PyMuPDF) (preferred) or [poppler-utils](https://en.wikipedia.org/wiki/Poppler_(software)) (only:`pdftotext`; deprecated): convert PDF to text for parsing
48
49
  - [bibtexparser](https://bibtexparser.readthedocs.io) : parse bibtex files
49
50
  - [crossrefapi](https://github.com/fabiobatalha/crossrefapi) : make polite requests to crossref API
50
51
  - [scholarly](https://github.com/OrganicIrradiation/scholarly) : interface for google scholar
@@ -54,7 +55,6 @@ command-line bibliography management tool. Aims:
54
55
  ## Install
55
56
 
56
57
  - `pip install papers-cli`
57
- - install third-party dependencies (Ubuntu: `sudo apt install poppler-utils`)
58
58
 
59
59
  Note there is another project registered on pypi as papers, hence `papers-cli` for command-line-interface.
60
60
 
@@ -311,7 +311,7 @@ There is also a command specifically designed to clean up the zombie files and f
311
311
  papers filecheck --clean-filesdir
312
312
 
313
313
  That command will ask before removig anything, unless `--force` is passed. Currently
314
- it ignores hidden files and folders, and will only consider folder that have a `.{folder}.bib` file inside,
314
+ it ignores hidden files and folders, and will only consider folder that have a `.{folder}.bib` file inside,
315
315
  which is the convention `papers` follows to store multiple attachments. That command works best
316
316
  when the files are in their own folder, and not mixed up with other things, obviously.
317
317
 
@@ -25,7 +25,7 @@ command-line bibliography management tool. Aims:
25
25
  ## Dependencies
26
26
 
27
27
  - python 3.9+
28
- - [poppler-utils](https://en.wikipedia.org/wiki/Poppler_(software)) (only:`pdftotext`): convert PDF to text for parsing
28
+ - [PyMuPDF](https://github.com/pymupdf/PyMuPDF) (preferred) or [poppler-utils](https://en.wikipedia.org/wiki/Poppler_(software)) (only:`pdftotext`; deprecated): convert PDF to text for parsing
29
29
  - [bibtexparser](https://bibtexparser.readthedocs.io) : parse bibtex files
30
30
  - [crossrefapi](https://github.com/fabiobatalha/crossrefapi) : make polite requests to crossref API
31
31
  - [scholarly](https://github.com/OrganicIrradiation/scholarly) : interface for google scholar
@@ -35,7 +35,6 @@ command-line bibliography management tool. Aims:
35
35
  ## Install
36
36
 
37
37
  - `pip install papers-cli`
38
- - install third-party dependencies (Ubuntu: `sudo apt install poppler-utils`)
39
38
 
40
39
  Note there is another project registered on pypi as papers, hence `papers-cli` for command-line-interface.
41
40
 
@@ -292,7 +291,7 @@ There is also a command specifically designed to clean up the zombie files and f
292
291
  papers filecheck --clean-filesdir
293
292
 
294
293
  That command will ask before removig anything, unless `--force` is passed. Currently
295
- it ignores hidden files and folders, and will only consider folder that have a `.{folder}.bib` file inside,
294
+ it ignores hidden files and folders, and will only consider folder that have a `.{folder}.bib` file inside,
296
295
  which is the convention `papers` follows to store multiple attachments. That command works best
297
296
  when the files are in their own folder, and not mixed up with other things, obviously.
298
297
 
@@ -1,6 +1,7 @@
1
1
  """That is the script called by the papers cli command
2
2
  """
3
3
  import os
4
+ import copy
4
5
  import sys
5
6
  from pathlib import Path
6
7
  import logging
@@ -80,6 +81,8 @@ def _backup_bib(biblio, config, message=None):
80
81
  shutil.copy(config.bibtex, config.backupfile)
81
82
  config.gitcmd(f"add {config.backupfile.name}")
82
83
 
84
+ biblio = copy.deepcopy(biblio)
85
+
83
86
  if config.backup_files:
84
87
  logger.info('backup bibliography with files')
85
88
  backupfilesdir = backupdir/"files"
@@ -561,6 +564,7 @@ def addcmd(parser, o, config):
561
564
  set_keyformat_config_from_cmd(o, config)
562
565
 
563
566
  biblio = get_biblio(config)
567
+ biblio_init = copy.deepcopy(biblio)
564
568
 
565
569
  entries = []
566
570
 
@@ -650,10 +654,33 @@ def addcmd(parser, o, config):
650
654
  return
651
655
 
652
656
  entries = [{k:v for k,v in e.items() if v != ""} for e in entries]
653
- biblio.db.entries = otherentries + entries
657
+ biblio.db.entries = sorted(otherentries + entries, key=lambda e: biblio.key(e))
654
658
 
655
659
  savebib(biblio, config)
656
660
 
661
+ # compare entries to inform user
662
+ # this is not very efficient but I have yet to hear a complaint about speed
663
+
664
+ old_set = set(e["ID"] for e in biblio_init.entries)
665
+ old_entries_by_key = {e["ID"]:e for e in biblio_init.db.entries}
666
+ new_set = set(e["ID"] for e in biblio.entries)
667
+ new_entries_by_key = {e["ID"]:e for e in biblio.db.entries}
668
+ modified_set = set(e["ID"] for e in entries ).intersection(set.intersection(old_set, new_set))
669
+
670
+ for ID in sorted(old_set - new_set):
671
+ print(format_entry(biblio_init, old_entries_by_key[ID], prefix="Removed"))
672
+
673
+ for ID in sorted(new_set - old_set):
674
+ print(format_entry(biblio, new_entries_by_key[ID], prefix="Added"))
675
+
676
+ for ID in sorted(modified_set):
677
+ before = old_entries_by_key[ID]
678
+ after = new_entries_by_key[ID]
679
+ if before != after:
680
+ print(format_entry(biblio, after, prefix="Modified"))
681
+ else:
682
+ print(format_entry(biblio, after, prefix="Existing"))
683
+
657
684
  if o.open:
658
685
  for e in entries:
659
686
  view_entry_files(biblio, e)
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '2.14'
21
- __version_tuple__ = version_tuple = (2, 14)
20
+ __version__ = version = '2.14.1'
21
+ __version_tuple__ = version_tuple = (2, 14, 1)
@@ -273,7 +273,6 @@ class Biblio:
273
273
  keys = [self.key(ei) for ei in self.db.entries]
274
274
  return bisect.bisect_left(keys, self.key(entry))
275
275
 
276
-
277
276
  def insert_entry(self, entry, update_key=False, check_duplicate=False, rename=False, copy=False, metadata={}, **checkopt):
278
277
  """
279
278
  """
@@ -296,20 +295,21 @@ class Biblio:
296
295
  if i < len(self.entries) and self.key(self.entries[i]) == self.key(entry):
297
296
  logger.info('key duplicate: '+self.key(self.entries[i]))
298
297
 
298
+ if self.entries[i]["title"] == entry["title"]:
299
+ logger.info('exact duplicate')
300
+ return [ self.entries[i] ]
301
+
299
302
  if update_key:
300
303
  newkey = self.append_abc_to_key(entry) # add abc
301
304
  logger.info('update key: {} => {}'.format(entry['ID'], newkey))
302
305
  entry['ID'] = newkey
303
306
 
304
307
  else:
305
- print(format_entry(self, entry, prefix="New entry"))
306
- print(format_entry(self, self.entries[i], prefix="Conflicts with"))
307
308
  raise DuplicateKeyError('Key conflict. Try update_key is True or specify --key KEY explicitly')
308
309
 
309
310
  else:
310
311
  logger.info('new entry: '+self.key(entry))
311
312
 
312
- print(format_entry(self, entry, prefix="Added"))
313
313
  self.entries.insert(i, entry)
314
314
 
315
315
  if rename: self.rename_entry_files(entry, copy=copy)
@@ -338,7 +338,6 @@ class Biblio:
338
338
  if entry == candidate:
339
339
  logger.debug('exact duplicate')
340
340
  if rename: self.rename_entry_files(candidate, copy=copy)
341
- print(format_entry(self, entry, prefix="Existing"))
342
341
  return [ entry ] # do nothing
343
342
 
344
343
  if update_key and entry['ID'] != candidate['ID']:
@@ -357,7 +356,6 @@ class Biblio:
357
356
  logger.debug('fixed: exact duplicate')
358
357
  entry = candidate
359
358
  if rename: self.rename_entry_files(candidate, copy=copy)
360
- print(format_entry(self, entry, prefix="Existing"))
361
359
  return [ entry ] # do nothing
362
360
 
363
361
  logger.debug('conflict resolution: '+on_conflict)
@@ -438,10 +436,10 @@ class Biblio:
438
436
  files += attachments
439
437
 
440
438
  self.set_files(entry, [os.path.abspath(f) for f in files])
441
- entry['ID'] = self.generate_key(entry)
439
+ entry['ID'] = self.keyformat(entry)
442
440
  logger.debug('generated PDF key: '+entry['ID'])
443
441
 
444
- kw["update_key"] = False # already updated
442
+ kw["update_key"] = True
445
443
  return self.insert_entry(entry, **kw)
446
444
 
447
445
 
@@ -143,9 +143,9 @@ class Config:
143
143
  def load(cls, the_file):
144
144
  js = json.load(open(the_file))
145
145
  if 'nameformat' in js:
146
- js['nameformat'] = Format(**js.get('nameformat'))
146
+ js['nameformat'] = Format(**{**vars(NAMEFORMAT), **js.get('nameformat')})
147
147
  if 'keyformat' in js:
148
- js['keyformat'] = Format(**js.get('keyformat'))
148
+ js['keyformat'] = Format(**{**vars(KEYFORMAT), **js.get('keyformat')})
149
149
  cfg = cls(file=the_file, **js)
150
150
  cfg._update_paths_to_absolute()
151
151
  return cfg
@@ -367,12 +367,6 @@ def edit_entries(entries, diff=False, ndiff=False, editor=None):
367
367
  if res == 0:
368
368
  logger.info('sucessfully edited file, insert edited entries')
369
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
370
  return db_updated.entries
377
371
 
378
372
  else:
@@ -26,12 +26,28 @@ class DOIRequestError(ValueError):
26
26
 
27
27
  # PDF parsing / crossref requests
28
28
  # ===============================
29
+ def readpdf_fitz(pdf_path, pages=None, first=None, last=None):
30
+ import fitz
29
31
 
30
- def readpdf(pdf, first=None, last=None):
31
- # TODO the python package pdftotext can do this directly, with no temp file and no I/O.
32
- if not os.path.isfile(pdf):
33
- raise ValueError(repr(pdf) + ": not a file")
32
+ # Open the PDF file
33
+ document = fitz.open(pdf_path)
34
+ text = ""
35
+
36
+ # Iterate through the pages
37
+ for page_num in range(len(document)):
38
+ if pages is not None and page_num+1 not in pages:
39
+ continue
40
+ elif first is not None and page_num+1 < first:
41
+ continue
42
+ elif last is not None and page_num+1 > last:
43
+ continue
44
+ page = document.load_page(page_num)
45
+ text += page.get_text()
46
+
47
+ return text
34
48
 
49
+ def readpdf_poputils(pdf, first=None, last=None, pages=None):
50
+ # DEPRECATED
35
51
  tmptxt = tempfile.mktemp(suffix='.txt')
36
52
 
37
53
  cmd = ['pdftotext']
@@ -46,6 +62,18 @@ def readpdf(pdf, first=None, last=None):
46
62
 
47
63
  return txt
48
64
 
65
+ def readpdf(pdf, first=None, last=None):
66
+ # TODO the python package pdftotext can do this directly, with no temp file and no I/O.
67
+ if not os.path.isfile(pdf):
68
+ raise ValueError(repr(pdf) + ": not a file")
69
+ try:
70
+ return readpdf_fitz(pdf, first=first, last=last)
71
+ except ImportError:
72
+ logger.warning("PyMuPDF not installed, using pdftotext")
73
+ return readpdf_fitz(pdf, first=first, last=last)
74
+
75
+ return txt
76
+
49
77
 
50
78
  def readpdf_image(pdf, first=None, last=None):
51
79
 
@@ -22,6 +22,10 @@ def _cite_author(names):
22
22
  else:
23
23
  return names[0]
24
24
 
25
+ UNKNOWN_AUTHOR = 'unknown'
26
+ UNKNOWN_YEAR = '0000'
27
+ UNKNOWN_JOURNAL = None
28
+ UNKNOWN_TITLE = ""
25
29
 
26
30
  def make_template_fields(
27
31
  entry,
@@ -31,6 +35,7 @@ def make_template_fields(
31
35
  title_length=100,
32
36
  author_sep="_",
33
37
  title_sep="-",
38
+ **ignore,
34
39
  ):
35
40
  """
36
41
  Available fields in output are explicitly listed here, and this is the single source of truth for these.
@@ -43,10 +48,12 @@ def make_template_fields(
43
48
  - Title: same as title by with capitalized words
44
49
  - year
45
50
  - ID : bibtex key
51
+ - doi : bibtex DOI
52
+ - doi- : slugified doi (for use in filenames)
46
53
  Each one of these needs a specific, explicit assignment below.
47
54
  """
48
55
  # names = bibtexparser.customization.getnames(entry.get('author','unknown').lower().split(' and '))
49
- _names = family_names(entry.get("author", "unknown").lower())
56
+ _names = family_names(entry.get("author", UNKNOWN_AUTHOR).lower())
50
57
  _names = [slugify(nm) for nm in _names]
51
58
  author = author_sep.join([nm for nm in _names[:author_num]])
52
59
  Author = author_sep.join([nm.capitalize() for nm in _names[:author_num]])
@@ -54,13 +61,13 @@ def make_template_fields(
54
61
  authorX = AuthorX.lower()
55
62
 
56
63
  # a thing that's not a bibtex article won't have a journal
57
- journal = entry.get("journal", None)
64
+ journal = entry.get("journal", UNKNOWN_JOURNAL)
58
65
 
59
- year = str(entry.get("year", "0000"))
66
+ year = str(entry.get("year", UNKNOWN_YEAR))
60
67
 
61
68
  if not title_word_num or not entry.get("title", ""):
62
- title = ""
63
- Title = ""
69
+ title = UNKNOWN_TITLE
70
+ Title = UNKNOWN_TITLE
64
71
  else:
65
72
  titlewords = normalize(entry["title"]).lower().split()
66
73
  _titles = listtag(
@@ -84,15 +91,12 @@ def make_template_fields(
84
91
  "title": title,
85
92
  "Title": Title,
86
93
  "ID": entry.get("ID"),
94
+ "doi": entry.get("doi"),
95
+ "doi_": slugify(entry.get("doi", "")),
96
+ "doi_or_id": slugify(entry.get("doi", entry.get("ID", ""))),
87
97
  }
88
98
 
89
99
 
90
- def stringify_entry(entry, template, **opt):
91
- fields = make_template_fields(entry, **opt)
92
- res = template.format(**fields)
93
- return res
94
-
95
-
96
100
  class Format:
97
101
  """
98
102
  Store formatting info as python template formatted with str.format() method. See make_template_fields for available fields.
@@ -113,7 +117,8 @@ class Format:
113
117
 
114
118
  """
115
119
  # def __init__(self, template="{author}{year}{title}", author_num=2, title_word_num=5, author_sep="_", title_sep="-")
116
- def __init__(self, template, author_num=2, title_word_num=100, title_word_size=1, title_length=100, author_sep="_", title_sep="-"):
120
+ def __init__(self, template, author_num=2, title_word_num=100, title_word_size=1, title_length=100,
121
+ author_sep="_", title_sep="-", unknown_strict=False, unknown_template=None):
117
122
  self.template = template
118
123
  self.author_num = author_num
119
124
  self.author_sep = author_sep
@@ -121,16 +126,30 @@ class Format:
121
126
  self.title_sep = title_sep
122
127
  self.title_word_num = title_word_num
123
128
  self.title_word_size = title_word_size
129
+ self.unknown_strict = unknown_strict
130
+ self.unknown_template = unknown_template
124
131
 
125
132
  def todict(self):
126
133
  return vars(self)
127
134
 
135
+ def is_unknown(self, entry):
136
+ conditions = ( entry.get("author", UNKNOWN_AUTHOR) == UNKNOWN_AUTHOR, entry.get("year", UNKNOWN_YEAR) == UNKNOWN_YEAR, entry.get("title", UNKNOWN_TITLE) == UNKNOWN_TITLE)
137
+ if self.unknown_strict:
138
+ return all(conditions)
139
+ else:
140
+ return any(conditions)
141
+
128
142
  def render(self, **entry):
129
- return stringify_entry(entry, **vars(self))
143
+ fields = make_template_fields(entry, **vars(self))
144
+
145
+ if self.unknown_template and self.is_unknown(entry):
146
+ return self.unknown_template.format(**fields)
147
+
148
+ return self.template.format(**fields)
130
149
 
131
150
  def __call__(self, entry):
132
151
  return self.render(**entry)
133
152
 
134
153
 
135
- KEYFORMAT = Format(template='{author}{year}', author_num=2, author_sep="_")
136
- NAMEFORMAT = Format(template='{authorX}_{year}_{title}', author_sep="_", title_sep="-")
154
+ KEYFORMAT = Format(template='{author}{year}', author_num=2, author_sep="_", unknown_template="{doi}")
155
+ NAMEFORMAT = Format(template='{authorX}_{year}_{title}', author_sep="_", title_sep="-", unknown_template="unknown_{doi_or_id}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: papers-cli
3
- Version: 2.14
3
+ Version: 2.14.1
4
4
  Summary: utilities to keep your PDF library organized
5
5
  Author-email: Mahé Perrette <mahe.perrette@gmail.com>
6
6
  License: MIT
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3
10
10
  Requires-Python: >=3.9
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
+ Requires-Dist: PyMuPDF
13
14
  Requires-Dist: unidecode
14
15
  Requires-Dist: crossrefapi
15
16
  Requires-Dist: bibtexparser
@@ -44,7 +45,7 @@ command-line bibliography management tool. Aims:
44
45
  ## Dependencies
45
46
 
46
47
  - python 3.9+
47
- - [poppler-utils](https://en.wikipedia.org/wiki/Poppler_(software)) (only:`pdftotext`): convert PDF to text for parsing
48
+ - [PyMuPDF](https://github.com/pymupdf/PyMuPDF) (preferred) or [poppler-utils](https://en.wikipedia.org/wiki/Poppler_(software)) (only:`pdftotext`; deprecated): convert PDF to text for parsing
48
49
  - [bibtexparser](https://bibtexparser.readthedocs.io) : parse bibtex files
49
50
  - [crossrefapi](https://github.com/fabiobatalha/crossrefapi) : make polite requests to crossref API
50
51
  - [scholarly](https://github.com/OrganicIrradiation/scholarly) : interface for google scholar
@@ -54,7 +55,6 @@ command-line bibliography management tool. Aims:
54
55
  ## Install
55
56
 
56
57
  - `pip install papers-cli`
57
- - install third-party dependencies (Ubuntu: `sudo apt install poppler-utils`)
58
58
 
59
59
  Note there is another project registered on pypi as papers, hence `papers-cli` for command-line-interface.
60
60
 
@@ -311,7 +311,7 @@ There is also a command specifically designed to clean up the zombie files and f
311
311
  papers filecheck --clean-filesdir
312
312
 
313
313
  That command will ask before removig anything, unless `--force` is passed. Currently
314
- it ignores hidden files and folders, and will only consider folder that have a `.{folder}.bib` file inside,
314
+ it ignores hidden files and folders, and will only consider folder that have a `.{folder}.bib` file inside,
315
315
  which is the convention `papers` follows to store multiple attachments. That command works best
316
316
  when the files are in their own folder, and not mixed up with other things, obviously.
317
317
 
@@ -1,3 +1,4 @@
1
+ PyMuPDF
1
2
  unidecode
2
3
  crossrefapi
3
4
  bibtexparser
@@ -16,6 +16,7 @@ classifiers = [
16
16
  "Programming Language :: Python :: 3",
17
17
  ]
18
18
  dependencies = [
19
+ "PyMuPDF",
19
20
  "unidecode",
20
21
  "crossrefapi",
21
22
  "bibtexparser",
@@ -58,6 +59,7 @@ python =
58
59
  commands = pytest --cov=papers --cov-append --cov-report=term-missing {posargs} -xv
59
60
  deps =
60
61
  bibtexparser < 2.0.0
62
+ PyMuPDF
61
63
  scholarly
62
64
  crossrefapi
63
65
  rapidfuzz
@@ -1,3 +1,4 @@
1
+ PyMuPDF
1
2
  unidecode
2
3
  crossrefapi
3
4
  bibtexparser < 2.0.0
@@ -305,7 +305,7 @@ class TestAddConflict(BibTest):
305
305
  title = {Something else entirely}
306
306
  }"""
307
307
 
308
- bibtex_conflict_key_fixed = """@article{perrette_author0000,
308
+ bibtex_conflict_key_fixed = """@article{10.5194/bg-8-515-2011XXX,
309
309
  author = {M. Perrette and Another author},
310
310
  doi = {10.5194/bg-8-515-2011XXX},
311
311
  title = {Something else entirely}
@@ -416,7 +416,7 @@ class TestAddConflict(BibTest):
416
416
  # key conflict and update entry
417
417
  open(self.otherbib, 'w').write(self.bibtex_conflict_key)
418
418
  paperscmd(f'add {self.otherbib} --bibtex {self.mybib} -u')
419
- expected = self.bibtex+'\n\n'+self.bibtex_conflict_key_fixed
419
+ expected = self.bibtex_conflict_key_fixed+'\n\n'+self.bibtex
420
420
  self.assertMultiLineEqual(open(self.mybib).read().strip(), expected) # entries did not change
421
421
 
422
422
  def test_add_same_doi_unchecked(self):
@@ -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{fieldRareYear,
269
+ expected = """@article{10.5194/bg-8-515-2011,
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