papers-cli 2.17.4__tar.gz → 2.17.5__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 (81) hide show
  1. {papers_cli-2.17.4 → papers_cli-2.17.5}/PKG-INFO +7 -1
  2. {papers_cli-2.17.4 → papers_cli-2.17.5}/README.md +6 -0
  3. papers_cli-2.17.5/docs/adding-entries.md +93 -0
  4. {papers_cli-2.17.4 → papers_cli-2.17.5}/docs/quickstart.md +3 -0
  5. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers/__main__.py +63 -22
  6. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers/_version.py +3 -3
  7. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers/backup.py +9 -1
  8. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers/bib.py +16 -4
  9. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers/extract.py +198 -41
  10. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers_cli.egg-info/PKG-INFO +7 -1
  11. papers_cli-2.17.5/papers_cli.egg-info/scm_version.json +8 -0
  12. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/test_add.py +113 -0
  13. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/test_backup_dirs.py +85 -1
  14. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/test_duplicates.py +2 -2
  15. papers_cli-2.17.5/tests/test_extract_unit.py +430 -0
  16. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/test_list.py +7 -0
  17. papers_cli-2.17.4/docs/adding-entries.md +0 -61
  18. papers_cli-2.17.4/papers_cli.egg-info/scm_version.json +0 -8
  19. papers_cli-2.17.4/tests/test_extract_unit.py +0 -192
  20. {papers_cli-2.17.4 → papers_cli-2.17.5}/.gitattributes +0 -0
  21. {papers_cli-2.17.4 → papers_cli-2.17.5}/.github/FUNDING.yml +0 -0
  22. {papers_cli-2.17.4 → papers_cli-2.17.5}/.github/workflows/docs.yml +0 -0
  23. {papers_cli-2.17.4 → papers_cli-2.17.5}/.github/workflows/pypi.yml +0 -0
  24. {papers_cli-2.17.4 → papers_cli-2.17.5}/.github/workflows/release.yml +0 -0
  25. {papers_cli-2.17.4 → papers_cli-2.17.5}/.github/workflows/tests.yml +0 -0
  26. {papers_cli-2.17.4 → papers_cli-2.17.5}/.gitignore +0 -0
  27. {papers_cli-2.17.4 → papers_cli-2.17.5}/LICENSE +0 -0
  28. {papers_cli-2.17.4 → papers_cli-2.17.5}/docs/comparison.md +0 -0
  29. {papers_cli-2.17.4 → papers_cli-2.17.5}/docs/configuration.md +0 -0
  30. {papers_cli-2.17.4 → papers_cli-2.17.5}/docs/features.md +0 -0
  31. {papers_cli-2.17.4 → papers_cli-2.17.5}/docs/git.md +0 -0
  32. {papers_cli-2.17.4 → papers_cli-2.17.5}/docs/index.md +0 -0
  33. {papers_cli-2.17.4 → papers_cli-2.17.5}/docs/installation.md +0 -0
  34. {papers_cli-2.17.4 → papers_cli-2.17.5}/docs/library-management.md +0 -0
  35. {papers_cli-2.17.4 → papers_cli-2.17.5}/docs/listing.md +0 -0
  36. {papers_cli-2.17.4 → papers_cli-2.17.5}/docs/renaming.md +0 -0
  37. {papers_cli-2.17.4 → papers_cli-2.17.5}/docs/undo-redo.md +0 -0
  38. {papers_cli-2.17.4 → papers_cli-2.17.5}/mkdocs.yml +0 -0
  39. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers/__init__.py +0 -0
  40. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers/config.py +0 -0
  41. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers/duplicate.py +0 -0
  42. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers/encoding.py +0 -0
  43. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers/entries.py +0 -0
  44. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers/filename.py +0 -0
  45. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers/install.py +0 -0
  46. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers/latexenc.py +0 -0
  47. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers/utils.py +0 -0
  48. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers_cli.egg-info/SOURCES.txt +0 -0
  49. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers_cli.egg-info/dependency_links.txt +0 -0
  50. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers_cli.egg-info/entry_points.txt +0 -0
  51. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers_cli.egg-info/requires.txt +0 -0
  52. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers_cli.egg-info/scm_file_list.json +0 -0
  53. {papers_cli-2.17.4 → papers_cli-2.17.5}/papers_cli.egg-info/top_level.txt +0 -0
  54. {papers_cli-2.17.4 → papers_cli-2.17.5}/pyproject.toml +0 -0
  55. {papers_cli-2.17.4 → papers_cli-2.17.5}/requirements.txt +0 -0
  56. {papers_cli-2.17.4 → papers_cli-2.17.5}/scripts/README.md +0 -0
  57. {papers_cli-2.17.4 → papers_cli-2.17.5}/scripts/benchmark_bibtexparser_load.py +0 -0
  58. {papers_cli-2.17.4 → papers_cli-2.17.5}/scripts/generate_dummy_bib.py +0 -0
  59. {papers_cli-2.17.4 → papers_cli-2.17.5}/setup.cfg +0 -0
  60. {papers_cli-2.17.4 → papers_cli-2.17.5}/setup.py +0 -0
  61. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/__init__.py +0 -0
  62. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/common.py +0 -0
  63. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/config.py +0 -0
  64. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/download.py +0 -0
  65. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/downloadedpapers/.gitignore +0 -0
  66. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/downloadedpapers/bg-8-515-2011.pdf +0 -0
  67. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/downloadedpapers/bg-8-515-2011.txt +0 -0
  68. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/downloadedpapers/esd-4-11-2013-supplement.pdf +0 -0
  69. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/downloadedpapers/esd-4-11-2013.pdf +0 -0
  70. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/test_bib_unit.py +0 -0
  71. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/test_biblio.py +0 -0
  72. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/test_check.py +0 -0
  73. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/test_config_unit.py +0 -0
  74. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/test_encoding.py +0 -0
  75. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/test_extract.py +0 -0
  76. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/test_filecheck.py +0 -0
  77. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/test_filename_unit.py +0 -0
  78. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/test_install.py +0 -0
  79. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/test_latexenc.py +0 -0
  80. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/test_undo.py +0 -0
  81. {papers_cli-2.17.4 → papers_cli-2.17.5}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: papers-cli
