easybib 0.2.0__tar.gz → 0.3.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easybib
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Automatically fetch BibTeX entries from INSPIRE and ADS for LaTeX projects
5
5
  Author-email: Gregory Ashton <gregory.ashton@ligo.org>
6
6
  License-Expression: MIT
@@ -42,17 +42,18 @@ Pass a directory to scan all `.tex` files recursively, or a single `.tex` file.
42
42
  | Flag | Description |
43
43
  |------|-------------|
44
44
  | `-o`, `--output` | Output BibTeX file (default: `references.bib`) |
45
- | `-s`, `--source` | Preferred source: `ads` (default), `inspire`, or `auto` |
45
+ | `-s`, `--preferred-source` | Preferred source: `ads` (default), `inspire`, or `auto` |
46
46
  | `-a`, `--max-authors` | Truncate author lists (default: 3, use 0 for no limit) |
47
47
  | `-l`, `--list-keys` | List found citation keys and exit (no fetching) |
48
48
  | `--fresh` | Ignore existing output file and start from scratch |
49
49
  | `--ads-api-key` | ADS API key (overrides `ADS_API_KEY` environment variable) |
50
+ | `--config` | Path to config file (default: `~/.easybib.config`) |
50
51
 
51
52
  ### Examples
52
53
 
53
54
  ```bash
54
55
  # Scan a directory
55
- easybib ./paper -s inspire
56
+ easybib ./paper --preferred-source inspire
56
57
 
57
58
  # Scan a single file
58
59
  easybib paper.tex
@@ -67,6 +68,34 @@ easybib ./paper -l
67
68
  easybib ./paper -a 0
68
69
  ```
69
70
 
71
+ ### Config file
72
+
73
+ You can create a config file at `~/.easybib.config` to set persistent defaults, so you don't have to pass the same flags every time:
74
+
75
+ ```ini
76
+ [easybib]
77
+ output = references.bib
78
+ max-authors = 3
79
+ preferred-source = ads
80
+ ads-api-key = your-key-here
81
+ ```
82
+
83
+ All fields are optional. CLI flags override config file values, which override the built-in defaults.
84
+
85
+ To use a config file at a different location:
86
+
87
+ ```bash
88
+ easybib ./paper --config /path/to/my.config
89
+ ```
90
+
91
+ ### Source selection
92
+
93
+ The `--preferred-source` flag controls where BibTeX entries are fetched from. The source determines which service provides the BibTeX data, regardless of the key format used in your `.tex` files.
94
+
95
+ - **`ads`** (default) — Fetches BibTeX from ADS. If you use an INSPIRE-style key (e.g. `Author:2020abc`), easybib will cross-reference it via INSPIRE to find the corresponding ADS record, then pull the BibTeX from ADS. Falls back to INSPIRE if ADS lookup fails.
96
+ - **`inspire`** — Fetches BibTeX from INSPIRE. Falls back to ADS if the INSPIRE lookup fails. Does not require an ADS API key unless the fallback is triggered.
97
+ - **`auto`** — Chooses the source based on the key format: ADS bibcodes (e.g. `2016PhRvL.116f1102A`) are fetched from ADS, while INSPIRE-style keys are fetched from INSPIRE. Falls back to the other source if the preferred one fails.
98
+
70
99
  ### ADS API key
71
100
 
72
101
  When using ADS as the source (the default), provide your API key either via the command line:
@@ -27,17 +27,18 @@ Pass a directory to scan all `.tex` files recursively, or a single `.tex` file.
27
27
  | Flag | Description |
28
28
  |------|-------------|
29
29
  | `-o`, `--output` | Output BibTeX file (default: `references.bib`) |
30
- | `-s`, `--source` | Preferred source: `ads` (default), `inspire`, or `auto` |
30
+ | `-s`, `--preferred-source` | Preferred source: `ads` (default), `inspire`, or `auto` |
31
31
  | `-a`, `--max-authors` | Truncate author lists (default: 3, use 0 for no limit) |
32
32
  | `-l`, `--list-keys` | List found citation keys and exit (no fetching) |
33
33
  | `--fresh` | Ignore existing output file and start from scratch |
34
34
  | `--ads-api-key` | ADS API key (overrides `ADS_API_KEY` environment variable) |
35
+ | `--config` | Path to config file (default: `~/.easybib.config`) |
35
36
 
36
37
  ### Examples
37
38
 
