Parse a single entry into a ChangeEntry object.
(self, entry_text: str, change_type: str)
| 557 | return entries |
| 558 | |
| 559 | def _parse_single_entry(self, entry_text: str, change_type: str) -> Optional[ChangeEntry]: |
| 560 | """Parse a single entry into a ChangeEntry object.""" |
| 561 | # Extract authors |
| 562 | description, authors = AuthorParser.parse_authors(entry_text) |
| 563 | |
| 564 | # Extract issues/PRs |
| 565 | links = IssueExtractor.extract_issues(description) |
| 566 | |
| 567 | # Remove all issue/PR IDs from the description text |
| 568 | # Handle multiple formats of issue references at the beginning: |
| 569 | |
| 570 | # 1. Remove leading issues with mixed projects: "LUCENE-3323,SOLR-2659,LUCENE-3329,SOLR-2666: description" |
| 571 | description = re.sub(r'^(?:(?:SOLR|LUCENE|INFRA)-\d+(?:\s*[,:]?\s*)?)+:\s*', '', description) |
| 572 | |
| 573 | # 2. Remove SOLR-specific issues: "SOLR-12345: description" or "SOLR-12345, SOLR-12346: description" |
| 574 | description = re.sub(r'^(?:SOLR-\d+(?:\s*,\s*SOLR-\d+)*\s*[:,]?\s*)+', '', description) |
| 575 | |
| 576 | # 3. Remove PR references: "PR#123: description" or "GITHUB#456: description" |
| 577 | description = re.sub(r'^(?:(?:PR|GITHUB)#\d+(?:\s*,\s*(?:PR|GITHUB)#\d+)*\s*[:,]?\s*)+', '', description) |
| 578 | |
| 579 | # 4. Remove parenthesized issue lists at start: "(SOLR-123, SOLR-456)" |
| 580 | description = re.sub(r'^\s*\((?:SOLR-\d+(?:\s*,\s*)?)+\)\s*', '', description) |
| 581 | description = re.sub(r'^\s*\((?:(?:SOLR|LUCENE|INFRA)-\d+(?:\s*,\s*)?)+\)\s*', '', description) |
| 582 | |
| 583 | # 5. Remove any remaining leading issue references |
| 584 | description = re.sub(r'^[\s,;]*(?:SOLR-\d+|LUCENE-\d+|INFRA-\d+|PR#\d+|GITHUB#\d+)[\s,:;]*', '', description) |
| 585 | while re.match(r'^[\s,;]*(?:SOLR-\d+|LUCENE-\d+|INFRA-\d+|PR#\d+|GITHUB#\d+)', description): |
| 586 | description = re.sub(r'^[\s,;]*(?:SOLR-\d+|LUCENE-\d+|INFRA-\d+|PR#\d+|GITHUB#\d+)[\s,:;]*', '', description) |
| 587 | |
| 588 | description = description.strip() |
| 589 | |
| 590 | # Normalize whitespace: collapse multiple newlines/spaces into single spaces |
| 591 | # This joins multi-line formatted text into a single coherent paragraph |
| 592 | description = re.sub(r'\s+', ' ', description) |
| 593 | |
| 594 | # Escape HTML angle brackets to prevent markdown rendering issues |
| 595 | # Only escape < and > to avoid breaking markdown links and quotes |
| 596 | description = description.replace('<', '<').replace('>', '>') |
| 597 | |
| 598 | if not description: |
| 599 | return None |
| 600 | |
| 601 | return ChangeEntry( |
| 602 | title=description, |
| 603 | change_type=change_type, |
| 604 | authors=authors, |
| 605 | links=links, |
| 606 | ) |
| 607 | |
| 608 | |
| 609 | class YamlWriter: |
no test coverage detected