papers-cli 2.7.2__py3-none-any.whl → 2.9__py3-none-any.whl
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/__main__.py +25 -27
- papers/_version.py +2 -2
- papers/bib.py +5 -5
- papers/encoding.py +41 -0
- papers/utils.py +15 -3
- {papers_cli-2.7.2.dist-info → papers_cli-2.9.dist-info}/METADATA +9 -9
- papers_cli-2.9.dist-info/RECORD +17 -0
- papers_cli-2.7.2.dist-info/RECORD +0 -17
- {papers_cli-2.7.2.dist-info → papers_cli-2.9.dist-info}/LICENSE +0 -0
- {papers_cli-2.7.2.dist-info → papers_cli-2.9.dist-info}/WHEEL +0 -0
- {papers_cli-2.7.2.dist-info → papers_cli-2.9.dist-info}/entry_points.txt +0 -0
- {papers_cli-2.7.2.dist-info → papers_cli-2.9.dist-info}/top_level.txt +0 -0
papers/__main__.py
CHANGED
|
@@ -16,11 +16,11 @@ import papers
|
|
|
16
16
|
from papers import logger
|
|
17
17
|
from papers.extract import extract_pdf_doi, isvaliddoi, extract_pdf_metadata
|
|
18
18
|
from papers.extract import fetch_bibtex_by_doi
|
|
19
|
-
from papers.encoding import parse_file, format_file, family_names, format_entries, standard_name
|
|
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
22
|
from papers.bib import Biblio, FUZZY_RATIO, DEFAULT_SIMILARITY, entry_filecheck, backupfile as backupfile_func, isvalidkey
|
|
23
|
-
from papers.utils import move, checksum,
|
|
23
|
+
from papers.utils import move, checksum, view_pdf, open_folder
|
|
24
24
|
from papers import __version__
|
|
25
25
|
|
|
26
26
|
|
|
@@ -233,7 +233,27 @@ def savebib(biblio, config):
|
|
|
233
233
|
# config.gitcommit()
|
|
234
234
|
|
|
235
235
|
|
|
236
|
+
|
|
237
|
+
def is_subdirectory(parent, child):
|
|
238
|
+
# Resolve paths to absolute paths
|
|
239
|
+
parent = Path(parent).resolve()
|
|
240
|
+
child = Path(child).resolve()
|
|
241
|
+
|
|
242
|
+
# Check if the child path is relative to the parent path
|
|
243
|
+
return parent in child.parents
|
|
244
|
+
|
|
245
|
+
|
|
236
246
|
def view_entry_files(biblio, entry):
|
|
247
|
+
files = biblio.get_files(entry)
|
|
248
|
+
|
|
249
|
+
# in case several attachments are present and the files are ordered under filesdir,
|
|
250
|
+
# open the subfolder instead of the single files
|
|
251
|
+
if len(files) > 1:
|
|
252
|
+
parent = os.path.commonpath(files)
|
|
253
|
+
if is_subdirectory(biblio.filesdir, parent):
|
|
254
|
+
open_folder(parent)
|
|
255
|
+
return
|
|
256
|
+
|
|
237
257
|
for f in biblio.get_files(entry):
|
|
238
258
|
logger.info(f"opening {f} ...")
|
|
239
259
|
view_pdf(f)
|
|
@@ -801,15 +821,6 @@ def listcmd(parser, o, config):
|
|
|
801
821
|
eq = lambda a, b: a['ID'] == b['ID'] or are_duplicates(a, b, similarity="PARTIAL", fuzzy_ratio=o.fuzzy_ratio)
|
|
802
822
|
entries = list_dup(entries, eq=eq)
|
|
803
823
|
|
|
804
|
-
if o.no_key:
|
|
805
|
-
key = lambda e: ''
|
|
806
|
-
else:
|
|
807
|
-
# key = lambda e: bcolors.OKBLUE+e['ID']+filetag(e)+':'+bcolors.ENDC
|
|
808
|
-
key = lambda e: _nfiles(e)*(bcolors.BOLD)+bcolors.OKBLUE+e['ID']+':'+bcolors.ENDC
|
|
809
|
-
|
|
810
|
-
def parse_keywords(e):
|
|
811
|
-
return [w.strip() for w in e.get('keywords', '').split(',') if w.strip()]
|
|
812
|
-
|
|
813
824
|
if o.add_keywords:
|
|
814
825
|
for e in entries:
|
|
815
826
|
keywords = parse_keywords(e)
|
|
@@ -861,27 +872,13 @@ def listcmd(parser, o, config):
|
|
|
861
872
|
elif o.field:
|
|
862
873
|
# entries = [{k:e[k] for k in e if k in o.field+['ID','ENTRYTYPE']} for e in entries]
|
|
863
874
|
for e in entries:
|
|
864
|
-
print(
|
|
875
|
+
print(format_key(e, no_key=o.no_key),*[e.get(k, "") for k in o.field])
|
|
865
876
|
elif o.key_only:
|
|
866
877
|
for e in entries:
|
|
867
878
|
print(e['ID'])
|
|
868
879
|
elif o.one_liner:
|
|
869
880
|
for e in entries:
|
|
870
|
-
|
|
871
|
-
info = []
|
|
872
|
-
if e.get('doi',''):
|
|
873
|
-
info.append(link(f"https://doi.org/{e['doi']}", 'doi:'+e['doi']))
|
|
874
|
-
n = _nfiles(e)
|
|
875
|
-
if n:
|
|
876
|
-
files = parse_file(e.get('file',''), relative_to=biblio.relative_to)
|
|
877
|
-
file_link = f"file:///{Path(files[0]).resolve()}"
|
|
878
|
-
ansi_link = link(file_link, f'{"file" if n == 1 else "files"}:{str(n)}')
|
|
879
|
-
info.append(bcolors.OKGREEN+ansi_link+bcolors.ENDC)
|
|
880
|
-
if e.get('keywords',''):
|
|
881
|
-
keywords = parse_keywords(e)
|
|
882
|
-
info.append(bcolors.WARNING+" | ".join(keywords)+bcolors.ENDC)
|
|
883
|
-
infotag = '('+', '.join(info)+')' if info else ''
|
|
884
|
-
print(key(e), tit, infotag)
|
|
881
|
+
print(format_entry(biblio, e, no_key=o.no_key))
|
|
885
882
|
|
|
886
883
|
else:
|
|
887
884
|
print(format_entries(entries))
|
|
@@ -1147,6 +1144,7 @@ def get_parser(config=None):
|
|
|
1147
1144
|
|
|
1148
1145
|
grp = listp.add_argument_group('formatting')
|
|
1149
1146
|
mgrp = grp.add_mutually_exclusive_group()
|
|
1147
|
+
mgrp.add_argument('--plain', action='store_false', dest="one_liner", help='print in bibtex format')
|
|
1150
1148
|
mgrp.add_argument('-l', '-1', '--one-liner', action='store_true', help='one liner')
|
|
1151
1149
|
mgrp.add_argument('--key-only', action='store_true')
|
|
1152
1150
|
mgrp.add_argument('-f', '--field', nargs='+', help='specific field(s) only')
|
papers/_version.py
CHANGED
papers/bib.py
CHANGED
|
@@ -19,7 +19,7 @@ from papers.extract import extract_pdf_metadata
|
|
|
19
19
|
from papers.extract import fetch_bibtex_by_fulltext_crossref, fetch_bibtex_by_doi
|
|
20
20
|
|
|
21
21
|
from papers.encoding import unicode_to_latex, unicode_to_ascii
|
|
22
|
-
from papers.encoding import parse_file, format_file, standard_name, family_names, format_entries, update_file_path
|
|
22
|
+
from papers.encoding import parse_file, format_file, standard_name, family_names, format_entries, update_file_path, format_entry
|
|
23
23
|
|
|
24
24
|
from papers.filename import NAMEFORMAT, KEYFORMAT
|
|
25
25
|
from papers.utils import bcolors, checksum, move as _move
|
|
@@ -298,14 +298,14 @@ class Biblio:
|
|
|
298
298
|
newkey = self.append_abc_to_key(entry) # add abc
|
|
299
299
|
logger.info('update key: {} => {}'.format(entry['ID'], newkey))
|
|
300
300
|
entry['ID'] = newkey
|
|
301
|
-
print(
|
|
301
|
+
print(format_entry(self, entry, prefix="Updated"))
|
|
302
302
|
|
|
303
303
|
else:
|
|
304
304
|
raise DuplicateKeyError('this error can be avoided if update_key is True')
|
|
305
305
|
|
|
306
306
|
else:
|
|
307
307
|
logger.info('new entry: '+self.key(entry))
|
|
308
|
-
print(
|
|
308
|
+
print(format_entry(self, entry, prefix="Added"))
|
|
309
309
|
|
|
310
310
|
self.entries.insert(i, entry)
|
|
311
311
|
|
|
@@ -335,7 +335,7 @@ class Biblio:
|
|
|
335
335
|
if entry == candidate:
|
|
336
336
|
logger.debug('exact duplicate')
|
|
337
337
|
if rename: self.rename_entry_files(candidate, copy=copy)
|
|
338
|
-
print(
|
|
338
|
+
print(format_entry(self, entry, prefix="Existing"))
|
|
339
339
|
return [ entry ] # do nothing
|
|
340
340
|
|
|
341
341
|
if update_key and entry['ID'] != candidate['ID']:
|
|
@@ -354,7 +354,7 @@ class Biblio:
|
|
|
354
354
|
logger.debug('fixed: exact duplicate')
|
|
355
355
|
entry = candidate
|
|
356
356
|
if rename: self.rename_entry_files(candidate, copy=copy)
|
|
357
|
-
print(
|
|
357
|
+
print(format_entry(self, entry, prefix="Existing"))
|
|
358
358
|
return [ entry ] # do nothing
|
|
359
359
|
|
|
360
360
|
logger.debug('conflict resolution: '+on_conflict)
|
papers/encoding.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import bibtexparser
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from unidecode import unidecode as unicode_to_ascii
|
|
4
5
|
from papers.latexenc import unicode_to_latex
|
|
6
|
+
from papers.utils import ansi_link as link, bcolors
|
|
5
7
|
from papers import logger
|
|
6
8
|
|
|
7
9
|
# fix bibtexparser call on empty strings
|
|
@@ -89,6 +91,45 @@ def format_entries(entries):
|
|
|
89
91
|
db.entries.extend(entries)
|
|
90
92
|
return bibtexparser.dumps(db)
|
|
91
93
|
|
|
94
|
+
def parse_keywords(e):
|
|
95
|
+
return [w.strip() for w in e.get('keywords', '').split(',') if w.strip()]
|
|
96
|
+
|
|
97
|
+
def format_key(e, no_key=False):
|
|
98
|
+
if no_key:
|
|
99
|
+
key = lambda e: ''
|
|
100
|
+
else:
|
|
101
|
+
n = len(parse_file(e.get('file','')))
|
|
102
|
+
key = lambda e: n*(bcolors.BOLD)+bcolors.OKBLUE+e['ID']+':'+bcolors.ENDC
|
|
103
|
+
return key(e)
|
|
104
|
+
|
|
105
|
+
def format_entry(biblio, e, no_key=False, prefix=""):
|
|
106
|
+
"""One-liner formatter
|
|
107
|
+
"""
|
|
108
|
+
tit = e.get('title', '')[:60]+ ('...' if len(e.get('title', ''))>60 else '')
|
|
109
|
+
info = []
|
|
110
|
+
if e.get('doi',''):
|
|
111
|
+
info.append(link(f"https://doi.org/{e['doi']}", 'doi:'+e['doi']))
|
|
112
|
+
|
|
113
|
+
files = parse_file(e.get('file',''), relative_to=biblio.relative_to)
|
|
114
|
+
n = len(files)
|
|
115
|
+
|
|
116
|
+
if n:
|
|
117
|
+
file_link = f"file:///{Path(files[0]).resolve()}" if n == 1 else f"file:///{Path(os.path.commonpath(files)).resolve()}"
|
|
118
|
+
ansi_link = link(file_link, f'{"file" if n == 1 else "files"}:{str(n)}')
|
|
119
|
+
info.append(bcolors.OKGREEN+ansi_link+bcolors.ENDC)
|
|
120
|
+
|
|
121
|
+
if e.get('keywords',''):
|
|
122
|
+
keywords = parse_keywords(e)
|
|
123
|
+
info.append(bcolors.WARNING+" | ".join(keywords)+bcolors.ENDC)
|
|
124
|
+
|
|
125
|
+
infotag = '('+', '.join(info)+')' if info else ''
|
|
126
|
+
if prefix:
|
|
127
|
+
prefixtag = f"{bcolors.WARNING}{prefix} -> {bcolors.ENDC}"
|
|
128
|
+
else:
|
|
129
|
+
prefixtag = ""
|
|
130
|
+
return f"{prefixtag}{format_key(e, no_key=no_key)} {tit} {infotag}"
|
|
131
|
+
|
|
132
|
+
|
|
92
133
|
# Parse name entry
|
|
93
134
|
# ================
|
|
94
135
|
|
papers/utils.py
CHANGED
|
@@ -2,7 +2,7 @@ import os
|
|
|
2
2
|
import re
|
|
3
3
|
import shutil
|
|
4
4
|
import hashlib
|
|
5
|
-
|
|
5
|
+
import subprocess, platform
|
|
6
6
|
from contextlib import contextmanager
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
@@ -191,10 +191,22 @@ def view_pdf(filepath):
|
|
|
191
191
|
"""
|
|
192
192
|
https://stackoverflow.com/questions/434597/open-document-with-default-os-application-in-python-both-in-windows-and-mac-os
|
|
193
193
|
"""
|
|
194
|
-
import subprocess, os, platform
|
|
195
194
|
if platform.system() == 'Darwin': # macOS
|
|
196
195
|
subprocess.call(('open', filepath))
|
|
197
196
|
elif platform.system() == 'Windows': # Windows
|
|
198
197
|
os.startfile(filepath)
|
|
199
198
|
else: # linux variants
|
|
200
|
-
subprocess.call(('xdg-open', filepath))
|
|
199
|
+
subprocess.call(('xdg-open', filepath))
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def open_folder(path):
|
|
203
|
+
system_platform = platform.system()
|
|
204
|
+
|
|
205
|
+
if system_platform == "Windows":
|
|
206
|
+
os.startfile(path)
|
|
207
|
+
elif system_platform == "Darwin": # macOS
|
|
208
|
+
os.system(f'open "{path}"')
|
|
209
|
+
elif system_platform == "Linux":
|
|
210
|
+
os.system(f'xdg-open "{path}"')
|
|
211
|
+
else:
|
|
212
|
+
print("Unsupported operating system.")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: papers-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.9
|
|
4
4
|
Summary: utilities to keep your PDF library organized
|
|
5
5
|
Author-email: Mahé Perrette <mahe.perrette@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -17,7 +17,7 @@ Requires-Dist: scholarly
|
|
|
17
17
|
Requires-Dist: rapidfuzz
|
|
18
18
|
Requires-Dist: normality
|
|
19
19
|
|
|
20
|
-
[](https://github.com/perrette/papers/actions)
|
|
20
|
+
[](https://github.com/perrette/papers/actions)
|
|
21
21
|
[]()
|
|
22
22
|
[]()
|
|
23
23
|
[]()
|
|
@@ -121,25 +121,25 @@ Some old files don't have a DOI. Best is to add the entry from its bibtex:
|
|
|
121
121
|
|
|
122
122
|
### List entries (and edit etc...)
|
|
123
123
|
|
|
124
|
-
Pretty listing (
|
|
124
|
+
Pretty listing by default (otherwise pass --plain for plain bibtex):
|
|
125
125
|
|
|
126
|
-
$> papers list
|
|
126
|
+
$> papers list
|
|
127
127
|
Perrette2013: A scaling approach to project regional sea level rise and it... (doi:10.5194/esd-4-11-2013, file:1)
|
|
128
128
|
|
|
129
129
|
Search with any number of keywords:
|
|
130
130
|
|
|
131
|
-
$> papers list perrette scaling approach sea level
|
|
131
|
+
$> papers list perrette scaling approach sea level
|
|
132
132
|
... (short list)
|
|
133
|
-
$> papers list perrette scaling approach sea level --any
|
|
133
|
+
$> papers list perrette scaling approach sea level --any
|
|
134
134
|
... (long list)
|
|
135
|
-
$> papers list --key perrette2013 --author perrette --year 2013 --title scaling approach sea level
|
|
135
|
+
$> papers list --key perrette2013 --author perrette --year 2013 --title scaling approach sea level
|
|
136
136
|
... (precise list)
|
|
137
137
|
|
|
138
138
|
Add tags to view papers by topic:
|
|
139
139
|
|
|
140
|
-
$> papers list perrette2013 --add-tag sea-level projections
|
|
140
|
+
$> papers list perrette2013 --add-tag sea-level projections
|
|
141
141
|
...
|
|
142
|
-
$> papers list --tag sea-level projections
|
|
142
|
+
$> papers list --tag sea-level projections
|
|
143
143
|
Perrette2013: A scaling approach to project regional sea level rise and it... (doi:10.5194/esd-4-11-2013, file:1, sea-level | projections )
|
|
144
144
|
|
|
145
145
|
`papers list` is a powerful command, inspired from unix's `find` and `grep`.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
papers/__init__.py,sha256=gEfqa4nhhKYjbk9g07WpJuzU5iAeEHFRGUSvA4VXm8k,108
|
|
2
|
+
papers/__main__.py,sha256=3Ly_JNajRA3H4FPx6JDdyPgmWn930EBO7mzkqKSfjBE,56936
|
|
3
|
+
papers/_version.py,sha256=kerpau_VRaLumk1H9Bkb0gPPzn5FlTh1lEgD7R2rfnk,406
|
|
4
|
+
papers/bib.py,sha256=JSPzIlahE3pqyxbdpxMGvOZTWQYRF9SSDaifIfmbS9I,29817
|
|
5
|
+
papers/config.py,sha256=ufLMp0rBP62bEtesh1s7DZ3FsY4v9Ys669JBua4K_HU,9761
|
|
6
|
+
papers/duplicate.py,sha256=AgGh3QHgE1ZqqqAm8_j13Ao3T-1K-Qe6O8tC4Cyw4DQ,18723
|
|
7
|
+
papers/encoding.py,sha256=5I6v2rK0XQN_xqzpv9dHmRTqObRdyyoWLY2DAlS1ElM,5376
|
|
8
|
+
papers/extract.py,sha256=-RJ8N_AMimjL3jp91txpL7E2vUhEAO-QlT-XssxjqAM,11757
|
|
9
|
+
papers/filename.py,sha256=1uuVnbNkpRogALvDnsy2THfxAuQ2kTH2vlgpfEzsKoI,4744
|
|
10
|
+
papers/latexenc.py,sha256=qH03t6yVUnGZvkZT2-9OWXU-iHqZvVthhQopaM281D4,98127
|
|
11
|
+
papers/utils.py,sha256=DYFufF2YbcBZAIl9uoqtTB-yHzH4A467xC9kuxoMAFE,5502
|
|
12
|
+
papers_cli-2.9.dist-info/LICENSE,sha256=Uc7FclCyMqKU9cc366vVpOLy9gU2w5btUJX9EDzQVIw,1071
|
|
13
|
+
papers_cli-2.9.dist-info/METADATA,sha256=wkg_oIB9KLopnp_ypppm-u6EzTaJJww7se2IxEJOYQ4,18313
|
|
14
|
+
papers_cli-2.9.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
15
|
+
papers_cli-2.9.dist-info/entry_points.txt,sha256=E1H8yJ7ieFOJT8rLGPQOQB7I1KbqTrcjze3FPfgk8uI,59
|
|
16
|
+
papers_cli-2.9.dist-info/top_level.txt,sha256=sLdcLzjapCbtCY-yXhtEHZEniJB7I35rxRLLLURc8mo,7
|
|
17
|
+
papers_cli-2.9.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
papers/__init__.py,sha256=gEfqa4nhhKYjbk9g07WpJuzU5iAeEHFRGUSvA4VXm8k,108
|
|
2
|
-
papers/__main__.py,sha256=Ua07_Qb57lce0jOYp3jrKH7kPGM8QvVUiwtIkFOFxAc,57292
|
|
3
|
-
papers/_version.py,sha256=FBqtgBb5hG2kHWDiVQT5o8_jJfQmv2FROzH4xAlajxU,411
|
|
4
|
-
papers/bib.py,sha256=SFXwNnF0KR71TbtHymPqQ9rrugjjSCeQfAH_HH0MYF4,29761
|
|
5
|
-
papers/config.py,sha256=ufLMp0rBP62bEtesh1s7DZ3FsY4v9Ys669JBua4K_HU,9761
|
|
6
|
-
papers/duplicate.py,sha256=AgGh3QHgE1ZqqqAm8_j13Ao3T-1K-Qe6O8tC4Cyw4DQ,18723
|
|
7
|
-
papers/encoding.py,sha256=S-36AltnOCTCrJ-eSQWQYR4T095KRnPwJQB_15M7Ai0,3929
|
|
8
|
-
papers/extract.py,sha256=-RJ8N_AMimjL3jp91txpL7E2vUhEAO-QlT-XssxjqAM,11757
|
|
9
|
-
papers/filename.py,sha256=1uuVnbNkpRogALvDnsy2THfxAuQ2kTH2vlgpfEzsKoI,4744
|
|
10
|
-
papers/latexenc.py,sha256=qH03t6yVUnGZvkZT2-9OWXU-iHqZvVthhQopaM281D4,98127
|
|
11
|
-
papers/utils.py,sha256=Hql6Rlnd4SKb8CcumTx0Vw41kUUeJuGfo8UTrOt98PQ,5164
|
|
12
|
-
papers_cli-2.7.2.dist-info/LICENSE,sha256=Uc7FclCyMqKU9cc366vVpOLy9gU2w5btUJX9EDzQVIw,1071
|
|
13
|
-
papers_cli-2.7.2.dist-info/METADATA,sha256=vQv1NB70X2zg35kellxT9JM1-ZOCNySDEkNXMoUiVds,18325
|
|
14
|
-
papers_cli-2.7.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
15
|
-
papers_cli-2.7.2.dist-info/entry_points.txt,sha256=E1H8yJ7ieFOJT8rLGPQOQB7I1KbqTrcjze3FPfgk8uI,59
|
|
16
|
-
papers_cli-2.7.2.dist-info/top_level.txt,sha256=sLdcLzjapCbtCY-yXhtEHZEniJB7I35rxRLLLURc8mo,7
|
|
17
|
-
papers_cli-2.7.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|