38
39
  ```bash
39
40
  # Scan a directory
40
- easybib ./paper -s inspire
41
+ easybib ./paper --preferred-source inspire
41
42
 
42
43
  # Scan a single file
43
44
  easybib paper.tex
@@ -52,6 +53,34 @@ easybib ./paper -l
52
53
  easybib ./paper -a 0
53
54
  ```
54
55
 
56
+ ### Config file
57
+
58
+ You can create a config file at `~/.easybib.config` to set persistent defaults, so you don't have to pass the same flags every time:
59
+
60
+ ```ini
61
+ [easybib]
62
+ output = references.bib
63
+ max-authors = 3
64
+ preferred-source = ads
65
+ ads-api-key = your-key-here
66
+ ```
67
+
68
+ All fields are optional. CLI flags override config file values, which override the built-in defaults.
69
+
70
+ To use a config file at a different location:
71
+
72
+ ```bash
73
+ easybib ./paper --config /path/to/my.config
74
+ ```
75
+
76
+ ### Source selection
77
+
78
+ The `--preferred-source` flag controls where BibTeX entries are fetched from. The source determines which service provides the BibTeX data, regardless of the key format used in your `.tex` files.
79
+
80
+ - **`ads`** (default) — Fetches BibTeX from ADS. If you use an INSPIRE-style key (e.g. `Author:2020abc`), easybib will cross-reference it via INSPIRE to find the corresponding ADS record, then pull the BibTeX from ADS. Falls back to INSPIRE if ADS lookup fails.
81
+ - **`inspire`** — Fetches BibTeX from INSPIRE. Falls back to ADS if the INSPIRE lookup fails. Does not require an ADS API key unless the fallback is triggered.
82
+ - **`auto`** — Chooses the source based on the key format: ADS bibcodes (e.g. `2016PhRvL.116f1102A`) are fetched from ADS, while INSPIRE-style keys are fetched from INSPIRE. Falls back to the other source if the preferred one fails.
83
+
55
84
  ### ADS API key
56
85
 
57
86
  When using ADS as the source (the default), provide your API key either via the command line:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "easybib"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Automatically fetch BibTeX entries from INSPIRE and ADS for LaTeX projects"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,9 +1,11 @@
1
1
  """Command-line interface for easybib."""
2
2
 
3
+ import configparser
3
4
  import os
4
5
  import argparse
5
6
  from pathlib import Path
6
7
 
8
+ from easybib import __version__
7
9
  from easybib.core import (
8
10
  extract_cite_keys,
9
11
  extract_existing_bib_keys,
@@ -13,11 +15,52 @@ from easybib.core import (
13
15
  )
14
16
 
15
17
 
18
+ def load_config(config_path):
19
+ """Read an INI config file and return a dict from the [easybib] section."""
20
+ path = Path(config_path).expanduser()
21
+ if not path.is_file():
22
+ return {}
23
+ config = configparser.ConfigParser()
24
+ config.read(path)
25
+ if "easybib" not in config:
26
+ return {}
27
+ return dict(config["easybib"])
28
+
29
+
16
30
  def main():
31
+ # First pass: extract --config so we know which config file to load
32
+ pre_parser = argparse.ArgumentParser(add_help=False)
33
+ pre_parser.add_argument(
34
+ "--config",
35
+ default="~/.easybib.config",
36
+ help="Path to config file (default: ~/.easybib.config)",
37
+ )
38
+ pre_args, _ = pre_parser.parse_known_args()
39
+
40
+ # Load config and build defaults
41
+ cfg = load_config(pre_args.config)
42
+ config_defaults = {}
43
+ if "output" in cfg:
44
+ config_defaults["output"] = cfg["output"]
45
+ if "max-authors" in cfg:
46
+ config_defaults["max_authors"] = int(cfg["max-authors"])
47
+ if "preferred-source" in cfg:
48
+ config_defaults["preferred_source"] = cfg["preferred-source"]
49
+ if "ads-api-key" in cfg:
50
+ config_defaults["ads_api_key"] = cfg["ads-api-key"]
51
+
17
52
  parser = argparse.ArgumentParser(
18
53
  description="Extract citations and download BibTeX from NASA/ADS"
19
54
  )
55
+ parser.add_argument(
56
+ "--version", action="version", version=f"%(prog)s {__version__}"
57
+ )
20
58
  parser.add_argument("path", help="LaTeX file or directory containing LaTeX files")
59
+ parser.add_argument(
60
+ "--config",
61
+ default="~/.easybib.config",
62
+ help="Path to config file (default: ~/.easybib.config)",
63
+ )
21
64
  parser.add_argument(
22
65
  "-o", "--output", default="references.bib", help="Output BibTeX file (existing entries are retained)"
23
66
  )
@@ -41,7 +84,7 @@ def main():
41
84
  )