3
- Version: 2.17.4
3
+ Version: 2.17.5
4
4
  Summary: utilities to keep your PDF library organized
5
5
  Author-email: Mahé Perrette <mahe.perrette@gmail.com>
6
6
  License: MIT
@@ -91,6 +91,12 @@ papers add esd-4-11-2013.pdf --rename --copy --bibtex papers.bib --filesdir file
91
91
  papers list perrette scaling approach sea level
92
92
  ```
93
93
 
94
+ Without `--rename` (`-r`) the PDF is linked where it is: the entry points to
95
+ its current location and nothing is copied into the files directory. `--rename`
96
+ moves it into `--filesdir` under a name made from the name template, and
97
+ `--copy` (`-c`) makes a copy instead of moving (it has no effect without
98
+ `--rename`).
99
+
94
100
  See the [quickstart](https://perrette.github.io/papers/quickstart/) for a full
95
101
  walkthrough.
96
102
 
@@ -63,6 +63,12 @@ papers add esd-4-11-2013.pdf --rename --copy --bibtex papers.bib --filesdir file
63
63
  papers list perrette scaling approach sea level
64
64
  ```
65
65
 
66
+ Without `--rename` (`-r`) the PDF is linked where it is: the entry points to
67
+ its current location and nothing is copied into the files directory. `--rename`
68
+ moves it into `--filesdir` under a name made from the name template, and
69
+ `--copy` (`-c`) makes a copy instead of moving (it has no effect without
70
+ `--rename`).
71
+
66
72
  See the [quickstart](https://perrette.github.io/papers/quickstart/) for a full
67
73
  walkthrough.
68
74
 
@@ -0,0 +1,93 @@
1
+ # Adding entries
2
+
3
+ The `papers add` command adds entries to your library, with or without a PDF
4
+ attachment.
5
+
6
+ ## Add a PDF (extract metadata automatically)
7
+
8
+ ```
9
+ $> papers add esd-4-11-2013.pdf --rename --copy --bibtex papers.bib --filesdir files --info
10
+ INFO:papers:found doi:10.5194/esd-4-11-2013
11
+ INFO:papers:new entry: perrette_landerer2013
12
+ INFO:papers:mv /home/perrette/playground/papers/esd-4-11-2013.pdf files/perrette_et_al_2013_a-scaling-approach-to-project-regional-sea-level-rise-and-its-uncertainties.pdf
13
+ INFO:papers:renamed file(s): 1
14
+ ```
15
+
16
+ That is equivalent to doing:
17
+
18
+ ```
19
+ papers extract esd-4-11-2013.pdf > entry.bib
20
+ papers add entry.bib --bibtex papers.bib --attachment esd-4-11-2013.pdf --rename --copy
21
+ ```
22
+
23
+ Without `--rename` (`-r`) the PDF is linked where it is: the `file` field
24
+ points to its current location and nothing is moved or copied into the files
25
+ directory. `--rename` moves the file into `--filesdir` under a name made from
26
+ the name template, and `--copy` (`-c`) makes a copy there instead of moving
27
+ the original (it has no effect without `--rename`). The same applies to the
28
+ files linked from the entries of a bibtex file added with `papers add
29
+ other.bib`: their paths are read relative to that bibtex and stored so that
30
+ they remain valid from your own bibtex (absolute, or relative to it, according
31
+ to `--absolute-paths`/`--relative-paths` or the installed setting); with
32
+ `--rename` they are moved (or copied with `--copy`) into the files directory.
33
+
34
+ See [Renaming files and keys](renaming.md) for how to specify file naming
35
+ patterns.
36
+
37
+ ## Add a whole directory of PDFs
38
+
39
+ It is possible to do this on a full directory of files, recursively:
40
+
41
+ ```
42
+ papers add --rename --recursive /home/perette/playground/papers/papers_test
43
+ ```
44
+
45
+ where, above, the `papers_test` directory contains a few PDF files. For each
46
+ PDF, `papers` will attempt to extract the metadata and add the relevant file to
47
+ the bibliography, and rename files into the files directory.
48
+
49
+ ## Add an entry from its DOI
50
+
51
+ If you already know the DOI of a PDF, and don't want to gamble the fulltext
52
+ search and match, you can indicate it via `--doi`:
53
+
54
+ ```
55
+ papers add esd-4-11-2013.pdf --doi 10.5194/esd-4-11-2013 --bibtex papers.bib
56
+ ```
57
+
58
+ The `add` command above also works without any PDF (create a bibtex entry
59
+ without file attachment):
60
+
61
+ ```
62
+ papers add --doi 10.5194/esd-4-11-2013 --bibtex papers.bib
63
+ ```
64
+
65
+ ## Add an entry without DOI from bibtex + PDF
66
+
67
+ Some old files don't have a DOI. The best approach is to add the entry from its
68
+ bibtex:
69
+
70
+ ```
71
+ papers add entry.bib --attachment esd-4-11-2013.pdf
72
+ ```
73
+
74
+ ## Duplicates and keys
75
+
76
+ Before inserting an entry, `papers add` looks for a duplicate in the library
77
+ (same DOI, or same authors and title). The `-m/--mode` option says what to do
78
+ when one is found: `i` (interactive, the default), `u` (update the existing
79
+ entry with the new entry's missing fields, existing values win), `U` (update
80
+ the existing entry with the new entry's values, new values win), `o`
81
+ (overwrite), `s` (skip the new entry), `a` (append it anyway), `r` (raise an
82
+ error).
83
+
84
+ Entries added from a bibtex file keep their own key, unless `-u/--update-key`
85
+ is passed, in which case the key is regenerated from the key template (see
86
+ [Renaming files and keys](renaming.md)). Entries extracted from a PDF or
87
+ fetched from a DOI always get a generated key.
88
+
89
+ With `-u/--update-key`, a duplicate is always merged under the key of the entry
90
+ already in the library, in every mode (`u`, `U`, `o`; with `a` the existing
91
+ entry keeps its key and the appended entry gets a fresh one). Without `-u`,
92
+ modes `u` and `s` keep the existing entry's key, while `U` and `o` take over
93
+ the new entry's key.
@@ -43,6 +43,9 @@ INFO:papers:renamed file(s): 1
43
43
  (the `--info` argument asks for the above output information to be printed out
44
44
  to the terminal)
45
45
 
46
+ Without `--rename` the PDF is linked where it is, and nothing is copied into
47
+ the files directory; `--copy` only applies together with `--rename`.
48
+
46
49
  See [Adding entries](adding-entries.md) for the full range of ways to add
47
50
  entries, and [Renaming files and keys](renaming.md) for how to specify file
48
51
  naming patterns.
@@ -69,10 +69,14 @@ def savebib(biblio, config):
69
69
  logger.info(f'Saving {config.bibtex}')
70
70
  if biblio is not None:
71
71
  biblio.save(config.bibtex)
72
- if config.file and config.git:
73
- silent_backup_bib(biblio, config)
72
+ if config.git:
73
+ # the backup comes last: a failure must not undo the work done above
74
+ try:
75
+ silent_backup_bib(biblio, config)
76
+ except Exception as error:
77
+ logger.error(f'the bibliography was saved, but its git backup failed: {error}')
74
78
  else:
75
- logger.debug(f'do not backup bib: {config.file}, {config.git}')
79
+ logger.debug(f'do not backup bib: git is off')
76
80
  # if config.git:
77
81
  # config.gitcommit()
78
82
 
@@ -177,11 +181,43 @@ def uninstallcmd(parser, o, config):
177
181
  if os.path.exists(configfile):
178
182
  uninstallcmd(parser, o, Config.load(configfile))
179
183
 
184
+ def config_for_bibtex(bibtex, config):
185
+ """The configuration to use when --bibtex is passed on the command line.
186
+
187
+ If it is another library than the configured one, the local configuration
188
+ of that library (searched from the bibtex's directory upwards, as for the
189
+ current directory) applies: its files directory, git backup and formats.
190
+ Without one, the current configuration applies to the bibtex, except that
191
+ it is never written back to disk and the backup goes to a directory of
192
+ the bibtex's own.
193
+ """
194
+ if config.bibtex and os.path.realpath(bibtex) == os.path.realpath(config.bibtex):
195
+ return config
196
+
197
+ configfile = search_config([CONFIG_FILE_LOCAL, os.path.join(".papers", "config.json")],
198
+ start_dir=os.path.dirname(os.path.abspath(bibtex)), default=None)
199
+ if configfile is not None:
200
+ other = Config.load(check_legacy_config(configfile))
201
+ if other.bibtex and os.path.realpath(other.bibtex) == os.path.realpath(bibtex):
202
+ logger.info(f'configuration of {bibtex}: {configfile}')
203
+ return other
204
+
205
+ logger.debug(f'{bibtex} has no configuration of its own: use {config.file} without saving it')
206
+ config = copy.copy(config)
207
+ config.file = None
208
+ if config.git:
209
+ from papers.backup import resolve_gitdir
210
+ config.gitdir = resolve_gitdir(bibtex)
211
+ return config
212
+
213
+
180
214
  def check_install(parser, o, config, bibtex_must_exist=True):
181
215
  """
182
216
  Given an option and config, checks to see if the install is done correctly on this filesystem.
217
+ Returns the configuration to use (see config_for_bibtex).
183
218
  """
184
219
  if getattr(o, "bibtex", None) is not None:
220
+ config = config_for_bibtex(o.bibtex, config)
185
221
  config.bibtex = o.bibtex
186
222
  if getattr(o, "filesdir", None) is not None:
187
223
  config.filesdir = o.filesdir
@@ -201,7 +237,7 @@ def check_install(parser, o, config, bibtex_must_exist=True):
201
237
  raise PapersExit()
202
238
  logger.info(f'bibtex: {config.bibtex!r}')
203
239
  logger.info(f'filesdir: {config.filesdir!r}')
204
- return True
240
+ return config
205
241
 
206
242
 
207
243
  def _print_biblio_diff(biblio_init, biblio, touched_entries, show_existing=False):
@@ -781,9 +817,9 @@ def get_parser(config=None):
781
817
 
782
818
  cfg = argparse.ArgumentParser(add_help=False, parents=[loggingp])
783
819
  grp = cfg.add_argument_group('config')
784
- grp.add_argument('--filesdir', default=None,
820
+ grp.add_argument('--filesdir', default=None, type=file_uri_to_path,
785
821
  help=f'files directory (default: {config.filesdir}')
786
- grp.add_argument('--bibtex', default=None,
822
+ grp.add_argument('--bibtex', default=None, type=file_uri_to_path,
787
823
  help=f'bibtex database (default: {config.bibtex}')
788
824
  grp.add_argument('--dry-run', action='store_true',
789
825
  help='no PDF renaming/copying, no bibtex writing on disk (for testing)')
@@ -878,7 +914,7 @@ def get_parser(config=None):
878
914
  # ===
879
915
  addp = subparsers.add_parser('add', description='add PDF(s) or bibtex(s) to library',
880
916
  parents=[cfg, namefmt, keyfmt])
881
- addp.add_argument('file', nargs='*', default=[])
917
+ addp.add_argument('file', nargs='*', default=[], type=file_uri_to_path)
882
918
  # addp.add_argument('-f','--force', action='store_true', help='disable interactive')
883
919
 
884
920
  grp = addp.add_argument_group('duplicate check')
@@ -918,12 +954,17 @@ def get_parser(config=None):
918
954
  grp.add_argument('--no-query-fulltext', action='store_true', help='do not attempt to query fulltext in case doi query fails')
919
955
  grp.add_argument('--scholar', action='store_true', help='use google scholar instead of crossref')
920
956
 
921
- grp = addp.add_argument_group('attached files')
922
- grp.add_argument('-a','--attachment', nargs='+') #'supplementary material')
957
+ grp = addp.add_argument_group('attached files',
958
+ description='''Without -r/--rename, the PDF (and the files of a bibtex entry) are linked
959
+ where they are: the file field points to their current location and nothing is
960
+ moved or copied into the files directory. -r moves them into the files directory
961
+ (--filesdir) under a name made from the name template; with -c the original is
962
+ kept and a copy is made instead (-c has no effect without -r).''')
963
+ grp.add_argument('-a','--attachment', nargs='+', type=file_uri_to_path, help='attach these files to the entry (supplementary material)')
923
964
  grp.add_argument('-r','--rename', action='store_true',
924
- help='rename PDFs according to key')
965
+ help='move the files into the files directory, renamed according to the name template')
925
966
  grp.add_argument('-c','--copy', action='store_true',
926
- help='copy file instead of moving them')
967
+ help='with -r, copy the files instead of moving them')
927
968
 
928
969
  grp = addp.add_argument_group('actions')
929
970
  grp.add_argument('-e', '--edit', action='store_true', help='edit entry')
@@ -1048,7 +1089,7 @@ def get_parser(config=None):
1048
1089
  grp.add_argument('-e', '--edit', action='store_true', help='interactive edit text file with entries, and re-insert them')
1049
1090
  grp.add_argument('--fetch', action='store_true', help='fetch and fix metadata')
1050
1091
  grp.add_argument('--add-keywords', '--add-tag', nargs='+', help='add keywords to the selected entries')
1051
- grp.add_argument('--add-files', nargs='+', help='add files to the selected entries (only one entry must be listed)')
1092
+ grp.add_argument('--add-files', nargs='+', type=file_uri_to_path, help='add files to the selected entries (only one entry must be listed)')
1052
1093
  grp.add_argument('--rename', action='store_true', help='rename file(s) of the listed entries into the files folder (also applies to files added via --add-files)')
1053
1094
  grp.add_argument('--copy', action='store_true', help='in combination with --rename, keep a copy of the file(s) in their original location')
1054
1095
  grp.add_argument('-o', '--open', action='store_true', help='open attachments (if any)')
@@ -1059,12 +1100,12 @@ def get_parser(config=None):
1059
1100
  # open
1060
1101
  # ====
1061
1102
  openp = subparsers.add_parser('open', description='open entry attachments or files with the system viewer', parents=[cfg])
1062
- openp.add_argument('key', nargs='+', help='entry key(s) (case-insensitive), or existing file path(s) to open directly')
1103
+ openp.add_argument('key', nargs='+', type=file_uri_to_path, help='entry key(s) (case-insensitive), or existing file path(s) to open directly')
1063
1104
 
1064
1105
  # doi
1065
1106
  # ===
1066
1107
  doip = subparsers.add_parser('doi', description='parse DOI from PDF', parents=[loggingp])
1067
- doip.add_argument('pdf')
1108
+ doip.add_argument('pdf', type=file_uri_to_path)
1068
1109
  doip.add_argument('--image', action='store_true', help='convert to image and use tesseract instead of pdftotext')
1069
1110
 
1070
1111
  # fetch
@@ -1076,7 +1117,7 @@ def get_parser(config=None):
1076
1117
  # extract
1077
1118
  # ========
1078
1119
  extractp = subparsers.add_parser('extract', description='extract pdf metadata', parents=[loggingp])
1079
- extractp.add_argument('pdf', nargs='+', help='one or several PDF files')
1120
+ extractp.add_argument('pdf', nargs='+', type=file_uri_to_path, help='one or several PDF files')
1080
1121
  extractp.add_argument('--recursive', action='store_true', help='accept directories and scan them for PDF files')
1081
1122
  extractp.add_argument('-n', '--word-count', type=int, default=200)
1082
1123
  extractp.add_argument('--fulltext', action='store_true', help='fulltext only (otherwise DOI-based)')
@@ -1212,23 +1253,23 @@ def main(args=None):
1212
1253
  uninstallcmd(subp, o, config)
1213
1254
  print(config.status(verbose=True))
1214
1255
  elif o.cmd == 'add':
1215
- check_install(subp, o, config) and addcmd(subp, o, config)
1256
+ addcmd(subp, o, check_install(subp, o, config))
1216
1257
  elif o.cmd == 'check':
1217
- check_install(subp, o, config) and checkcmd(subp, o, config)
1258
+ checkcmd(subp, o, check_install(subp, o, config))
1218
1259
  elif o.cmd == 'filecheck':
1219
- check_install(subp, o, config) and filecheckcmd(subp, o, config)
1260
+ filecheckcmd(subp, o, check_install(subp, o, config))
1220
1261
  elif o.cmd == 'list':
1221
- check_install(subp, o, config) and listcmd(subp, o, config)
1262
+ listcmd(subp, o, check_install(subp, o, config))
1222
1263
  elif o.cmd == 'open':
1223
1264
  # no install required when opening plain files; opencmd reports the
1224
1265
  # missing bibliography itself when an argument must be looked up as a key
1225
1266
  opencmd(subp, o, config)
1226
1267
  elif o.cmd == 'undo':
1227
- check_install(subp, o, config) and undocmd(subp, o, config)
1268
+ undocmd(subp, o, check_install(subp, o, config))
1228
1269
  elif o.cmd == 'redo':
1229
- check_install(subp, o, config) and redocmd(subp, o, config)
1270
+ redocmd(subp, o, check_install(subp, o, config))
1230
1271
  elif o.cmd == 'restore-backup':
1231
- check_install(subp, o, config, bibtex_must_exist=False) and restorecmd(subp, o, config)
1272
+ restorecmd(subp, o, check_install(subp, o, config, bibtex_must_exist=False))
1232
1273
  elif o.cmd == 'git':
1233
1274
  if not installed:
1234
1275
  subp.error('papers must be installed to use git command')
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.17.4'
22
- __version_tuple__ = version_tuple = (2, 17, 4)
21
+ __version__ = version = '2.17.5'
22
+ __version_tuple__ = version_tuple = (2, 17, 5)
23
23
 
24
- __commit_id__ = commit_id = 'gc3df29014'
24
+ __commit_id__ = commit_id = 'g6f089411a'
@@ -244,13 +244,21 @@ def backup_bib(biblio, config, message=None):
244
244
 
245
245
  if not config.git:
246
246
  raise PapersExit('cannot backup without --git enabled')
247
+ if not config.gitdir:
248
+ config.gitdir = resolve_gitdir(config.bibtex)
247
249
  backupdir = Path(config.gitdir)
248
- backupdir.mkdir(exist_ok=True)
250
+ backupdir.mkdir(parents=True, exist_ok=True)
249
251
 
250
252
  # never mix the histories of two libraries in one backup directory
251
253
  check_gitdir_ownership(config)
252
254
  backupdir = Path(config.gitdir) # may have been re-allocated
253
255
 
256
+ # a backup directory that was never initialized (or removed since the install)
257
+ if not (backupdir / ".git").exists():
258
+ logger.info(f"initialize backup repository: {backupdir}")
259
+ run_git(config.gitdir, ["init"])
260
+ write_manifest(config.gitdir, config.bibtex)
261
+
254
262
  # a snapshot made while undone must append to the full history
255
263
  adopt_legacy_undone_state(config.gitdir)
256
264
 
@@ -463,7 +463,13 @@ class Biblio:
463
463
  candidate.key = candidate_key_after
464
464
  entries = []
465
465
  for e in resolved:
466
- entries.extend( self.insert_entry(e, update_key, rename=rename, copy=copy) )
466
+ # the existing entry keeps its key (and so does the single
467
+ # resolved entry that carries it, e.g. an overwrite with
468
+ # --update-key): only entries new to the library get a key
469
+ # regenerated from the template
470
+ keeps_key = e is candidate or (
471
+ len(resolved) == 1 and get_entry_val(e, 'ID', '') == candidate_key_before)
472
+ entries.extend( self.insert_entry(e, update_key and not keeps_key, rename=rename, copy=copy) )
467
473
  return entries
468
474
 
469
475
 
@@ -480,13 +486,17 @@ class Biblio:
480
486
 
481
487
 
482
488
  def set_files(self, entry, files, relative_to=None):
483
- entry['file'] = format_file(list(sorted(set(files), key=lambda f: files.index(f))), relative_to=relative_to or self.relative_to)
489
+ if relative_to is None:
490
+ relative_to = self.relative_to
491
+ entry['file'] = format_file(list(sorted(set(files), key=lambda f: files.index(f))), relative_to=relative_to)
484
492
  # delete field if empty
485
493
  if not entry['file'].strip():
486
494
  del entry['file']
487
495
 
488
496
  def get_files(self, entry, relative_to=None):
489
- return parse_file(get_entry_val(entry, 'file', ''), relative_to=relative_to or self.relative_to)
497
+ if relative_to is None:
498
+ relative_to = self.relative_to
499
+ return parse_file(get_entry_val(entry, 'file', ''), relative_to=relative_to)
490
500
 
491
501
  def add_bibtex(self, bibtex, relative_to=None, attachments=None, convert_to_unicode=False, **kw):
492
502
  bib = parse_string(bibtex)
@@ -522,7 +532,9 @@ class Biblio:
522
532
 
523
533
  def add_bibtex_file(self, file, **kw):
524
534
  bibtex = open(file).read()
525
- return self.add_bibtex(bibtex, relative_to=os.path.dirname(file), **kw)
535
+ # file links in the added bibtex are relative to its own directory
536
+ # (the absolute path matters: dirname('papers.bib') is '')
537
+ return self.add_bibtex(bibtex, relative_to=os.path.dirname(os.path.abspath(file)), **kw)
526
538
 
527
539
 
528
540
  def fetch_doi(self, doi, **kw):