papers-cli 2.12.1__tar.gz → 2.14__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.14/.github/FUNDING.yml +2 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/PKG-INFO +11 -9
- {papers_cli-2.12.1 → papers_cli-2.14}/README.md +10 -8
- {papers_cli-2.12.1 → papers_cli-2.14}/papers/__main__.py +71 -21
- {papers_cli-2.12.1 → papers_cli-2.14}/papers/_version.py +9 -4
- {papers_cli-2.12.1 → papers_cli-2.14}/papers/bib.py +71 -8
- {papers_cli-2.12.1 → papers_cli-2.14}/papers/config.py +1 -1
- {papers_cli-2.12.1 → papers_cli-2.14}/papers/encoding.py +0 -1
- {papers_cli-2.12.1 → papers_cli-2.14}/papers_cli.egg-info/PKG-INFO +11 -9
- {papers_cli-2.12.1 → papers_cli-2.14}/papers_cli.egg-info/SOURCES.txt +1 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/.gitattributes +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/.github/workflows/pypi.yml +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/.github/workflows/tests.yml +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/.gitignore +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/LICENSE +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/papers/__init__.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/papers/duplicate.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/papers/extract.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/papers/filename.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/papers/latexenc.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/papers/utils.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/papers_cli.egg-info/dependency_links.txt +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/papers_cli.egg-info/entry_points.txt +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/papers_cli.egg-info/requires.txt +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/papers_cli.egg-info/top_level.txt +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/pyproject.toml +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/requirements.txt +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/setup.cfg +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/setup.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/tests/__init__.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/tests/common.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/tests/config.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/tests/download.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/tests/downloadedpapers/.gitignore +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/tests/downloadedpapers/bg-8-515-2011.txt +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/tests/test_add.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/tests/test_biblio.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/tests/test_duplicates.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/tests/test_encoding.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/tests/test_extract.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/tests/test_filecheck.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/tests/test_install.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/tests/test_list.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/tests/test_misc.py +0 -0
- {papers_cli-2.12.1 → papers_cli-2.14}/tests/test_undo.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: papers-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.14
|
|
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)
|
|
21
21
|
[]()
|
|
22
22
|
[]()
|
|
23
23
|
[]()
|
|
@@ -306,6 +306,14 @@ That command is also convenient to check on what's actually tracked and what is
|
|
|
306
306
|
# ...
|
|
307
307
|
papers filecheck --rename --filesdir files
|
|
308
308
|
|
|
309
|
+
There is also a command specifically designed to clean up the zombie files and folders:
|
|
310
|
+
|
|
311
|
+
papers filecheck --clean-filesdir
|
|
312
|
+
|
|
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,
|
|
315
|
+
which is the convention `papers` follows to store multiple attachments. That command works best
|
|
316
|
+
when the files are in their own folder, and not mixed up with other things, obviously.
|
|
309
317
|
|
|
310
318
|
### Setup git-tracked library (optional)
|
|
311
319
|
|
|
@@ -321,14 +329,8 @@ If `--git-lfs` is passed, the files will be backed-up along with the bibtex.
|
|
|
321
329
|
Under the hood, bibtex and files (if applicable) are copied (hard-linked) to a back-up directory.
|
|
322
330
|
Details are described in [issue 51](https://github.com/perrette/papers/issues/51).
|
|
323
331
|
|
|
324
|
-
|
|
325
|
-
Type `papers status -v` to find out.
|
|
326
|
-
|
|
332
|
+
Backup occurs in a subfolder of `~/.local/.share/papers` regardless of the type of installation. Type `papers status -v` to find out.
|
|
327
333
|
For local install that are already git-tracked, the feature remains useful as it is the basis for `papers undo` and `papers redo`.
|
|
328
|
-
You might want to add `.papers` to your .gitignore to avoid messing up with your larger project.
|
|
329
|
-
|
|
330
|
-
This feature is experimental.
|
|
331
|
-
|
|
332
334
|
|
|
333
335
|
### undo / redo
|
|
334
336
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[](https://github.com/perrette/papers/actions)
|
|
2
2
|
[]()
|
|
3
3
|
[]()
|
|
4
4
|
[]()
|
|
@@ -287,6 +287,14 @@ That command is also convenient to check on what's actually tracked and what is
|
|
|
287
287
|
# ...
|
|
288
288
|
papers filecheck --rename --filesdir files
|
|
289
289
|
|
|
290
|
+
There is also a command specifically designed to clean up the zombie files and folders:
|
|
291
|
+
|
|
292
|
+
papers filecheck --clean-filesdir
|
|
293
|
+
|
|
294
|
+
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,
|
|
296
|
+
which is the convention `papers` follows to store multiple attachments. That command works best
|
|
297
|
+
when the files are in their own folder, and not mixed up with other things, obviously.
|
|
290
298
|
|
|
291
299
|
### Setup git-tracked library (optional)
|
|
292
300
|
|
|
@@ -302,14 +310,8 @@ If `--git-lfs` is passed, the files will be backed-up along with the bibtex.
|
|
|
302
310
|
Under the hood, bibtex and files (if applicable) are copied (hard-linked) to a back-up directory.
|
|
303
311
|
Details are described in [issue 51](https://github.com/perrette/papers/issues/51).
|
|
304
312
|
|
|
305
|
-
|
|
306
|
-
Type `papers status -v` to find out.
|
|
307
|
-
|
|
313
|
+
Backup occurs in a subfolder of `~/.local/.share/papers` regardless of the type of installation. Type `papers status -v` to find out.
|
|
308
314
|
For local install that are already git-tracked, the feature remains useful as it is the basis for `papers undo` and `papers redo`.
|
|
309
|
-
You might want to add `.papers` to your .gitignore to avoid messing up with your larger project.
|
|
310
|
-
|
|
311
|
-
This feature is experimental.
|
|
312
|
-
|
|
313
315
|
|
|
314
316
|
### undo / redo
|
|
315
317
|
|
|
@@ -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:
|
|
@@ -288,19 +290,41 @@ def installcmd(parser, o, config):
|
|
|
288
290
|
"""
|
|
289
291
|
prompt = o.prompt and not o.edit
|
|
290
292
|
# installed = config.file is not None
|
|
293
|
+
# WARNING if pre-existing config file
|
|
291
294
|
if config.file is not None and prompt:
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
295
|
+
if ((o.local and config.local) or ((not o.local and not config.local))):
|
|
296
|
+
while True:
|
|
297
|
+
ans = input(f'An existing {"local" if config.local else "global"} install was found: {config.file}. Overwrite (O) or Edit (E) ? [o / e]')
|
|
298
|
+
if ans.lower() in ('o', 'e'):
|
|
299
|
+
break
|
|
300
|
+
else:
|
|
301
|
+
print('Use the --edit option to selectively edit existing configuration, or --force to ignore pre-existing configuration.')
|
|
302
|
+
o.edit = ans.lower() == 'e'
|
|
303
|
+
|
|
304
|
+
elif not o.local and config.local:
|
|
305
|
+
print(f"A local configuration file was found that will have precedence over the global install: {config.file}. Delete (D) or Ignore (any other key) ?")
|
|
306
|
+
ans = input()
|
|
307
|
+
if ans.lower() == "d":
|
|
308
|
+
logger.info(f"Removing pre-existing local configuration file {config.file}")
|
|
309
|
+
os.remove(config.file)
|
|
310
|
+
config = Config()
|
|
311
|
+
|
|
312
|
+
elif o.local and not config.local:
|
|
313
|
+
logger.warning(f"A global configuration file was found, but the local install will have precedence: {config.file}. Delete the global configuration file (D) or Ignore (any other key) ?")
|
|
314
|
+
ans = input()
|
|
315
|
+
if ans.lower() == "d":
|
|
316
|
+
logger.info(f"Removing pre-existing global configuration file {config.file}")
|
|
317
|
+
os.remove(config.file)
|
|
318
|
+
config = Config()
|
|
319
|
+
|
|
320
|
+
else:
|
|
321
|
+
raise PapersExit("Unexpected configuration state")
|
|
322
|
+
|
|
299
323
|
|
|
300
324
|
if not o.edit:
|
|
301
|
-
if config.
|
|
325
|
+
if config.file and os.path.exists(config.file) and ((o.local and config.local) or ((not o.local and not config.local))):
|
|
302
326
|
# if we don't do that the local install will take precedence over global install
|
|
303
|
-
logger.warning(f"remove pre-existing
|
|
327
|
+
logger.warning(f"remove pre-existing configuration file: {config.file}")
|
|
304
328
|
os.remove(config.file)
|
|
305
329
|
config = Config()
|
|
306
330
|
|
|
@@ -673,6 +697,10 @@ def filecheckcmd(parser, o, config):
|
|
|
673
697
|
if o.rename:
|
|
674
698
|
biblio.rename_entries_files(o.copy)
|
|
675
699
|
|
|
700
|
+
if o.clean_filesdir:
|
|
701
|
+
print("Check files directory for unlinked files")
|
|
702
|
+
clean_filesdir(biblio, interactive=not o.force, ignore_files=[config.bibtex])
|
|
703
|
+
|
|
676
704
|
savebib(biblio, config)
|
|
677
705
|
|
|
678
706
|
def redocmd(parser, o, config):
|
|
@@ -717,7 +745,20 @@ def doicmd(parser, o):
|
|
|
717
745
|
print(extract_pdf_doi(o.pdf, image=o.image))
|
|
718
746
|
|
|
719
747
|
def fetchcmd(parser, o):
|
|
720
|
-
|
|
748
|
+
# either one or several DOIs
|
|
749
|
+
if all(isvaliddoi(field) for field in o.doi_or_text):
|
|
750
|
+
if o.scholar:
|
|
751
|
+
parser.error("Fetching from DOI does not support Google Scholar option")
|
|
752
|
+
for doi in o.doi_or_text:
|
|
753
|
+
print(fetch_bibtex_by_doi(doi))
|
|
754
|
+
return
|
|
755
|
+
|
|
756
|
+
# or one full text search
|
|
757
|
+
field = " ".join(o.doi_or_text)
|
|
758
|
+
if o.scholar:
|
|
759
|
+
print(fetch_bibtex_by_fulltext_scholar(field))
|
|
760
|
+
else:
|
|
761
|
+
print(fetch_bibtex_by_fulltext_crossref(field))
|
|
721
762
|
|
|
722
763
|
def extractcmd(parser, o):
|
|
723
764
|
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 +1144,9 @@ def get_parser(config=None):
|
|
|
1103
1144
|
filecheckp.add_argument('-d', '--delete-broken', action='store_true',
|
|
1104
1145
|
help='remove file entry if the file link is broken')
|
|
1105
1146
|
|
|
1147
|
+
filecheckp.add_argument('--clean-filesdir', action='store_true',
|
|
1148
|
+
help='remove files in filesdir if not referred to in any entry')
|
|
1149
|
+
|
|
1106
1150
|
filecheckp.add_argument('--fix-mendeley', action='store_true',
|
|
1107
1151
|
help='fix a Mendeley bug where the leading "/" is omitted.')
|
|
1108
1152
|
|
|
@@ -1174,15 +1218,15 @@ def get_parser(config=None):
|
|
|
1174
1218
|
|
|
1175
1219
|
# doi
|
|
1176
1220
|
# ===
|
|
1177
|
-
doip = subparsers.add_parser('doi', description='parse DOI from PDF')
|
|
1221
|
+
doip = subparsers.add_parser('doi', description='parse DOI from PDF', parents=[loggingp])
|
|
1178
1222
|
doip.add_argument('pdf')
|
|
1179
1223
|
doip.add_argument('--image', action='store_true', help='convert to image and use tesseract instead of pdftotext')
|
|
1180
1224
|
|
|
1181
1225
|
# fetch
|
|
1182
1226
|
# =====
|
|
1183
|
-
fetchp = subparsers.add_parser('fetch', description='fetch bibtex from DOI')
|
|
1184
|
-
fetchp.add_argument('
|
|
1185
|
-
|
|
1227
|
+
fetchp = subparsers.add_parser('fetch', description='fetch bibtex from DOI or full-text', parents=[loggingp])
|
|
1228
|
+
fetchp.add_argument('doi_or_text', nargs='+', help='DOI or full text.')
|
|
1229
|
+
fetchp.add_argument('--scholar', action='store_true', help='use google scholar instead of default crossref for fulltext search')
|
|
1186
1230
|
|
|
1187
1231
|
# extract
|
|
1188
1232
|
# ========
|
|
@@ -1219,6 +1263,13 @@ def get_parser(config=None):
|
|
|
1219
1263
|
# Main script
|
|
1220
1264
|
#############
|
|
1221
1265
|
|
|
1266
|
+
def handle_logging(o):
|
|
1267
|
+
# verbosity
|
|
1268
|
+
if getattr(o,'logging_level',None):
|
|
1269
|
+
logger.setLevel(o.logging_level)
|
|
1270
|
+
logger.debug("LOGGER LEVEL: "+logging.getLevelName(logger.getEffectiveLevel()))
|
|
1271
|
+
|
|
1272
|
+
|
|
1222
1273
|
def main(args=None):
|
|
1223
1274
|
papers.config.DRYRUN = False # reset in case main() if called directly
|
|
1224
1275
|
if args is not None:
|
|
@@ -1242,9 +1293,8 @@ def main(args=None):
|
|
|
1242
1293
|
print(__version__)
|
|
1243
1294
|
return
|
|
1244
1295
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
logger.setLevel(o.logging_level)
|
|
1296
|
+
handle_logging(o)
|
|
1297
|
+
|
|
1248
1298
|
# modify disk state?
|
|
1249
1299
|
if hasattr(o,'dry_run'):
|
|
1250
1300
|
papers.config.DRYRUN = o.dry_run
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# file generated by
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
3
6
|
TYPE_CHECKING = False
|
|
4
7
|
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
12
|
else:
|
|
8
13
|
VERSION_TUPLE = object
|
|
@@ -12,5 +17,5 @@ __version__: str
|
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
|
13
18
|
version_tuple: VERSION_TUPLE
|
|
14
19
|
|
|
15
|
-
__version__ = version = '2.
|
|
16
|
-
__version_tuple__ = version_tuple = (2,
|
|
20
|
+
__version__ = version = '2.14'
|
|
21
|
+
__version_tuple__ = version_tuple = (2, 14)
|
|
@@ -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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: papers-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.14
|
|
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)
|
|
21
21
|
[]()
|
|
22
22
|
[]()
|
|
23
23
|
[]()
|
|
@@ -306,6 +306,14 @@ That command is also convenient to check on what's actually tracked and what is
|
|
|
306
306
|
# ...
|
|
307
307
|
papers filecheck --rename --filesdir files
|
|
308
308
|
|
|
309
|
+
There is also a command specifically designed to clean up the zombie files and folders:
|
|
310
|
+
|
|
311
|
+
papers filecheck --clean-filesdir
|
|
312
|
+
|
|
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,
|
|
315
|
+
which is the convention `papers` follows to store multiple attachments. That command works best
|
|
316
|
+
when the files are in their own folder, and not mixed up with other things, obviously.
|
|
309
317
|
|
|
310
318
|
### Setup git-tracked library (optional)
|
|
311
319
|
|
|
@@ -321,14 +329,8 @@ If `--git-lfs` is passed, the files will be backed-up along with the bibtex.
|
|
|
321
329
|
Under the hood, bibtex and files (if applicable) are copied (hard-linked) to a back-up directory.
|
|
322
330
|
Details are described in [issue 51](https://github.com/perrette/papers/issues/51).
|
|
323
331
|
|
|
324
|
-
|
|
325
|
-
Type `papers status -v` to find out.
|
|
326
|
-
|
|
332
|
+
Backup occurs in a subfolder of `~/.local/.share/papers` regardless of the type of installation. Type `papers status -v` to find out.
|
|
327
333
|
For local install that are already git-tracked, the feature remains useful as it is the basis for `papers undo` and `papers redo`.
|
|
328
|
-
You might want to add `.papers` to your .gitignore to avoid messing up with your larger project.
|
|
329
|
-
|
|
330
|
-
This feature is experimental.
|
|
331
|
-
|
|
332
334
|
|
|
333
335
|
### undo / redo
|
|
334
336
|
|
|
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
|