rxiv-maker 1.16.5__py3-none-any.whl → 1.16.7__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.
- rxiv_maker/__version__.py +1 -1
- rxiv_maker/cli/commands/upgrade.py +22 -134
- rxiv_maker/cli/framework/utility_commands.py +5 -2
- rxiv_maker/engines/operations/generate_figures.py +146 -18
- rxiv_maker/utils/rich_upgrade_notifier.py +162 -0
- rxiv_maker/validators/syntax_validator.py +13 -4
- {rxiv_maker-1.16.5.dist-info → rxiv_maker-1.16.7.dist-info}/METADATA +10 -5
- {rxiv_maker-1.16.5.dist-info → rxiv_maker-1.16.7.dist-info}/RECORD +11 -10
- {rxiv_maker-1.16.5.dist-info → rxiv_maker-1.16.7.dist-info}/WHEEL +0 -0
- {rxiv_maker-1.16.5.dist-info → rxiv_maker-1.16.7.dist-info}/entry_points.txt +0 -0
- {rxiv_maker-1.16.5.dist-info → rxiv_maker-1.16.7.dist-info}/licenses/LICENSE +0 -0
rxiv_maker/__version__.py
CHANGED
|
@@ -1,68 +1,18 @@
|
|
|
1
1
|
"""Upgrade command for rxiv-maker CLI."""
|
|
2
2
|
|
|
3
|
-
import subprocess # nosec B404 - needed for executing upgrade commands
|
|
4
3
|
import sys
|
|
5
4
|
|
|
6
5
|
import click
|
|
7
|
-
from henriqueslab_updater import
|
|
6
|
+
from henriqueslab_updater import handle_upgrade_workflow
|
|
8
7
|
from rich.console import Console
|
|
9
8
|
|
|
10
9
|
from ... import __version__
|
|
11
|
-
from ...utils.
|
|
12
|
-
from ...utils.
|
|
13
|
-
from ..interactive import prompt_confirm
|
|
10
|
+
from ...utils.install_detector import detect_install_method
|
|
11
|
+
from ...utils.rich_upgrade_notifier import RxivUpgradeNotifier
|
|
14
12
|
|
|
15
13
|
console = Console()
|
|
16
14
|
|
|
17
15
|
|
|
18
|
-
def _display_changelog(console: Console, current_version: str, latest_version: str) -> None:
|
|
19
|
-
"""Display changelog summary for version range.
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
console: Rich console for output
|
|
23
|
-
current_version: Current installed version
|
|
24
|
-
latest_version: Latest available version
|
|
25
|
-
"""
|
|
26
|
-
console.print("\n📋 What's changing:", style="bold blue")
|
|
27
|
-
|
|
28
|
-
# Fetch changelog summary
|
|
29
|
-
summary, error = fetch_and_format_changelog(
|
|
30
|
-
current_version=current_version,
|
|
31
|
-
latest_version=latest_version,
|
|
32
|
-
highlights_per_version=3,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
if error:
|
|
36
|
-
console.print(" Unable to fetch changelog details", style="dim yellow")
|
|
37
|
-
console.print(
|
|
38
|
-
f" View online: https://github.com/henriqueslab/rxiv-maker/releases/tag/v{latest_version}",
|
|
39
|
-
style="dim blue",
|
|
40
|
-
)
|
|
41
|
-
return
|
|
42
|
-
|
|
43
|
-
if summary:
|
|
44
|
-
# Display the changelog with proper formatting
|
|
45
|
-
for line in summary.split("\n"):
|
|
46
|
-
if line.startswith("⚠️"):
|
|
47
|
-
# Highlight breaking changes prominently
|
|
48
|
-
console.print(line, style="bold red")
|
|
49
|
-
elif line.startswith("What's New:"):
|
|
50
|
-
console.print(line, style="bold cyan")
|
|
51
|
-
elif line.startswith(" v"):
|
|
52
|
-
# Version headers
|
|
53
|
-
console.print(line, style="bold yellow")
|
|
54
|
-
elif line.strip().startswith(("✨", "🔄", "🐛", "🗑️", "🔒", "📝")):
|
|
55
|
-
# Change items
|
|
56
|
-
console.print(f" {line.strip()}", style="white")
|
|
57
|
-
elif line.strip().startswith("•"):
|
|
58
|
-
# Breaking change items
|
|
59
|
-
console.print(f" {line.strip()}", style="yellow")
|
|
60
|
-
elif line.strip():
|
|
61
|
-
console.print(f" {line}", style="dim")
|
|
62
|
-
else:
|
|
63
|
-
console.print(" No detailed changelog available", style="dim")
|
|
64
|
-
|
|
65
|
-
|
|
66
16
|
@click.command()
|
|
67
17
|
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
|
68
18
|
@click.option("--check-only", "-c", is_flag=True, help="Only check for updates, don't upgrade")
|
|
@@ -72,93 +22,31 @@ def upgrade(ctx: click.Context, yes: bool, check_only: bool) -> None:
|
|
|
72
22
|
|
|
73
23
|
This command automatically detects how rxiv-maker was installed
|
|
74
24
|
(Homebrew, pip, uv, pipx, etc.) and runs the appropriate upgrade command.
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
rxiv upgrade # Check and upgrade with confirmation
|
|
28
|
+
rxiv upgrade --yes # Upgrade without confirmation
|
|
29
|
+
rxiv upgrade --check-only # Only check for updates
|
|
75
30
|
"""
|
|
76
|
-
#
|
|
31
|
+
# Handle development installations specially
|
|
77
32
|
install_method = detect_install_method()
|
|
78
|
-
install_name = get_friendly_install_name(install_method)
|
|
79
|
-
|
|
80
|
-
console.print(f"🔍 Detected installation method: {install_name}", style="blue")
|
|
81
|
-
|
|
82
|
-
# Handle development installations
|
|
83
33
|
if install_method == "dev":
|
|
84
34
|
console.print("⚠️ Development installation detected", style="yellow")
|
|
85
35
|
console.print(" To update, pull the latest changes from git:", style="yellow")
|
|
86
|
-
console.print(" cd <repo> && git pull && uv sync", style="yellow")
|
|
36
|
+
console.print(" [cyan]cd <repo> && git pull && uv sync[/cyan]", style="yellow")
|
|
87
37
|
sys.exit(0)
|
|
88
38
|
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
# Fetch and display changelog
|
|
101
|
-
_display_changelog(console, __version__, latest_version)
|
|
102
|
-
|
|
103
|
-
if check_only:
|
|
104
|
-
upgrade_cmd = get_upgrade_command(install_method)
|
|
105
|
-
console.print(f"\n Run: {upgrade_cmd}", style="blue")
|
|
106
|
-
sys.exit(0)
|
|
107
|
-
|
|
108
|
-
except Exception as e:
|
|
109
|
-
console.print(f"⚠️ Could not check for updates: {e}", style="yellow")
|
|
110
|
-
console.print(" Proceeding with upgrade attempt...", style="yellow")
|
|
111
|
-
latest_version = "latest"
|
|
112
|
-
|
|
113
|
-
# Get upgrade command
|
|
114
|
-
upgrade_cmd = get_upgrade_command(install_method)
|
|
115
|
-
|
|
116
|
-
# Show confirmation
|
|
117
|
-
if not yes:
|
|
118
|
-
console.print(f"\n📦 About to run: {upgrade_cmd}", style="blue")
|
|
119
|
-
if not prompt_confirm("Do you want to continue?", default=True):
|
|
120
|
-
console.print("❌ Upgrade cancelled", style="yellow")
|
|
121
|
-
sys.exit(0)
|
|
122
|
-
|
|
123
|
-
# Execute upgrade command directly
|
|
124
|
-
console.print("\n🚀 Upgrading rxiv-maker...", style="blue")
|
|
125
|
-
console.print(f" Running: {upgrade_cmd}", style="dim")
|
|
126
|
-
|
|
127
|
-
try:
|
|
128
|
-
# Split command for safer execution without shell=True
|
|
129
|
-
import shlex
|
|
130
|
-
|
|
131
|
-
cmd_parts = shlex.split(upgrade_cmd)
|
|
132
|
-
result = subprocess.run(
|
|
133
|
-
cmd_parts,
|
|
134
|
-
check=True,
|
|
135
|
-
capture_output=False,
|
|
136
|
-
timeout=300,
|
|
137
|
-
) # nosec B603 - upgrade_cmd comes from trusted install_detector module
|
|
138
|
-
success = result.returncode == 0
|
|
139
|
-
error = None
|
|
140
|
-
except subprocess.CalledProcessError as e:
|
|
141
|
-
success = False
|
|
142
|
-
error = f"Command failed with exit code {e.returncode}"
|
|
143
|
-
except subprocess.TimeoutExpired:
|
|
144
|
-
success = False
|
|
145
|
-
error = "Upgrade timed out after 5 minutes"
|
|
146
|
-
except Exception as e:
|
|
147
|
-
success = False
|
|
148
|
-
error = str(e)
|
|
149
|
-
|
|
150
|
-
if success:
|
|
151
|
-
console.print("\n✅ Upgrade completed successfully!", style="green")
|
|
152
|
-
console.print(" Run 'rxiv --version' to verify the installation", style="blue")
|
|
39
|
+
# Use centralized upgrade workflow
|
|
40
|
+
notifier = RxivUpgradeNotifier(console)
|
|
41
|
+
success, error = handle_upgrade_workflow(
|
|
42
|
+
package_name="rxiv-maker",
|
|
43
|
+
current_version=__version__,
|
|
44
|
+
check_only=check_only,
|
|
45
|
+
skip_confirmation=yes,
|
|
46
|
+
notifier=notifier,
|
|
47
|
+
github_org="HenriquesLab",
|
|
48
|
+
github_repo="rxiv-maker",
|
|
49
|
+
)
|
|
153
50
|
|
|
154
|
-
|
|
155
|
-
if latest_version != "latest":
|
|
156
|
-
console.print(f"\n🎉 What's new in v{latest_version}:", style="bold green")
|
|
157
|
-
console.print(
|
|
158
|
-
f" View full changelog: https://github.com/henriqueslab/rxiv-maker/releases/tag/v{latest_version}",
|
|
159
|
-
style="blue",
|
|
160
|
-
)
|
|
161
|
-
else:
|
|
162
|
-
console.print(f"\n❌ Upgrade failed: {error}", style="red")
|
|
163
|
-
console.print(f" Try running manually: {upgrade_cmd}", style="yellow")
|
|
51
|
+
if not success and error:
|
|
164
52
|
sys.exit(1)
|
|
@@ -187,13 +187,16 @@ class CheckInstallationCommand(BaseCommand):
|
|
|
187
187
|
"""Show next steps after dependency check."""
|
|
188
188
|
if not missing_results:
|
|
189
189
|
self.console.print("\n🚀 Next steps:", style="blue")
|
|
190
|
-
self.console.print(" •
|
|
190
|
+
self.console.print(" • Get example manuscript: rxiv get-rxiv-preprint")
|
|
191
|
+
self.console.print(" • Navigate to manuscript: cd manuscript-rxiv-maker/MANUSCRIPT")
|
|
192
|
+
self.console.print(" • Generate PDF: rxiv pdf")
|
|
191
193
|
return
|
|
192
194
|
|
|
193
195
|
self.console.print("\n🔧 Next steps:", style="blue")
|
|
194
196
|
self.console.print(" • Install missing dependencies shown above")
|
|
195
197
|
self.console.print(" • Re-run: rxiv check-installation")
|
|
196
|
-
self.console.print(" •
|
|
198
|
+
self.console.print(" • Get example manuscript: rxiv get-rxiv-preprint")
|
|
199
|
+
self.console.print(" • Generate PDF: cd manuscript-rxiv-maker/MANUSCRIPT && rxiv pdf")
|
|
197
200
|
|
|
198
201
|
def _show_basic_results(self, results: dict) -> None:
|
|
199
202
|
"""Show basic installation results."""
|
|
@@ -250,31 +250,95 @@ class FigureGenerator:
|
|
|
250
250
|
response = get_with_retry(mermaid_url, max_attempts=5, timeout=30)
|
|
251
251
|
else:
|
|
252
252
|
response = requests.get(mermaid_url, timeout=30)
|
|
253
|
-
except
|
|
254
|
-
#
|
|
255
|
-
return self._create_fallback_mermaid_diagram(
|
|
253
|
+
except requests.Timeout:
|
|
254
|
+
# Timeout - likely diagram too complex or service slow
|
|
255
|
+
return self._create_fallback_mermaid_diagram(
|
|
256
|
+
input_file, output_file, reason="timeout", details="30s timeout exceeded"
|
|
257
|
+
)
|
|
258
|
+
except requests.HTTPError as e:
|
|
259
|
+
# HTTP error (400, 503, etc.) - extract status code
|
|
260
|
+
status_code = e.response.status_code if hasattr(e, "response") else "unknown"
|
|
261
|
+
if status_code == 400:
|
|
262
|
+
details = "syntax error or diagram too complex"
|
|
263
|
+
elif status_code == 503:
|
|
264
|
+
details = "service timeout (diagram too complex)"
|
|
265
|
+
else:
|
|
266
|
+
details = f"HTTP {status_code}"
|
|
267
|
+
return self._create_fallback_mermaid_diagram(
|
|
268
|
+
input_file, output_file, reason="http_error", details=details
|
|
269
|
+
)
|
|
270
|
+
except Exception as e:
|
|
271
|
+
# Network or other error during request
|
|
272
|
+
error_msg = str(e)
|
|
273
|
+
# Try to extract status code from error message if it's there
|
|
274
|
+
if "400" in error_msg:
|
|
275
|
+
details = "syntax error or diagram too complex"
|
|
276
|
+
elif "503" in error_msg:
|
|
277
|
+
details = "service timeout (diagram too complex)"
|
|
278
|
+
elif "429" in error_msg:
|
|
279
|
+
details = "rate limit exceeded"
|
|
280
|
+
else:
|
|
281
|
+
details = "connection error"
|
|
282
|
+
return self._create_fallback_mermaid_diagram(
|
|
283
|
+
input_file, output_file, reason="network_error", details=details
|
|
284
|
+
)
|
|
256
285
|
|
|
257
286
|
if response.status_code == 200:
|
|
258
287
|
with open(output_file, "wb") as f:
|
|
259
288
|
f.write(response.content)
|
|
260
289
|
return True
|
|
261
290
|
else:
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
291
|
+
# Determine failure reason from status code
|
|
292
|
+
if response.status_code == 400:
|
|
293
|
+
reason_msg = "syntax error or diagram too complex"
|
|
294
|
+
elif response.status_code == 429:
|
|
295
|
+
reason_msg = "rate limit exceeded"
|
|
296
|
+
elif response.status_code == 503:
|
|
297
|
+
reason_msg = "service timeout (diagram too complex)"
|
|
298
|
+
elif response.status_code >= 500:
|
|
299
|
+
reason_msg = "service unavailable"
|
|
300
|
+
else:
|
|
301
|
+
reason_msg = f"HTTP {response.status_code}"
|
|
302
|
+
|
|
303
|
+
return self._create_fallback_mermaid_diagram(
|
|
304
|
+
input_file, output_file, reason="http_error", details=reason_msg
|
|
265
305
|
)
|
|
266
|
-
return self._create_fallback_mermaid_diagram(input_file, output_file)
|
|
267
306
|
else:
|
|
268
|
-
|
|
269
|
-
|
|
307
|
+
return self._create_fallback_mermaid_diagram(
|
|
308
|
+
input_file, output_file, reason="no_requests_lib", details="requests library not available"
|
|
309
|
+
)
|
|
270
310
|
|
|
271
311
|
except Exception as e:
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
312
|
+
return self._create_fallback_mermaid_diagram(
|
|
313
|
+
input_file, output_file, reason="unexpected_error", details=str(e)
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
def _create_fallback_mermaid_diagram(
|
|
317
|
+
self, input_file: Path, output_file: Path, reason: str = "unknown", details: str = "service unavailable"
|
|
318
|
+
) -> bool:
|
|
319
|
+
"""Create a fallback placeholder diagram when Mermaid service is unavailable.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
input_file: Source mermaid file
|
|
323
|
+
output_file: Output file path
|
|
324
|
+
reason: Failure reason category (timeout, http_error, network_error, etc.)
|
|
325
|
+
details: Detailed error message
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
True if placeholder was created successfully
|
|
329
|
+
"""
|
|
330
|
+
# Generate user-friendly warning message based on failure reason
|
|
331
|
+
if reason == "timeout":
|
|
332
|
+
warning_msg = f"diagram rendering timed out ({details})"
|
|
333
|
+
elif reason == "http_error":
|
|
334
|
+
warning_msg = f"{details}"
|
|
335
|
+
elif reason == "network_error":
|
|
336
|
+
warning_msg = f"network error: {details}"
|
|
337
|
+
elif reason == "no_requests_lib":
|
|
338
|
+
warning_msg = "requests library not available"
|
|
339
|
+
else:
|
|
340
|
+
warning_msg = f"{details}"
|
|
275
341
|
|
|
276
|
-
def _create_fallback_mermaid_diagram(self, input_file: Path, output_file: Path) -> bool:
|
|
277
|
-
"""Create a fallback placeholder diagram when Mermaid service is unavailable."""
|
|
278
342
|
try:
|
|
279
343
|
if self.output_format.lower() == "svg":
|
|
280
344
|
# Create SVG placeholder
|
|
@@ -294,6 +358,7 @@ class FigureGenerator:
|
|
|
294
358
|
</svg>"""
|
|
295
359
|
with open(output_file, "w", encoding="utf-8") as f:
|
|
296
360
|
f.write(svg_content)
|
|
361
|
+
print(f"⚠️ Created placeholder SVG for {input_file.name} ({warning_msg})")
|
|
297
362
|
return True
|
|
298
363
|
elif self.output_format.lower() == "png":
|
|
299
364
|
# Create minimal PNG placeholder (1x1 white pixel)
|
|
@@ -303,7 +368,7 @@ class FigureGenerator:
|
|
|
303
368
|
)
|
|
304
369
|
with open(output_file, "wb") as f:
|
|
305
370
|
f.write(png_data)
|
|
306
|
-
print(f"⚠️ Created placeholder PNG for {input_file.name} (
|
|
371
|
+
print(f"⚠️ Created placeholder PNG for {input_file.name} ({warning_msg})")
|
|
307
372
|
return True
|
|
308
373
|
elif self.output_format.lower() == "pdf":
|
|
309
374
|
# Create minimal PDF placeholder
|
|
@@ -374,20 +439,83 @@ startxref
|
|
|
374
439
|
"""
|
|
375
440
|
with open(output_file, "w", encoding="utf-8") as f:
|
|
376
441
|
f.write(pdf_content)
|
|
377
|
-
print(f"⚠️ Created placeholder PDF for {input_file.name} (
|
|
442
|
+
print(f"⚠️ Created placeholder PDF for {input_file.name} ({warning_msg})")
|
|
378
443
|
return True
|
|
379
444
|
else:
|
|
380
445
|
# Fallback for other formats - create text file with warning
|
|
381
446
|
with open(output_file.with_suffix(".txt"), "w", encoding="utf-8") as f:
|
|
382
447
|
f.write(f"Mermaid diagram placeholder for {input_file.name}\n")
|
|
383
|
-
f.write("
|
|
448
|
+
f.write(f"Reason: {warning_msg}\n")
|
|
384
449
|
f.write("\n💡 Tip: Check your Mermaid diagram syntax at https://www.mermaidchart.com/\n")
|
|
385
|
-
print(f"⚠️ Created text placeholder for {input_file.name} (
|
|
450
|
+
print(f"⚠️ Created text placeholder for {input_file.name} ({warning_msg})")
|
|
386
451
|
return True
|
|
387
452
|
except Exception as e:
|
|
388
453
|
print(f"Failed to create fallback diagram: {e}")
|
|
389
454
|
return False
|
|
390
455
|
|
|
456
|
+
def validate_mermaid_diagram(self, mmd_file: Path) -> tuple[bool, str, dict]:
|
|
457
|
+
"""Validate a Mermaid diagram for mermaid.ink compatibility.
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
mmd_file: Path to .mmd file
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
Tuple of (is_valid, message, details_dict)
|
|
464
|
+
- is_valid: True if diagram will render successfully
|
|
465
|
+
- message: Human-readable validation result
|
|
466
|
+
- details_dict: Dict with complexity metrics and suggestions
|
|
467
|
+
"""
|
|
468
|
+
if not requests:
|
|
469
|
+
return False, "requests library not available for validation", {}
|
|
470
|
+
|
|
471
|
+
try:
|
|
472
|
+
# Read and analyze the diagram
|
|
473
|
+
with open(mmd_file, "r", encoding="utf-8") as f:
|
|
474
|
+
content = f.read().strip()
|
|
475
|
+
|
|
476
|
+
# Analyze complexity
|
|
477
|
+
details = {
|
|
478
|
+
"file_size": len(content),
|
|
479
|
+
"line_count": content.count("\n") + 1,
|
|
480
|
+
"subgraph_count": content.count("subgraph"),
|
|
481
|
+
"node_count": len(re.findall(r"\w+\[", content)),
|
|
482
|
+
"class_def_count": content.count("classDef"),
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
# Check for known problematic patterns
|
|
486
|
+
warnings = []
|
|
487
|
+
if details["file_size"] > 2500:
|
|
488
|
+
warnings.append(f"Large diagram ({details['file_size']} chars, limit ~2500)")
|
|
489
|
+
if details["subgraph_count"] > 5:
|
|
490
|
+
warnings.append(f"Many subgraphs ({details['subgraph_count']}, limit ~5)")
|
|
491
|
+
if details["class_def_count"] > 6:
|
|
492
|
+
warnings.append(f"Heavy styling ({details['class_def_count']} classes, limit ~6)")
|
|
493
|
+
|
|
494
|
+
# Test with mermaid.ink
|
|
495
|
+
encoded = base64.b64encode(content.encode("utf-8")).decode("ascii")
|
|
496
|
+
test_url = f"https://mermaid.ink/svg/{encoded}" # Use SVG for faster testing
|
|
497
|
+
|
|
498
|
+
try:
|
|
499
|
+
response = requests.get(test_url, timeout=10)
|
|
500
|
+
if response.status_code == 200:
|
|
501
|
+
if warnings:
|
|
502
|
+
msg = f"✓ Valid (but complex: {', '.join(warnings)})"
|
|
503
|
+
return True, msg, details
|
|
504
|
+
return True, "✓ Valid and will render successfully", details
|
|
505
|
+
elif response.status_code == 400:
|
|
506
|
+
return False, "✗ Syntax error or too complex for mermaid.ink", details
|
|
507
|
+
elif response.status_code == 503:
|
|
508
|
+
return False, "✗ Diagram too complex (service timeout)", details
|
|
509
|
+
else:
|
|
510
|
+
return False, f"✗ HTTP {response.status_code}", details
|
|
511
|
+
except requests.Timeout:
|
|
512
|
+
return False, "✗ Validation timeout (diagram likely too complex)", details
|
|
513
|
+
except Exception as e:
|
|
514
|
+
return False, f"✗ Network error: {str(e)[:50]}", details
|
|
515
|
+
|
|
516
|
+
except Exception as e:
|
|
517
|
+
return False, f"✗ Error reading diagram: {str(e)[:50]}", {}
|
|
518
|
+
|
|
391
519
|
def _execute_python_files(self, progress=None, task_id=None, use_rich: bool = True):
|
|
392
520
|
"""Execute Python scripts to generate figures using local Python."""
|
|
393
521
|
py_files = list(self.figures_dir.glob("*.py"))
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Rich console adapter for upgrade notifications in rxiv-maker."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RxivUpgradeNotifier:
|
|
8
|
+
"""Adapt Rich console to UpgradeNotifier protocol for rxiv-maker.
|
|
9
|
+
|
|
10
|
+
Integrates with rxiv-maker's changelog parser to show rich change summaries.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, console: Console):
|
|
14
|
+
"""Initialize with Rich console instance.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
console: Rich Console instance for styled output
|
|
18
|
+
"""
|
|
19
|
+
self.console = console
|
|
20
|
+
|
|
21
|
+
def show_checking(self) -> None:
|
|
22
|
+
"""Show 'checking for updates' message."""
|
|
23
|
+
self.console.print("🔍 Checking for updates...", style="blue")
|
|
24
|
+
|
|
25
|
+
def show_version_check(self, current: str, latest: str, available: bool) -> None:
|
|
26
|
+
"""Show version check results.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
current: Current installed version
|
|
30
|
+
latest: Latest available version
|
|
31
|
+
available: Whether an update is available
|
|
32
|
+
"""
|
|
33
|
+
if available:
|
|
34
|
+
self.console.print(
|
|
35
|
+
f"📦 Update available: [cyan]v{current}[/cyan] → [green bold]v{latest}[/green bold]",
|
|
36
|
+
style="yellow",
|
|
37
|
+
)
|
|
38
|
+
else:
|
|
39
|
+
self.console.print(
|
|
40
|
+
f"✅ You already have the latest version ([cyan]v{current}[/cyan])",
|
|
41
|
+
style="green",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def show_update_info(self, current: str, latest: str, release_url: str) -> None:
|
|
45
|
+
"""Show update available information with changelog integration.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
current: Current version
|
|
49
|
+
latest: Latest version
|
|
50
|
+
release_url: URL to release notes
|
|
51
|
+
"""
|
|
52
|
+
# Import here to avoid circular dependencies
|
|
53
|
+
from .changelog_parser import fetch_and_format_changelog
|
|
54
|
+
|
|
55
|
+
# Fetch and display changelog
|
|
56
|
+
summary, error = fetch_and_format_changelog(
|
|
57
|
+
current_version=current,
|
|
58
|
+
latest_version=latest,
|
|
59
|
+
highlights_per_version=3,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if summary and not error:
|
|
63
|
+
self.console.print("\n📋 What's changing:", style="bold blue")
|
|
64
|
+
# Display changelog - format_summary returns rich-formatted text
|
|
65
|
+
# Parse and display with proper styling
|
|
66
|
+
for line in summary.split("\n"):
|
|
67
|
+
if line.startswith("⚠️"):
|
|
68
|
+
# Highlight breaking changes prominently
|
|
69
|
+
self.console.print(line, style="bold red")
|
|
70
|
+
elif "What's New:" in line or "What's changing:" in line:
|
|
71
|
+
self.console.print(line, style="bold cyan")
|
|
72
|
+
elif line.strip().startswith("v"):
|
|
73
|
+
# Version headers
|
|
74
|
+
self.console.print(line, style="bold yellow")
|
|
75
|
+
elif line.strip().startswith(("✨", "🔄", "🐛", "🗑️", "🔒", "📝")):
|
|
76
|
+
# Change items with emojis
|
|
77
|
+
self.console.print(f" {line.strip()}", style="white")
|
|
78
|
+
elif line.strip().startswith("•"):
|
|
79
|
+
# Breaking change items
|
|
80
|
+
self.console.print(f" {line.strip()}", style="yellow")
|
|
81
|
+
elif line.strip():
|
|
82
|
+
self.console.print(f" {line}", style="dim")
|
|
83
|
+
else:
|
|
84
|
+
# Fallback if changelog unavailable
|
|
85
|
+
self.console.print(
|
|
86
|
+
f"\nView release notes: [link]{release_url}[/link]",
|
|
87
|
+
style="blue",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def show_installer_info(self, friendly_name: str, command: str) -> None:
|
|
91
|
+
"""Show detected installer information.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
friendly_name: Human-readable installer name
|
|
95
|
+
command: The upgrade command that will be executed
|
|
96
|
+
"""
|
|
97
|
+
self.console.print()
|
|
98
|
+
self.console.print(
|
|
99
|
+
f"🔍 Detected installation method: [bold]{friendly_name}[/bold]",
|
|
100
|
+
style="blue",
|
|
101
|
+
)
|
|
102
|
+
self.console.print(f"📦 Running: [yellow]{command}[/yellow]")
|
|
103
|
+
|
|
104
|
+
def show_success(self, version: str) -> None:
|
|
105
|
+
"""Show successful upgrade message.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
version: Version that was successfully installed
|
|
109
|
+
"""
|
|
110
|
+
self.console.print()
|
|
111
|
+
self.console.print("✅ Upgrade completed successfully!", style="green bold")
|
|
112
|
+
self.console.print(f" Now running: [green]v{version}[/green]")
|
|
113
|
+
self.console.print()
|
|
114
|
+
self.console.print(" Run [blue]'rxiv --version'[/blue] to verify the installation", style="dim")
|
|
115
|
+
|
|
116
|
+
def show_error(self, error: str | None) -> None:
|
|
117
|
+
"""Show upgrade error message.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
error: Error message or None
|
|
121
|
+
"""
|
|
122
|
+
self.console.print()
|
|
123
|
+
self.console.print("❌ Upgrade failed", style="red bold")
|
|
124
|
+
if error:
|
|
125
|
+
self.console.print(f" {error}", style="red")
|
|
126
|
+
|
|
127
|
+
def show_manual_instructions(self, install_method: str) -> None:
|
|
128
|
+
"""Show manual upgrade instructions.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
install_method: The detected installation method
|
|
132
|
+
"""
|
|
133
|
+
self.console.print("\n💡 Try running manually:", style="yellow bold")
|
|
134
|
+
|
|
135
|
+
if install_method == "homebrew":
|
|
136
|
+
self.console.print(" [cyan]brew update && brew upgrade rxiv-maker[/cyan]")
|
|
137
|
+
elif install_method == "pipx":
|
|
138
|
+
self.console.print(" [cyan]pipx upgrade rxiv-maker[/cyan]")
|
|
139
|
+
elif install_method == "uv":
|
|
140
|
+
self.console.print(" [cyan]uv tool upgrade rxiv-maker[/cyan]")
|
|
141
|
+
elif install_method == "dev":
|
|
142
|
+
self.console.print(" [cyan]cd <repo> && git pull && uv sync[/cyan]", style="dim")
|
|
143
|
+
else:
|
|
144
|
+
self.console.print(" [cyan]pip install --upgrade rxiv-maker[/cyan]")
|
|
145
|
+
self.console.print(" [dim]# Or with --user flag:[/dim]")
|
|
146
|
+
self.console.print(" [cyan]pip install --upgrade --user rxiv-maker[/cyan]")
|
|
147
|
+
|
|
148
|
+
def confirm_upgrade(self, version: str) -> bool:
|
|
149
|
+
"""Prompt user for confirmation using click.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
version: Version to upgrade to
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
True if user confirms, False otherwise
|
|
156
|
+
"""
|
|
157
|
+
try:
|
|
158
|
+
self.console.print()
|
|
159
|
+
return click.confirm(f"Upgrade rxiv-maker to v{version}?", default=True)
|
|
160
|
+
except (KeyboardInterrupt, EOFError):
|
|
161
|
+
self.console.print("\n⚠️ Upgrade cancelled.", style="yellow")
|
|
162
|
+
return False
|
|
@@ -854,8 +854,17 @@ class SyntaxValidator(BaseValidator):
|
|
|
854
854
|
heading_texts = {}
|
|
855
855
|
previous_level = 0
|
|
856
856
|
|
|
857
|
-
|
|
858
|
-
|
|
857
|
+
# Protect code blocks (including {{py:exec}}) from being treated as headings
|
|
858
|
+
content = "\n".join(lines)
|
|
859
|
+
protected_content = self._protect_validation_sensitive_content(content)
|
|
860
|
+
protected_lines = protected_content.split("\n")
|
|
861
|
+
|
|
862
|
+
for line_num, (original_line, protected_line) in enumerate(zip(lines, protected_lines, strict=False), 1):
|
|
863
|
+
# Skip protected code blocks (they might contain # comments)
|
|
864
|
+
if "XXPROTECTEDCODEXX" in protected_line:
|
|
865
|
+
continue
|
|
866
|
+
|
|
867
|
+
match = heading_pattern.match(protected_line)
|
|
859
868
|
if match:
|
|
860
869
|
hashes = match.group(1)
|
|
861
870
|
heading_text = match.group(2).strip()
|
|
@@ -904,7 +913,7 @@ class SyntaxValidator(BaseValidator):
|
|
|
904
913
|
f"Heading hierarchy skips levels: {previous_level} → {level}",
|
|
905
914
|
file_path=file_path,
|
|
906
915
|
line_number=line_num,
|
|
907
|
-
context=
|
|
916
|
+
context=original_line.strip(),
|
|
908
917
|
suggestion=(
|
|
909
918
|
f"Consider using {'#' * (previous_level + 1)} instead of {'#' * level}.\n"
|
|
910
919
|
f" Skipping heading levels (e.g., ## to ####) makes document structure unclear."
|
|
@@ -927,7 +936,7 @@ class SyntaxValidator(BaseValidator):
|
|
|
927
936
|
f"Standard section '{heading_text}' using level 1 heading (#)",
|
|
928
937
|
file_path=file_path,
|
|
929
938
|
line_number=line_num,
|
|
930
|
-
context=
|
|
939
|
+
context=original_line.strip(),
|
|
931
940
|
suggestion=(
|
|
932
941
|
f"Change to level 2 heading: ## {heading_text}\n"
|
|
933
942
|
f" Level 1 (#) should only be used for the document title.\n"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rxiv-maker
|
|
3
|
-
Version: 1.16.
|
|
3
|
+
Version: 1.16.7
|
|
4
4
|
Summary: Write scientific preprints in Markdown. Generate publication-ready PDFs efficiently.
|
|
5
5
|
Project-URL: Homepage, https://github.com/HenriquesLab/rxiv-maker
|
|
6
6
|
Project-URL: Documentation, https://github.com/HenriquesLab/rxiv-maker#readme
|
|
@@ -27,7 +27,7 @@ Requires-Python: >=3.10
|
|
|
27
27
|
Requires-Dist: click>=8.0.0
|
|
28
28
|
Requires-Dist: crossref-commons>=0.0.7
|
|
29
29
|
Requires-Dist: gitpython>=3.1.0
|
|
30
|
-
Requires-Dist: henriqueslab-updater>=1.
|
|
30
|
+
Requires-Dist: henriqueslab-updater>=1.2.0
|
|
31
31
|
Requires-Dist: latex2mathml>=3.78.0
|
|
32
32
|
Requires-Dist: matplotlib>=3.7.0
|
|
33
33
|
Requires-Dist: numpy>=1.24.0
|
|
@@ -358,9 +358,14 @@ rxiv config --non-interactive # Show current settings
|
|
|
358
358
|
|
|
359
359
|
### Maintenance
|
|
360
360
|
```bash
|
|
361
|
-
|
|
362
|
-
rxiv
|
|
363
|
-
rxiv
|
|
361
|
+
# Upgrade commands (auto-detects Homebrew, pip, uv, pipx)
|
|
362
|
+
rxiv upgrade # Interactive upgrade with confirmation
|
|
363
|
+
rxiv upgrade --yes # Upgrade without confirmation
|
|
364
|
+
rxiv upgrade --check-only # Check for updates only
|
|
365
|
+
|
|
366
|
+
# Changelog and version information
|
|
367
|
+
rxiv changelog # View changelog and release notes
|
|
368
|
+
rxiv changelog --recent 5 # View last 5 versions
|
|
364
369
|
```
|
|
365
370
|
|
|
366
371
|
> **💡 CI/Automation Note:** All interactive commands support non-interactive mode or configuration files for use in CI/CD pipelines and automated workflows. Use `--non-interactive` flag or configure via `~/.rxiv-maker/config` for non-TTY environments.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
rxiv_maker/__init__.py,sha256=p04JYC5ZhP6dLXkoWVlKNyiRvsDE1a4C88f9q4xO3tA,3268
|
|
2
|
-
rxiv_maker/__version__.py,sha256
|
|
2
|
+
rxiv_maker/__version__.py,sha256=--L7TPRaASOQ9_Gb1AzwOD0jIisWKuGJB7fbHOHsoFs,51
|
|
3
3
|
rxiv_maker/rxiv_maker_cli.py,sha256=9Lu_mhFPXwx5jzAR6StCNxwCm_fkmP5qiOYdNuh_AwI,120
|
|
4
4
|
rxiv_maker/validate.py,sha256=AIzgP59KbCQJqC9WIGfUdVv0xI6ud9g1fFznQkaGz5Q,9373
|
|
5
5
|
rxiv_maker/cli/__init__.py,sha256=Jw0DTFUSofN-02xpVrt1UUzRcgH5NNd-GPNidhmNwpU,77
|
|
@@ -29,7 +29,7 @@ rxiv_maker/cli/commands/repos.py,sha256=SQ9nuhSkyHKFPYn_TrOxyQoGdDRI-OBOgUSGJpRu
|
|
|
29
29
|
rxiv_maker/cli/commands/repos_search.py,sha256=6sUMvyHlHEX1p88hPtu0_Hf8z6JpOinJ53l9ZI-rirc,7743
|
|
30
30
|
rxiv_maker/cli/commands/setup.py,sha256=9ue4bDETpSPGVkWFDfpuTDsysax5-QKGxmt42Gb7oeU,2294
|
|
31
31
|
rxiv_maker/cli/commands/track_changes.py,sha256=omf_77A7htRSa8naUEPTTtUTxrSwMpzHFOuU0j1xAJw,1163
|
|
32
|
-
rxiv_maker/cli/commands/upgrade.py,sha256=
|
|
32
|
+
rxiv_maker/cli/commands/upgrade.py,sha256=UpdqEQwbNYmDMbSrYGv_pVd-7u8PPT3US5RVENhKK4w,1852
|
|
33
33
|
rxiv_maker/cli/commands/validate.py,sha256=3JghFQevJvQDQII4p_QWbQXMEUyDpM-t9-WxtaT4edo,1629
|
|
34
34
|
rxiv_maker/cli/commands/version.py,sha256=VMlfSxxsrZH02d24MXLUDZfHBW39yZRpWxUpMhQ-X0Y,2737
|
|
35
35
|
rxiv_maker/cli/framework/__init__.py,sha256=4FPXdP8J6v4eeEn46mwY0VtnwxjR1jnW_kTrXykQlQs,2704
|
|
@@ -38,7 +38,7 @@ rxiv_maker/cli/framework/cache_commands.py,sha256=J91UYLTsLTRoNdzuhAbNL2bJJovYYf
|
|
|
38
38
|
rxiv_maker/cli/framework/config_commands.py,sha256=a1uOQkCCw3d4qlro3OwHIorcoNg03T_R4-HbfVb-hmQ,19336
|
|
39
39
|
rxiv_maker/cli/framework/content_commands.py,sha256=RilxKeG2c1m2fu0CtWAvP3cGh11DGx9P-nh2kIewAg4,22596
|
|
40
40
|
rxiv_maker/cli/framework/decorators.py,sha256=fh085e3k1CaLSMoZevt8hvgnEuejrf-mcNS-dwXoY_A,10365
|
|
41
|
-
rxiv_maker/cli/framework/utility_commands.py,sha256=
|
|
41
|
+
rxiv_maker/cli/framework/utility_commands.py,sha256=drIAc1TAYpne76gj7SZeZhPozVAY5uL9GFPVT_Ez0-E,26437
|
|
42
42
|
rxiv_maker/cli/framework/workflow_commands.py,sha256=CFa3c5oJMmy9cUZxTAU97eKC6YrOljzerSAMrywjbKw,29684
|
|
43
43
|
rxiv_maker/config/defaults.py,sha256=vHyLGVxe5-z9TLxu5f6NhquPvqQkER_KZv_j1I4_dHQ,3055
|
|
44
44
|
rxiv_maker/config/validator.py,sha256=9XDPfo_YgasGt6NLkl6HIhaGh1fr6XsFNiXU2DSsivw,38299
|
|
@@ -96,7 +96,7 @@ rxiv_maker/engines/operations/build_manager.py,sha256=TAX4-r8HjraAzzvQuIt0CNlvWL
|
|
|
96
96
|
rxiv_maker/engines/operations/cleanup.py,sha256=RfbXif0neEVMlprFIHWyvQh6kwghalcesY3t-69Iwsw,18095
|
|
97
97
|
rxiv_maker/engines/operations/fix_bibliography.py,sha256=ZD8uO4YCxDCMWH4WtBSDc4TOMgM383fcLgsCCW0yK60,22428
|
|
98
98
|
rxiv_maker/engines/operations/generate_docs.py,sha256=8d_oVYUuRRqTuYN1KnJKqM5Ydp4_yt52ntBv8gUoRVk,11223
|
|
99
|
-
rxiv_maker/engines/operations/generate_figures.py,sha256=
|
|
99
|
+
rxiv_maker/engines/operations/generate_figures.py,sha256=YeKzH6qVsuPGjtCsvWugLJoys6y73xTyO7Y5g30KM20,38730
|
|
100
100
|
rxiv_maker/engines/operations/generate_preprint.py,sha256=wpKDAu2RLJ4amSdhX5GZ7hU-iTsTRt4etcEA7AZYp04,2662
|
|
101
101
|
rxiv_maker/engines/operations/prepare_arxiv.py,sha256=cd0JN5IO-Wy9T8ab75eibyaA8_K8Gpwrz2F-95OMnx4,21551
|
|
102
102
|
rxiv_maker/engines/operations/setup_environment.py,sha256=gERuThHTldH0YqgXn85995deHBP6csY1ZhCNgU6-vFg,12691
|
|
@@ -165,6 +165,7 @@ rxiv_maker/utils/performance.py,sha256=EBDVNshSaeG7Nu-GCZtRAzTunGn4z_Bb2jEck045b
|
|
|
165
165
|
rxiv_maker/utils/platform.py,sha256=DCD3gvm7_DBcT67gGIXhTDV5mPrBjWrL7R2JdsmIgng,17773
|
|
166
166
|
rxiv_maker/utils/python_execution_reporter.py,sha256=l3hqLXtGAg_wKUlkikK1oaHPeBpoCkj3iR6i1fc11ys,10606
|
|
167
167
|
rxiv_maker/utils/retry.py,sha256=aNsuc7HuxMwG4yubgX0GxIEZ0iF6_4lACusVMc0ZSXA,11026
|
|
168
|
+
rxiv_maker/utils/rich_upgrade_notifier.py,sha256=aMqkx9l_2KtEiwVFdhOU4oqsAgdWQ86LuqWN4k_cTA0,6365
|
|
168
169
|
rxiv_maker/utils/text_utils.py,sha256=ntovIx7qXB0LWO9OmCu7-rqTSfyEvthrTjYEJTbmZ_U,1972
|
|
169
170
|
rxiv_maker/utils/tips.py,sha256=XHgbJkyFjYt5Pz-Rdz_yCjnRLguxIsHwdFmNSg323NA,8078
|
|
170
171
|
rxiv_maker/utils/title_sync.py,sha256=C7NT80DgBJrS70mil6b7ghcZyKR2n5_MNlC6Yli_dXM,15094
|
|
@@ -180,15 +181,15 @@ rxiv_maker/validators/latex_error_parser.py,sha256=crk3NAniLBp2iABP4lxts7gvCEg6K
|
|
|
180
181
|
rxiv_maker/validators/math_validator.py,sha256=LcRIGAv47OsPfOg4E48l2vKN1Q7lHAeduNN5MFMLQGE,27669
|
|
181
182
|
rxiv_maker/validators/pdf_validator.py,sha256=YU4WRPeTEOtvBlquFEtZrG9p_WjlN5nnCByTSRAvWyw,21530
|
|
182
183
|
rxiv_maker/validators/reference_validator.py,sha256=UqvsEa3kVOBkbGMo24fXpFVUtpN1feIf9MfDQIraQZs,15868
|
|
183
|
-
rxiv_maker/validators/syntax_validator.py,sha256=
|
|
184
|
+
rxiv_maker/validators/syntax_validator.py,sha256=hHpKVKky3UiA1ZylA6jJVP3DN47LgaSSJCK2PPA-8BA,43599
|
|
184
185
|
rxiv_maker/validators/doi/__init__.py,sha256=NqATXseuS0zVNns56RvFe8TdqgvueY0Rbw2Pjozlajc,494
|
|
185
186
|
rxiv_maker/validators/doi/api_clients.py,sha256=tqdYUq8LFgRIO0tWfcenwmy2uO-IB1-GMvBfF3lP7-0,21763
|
|
186
187
|
rxiv_maker/validators/doi/metadata_comparator.py,sha256=euqHhKP5sHQAdZbdoAahUn6YqJqOfXIOobNgAqFHlN8,11533
|
|
187
188
|
rxiv_maker/tex/template.tex,sha256=zrJ3aFfu8j9zkg1l375eE9w-j42P3rz16wMD3dSgi1I,1354
|
|
188
189
|
rxiv_maker/tex/style/rxiv_maker_style.bst,sha256=jbVqrJgAm6F88cow5vtZuPBwwmlcYykclTm8RvZIo6Y,24281
|
|
189
190
|
rxiv_maker/tex/style/rxiv_maker_style.cls,sha256=F2qtnS9mI6SwOIaVH76egXZkB2_GzbH4gCTG_ZcfCDQ,24253
|
|
190
|
-
rxiv_maker-1.16.
|
|
191
|
-
rxiv_maker-1.16.
|
|
192
|
-
rxiv_maker-1.16.
|
|
193
|
-
rxiv_maker-1.16.
|
|
194
|
-
rxiv_maker-1.16.
|
|
191
|
+
rxiv_maker-1.16.7.dist-info/METADATA,sha256=6O761FLD317XmFqsnlCerTogrcC1Kq4wUJW9xnz4psM,18177
|
|
192
|
+
rxiv_maker-1.16.7.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
193
|
+
rxiv_maker-1.16.7.dist-info/entry_points.txt,sha256=ghCN0hI9A1GlG7QY5F6E-xYPflA8CyS4B6bTQ1YLop0,97
|
|
194
|
+
rxiv_maker-1.16.7.dist-info/licenses/LICENSE,sha256=GSZFoPIhWDNJEtSHTQ5dnELN38zFwRiQO2antBezGQk,1093
|
|
195
|
+
rxiv_maker-1.16.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|