rxiv-maker 1.15.9__py3-none-any.whl → 1.16.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.
rxiv_maker/__version__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Version information."""
2
2
 
3
- __version__ = "1.15.9"
3
+ __version__ = "1.16.0"
@@ -10,6 +10,8 @@ from typing import Any, Dict
10
10
  DEFAULT_CONFIG: Dict[str, Any] = {
11
11
  # Citation configuration
12
12
  "citation_style": "numbered", # or "author-date"
13
+ # Bibliography author name format
14
+ "bibliography_author_format": "lastname_firstname", # Options: lastname_initials, lastname_firstname, firstname_lastname
13
15
  # Figures configuration
14
16
  "figures": {
15
17
  "directory": "FIGURES",
@@ -391,6 +391,10 @@ class ConfigValidator:
391
391
  },
392
392
  "bibliography": {"type": "string"},
393
393
  "citation_style": {"type": "string", "enum": ["numbered", "author-date"]},
394
+ "bibliography_author_format": {
395
+ "type": "string",
396
+ "enum": ["lastname_initials", "lastname_firstname", "firstname_lastname"],
397
+ },
394
398
  "enable_inline_doi_resolution": {"type": "boolean"},
395
399
  "language": {"type": "string", "enum": ["en", "es", "pt", "fr", "de"]},
396
400
  "license": {"type": "string"},
@@ -158,10 +158,10 @@ def process_citations_in_text(text: MarkdownContent, citation_style: str = "numb
158
158
  # Match email-like patterns: word@word.word or @word.word (domain patterns)
159
159
  text = re.sub(r"(\w+@[\w.-]+\.\w+|@[\w.-]+\.\w+)", protect_email, text)
160
160
 
161
- # Handle single citations like @citation_key (but not figure/equation references)
161
+ # Handle single citations like @citation_key (but not cross-references)
162
162
  # Allow alphanumeric, underscore, and hyphen in citation keys
163
- # Exclude figure and equation references by not matching @fig: or @eq: patterns
164
- text = re.sub(r"@(?!fig:|eq:)([a-zA-Z0-9_-]+)", rf"\\{inline_cmd}{{\1}}", text)
163
+ # Exclude all cross-references by not matching @word: patterns (@fig:, @sfig:, @snote:, @tbl:, etc.)
164
+ text = re.sub(r"@(?![a-zA-Z]+:)([a-zA-Z0-9_-]+)", rf"\\{inline_cmd}{{\1}}", text)
165
165
 
166
166
  # Restore protected email patterns
167
167
  for i, pattern in enumerate(email_patterns):
@@ -194,16 +194,29 @@ def extract_citations_from_text(text: MarkdownContent) -> list[CitationKey]:
194
194
  """
195
195
  citations: list[CitationKey] = []
196
196
 
197
+ # First, remove content inside backticks (inline code) to avoid extracting example citations
198
+ # This prevents `@key` or `@Knuth...` from being treated as actual citations
199
+ backtick_patterns = []
200
+
201
+ def protect_backticks(match):
202
+ backtick_patterns.append(match.group(0))
203
+ return f"__BACKTICK_PATTERN_{len(backtick_patterns) - 1}__"
204
+
205
+ # Match both single backticks `...` and triple backticks ```...```
206
+ text_cleaned = re.sub(r"`[^`]+`", protect_backticks, text)
207
+ text_cleaned = re.sub(r"```.*?```", protect_backticks, text_cleaned, flags=re.DOTALL)
208
+
197
209
  # Find bracketed multiple citations
198
- bracketed_matches = re.findall(r"\[(@[^]]+)\]", text)
210
+ bracketed_matches = re.findall(r"\[(@[^]]+)\]", text_cleaned)
199
211
  for match in bracketed_matches:
200
212
  for cite in match.split(";"):
201
213
  clean_cite = cite.strip().lstrip("@").strip()
202
214
  if clean_cite and clean_cite not in citations:
203
215
  citations.append(clean_cite)
204
216
 
205
- # Find single citations (excluding figure and equation references)
206
- single_matches = re.findall(r"@(?!fig:|eq:)([a-zA-Z0-9_-]+)", text)
217
+ # Find single citations (excluding all cross-references like @fig:, @sfig:, @snote:, @tbl:, etc.)
218
+ # Exclude any pattern that looks like @word: (cross-reference format)
219
+ single_matches = re.findall(r"@(?![a-zA-Z]+:)([a-zA-Z0-9_-]+)", text_cleaned)
207
220
  for cite in single_matches:
208
221
  if cite not in citations:
209
222
  citations.append(cite)
@@ -38,6 +38,7 @@ class ConfigManager:
38
38
  # Configuration search paths (in order of priority)
39
39
  # Only search in manuscript directory, no global home directory configs