42
85
  parser.add_argument(
43
86
  "-s",
44
- "--source",
87
+ "--preferred-source",
45
88
  choices=["ads", "inspire", "auto"],
46
89
  default="ads",
47
90
  help="Preferred BibTeX source: 'ads' (default), 'inspire', or 'auto' (based on key format)",
@@ -50,6 +93,11 @@ def main():
50
93
  "--ads-api-key",
51
94
  help="ADS API key (overrides ADS_API_KEY environment variable)",
52
95
  )
96
+
97
+ # Apply config file defaults (CLI flags will still override)
98
+ if config_defaults:
99
+ parser.set_defaults(**config_defaults)
100
+
53
101
  args = parser.parse_args()
54
102
 
55
103
  # Collect all citation keys
@@ -80,12 +128,12 @@ def main():
80
128
  print(key)
81
129
  return 0
82
130
 
83
- # Check for ADS API key (not required if using --source inspire)
131
+ # Check for ADS API key (not required if using --preferred-source inspire)
84
132
  api_key = args.ads_api_key or os.getenv("ADS_API_KEY")
85
- if not api_key and args.source != "inspire":
133
+ if not api_key and args.preferred_source != "inspire":
86
134
  print("Error: ADS_API_KEY environment variable not set")
87
135
  print("Get your API key from: https://ui.adsabs.harvard.edu/user/settings/token")
88
- print("(Or use --source inspire to fetch from INSPIRE without an ADS key)")
136
+ print("(Or use --preferred-source inspire to fetch from INSPIRE without an ADS key)")
89
137
  return 1
90
138
 
91
139
  # Check for existing bib file and determine which keys to fetch
@@ -108,7 +156,7 @@ def main():
108
156
  not_found = []
109
157
  for key in sorted(keys_to_fetch):
110
158
  print(f"Fetching {key}...", end=" ")
111
- bibtex, source = fetch_bibtex(key, api_key, args.source)
159
+ bibtex, source = fetch_bibtex(key, api_key, args.preferred_source)
112
160
  if bibtex:
113
161
  bibtex = replace_bibtex_key(bibtex, key)
114
162
  bibtex = truncate_authors(bibtex, args.max_authors)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easybib
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Automatically fetch BibTeX entries from INSPIRE and ADS for LaTeX projects
5
5
  Author-email: Gregory Ashton <gregory.ashton@ligo.org>
6
6
  License-Expression: MIT
@@ -42,17 +42,18 @@ Pass a directory to scan all `.tex` files recursively, or a single `.tex` file.
42
42
  | Flag | Description |
43
43
  |------|-------------|
44
44
  | `-o`, `--output` | Output BibTeX file (default: `references.bib`) |
45
- | `-s`, `--source` | Preferred source: `ads` (default), `inspire`, or `auto` |
45
+ | `-s`, `--preferred-source` | Preferred source: `ads` (default), `inspire`, or `auto` |
46
46
  | `-a`, `--max-authors` | Truncate author lists (default: 3, use 0 for no limit) |
47
47
  | `-l`, `--list-keys` | List found citation keys and exit (no fetching) |
48
48
  | `--fresh` | Ignore existing output file and start from scratch |
49
49
  | `--ads-api-key` | ADS API key (overrides `ADS_API_KEY` environment variable) |
50
+ | `--config` | Path to config file (default: `~/.easybib.config`) |
50
51
 
51
52
  ### Examples
52
53
 
53
54
  ```bash
54
55
  # Scan a directory
55
- easybib ./paper -s inspire
56
+ easybib ./paper --preferred-source inspire
56
57
 
57
58
  # Scan a single file
58
59
  easybib paper.tex
@@ -67,6 +68,34 @@ easybib ./paper -l
67
68
  easybib ./paper -a 0
68
69
  ```
69
70
 
