rxiv-maker 1.16.5__py3-none-any.whl → 1.16.6__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 CHANGED
@@ -1,3 +1,3 @@
1
1
  """Version information."""
2
2
 
3
- __version__ = "1.16.5"
3
+ __version__ = "1.16.6"
@@ -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(" • Test PDF generation: rxiv pdf ../manuscript-rxiv-maker/MANUSCRIPT")
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(" • Test PDF generation: rxiv pdf ../manuscript-rxiv-maker/MANUSCRIPT")
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 Exception:
254
- # If retry fails, fall back to placeholder
255
- return self._create_fallback_mermaid_diagram(input_file, output_file)
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
- print(f"mermaid.ink service returned status {response.status_code}")
263
- print(
264
- "💡 Tip: If this is a syntax error, check your Mermaid diagram at https://www.mermaidchart.com/"
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
- print("requests library not available for Mermaid generation")
269
- return self._create_fallback_mermaid_diagram(input_file, output_file)
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
- print(f"mermaid.ink service error: {e}")
273
- print("💡 Tip: Check your Mermaid diagram syntax at https://www.mermaidchart.com/")
274
- return self._create_fallback_mermaid_diagram(input_file, output_file)
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} (mermaid.ink unavailable)")
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} (mermaid.ink unavailable)")
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("mermaid.ink service unavailable - diagram generation failed\n")
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} (mermaid.ink unavailable)")
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"))
@@ -854,8 +854,17 @@ class SyntaxValidator(BaseValidator):
854
854
  heading_texts = {}
855
855
  previous_level = 0
856
856
 
857
- for line_num, line in enumerate(lines, 1):
858
- match = heading_pattern.match(line)
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=line.strip(),
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=line.strip(),
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.5
3
+ Version: 1.16.6
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
@@ -1,5 +1,5 @@
1
1
  rxiv_maker/__init__.py,sha256=p04JYC5ZhP6dLXkoWVlKNyiRvsDE1a4C88f9q4xO3tA,3268
2
- rxiv_maker/__version__.py,sha256=scH9t8S4Yy0pZ0rKdWKP1B1m1lQmPo9FwrixAZxLlR0,51
2
+ rxiv_maker/__version__.py,sha256=qf0GMhAIH5L_wI59eHcfpHtgoZxaqziVuvj0M1daj0g,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
@@ -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=_P_KwjlyiNS-vVboU-DqkHynGPqzXyoZRWGmLMLTnOs,26214
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=3oIuS0wryO9WpPZ3UD2qm0YscNidTOEiO4_Jd6r3SmY,32842
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
@@ -180,15 +180,15 @@ rxiv_maker/validators/latex_error_parser.py,sha256=crk3NAniLBp2iABP4lxts7gvCEg6K
180
180
  rxiv_maker/validators/math_validator.py,sha256=LcRIGAv47OsPfOg4E48l2vKN1Q7lHAeduNN5MFMLQGE,27669
181
181
  rxiv_maker/validators/pdf_validator.py,sha256=YU4WRPeTEOtvBlquFEtZrG9p_WjlN5nnCByTSRAvWyw,21530
182
182
  rxiv_maker/validators/reference_validator.py,sha256=UqvsEa3kVOBkbGMo24fXpFVUtpN1feIf9MfDQIraQZs,15868
183
- rxiv_maker/validators/syntax_validator.py,sha256=H7JK3H116nI9f_3uzAS_RlIsbcbgajhpoU5T2aGh46w,43098
183
+ rxiv_maker/validators/syntax_validator.py,sha256=hHpKVKky3UiA1ZylA6jJVP3DN47LgaSSJCK2PPA-8BA,43599
184
184
  rxiv_maker/validators/doi/__init__.py,sha256=NqATXseuS0zVNns56RvFe8TdqgvueY0Rbw2Pjozlajc,494
185
185
  rxiv_maker/validators/doi/api_clients.py,sha256=tqdYUq8LFgRIO0tWfcenwmy2uO-IB1-GMvBfF3lP7-0,21763
186
186
  rxiv_maker/validators/doi/metadata_comparator.py,sha256=euqHhKP5sHQAdZbdoAahUn6YqJqOfXIOobNgAqFHlN8,11533
187
187
  rxiv_maker/tex/template.tex,sha256=zrJ3aFfu8j9zkg1l375eE9w-j42P3rz16wMD3dSgi1I,1354
188
188
  rxiv_maker/tex/style/rxiv_maker_style.bst,sha256=jbVqrJgAm6F88cow5vtZuPBwwmlcYykclTm8RvZIo6Y,24281
189
189
  rxiv_maker/tex/style/rxiv_maker_style.cls,sha256=F2qtnS9mI6SwOIaVH76egXZkB2_GzbH4gCTG_ZcfCDQ,24253
190
- rxiv_maker-1.16.5.dist-info/METADATA,sha256=2U4BcFzbnYaEhzXTOgtUCk13H5w_J_RyoCZ5lQQjaGE,17950
191
- rxiv_maker-1.16.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
192
- rxiv_maker-1.16.5.dist-info/entry_points.txt,sha256=ghCN0hI9A1GlG7QY5F6E-xYPflA8CyS4B6bTQ1YLop0,97
193
- rxiv_maker-1.16.5.dist-info/licenses/LICENSE,sha256=GSZFoPIhWDNJEtSHTQ5dnELN38zFwRiQO2antBezGQk,1093
194
- rxiv_maker-1.16.5.dist-info/RECORD,,
190
+ rxiv_maker-1.16.6.dist-info/METADATA,sha256=7_r-ZEzlmKCVaCu_a-Z8tg8pr4kzfI3tDbvfCbc1dCw,17950
191
+ rxiv_maker-1.16.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
192
+ rxiv_maker-1.16.6.dist-info/entry_points.txt,sha256=ghCN0hI9A1GlG7QY5F6E-xYPflA8CyS4B6bTQ1YLop0,97
193
+ rxiv_maker-1.16.6.dist-info/licenses/LICENSE,sha256=GSZFoPIhWDNJEtSHTQ5dnELN38zFwRiQO2antBezGQk,1093
194
+ rxiv_maker-1.16.6.dist-info/RECORD,,