40
40
  self.config_paths = [
41
+ self.base_dir / "00_CONFIG.yml", # Legacy manuscript config (first priority)
41
42
  self.base_dir / "rxiv.yml",
42
43
  self.base_dir / "rxiv.yaml",
43
44
  self.base_dir / ".rxiv.yml",
@@ -39,6 +39,7 @@ CONFIG_SCOPE_MAP = {
39
39
  "output": ConfigScope.MANUSCRIPT,
40
40
  "figures": ConfigScope.MANUSCRIPT,
41
41
  "bibliography": ConfigScope.MANUSCRIPT,
42
+ "bibliography_author_format": ConfigScope.MANUSCRIPT,
42
43
  "validation": ConfigScope.MANUSCRIPT,
43
44
  "cache": ConfigScope.MANUSCRIPT,
44
45
  "acknowledge_rxiv_maker": ConfigScope.MANUSCRIPT,
@@ -781,8 +781,30 @@ class BuildManager:
781
781
  self.log("Copying style files...", "STEP")
782
782
 
783
783
  try:
784
- # Use centralized path manager method for style file copying
785
- copied_files = self.path_manager.copy_style_files_to_output()
784
+ # Generate custom .bst file with author name format preference
785
+ from ...core.managers.config_manager import ConfigManager
786
+ from ...utils.bst_generator import generate_bst_file
787
+
788
+ # Load config to get bibliography author format
789
+ config_manager = ConfigManager(base_dir=self.path_manager.manuscript_path)
790
+ config = config_manager.load_config()
791
+ author_format = config.get("bibliography_author_format", "lastname_firstname")
792
+
793
+ # Generate custom .bst file directly to output directory
794
+ try:
795
+ output_dir = self.path_manager.output_dir
796
+ generate_bst_file(author_format, output_dir)
797
+ self.log(f"Generated custom .bst file with format: {author_format}", "INFO")
798
+ except Exception as e:
799
+ self.log(f"Failed to generate custom .bst file: {e}. Using default.", "WARNING")
800
+ # If generation failed, copy the default .bst
801
+ copied_files = self.path_manager.copy_style_files_to_output()
802
+ for copied_file in copied_files:
803
+ self.log(f"Copied {copied_file.name} to output directory", "INFO")
804
+ return True
805
+
806
+ # Copy only .cls file (not .bst since we generated a custom one)
807
+ copied_files = self.path_manager.copy_style_files_to_output(style_files=["rxiv_maker_style.cls"])
786
808
 
787
809
  for copied_file in copied_files:
788
810
  self.log(f"Copied {copied_file.name} to output directory", "INFO")
@@ -897,10 +919,12 @@ class BuildManager:
897
919
  verbose=self.verbose,
898
920
  )
899
921
  success = tracker.generate_change_tracked_pdf()
900
-
922
+
901
923
  if self.performance_tracker:
902
- self.performance_tracker.end_operation("complete_build", metadata={"result": "success" if success else "failure"})
903
-
924
+ self.performance_tracker.end_operation(
925
+ "complete_build", metadata={"result": "success" if success else "failure"}
926
+ )
927
+
904
928
  op.add_metadata("build_successful", success)
905
929
  op.add_metadata("track_changes", True)
906
930
  return success
@@ -34,7 +34,7 @@ def generate_preprint(output_dir, yaml_metadata, manuscript_path=None):
34
34
  manuscript_md = find_manuscript_md(manuscript_path)
35
35
 
36
36
  # Process all template replacements
37
- template_content = process_template_replacements(template_content, yaml_metadata, str(manuscript_md))
37
+ template_content = process_template_replacements(template_content, yaml_metadata, str(manuscript_md), output_dir)
38
38
 
39
39
  # Extract manuscript name using centralized logic (PathManager handles this via write_manuscript_output)
40
40
  # The write_manuscript_output function now uses PathManager internally for consistent name extraction
@@ -11,6 +11,7 @@ from pathlib import Path
11
11
  from typing import Any, Dict
12
12
 
13
13
  from ..core.logging_config import get_logger
14
+ from ..core.managers.config_manager import ConfigManager
14
15
  from ..core.path_manager import PathManager
15
16
  from ..processors.yaml_processor import extract_yaml_metadata
16
17
  from ..utils.bibliography_parser import parse_bib_file
@@ -44,6 +45,11 @@ class DocxExporter:
44
45
  self.resolve_dois = resolve_dois
45
46
  self.include_footnotes = include_footnotes
46
47
 
48
+ # Load config to get author name format preference
49
+ config_manager = ConfigManager(base_dir=Path(manuscript_path))
50
+ config = config_manager.load_config()
51
+ self.author_format = config.get("bibliography_author_format", "lastname_firstname")
52
+
47
53
  # Components
48
54
  self.citation_mapper = CitationMapper()
49
55
  self.content_processor = DocxContentProcessor()
@@ -317,7 +323,7 @@ class DocxExporter:
317
323
  # doi = self._resolve_doi_from_metadata(entry)
318
324
 
319
325
  # Format entry (full format for DOCX bibliography)
320
- formatted = format_bibliography_entry(entry, doi, slim=False)
326
+ formatted = format_bibliography_entry(entry, doi, slim=False, author_format=self.author_format)
321
327
 
322
328
  bibliography[number] = {"key": key, "entry": entry, "doi": doi, "formatted": formatted}
323
329
 
@@ -146,14 +146,23 @@ class DocxWriter:
146
146
 
147
147
  # Collect unique affiliations and build mapping
148
148
  all_affiliations = []
149
- affiliation_map = {} # Maps affiliation string to number
149
+ affiliation_map = {} # Maps affiliation shortname to number
150
+
151
+ # Get full affiliation details from metadata
152
+ affiliation_details = {a.get("shortname"): a for a in metadata.get("affiliations", [])}
150
153
 
151
154
  for author in authors:
152
155
  author_affils = author.get("affiliations", [])
153
- for affil in author_affils:
154
- if affil not in affiliation_map:
155
- affiliation_map[affil] = len(affiliation_map) + 1
156
- all_affiliations.append(affil)
156
+ for affil_shortname in author_affils:
157
+ if affil_shortname not in affiliation_map:
158
+ affiliation_map[affil_shortname] = len(affiliation_map) + 1
159
+ # Look up full affiliation info
160
+ affil_info = affiliation_details.get(affil_shortname, {})
161
+ full_name = affil_info.get("full_name", affil_shortname)
162
+ location = affil_info.get("location", "")
163
+ # Format: "Full Name, Location" or just "Full Name" if no location
164
+ affil_text = f"{full_name}, {location}" if location else full_name
165
+ all_affiliations.append(affil_text)
157
166
 
158
167
  # Add authors with superscript affiliation numbers
159
168
  if authors:
@@ -298,8 +298,20 @@ def generate_keywords(yaml_metadata):
298
298
  return result
299
299
 
300
300
 
301
- def generate_bibliography(yaml_metadata):
302
- """Generate LaTeX bibliography section from YAML metadata."""
301
+ def generate_bibliography(yaml_metadata, output_dir=None):
302
+ """Generate LaTeX bibliography section from YAML metadata.
303
+
304
+ Args:
305
+ yaml_metadata: Manuscript metadata dictionary
306
+ output_dir: Output directory for generated files (optional)
307
+
308
+ Returns:
309
+ LaTeX bibliography command string
310
+ """
311
+ from pathlib import Path
312
+
313
+ from ..utils.bst_generator import generate_bst_file
314
+
303
315
  bibliography_config = yaml_metadata.get("bibliography", "03_REFERENCES")
304
316
 
305
317
  # Handle both dict and string formats for backward compatibility
@@ -312,11 +324,34 @@ def generate_bibliography(yaml_metadata):
312
324
  if bibliography.endswith(".bib"):
313
325
  bibliography = bibliography[:-4]
314
326
 
327
+ # Generate custom .bst file with author name format preference
328
+ if output_dir:
329
+ author_format = yaml_metadata.get("bibliography_author_format", "lastname_firstname")
330
+ try:
331
+ output_path = Path(output_dir)
332
+ generate_bst_file(author_format, output_path)
333
+ except Exception as e:
334
+ # Log warning but don't fail - fall back to default .bst
335
+ import logging
336
+
337
+ logger = logging.getLogger(__name__)
338
+ logger.warning(f"Failed to generate custom .bst file: {e}. Using default.")
339
+
315
340
  return f"\\bibliography{{{bibliography}}}"
316
341
 
317
342
 
318
- def process_template_replacements(template_content, yaml_metadata, article_md):
319
- """Process all template replacements with metadata and content."""
343
+ def process_template_replacements(template_content, yaml_metadata, article_md, output_dir=None):
344
+ """Process all template replacements with metadata and content.
345
+
346
+ Args:
347
+ template_content: LaTeX template content
348
+ yaml_metadata: Manuscript metadata dictionary
349
+ article_md: Article markdown content
350
+ output_dir: Output directory for generated files (optional)
351
+
352
+ Returns:
353
+ Processed template content with all replacements
354
+ """
320
355
  # Process draft watermark based on status field
321
356
  is_draft = False
322
357
  if "status" in yaml_metadata:
@@ -424,7 +459,7 @@ def process_template_replacements(template_content, yaml_metadata, article_md):
424
459
  template_content = template_content.replace("<PY-RPL:KEYWORDS>", keywords_section)
425
460
 
426
461
  # Generate bibliography section
427
- bibliography_section = generate_bibliography(yaml_metadata)
462
+ bibliography_section = generate_bibliography(yaml_metadata, output_dir)
428
463
  template_content = template_content.replace("<PY-RPL:BIBLIOGRAPHY>", bibliography_section)
429
464
 
430
465
  # Extract content sections from markdown
@@ -221,7 +221,7 @@ class ManuscriptService(BaseService):
221
221
 
222
222
  # Process template with metadata
223
223
  processed_content = process_template_replacements(
224
- template_content, metadata.raw, str(metadata.manuscript_path)
224
+ template_content, metadata.raw, str(metadata.manuscript_path), output_dir
225
225
  )
226
226
 
227
227
  # Write output
@@ -154,6 +154,9 @@ citation_style: "numbered"
154
154
  #
155
155
  # methods_placement: "after_bibliography" # Options: "inline", "after_results", "after_bibliography"
156
156
  # acknowledge_rxiv_maker: true
157
+ #
158
+ # # Bibliography author name format (applies to both PDF and DOCX):
159
+ # bibliography_author_format: "lastname_firstname" # Options: "lastname_initials" (Smith, J.A.), "lastname_firstname" (Smith, John A.), "firstname_lastname" (John A. Smith)
157
160
  """
158
161
 
159
162
  def _get_default_main_template(self) -> str:
@@ -0,0 +1,311 @@
1
+ """Author name parsing and formatting utilities.
2
+
3
+ This module provides functionality to parse, format, and transform author names
4
+ between different bibliographic citation formats.
5
+
6
+ Supported formats:
7
+ - lastname_initials: "Smith, J.A."
8
+ - lastname_firstname: "Smith, John A."
9
+ - firstname_lastname: "John A. Smith"
10
+ """
11
+
12
+ import re
13
+ from typing import Dict
14
+
15
+
16
+ def extract_initials(given_name: str) -> str:
17
+ """Extract initials from a given name.
18
+
19
+ Args:
20
+ given_name: Given name(s), which may include first and middle names
21
+
22
+ Returns:
23
+ Formatted initials with periods
24
+
25
+ Examples:
26
+ >>> extract_initials("John Alan")
27
+ 'J.A.'
28
+ >>> extract_initials("J. A.")
29
+ 'J.A.'
30
+ >>> extract_initials("Jean-Paul")
31
+ 'J.-P.'
32
+ >>> extract_initials("John")
33
+ 'J.'
34
+ """
35
+ if not given_name or not given_name.strip():
36
+ return ""
37
+
38
+ given_name = given_name.strip()
39
+
40
+ # Handle hyphenated names specially (e.g., "Jean-Paul" → "J.-P.")
41
+ if "-" in given_name:
42
+ # Remove periods first, then split on hyphen
43
+ cleaned = given_name.replace(".", "").strip()
44
+ parts = cleaned.split("-")
45
+ initials = "-".join(part[0].upper() + "." for part in parts if part and part.strip())
46
+ return initials
47
+
48
+ # Remove periods and split on whitespace to handle multi-word names
49
+ cleaned = given_name.replace(".", "").strip()
50
+ words = cleaned.split()
51
+
52
+ if not words:
53
+ return ""
54
+
55
+ # Handle concatenated initials like "JA" → "J.A."
56
+ # If we have a single "word" with multiple uppercase letters, treat each as an initial
57
+ if len(words) == 1 and len(words[0]) > 1:
58
+ word = words[0]
59
+ # Check if it looks like concatenated initials (all or mostly uppercase)
60
+ uppercase_count = sum(1 for c in word if c.isupper())
61
+ if uppercase_count >= len(word) * 0.8: # 80% or more uppercase
62
+ # Treat each uppercase letter as an initial
63
+ initials = [c.upper() + "." for c in word if c.isupper()]
64
+ return "".join(initials) if initials else ""
65
+
66
+ # Extract first letter from each word
67
+ initials = []
68
+ for word in words:
69
+ if word and word[0].isalpha():
70
+ initials.append(word[0].upper() + ".")
71
+
72
+ return "".join(initials) if initials else ""
73
+
74
+
75
+ def parse_author_name(name_str: str) -> Dict[str, str]:
76
+ """Parse an author name into components.
77
+
78
+ Handles both "LastName, FirstName MiddleName" and "FirstName MiddleName LastName" formats.
79
+
80
+ Args:
81
+ name_str: Author name string to parse
82
+
83
+ Returns:
84
+ Dictionary with keys: first, middle, last, suffix, von
85
+ Empty strings for missing components
86
+
87
+ Examples:
88
+ >>> parse_author_name("Smith, John A.")
89
+ {'first': 'John', 'middle': 'A.', 'last': 'Smith', 'suffix': '', 'von': ''}
90
+ >>> parse_author_name("von Neumann, John")
91
+ {'first': 'John', 'middle': '', 'last': 'von Neumann', 'suffix': '', 'von': 'von'}
92
+ >>> parse_author_name("Martin, James Jr.")
93
+ {'first': 'James', 'middle': '', 'last': 'Martin', 'suffix': 'Jr.', 'von': ''}
94
+ >>> parse_author_name("John A. Smith")
95
+ {'first': 'John', 'middle': 'A.', 'last': 'Smith', 'suffix': '', 'von': ''}
96
+ """
97
+ if not name_str or not name_str.strip():
98
+ return {"first": "", "middle": "", "last": "", "suffix": "", "von": ""}
99
+
100
+ name_str = name_str.strip()
101
+
102
+ # Common suffixes to detect
103
+ suffixes = ["Jr.", "Jr", "Sr.", "Sr", "II", "III", "IV", "V"]
104
+
105
+ # Check for "LastName, FirstName" format (comma indicates this format)
106
+ if "," in name_str:
107
+ # Split by comma
108
+ parts = [p.strip() for p in name_str.split(",", 1)]
109
+ last_part = parts[0]
110
+ given_part = parts[1] if len(parts) > 1 else ""
111
+
112
+ # Check if last part of given_part is a suffix
113
+ suffix = ""
114
+ if given_part:
115
+ given_words = given_part.split()
116
+ if given_words and given_words[-1] in suffixes:
117
+ suffix = given_words[-1]
118
+ given_part = " ".join(given_words[:-1])
119
+
120
+ # Parse given names (first + middle)
121
+ given_words = given_part.split() if given_part else []
122
+ first = given_words[0] if given_words else ""
123
+ middle = " ".join(given_words[1:]) if len(given_words) > 1 else ""
124
+
125
+ # Check for von/van prefix in last name
126
+ von = ""
127
+ last_words = last_part.split()
128
+ if last_words and last_words[0].lower() in ["von", "van", "de", "del", "della", "di"]:
129
+ von = last_words[0]
130
+ # Keep von as part of last name
131
+ last = last_part
132
+ else:
133
+ last = last_part
134
+
135
+ return {
136
+ "first": first,
137
+ "middle": middle,
138
+ "last": last,
139
+ "suffix": suffix,
140
+ "von": von,
141
+ }
142
+ else:
143
+ # "FirstName MiddleName LastName" format
144
+ words = name_str.split()
145
+
146
+ if not words:
147
+ return {"first": "", "middle": "", "last": "", "suffix": "", "von": ""}
148
+
149
+ # Check if last word is a suffix
150
+ suffix = ""
151
+ if words[-1] in suffixes:
152
+ suffix = words[-1]
153
+ words = words[:-1]
154
+
155
+ if not words:
156
+ return {"first": "", "middle": "", "last": "", "suffix": suffix, "von": ""}
157
+
158
+ # Single name (e.g., "Plato")
159
+ if len(words) == 1:
160
+ return {
161
+ "first": "",
162
+ "middle": "",
163
+ "last": words[0],
164
+ "suffix": suffix,
165
+ "von": "",
166
+ }
167
+
168
+ # Check for von/van prefix
169
+ von = ""
170
+ von_idx = None
171
+ for i, word in enumerate(words[:-1]): # Don't check last word
172
+ if word.lower() in ["von", "van", "de", "del", "della", "di"]:
173
+ von = word
174
+ von_idx = i
175
+ break
176
+
177
+ if von_idx is not None:
178
+ # Everything before von is first/middle, von + rest is last
179
+ first = words[0] if von_idx > 0 else ""
180
+ middle = " ".join(words[1:von_idx]) if von_idx > 1 else ""
181
+ last = " ".join(words[von_idx:])
182
+ else:
183
+ # Standard: First [Middle...] Last
184
+ first = words[0]
185
+ middle = " ".join(words[1:-1]) if len(words) > 2 else ""
186
+ last = words[-1]
187
+
188
+ return {
189
+ "first": first,
190
+ "middle": middle,
191
+ "last": last,
192
+ "suffix": suffix,
193
+ "von": von,
194
+ }
195
+
196
+
197
+ def format_author_name(author_parts: Dict[str, str], format_type: str) -> str:
198
+ """Format an author name according to the specified format.
199
+
200
+ Args:
201
+ author_parts: Dictionary with author name components (from parse_author_name)
202
+ format_type: One of "lastname_initials", "lastname_firstname", "firstname_lastname"
203
+
204
+ Returns:
205
+ Formatted author name string
206
+
207
+ Examples:
208
+ >>> parts = {'first': 'John', 'middle': 'A.', 'last': 'Smith', 'suffix': '', 'von': ''}
209
+ >>> format_author_name(parts, "lastname_initials")
210
+ 'Smith, J.A.'
211
+ >>> format_author_name(parts, "lastname_firstname")
212
+ 'Smith, John A.'
213
+ >>> format_author_name(parts, "firstname_lastname")
214
+ 'John A. Smith'
215
+ """
216
+ first = author_parts.get("first", "").strip()
217
+ middle = author_parts.get("middle", "").strip()
218
+ last = author_parts.get("last", "").strip()
219
+ suffix = author_parts.get("suffix", "").strip()
220
+
221
+ # Build given name (first + middle)
222
+ given_parts = []
223
+ if first:
224
+ given_parts.append(first)
225
+ if middle:
226
+ given_parts.append(middle)
227
+ given_name = " ".join(given_parts)
228
+
229
+ # Handle single-name authors (e.g., "Plato")
230
+ if not given_name and last:
231
+ return last
232
+
233
+ if not last:
234
+ return given_name # Fallback if no last name
235
+
236
+ # Format based on type
237
+ if format_type == "lastname_initials":
238
+ # Extract initials from given name
239
+ initials = extract_initials(given_name)
240
+ if initials:
241
+ result = f"{last}, {initials}"
242
+ else:
243
+ result = last
244
+ elif format_type == "lastname_firstname":
245
+ if given_name:
246
+ result = f"{last}, {given_name}"
247
+ else:
248
+ result = last
249
+ elif format_type == "firstname_lastname":
250
+ if given_name:
251
+ result = f"{given_name} {last}"
252
+ else:
253
+ result = last
254
+ else:
255
+ # Default to lastname_firstname
256
+ if given_name:
257
+ result = f"{last}, {given_name}"
258
+ else:
259
+ result = last
260
+
261
+ # Add suffix if present
262
+ if suffix:
263
+ if format_type in ["lastname_initials", "lastname_firstname"]:
264
+ result = f"{result}, {suffix}"
265
+ else:
266
+ result = f"{result} {suffix}"
267
+
268
+ return result
269
+
270
+
271
+ def format_author_list(authors_str: str, format_type: str) -> str:
272
+ """Format a list of authors separated by 'and'.
273
+
274
+ Note: Caller should clean LaTeX commands from authors_str before calling this function.
275
+
276
+ Args:
277
+ authors_str: String of authors separated by " and " (should be LaTeX-cleaned)
278
+ format_type: One of "lastname_initials", "lastname_firstname", "firstname_lastname"
279
+
280
+ Returns:
281
+ Formatted author list joined by " and "
282
+
283
+ Examples:
284
+ >>> format_author_list("Smith, John and Jones, Mary A.", "lastname_initials")
285
+ 'Smith, J. and Jones, M.A.'
286
+ >>> format_author_list("Smith, John A. and Jones, Mary", "firstname_lastname")
287
+ 'John A. Smith and Mary Jones'
288
+ """
289
+ if not authors_str or not authors_str.strip():
290
+ return ""
291
+
292
+ # Split by " and " (BibTeX standard separator)
293
+ # Handle potential variations: "and", " and ", " and "
294
+ author_list = re.split(r"\s+and\s+", authors_str)
295
+
296
+ # Parse and format each author
297
+ formatted_authors = []
298
+ for author in author_list:
299
+ author = author.strip()
300
+ if not author:
301
+ continue
302
+
303
+ # Parse the name
304
+ author_parts = parse_author_name(author)
305
+
306
+ # Format according to specified type
307
+ formatted = format_author_name(author_parts, format_type)
308
+ formatted_authors.append(formatted)
309
+
310
+ # Join with " and "
311
+ return " and ".join(formatted_authors)
@@ -0,0 +1,119 @@
1
+ """BibTeX style file generator for custom author name formatting.
2
+
3
+ This module generates custom .bst files with different author name format strings
4
+ to support lastname_initials, lastname_firstname, and firstname_lastname formats.
5
+ """
6
+
7
+ import re
8
+ from pathlib import Path
9
+ from typing import Dict
10
+
11
+ from ..core.logging_config import get_logger
12
+
13
+ logger = get_logger()
14
+
15
+ # BibTeX format string mapping
16
+ # BibTeX format codes:
17
+ # ff = full first name, f = first initial
18
+ # vv = von part, ll = last name, jj = junior part
19
+ BST_FORMAT_MAP: Dict[str, str] = {
20
+ "lastname_initials": "{vv~}{ll}{, f.}", # Smith, J.A.
21
+ "lastname_firstname": "{ff~}{vv~}{ll}{, jj}", # Smith, John A. (current default)
22
+ "firstname_lastname": "{ff~}{vv~}{ll}", # John A. Smith
23
+ }
24
+
25
+
26
+ def generate_bst_file(format_type: str, output_dir: Path) -> Path:
27
+ """Generate a custom .bst file with the specified author name format.
28
+
29
+ Args:
30
+ format_type: One of "lastname_initials", "lastname_firstname", "firstname_lastname"
31
+ output_dir: Directory where the generated .bst file should be written
32
+
33
+ Returns:
34
+ Path to the generated .bst file
35
+
36
+ Raises:
37
+ ValueError: If format_type is not recognized
38
+ FileNotFoundError: If template .bst file cannot be found
39
+ IOError: If .bst file cannot be written
40
+
41
+ Example:
42
+ >>> output_path = generate_bst_file("lastname_initials", Path("./output"))
43
+ >>> # Creates ./output/rxiv_maker_style.bst with lastname, initials format
44
+ """
45
+ if format_type not in BST_FORMAT_MAP:
46
+ valid_formats = ", ".join(BST_FORMAT_MAP.keys())
47
+ raise ValueError(f"Invalid format_type '{format_type}'. Must be one of: {valid_formats}")
48
+
49
+ # Get the format string for this format type
50
+ format_string = BST_FORMAT_MAP[format_type]
51
+
52
+ # Find the template .bst file in the package
53
+ # The template is located at src/tex/style/rxiv_maker_style.bst
54
+ package_root = Path(__file__).parent.parent.parent
55
+ template_path = package_root / "tex" / "style" / "rxiv_maker_style.bst"
56
+
57
+ if not template_path.exists():
58
+ raise FileNotFoundError(f"Template .bst file not found at: {template_path}")
59
+
60
+ # Read the template file
61
+ try:
62
+ with open(template_path, "r", encoding="utf-8") as f:
63
+ bst_content = f.read()
64
+ except IOError as e:
65
+ raise IOError(f"Failed to read template .bst file: {e}") from e
66
+
67
+ # Replace the format string on line 222
68
+ # The line looks like: s nameptr "{ff~}{vv~}{ll}{, jj}" format.name$ 't :=
69
+ # We need to replace the format string in quotes
70
+ pattern = r'(s\s+nameptr\s+")([^"]+)("\s+format\.name\$)'
71
+ replacement = rf"\1{format_string}\3"
72
+
73
+ modified_content, num_subs = re.subn(pattern, replacement, bst_content)
74
+
75
+ if num_subs == 0:
76
+ logger.warning("No format string pattern found in .bst file. The .bst file may have been modified.")
77
+ # Still write the file but log a warning
78
+ elif num_subs > 1:
79
+ logger.warning(
80
+ f"Found {num_subs} format string patterns in .bst file. Expected only 1. All have been replaced."
81
+ )
82
+
83
+ # Create output directory if it doesn't exist
84
+ output_dir = Path(output_dir)
85
+ output_dir.mkdir(parents=True, exist_ok=True)
86
+
87
+ # Write the generated .bst file
88
+ output_path = output_dir / "rxiv_maker_style.bst"
89
+ try:
90
+ with open(output_path, "w", encoding="utf-8") as f:
91
+ f.write(modified_content)
92
+ except IOError as e:
93
+ raise IOError(f"Failed to write generated .bst file: {e}") from e
94
+
95
+ logger.debug(f"Generated .bst file with format '{format_type}' at: {output_path}")
96
+
97
+ return output_path
98
+
99
+
100
+ def get_bst_format_string(format_type: str) -> str:
101
+ """Get the BibTeX format string for a given format type.
102
+
103
+ Args:
104
+ format_type: One of "lastname_initials", "lastname_firstname", "firstname_lastname"
105
+
106
+ Returns:
107
+ BibTeX format string
108
+
109
+ Raises:
110
+ ValueError: If format_type is not recognized
111
+
112
+ Example:
113
+ >>> get_bst_format_string("lastname_initials")
114
+ '{vv~}{ll}{, f.}'
115
+ """
116
+ if format_type not in BST_FORMAT_MAP:
117
+ valid_formats = ", ".join(BST_FORMAT_MAP.keys())
118
+ raise ValueError(f"Invalid format_type '{format_type}'. Must be one of: {valid_formats}")
119
+ return BST_FORMAT_MAP[format_type]
@@ -14,6 +14,8 @@ from typing import Optional
14
14
 
15
15
  from PIL import Image
16
16
 
17
+ from rxiv_maker.utils.author_name_formatter import format_author_list
18
+
17
19
  from ..utils.bibliography_parser import BibEntry
18
20
 
19
21
 
@@ -39,7 +41,12 @@ def remove_yaml_header(content: str) -> str:
39
41
  return content
40
42
 
41
43
 
42
- def format_bibliography_entry(entry: BibEntry, doi: Optional[str] = None, slim: bool = False) -> str:
44
+ def format_bibliography_entry(
45
+ entry: BibEntry,
46
+ doi: Optional[str] = None,
47
+ slim: bool = False,
48
+ author_format: str = "lastname_firstname",
49
+ ) -> str:
43
50
  """Format a bibliography entry for display.
44
51
 
45
52
  Full format: Author (Year). Title. Journal Volume(Number): Pages. DOI (if provided)
@@ -50,6 +57,7 @@ def format_bibliography_entry(entry: BibEntry, doi: Optional[str] = None, slim:
50
57
  entry: Bibliography entry to format
51
58
  doi: DOI string (optional)
52
59
  slim: If True, use slim format (LastName, Year)
60
+ author_format: Format for author names - "lastname_initials", "lastname_firstname", "firstname_lastname"
53
61
 
54
62
  Returns:
55
63
  Formatted bibliography string
@@ -69,8 +77,9 @@ def format_bibliography_entry(entry: BibEntry, doi: Optional[str] = None, slim:
69
77
  author = entry.fields.get("author", "Unknown Author")
70
78
  year = entry.fields.get("year", "n.d.")
71
79
 
72
- # Clean LaTeX commands from author names
80
+ # Clean LaTeX commands from author names and format according to specified style
73
81
  author = clean_latex_commands(author)
82
+ author = format_author_list(author, author_format)
74
83
 
75
84
  if slim:
76
85
  # Slim format: First author last name, Year
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rxiv-maker
3
- Version: 1.15.9
3
+ Version: 1.16.0
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
@@ -201,6 +201,7 @@ rxiv pdf
201
201
  - BibTeX integration with `[@citation]` syntax
202
202
  - Automatic bibliography generation
203
203
  - **Multiple citation styles**: Choose between numbered `[1, 2]` or author-date `(Smith, 2024)` citations
204
+ - **Configurable author name formatting**: Format bibliography as `Smith, J.A.` or `Smith, John A.` or `John A. Smith`
204
205
  - **Inline DOI resolution**: Paste DOIs directly in text `10.1038/...` and auto-convert to citations
205
206
  - CrossRef/DataCite DOI validation and metadata fetching
206
207
 
@@ -1,5 +1,5 @@
1
1
  rxiv_maker/__init__.py,sha256=p04JYC5ZhP6dLXkoWVlKNyiRvsDE1a4C88f9q4xO3tA,3268
2
- rxiv_maker/__version__.py,sha256=Jie3UBhYYz9xfwQlIZS6rCw1H9FHwp9v16l4VwSxKVc,51
2
+ rxiv_maker/__version__.py,sha256=0b3_5IT4Rl1KTRqfSivXOu2oAnVS0q_ptEV7xe5L9Yo,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
@@ -40,10 +40,10 @@ rxiv_maker/cli/framework/content_commands.py,sha256=RilxKeG2c1m2fu0CtWAvP3cGh11D
40
40
  rxiv_maker/cli/framework/decorators.py,sha256=fh085e3k1CaLSMoZevt8hvgnEuejrf-mcNS-dwXoY_A,10365
41
41
  rxiv_maker/cli/framework/utility_commands.py,sha256=_P_KwjlyiNS-vVboU-DqkHynGPqzXyoZRWGmLMLTnOs,26214
42
42
  rxiv_maker/cli/framework/workflow_commands.py,sha256=GbK1Wv4cTQfCiOyukn_R3q3IUqKvLk8MMK1vOXW3PaE,30556
43
- rxiv_maker/config/defaults.py,sha256=UYktZW3fTx9gs7uoP419nePYN-gXddAw1QMZCbZD9Mk,2891
44
- rxiv_maker/config/validator.py,sha256=TOp5nI_ma-eEmv2LNWB40IRXhBXHE43zfIL3g5GIJIY,38083
43
+ rxiv_maker/config/defaults.py,sha256=vHyLGVxe5-z9TLxu5f6NhquPvqQkER_KZv_j1I4_dHQ,3055
44
+ rxiv_maker/config/validator.py,sha256=9XDPfo_YgasGt6NLkl6HIhaGh1fr6XsFNiXU2DSsivw,38299
45
45
  rxiv_maker/converters/__init__.py,sha256=d7WGsRwWqRQWO117IkKDP0Ap0ERiK0N2-dXHInye3_A,685
46
- rxiv_maker/converters/citation_processor.py,sha256=1wYca1sZ8-Aw6ajS_N4d1BGoQmzg5agkfej9BOOHW5Q,8198
46
+ rxiv_maker/converters/citation_processor.py,sha256=Y4JpSvoqYU4GnqYC5bnB0Zn0OMTAR_FG3G8hWkgQj0o,8917
47
47
  rxiv_maker/converters/code_processor.py,sha256=ZFkJsqJ4nYUiDGtLeV7yWgOWUZNUXIFaBxZOPq88UrU,9324
48
48
  rxiv_maker/converters/comment_processor.py,sha256=Tlem4btYqMmfRf5AM5s6ZbB_pZPvkFpACz8XsLNVl50,7845
49
49
  rxiv_maker/converters/custom_command_processor.py,sha256=89wkIKP2It89qhXPBxiK-ib3Wwb5bKYsVf8E6BBRTlk,23467
@@ -79,25 +79,25 @@ rxiv_maker/core/cache/doi_cache.py,sha256=lw_ouHlfKNzg4zDMAOEUDhsFLfm88rWcPYBVyj
79
79
  rxiv_maker/core/cache/secure_cache_utils.py,sha256=EejPWvxw_mUPqO0TRBHYYTsLXWZEUH1qykEfBwgpkcc,18000
80
80
  rxiv_maker/core/managers/__init__.py,sha256=sh4ZuZH4YrAu4XTiN9ky1-tQQASKiSTY0udJJAzDRcU,950
81
81
  rxiv_maker/core/managers/cache_manager.py,sha256=8btUaRDYPOrUynHWBMz7RVeS9xcpUoyhemFNUi8NqpQ,21893
82
- rxiv_maker/core/managers/config_manager.py,sha256=dqruhoi3r_0eyV65LD6RZRY4afamqKvBzqoUQfUcYeE,18739
82
+ rxiv_maker/core/managers/config_manager.py,sha256=DdM-3_mfrjHFkPQ2vOmpQnXaoSJU-ianVwUvCEIwZss,18829
83
83
  rxiv_maker/core/managers/dependency_manager.py,sha256=DwVRcvk4JcZGzYc-K6ue7aGfw8fSnne50pPI_8Arlzw,25800
84
84
  rxiv_maker/core/managers/execution_manager.py,sha256=cEDS0KyWBHf_N74Fc8MC4VRXxmEcaz_DJtyWO5-o628,29585
85
85
  rxiv_maker/core/managers/file_manager.py,sha256=SVRnP1JQoGCAms3E7iSpOp_RG60P36Qk9HGAmJDaFvE,18641
86
86
  rxiv_maker/core/managers/install_manager.py,sha256=8HChOfbm5-uXsksKMDqmgrYNMJJtyDoL2RDNpbFwhwc,15533
87
87
  rxiv_maker/core/managers/state_manager.py,sha256=vqcg5aiAh9NvUJDiV7wTgU2EIGS-qAgZ50n4Xcuo49A,17713
88
- rxiv_maker/core/managers/unified_config_manager.py,sha256=qOn6cQ8UE8Th4GIq1Bmp4fqpwDUgJ2DDwUA2D__u-ug,12529
88
+ rxiv_maker/core/managers/unified_config_manager.py,sha256=fqoL9-qwixdBOw4kZ3iFZ06nG9nxkUl-Ckc8hxYwxmo,12587
89
89
  rxiv_maker/core/managers/validation_manager.py,sha256=wqmYJUybgAX5dJRe6S9lG7tShLWjYOmARIhR0xygHVM,29948
90
90
  rxiv_maker/core/managers/workflow_manager.py,sha256=WJehwdVZpaWgjk0SbvRb5AdHIK_b5FQTKFF12bv90HY,27082
91
91
  rxiv_maker/data/tips.yaml,sha256=PZvLH4L-keJko1BOvnP_aQpvNVOQ20t6OcPLF1vTWzg,4739
92
92
  rxiv_maker/engines/__init__.py,sha256=FiVpmWADcXzP2w_tQk9E4KEABGtsoXniPE7TFk-mwkY,1075
93
93
  rxiv_maker/engines/operations/__init__.py,sha256=m0es7aH_qVqAGk6CLS6SMo3ZNL2F_gLrM_wLY1shDR4,1292
94
94
  rxiv_maker/engines/operations/add_bibliography.py,sha256=eIYbZRmRFHPm09svhnrsgIjjzt3Imc1U3h4eeEj-D1M,20511
95
- rxiv_maker/engines/operations/build_manager.py,sha256=jS1KPMNkIWcoevos3PMBq7izjo5D3lLdrfYqLOk05y4,43733
95
+ rxiv_maker/engines/operations/build_manager.py,sha256=TAX4-r8HjraAzzvQuIt0CNlvWLo5pF40I7BRuIxZZRc,45022
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=f9IjZD2_nK-kD8RLRugRuXMslDgx4857f3AqLGRJFrY,11084
99
99
  rxiv_maker/engines/operations/generate_figures.py,sha256=3oIuS0wryO9WpPZ3UD2qm0YscNidTOEiO4_Jd6r3SmY,32842
100
- rxiv_maker/engines/operations/generate_preprint.py,sha256=EtWSL1-LGm8N61_CLABSswgvVw61haEx8m4727pdzgM,2650
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
103
103
  rxiv_maker/engines/operations/track_changes.py,sha256=XMU1x31nwLIwbi1lwYJn333O7Tx9llQfBv8yY9O_Dww,24734
@@ -106,8 +106,8 @@ rxiv_maker/engines/operations/validate_pdf.py,sha256=qyrtL752Uap3i6ntQheY570soVj
106
106
  rxiv_maker/exporters/__init__.py,sha256=NcTD1SDb8tTgsHhCS1A7TVEZncyWbDRTa6sJIdLqcsE,350
107
107
  rxiv_maker/exporters/docx_citation_mapper.py,sha256=Qp3IEqrR6lQGQPQ4JeGdOCWeg97XBbVCKdX1gtX9XfY,4584
108
108
  rxiv_maker/exporters/docx_content_processor.py,sha256=3srXfq4lC_FJpTAfu-WB930WheSm6rCy-vNUtFlsflY,23878
109
- rxiv_maker/exporters/docx_exporter.py,sha256=v1XSKfXHUOHxj2tEv3IFBVv9mN1p8RIIrkqHDs9Unxo,14792
110
- rxiv_maker/exporters/docx_writer.py,sha256=LAlhwZXjN1xDMa-ocX4FpHYp-IX-pn9qjPpd4t_IMaM,41284
109
+ rxiv_maker/exporters/docx_exporter.py,sha256=i2G_AF-Co4AOD6ZdRzDZ7VgcJLdCmxu-Fn3SykvBywQ,15152
110
+ rxiv_maker/exporters/docx_writer.py,sha256=qI_0JklqAxF_TUHxO3wPopZpJ44O2qYjMPOptrnU0XM,41915
111
111
  rxiv_maker/install/__init__.py,sha256=kAB6P-12IKg_K1MQ-uzeC5IR11O2cNxj0t_2JMhooZs,590
112
112
  rxiv_maker/install/dependency_handlers/__init__.py,sha256=NN9dP1usXpYgLpSw0uEnJ6ugX2zefihVjdyDdm1k-cE,231
113
113
  rxiv_maker/install/dependency_handlers/latex.py,sha256=xopSJxYkg3D63rH7RoVLN-Ykl87AZqhlUrrG3m6LoWo,3304
@@ -127,7 +127,7 @@ rxiv_maker/manuscript_utils/figure_utils.py,sha256=FsNtMiA1IOHeA6gQsENmAWLZSAvPp
127
127
  rxiv_maker/processors/__init__.py,sha256=8UmaeFkbPfcwAL5MnhN2DcOA2k8iuJu0B5dDA6_pmnA,720
128
128
  rxiv_maker/processors/author_processor.py,sha256=jC4qZ9M_GADelkp1v8pk46ESUf4DNraXLfMYGhn_7ZM,10016
129
129
  rxiv_maker/processors/markdown_preprocessor.py,sha256=Ez_2lhSFEdJY0CS2SKJJESRfnmSdMq4s0jP2nlFSKN8,11228
130
- rxiv_maker/processors/template_processor.py,sha256=xOgPmHOtkPsbBf9Dh4xYELYfz_dqZM9ezbg_dy4_LTw,28834
130
+ rxiv_maker/processors/template_processor.py,sha256=mX8L19Evo9c8DvcdvSdtFHGaGGrQseVsF1CKfj_WYow,30024
131
131
  rxiv_maker/processors/yaml_processor.py,sha256=SSXHpwY1Cw81IDN4x40UFtosz-T9l29oh-CZpusmlYo,11499
132
132
  rxiv_maker/scripts/__init__.py,sha256=nKsDmj6UAc90uC6sKd-V6O80OMYjAl4Uha9zF_6ayX0,154
133
133
  rxiv_maker/scripts/custom_doc_generator.py,sha256=pMkdTI8a4qG8Z3gFUJtPwRuu2i0ioW3pKfs22es0KCk,6311
@@ -136,20 +136,22 @@ rxiv_maker/services/__init__.py,sha256=8PtucdpuVrO6OmzvW4AyaWXKlQpdErIDeCLlXpKEM
136
136
  rxiv_maker/services/base.py,sha256=iCfX8yGyCFTNIQf4tPblQzlc4-LFMv-H7p9I3Qc2LT0,5484
137
137
  rxiv_maker/services/build_service.py,sha256=0irCnWoYw-0tagwNiNxAwDxlqgS_qpOvSCpc5EJ85I0,2120
138
138
  rxiv_maker/services/configuration_service.py,sha256=MCKWFLkMA9hi7wQE5xPpH6KxO6QiWpogrl1ivFRL00o,3899
139
- rxiv_maker/services/manuscript_service.py,sha256=ZrTnZBcFY-iIxk-2pw3JiVf0mhSMo7BuiFzJHLdga4k,11292
139
+ rxiv_maker/services/manuscript_service.py,sha256=wev9gBTbKbcypQMmoKvR7gh2Ha1nRuvRdkrFJcmr3Io,11304
140
140
  rxiv_maker/services/publication_service.py,sha256=0p8yQ1jrY3RHwCkzTEl_sAbWYTafRk-NaTqHWZW3_dg,1740
141
141
  rxiv_maker/services/validation_service.py,sha256=eWg14NqJu6LzyJBgeXkTaVZAlX4wYFX8ZEvSR5hMx7U,14619
142
142
  rxiv_maker/templates/__init__.py,sha256=UTet1pYPkPdgvrLw-wwaY-PAgdjGJasAi_hdyIh0J8s,562
143
143
  rxiv_maker/templates/manager.py,sha256=HlI7Qb52866Okf4k1aRh0fUy9heOSNGjMQJtrCdL3Xk,6131
144
- rxiv_maker/templates/registry.py,sha256=NA88HsCpwDmsNVilp_dAcQOQtsQ96GyIK1FGfH6ScVo,12749
144
+ rxiv_maker/templates/registry.py,sha256=L8M7M9Yhf2P0SQEghmCXufWfsGQhaW1TYjdkUw-wvDw,12993
145
145
  rxiv_maker/tex/python_execution_section.tex,sha256=pHz6NGfZN4ViBo6rInUO5FAuk81sV_Ppqszrvl00w_4,2218
146
146
  rxiv_maker/utils/__init__.py,sha256=4ya5VR8jqRqUChlnUeMeeetOuWV-gIvjPwcE1u_1OnI,1540
147
+ rxiv_maker/utils/author_name_formatter.py,sha256=UjvarbyQm89EUIYqckygx3g37o-EcNyvipBtY8GJDxs,10222
147
148
  rxiv_maker/utils/bibliography_checksum.py,sha256=Jh4VILSpGQ5KJ9UBCUb7oFy6lZ9_ncXD87vEXxw5jbY,10270
148
149
  rxiv_maker/utils/bibliography_parser.py,sha256=WZIQoEpVwdbLmbkw9FdkVgoLE5GX7itqnzPnEEb_fFU,6846
150
+ rxiv_maker/utils/bst_generator.py,sha256=EHNhMT0Tq6YXUh2TfpGwOzYaarAyjEsAeeJ4H4np7Og,4313
149
151
  rxiv_maker/utils/changelog_parser.py,sha256=WCDp9Iy6H6_3nC6FB7RLt6i00zuCyvU17sCU4e3pqCY,11954
150
152
  rxiv_maker/utils/citation_utils.py,sha256=spIgVxPAN6jPvoG-eOE00rVX_buUGKnUjP1Fhz31sl4,5134
151
153
  rxiv_maker/utils/dependency_checker.py,sha256=EdyIvk-W_bhC1DJCpFw5ePhjEU74C9j7RYMm06unBMA,14366
152
- rxiv_maker/utils/docx_helpers.py,sha256=QUKTyhhVakovoEOhDMpC8j-H2InTkjuVhNHciKpBgUY,12519
154
+ rxiv_maker/utils/docx_helpers.py,sha256=jhxkrU80JjDJXDE3gNfsJt9PqLyByYDfymY_Jm9ZqoI,12860
153
155
  rxiv_maker/utils/doi_resolver.py,sha256=8_oy5cTtklm1GCKXpn509yqYsu4P5gYbMjtfQ8dRgFA,10253
154
156
  rxiv_maker/utils/email_encoder.py,sha256=QMD5JbGNu68gD8SBdGHfNY8uCgbMzEcmzE1TCYDMgWY,5139
155
157
  rxiv_maker/utils/figure_checksum.py,sha256=PWgh2QAErNnnQCV-t-COACQXKICUaggAAIxhgHLCGNM,10748
@@ -185,8 +187,8 @@ rxiv_maker/validators/doi/metadata_comparator.py,sha256=euqHhKP5sHQAdZbdoAahUn6Y
185
187
  rxiv_maker/tex/template.tex,sha256=zrJ3aFfu8j9zkg1l375eE9w-j42P3rz16wMD3dSgi1I,1354
186
188
  rxiv_maker/tex/style/rxiv_maker_style.bst,sha256=jbVqrJgAm6F88cow5vtZuPBwwmlcYykclTm8RvZIo6Y,24281
187
189
  rxiv_maker/tex/style/rxiv_maker_style.cls,sha256=F2qtnS9mI6SwOIaVH76egXZkB2_GzbH4gCTG_ZcfCDQ,24253
188
- rxiv_maker-1.15.9.dist-info/METADATA,sha256=pRVBi_qnjSNaLZl3Lz7D0RsNJ4gaMp3SnFxp-4e-TA8,19656
189
- rxiv_maker-1.15.9.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
190
- rxiv_maker-1.15.9.dist-info/entry_points.txt,sha256=ghCN0hI9A1GlG7QY5F6E-xYPflA8CyS4B6bTQ1YLop0,97
191
- rxiv_maker-1.15.9.dist-info/licenses/LICENSE,sha256=GSZFoPIhWDNJEtSHTQ5dnELN38zFwRiQO2antBezGQk,1093
192
- rxiv_maker-1.15.9.dist-info/RECORD,,
190
+ rxiv_maker-1.16.0.dist-info/METADATA,sha256=OM17giwfgx08ZNIEhfHvyQDdT3WqQ5yI3Z1ntj3XLtQ,19775
191
+ rxiv_maker-1.16.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
192
+ rxiv_maker-1.16.0.dist-info/entry_points.txt,sha256=ghCN0hI9A1GlG7QY5F6E-xYPflA8CyS4B6bTQ1YLop0,97
193
+ rxiv_maker-1.16.0.dist-info/licenses/LICENSE,sha256=GSZFoPIhWDNJEtSHTQ5dnELN38zFwRiQO2antBezGQk,1093
194
+ rxiv_maker-1.16.0.dist-info/RECORD,,