papers-cli 2.13__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.13 → papers_cli-2.14b0}/PKG-INFO +1 -1
- {papers_cli-2.13 → papers_cli-2.14b0}/papers/__main__.py +12 -3
- {papers_cli-2.13 → papers_cli-2.14b0}/papers/_version.py +2 -2
- {papers_cli-2.13 → papers_cli-2.14b0}/papers/bib.py +71 -8
- {papers_cli-2.13 → papers_cli-2.14b0}/papers/config.py +1 -1
- {papers_cli-2.13 → papers_cli-2.14b0}/papers/encoding.py +0 -1
- {papers_cli-2.13 → papers_cli-2.14b0}/papers_cli.egg-info/PKG-INFO +1 -1
- {papers_cli-2.13 → papers_cli-2.14b0}/.gitattributes +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/.github/workflows/pypi.yml +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/.github/workflows/tests.yml +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/.gitignore +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/LICENSE +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/README.md +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/papers/__init__.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/papers/duplicate.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/papers/extract.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/papers/filename.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/papers/latexenc.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/papers/utils.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/papers_cli.egg-info/SOURCES.txt +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/papers_cli.egg-info/dependency_links.txt +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/papers_cli.egg-info/entry_points.txt +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/papers_cli.egg-info/requires.txt +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/papers_cli.egg-info/top_level.txt +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/pyproject.toml +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/requirements.txt +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/setup.cfg +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/setup.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/tests/__init__.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/tests/common.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/tests/config.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/tests/download.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/tests/downloadedpapers/.gitignore +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/tests/downloadedpapers/bg-8-515-2011.txt +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/tests/test_add.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/tests/test_biblio.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/tests/test_duplicates.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/tests/test_encoding.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/tests/test_extract.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/tests/test_filecheck.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/tests/test_install.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/tests/test_list.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/tests/test_misc.py +0 -0
- {papers_cli-2.13 → papers_cli-2.14b0}/tests/test_undo.py +0 -0
|
@@ -9,8 +9,6 @@ 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
|
|
@@ -19,7 +17,8 @@ from papers.extract import fetch_bibtex_by_doi, fetch_bibtex_by_fulltext_crossre
|
|
|
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):
|
|
@@ -1116,6 +1122,9 @@ def get_parser(config=None):
|
|
|
1116
1122
|
filecheckp.add_argument('-d', '--delete-broken', action='store_true',
|
|
1117
1123
|
help='remove file entry if the file link is broken')
|
|
1118
1124
|
|
|
1125
|
+
filecheckp.add_argument('--clean-filesdir', action='store_true',
|
|
1126
|
+
help='remove files in filesdir if not referred to in any entry')
|
|
1127
|
+
|
|
1119
1128
|
filecheckp.add_argument('--fix-mendeley', action='store_true',
|
|
1120
1129
|
help='fix a Mendeley bug where the leading "/" is omitted.')
|
|
1121
1130
|
|
|
@@ -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
|