71
+ ### Config file
72
+
73
+ You can create a config file at `~/.easybib.config` to set persistent defaults, so you don't have to pass the same flags every time:
74
+
75
+ ```ini
76
+ [easybib]
77
+ output = references.bib
78
+ max-authors = 3
79
+ preferred-source = ads
80
+ ads-api-key = your-key-here
81
+ ```
82
+
83
+ All fields are optional. CLI flags override config file values, which override the built-in defaults.
84
+
85
+ To use a config file at a different location:
86
+
87
+ ```bash
88
+ easybib ./paper --config /path/to/my.config
89
+ ```
90
+
91
+ ### Source selection
92
+
93
+ The `--preferred-source` flag controls where BibTeX entries are fetched from. The source determines which service provides the BibTeX data, regardless of the key format used in your `.tex` files.
94
+
95
+ - **`ads`** (default) — Fetches BibTeX from ADS. If you use an INSPIRE-style key (e.g. `Author:2020abc`), easybib will cross-reference it via INSPIRE to find the corresponding ADS record, then pull the BibTeX from ADS. Falls back to INSPIRE if ADS lookup fails.
96
+ - **`inspire`** — Fetches BibTeX from INSPIRE. Falls back to ADS if the INSPIRE lookup fails. Does not require an ADS API key unless the fallback is triggered.
97
+ - **`auto`** — Chooses the source based on the key format: ADS bibcodes (e.g. `2016PhRvL.116f1102A`) are fetched from ADS, while INSPIRE-style keys are fetched from INSPIRE. Falls back to the other source if the preferred one fails.
98
+
70
99
  ### ADS API key
71
100
 
72
101
  When using ADS as the source (the default), provide your API key either via the command line:
