article-cli 1.0.0__py3-none-any.whl
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.
- article_cli/__init__.py +20 -0
- article_cli/__main__.py +8 -0
- article_cli/cli.py +308 -0
- article_cli/config.py +275 -0
- article_cli/git_manager.py +356 -0
- article_cli/zotero.py +359 -0
- article_cli-1.0.0.dist-info/METADATA +215 -0
- article_cli-1.0.0.dist-info/RECORD +11 -0
- article_cli-1.0.0.dist-info/WHEEL +4 -0
- article_cli-1.0.0.dist-info/entry_points.txt +2 -0
- article_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
article_cli/zotero.py
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Zotero bibliography synchronization module
|
|
3
|
+
|
|
4
|
+
Handles fetching BibTeX entries from Zotero API with robust pagination,
|
|
5
|
+
error handling, and rate limiting.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from typing import Optional, Tuple, List
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import requests
|
|
15
|
+
except ImportError:
|
|
16
|
+
print("Error: 'requests' library is required. Install with: pip install requests")
|
|
17
|
+
sys.exit(1)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Colors:
|
|
21
|
+
"""ANSI color codes for terminal output"""
|
|
22
|
+
|
|
23
|
+
HEADER = "\033[95m"
|
|
24
|
+
OKBLUE = "\033[94m"
|
|
25
|
+
OKCYAN = "\033[96m"
|
|
26
|
+
OKGREEN = "\033[92m"
|
|
27
|
+
WARNING = "\033[93m"
|
|
28
|
+
FAIL = "\033[91m"
|
|
29
|
+
ENDC = "\033[0m"
|
|
30
|
+
BOLD = "\033[1m"
|
|
31
|
+
UNDERLINE = "\033[4m"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def print_success(msg: str):
|
|
35
|
+
"""Print success message in green"""
|
|
36
|
+
print(f"{Colors.OKGREEN}✓ {msg}{Colors.ENDC}")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def print_error(msg: str):
|
|
40
|
+
"""Print error message in red"""
|
|
41
|
+
print(f"{Colors.FAIL}✗ Error: {msg}{Colors.ENDC}", file=sys.stderr)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def print_warning(msg: str):
|
|
45
|
+
"""Print warning message in yellow"""
|
|
46
|
+
print(f"{Colors.WARNING}⚠ Warning: {msg}{Colors.ENDC}")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def print_info(msg: str):
|
|
50
|
+
"""Print info message in cyan"""
|
|
51
|
+
print(f"{Colors.OKCYAN}ℹ {msg}{Colors.ENDC}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ZoteroBibTexUpdater:
|
|
55
|
+
"""Handle Zotero BibTeX synchronization with robust error handling and rate limiting"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
api_key: str,
|
|
60
|
+
user_id: Optional[str] = None,
|
|
61
|
+
group_id: Optional[str] = None,
|
|
62
|
+
output_file: str = "references.bib",
|
|
63
|
+
):
|
|
64
|
+
self.api_key = api_key
|
|
65
|
+
self.user_id = user_id
|
|
66
|
+
self.group_id = group_id
|
|
67
|
+
self.output_file = output_file
|
|
68
|
+
self.base_url = "https://api.zotero.org"
|
|
69
|
+
self.limit = 100
|
|
70
|
+
self._group_name: Optional[str] = None
|
|
71
|
+
|
|
72
|
+
# Create session with persistent headers
|
|
73
|
+
self.session = requests.Session()
|
|
74
|
+
self.session.headers.update(
|
|
75
|
+
{
|
|
76
|
+
"Zotero-API-Version": "3",
|
|
77
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
78
|
+
"User-Agent": "ArticleCLI/1.0",
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def _build_url(self) -> str:
|
|
83
|
+
"""Build the Zotero API URL based on user or group ID"""
|
|
84
|
+
if self.group_id:
|
|
85
|
+
return f"{self.base_url}/groups/{self.group_id}/items"
|
|
86
|
+
elif self.user_id:
|
|
87
|
+
return f"{self.base_url}/users/{self.user_id}/items"
|
|
88
|
+
else:
|
|
89
|
+
raise ValueError("Either user_id or group_id must be provided")
|
|
90
|
+
|
|
91
|
+
def _get_group_info(self) -> Optional[dict]:
|
|
92
|
+
"""Get group information including name"""
|
|
93
|
+
if not self.group_id:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
url = f"{self.base_url}/groups/{self.group_id}"
|
|
98
|
+
response = self.session.get(url, timeout=10)
|
|
99
|
+
response.raise_for_status()
|
|
100
|
+
return response.json()
|
|
101
|
+
except Exception as e:
|
|
102
|
+
print_warning(f"Could not fetch group info: {e}")
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
def get_group_name(self) -> Optional[str]:
|
|
106
|
+
"""Get the name of the Zotero group"""
|
|
107
|
+
if self._group_name is not None:
|
|
108
|
+
return self._group_name
|
|
109
|
+
|
|
110
|
+
if not self.group_id:
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
group_info = self._get_group_info()
|
|
114
|
+
if group_info and "data" in group_info:
|
|
115
|
+
self._group_name = group_info["data"].get("name", "Unknown Group")
|
|
116
|
+
return self._group_name
|
|
117
|
+
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
def _fetch_page(self, url: str, start: int = 0) -> Tuple[str, dict]:
|
|
121
|
+
"""
|
|
122
|
+
Fetch a single page of results from Zotero API
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
url: Base API URL
|
|
126
|
+
start: Starting index for pagination
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Tuple of (response_content, headers_dict)
|
|
130
|
+
"""
|
|
131
|
+
params = {"format": "bibtex", "start": start, "limit": self.limit}
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
response = self.session.get(url, params=params, timeout=30)
|
|
135
|
+
response.raise_for_status()
|
|
136
|
+
|
|
137
|
+
# Check rate limiting
|
|
138
|
+
rate_limit_remaining = response.headers.get("X-RateLimit-Remaining")
|
|
139
|
+
if rate_limit_remaining and int(rate_limit_remaining) < 5:
|
|
140
|
+
rate_limit_reset = int(response.headers.get("X-RateLimit-Reset", 0))
|
|
141
|
+
wait_time = max(0, rate_limit_reset - time.time())
|
|
142
|
+
if wait_time > 0:
|
|
143
|
+
print_warning(
|
|
144
|
+
f"Rate limit low ({rate_limit_remaining} remaining). Waiting {wait_time:.1f}s..."
|
|
145
|
+
)
|
|
146
|
+
time.sleep(wait_time + 1)
|
|
147
|
+
|
|
148
|
+
return response.text, dict(response.headers)
|
|
149
|
+
|
|
150
|
+
except requests.exceptions.HTTPError as e:
|
|
151
|
+
if e.response.status_code == 429:
|
|
152
|
+
retry_after = int(e.response.headers.get("Retry-After", 60))
|
|
153
|
+
print_error(f"Rate limit exceeded. Retry after {retry_after}s")
|
|
154
|
+
raise
|
|
155
|
+
elif e.response.status_code == 403:
|
|
156
|
+
print_error("Access forbidden. Check your API key and permissions.")
|
|
157
|
+
raise
|
|
158
|
+
elif e.response.status_code == 404:
|
|
159
|
+
print_error("Resource not found. Check your user/group ID.")
|
|
160
|
+
raise
|
|
161
|
+
else:
|
|
162
|
+
print_error(f"HTTP error {e.response.status_code}: {e.response.reason}")
|
|
163
|
+
raise
|
|
164
|
+
except requests.exceptions.RequestException as e:
|
|
165
|
+
print_error(f"Network error: {str(e)}")
|
|
166
|
+
raise
|
|
167
|
+
|
|
168
|
+
def _count_bibtex_entries(self, content: str) -> int:
|
|
169
|
+
"""Count the number of BibTeX entries in the content"""
|
|
170
|
+
return (
|
|
171
|
+
content.count("@article")
|
|
172
|
+
+ content.count("@book")
|
|
173
|
+
+ content.count("@inproceedings")
|
|
174
|
+
+ content.count("@incollection")
|
|
175
|
+
+ content.count("@misc")
|
|
176
|
+
+ content.count("@phdthesis")
|
|
177
|
+
+ content.count("@techreport")
|
|
178
|
+
+ content.count("@mastersthesis")
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def _filter_empty_entries(self, content: str) -> Tuple[str, int]:
|
|
182
|
+
"""
|
|
183
|
+
Filter out empty or near-empty BibTeX entries
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
content: BibTeX content to filter
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Tuple of (filtered_content, num_removed)
|
|
190
|
+
"""
|
|
191
|
+
import re
|
|
192
|
+
|
|
193
|
+
# Pattern to match BibTeX entries
|
|
194
|
+
entry_pattern = r"@\w+\{[^,]*,\s*\}"
|
|
195
|
+
|
|
196
|
+
# Find all empty entries (those with just the citation key and closing brace)
|
|
197
|
+
empty_entries = re.findall(entry_pattern, content)
|
|
198
|
+
|
|
199
|
+
if not empty_entries:
|
|
200
|
+
return content, 0
|
|
201
|
+
|
|
202
|
+
# Remove empty entries
|
|
203
|
+
filtered_content = content
|
|
204
|
+
for entry in empty_entries:
|
|
205
|
+
filtered_content = filtered_content.replace(entry, "")
|
|
206
|
+
|
|
207
|
+
# Clean up multiple blank lines
|
|
208
|
+
filtered_content = re.sub(r"\n\s*\n\s*\n+", "\n\n", filtered_content)
|
|
209
|
+
|
|
210
|
+
return filtered_content, len(empty_entries)
|
|
211
|
+
|
|
212
|
+
def _backup_existing_file(self):
|
|
213
|
+
"""Create a backup of the existing BibTeX file"""
|
|
214
|
+
if os.path.exists(self.output_file):
|
|
215
|
+
backup_file = f"{self.output_file}.backup"
|
|
216
|
+
try:
|
|
217
|
+
with open(self.output_file, "r", encoding="utf-8") as src:
|
|
218
|
+
with open(backup_file, "w", encoding="utf-8") as dst:
|
|
219
|
+
dst.write(src.read())
|
|
220
|
+
print_info(f"Created backup: {backup_file}")
|
|
221
|
+
except Exception as e:
|
|
222
|
+
print_warning(f"Could not create backup: {e}")
|
|
223
|
+
|
|
224
|
+
def update(self, backup: bool = True) -> bool:
|
|
225
|
+
"""
|
|
226
|
+
Fetch and update BibTeX file from Zotero with improved pagination
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
backup: Whether to create a backup of existing file
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
True if successful, False otherwise
|
|
233
|
+
"""
|
|
234
|
+
try:
|
|
235
|
+
# Validate inputs
|
|
236
|
+
if not self.api_key:
|
|
237
|
+
print_error("Zotero API key is required")
|
|
238
|
+
return False
|
|
239
|
+
|
|
240
|
+
if not self.user_id and not self.group_id:
|
|
241
|
+
print_error("Either user_id or group_id must be provided")
|
|
242
|
+
return False
|
|
243
|
+
|
|
244
|
+
# Create backup if requested
|
|
245
|
+
if backup:
|
|
246
|
+
self._backup_existing_file()
|
|
247
|
+
|
|
248
|
+
# Build URL
|
|
249
|
+
base_url = self._build_url()
|
|
250
|
+
|
|
251
|
+
# Show what we're connecting to
|
|
252
|
+
if self.group_id:
|
|
253
|
+
group_name = self.get_group_name()
|
|
254
|
+
if group_name:
|
|
255
|
+
print_info(
|
|
256
|
+
f"Connecting to Zotero group: {group_name} (ID: {self.group_id})"
|
|
257
|
+
)
|
|
258
|
+
else:
|
|
259
|
+
print_info(f"Connecting to Zotero group ID: {self.group_id}")
|
|
260
|
+
elif self.user_id:
|
|
261
|
+
print_info(f"Connecting to Zotero user library (ID: {self.user_id})")
|
|
262
|
+
|
|
263
|
+
# First request to get total count
|
|
264
|
+
print_info("Fetching BibTeX entries from Zotero...")
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
initial_content, initial_headers = self._fetch_page(base_url, start=0)
|
|
268
|
+
except Exception as e:
|
|
269
|
+
print_error(f"Failed to connect to Zotero: {e}")
|
|
270
|
+
return False
|
|
271
|
+
|
|
272
|
+
# Get total number of items from header
|
|
273
|
+
total_results = int(initial_headers.get("Total-Results", 0))
|
|
274
|
+
|
|
275
|
+
if total_results == 0:
|
|
276
|
+
print_warning("No items found in Zotero library")
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
print_info(f"Found {total_results} total items in library")
|
|
280
|
+
|
|
281
|
+
# Collect all BibTeX content
|
|
282
|
+
all_bibtex_content: List[str] = []
|
|
283
|
+
|
|
284
|
+
# Add initial page
|
|
285
|
+
if initial_content.strip():
|
|
286
|
+
all_bibtex_content.append(initial_content)
|
|
287
|
+
|
|
288
|
+
# Calculate pages needed
|
|
289
|
+
pages_needed = (total_results + self.limit - 1) // self.limit
|
|
290
|
+
print_info(
|
|
291
|
+
f"Fetching {pages_needed} pages ({self.limit} items per page)..."
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Fetch remaining pages
|
|
295
|
+
for page in range(1, pages_needed):
|
|
296
|
+
start = page * self.limit
|
|
297
|
+
progress = ((page + 1) / pages_needed) * 100
|
|
298
|
+
|
|
299
|
+
print(
|
|
300
|
+
f" Progress: {progress:.0f}% ({start + self.limit if start + self.limit < total_results else total_results}/{total_results} items)",
|
|
301
|
+
end="\r",
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
try:
|
|
305
|
+
content, _ = self._fetch_page(base_url, start=start)
|
|
306
|
+
if content.strip():
|
|
307
|
+
all_bibtex_content.append(content)
|
|
308
|
+
except Exception as e:
|
|
309
|
+
print_error(f"\nFailed to fetch page {page + 1}: {e}")
|
|
310
|
+
return False
|
|
311
|
+
|
|
312
|
+
print() # New line after progress
|
|
313
|
+
|
|
314
|
+
# Combine all content
|
|
315
|
+
combined_content = "\n\n".join(all_bibtex_content)
|
|
316
|
+
|
|
317
|
+
# Filter out empty entries
|
|
318
|
+
filtered_content, num_removed = self._filter_empty_entries(combined_content)
|
|
319
|
+
if num_removed > 0:
|
|
320
|
+
print_warning(
|
|
321
|
+
f"Filtered out {num_removed} empty/incomplete entries from Zotero"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# Count total valid entries
|
|
325
|
+
total_entries = self._count_bibtex_entries(filtered_content)
|
|
326
|
+
|
|
327
|
+
if total_entries == 0:
|
|
328
|
+
print_warning("No valid BibTeX entries found after filtering")
|
|
329
|
+
return False
|
|
330
|
+
|
|
331
|
+
# Write to file
|
|
332
|
+
print_info(
|
|
333
|
+
f"Writing {total_entries} valid entries to {self.output_file}..."
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
try:
|
|
337
|
+
with open(self.output_file, "w", encoding="utf-8") as f:
|
|
338
|
+
# Add header comment
|
|
339
|
+
f.write(f"% BibTeX entries from Zotero\n")
|
|
340
|
+
f.write(f"% Total entries: {total_entries}\n")
|
|
341
|
+
if num_removed > 0:
|
|
342
|
+
f.write(f"% Filtered out: {num_removed} empty entries\n")
|
|
343
|
+
f.write(f"% Generated: {time.strftime('%Y-%m-%d %H:%M:%S')}\n\n")
|
|
344
|
+
f.write(filtered_content)
|
|
345
|
+
if not filtered_content.endswith("\n"):
|
|
346
|
+
f.write("\n")
|
|
347
|
+
|
|
348
|
+
print_success(
|
|
349
|
+
f"Successfully updated {self.output_file} with {total_entries} entries"
|
|
350
|
+
)
|
|
351
|
+
return True
|
|
352
|
+
|
|
353
|
+
except IOError as e:
|
|
354
|
+
print_error(f"Failed to write to {self.output_file}: {e}")
|
|
355
|
+
return False
|
|
356
|
+
|
|
357
|
+
except Exception as e:
|
|
358
|
+
print_error(f"Unexpected error during update: {e}")
|
|
359
|
+
return False
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: article-cli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: CLI tool for managing LaTeX articles with git integration and Zotero bibliography
|
|
5
|
+
Project-URL: Homepage, https://github.com/feelpp/article.cli
|
|
6
|
+
Project-URL: Documentation, https://github.com/feelpp/article.cli#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/feelpp/article.cli.git
|
|
8
|
+
Project-URL: Issues, https://github.com/feelpp/article.cli/issues
|
|
9
|
+
Author-email: Christophe Prud'homme <prudhomm@cemosis.fr>
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2024 Christophe Prud'homme
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Keywords: academic,bibliography,git,latex,research,zotero
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
|
+
Classifier: Intended Audience :: Science/Research
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
43
|
+
Classifier: Topic :: Scientific/Engineering
|
|
44
|
+
Classifier: Topic :: Text Processing :: Markup :: LaTeX
|
|
45
|
+
Requires-Python: >=3.8
|
|
46
|
+
Requires-Dist: requests>=2.28.0
|
|
47
|
+
Provides-Extra: dev
|
|
48
|
+
Requires-Dist: black>=23.0; extra == 'dev'
|
|
49
|
+
Requires-Dist: flake8>=6.0; extra == 'dev'
|
|
50
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
51
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
52
|
+
Description-Content-Type: text/markdown
|
|
53
|
+
|
|
54
|
+
# Article CLI
|
|
55
|
+
|
|
56
|
+
[](https://github.com/feelpp/article.cli/actions/workflows/ci.yml)
|
|
57
|
+
[](https://badge.fury.io/py/article-cli)
|
|
58
|
+
[](https://pypi.org/project/article-cli/)
|
|
59
|
+
|
|
60
|
+
A command-line tool for managing LaTeX articles with git integration and Zotero bibliography synchronization.
|
|
61
|
+
|
|
62
|
+
## Features
|
|
63
|
+
|
|
64
|
+
- **Git Release Management**: Create, list, and delete releases with gitinfo2 support
|
|
65
|
+
- **Zotero Integration**: Synchronize bibliography from Zotero with robust pagination and error handling
|
|
66
|
+
- **LaTeX Build Management**: Clean build files and manage LaTeX compilation artifacts
|
|
67
|
+
- **Git Hooks Setup**: Automated setup of git hooks for gitinfo2 integration
|
|
68
|
+
- **Local Configuration**: Support for project-specific configuration files
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
71
|
+
|
|
72
|
+
### From PyPI (recommended)
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install article-cli
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### From Source
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
git clone https://github.com/feelpp/article.cli.git
|
|
82
|
+
cd article.cli
|
|
83
|
+
pip install -e .
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Quick Start
|
|
87
|
+
|
|
88
|
+
1. **Setup git hooks** (run once per repository):
|
|
89
|
+
```bash
|
|
90
|
+
article-cli setup
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
2. **Configure Zotero credentials** (one-time setup):
|
|
94
|
+
```bash
|
|
95
|
+
export ZOTERO_API_KEY="your_api_key_here"
|
|
96
|
+
export ZOTERO_GROUP_ID="your_group_id" # or ZOTERO_USER_ID
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
3. **Update bibliography from Zotero**:
|
|
100
|
+
```bash
|
|
101
|
+
article-cli update-bibtex
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
4. **Create a release**:
|
|
105
|
+
```bash
|
|
106
|
+
article-cli create v1.0.0
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Configuration
|
|
110
|
+
|
|
111
|
+
### Environment Variables
|
|
112
|
+
|
|
113
|
+
- `ZOTERO_API_KEY`: Your Zotero API key (required for bibliography updates)
|
|
114
|
+
- `ZOTERO_USER_ID`: Your Zotero user ID (alternative to group ID)
|
|
115
|
+
- `ZOTERO_GROUP_ID`: Your Zotero group ID (alternative to user ID)
|
|
116
|
+
|
|
117
|
+
### Local Configuration File
|
|
118
|
+
|
|
119
|
+
Create a `.article-cli.toml` file in your project root for project-specific settings:
|
|
120
|
+
|
|
121
|
+
```toml
|
|
122
|
+
[zotero]
|
|
123
|
+
api_key = "your_api_key_here"
|
|
124
|
+
group_id = "6219333"
|
|
125
|
+
# user_id = "your_user_id" # alternative to group_id
|
|
126
|
+
output_file = "references.bib"
|
|
127
|
+
|
|
128
|
+
[git]
|
|
129
|
+
auto_push = true
|
|
130
|
+
default_branch = "main"
|
|
131
|
+
|
|
132
|
+
[latex]
|
|
133
|
+
clean_extensions = [".aux", ".bbl", ".blg", ".log", ".out", ".synctex.gz"]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Usage
|
|
137
|
+
|
|
138
|
+
### Git Release Management
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Create a new release
|
|
142
|
+
article-cli create v1.2.3
|
|
143
|
+
|
|
144
|
+
# List recent releases
|
|
145
|
+
article-cli list --count 10
|
|
146
|
+
|
|
147
|
+
# Delete a release
|
|
148
|
+
article-cli delete v1.2.3
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Bibliography Management
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Update bibliography from Zotero
|
|
155
|
+
article-cli update-bibtex
|
|
156
|
+
|
|
157
|
+
# Specify custom output file
|
|
158
|
+
article-cli update-bibtex --output my-refs.bib
|
|
159
|
+
|
|
160
|
+
# Skip backup creation
|
|
161
|
+
article-cli update-bibtex --no-backup
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Project Setup
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Setup git hooks for gitinfo2
|
|
168
|
+
article-cli setup
|
|
169
|
+
|
|
170
|
+
# Clean LaTeX build files
|
|
171
|
+
article-cli clean
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Advanced Usage
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Override configuration via command line
|
|
178
|
+
article-cli update-bibtex --api-key YOUR_KEY --group-id YOUR_GROUP
|
|
179
|
+
|
|
180
|
+
# Specify custom configuration file
|
|
181
|
+
article-cli --config custom-config.toml update-bibtex
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Version Format
|
|
185
|
+
|
|
186
|
+
Release versions must follow the semantic versioning format:
|
|
187
|
+
- `vX.Y.Z` for stable releases (e.g., `v1.2.3`)
|
|
188
|
+
- `vX.Y.Z-pre.N` for pre-releases (e.g., `v1.2.3-pre.1`)
|
|
189
|
+
|
|
190
|
+
## Requirements
|
|
191
|
+
|
|
192
|
+
- Python 3.8+
|
|
193
|
+
- Git repository with gitinfo2 package (for LaTeX integration)
|
|
194
|
+
- Zotero account with API access (for bibliography features)
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT License - see LICENSE file for details.
|
|
199
|
+
|
|
200
|
+
## Contributing
|
|
201
|
+
|
|
202
|
+
1. Fork the repository
|
|
203
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
204
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
205
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
206
|
+
5. Open a Pull Request
|
|
207
|
+
|
|
208
|
+
## Changelog
|
|
209
|
+
|
|
210
|
+
### v1.0.0
|
|
211
|
+
- Initial release
|
|
212
|
+
- Git release management
|
|
213
|
+
- Zotero bibliography synchronization
|
|
214
|
+
- LaTeX build file cleanup
|
|
215
|
+
- Configuration file support
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
article_cli/__init__.py,sha256=LBaDt3ml_owdcQghSIsChwgqmhuZ76O4aXTVq-rvxG0,521
|
|
2
|
+
article_cli/__main__.py,sha256=tKzgx5NUMWBgIxJz23H7pSPF2G0xG6waivcNTmrb4eg,134
|
|
3
|
+
article_cli/cli.py,sha256=cbAoPv3bfRgo7MQFRUJUFt_BYv39ucRvvyDdFam_SCw,10120
|
|
4
|
+
article_cli/config.py,sha256=g4xPvzpHJk-Y0fPEXsgrPOdJ90b5zBpN5yiG3saF6ns,9152
|
|
5
|
+
article_cli/git_manager.py,sha256=uJtzgjRmPpcXDuCBMdbf0BWpsvAtoCQtSxDSFB0836E,12017
|
|
6
|
+
article_cli/zotero.py,sha256=GFbetgSvfLQS9U-EA2q9sb7GKgR3jTEOhPEDV1OLmKM,12439
|
|
7
|
+
article_cli-1.0.0.dist-info/METADATA,sha256=gaBMHKioyy1N2nTpAnPXjZ-Ul2Wxm7wMB-RcC5a6Qhw,6537
|
|
8
|
+
article_cli-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
9
|
+
article_cli-1.0.0.dist-info/entry_points.txt,sha256=TTD3ZO2r7w6Om13VFoFu_rW1LTmYzknhNsIep-uAvBg,53
|
|
10
|
+
article_cli-1.0.0.dist-info/licenses/LICENSE,sha256=SjACEUOvqClPlozgN4K7uHFc39TDDikcH0YEot056l0,1077
|
|
11
|
+
article_cli-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Christophe Prud'homme
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|