papers-cli 2.12.1__tar.gz → 2.14b0__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.12.1 → papers_cli-2.14b0}/PKG-INFO +1 -1
- {papers_cli-2.12.1 → papers_cli-2.14b0}/papers/__main__.py +30 -8
- {papers_cli-2.12.1 → papers_cli-2.14b0}/papers/_version.py +2 -2
- {papers_cli-2.12.1 → papers_cli-2.14b0}/papers/bib.py +71 -8
- {papers_cli-2.12.1 → papers_cli-2.14b0}/papers/config.py +1 -1
- {papers_cli-2.12.1 → papers_cli-2.14b0}/papers/encoding.py +0 -1
- {papers_cli-2.12.1 → papers_cli-2.14b0}/papers_cli.egg-info/PKG-INFO +1 -1
- {papers_cli-2.12.1 → papers_cli-2.14b0}/.gitattributes +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/.github/workflows/pypi.yml +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/.github/workflows/tests.yml +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/.gitignore +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/LICENSE +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/README.md +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/papers/__init__.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/papers/duplicate.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/papers/extract.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/papers/filename.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/papers/latexenc.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/papers/utils.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/papers_cli.egg-info/SOURCES.txt +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/papers_cli.egg-info/dependency_links.txt +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/papers_cli.egg-info/entry_points.txt +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/papers_cli.egg-info/requires.txt +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/papers_cli.egg-info/top_level.txt +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/pyproject.toml +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/requirements.txt +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/setup.cfg +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/setup.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/tests/__init__.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/tests/common.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/tests/config.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/tests/download.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/tests/downloadedpapers/.gitignore +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/tests/downloadedpapers/bg-8-515-2011.txt +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/tests/test_add.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/tests/test_biblio.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/tests/test_duplicates.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/tests/test_encoding.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/tests/test_extract.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/tests/test_filecheck.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/tests/test_install.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/tests/test_list.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/tests/test_misc.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14b0}/tests/test_undo.py +0 -0
|
@@ -9,17 +9,16 @@ 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
|
|
14
12
|
|
|
15
13
|
import papers
|
|
16
14
|
from papers import logger
|
|
17
15
|
from papers.extract import extract_pdf_doi, isvaliddoi, extract_pdf_metadata
|
|
18
|
-
from papers.extract import fetch_bibtex_by_doi
|
|
16
|
+
from papers.extract import fetch_bibtex_by_doi, fetch_bibtex_by_fulltext_crossref, fetch_bibtex_by_fulltext_scholar
|
|
19
17
|
from papers.encoding import parse_file, format_file, family_names, format_entries, standard_name, format_entry, parse_keywords, format_key
|
|
20
18
|
from papers.config import bcolors, Config, search_config, CONFIG_FILE, CONFIG_FILE_LOCAL, DATA_DIR, CONFIG_FILE_LEGACY, BACKUP_DIR
|
|
21
19
|
from papers.duplicate import list_duplicates, list_uniques, edit_entries
|
|
22
|
-
from papers.bib import Biblio, FUZZY_RATIO, DEFAULT_SIMILARITY, entry_filecheck,
|
|
20
|
+
from papers.bib import (Biblio, FUZZY_RATIO, DEFAULT_SIMILARITY, entry_filecheck,
|
|
21
|
+
backupfile as backupfile_func, isvalidkey, DuplicateKeyError, clean_filesdir)
|
|
23
22
|
from papers.utils import move, checksum, view_pdf, open_folder
|
|
24
23
|
from papers import __version__
|
|
25
24
|
|
|
@@ -89,6 +88,9 @@ def _backup_bib(biblio, config, message=None):
|
|
|
89
88
|
biblio.rename_entries_files(copy=True, relative_to=backupdir, hardlink=True)
|
|
90
89
|
biblio.save(config.backupfile_clean)
|
|
91
90
|
config.gitcmd(f"add {config.backupfile_clean.name}")
|
|
91
|
+
|
|
92
|
+
# Remove unlink files
|
|
93
|
+
clean_filesdir(biblio, interactive=False, ignore_files=[config.backupfile, config.backupfile_clean])
|
|
92
94
|
config.gitcmd(f"add files")
|
|
93
95
|
|
|
94
96
|
else:
|
|
@@ -673,6 +675,10 @@ def filecheckcmd(parser, o, config):
|
|
|
673
675
|
if o.rename:
|
|
674
676
|
biblio.rename_entries_files(o.copy)
|
|
675
677
|
|
|
678
|
+
if o.clean_filesdir:
|
|
679
|
+
print("Check files directory for unlinked files")
|
|
680
|
+
clean_filesdir(biblio, interactive=not o.force, ignore_files=[config.bibtex])
|
|
681
|
+
|
|
676
682
|
savebib(biblio, config)
|
|
677
683
|
|
|
678
684
|
def redocmd(parser, o, config):
|
|
@@ -717,7 +723,20 @@ def doicmd(parser, o):
|
|
|
717
723
|
print(extract_pdf_doi(o.pdf, image=o.image))
|
|
718
724
|
|
|
719
725
|
def fetchcmd(parser, o):
|
|
720
|
-
|
|
726
|
+
# either one or several DOIs
|
|
727
|
+
if all(isvaliddoi(field) for field in o.doi_or_text):
|
|
728
|
+
if o.scholar:
|
|
729
|
+
parser.error("Fetching from DOI does not support Google Scholar option")
|
|
730
|
+
for doi in o.doi_or_text:
|
|
731
|
+
print(fetch_bibtex_by_doi(doi))
|
|
732
|
+
return
|
|
733
|
+
|
|
734
|
+
# or one full text search
|
|
735
|
+
field = " ".join(o.doi_or_text)
|
|
736
|
+
if o.scholar:
|
|
737
|
+
print(fetch_bibtex_by_fulltext_scholar(field))
|
|
738
|
+
else:
|
|
739
|
+
print(fetch_bibtex_by_fulltext_crossref(field))
|
|
721
740
|
|
|
722
741
|
def extractcmd(parser, o):
|
|
723
742
|
print(extract_pdf_metadata(o.pdf, search_doi=not o.fulltext, search_fulltext=True, scholar=o.scholar, minwords=o.word_count, max_query_words=o.word_count, image=o.image))
|
|
@@ -1103,6 +1122,9 @@ def get_parser(config=None):
|
|
|
1103
1122
|
filecheckp.add_argument('-d', '--delete-broken', action='store_true',
|
|
1104
1123
|
help='remove file entry if the file link is broken')
|
|
1105
1124
|
|
|
1125
|
+
filecheckp.add_argument('--clean-filesdir', action='store_true',
|
|
1126
|
+
help='remove files in filesdir if not referred to in any entry')
|
|
1127
|
+
|
|
1106
1128
|
filecheckp.add_argument('--fix-mendeley', action='store_true',
|
|
1107
1129
|
help='fix a Mendeley bug where the leading "/" is omitted.')
|
|
1108
1130
|
|
|
@@ -1180,9 +1202,9 @@ def get_parser(config=None):
|
|
|
1180
1202
|
|
|
1181
1203
|
# fetch
|
|
1182
1204
|
# =====
|
|
1183
|
-
fetchp = subparsers.add_parser('fetch', description='fetch bibtex from DOI')
|
|
1184
|
-
fetchp.add_argument('
|
|
1185
|
-
|
|
1205
|
+
fetchp = subparsers.add_parser('fetch', description='fetch bibtex from DOI or full-text')
|
|
1206
|
+
fetchp.add_argument('doi_or_text', nargs='+', help='DOI or full text.')
|
|
1207
|
+
fetchp.add_argument('--scholar', action='store_true', help='use google scholar instead of default crossref for fulltext search')
|
|
1186
1208
|
|
|
1187
1209
|
# extract
|
|
1188
1210
|
# ========
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
import logging
|
|
4
|
-
# logger.basicConfig(level=logger.INFO)
|
|
5
|
-
import argparse
|
|
6
|
-
import subprocess as sp
|
|
7
3
|
import shutil
|
|
8
4
|
import bisect
|
|
9
|
-
import
|
|
10
|
-
import fnmatch # unix-like match
|
|
5
|
+
from unidecode import unidecode as unicode_to_ascii
|
|
11
6
|
import bibtexparser
|
|
12
7
|
from bibtexparser.customization import convert_to_unicode
|
|
13
8
|
|
|
@@ -18,8 +13,8 @@ from papers.extract import extract_pdf_doi, isvaliddoi, parse_doi
|
|
|
18
13
|
from papers.extract import extract_pdf_metadata
|
|
19
14
|
from papers.extract import fetch_bibtex_by_fulltext_crossref, fetch_bibtex_by_doi
|
|
20
15
|
|
|
21
|
-
from papers.encoding import unicode_to_latex, unicode_to_ascii
|
|
22
16
|
from papers.encoding import parse_file, format_file, standard_name, family_names, format_entries, update_file_path, format_entry
|
|
17
|
+
from papers.latexenc import unicode_to_latex, latex_to_unicode
|
|
23
18
|
|
|
24
19
|
from papers.filename import NAMEFORMAT, KEYFORMAT
|
|
25
20
|
from papers.utils import bcolors, checksum, move as _move
|
|
@@ -309,7 +304,7 @@ class Biblio:
|
|
|
309
304
|
else:
|
|
310
305
|
print(format_entry(self, entry, prefix="New entry"))
|
|
311
306
|
print(format_entry(self, self.entries[i], prefix="Conflicts with"))
|
|
312
|
-
raise DuplicateKeyError('Key conflict. Try update_key is True
|
|
307
|
+
raise DuplicateKeyError('Key conflict. Try update_key is True or specify --key KEY explicitly')
|
|
313
308
|
|
|
314
309
|
else:
|
|
315
310
|
logger.info('new entry: '+self.key(entry))
|
|
@@ -821,3 +816,71 @@ def entry_filecheck(e, delete_broken=False, fix_mendeley=False,
|
|
|
821
816
|
newfiles.append(file)
|
|
822
817
|
|
|
823
818
|
e['file'] = format_file(newfiles, relative_to=relative_to)
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
def clean_filesdir(biblio, interactive=True, ignore_files=None, ignore_folders=None):
|
|
822
|
+
if biblio.filesdir is None:
|
|
823
|
+
raise ValueError('filesdir is not defined, cannot clean')
|
|
824
|
+
removed_files = []
|
|
825
|
+
|
|
826
|
+
allfiles = set(os.path.abspath(file) for e in biblio.entries for file in biblio.get_files(e))
|
|
827
|
+
if ignore_files:
|
|
828
|
+
for f in ignore_files:
|
|
829
|
+
allfiles.add(os.path.abspath(f))
|
|
830
|
+
allfolders = set(os.path.abspath(folder) for e in biblio.entries for folder in {os.path.dirname(file) for file in biblio.get_files(e)})
|
|
831
|
+
|
|
832
|
+
if ignore_folders is None:
|
|
833
|
+
ignore_folders = []
|
|
834
|
+
|
|
835
|
+
for root, direcs, files in os.walk(biblio.filesdir):
|
|
836
|
+
|
|
837
|
+
ANS = None # Y(es) or N(o) for all files in this folder (reset at each pass)
|
|
838
|
+
if root in ignore_folders:
|
|
839
|
+
continue
|
|
840
|
+
if '.git' in root.split(os.path.sep):
|
|
841
|
+
continue
|
|
842
|
+
for file in files:
|
|
843
|
+
path = os.path.abspath(os.path.join(root, file))
|
|
844
|
+
if file.startswith('.') or file.endswith('.bib'):
|
|
845
|
+
continue
|
|
846
|
+
if path not in allfiles:
|
|
847
|
+
if interactive:
|
|
848
|
+
if ANS is None:
|
|
849
|
+
ans = input(f"File: {os.path.relpath(path, biblio.filesdir)} not in library. Remove ? [Y/n/Y/N] ")
|
|
850
|
+
else:
|
|
851
|
+
ans = ANS
|
|
852
|
+
if ans in ['Y', 'N']:
|
|
853
|
+
ans = ANS = ans.lower() # same reply for all files in this folder
|
|
854
|
+
if ans.lower() == 'y':
|
|
855
|
+
os.remove(path)
|
|
856
|
+
removed_files.append(path)
|
|
857
|
+
else:
|
|
858
|
+
logger.info(f"Remove unlinked file {os.path.relpath(path, biblio.filesdir)}.")
|
|
859
|
+
os.remove(path)
|
|
860
|
+
removed_files.append(path)
|
|
861
|
+
|
|
862
|
+
for direc in direcs:
|
|
863
|
+
if direc.startswith('.'):
|
|
864
|
+
continue
|
|
865
|
+
if direc in ignore_folders:
|
|
866
|
+
continue
|
|
867
|
+
|
|
868
|
+
# Check multifile entries
|
|
869
|
+
bibtex = os.path.join(root, direc, '.'+direc+'.bib')
|
|
870
|
+
if not os.path.exists(bibtex):
|
|
871
|
+
continue
|
|
872
|
+
|
|
873
|
+
direcpath = os.path.abspath(os.path.join(root, direc))
|
|
874
|
+
|
|
875
|
+
if direcpath not in allfolders:
|
|
876
|
+
if interactive:
|
|
877
|
+
ans = input(f"Folder: {os.path.relpath(direcpath, root)} not in library. Remove ? [Y/n] ")
|
|
878
|
+
if ans.lower() == 'y':
|
|
879
|
+
shutil.rmtree(os.path.join(root, direcpath))
|
|
880
|
+
else:
|
|
881
|
+
logger.info(f"Remove folder {direc}.")
|
|
882
|
+
shutil.rmtree(os.path.join(root, direcpath))
|
|
883
|
+
|
|
884
|
+
break
|
|
885
|
+
|
|
886
|
+
return removed_files
|
|
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
|