@@ -0,0 +1,274 @@
1
+ """Tests for easybib CLI."""
2
+
3
+ import subprocess
4
+ import sys
5
+ from unittest.mock import patch
6
+
7
+ from easybib.cli import main
8
+
9
+
10
+ class TestListKeys:
11
+ def test_list_keys_single_file(self, tmp_path, capsys):
12
+ tex = tmp_path / "test.tex"
13
+ tex.write_text(r"\cite{Author:2020abc} and \citep{Other:2021xyz}")
14
+ with patch("sys.argv", ["easybib", str(tex), "--list-keys"]):
15
+ result = main()
16
+ assert result == 0
17
+ captured = capsys.readouterr()
18
+ assert "Author:2020abc" in captured.out
19
+ assert "Other:2021xyz" in captured.out
20
+
21
+ def test_list_keys_directory(self, tmp_path, capsys):
22
+ sub = tmp_path / "subdir"
23
+ sub.mkdir()
24
+ (tmp_path / "a.tex").write_text(r"\cite{A:2020abc}")
25
+ (sub / "b.tex").write_text(r"\cite{B:2021xyz}")
26
+ with patch("sys.argv", ["easybib", str(tmp_path), "--list-keys"]):
27
+ result = main()
28
+ assert result == 0
29
+ captured = capsys.readouterr()
30
+ assert "A:2020abc" in captured.out
31
+ assert "B:2021xyz" in captured.out
32
+
33
+
34
+ class TestMissingApiKey:
35
+ def test_error_without_api_key(self, tmp_path, capsys):
36
+ tex = tmp_path / "test.tex"
37
+ tex.write_text(r"\cite{Author:2020abc}")
38
+ no_config = str(tmp_path / "nonexistent.config")
39
+ with (
40
+ patch("sys.argv", ["easybib", str(tex), "--config", no_config]),
41
+ patch.dict("os.environ", {}, clear=True),
42
+ ):
43
+ result = main()
44
+ assert result == 1
45
+ captured = capsys.readouterr()
46
+ assert "ADS_API_KEY" in captured.out
47
+
48
+ def test_inspire_source_no_api_key_ok(self, tmp_path, capsys):
49
+ """Using --preferred-source inspire should not require an ADS API key."""
50
+ tex = tmp_path / "test.tex"
51
+ tex.write_text(r"\cite{Author:2020abc}")
52
+ no_config = str(tmp_path / "nonexistent.config")
53
+ with (
54
+ patch("sys.argv", ["easybib", str(tex), "--preferred-source", "inspire", "--config", no_config]),
55
+ patch.dict("os.environ", {}, clear=True),
56
+ patch("easybib.cli.fetch_bibtex", return_value=(None, None)),
57
+ ):
58
+ result = main()
59
+ # Should not return 1 for missing API key
60
+ assert result is None
61
+
62
+
63
+ class TestAdsApiKeyOverride:
64
+ def test_flag_overrides_env(self, tmp_path, capsys):
65
+ tex = tmp_path / "test.tex"
66
+ tex.write_text(r"\cite{Author:2020abc}")
67
+ with (
68
+ patch(
69
+ "sys.argv",
70
+ ["easybib", str(tex), "--ads-api-key", "flag-key", "-o", str(tmp_path / "out.bib")],
71
+ ),
72
+ patch.dict("os.environ", {"ADS_API_KEY": "env-key"}, clear=True),
73
+ patch("easybib.cli.fetch_bibtex") as mock_fetch,
74
+ ):
75
+ mock_fetch.return_value = (
76
+ "@article{Author:2020abc,\n title={Test},\n author={Doe, J.},\n}",
77
+ "ADS",
78
+ )
79
+ main()
80
+ # The flag value should be used, not the env var
81
+ call_args = mock_fetch.call_args
82
+ assert call_args[0][1] == "flag-key"
83
+
84
+
85
+ class TestConfigFile:
86
+ def test_config_sets_defaults(self, tmp_path, capsys):
87
+ """Config file values are used when no CLI flags are given."""
88
+ tex = tmp_path / "test.tex"
89
+ tex.write_text(r"\cite{Author:2020abc}")
90
+ cfg = tmp_path / "test.config"
91
+ cfg.write_text(
92
+ "[easybib]\noutput = custom.bib\nmax-authors = 5\npreferred-source = inspire\nads-api-key = cfg-key\n"
93
+ )
94
+ with (
95
+ patch(
96
+ "sys.argv",
97
+ ["easybib", str(tex), "--config", str(cfg), "--list-keys"],
98
+ ),
99
+ ):
100
+ main()
101
+ captured = capsys.readouterr()
102
+ assert "Author:2020abc" in captured.out
103
+
104
+ def test_config_values_applied(self, tmp_path):
105
+ """Config file values feed into parsed args."""
106
+ tex = tmp_path / "test.tex"
107
+ tex.write_text(r"\cite{Author:2020abc}")
108
+ cfg = tmp_path / "test.config"
109
+ cfg.write_text(
110
+ "[easybib]\noutput = custom.bib\nmax-authors = 5\npreferred-source = inspire\nads-api-key = cfg-key\n"
111
+ )
112
+ with (
113
+ patch(
114
+ "sys.argv",
115
+ ["easybib", str(tex), "--config", str(cfg), "--list-keys"],
116
+ ),
117
+ patch("easybib.cli.extract_cite_keys", return_value=(set(), [])),
118
+ ):
119
+ # Access the parsed args by patching parse_args
120
+ import easybib.cli as cli_mod
121
+
122
+ original_parse = cli_mod.argparse.ArgumentParser.parse_args
123
+
124
+ captured_args = {}
125
+
126
+ def spy_parse(self_parser, *a, **kw):
127
+ result = original_parse(self_parser, *a, **kw)
128
+ captured_args.update(vars(result))
129
+ return result
130
+
131
+ with patch.object(
132
+ cli_mod.argparse.ArgumentParser, "parse_args", spy_parse
133
+ ):
134
+ main()
135
+
136
+ assert captured_args["output"] == "custom.bib"
137
+ assert captured_args["max_authors"] == 5
138
+ assert captured_args["preferred_source"] == "inspire"
139
+ assert captured_args["ads_api_key"] == "cfg-key"
140
+
141
+ def test_cli_flags_override_config(self, tmp_path):
142
+ """CLI flags take priority over config file values."""
143
+ tex = tmp_path / "test.tex"
144
+ tex.write_text(r"\cite{Author:2020abc}")
145
+ cfg = tmp_path / "test.config"
146
+ cfg.write_text(
147
+ "[easybib]\noutput = config.bib\nmax-authors = 5\npreferred-source = inspire\nads-api-key = cfg-key\n"
148
+ )
149
+ with (
150
+ patch(
151
+ "sys.argv",
152
+ [
153
+ "easybib",
154
+ str(tex),
155
+ "--config",
156
+ str(cfg),
157
+ "--list-keys",
158
+ "-o",
159
+ "cli.bib",
160
+ "--max-authors",
161
+ "10",
162
+ "--preferred-source",
163
+ "ads",
164
+ "--ads-api-key",
165
+ "cli-key",
166
+ ],
167
+ ),
168
+ patch("easybib.cli.extract_cite_keys", return_value=(set(), [])),
169
+ ):
170
+ import easybib.cli as cli_mod
171
+
172
+ original_parse = cli_mod.argparse.ArgumentParser.parse_args
173
+ captured_args = {}
174
+
175
+ def spy_parse(self_parser, *a, **kw):
176
+ result = original_parse(self_parser, *a, **kw)
177
+ captured_args.update(vars(result))
178
+ return result
179
+
180
+ with patch.object(
181
+ cli_mod.argparse.ArgumentParser, "parse_args", spy_parse
182
+ ):
183
+ main()
184
+
185
+ assert captured_args["output"] == "cli.bib"
186
+ assert captured_args["max_authors"] == 10
187
+ assert captured_args["preferred_source"] == "ads"
188
+ assert captured_args["ads_api_key"] == "cli-key"
189
+
190
+ def test_missing_config_silently_ignored(self, tmp_path, capsys):
191
+ """A nonexistent config file does not cause an error."""
192
+ tex = tmp_path / "test.tex"
193
+ tex.write_text(r"\cite{Author:2020abc}")
194
+ with patch(
195
+ "sys.argv",
196
+ [
197
+ "easybib",
198
+ str(tex),
199
+ "--config",
200
+ str(tmp_path / "nonexistent.config"),
201
+ "--list-keys",
202
+ ],
203
+ ):
204
+ result = main()
205
+ assert result == 0
206
+ captured = capsys.readouterr()
207
+ assert "Author:2020abc" in captured.out
208
+
209
+ def test_custom_config_path(self, tmp_path, capsys):
210
+ """--config flag points to a custom path."""
211
+ tex = tmp_path / "test.tex"
212
+ tex.write_text(r"\cite{Author:2020abc}")
213
+ custom_dir = tmp_path / "custom"
214
+ custom_dir.mkdir()
215
+ cfg = custom_dir / "my.config"
216
+ cfg.write_text("[easybib]\npreferred-source = inspire\n")
217
+ with (
218
+ patch(
219
+ "sys.argv",
220
+ ["easybib", str(tex), "--config", str(cfg), "--list-keys"],
221
+ ),
222
+ ):
223
+ result = main()
224
+ assert result == 0
225
+
226
+ def test_config_ads_api_key_used_for_lookup(self, tmp_path):
227
+ """ads-api-key from config feeds into the API key chain."""
228
+ tex = tmp_path / "test.tex"
229
+ tex.write_text(r"\cite{Author:2020abc}")
230
+ cfg = tmp_path / "test.config"
231
+ cfg.write_text("[easybib]\nads-api-key = config-api-key\n")
232
+ with (
233
+ patch(
234
+ "sys.argv",
235
+ [
236
+ "easybib",
237
+ str(tex),
238
+ "--config",
239
+ str(cfg),
240
+ "-o",
241
+ str(tmp_path / "out.bib"),
242
+ ],
243
+ ),
244
+ patch.dict("os.environ", {}, clear=True),
245
+ patch("easybib.cli.fetch_bibtex") as mock_fetch,
246
+ ):
247
+ mock_fetch.return_value = (
248
+ "@article{Author:2020abc,\n title={Test},\n author={Doe, J.},\n}",
249
+ "ADS",
250
+ )
251
+ main()
252
+ call_args = mock_fetch.call_args
253
+ assert call_args[0][1] == "config-api-key"
254
+
255
+
256
+ class TestFileVsDirectory:
257
+ def test_single_file(self, tmp_path, capsys):
258
+ tex = tmp_path / "paper.tex"
259
+ tex.write_text(r"\cite{A:2020abc}")
260
+ with patch("sys.argv", ["easybib", str(tex), "--list-keys"]):
261
+ main()
262
+ captured = capsys.readouterr()
263
+ assert "A:2020abc" in captured.out
264
+
265
+ def test_directory_recursive(self, tmp_path, capsys):
266
+ nested = tmp_path / "ch1"
267
+ nested.mkdir()
268
+ (nested / "intro.tex").write_text(r"\cite{A:2020abc}")
269
+ (tmp_path / "main.tex").write_text(r"\cite{B:2021xyz}")
270
+ with patch("sys.argv", ["easybib", str(tmp_path), "--list-keys"]):
271
+ main()
272
+ captured = capsys.readouterr()
273
+ assert "A:2020abc" in captured.out
274
+ assert "B:2021xyz" in captured.out
@@ -1,101 +0,0 @@
1
- """Tests for easybib CLI."""
2
-
3
- import subprocess
4
- import sys
5
- from unittest.mock import patch
6
-
7
- from easybib.cli import main
8
-
9
-
10
- class TestListKeys:
11
- def test_list_keys_single_file(self, tmp_path, capsys):
12
- tex = tmp_path / "test.tex"
13
- tex.write_text(r"\cite{Author:2020abc} and \citep{Other:2021xyz}")
14
- with patch("sys.argv", ["easybib", str(tex), "--list-keys"]):
15
- result = main()
16
- assert result == 0
17
- captured = capsys.readouterr()
18
- assert "Author:2020abc" in captured.out
19
- assert "Other:2021xyz" in captured.out
20
-
21
- def test_list_keys_directory(self, tmp_path, capsys):
22
- sub = tmp_path / "subdir"
23
- sub.mkdir()
24
- (tmp_path / "a.tex").write_text(r"\cite{A:2020abc}")
25
- (sub / "b.tex").write_text(r"\cite{B:2021xyz}")
26
- with patch("sys.argv", ["easybib", str(tmp_path), "--list-keys"]):
27
- result = main()
28
- assert result == 0
29
- captured = capsys.readouterr()
30
- assert "A:2020abc" in captured.out
31
- assert "B:2021xyz" in captured.out
32
-
33
-
34
- class TestMissingApiKey:
35
- def test_error_without_api_key(self, tmp_path, capsys):
36
- tex = tmp_path / "test.tex"
37
- tex.write_text(r"\cite{Author:2020abc}")
38
- with (
39
- patch("sys.argv", ["easybib", str(tex)]),
40
- patch.dict("os.environ", {}, clear=True),
41
- ):
42
- result = main()
43
- assert result == 1
44
- captured = capsys.readouterr()
45
- assert "ADS_API_KEY" in captured.out
46
-
47
- def test_inspire_source_no_api_key_ok(self, tmp_path, capsys):
48
- """Using --source inspire should not require an ADS API key."""
49
- tex = tmp_path / "test.tex"
50
- tex.write_text(r"\cite{Author:2020abc}")
51
- with (
52
- patch("sys.argv", ["easybib", str(tex), "--source", "inspire"]),
53
- patch.dict("os.environ", {}, clear=True),
54
- patch("easybib.cli.fetch_bibtex", return_value=(None, None)),
55
- ):
56
- result = main()
57
- # Should not return 1 for missing API key
58
- assert result is None
59
-
60
-
61
- class TestAdsApiKeyOverride:
62
- def test_flag_overrides_env(self, tmp_path, capsys):
63
- tex = tmp_path / "test.tex"
64
- tex.write_text(r"\cite{Author:2020abc}")
65
- with (
66
- patch(
67
- "sys.argv",
68
- ["easybib", str(tex), "--ads-api-key", "flag-key"],
69
- ),
70
- patch.dict("os.environ", {"ADS_API_KEY": "env-key"}, clear=True),
71
- patch("easybib.cli.fetch_bibtex") as mock_fetch,
72
- ):
73
- mock_fetch.return_value = (
74
- "@article{Author:2020abc,\n title={Test},\n author={Doe, J.},\n}",
75
- "ADS",
76
- )
77
- main()
78
- # The flag value should be used, not the env var
79
- call_args = mock_fetch.call_args
80
- assert call_args[0][1] == "flag-key"
81
-
82
-
83
- class TestFileVsDirectory:
84
- def test_single_file(self, tmp_path, capsys):
85
- tex = tmp_path / "paper.tex"
86
- tex.write_text(r"\cite{A:2020abc}")
87
- with patch("sys.argv", ["easybib", str(tex), "--list-keys"]):
88
- main()
89
- captured = capsys.readouterr()
90
- assert "A:2020abc" in captured.out
91
-
92
- def test_directory_recursive(self, tmp_path, capsys):
93
- nested = tmp_path / "ch1"
94
- nested.mkdir()
95
- (nested / "intro.tex").write_text(r"\cite{A:2020abc}")
96
- (tmp_path / "main.tex").write_text(r"\cite{B:2021xyz}")
97
- with patch("sys.argv", ["easybib", str(tmp_path), "--list-keys"]):
98
- main()
99
- captured = capsys.readouterr()
100
- assert "A:2020abc" in captured.out
101
- assert "B:2021xyz" in captured.out
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes