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.
Files changed (44) hide show
  1. {papers_cli-2.9.1 → papers_cli-2.11}/PKG-INFO +1 -1
  2. {papers_cli-2.9.1 → papers_cli-2.11}/papers/__main__.py +47 -38
  3. {papers_cli-2.9.1 → papers_cli-2.11}/papers/_version.py +2 -2
  4. {papers_cli-2.9.1 → papers_cli-2.11}/papers/bib.py +16 -7
  5. {papers_cli-2.9.1 → papers_cli-2.11}/papers/duplicate.py +11 -6
  6. {papers_cli-2.9.1 → papers_cli-2.11}/papers/extract.py +107 -63
  7. {papers_cli-2.9.1 → papers_cli-2.11}/papers_cli.egg-info/PKG-INFO +1 -1
  8. {papers_cli-2.9.1 → papers_cli-2.11}/tests/common.py +16 -22
  9. {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_add.py +1 -1
  10. {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_duplicates.py +1 -1
  11. {papers_cli-2.9.1 → papers_cli-2.11}/.gitattributes +0 -0
  12. {papers_cli-2.9.1 → papers_cli-2.11}/.github/workflows/pypi.yml +0 -0
  13. {papers_cli-2.9.1 → papers_cli-2.11}/.github/workflows/tests.yml +0 -0
  14. {papers_cli-2.9.1 → papers_cli-2.11}/.gitignore +0 -0
  15. {papers_cli-2.9.1 → papers_cli-2.11}/LICENSE +0 -0
  16. {papers_cli-2.9.1 → papers_cli-2.11}/README.md +0 -0
  17. {papers_cli-2.9.1 → papers_cli-2.11}/papers/__init__.py +0 -0
  18. {papers_cli-2.9.1 → papers_cli-2.11}/papers/config.py +0 -0
  19. {papers_cli-2.9.1 → papers_cli-2.11}/papers/encoding.py +0 -0
  20. {papers_cli-2.9.1 → papers_cli-2.11}/papers/filename.py +0 -0
  21. {papers_cli-2.9.1 → papers_cli-2.11}/papers/latexenc.py +0 -0
  22. {papers_cli-2.9.1 → papers_cli-2.11}/papers/utils.py +0 -0
  23. {papers_cli-2.9.1 → papers_cli-2.11}/papers_cli.egg-info/SOURCES.txt +0 -0
  24. {papers_cli-2.9.1 → papers_cli-2.11}/papers_cli.egg-info/dependency_links.txt +0 -0
  25. {papers_cli-2.9.1 → papers_cli-2.11}/papers_cli.egg-info/entry_points.txt +0 -0
  26. {papers_cli-2.9.1 → papers_cli-2.11}/papers_cli.egg-info/requires.txt +0 -0
  27. {papers_cli-2.9.1 → papers_cli-2.11}/papers_cli.egg-info/top_level.txt +0 -0
  28. {papers_cli-2.9.1 → papers_cli-2.11}/pyproject.toml +0 -0
  29. {papers_cli-2.9.1 → papers_cli-2.11}/requirements.txt +0 -0
  30. {papers_cli-2.9.1 → papers_cli-2.11}/setup.cfg +0 -0
  31. {papers_cli-2.9.1 → papers_cli-2.11}/setup.py +0 -0
  32. {papers_cli-2.9.1 → papers_cli-2.11}/tests/__init__.py +0 -0
  33. {papers_cli-2.9.1 → papers_cli-2.11}/tests/config.py +0 -0
  34. {papers_cli-2.9.1 → papers_cli-2.11}/tests/download.py +0 -0
  35. {papers_cli-2.9.1 → papers_cli-2.11}/tests/downloadedpapers/.gitignore +0 -0
  36. {papers_cli-2.9.1 → papers_cli-2.11}/tests/downloadedpapers/bg-8-515-2011.txt +0 -0
  37. {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_biblio.py +0 -0
  38. {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_encoding.py +0 -0
  39. {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_extract.py +0 -0
  40. {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_filecheck.py +0 -0
  41. {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_install.py +0 -0
  42. {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_list.py +0 -0
  43. {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_misc.py +0 -0
  44. {papers_cli-2.9.1 → papers_cli-2.11}/tests/test_undo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: papers-cli
3
- Version: 2.9.1
3
+ Version: 2.11
4
4
  Summary: utilities to keep your PDF library organized
5
5
  Author-email: Mahé Perrette <mahe.perrette@gmail.com>
6
6
  License: MIT
@@ -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 o.attachment:
548
- logger.error('--attachment is only valid for one PDF / BIBTEX entry')
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
- if o.doi:
551
- logger.error('--doi is only valid for one added file')
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
- if len(o.file) == 0:
572
+ metadata.setdefault('ID', biblio.generate_key(metadata))
573
+ metadata.setdefault('ENTRYTYPE', 'article')
555
574
  if o.edit:
556
- pass
557
- elif not o.doi:
558
- logger.error('Please provide either a PDF file or BIBTEX entry or specify `--doi DOI` or `--edit`')
559
- raise PapersExit()
560
- elif o.no_query_doi:
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
- if len(entries) == 0:
605
- entries = [{
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('pdf metadata')
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:
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '2.9.1'
16
- __version_tuple__ = version_tuple = (2, 9, 1)
15
+ __version__ = version = '2.11'
16
+ __version_tuple__ = version_tuple = (2, 11)
@@ -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
- raise DuplicateKeyError('this error can be avoided if update_key is True')
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.pop('update_key', True)
440
- # logger.warning('fetched key is always updated when adding PDF to existing bib')
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
- db = bibtexparser.loads(open(filename).read())
371
- return db.entries
372
-
373
- raise ValueError('error when editing entries file: '+filename)
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
- @cached('crossref-bibtex.json')
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
- if response.ok:
219
- bibtex = response.text.strip()
220
- return bibtex
221
- raise DOIRequestError(repr(doi)+': '+response.text)
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
- def crossref_to_bibtex(r):
290
- """convert crossref result to bibtex
291
- """
292
- bib = {}
293
-
294
- if 'author' in r:
295
- family = lambda p: p['family'] if len(p['family'].split()) == 1 else '{'+p['family']+'}'
296
- bib['author'] = ' and '.join([family(p) + ', '+ p.get('given','')
297
- for p in r.get('author',[]) if 'family' in p])
298
-
299
- # for k in ['issued','published-print', 'published-online']:
300
- k = 'issued'
301
- if k in r and 'date-parts' in r[k] and len(r[k]['date-parts'])>0:
302
- date = r[k]['date-parts'][0]
303
- bib['year'] = str(date[0])
304
- if len(date) >= 2:
305
- bib['month'] = str(date[1])
306
- # break
307
-
308
- if 'DOI' in r: bib['doi'] = r['DOI']
309
- if 'URL' in r: bib['url'] = r['URL']
310
- if 'title' in r: bib['title'] = r['title'][0]
311
- if 'container-title' in r: bib['journal'] = r['container-title'][0]
312
- if 'volume' in r: bib['volume'] = r['volume']
313
- if 'issue' in r: bib['number'] = r['issue']
314
- if 'page' in r: bib['pages'] = r['page']
315
- if 'publisher' in r: bib['publisher'] = r['publisher']
316
-
317
- # entry type
318
- type = bib.get('type','journal-article')
319
- type_mapping = {'journal-article':'article'}
320
- bib['ENTRYTYPE'] = type_mapping.get(type, type)
321
-
322
- # bibtex key
323
- year = str(bib.get('year','0000'))
324
- if 'author' in r:
325
- ID = r['author'][0]['family'] + '_' + year
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
- ID = year
328
- bib['ID'] = ID
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.append(bib)
332
- return bibtexparser.dumps(db)
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'])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: papers-cli
3
- Version: 2.9.1
3
+ Version: 2.11
4
4
  Summary: utilities to keep your PDF library organized
5
5
  Author-email: Mahé Perrette <mahe.perrette@gmail.com>
6
6
  License: MIT
@@ -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 = 'Perrette_2011'
80
+ key = '10.5194/bg-8-515-2011'
81
81
  newkey = 'perrette_yool2011'
82
82
  year = '2011'
83
- bibtex = """@article{Perrette_2011,
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–524},
91
- publisher = {Copernicus GmbH},
88
+ pages = {515-524},
92
89
  title = {Near-ubiquity of ice-edge blooms in the Arctic},
93
- url = {http://dx.doi.org/10.5194/bg-8-515-2011},
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 = 'Perrette_2013'
105
+ key = '10.5194/esd-4-11-2013'
108
106
  newkey = 'perrette_landerer2013'
109
107
  year = '2013'
110
- bibtex = """@article{Perrette_2013,
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–29},
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 = {http://dx.doi.org/10.5194/esd-4-11-2013},
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{Perrette_2011,
189
- author = {M. Perrette and A. Yool and G. D. Quartly and E. E. Popova},
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--524},
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{Perrette_2011b,
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{Perrette_2011,
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